From 7b3ec6ae1eea38c8c484c4f0fda5bc091a455bf4 Mon Sep 17 00:00:00 2001 From: dblock Date: Fri, 19 Jun 2015 13:07:31 -0400 Subject: [PATCH] Initial implementing, using Dentaku. --- .gitignore | 1 + .rspec | 2 + .rubocop.yml | 6 + .rubocop_todo.yml | 45 +++++++ .travis.yml | 3 + CHANGELOG.md | 3 + CONTRIBUTING.md | 125 ++++++++++++++++++ DEPLOYMENT.md | 25 ++++ Gemfile | 23 ++++ Gemfile.lock | 105 +++++++++++++++ LICENSE.md | 22 +++ Procfile | 1 + README.md | 52 ++++++++ Rakefile | 18 +++ app.json | 6 + app.rb | 11 ++ app/slack-mathbot.rb | 18 +++ app/slack-mathbot/about.rb | 7 + app/slack-mathbot/app.rb | 83 ++++++++++++ app/slack-mathbot/commands.rb | 6 + app/slack-mathbot/commands/about.rb | 9 ++ app/slack-mathbot/commands/base.rb | 16 +++ app/slack-mathbot/commands/calculate.rb | 10 ++ app/slack-mathbot/commands/help.rb | 9 ++ app/slack-mathbot/commands/hi.rb | 9 ++ app/slack-mathbot/commands/unknown.rb | 9 ++ app/slack-mathbot/config.rb | 13 ++ app/slack-mathbot/hooks.rb | 3 + app/slack-mathbot/hooks/base.rb | 10 ++ app/slack-mathbot/hooks/hello.rb | 11 ++ app/slack-mathbot/hooks/message.rb | 33 +++++ app/slack-mathbot/version.rb | 3 + app/slack_mathbot.rb | 1 + config.ru | 7 + config/application.rb | 14 ++ config/boot.rb | 5 + config/environment.rb | 3 + config/initializers/slack/request.rb | 13 ++ screenshots/register-bot.png | Bin 0 -> 57962 bytes screenshots/two-plus-two.gif | Bin 0 -> 18537 bytes spec/fixtures/slack/auth_test.yml | 103 +++++++++++++++ spec/fixtures/slack/user_info.yml | 59 +++++++++ spec/slack-mathbot/app_spec.rb | 32 +++++ spec/slack-mathbot/commands/about_spec.rb | 10 ++ spec/slack-mathbot/commands/calculate_spec.rb | 16 +++ spec/slack-mathbot/commands/help_spec.rb | 7 + spec/slack-mathbot/commands/hi_spec.rb | 7 + spec/slack-mathbot/commands/unknown_spec.rb | 11 ++ spec/slack-mathbot/version_spec.rb | 7 + spec/spec_helper.rb | 12 ++ .../slack-mathbot/respond_with_error.rb | 30 +++++ .../respond_with_slack_message.rb | 19 +++ spec/support/slack_api_key.rb | 5 + spec/support/slack_calculator.rb | 4 + spec/support/vcr.rb | 8 ++ 55 files changed, 1070 insertions(+) create mode 100644 .gitignore create mode 100644 .rspec create mode 100644 .rubocop.yml create mode 100644 .rubocop_todo.yml create mode 100644 .travis.yml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 DEPLOYMENT.md create mode 100644 Gemfile create mode 100644 Gemfile.lock create mode 100644 LICENSE.md create mode 100644 Procfile create mode 100644 README.md create mode 100644 Rakefile create mode 100644 app.json create mode 100644 app.rb create mode 100644 app/slack-mathbot.rb create mode 100644 app/slack-mathbot/about.rb create mode 100644 app/slack-mathbot/app.rb create mode 100644 app/slack-mathbot/commands.rb create mode 100644 app/slack-mathbot/commands/about.rb create mode 100644 app/slack-mathbot/commands/base.rb create mode 100644 app/slack-mathbot/commands/calculate.rb create mode 100644 app/slack-mathbot/commands/help.rb create mode 100644 app/slack-mathbot/commands/hi.rb create mode 100644 app/slack-mathbot/commands/unknown.rb create mode 100644 app/slack-mathbot/config.rb create mode 100644 app/slack-mathbot/hooks.rb create mode 100644 app/slack-mathbot/hooks/base.rb create mode 100644 app/slack-mathbot/hooks/hello.rb create mode 100644 app/slack-mathbot/hooks/message.rb create mode 100644 app/slack-mathbot/version.rb create mode 100644 app/slack_mathbot.rb create mode 100644 config.ru create mode 100644 config/application.rb create mode 100644 config/boot.rb create mode 100644 config/environment.rb create mode 100644 config/initializers/slack/request.rb create mode 100644 screenshots/register-bot.png create mode 100644 screenshots/two-plus-two.gif create mode 100644 spec/fixtures/slack/auth_test.yml create mode 100644 spec/fixtures/slack/user_info.yml create mode 100644 spec/slack-mathbot/app_spec.rb create mode 100644 spec/slack-mathbot/commands/about_spec.rb create mode 100644 spec/slack-mathbot/commands/calculate_spec.rb create mode 100644 spec/slack-mathbot/commands/help_spec.rb create mode 100644 spec/slack-mathbot/commands/hi_spec.rb create mode 100644 spec/slack-mathbot/commands/unknown_spec.rb create mode 100644 spec/slack-mathbot/version_spec.rb create mode 100644 spec/spec_helper.rb create mode 100644 spec/support/slack-mathbot/respond_with_error.rb create mode 100644 spec/support/slack-mathbot/respond_with_slack_message.rb create mode 100644 spec/support/slack_api_key.rb create mode 100644 spec/support/slack_calculator.rb create mode 100644 spec/support/vcr.rb diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..8c18f1a --- /dev/null +++ b/.rspec @@ -0,0 +1,2 @@ +--format documentation +--color diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..bdd4025 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,6 @@ +AllCops: + Exclude: + - vendor/**/* + - bin/**/* + +inherit_from: .rubocop_todo.yml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml new file mode 100644 index 0000000..30050ca --- /dev/null +++ b/.rubocop_todo.yml @@ -0,0 +1,45 @@ +# This configuration was generated by `rubocop --auto-gen-config` +# on 2015-06-19 13:09:29 -0400 using RuboCop version 0.31.0. +# The point is for the user to remove these configuration records +# one by one as the offenses are removed from the code base. +# Note that changes in the inspected code, or installation of new +# versions of RuboCop, may require this file to be generated again. + +# Offense count: 1 +Lint/HandleExceptions: + Enabled: false + +# Offense count: 2 +Metrics/AbcSize: + Max: 21 + +# Offense count: 1 +Metrics/CyclomaticComplexity: + Max: 7 + +# Offense count: 19 +# Configuration parameters: AllowURI, URISchemes. +Metrics/LineLength: + Max: 130 + +# Offense count: 1 +# Configuration parameters: CountComments. +Metrics/MethodLength: + Max: 17 + +# Offense count: 16 +Style/Documentation: + Enabled: false + +# Offense count: 1 +# Configuration parameters: Exclude. +Style/FileName: + Enabled: false + +# Offense count: 1 +Style/ModuleFunction: + Enabled: false + +# Offense count: 1 +Style/RescueModifier: + Enabled: false diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..e6ba548 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +rvm: + - 2.1.6 + diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b018b31 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +### 0.1.0 (Next) + +* Initial public release - [@dblock](https://github.com/dblock). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..f817934 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,125 @@ +# Contributing to SlackMathbot + +This project is work of [many contributors](https://github.com/dblock/slack-mathbot/graphs/contributors). + +You're encouraged to submit [pull requests](https://github.com/dblock/slack-mathbot/pulls), [propose features and discuss issues](https://github.com/dblock/slack-mathbot/issues). + +In the examples below, substitute your Github username for `contributor` in URLs. + +## Fork the Project + +Fork the [project on Github](https://github.com/dblock/slack-mathbot) and check out your copy. + +``` +git clone https://github.com/contributor/slack-mathbot.git +cd slack-mathbot +git remote add upstream https://github.com/dblock/slack-mathbot.git +``` + +## Create a Topic Branch + +Make sure your fork is up-to-date and create a topic branch for your feature or bug fix. + +``` +git checkout master +git pull upstream master +git checkout -b my-feature-branch +``` + +## Bundle Install and Test + +Ensure that you can build the project and run tests. + +``` +bundle install +bundle exec rake +``` + +## Write Tests + +Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build. +Add to [spec](spec). + +We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix. + +## Write Code + +Implement your feature or bug fix. + +Ruby style is enforced with [Rubocop](https://github.com/bbatsov/rubocop). +Run `bundle exec rubocop` and fix any style issues highlighted. + +Make sure that `bundle exec rake` completes without errors. + +## Write Documentation + +Document any external behavior in the [README](README.md). + +## Update Changelog + +Add a line to [CHANGELOG](CHANGELOG.md) under *Next Release*. +Make it look like every other line, including your name and link to your Github account. + +## Commit Changes + +Make sure git knows your name and email address: + +``` +git config --global user.name "Your Name" +git config --global user.email "contributor@example.com" +``` + +Writing good commit logs is important. A commit log should describe what changed and why. + +``` +git add ... +git commit +``` + +## Push + +``` +git push origin my-feature-branch +``` + +## Make a Pull Request + +Go to https://github.com/contributor/slack-mathbot and select your feature branch. +Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days. + +## Rebase + +If you've been working on a change for a while, rebase with upstream/master. + +``` +git fetch upstream +git rebase upstream/master +git push origin my-feature-branch -f +``` + +## Update CHANGELOG Again + +Update the [CHANGELOG](CHANGELOG.md) with the pull request number. A typical entry looks as follows. + +``` +* [#123](https://github.com/dblock/slack-mathbot/pull/123): Reticulated splines - [@contributor](https://github.com/contributor). +``` + +Amend your previous commit and force push the changes. + +``` +git commit --amend +git push origin my-feature-branch -f +``` + +## Check on Your Pull Request + +Go back to your pull request after a few minutes and see whether it passed muster with Travis-CI. Everything should look green, otherwise fix issues and amend your commit as described above. + +## Be Patient + +It's likely that your change will not be merged and that the nitpicky maintainers will ask you to do more, or fix seemingly benign problems. Hang on there! + +## Thank You + +Please do know that we really appreciate and value your time and work. We love you, really. diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..02542db --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,25 @@ +## Installation + +Create a new Bot Integration under [services/new/bot](http://slack.com/services/new/bot). + +![](screenshots/register-bot.png) + +On the next screen, note the API token. + +## Deploy Slack-Gamebot + +[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy) + +### Environment + +#### SLACK_API_TOKEN + +Set SLACK_API_TOKEN from the Bot integration settings on Slack. + +``` +heroku config:add SLACK_API_TOKEN=... +``` + +### Heroku Idling + +Heroku free tier applications will idle. Use [UptimeRobot](http://uptimerobot.com) or similar to prevent your instance from sleeping or pay for a production dyno. diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..c419b7b --- /dev/null +++ b/Gemfile @@ -0,0 +1,23 @@ +source 'http://rubygems.org' + +ruby '2.1.6' + +gem 'hashie' +gem 'slack-api', '~> 1.1.6', require: 'slack' +gem 'puma' +gem 'sinatra' +gem 'activesupport' +gem 'dentaku' + +group :development, :test do + gem 'rake', '~> 10.4' + gem 'rubocop', '0.31.0' + gem 'foreman' +end + +group :test do + gem 'rspec', '~> 3.2' + gem 'rack-test', '~> 0.6.2' + gem 'vcr' + gem 'webmock' +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 0000000..425fe85 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,105 @@ +GEM + remote: http://rubygems.org/ + specs: + activesupport (4.2.1) + i18n (~> 0.7) + json (~> 1.7, >= 1.7.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + addressable (2.3.8) + ast (2.0.0) + astrolabe (1.3.0) + parser (>= 2.2.0.pre.3, < 3.0) + crack (0.4.2) + safe_yaml (~> 1.0.0) + dentaku (1.2.6) + diff-lcs (1.2.5) + eventmachine (1.0.7) + faraday (0.9.1) + multipart-post (>= 1.2, < 3) + faraday_middleware (0.9.1) + faraday (>= 0.7.4, < 0.10) + faye-websocket (0.9.2) + eventmachine (>= 0.12.0) + websocket-driver (>= 0.5.1) + foreman (0.78.0) + thor (~> 0.19.1) + hashie (3.4.1) + i18n (0.7.0) + json (1.8.2) + minitest (5.6.1) + multi_json (1.11.0) + multipart-post (2.0.0) + parser (2.3.0.pre.2) + ast (>= 1.1, < 3.0) + powerpack (0.1.1) + puma (2.11.0) + rack (>= 1.1, < 2.0) + rack (1.6.0) + rack-protection (1.5.3) + rack + rack-test (0.6.3) + rack (>= 1.0) + rainbow (2.0.0) + rake (10.4.2) + rspec (3.2.0) + rspec-core (~> 3.2.0) + rspec-expectations (~> 3.2.0) + rspec-mocks (~> 3.2.0) + rspec-core (3.2.3) + rspec-support (~> 3.2.0) + rspec-expectations (3.2.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.2.0) + rspec-mocks (3.2.1) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.2.0) + rspec-support (3.2.2) + rubocop (0.31.0) + astrolabe (~> 1.3) + parser (>= 2.2.2.1, < 3.0) + powerpack (~> 0.1) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.4) + ruby-progressbar (1.7.5) + safe_yaml (1.0.4) + sinatra (1.4.5) + rack (~> 1.4) + rack-protection (~> 1.4) + tilt (~> 1.3, >= 1.3.4) + slack-api (1.1.6) + faraday (>= 0.7, < 0.10) + faraday_middleware (~> 0.8) + faye-websocket (~> 0.9.2) + multi_json (~> 1.0, >= 1.0.3) + thor (0.19.1) + thread_safe (0.3.5) + tilt (1.4.1) + tzinfo (1.2.2) + thread_safe (~> 0.1) + vcr (2.9.3) + webmock (1.21.0) + addressable (>= 2.3.6) + crack (>= 0.3.2) + websocket-driver (0.5.4) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) + +PLATFORMS + ruby + +DEPENDENCIES + activesupport + dentaku + foreman + hashie + puma + rack-test (~> 0.6.2) + rake (~> 10.4) + rspec (~> 3.2) + rubocop (= 0.31.0) + sinatra + slack-api (~> 1.1.6) + vcr + webmock diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..81ae90d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2015 Daniel Doubrovkine, Artsy and Contributors + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..4fd3163 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: bundle exec puma -p $PORT diff --git a/README.md b/README.md new file mode 100644 index 0000000..44773b7 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +Slack-Gamebot +============= + +[![Build Status](https://travis-ci.org/dblock/slack-mathbot.png)](https://travis-ci.org/dblock/slack-mathbot) + +A math bot for Slack. + +![](screenshots/two-plus-two.gif) + +## Installation + +Create a new Bot Integration under [services/new/bot](http://slack.com/services/new/bot). + +![](screenshots/register-bot.png) + +On the next screen, note the API token. + +Run `SLACK_API_TOKEN= foreman start` + +## Production Deployment + +See [DEPLOYMENT](DEPLOYMENT.md). + +## Usage + +### Commands + +#### mathbot calculate [expression] + +Calculates an expression, currently just basic math. See [Dentaku](https://github.com/rubysolo/dentaku) for what's supported. + +#### mathbot + +Shows MathBot version and links. + +#### mathbot hi + +Politely says 'hi' back. + +#### mathbot help + +Get help. + +## Contributing + +See [CONTRIBUTING](CONTRIBUTING.md). + +## Copyright and License + +Copyright (c) 2015, Daniel Doubrovkine, Artsy and [Contributors](CHANGELOG.md). + +This project is licensed under the [MIT License](LICENSE.md). diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..32f34c9 --- /dev/null +++ b/Rakefile @@ -0,0 +1,18 @@ +require 'rubygems' +require 'bundler' + +Bundler.setup :default, :development + +unless ENV['RACK_ENV'] == 'production' + require 'rspec/core' + require 'rspec/core/rake_task' + + RSpec::Core::RakeTask.new(:spec) do |spec| + spec.pattern = FileList['spec/**/*_spec.rb'] + end + + require 'rubocop/rake_task' + RuboCop::RakeTask.new + + task default: [:rubocop, :spec] +end diff --git a/app.json b/app.json new file mode 100644 index 0000000..26085b2 --- /dev/null +++ b/app.json @@ -0,0 +1,6 @@ +{ + "name": "Math bot for Slack", + "description": "Slack integration with math.", + "respository": "https://github.com/dblock/slack-mathbot", + "keywords": ["slack", "integration", "math"], +} diff --git a/app.rb b/app.rb new file mode 100644 index 0000000..62238ee --- /dev/null +++ b/app.rb @@ -0,0 +1,11 @@ +require File.expand_path('../config/environment', __FILE__) + +require 'sinatra/base' + +module SlackMathbot + class Web < Sinatra::Base + get '/' do + 'Math is good for you.' + end + end +end diff --git a/app/slack-mathbot.rb b/app/slack-mathbot.rb new file mode 100644 index 0000000..0a6765b --- /dev/null +++ b/app/slack-mathbot.rb @@ -0,0 +1,18 @@ +require 'slack-mathbot/version' +require 'slack-mathbot/about' +require 'slack-mathbot/config' +require 'slack-mathbot/hooks' +require 'slack-mathbot/commands' +require 'slack-mathbot/app' + +module SlackMathbot + class << self + def configure + block_given? ? yield(Config) : Config + end + + def config + Config + end + end +end diff --git a/app/slack-mathbot/about.rb b/app/slack-mathbot/about.rb new file mode 100644 index 0000000..9401cdb --- /dev/null +++ b/app/slack-mathbot/about.rb @@ -0,0 +1,7 @@ +module SlackMathbot + ABOUT = <<-ABOUT + #{SlackMathbot::VERSION} + https://github.com/dblock/slack-mathbot + https://twitter.com/dblockdotorg + ABOUT +end diff --git a/app/slack-mathbot/app.rb b/app/slack-mathbot/app.rb new file mode 100644 index 0000000..87f9e71 --- /dev/null +++ b/app/slack-mathbot/app.rb @@ -0,0 +1,83 @@ +module SlackMathbot + class App + cattr_accessor :hooks + + include SlackMathbot::Hooks::Hello + include SlackMathbot::Hooks::Message + + def initialize + SlackMathbot.configure do |config| + config.token = ENV['SLACK_API_TOKEN'] || fail("Missing ENV['SLACK_API_TOKEN'].") + end + Slack.configure do |config| + config.token = SlackMathbot.config.token + end + end + + def config + SlackMathbot.config + end + + def self.instance + @instance ||= SlackMathbot::App.new + end + + def run + auth! + start! + end + + def stop! + client.stop + end + + private + + def logger + @logger ||= begin + $stdout.sync = true + Logger.new(STDOUT) + end + end + + def start! + loop do + client.start + @client = nil + end + end + + def client + @client ||= begin + client = Slack.realtime + hooks.each do |hook| + client.on hook do |data| + begin + send hook, data + rescue StandardError => e + logger.error e + begin + Slack.chat_postMessage(channel: data['channel'], text: e.message) if data.key?('channel') + rescue + # ignore + end + end + end + end + client + end + end + + def auth! + auth = Slack.auth_test + SlackMathbot.configure do |config| + config.url = auth['url'] + config.team = auth['team'] + config.user = auth['user'] + config.team_id = auth['team_id'] + config.user_id = auth['user_id'] + end + logger.info "Welcome '#{SlackMathbot.config.user}' to the '#{SlackMathbot.config.team}' team at #{SlackMathbot.config.url}." + end + end +end diff --git a/app/slack-mathbot/commands.rb b/app/slack-mathbot/commands.rb new file mode 100644 index 0000000..ab8311a --- /dev/null +++ b/app/slack-mathbot/commands.rb @@ -0,0 +1,6 @@ +require 'slack-mathbot/commands/base' +require 'slack-mathbot/commands/calculate' +require 'slack-mathbot/commands/about' +require 'slack-mathbot/commands/help' +require 'slack-mathbot/commands/hi' +require 'slack-mathbot/commands/unknown' diff --git a/app/slack-mathbot/commands/about.rb b/app/slack-mathbot/commands/about.rb new file mode 100644 index 0000000..8dd9328 --- /dev/null +++ b/app/slack-mathbot/commands/about.rb @@ -0,0 +1,9 @@ +module SlackMathbot + module Commands + class Default < Base + def self.call(data, _command, _arguments) + send_message data.channel, SlackMathbot::ABOUT + end + end + end +end diff --git a/app/slack-mathbot/commands/base.rb b/app/slack-mathbot/commands/base.rb new file mode 100644 index 0000000..f89caa1 --- /dev/null +++ b/app/slack-mathbot/commands/base.rb @@ -0,0 +1,16 @@ +module SlackMathbot + module Commands + class Base + def self.send_message(channel, text) + Slack.chat_postMessage(channel: channel, text: text) + end + + def self.logger + @logger ||= begin + $stdout.sync = true + Logger.new(STDOUT) + end + end + end + end +end diff --git a/app/slack-mathbot/commands/calculate.rb b/app/slack-mathbot/commands/calculate.rb new file mode 100644 index 0000000..a554d61 --- /dev/null +++ b/app/slack-mathbot/commands/calculate.rb @@ -0,0 +1,10 @@ +module SlackMathbot + module Commands + class Calculate < Base + def self.call(data, _command, arguments) + result = Dentaku::Calculator.new.evaluate(arguments.join) || 'Got nothing.' + send_message data.channel, result.to_s + end + end + end +end diff --git a/app/slack-mathbot/commands/help.rb b/app/slack-mathbot/commands/help.rb new file mode 100644 index 0000000..b9de666 --- /dev/null +++ b/app/slack-mathbot/commands/help.rb @@ -0,0 +1,9 @@ +module SlackMathbot + module Commands + class Help < Base + def self.call(data, _command, _arguments) + send_message data.channel, 'See https://github.com/dblock/slack-mathbot, please.' + end + end + end +end diff --git a/app/slack-mathbot/commands/hi.rb b/app/slack-mathbot/commands/hi.rb new file mode 100644 index 0000000..85711d3 --- /dev/null +++ b/app/slack-mathbot/commands/hi.rb @@ -0,0 +1,9 @@ +module SlackMathbot + module Commands + class Hi < Base + def self.call(data, _command, _arguments) + send_message data.channel, "Hi <@#{data.user}>!" + end + end + end +end diff --git a/app/slack-mathbot/commands/unknown.rb b/app/slack-mathbot/commands/unknown.rb new file mode 100644 index 0000000..ef43ec5 --- /dev/null +++ b/app/slack-mathbot/commands/unknown.rb @@ -0,0 +1,9 @@ +module SlackMathbot + module Commands + class Unknown < Base + def self.call(data, _command, _arguments) + send_message data.channel, "Sorry <@#{data.user}>, I don't understand that command!" + end + end + end +end diff --git a/app/slack-mathbot/config.rb b/app/slack-mathbot/config.rb new file mode 100644 index 0000000..8762192 --- /dev/null +++ b/app/slack-mathbot/config.rb @@ -0,0 +1,13 @@ +module SlackMathbot + module Config + extend self + + attr_accessor :token + attr_accessor :url + attr_accessor :user + attr_accessor :user_id + attr_accessor :team + attr_accessor :team_id + attr_accessor :secret + end +end diff --git a/app/slack-mathbot/hooks.rb b/app/slack-mathbot/hooks.rb new file mode 100644 index 0000000..7df4162 --- /dev/null +++ b/app/slack-mathbot/hooks.rb @@ -0,0 +1,3 @@ +require 'slack-mathbot/hooks/base' +require 'slack-mathbot/hooks/hello' +require 'slack-mathbot/hooks/message' diff --git a/app/slack-mathbot/hooks/base.rb b/app/slack-mathbot/hooks/base.rb new file mode 100644 index 0000000..55e1875 --- /dev/null +++ b/app/slack-mathbot/hooks/base.rb @@ -0,0 +1,10 @@ +module SlackMathbot + module Hooks + module Base + def included(caller) + caller.hooks ||= [] + caller.hooks << name.demodulize.underscore.to_sym + end + end + end +end diff --git a/app/slack-mathbot/hooks/hello.rb b/app/slack-mathbot/hooks/hello.rb new file mode 100644 index 0000000..8b0a2d1 --- /dev/null +++ b/app/slack-mathbot/hooks/hello.rb @@ -0,0 +1,11 @@ +module SlackMathbot + module Hooks + module Hello + extend Base + + def hello(_data) + logger.info "Successfully connected to #{SlackMathbot.config.url}." + end + end + end +end diff --git a/app/slack-mathbot/hooks/message.rb b/app/slack-mathbot/hooks/message.rb new file mode 100644 index 0000000..776e356 --- /dev/null +++ b/app/slack-mathbot/hooks/message.rb @@ -0,0 +1,33 @@ +module SlackMathbot + module Hooks + module Message + extend Base + + def message(data) + data = Hashie::Mash.new(data) + bot_name, command, arguments = parse_command(data.text) + return unless bot_name == SlackMathbot.config.user + klass = command_to_class(command || 'Default') + klass.call data, command, arguments + end + + private + + def parse_command(text) + return unless text + text = '= ' + text[1..text.length] if text[0] == '=' + parts = text.split.reject(&:blank?) + if parts && parts[0] == '=' + parts[0] = SlackMathbot.config.user + parts.insert 1, 'calculate' + end + [parts.first.downcase, parts[1].try(:downcase), parts[2..parts.length]] if parts && parts.any? + end + + def command_to_class(command) + klass = "SlackMathbot::Commands::#{command.titleize}".constantize rescue nil + klass || SlackMathbot::Commands::Unknown + end + end + end +end diff --git a/app/slack-mathbot/version.rb b/app/slack-mathbot/version.rb new file mode 100644 index 0000000..ec90e43 --- /dev/null +++ b/app/slack-mathbot/version.rb @@ -0,0 +1,3 @@ +module SlackMathbot + VERSION = '0.1.0' +end diff --git a/app/slack_mathbot.rb b/app/slack_mathbot.rb new file mode 100644 index 0000000..bb25115 --- /dev/null +++ b/app/slack_mathbot.rb @@ -0,0 +1 @@ +require 'slack-mathbot' diff --git a/config.ru b/config.ru new file mode 100644 index 0000000..bf4320f --- /dev/null +++ b/config.ru @@ -0,0 +1,7 @@ +require File.expand_path('../app', __FILE__) + +Thread.new do + SlackMathbot::App.instance.run +end + +run SlackMathbot::Web diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000..c2134eb --- /dev/null +++ b/config/application.rb @@ -0,0 +1,14 @@ +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'app')) +$LOAD_PATH.unshift(File.dirname(__FILE__)) + +require 'boot' + +Bundler.require :default, ENV['RACK_ENV'] + +Dir[File.expand_path('../initializers', __FILE__) + '/**/*.rb'].each do |file| + require file +end + +require File.expand_path('../application', __FILE__) + +require 'slack_mathbot' diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000..ca944a8 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,5 @@ +require 'rubygems' +require 'bundler/setup' +require 'logger' +require 'active_support' +require 'active_support/core_ext' diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000..f45d5f6 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,3 @@ +ENV['RACK_ENV'] ||= 'development' + +require File.expand_path('../application', __FILE__) diff --git a/config/initializers/slack/request.rb b/config/initializers/slack/request.rb new file mode 100644 index 0000000..66b1583 --- /dev/null +++ b/config/initializers/slack/request.rb @@ -0,0 +1,13 @@ +module Slack + module Request + private + + alias_method :_request, :request + + def request(method, path, options) + response = _request(method, path, options) + fail response['error'] unless response['ok'] if response.is_a?(Hash) + response + end + end +end diff --git a/screenshots/register-bot.png b/screenshots/register-bot.png new file mode 100644 index 0000000000000000000000000000000000000000..391a2011435a8ea42d94bcb5a9dfae7a5ea4f545 GIT binary patch literal 57962 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 VheelLJP5##^c#7JaxsH|{|^Fg5X1lg literal 0 HcmV?d00001 diff --git a/screenshots/two-plus-two.gif b/screenshots/two-plus-two.gif new file mode 100644 index 0000000000000000000000000000000000000000..b95dbe0c3cc69a336f9cbfe8467a0b0d100777d6 GIT binary patch literal 18537 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

