slack-ruby-bot-boilerplate 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c789624ba72b2006ff5c87bc4e19521adede2e9aa144b261c1db2fb818329a56
4
+ data.tar.gz: aff4faf0c5de0ce2b06c615023b67c26b1bd4b016a614f176c4bb49c297babe0
5
+ SHA512:
6
+ metadata.gz: 16747df3e9ce75eb19a326ca6d57f00fa6953561964306af2c1b29e785326e2be095534dd6aadaf402eabb79b8db587b1931aee4ba8603c5c3f9fae25107791b
7
+ data.tar.gz: d9318358837b69624337c6b5ef81d5f7d3f4804f3c7d397d44943f265319559c7ad41444e951faf7ce7793fc54cc4264f5dcc23ac2861e865d48ca092aad015f
@@ -0,0 +1,5 @@
1
+ .env
2
+ pkg
3
+ Gemfile.lock
4
+ .bundle
5
+ .idea/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,16 @@
1
+ AllCops:
2
+ Exclude:
3
+ - vendor/**/*
4
+ - examples/**/vendor/**/*
5
+ - bin/**/*
6
+
7
+ Metrics:
8
+ Enabled: false
9
+
10
+ Metrics/LineLength:
11
+ Max: 512
12
+
13
+ Style/Documentation:
14
+ Enabled: false
15
+
16
+ inherit_from: .rubocop_todo.yml
@@ -0,0 +1,77 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2018-08-27 14:16:34 +0200 using RuboCop version 0.58.2.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 2
10
+ # Cop supports --auto-correct.
11
+ # Configuration parameters: EnforcedStyle.
12
+ # SupportedStyles: auto_detection, squiggly, active_support, powerpack, unindent
13
+ Layout/IndentHeredoc:
14
+ Exclude:
15
+ - 'lib/slack-ruby-bot/commands/help.rb'
16
+ - 'spec/slack-ruby-bot/commands/help_spec.rb'
17
+
18
+ # Offense count: 1
19
+ Lint/DuplicateMethods:
20
+ Exclude:
21
+ - 'lib/slack-ruby-bot/hooks/set.rb'
22
+
23
+ # Offense count: 2
24
+ Lint/HandleExceptions:
25
+ Exclude:
26
+ - 'lib/initializers/giphy.rb'
27
+ - 'lib/initializers/giphy_client.rb'
28
+
29
+ # Offense count: 1
30
+ # Cop supports --auto-correct.
31
+ Lint/UnneededCopEnableDirective:
32
+ Exclude:
33
+ - 'Gemfile'
34
+
35
+ # Offense count: 2
36
+ # Configuration parameters: EnforcedStyle.
37
+ # SupportedStyles: snake_case, normalcase, non_integer
38
+ Naming/VariableNumber:
39
+ Exclude:
40
+ - 'spec/slack-ruby-bot/hooks/set_spec.rb'
41
+
42
+ # Offense count: 1
43
+ # Configuration parameters: EnforcedStyle.
44
+ # SupportedStyles: inline, group
45
+ Style/AccessModifierDeclarations:
46
+ Exclude:
47
+ - 'lib/slack-ruby-bot/hooks/hook_support.rb'
48
+
49
+ # Offense count: 1
50
+ Style/DoubleNegation:
51
+ Exclude:
52
+ - 'lib/slack-ruby-bot/commands/base.rb'
53
+
54
+ # Offense count: 5
55
+ # Cop supports --auto-correct.
56
+ Style/ExpandPathArguments:
57
+ Exclude:
58
+ - 'lib/config/application.rb'
59
+ - 'lib/config/environment.rb'
60
+ - 'lib/slack-ruby-bot.rb'
61
+ - 'slack-ruby-bot.gemspec'
62
+
63
+ # Offense count: 2
64
+ # Cop supports --auto-correct.
65
+ Style/IfUnlessModifier:
66
+ Exclude:
67
+ - 'lib/slack-ruby-bot/commands/support/match.rb'
68
+ - 'lib/slack-ruby-bot/mvc/controller/base.rb'
69
+
70
+ # Offense count: 2
71
+ # Cop supports --auto-correct.
72
+ # Configuration parameters: EnforcedStyle.
73
+ # SupportedStyles: module_function, extend_self
74
+ Style/ModuleFunction:
75
+ Exclude:
76
+ - 'lib/initializers/giphy_client.rb'
77
+ - 'lib/slack-ruby-bot/config.rb'
@@ -0,0 +1,28 @@
1
+ language: ruby
2
+
3
+ cache: bundler
4
+
5
+ matrix:
6
+ include:
7
+ - rvm: 2.3.0
8
+ script:
9
+ - bundle exec danger
10
+ - rvm: 2.3.0
11
+ env: CONCURRENCY=celluloid-io
12
+ - rvm: 2.3.0
13
+ env: CONCURRENCY=faye-websocket
14
+ - rvm: 2.3.0
15
+ env: CONCURRENCY=celluloid-io WITH_GIPHY=true
16
+ - rvm: 2.3.0
17
+ env: CONCURRENCY=faye-websocket WITH_GIPHY=true
18
+ - rvm: 2.3.0
19
+ env: CONCURRENCY=celluloid-io WITH_GIPHY_CLIENT=true
20
+ - rvm: 2.3.0
21
+ env: CONCURRENCY=faye-websocket WITH_GIPHY_CLIENT=true
22
+ - rvm: 2.5
23
+ env: CONCURRENCY=async-websocket WITH_GIPHY_CLIENT=true
24
+ - rvm: ruby-head
25
+ - rvm: jruby-head
26
+ allow_failures:
27
+ - rvm: ruby-head
28
+ - rvm: jruby-head
@@ -0,0 +1,3 @@
1
+ ### 0.1.0
2
+
3
+ * Refactor fork
@@ -0,0 +1,139 @@
1
+ # Contributing to SlackRubyBot
2
+
3
+ This project is work of [many contributors](https://github.com/slack-ruby/slack-ruby-bot/graphs/contributors).
4
+
5
+ You're encouraged to submit [pull requests](https://github.com/slack-ruby/slack-ruby-bot/pulls), [propose features and discuss issues](https://github.com/slack-ruby/slack-ruby-bot/issues).
6
+
7
+ In the examples below, substitute your Github username for `contributor` in URLs.
8
+
9
+ ## Fork the Project
10
+
11
+ Fork the [project on Github](https://github.com/slack-ruby/slack-ruby-bot) and check out your copy.
12
+
13
+ ```
14
+ git clone https://github.com/contributor/slack-ruby-bot.git
15
+ cd slack-ruby-bot
16
+ git remote add upstream https://github.com/slack-ruby/slack-ruby-bot.git
17
+ ```
18
+
19
+ ## Bundle Install and Test
20
+
21
+ Ensure that you can build the project and run tests.
22
+
23
+ ```
24
+ bundle install
25
+ bundle exec rake
26
+ ```
27
+
28
+ ## Run SlackRubyBot in Development
29
+
30
+ Create a private slack group for yourself.
31
+
32
+ Create a new Bot Integration under [services/new/bot](http://slack.com/services/new/bot).
33
+
34
+ ![](screenshots/register-bot.png)
35
+
36
+ On the next screen, note the API token.
37
+
38
+ Run `SLACK_API_TOKEN=<your API token> foreman start`.
39
+
40
+ You can also create a `.env` file with `SLACK_API_TOKEN=<your API token>` and just run `foreman start`.
41
+
42
+ ## Create a Topic Branch
43
+
44
+ Make sure your fork is up-to-date and create a topic branch for your feature or bug fix.
45
+
46
+ ```
47
+ git checkout master
48
+ git pull upstream master
49
+ git checkout -b my-feature-branch
50
+ ```
51
+
52
+ ## Write Tests
53
+
54
+ Try to write a test that reproduces the problem you're trying to fix or describes a feature that you want to build.
55
+ Add to [spec](spec).
56
+
57
+ We definitely appreciate pull requests that highlight or reproduce a problem, even without a fix.
58
+
59
+ ## Write Code
60
+
61
+ Implement your feature or bug fix.
62
+
63
+ Ruby style is enforced with [Rubocop](https://github.com/bbatsov/rubocop).
64
+ Run `bundle exec rubocop` and fix any style issues highlighted.
65
+
66
+ Make sure that `bundle exec rake` completes without errors.
67
+
68
+ ## Write Documentation
69
+
70
+ Document any external behavior in the [README](README.md).
71
+
72
+ ## Update Changelog
73
+
74
+ Add a line to [CHANGELOG](CHANGELOG.md) under *Next Release*.
75
+ Make it look like every other line, including your name and link to your Github account.
76
+
77
+ ## Commit Changes
78
+
79
+ Make sure git knows your name and email address:
80
+
81
+ ```
82
+ git config --global user.name "Your Name"
83
+ git config --global user.email "contributor@example.com"
84
+ ```
85
+
86
+ Writing good commit logs is important. A commit log should describe what changed and why.
87
+
88
+ ```
89
+ git add ...
90
+ git commit
91
+ ```
92
+
93
+ ## Push
94
+
95
+ ```
96
+ git push origin my-feature-branch
97
+ ```
98
+
99
+ ## Make a Pull Request
100
+
101
+ Go to https://github.com/contributor/slack-ruby-bot and select your feature branch.
102
+ Click the 'Pull Request' button and fill out the form. Pull requests are usually reviewed within a few days.
103
+
104
+ ## Rebase
105
+
106
+ If you've been working on a change for a while, rebase with upstream/master.
107
+
108
+ ```
109
+ git fetch upstream
110
+ git rebase upstream/master
111
+ git push origin my-feature-branch -f
112
+ ```
113
+
114
+ ## Update CHANGELOG Again
115
+
116
+ Update the [CHANGELOG](CHANGELOG.md) with the pull request number. A typical entry looks as follows.
117
+
118
+ ```
119
+ * [#123](https://github.com/slack-ruby/slack-ruby-bot/pull/123): Reticulated splines - [@contributor](https://github.com/contributor).
120
+ ```
121
+
122
+ Amend your previous commit and force push the changes.
123
+
124
+ ```
125
+ git commit --amend
126
+ git push origin my-feature-branch -f
127
+ ```
128
+
129
+ ## Check on Your Pull Request
130
+
131
+ 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.
132
+
133
+ ## Be Patient
134
+
135
+ 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!
136
+
137
+ ## Thank You
138
+
139
+ Please do know that we really appreciate and value your time and work. We love you, really.
@@ -0,0 +1,33 @@
1
+ ## Installation
2
+
3
+ Create a new Bot Integration under [services/new/bot](http://slack.com/services/new/bot).
4
+
5
+ ![](screenshots/register-bot.png)
6
+
7
+ On the next screen, note the API token.
8
+
9
+ ### Environment
10
+
11
+ #### SLACK_API_TOKEN
12
+
13
+ Set SLACK_API_TOKEN from the Bot integration settings on Slack.
14
+
15
+ ```
16
+ heroku config:add SLACK_API_TOKEN=...
17
+ ```
18
+
19
+ #### GIPHY_API_KEY
20
+
21
+ The bot replies with animated GIFs. While it's currently not necessary, you may need to set GIPHY_API_KEY in the future, see [github.com/Giphy/GiphyAPI](https://github.com/Giphy/GiphyAPI) for details.
22
+
23
+ #### SLACK_RUBY_BOT_ALIASES
24
+
25
+ Optional names for this bot.
26
+
27
+ ```
28
+ heroku config:add SLACK_RUBY_BOT_ALIASES=":pong: table-tennis ping-pong"
29
+ ```
30
+
31
+ ### Heroku Idling
32
+
33
+ Heroku free tier applications will idle. Either pay 7$ a month for the hobby dyno or use [UptimeRobot](http://uptimerobot.com) or similar to prevent your instance from sleeping or pay for a production dyno.
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'slack-ruby-client', '0.13.2', github: 'slack-ruby/slack-ruby-client'
6
+
7
+ # group :test do
8
+ # gem 'slack-ruby-danger', '~> 0.1.0', require: false
9
+ # end
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2015-2016 Daniel Doubrovkine, Artsy and Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,688 @@
1
+ Slack-Ruby-Bot-Boilerplate
2
+ ==============
3
+
4
+ A generic Slack bot framework written in Ruby on top of [slack-ruby-client](https://github.com/slack-ruby/slack-ruby-client). This library does all the heavy lifting, such as message parsing, so you can focus on implementing slack bot commands. It also attempts to introduce the bare minimum number of requirements or any sorts of limitations. It's a Slack bot boilerplate.
5
+
6
+ If you are not familiar with Slack bots or Slack API concepts, you might want to watch [this video](http://code.dblock.org/2016/03/11/your-first-slack-bot-service-video.html).
7
+
8
+ ![](slack.png)
9
+
10
+ ## Useful to Me?
11
+
12
+ * If you are just trying to send messages to Slack, use [slack-ruby-client](https://github.com/slack-ruby/slack-ruby-client), which this library is built on top of.
13
+ * If you're trying to roll out a full service with Slack button integration, check out [slack-ruby-bot-server](https://github.com/slack-ruby/slack-ruby-bot-server), which uses this library.
14
+ * Otherwise, this piece of the puzzle will help you create a single bot instance for one team.
15
+
16
+ ## Stable Release
17
+
18
+ You're reading the documentation for the **next** release of slack-ruby-bot.
19
+ Please see the documentation for the [last stable release, v0.11.2](https://github.com/slack-ruby/slack-ruby-bot/tree/v0.11.2) unless you're integrating with HEAD.
20
+ See [CHANGELOG](CHANGELOG.md) for a history of changes and [UPGRADING](UPGRADING.md) for how to upgrade to more recent versions.
21
+
22
+ ## Usage
23
+
24
+ ### A Minimal Bot
25
+
26
+ #### Gemfile
27
+
28
+ ```ruby
29
+ source 'https://rubygems.org'
30
+
31
+ gem 'slack-ruby-bot'
32
+ gem 'async-websocket'
33
+ ```
34
+
35
+ #### pongbot.rb
36
+
37
+ ```ruby
38
+ require 'slack-ruby-bot'
39
+
40
+ class PongBot < SlackRubyBot::Bot
41
+ command 'ping' do |client, data, match|
42
+ client.say(text: 'pong', channel: data.channel)
43
+ end
44
+ end
45
+
46
+ PongBot.run
47
+ ```
48
+
49
+ After [registering the bot](DEPLOYMENT.md), run with `SLACK_API_TOKEN=... bundle exec ruby pongbot.rb`. Have the bot join a channel and send it a ping.
50
+
51
+ ![](screenshots/demo.gif)
52
+
53
+ ### A Production Bot
54
+
55
+ A typical production Slack bot is a combination of a vanilla web server and a websocket application that talks to the Slack Real Time Messaging API. See our [Writing a Production Bot](TUTORIAL.md) tutorial for more information.
56
+
57
+ ### More Involved Examples
58
+
59
+ The following examples of bots based on slack-ruby-bot are listed in growing order of complexity.
60
+
61
+ * [slack-bot-on-rails](https://github.com/dblock/slack-bot-on-rails): A bot running on Rails and using React to display Slack messages on a website.
62
+ * [slack-mathbot](https://github.com/dblock/slack-mathbot): Slack integration with math.
63
+ * [slack-google-bot](https://github.com/dblock/slack-google-bot): A Slack bot that searches Google, including CSE.
64
+ * [slack-aws](https://github.com/dblock/slack-aws): Slack integration with Amazon Web Services.
65
+ * [slack-deploy-bot](https://github.com/accessd/slack-deploy-bot): A Slack bot that helps you to deploy your apps.
66
+ * [slack-gamebot](https://github.com/dblock/slack-gamebot): A game bot service for ping pong, chess, etc, hosted at [playplay.io](http://playplay.io).
67
+ * [slack-victorbot](https://github.com/uShip/victorbot): A Slack bot to talk to the Victorops service.
68
+
69
+ ### Commands and Operators
70
+
71
+ Bots are addressed by name, they respond to commands and operators. You can combine multiple commands.
72
+
73
+ ```ruby
74
+ class CallBot < SlackRubyBot::Bot
75
+ command 'call', '呼び出し' do |client, data, match|
76
+ client.say(channel: data.channel, text: 'called')
77
+ end
78
+ end
79
+ ```
80
+
81
+ Command match data includes `match['bot']`, `match['command']` and `match['expression']`. The `bot` match always checks against the `SlackRubyBot::Config.user` and `SlackRubyBot::Config.user_id` values obtained when the bot starts.
82
+
83
+ The `command` method can take strings, which will have to be escaped with `Regexp.escape`, and regular expressions.
84
+
85
+ ```ruby
86
+ class CallBot < SlackRubyBot::Bot
87
+ command 'string with spaces', /some\s*regex+\?*/ do |client, data, match|
88
+ client.say(channel: data.channel, text: match['command'])
89
+ end
90
+ end
91
+ ```
92
+
93
+ Operators are 1-letter long and are similar to commands. They don't require addressing a bot nor separating an operator from its arguments. The following class responds to `=2+2`.
94
+
95
+ ```ruby
96
+ class MathBot < SlackRubyBot::Bot
97
+ operator '=' do |client, data, match|
98
+ # implementation detail
99
+ end
100
+ end
101
+ ```
102
+
103
+ Operator match data includes `match['operator']` and `match['expression']`. The `bot` match always checks against the `SlackRubyBot::Config.user` setting.
104
+
105
+ ### Threaded Messages
106
+
107
+ To reply to a message in a thread you must provide a reference to the first message that initiated the thread, which is available as either `data.ts` if no threaded messages have been sent, or `data.thread_ts` if the message being replied to is already in a thread. See [message-threading](https://api.slack.com/docs/message-threading) for more information.
108
+
109
+ ```ruby
110
+ command 'reply in thread' do |client, data, match|
111
+ client.say(
112
+ channel: data.channel,
113
+ text: "let's avoid spamming everyone, I will tell you what you need in this thread",
114
+ thread_ts: data.thread_ts || data.ts
115
+ )
116
+ end
117
+ ```
118
+
119
+ _Note that sending a message using only `thread_ts: data.ts` can cause some permanent issues where Slack will keep reporting inaccessible messages as unread. At the time of writing the slack team is still having problems clearing those notifications. As recommended by the slack documentation ..._
120
+
121
+ > A true parent's thread_ts should be used when replying. Providing a child's message ID will result in a new, detached thread breaking all context and sense.
122
+
123
+ _... the replies to a thread should always be sent to the message `ts` that started the thread, available as `thread_ts` for subsequent messages. Hence `data.thread_ts || data.ts`._
124
+
125
+ For additional options, including broadcasting, see [slack-ruby-client#chat_postMessage](https://github.com/slack-ruby/slack-ruby-client/blob/41539c647ac877400f20aa338aa42d2ebfd2866b/lib/slack/web/api/endpoints/chat.rb#L105).
126
+
127
+ ### Bot Aliases
128
+
129
+ A bot will always respond to its name (eg. `rubybot`) and Slack ID (eg. `@rubybot`), but you can specify multiple aliases via the `SLACK_RUBY_BOT_ALIASES` environment variable or via an explicit configuration.
130
+
131
+ ```
132
+ SLACK_RUBY_BOT_ALIASES=:pp: table-tennis
133
+ ```
134
+
135
+ ```ruby
136
+ SlackRubyBot.configure do |config|
137
+ config.aliases = [':pong:', 'pongbot']
138
+ end
139
+ ```
140
+
141
+ This is particularly fun with emoji.
142
+
143
+ ![](screenshots/aliases.gif)
144
+
145
+ Bots will also respond to a direct message, with or without the bot name in the message itself.
146
+
147
+ ![](screenshots/dms.gif)
148
+
149
+ ### Generic Routing
150
+
151
+ Commands and operators are generic versions of bot routes. You can respond to just about anything by defining a custom route.
152
+
153
+ ```ruby
154
+ class Weather < SlackRubyBot::Bot
155
+ match /^How is the weather in (?<location>\w*)\?$/ do |client, data, match|
156
+ client.say(channel: data.channel, text: "The weather in #{match[:location]} is nice.")
157
+ end
158
+ end
159
+ ```
160
+
161
+ ![](screenshots/weather.gif)
162
+
163
+ You can also capture multiple matchers with `scan`.
164
+
165
+ ```ruby
166
+ class Market < SlackRubyBot::Bot
167
+ scan(/([A-Z]{2,5})/) do |client, data, stocks|
168
+ # lookup stock market price
169
+ end
170
+ end
171
+ ```
172
+
173
+ ![](screenshots/market.gif)
174
+
175
+ See [examples/market](examples/market/marketbot.rb) for a working example.
176
+
177
+ ### Matching text in message attachments
178
+
179
+ You can respond to text in [attachments](https://api.slack.com/docs/message-attachments) with
180
+ `attachment`. It will scan `text`, `pretext` and `title` fields in each attachment until a first
181
+ match is found.
182
+
183
+ For example you can match [this example attachment](http://goo.gl/K0cLkH)
184
+ by its `title` with the following bot:
185
+
186
+ ```ruby
187
+ class Attachment < SlackRubyBot::Bot
188
+ attachment 'Slack API Documentation' do |client, data, match|
189
+ client.say(channel: data.channel, text: "Matched by #{match.attachment_field}.")
190
+ client.say(channel: data.channel, text: "The attachment's text: #{match.attachment.text}.")
191
+ end
192
+ end
193
+ ```
194
+
195
+ You can also define which fields in attachment object should be scanned.
196
+
197
+ Scan only a single field:
198
+
199
+ ```ruby
200
+ class Attachment < SlackRubyBot::Bot
201
+ attachment 'Slack API Documentation', :title do |client, data, match|
202
+ # implementation details
203
+ end
204
+ end
205
+ ```
206
+
207
+ Scan multiple fields:
208
+
209
+ ```ruby
210
+ class Attachment < SlackRubyBot::Bot
211
+ attachment 'Slack API Documentation', %i[text pretext author_name] do |client, data, match|
212
+ # implementation details
213
+ end
214
+ end
215
+ ```
216
+
217
+ ### Providing description for your bot and commands
218
+
219
+ You can specify help information for bot or commands with `help` block, for example:
220
+
221
+ in case of bot:
222
+
223
+ ```ruby
224
+ class WeatherBot < SlackRubyBot::Bot
225
+ help do
226
+ title 'Weather Bot'
227
+ desc 'This bot tells you the weather.'
228
+
229
+ command 'clouds' do
230
+ desc 'Tells you how many clouds there\'re above you.'
231
+ end
232
+
233
+ command 'What\'s the weather in <city>?' do
234
+ desc 'Tells you the weather in a <city>.'
235
+ long_desc "Accurate 10 Day Weather Forecasts for thousands of places around the World.\n" \
236
+ 'Bot provides detailed Weather Forecasts over a 10 day period updated four times a day.'
237
+ end
238
+ end
239
+
240
+ # commands implementation
241
+ end
242
+ ```
243
+
244
+ ![](screenshots/help.png)
245
+
246
+ in case of your own command:
247
+
248
+ ```ruby
249
+ class Deploy < SlackRubyBot::Commands::Base
250
+ help do
251
+ title 'deploy'
252
+ desc 'deploys your app'
253
+ long_desc 'command format: *deploy <branch> to <env>* where <env> is production or staging'
254
+ end
255
+ end
256
+ ```
257
+
258
+ ### Customize your command help output
259
+
260
+ If you've used the `help` block described above to document your
261
+ commands, you can provide your own implementation of outputting help
262
+ for commands like so:
263
+
264
+ ```ruby
265
+ class Market < SlackRubyBot::Bot
266
+ command 'help' do |client, data, match|
267
+ user_command = match[:expression]
268
+ help_attrs = SlackRubyBot::Commands::Support::Help.instance.find_command_help_attrs(user_command)
269
+ client.say(channel: data.channel, text: "#{help_attrs.command_desc}\n\n#{help_attrs.command_long_desc}"
270
+ end
271
+ end
272
+ ```
273
+
274
+ ### SlackRubyBot::Commands::Base
275
+
276
+ The `SlackRubyBot::Bot` class is DSL sugar deriving from `SlackRubyBot::Commands::Base`. For more involved bots you can organize the bot implementation into subclasses of `SlackRubyBot::Commands::Base` manually. By default a command class responds, case-insensitively, to its name. A class called `Phone` that inherits from `SlackRubyBot::Commands::Base` responds to `phone` and `Phone` and calls the `call` method when implemented.
277
+
278
+ ```ruby
279
+ class Phone < SlackRubyBot::Commands::Base
280
+ command 'call'
281
+
282
+ def self.call(client, data, match)
283
+ client.say(channel: data.channel, text: 'called')
284
+ end
285
+ end
286
+ ```
287
+
288
+ To respond to custom commands and to disable automatic class name matching, use the `command` keyword. The following command responds to `call` and `呼び出し` (call in Japanese).
289
+
290
+ ```ruby
291
+ class Phone < SlackRubyBot::Commands::Base
292
+ command 'call'
293
+ command '呼び出し'
294
+
295
+ def self.call(client, data, match)
296
+ client.say(channel: data.channel, text: 'called')
297
+ end
298
+ end
299
+ ```
300
+
301
+ ### Authorization
302
+
303
+ The framework does not provide any user authentication or command authorization capability out of the box. However, the `SlackRubyBot::Commands::Base` class does check every command invocation for permission prior to executing the command. The default method always returns true.
304
+
305
+ Therefore, subclasses of `SlackRubyBot::Commands::Base` can override the `permitted?` private method to provide its own authorization logic. This method is intended to be exploited by user code or external gems that want to provide custom authorization logic for command execution.
306
+
307
+ ```ruby
308
+ class AuthorizedBot < SlackRubyBot::Commands::Base
309
+ command 'phone home' do |client, data, match|
310
+ client.say(channel: data.channel, text: 'Elliot!')
311
+ end
312
+
313
+ # Only allow user 'Uxyzabc' to run this command
314
+ def self.permitted?(client, data, match)
315
+ data && data.user && data.user == 'Uxyzabc'
316
+ end
317
+ end
318
+ ```
319
+
320
+ ### Animated GIFs
321
+
322
+ The `SlackRubyBot::Client` implementation comes with GIF support. To enable it add `gem GiphyClient` (official Giphy SDK) or `gem giphy` (older SDK, deprecated) to your **Gemfile** and set a Giphy key via `ENV['GIPHY_API_KEY']`. Obtain one from [developers.giphy.com](https://developers.giphy.com).
323
+
324
+ **Note:** Bots send animated GIFs in default commands and errors.
325
+
326
+ ```ruby
327
+ class Phone < SlackRubyBot::Commands::Base
328
+ command 'call'
329
+
330
+ def self.call(client, data, match)
331
+ client.say(channel: data.channel, text: 'called', gif: 'phone')
332
+ # Sends the text 'called' and a random GIF that matches the keyword 'phone'.
333
+ end
334
+ end
335
+ ```
336
+
337
+ Giphy API key is set automatically via `ENV['GIPHY_API_KEY']`. You can override this manually.
338
+
339
+ ```ruby
340
+ Giphy.configure do |config|
341
+ config.api_key = 'key'
342
+ end
343
+ ```
344
+
345
+ With `GiphyClient` you can configure the default GIF rating, which supports Y, G, PG, PG-13, and R. The default value is `G`.
346
+
347
+ ```ruby
348
+ Giphy.configure do |config|
349
+ config.rating = 'Y' # illustrated content only, i.e. cartoons
350
+ end
351
+ ```
352
+
353
+ If you use giphy for something else but don't want your bots to send GIFs you can set `ENV['SLACK_RUBY_BOT_SEND_GIFS']` or `SlackRubyBot::Config.send_gifs` to `false`. The latter takes precedence.
354
+
355
+ ```ruby
356
+ SlackRubyBot.configure do |config|
357
+ config.send_gifs = false
358
+ end
359
+ ```
360
+
361
+ ### Built-In Commands
362
+
363
+ Slack-ruby-bot comes with several built-in commands. You can re-define built-in commands, normally, as described above.
364
+
365
+ #### [bot name]
366
+
367
+ This is also known as the `default` command. Shows bot version and links.
368
+
369
+ #### [bot name] hi
370
+
371
+ Politely says 'hi' back.
372
+
373
+ #### [bot name] help
374
+
375
+ Get help.
376
+
377
+ ### Hooks
378
+
379
+ Hooks are event handlers and respond to Slack RTM API [events](https://api.slack.com/events), such as [hello](lib/slack-ruby-bot/hooks/hello.rb) or [message](lib/slack-ruby-bot/hooks/message.rb). You can implement your own in a couple of ways:
380
+
381
+ #### Implement and register a Hook Handler
382
+
383
+ A Hook Handler is any object that respond to a `call` message, like a proc, instance of an object, class with a `call` class method, etc.
384
+
385
+ Hooks can be registered using different methods based on user preference / use case.
386
+ Currently someone can use one of the following methods:
387
+
388
+ * Pass `hooks` in `SlackRubyBot::Server` initialization.
389
+ * Register `hooks` on `SlackRubyBot::Server` using `on` class method.
390
+ * Register `hooks` on `SlackRubyBot::Server` using `on` instance method.
391
+
392
+
393
+ ##### Hooks registration on `SlackRubyBot::Server` initialization
394
+
395
+ ```ruby
396
+ SlackRubyBot::Server.new(hook_handlers: {
397
+ hello: MyBot::Hooks::UserChange.new,
398
+ user_change: [->(client, data) { }, ->(client, data) {}]
399
+ })
400
+ ```
401
+
402
+ ##### Hooks registration on a `SlackRubyBot::Server` instance
403
+
404
+ ```ruby
405
+ # Register an object that implements `call` method
406
+ class MyBot::Hooks::Hello
407
+ def call(client, data)
408
+ puts "Hello"
409
+ end
410
+ end
411
+
412
+ server.on(:hello, MyBot::Hooks::Hello.new)
413
+
414
+ # or register a lambda function to handle the event
415
+ server.on(:hello, ->(client, data) { puts "Hello!" })
416
+ ```
417
+
418
+ For example, the following hook handles [user_change](https://api.slack.com/events/user_change), an event sent when a team member updates their profile or data. This can be useful to update the local user cache when a user is renamed.
419
+
420
+ ```ruby
421
+ module MyBot
422
+ module Hooks
423
+ class UserChange
424
+ def call(client, data)
425
+ # data['user']['id'] contains the user ID
426
+ # data['user']['name'] contains the new user name
427
+ # ...
428
+ end
429
+ end
430
+ end
431
+ end
432
+ ```
433
+
434
+ ##### Hooks registration on `SlackRubyBot::Server` class
435
+
436
+ Example:
437
+
438
+ ```ruby
439
+ module MyBot
440
+ class MyServer < SlackRubyBot::Server
441
+ on 'hello' do |client, data|
442
+ # data['user']['id'] contains the user ID
443
+ # data['user']['name'] contains the new user name
444
+ end
445
+
446
+ on 'user_change', ->(client, data) {
447
+ # data['user']['id'] contains the user ID
448
+ # data['user']['name'] contains the new user name
449
+ }
450
+ end
451
+ end
452
+ ```
453
+
454
+ These will get pushed into the hook set on initialization.
455
+
456
+ Either by configuration, explicit assignment or hook blocks, multiple handlers can exist for the same event type.
457
+
458
+
459
+ #### Deprecated hook registration
460
+
461
+ Registering a hook method using `hooks.add` is considered deprecated and
462
+ will be removed on future versions.
463
+
464
+ ```ruby
465
+ # [DEPRECATED]
466
+ server.hooks.add(:hello, MyBot::Hooks::UserChange.new)
467
+ server.hooks.add(:hello, ->(client, data) { puts "Hello!" })
468
+
469
+ ```
470
+
471
+ ### Message Loop Protection
472
+
473
+ By default bots do not respond to their own messages. If you wish to change that behavior, set `allow_message_loops` to `true`.
474
+
475
+ ```ruby
476
+ SlackRubyBot.configure do |config|
477
+ config.allow_message_loops = true
478
+ end
479
+ ```
480
+
481
+ ### Logging
482
+
483
+ By default bots set a logger to `$stdout` with `DEBUG` level. The logger is used in both the RealTime and Web clients.
484
+
485
+ Silence logger as follows.
486
+
487
+ ```ruby
488
+ SlackRubyBot::Client.logger.level = Logger::WARN
489
+ ```
490
+
491
+ If you wish to customize logger, set `logger` to your logger.
492
+
493
+ ```ruby
494
+ SlackRubyBot.configure do |config|
495
+ config.logger = Logger.new("slack-ruby-bot.log", "daily")
496
+ end
497
+ ```
498
+
499
+ ### Advanced Integration
500
+
501
+ You may want to integrate a bot or multiple bots into other systems, in which case a globally configured bot may not work for you. You may create instances of [SlackRubyBot::Server](lib/slack-ruby-bot/server.rb) which accepts `token`, `aliases` and `send_gifs`.
502
+
503
+ ```ruby
504
+ EM.run do
505
+ bot1 = SlackRubyBot::Server.new(token: token1, aliases: ['bot1'])
506
+ bot1.start_async
507
+
508
+ bot2 = SlackRubyBot::Server.new(token: token2, send_gifs: false, aliases: ['bot2'])
509
+ bot2.start_async
510
+ end
511
+ ```
512
+
513
+ For an example of advanced integration that supports multiple teams, see [slack-gamebot](https://github.com/dblock/slack-gamebot) and [playplay.io](http://playplay.io) that is built on top of it.
514
+
515
+ ### Proxy Configuration
516
+
517
+ There are [several proxy options](https://github.com/slack-ruby/slack-ruby-client#web-client-options) that can be configured on `Slack::Web::Client`. You can also control what proxy options are used by modifying the `http_proxy` environment variable per [Net::HTTP's documentation](https://docs.ruby-lang.org/en/2.0.0/Net/HTTP.html#class-Net::HTTP-label-Proxies).
518
+
519
+ Note that Docker on OSX seems to incorrectly set the proxy, [causing `Faraday::ConnectionFailed`](https://github.com/slack-ruby/slack-ruby-bot/issues/155), `ERROR -- : Failed to open TCP connection to : (getaddrinfo: Name or service not known)`. You might need to manually unset `http_proxy` in that case, eg. `http_proxy="" bundle exec ruby ./my_bot.rb`.
520
+
521
+ ### Model-View-Controller Design
522
+
523
+ The `command` method is essentially a controller method that receives input from the outside and acts upon it. Complex behaviors could lead to a long and difficult-to-understand `command` block. A complex `command` block is a candidate for separation into classes conforming to the Model-View-Controller pattern popularized by Rails.
524
+
525
+ The library provides three helpful base classes named `SlackRubyBot::MVC::Model::Base`, `SlackRubyBot::MVC::View::Base`, and `SlackRubyBot::MVC::Controller::Base`.
526
+
527
+ Testing a `command` block is difficult. As separate classes, the Model/View/Controller's behavior can be tested via `rspec` or a similar tool.
528
+
529
+ #### Controller
530
+
531
+ The Controller is the focal point of the bot behavior. Typically the code that would go into the `command` block will now go into an instance method in a Controller subclass. The instance method name should match the command name exactly (case sensitive).
532
+
533
+ As an example, these two classes are functionally equivalent.
534
+
535
+ Consider the following `Agent` class which is the simplest default approach to take.
536
+
537
+ ```ruby
538
+ class Agent < SlackRubyBot::Bot
539
+ command 'sayhello', 'alternate way to call hello' do |client, data, match|
540
+ client.say(channel: data.channel, text: "Received command #{match[:command]} with args #{match[:expression]}")
541
+ end
542
+ end
543
+ ```
544
+
545
+ Using the MVC functionality, we would create a controller instead to encapsulate this function.
546
+ ```ruby
547
+ class MyController < SlackRubyBot::MVC::Controller::Base
548
+ def sayhello
549
+ client.say(channel: data.channel, text: "Received command #{match[:command]} with args #{match[:expression]}")
550
+ end
551
+ alternate_name :sayhello, :alternate_way_to_call_hello
552
+ end
553
+ MyController.new(MyModel.new, MyView.new)
554
+ ```
555
+ Note in the above example that the Controller instance method `sayhello` does not receive any arguments. When the instance method is called, the Controller class sets up some accessor methods to provide the normal `client`, `data`, and `match` objects. These are the same objects passed to the `command` block.
556
+
557
+ However, the Controller anticipates that the model and view objects should contain business logic that will also operate on the `client`, `data`, and `match` objects. The controller provides access to the model and view via the `model` and `view` accessor methods. The [inventory example](examples/inventory/inventorybot.rb) provides a full example of a Model, View, and Controller working together.
558
+
559
+ A Controller may need helper methods for certain work. To prevent the helper method from creating a route that the bot will respond to directly, the instance method name should begin with an underscore (e.g. `_my_helper_method`). When building the bot routes, these methods will be skipped.
560
+
561
+ Calling `alternate_name` after the method definition allows for method aliases similar to the regular `command` structure. When commands can be triggered by multiple text strings it's useful to have that ability map to the controller methods too.
562
+
563
+ Lastly, the Controller class includes `ActiveSupport::Callbacks` which allows for full flexibility in creating `before`, `after`, and `around` hooks for all methods. Again, see the [inventory example](examples/inventory/inventorybot.rb) for more information.
564
+
565
+ #### Model
566
+
567
+ A complex bot may need to read or write data from a database or other network resource. Setting up and tearing down these connections can be costly, so the model can do it once upon instantiation.
568
+
569
+ The Model also includes `ActiveSupport::Callbacks`.
570
+
571
+ ```ruby
572
+ class MyModel < SlackRubyBot::MVC::Model::Base
573
+ define_callbacks :sanitize
574
+ set_callback :sanitize, :around, :sanitize_resource
575
+ attr_accessor :_resource
576
+
577
+ def initialize
578
+ @db = setup_database_connection
579
+ end
580
+
581
+ def read(resource)
582
+ self._resource = resource
583
+ run_callbacks :sanitize do
584
+ @db.select(:column1 => resource)
585
+ # ... do some expensive work
586
+ end
587
+ end
588
+
589
+ private
590
+
591
+ def sanitize_resource
592
+ self._resource.downcase
593
+ result = yield
594
+ puts "After read, result is #{result.inspect}"
595
+ end
596
+ end
597
+ ```
598
+
599
+ Like Controllers, the Model is automatically loaded with the latest version of the `client`, `data`, and `match` objects each time the controller method is called. Therefore the model will always have access to the latest objects when doing its work. It will typically only use the `data` and `match` objects.
600
+
601
+ Model methods are not matched to routes, so there is no restriction on how to name methods as there is in Controllers.
602
+
603
+ #### View
604
+
605
+ A typical bot just writes to a channel or uses the web client to react/unreact to a message. More complex bots will probably require more complex behaviors. These should be stored in a `SlackRubyBot::MVC::View::Base` subclass.
606
+
607
+ ```ruby
608
+ class MyView < SlackRubyBot::MVC::View::Base
609
+ define_callbacks :logit
610
+ set_callbacks :logit, :around, :audit_trail
611
+
612
+ def initialize
613
+ @mailer = setup_mailer
614
+ @ftp = setup_ftp_handler
615
+ end
616
+
617
+ def email_admin(message)
618
+ run_callbacks :logit do
619
+ @mailer.send(:administrator, message)
620
+ end
621
+ end
622
+
623
+ def react_thumbsup
624
+ client.web_client.reactions_add(
625
+ name: :thumbs_up,
626
+ channel: data.channel,
627
+ timestamp: data.ts,
628
+ as_user: true)
629
+ end
630
+
631
+ def react_thumbsdown
632
+ client.web_client.reactions_remove(
633
+ name: :thumbs_up,
634
+ channel: data.channel,
635
+ timestamp: data.ts,
636
+ as_user: true)
637
+ end
638
+
639
+ private
640
+
641
+ def audit_trail
642
+ Logger.audit("Sending email at [#{Time.now}]")
643
+ yield
644
+ Logger.audit("Email sent by [#{Time.now}]")
645
+ end
646
+ end
647
+ ```
648
+ Again, the View will have access to the most up to date `client`, `data`, and `match` objects. It will typically only use the `client` and `data` objects.
649
+
650
+ View methods are not matched to routes, so there is no restriction on how to name methods as there is in Controllers.
651
+
652
+ ### RSpec Shared Behaviors
653
+
654
+ Slack-ruby-bot ships with a number of shared RSpec behaviors that can be used in your RSpec tests.
655
+
656
+ * [behaves like a slack bot](lib/slack-ruby-bot/rspec/support/slack-ruby-bot/it_behaves_like_a_slack_bot.rb): A bot quacks like a Slack Ruby bot.
657
+ * [respond with slack message](lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_slack_message.rb): The bot responds with a message.
658
+ * [respond with slack messages](lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_slack_messages.rb): The bot responds with a multiple messages.
659
+ * [respond with error](lib/slack-ruby-bot/rspec/support/slack-ruby-bot/respond_with_error.rb): An exception is raised inside a bot command.
660
+
661
+ Require `slack-ruby-bot/rspec` in your `spec_helper.rb` along with the following dependencies in Gemfile.
662
+
663
+ ```ruby
664
+ group :development, :test do
665
+ gem 'rack-test'
666
+ gem 'rspec'
667
+ gem 'vcr'
668
+ gem 'webmock'
669
+ end
670
+ ```
671
+
672
+ ### Useful Libraries
673
+
674
+ * [newrelic-slack-ruby-bot](https://github.com/dblock/newrelic-slack-ruby-bot): NewRelic instrumentation for slack-ruby-bot.
675
+
676
+ ## Contributing
677
+
678
+ See [CONTRIBUTING](CONTRIBUTING.md).
679
+
680
+ ## Upgrading
681
+
682
+ See [CHANGELOG](CHANGELOG.md) for a history of changes and [UPGRADING](UPGRADING.md) for how to upgrade to more recent versions.
683
+
684
+ ## Copyright and License
685
+
686
+ Copyright (c) 2015-2016, [Daniel Doubrovkine](https://twitter.com/dblockdotorg), [Artsy](https://www.artsy.net) and [Contributors](CHANGELOG.md).
687
+
688
+ This project is licensed under the [MIT License](LICENSE.md).