W_$BEvmrRY{GH#QbvUu&7f?7%U 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 literal 0 HcmV?d00001 diff --git a/spec/fixtures/slack/auth_test.yml b/spec/fixtures/slack/auth_test.yml new file mode 100644 index 0000000..50f08fe --- /dev/null +++ b/spec/fixtures/slack/auth_test.yml @@ -0,0 +1,103 @@ +--- +http_interactions: +- request: + method: post + uri: https://slack.com/api/auth.test + body: + encoding: UTF-8 + string: token=token + headers: + Accept: + - application/json; charset=utf-8 + User-Agent: + - Slack Ruby Gem 1.1.1 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Access-Control-Allow-Origin: + - '*' + Cache-Control: + - private, no-cache, no-store, must-revalidate + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 28 Apr 2015 12:55:23 GMT + Expires: + - Mon, 26 Jul 1997 05:00:00 GMT + Pragma: + - no-cache + Server: + - Apache + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Accepted-Oauth-Scopes: + - identify + X-Content-Type-Options: + - nosniff + X-Oauth-Scopes: + - identify,read,post,client + X-Xss-Protection: + - '0' + Content-Length: + - '128' + Connection: + - keep-alive + body: + encoding: UTF-8 + string: '{"ok":true,"url":"https:\/\/math.slack.com\/","team":"calcteam","user":"calcuser","team_id":"TDEADBEEF","user_id":"UBAADFOOD"}' + http_version: + recorded_at: Tue, 28 Apr 2015 12:55:22 GMT +- request: + method: post + uri: https://slack.com/api/rtm.start + body: + encoding: UTF-8 + string: token=token + headers: + Accept: + - application/json; charset=utf-8 + User-Agent: + - Slack Ruby Gem 1.1.1 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Access-Control-Allow-Origin: + - '*' + Content-Type: + - application/json; charset=utf-8 + Date: + - Tue, 28 Apr 2015 21:41:33 GMT + Server: + - Apache + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Content-Type-Options: + - nosniff + X-Xss-Protection: + - '0' + Content-Length: + - '55' + Connection: + - keep-alive + body: + encoding: UTF-8 + string: '{"ok":false,"error":"invalid_auth"}' + http_version: + recorded_at: Tue, 28 Apr 2015 21:41:33 GMT +recorded_with: VCR 2.9.3 diff --git a/spec/fixtures/slack/user_info.yml b/spec/fixtures/slack/user_info.yml new file mode 100644 index 0000000..9751a21 --- /dev/null +++ b/spec/fixtures/slack/user_info.yml @@ -0,0 +1,59 @@ +--- +http_interactions: +- request: + method: post + uri: https://slack.com/api/users.info + body: + encoding: UTF-8 + string: token=token&user=user + headers: + Accept: + - application/json; charset=utf-8 + User-Agent: + - Slack Ruby Gem 1.1.1 + Content-Type: + - application/x-www-form-urlencoded + Accept-Encoding: + - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 + response: + status: + code: 200 + message: OK + headers: + Access-Control-Allow-Origin: + - '*' + Cache-Control: + - private, no-cache, no-store, must-revalidate + Content-Type: + - application/json; charset=utf-8 + Date: + - Wed, 29 Apr 2015 16:10:34 GMT + Expires: + - Mon, 26 Jul 1997 05:00:00 GMT + Pragma: + - no-cache + Server: + - Apache + Strict-Transport-Security: + - max-age=31536000; includeSubDomains; preload + Vary: + - Accept-Encoding + X-Accepted-Oauth-Scopes: + - read + X-Content-Type-Options: + - nosniff + X-Oauth-Scopes: + - identify,read,post,client + X-Xss-Protection: + - '0' + Content-Length: + - '448' + Connection: + - keep-alive + body: + encoding: UTF-8 + string: '{"ok":true,"user":{"id":"U007","name":"username","deleted":false,"status":null,"color":"9f69e7","real_name":"","tz":"America\/Indiana\/Indianapolis","tz_label":"Eastern + Daylight Time","tz_offset":-14400,"profile":{"real_name":"","real_name_normalized":"","email":"dblock@dblock.org","image_24":"https:\/\/secure.gravatar.com\/avatar\/3d925b45ac07ec0ae5bd04888f6c5b61.jpg?s=24&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F3654%2Fimg%2Favatars%2Fava_0015-24.png","image_32":"https:\/\/secure.gravatar.com\/avatar\/3d925b45ac07ec0ae5bd04888f6c5b61.jpg?s=32&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F3654%2Fimg%2Favatars%2Fava_0015-32.png","image_48":"https:\/\/secure.gravatar.com\/avatar\/3d925b45ac07ec0ae5bd04888f6c5b61.jpg?s=48&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F3654%2Fimg%2Favatars%2Fava_0015-48.png","image_72":"https:\/\/secure.gravatar.com\/avatar\/3d925b45ac07ec0ae5bd04888f6c5b61.jpg?s=72&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F3654%2Fimg%2Favatars%2Fava_0015-72.png","image_192":"https:\/\/secure.gravatar.com\/avatar\/3d925b45ac07ec0ae5bd04888f6c5b61.jpg?s=192&d=https%3A%2F%2Fslack.global.ssl.fastly.net%2F272a%2Fimg%2Favatars%2Fava_0015.png"},"is_admin":true,"is_owner":true,"is_primary_owner":true,"is_restricted":false,"is_ultra_restricted":false,"is_bot":false,"has_files":false}}' + http_version: + recorded_at: Wed, 29 Apr 2015 16:10:34 GMT + diff --git a/spec/slack-mathbot/app_spec.rb b/spec/slack-mathbot/app_spec.rb new file mode 100644 index 0000000..93a9fe5 --- /dev/null +++ b/spec/slack-mathbot/app_spec.rb @@ -0,0 +1,32 @@ +require 'spec_helper' + +describe SlackMathbot::App do + subject do + SlackMathbot::App.new + end + context 'not configured' do + before do + @slack_api_token = ENV.delete('SLACK_API_TOKEN') + end + after do + ENV['SLACK_API_TOKEN'] = @slack_api_token + end + it 'requires SLACK_API_TOKEN' do + expect { subject }.to raise_error RuntimeError, "Missing ENV['SLACK_API_TOKEN']." + end + end + context 'configured', vcr: { cassette_name: 'auth_test' } do + context 'run' do + before do + subject.send(:auth!) + end + it 'succeeds auth' do + expect(subject.config.url).to eq 'https://math.slack.com/' + expect(subject.config.team).to eq 'calcteam' + expect(subject.config.user).to eq 'calcuser' + expect(subject.config.team_id).to eq 'TDEADBEEF' + expect(subject.config.user_id).to eq 'UBAADFOOD' + end + end + end +end diff --git a/spec/slack-mathbot/commands/about_spec.rb b/spec/slack-mathbot/commands/about_spec.rb new file mode 100644 index 0000000..6ca4b20 --- /dev/null +++ b/spec/slack-mathbot/commands/about_spec.rb @@ -0,0 +1,10 @@ +require 'spec_helper' + +describe SlackMathbot::Commands::Default do + it 'mathbot' do + expect(message: 'mathbot').to respond_with_slack_message(SlackMathbot::ABOUT) + end + it 'Mathbot' do + expect(message: 'Mathbot').to respond_with_slack_message(SlackMathbot::ABOUT) + end +end diff --git a/spec/slack-mathbot/commands/calculate_spec.rb b/spec/slack-mathbot/commands/calculate_spec.rb new file mode 100644 index 0000000..5e8c5d4 --- /dev/null +++ b/spec/slack-mathbot/commands/calculate_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe SlackMathbot::Commands::Calculate, vcr: { cassette_name: 'user_info' } do + it 'adds two numbers' do + expect(message: 'mathbot calculate 2+2', channel: 'channel').to respond_with_slack_message('4') + end + it 'adds two numbers via =' do + expect(message: '= 2+2', channel: 'channel').to respond_with_slack_message('4') + end + it 'adds two numbers via = without a space' do + expect(message: '=2+2', channel: 'channel').to respond_with_slack_message('4') + end + it 'sends something without an answer' do + expect(message: 'mathbot calculate pi', channel: 'channel').to respond_with_slack_message('Got nothing.') + end +end diff --git a/spec/slack-mathbot/commands/help_spec.rb b/spec/slack-mathbot/commands/help_spec.rb new file mode 100644 index 0000000..0db53b4 --- /dev/null +++ b/spec/slack-mathbot/commands/help_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +describe SlackMathbot::Commands::Help do + it 'help' do + expect(message: 'mathbot help').to respond_with_slack_message('See https://github.com/dblock/slack-mathbot, please.') + end +end diff --git a/spec/slack-mathbot/commands/hi_spec.rb b/spec/slack-mathbot/commands/hi_spec.rb new file mode 100644 index 0000000..b6fb021 --- /dev/null +++ b/spec/slack-mathbot/commands/hi_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +describe SlackMathbot::Commands::Hi do + it 'says hi' do + expect(message: 'mathbot hi').to respond_with_slack_message('Hi <@user>!') + end +end diff --git a/spec/slack-mathbot/commands/unknown_spec.rb b/spec/slack-mathbot/commands/unknown_spec.rb new file mode 100644 index 0000000..a6330c4 --- /dev/null +++ b/spec/slack-mathbot/commands/unknown_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe SlackMathbot::Commands::Unknown, vcr: { cassette_name: 'user_info' } do + it 'invalid command' do + expect(message: 'mathbot foobar').to respond_with_slack_message("Sorry <@user>, I don't understand that command!") + end + it 'does not respond to sad face' do + expect(SlackMathbot::Commands::Base).to_not receive(:send_message) + SlackMathbot::App.new.send(:message, text: ':((') + end +end diff --git a/spec/slack-mathbot/version_spec.rb b/spec/slack-mathbot/version_spec.rb new file mode 100644 index 0000000..754c246 --- /dev/null +++ b/spec/slack-mathbot/version_spec.rb @@ -0,0 +1,7 @@ +require 'spec_helper' + +describe SlackMathbot do + it 'has a version' do + expect(SlackMathbot::VERSION).to_not be nil + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..ac07315 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,12 @@ +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..')) + +require 'rubygems' +require 'rspec' +require 'rack/test' + +require 'config/environment' +require 'slack-mathbot' + +Dir[File.join(File.dirname(__FILE__), 'support', '**/*.rb')].each do |file| + require file +end diff --git a/spec/support/slack-mathbot/respond_with_error.rb b/spec/support/slack-mathbot/respond_with_error.rb new file mode 100644 index 0000000..e35787c --- /dev/null +++ b/spec/support/slack-mathbot/respond_with_error.rb @@ -0,0 +1,30 @@ +require 'rspec/expectations' + +RSpec::Matchers.define :respond_with_error do |expected| + match do |actual| + channel, user, message = parse(actual) + app = SlackMathbot::App.new + SlackMathbot.config.user = 'mathbot' + begin + expect do + app.send(:message, text: message, channel: channel, user: user) + end.to raise_error ArgumentError, expected + rescue RSpec::Expectations::ExpectationNotMetError => e + @error_message = e.message + raise e + end + true + end + + failure_message do |actual| + _, _, message = parse(actual) + @error_message || "expected for '#{message}' to fail with '#{expected}'" + end + + private + + def parse(actual) + actual = { message: actual } unless actual.is_a?(Hash) + [actual[:channel] || 'channel', actual[:user] || 'user', actual[:message]] + end +end diff --git a/spec/support/slack-mathbot/respond_with_slack_message.rb b/spec/support/slack-mathbot/respond_with_slack_message.rb new file mode 100644 index 0000000..cbfb68b --- /dev/null +++ b/spec/support/slack-mathbot/respond_with_slack_message.rb @@ -0,0 +1,19 @@ +require 'rspec/expectations' + +RSpec::Matchers.define :respond_with_slack_message do |expected| + match do |actual| + channel, user, message = parse(actual) + app = SlackMathbot::App.new + SlackMathbot.config.user = 'mathbot' + expect(SlackMathbot::Commands::Base).to receive(:send_message).with(channel, expected) + app.send(:message, text: message, channel: channel, user: user) + true + end + + private + + def parse(actual) + actual = { message: actual } unless actual.is_a?(Hash) + [actual[:channel] || 'channel', actual[:user] || 'user', actual[:message]] + end +end diff --git a/spec/support/slack_api_key.rb b/spec/support/slack_api_key.rb new file mode 100644 index 0000000..fdaff36 --- /dev/null +++ b/spec/support/slack_api_key.rb @@ -0,0 +1,5 @@ +RSpec.configure do |config| + config.before :all do + ENV['SLACK_API_TOKEN'] ||= 'test' + end +end diff --git a/spec/support/slack_calculator.rb b/spec/support/slack_calculator.rb new file mode 100644 index 0000000..a80a44f --- /dev/null +++ b/spec/support/slack_calculator.rb @@ -0,0 +1,4 @@ +SlackMathbot.configure do |config| + config.token = 'testtoken' + config.user = 'mathbot' +end diff --git a/spec/support/vcr.rb b/spec/support/vcr.rb new file mode 100644 index 0000000..768541a --- /dev/null +++ b/spec/support/vcr.rb @@ -0,0 +1,8 @@ +require 'vcr' + +VCR.configure do |config| + config.cassette_library_dir = 'spec/fixtures/slack' + config.hook_into :webmock + # config.default_cassette_options = { record: :new_episodes } + config.configure_rspec_metadata! +end