slack-bot-server 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +24 -5
- data/README.md +55 -33
- data/lib/slack_bot_server.rb +11 -0
- data/lib/slack_bot_server/bot.rb +275 -81
- data/lib/slack_bot_server/local_queue.rb +10 -0
- data/lib/slack_bot_server/redis_queue.rb +13 -2
- data/lib/slack_bot_server/remote_control.rb +26 -1
- data/lib/slack_bot_server/server.rb +111 -41
- data/lib/slack_bot_server/simple_bot.rb +6 -1
- data/lib/slack_bot_server/version.rb +2 -1
- data/slack_bot_server.gemspec +3 -1
- metadata +34 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 143cba07b6d366616e823e2f9a502d1d24a4af8d
|
4
|
+
data.tar.gz: b739efffeddf7b60a7e23dc797073f3629285b8f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dfdf002fd3b658f13e6feb5486586e3937c409bc6fad2e45de4b871b7dbf7d0806ed5ac6383b4afb1d88b374bfda9023a0694718aebb6f4941edd375bd777829
|
7
|
+
data.tar.gz: 5bc62c5f100ecc33f77ad809f8ca5952be5e9373ff710c834865f84048bcc2dd72663a9ffe4b0529a7a06e6235c36887b36013542d6d9829d96fc9b73cb78c9d
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,26 @@
|
|
1
|
-
## 0.
|
1
|
+
## 0.4.0
|
2
|
+
|
3
|
+
### Added
|
4
|
+
- Allow bots to send a 'typing' message
|
5
|
+
- Messages will be sent via the Real-Team API if possible (not all message parameters are acceptable there)
|
6
|
+
- Subsequent bot callbacks won't fire if an earlier one returns `false`
|
7
|
+
- `SlackBotServer::Bot` now exposes `bot_user_name`, `bot_user_id`, `team_name`, and `team_id` methods
|
8
|
+
- The logger can now be set via `SlackBotServer.logger=`
|
9
|
+
- Access the underlying Slack client via the `SlackBotServer::Bot#client` method
|
10
|
+
|
11
|
+
### Changes
|
12
|
+
- Swapped internal API library from `slack-api` to `slack-ruby-client`
|
13
|
+
- Improve internal bot logging API
|
14
|
+
- Ensure rtm data is reloaded when reconnecting
|
15
|
+
- Add missing/implicit requires to server.rb and bot.rb
|
16
|
+
- Only listen for instructions on the queue if its non-nil
|
17
|
+
- Fix bug where malformed bot key could crash when processing instructions
|
18
|
+
- Allow `SlackBotServer::RedisQueue.new` to take a custom redis key; note that this has changed the argument format of the initialiser
|
2
19
|
|
3
|
-
Changes:
|
4
20
|
|
5
|
-
|
6
|
-
|
7
|
-
|
21
|
+
## 0.3.0
|
22
|
+
|
23
|
+
### Changes
|
24
|
+
- The `SlackBotServer::Server#on_new_proc` has been renamed to `Server#on_add`
|
25
|
+
- The `add` and `add_bot` methods on `SlackBotServer::Server` and `SlackBotServer::RemoteControl` control have been merged as `add_bot`
|
26
|
+
- Multiple arguments may be passed via the `add_bot` method to the block given to `SlackBotServer::on_add`
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# SlackBotServer
|
2
2
|
|
3
|
-
[![Build Status](https://travis-ci.org/exciting-io/slack-bot-server.svg)](https://travis-ci.org/exciting-io/slack-bot-server)
|
3
|
+
[![Build Status](https://travis-ci.org/exciting-io/slack-bot-server.svg)](https://travis-ci.org/exciting-io/slack-bot-server) [![Documentation](http://img.shields.io/badge/yard-docs-blue.svg)](http://www.rubydoc.info/github/exciting-io/slack-bot-server)
|
4
4
|
|
5
5
|
If you're building an integration just for yourself, running a single bot isn't too hard and there are plenty of examples available. However, if you're building an integration for your *product* to connect with multiple teams, running multiple instances of that bot is a bit trickier.
|
6
6
|
|
@@ -55,34 +55,7 @@ server.start
|
|
55
55
|
|
56
56
|
Running this script will start a server and keep it running; you may wish to use a tool like [Foreman](http://ddollar.github.io/foreman/) to actually start it and manage it in production.
|
57
57
|
|
58
|
-
###
|
59
|
-
|
60
|
-
The provided example `SimpleBot` illustrates the main ways to build a bot:
|
61
|
-
|
62
|
-
```ruby
|
63
|
-
require 'slack_bot_server/bot'
|
64
|
-
|
65
|
-
class SlackBotServer::SimpleBot < SlackBotServer::Bot
|
66
|
-
# Set the username displayed in Slack
|
67
|
-
username 'SimpleBot'
|
68
|
-
|
69
|
-
# Respond to mentions in the connected chat room (defaults to #general).
|
70
|
-
# As well as the normal data provided by Slack's API, we add the `message`,
|
71
|
-
# which is the `text` parameter with the username stripped out. For example,
|
72
|
-
# When a user sends 'simple_bot: how are you?', the `message` data contains
|
73
|
-
# only 'how are you'.
|
74
|
-
on_mention do |data|
|
75
|
-
reply text: "You said '#{data['message']}', and I'm frankly fascinated."
|
76
|
-
end
|
77
|
-
|
78
|
-
# Respond to messages sent via IM communication directly with the bot.
|
79
|
-
on_im do
|
80
|
-
reply text: "Hmm, OK, let me get back to you about that."
|
81
|
-
end
|
82
|
-
end
|
83
|
-
```
|
84
|
-
|
85
|
-
### Advanced example
|
58
|
+
### Advanced server example
|
86
59
|
|
87
60
|
This is a more advanced example of a server script, based on the that used by [Harmonia](https://harmonia.io), the product from which this was extracted.
|
88
61
|
|
@@ -96,7 +69,7 @@ require 'harmonia/slack_bot'
|
|
96
69
|
# Use a Redis-based queue to add/remove bots and to trigger
|
97
70
|
# bot messages to be sent. In this case we connect to the same
|
98
71
|
# redis instance as Resque, just for convenience.
|
99
|
-
queue = SlackBotServer::RedisQueue.new(Resque.redis)
|
72
|
+
queue = SlackBotServer::RedisQueue.new(redis: Resque.redis)
|
100
73
|
|
101
74
|
server = SlackBotServer::Server.new(queue: queue)
|
102
75
|
|
@@ -130,16 +103,65 @@ end
|
|
130
103
|
server.start
|
131
104
|
```
|
132
105
|
|
106
|
+
### Writing a bot
|
107
|
+
|
108
|
+
The provided example `SimpleBot` illustrates the main ways to build a bot:
|
109
|
+
|
110
|
+
```ruby
|
111
|
+
require 'slack_bot_server/bot'
|
112
|
+
|
113
|
+
class SlackBotServer::SimpleBot < SlackBotServer::Bot
|
114
|
+
# Set the friendly username displayed in Slack
|
115
|
+
username 'SimpleBot'
|
116
|
+
# Set the image to use as an avatar icon in Slack
|
117
|
+
icon_url 'http://my.server.example.com/assets/icon.png'
|
118
|
+
|
119
|
+
# Respond to mentions in the connected chat room (defaults to #general).
|
120
|
+
# As well as the normal data provided by Slack's API, we add the `message`,
|
121
|
+
# which is the `text` parameter with the username stripped out. For example,
|
122
|
+
# When a user sends 'simple_bot: how are you?', the `message` data contains
|
123
|
+
# only 'how are you'.
|
124
|
+
on_mention do |data|
|
125
|
+
if data['message'] == 'who are you'
|
126
|
+
reply text: "I am #{user} (user id: #{user_id}, connected to team #{team} with team id #{team_id}"
|
127
|
+
else
|
128
|
+
reply text: "You said '#{data['message']}', and I'm frankly fascinated."
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Respond to messages sent via IM communication directly with the bot.
|
133
|
+
on_im do
|
134
|
+
reply text: "Hmm, OK, let me get back to you about that."
|
135
|
+
end
|
136
|
+
end
|
137
|
+
```
|
138
|
+
|
139
|
+
As well as the special `on_mention` and `on_im` blocks, there are a number
|
140
|
+
of other hooks that you can use when writing a bot:
|
141
|
+
|
142
|
+
* `on :message` -- will fire for every message that's received from Slack in
|
143
|
+
the rooms that this bot is a member of
|
144
|
+
* `on :start` -- will fire when the bot establishes a connection to Slack
|
145
|
+
(note that periodic disconnections will occur, so this hook is best used
|
146
|
+
to gather data about the current state of Slack. You should not assume
|
147
|
+
this is the first time the bot has ever connected)
|
148
|
+
* `on :finish` -- will fire when the bot is disconnected from Slack. This
|
149
|
+
may be because a disconnection happened, or might be because the bot was
|
150
|
+
removed from the server via the `remove_bot` command. You can check if
|
151
|
+
the bot was accidentally/intermittently disconnected via the `running?`
|
152
|
+
method, which will return true unless the bot was explicitly stopped.
|
153
|
+
|
154
|
+
|
133
155
|
### Managing bots
|
134
156
|
|
135
|
-
When someone in your application
|
157
|
+
When someone in your application wants to connect their account with Slack, they'll need to provide a bot API token, which your application should store.
|
136
158
|
|
137
159
|
In order to actually create and connect their bot, you can use the remote
|
138
160
|
control to add the token to the server.
|
139
161
|
|
140
162
|
```ruby
|
141
163
|
# Somewhere within your application
|
142
|
-
queue = SlackBotServer::RedisQueue.new(Redis.new)
|
164
|
+
queue = SlackBotServer::RedisQueue.new(redis: Redis.new)
|
143
165
|
slack_remote = SlackBotServer::RemoteControl.new(queue: queue)
|
144
166
|
slack_remote.add_bot('user-accounts-slack-api-token')
|
145
167
|
```
|
@@ -172,7 +194,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
172
194
|
|
173
195
|
## Contributing
|
174
196
|
|
175
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/exciting-io/
|
197
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/exciting-io/slack-bot-server.
|
176
198
|
|
177
199
|
|
178
200
|
## License
|
data/lib/slack_bot_server.rb
CHANGED
@@ -2,10 +2,21 @@ require 'slack_bot_server/version'
|
|
2
2
|
require 'slack_bot_server/server'
|
3
3
|
require 'logger'
|
4
4
|
|
5
|
+
# A framework for running and controlling multiple bots. This
|
6
|
+
# is designed to make it easier for developers to provide Slack
|
7
|
+
# integration for their applications, instead of having individual
|
8
|
+
# users run their own bot instances.
|
5
9
|
module SlackBotServer
|
10
|
+
# A Logger instance, defaulting to +INFO+ level
|
6
11
|
def self.logger
|
7
12
|
@logger ||= Logger.new(STDOUT)
|
8
13
|
end
|
14
|
+
|
15
|
+
# Assign the logger to be used by SlackBotServer
|
16
|
+
# @param logger [Logger]
|
17
|
+
def self.logger=(logger)
|
18
|
+
@logger = logger
|
19
|
+
end
|
9
20
|
end
|
10
21
|
|
11
22
|
SlackBotServer.logger.level = Logger::INFO
|
data/lib/slack_bot_server/bot.rb
CHANGED
@@ -1,87 +1,215 @@
|
|
1
1
|
require 'slack'
|
2
|
-
require 'slack
|
3
|
-
|
2
|
+
require 'slack-ruby-client'
|
3
|
+
|
4
|
+
# A superclass for integration bot implementations.
|
5
|
+
#
|
6
|
+
# A simple example:
|
7
|
+
#
|
8
|
+
# class MyBot < SlackBotServer::Bot
|
9
|
+
# # Set the friendly username displayed in Slack
|
10
|
+
# username 'My Bot'
|
11
|
+
# # Set the image to use as an avatar icon in Slack
|
12
|
+
# icon_url 'http://my.server.example.com/assets/icon.png'
|
13
|
+
#
|
14
|
+
# # Respond to mentions in the connected chat room (defaults to #general).
|
15
|
+
# # As well as the normal data provided by Slack's API, we add the `message`,
|
16
|
+
# # which is the `text` parameter with the username stripped out. For example,
|
17
|
+
# # When a user sends 'simple_bot: how are you?', the `message` data contains
|
18
|
+
# # only 'how are you'.
|
19
|
+
# on_mention do |data|
|
20
|
+
# if data['message'] == 'who are you'
|
21
|
+
# reply text: "I am #{user} (user id: #{user_id}, connected to team #{team} with team id #{team_id}"
|
22
|
+
# else
|
23
|
+
# reply text: "You said '#{data['message']}', and I'm frankly fascinated."
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # Respond to messages sent via IM communication directly with the bot.
|
28
|
+
# on_im do
|
29
|
+
# reply text: "Hmm, OK, let me get back to you about that."
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
#
|
4
33
|
class SlackBotServer::Bot
|
34
|
+
|
35
|
+
# The user ID of the special slack user +SlackBot+
|
5
36
|
SLACKBOT_USER_ID = 'USLACKBOT'
|
6
37
|
|
7
|
-
attr_reader :key
|
38
|
+
attr_reader :key, :token, :client
|
8
39
|
|
40
|
+
# Raised if Slack rejected the token during authentication.
|
9
41
|
class InvalidToken < RuntimeError; end
|
10
42
|
|
43
|
+
# Create a new bot.
|
44
|
+
# This is normally called from within the block passed to
|
45
|
+
# {SlackBotServer::Server#on_add}, which should return a new
|
46
|
+
# bot instance.
|
47
|
+
# @param token [String] the Slack bot token to use for authentication
|
48
|
+
# @param key [String] a key used to target messages to this bot from
|
49
|
+
# your application when using {RemoteControl}. If not provided,
|
50
|
+
# this defaults to the token.
|
11
51
|
def initialize(token:, key: nil)
|
12
52
|
@token = token
|
13
53
|
@key = key || @token
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@
|
54
|
+
@client = ::Slack::RealTime::Client.new(token: @token)
|
55
|
+
@im_channels = []
|
56
|
+
@channels = []
|
17
57
|
@connected = false
|
18
58
|
@running = false
|
19
59
|
|
20
|
-
raise InvalidToken unless
|
60
|
+
raise InvalidToken unless @client.web_client.auth_test['ok']
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the username (for @ replying) of the bot user we are connected as,
|
64
|
+
# e.g. +'simple_bot'+
|
65
|
+
def bot_user_name
|
66
|
+
@client.self['name']
|
67
|
+
end
|
68
|
+
|
69
|
+
# Returns the ID of the bot user we are connected as, e.g. +'U123456'+
|
70
|
+
def bot_user_id
|
71
|
+
@client.self['id']
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns the name of the team we are connected to, e.g. +'My Team'+
|
75
|
+
def team_name
|
76
|
+
@client.team['name']
|
77
|
+
end
|
78
|
+
|
79
|
+
# Returns the ID of the team we are connected to, e.g. +'T234567'+
|
80
|
+
def team_id
|
81
|
+
@client.team['id']
|
21
82
|
end
|
22
83
|
|
84
|
+
# Send a message to Slack
|
85
|
+
# @param options [Hash] a hash containing any of the following:
|
86
|
+
# channel:: the name ('#general'), or the ID of the channel to send to
|
87
|
+
# text:: the actual text of the message
|
88
|
+
# username:: the name the message should appear from; defaults to the
|
89
|
+
# value given to `username` in the Bot class definition
|
90
|
+
# icon_url:: the image url to use as the avatar for this message;
|
91
|
+
# defaults to the value given to `icon_url` in the Bot
|
92
|
+
# class definition
|
23
93
|
def say(options)
|
24
|
-
|
94
|
+
message = symbolize_keys(default_message_options.merge(options))
|
95
|
+
|
96
|
+
if rtm_incompatible_message?(message)
|
97
|
+
debug "Sending via Web API", message
|
98
|
+
@client.web_client.chat_postMessage(message)
|
99
|
+
else
|
100
|
+
debug "Sending via RTM API", message
|
101
|
+
@client.message(message)
|
102
|
+
end
|
25
103
|
end
|
26
104
|
|
105
|
+
# Sends a message to every channel this bot is a member of
|
106
|
+
# @param options [Hash] As {#say}, although the +:channel+ option is
|
107
|
+
# redundant
|
27
108
|
def broadcast(options)
|
28
|
-
@
|
29
|
-
say(options.merge(channel: channel))
|
109
|
+
@channels.each do |channel|
|
110
|
+
say(options.merge(channel: channel['id']))
|
30
111
|
end
|
31
112
|
end
|
32
113
|
|
114
|
+
# Sends a reply to the same channel as the last message that was
|
115
|
+
# received by this bot.
|
116
|
+
# @param options [Hash] As {#say}, although the +:channel+ option is
|
117
|
+
# redundant
|
33
118
|
def reply(options)
|
34
119
|
channel = @last_received_data['channel']
|
35
120
|
say(options.merge(channel: channel))
|
36
121
|
end
|
37
122
|
|
123
|
+
# Sends a message via IM to a user
|
124
|
+
# @param user_id [String] the Slack user ID of the person to receive this message
|
125
|
+
# @param options [Hash] As {#say}, although the +:channel+ option is
|
126
|
+
# redundant
|
38
127
|
def say_to(user_id, options)
|
39
|
-
result = @
|
128
|
+
result = @client.web_client.im_open(user: user_id)
|
40
129
|
channel = result['channel']['id']
|
41
130
|
say(options.merge(channel: channel))
|
42
131
|
end
|
43
132
|
|
133
|
+
# Sends a typing notification
|
134
|
+
# @param options [Hash] can contain +:channel+, which should be an ID; if no options
|
135
|
+
# are provided, the channel from the most recently recieved message is used
|
136
|
+
def typing(options={})
|
137
|
+
last_received_channel = @last_received_data ? @last_received_data['channel'] : nil
|
138
|
+
default_options = {channel: last_received_channel}
|
139
|
+
@client.typing(default_options.merge(options))
|
140
|
+
end
|
141
|
+
|
142
|
+
# Call a method directly on the Slack web API (via Slack::Web::Client).
|
143
|
+
# Useful for debugging only.
|
44
144
|
def call(method, args)
|
45
145
|
args.symbolize_keys!
|
46
|
-
@
|
146
|
+
@client.web_client.send(method, args)
|
47
147
|
end
|
48
148
|
|
149
|
+
# Starts the bot running.
|
150
|
+
# You should not call this method; instead, the server will call it
|
151
|
+
# when it is ready for the bot to connect
|
152
|
+
# @see Server#start
|
49
153
|
def start
|
50
154
|
@running = true
|
51
|
-
@ws = Faye::WebSocket::Client.new(websocket_url, nil, ping: 60)
|
52
155
|
|
53
|
-
@
|
156
|
+
@client.on :open do |event|
|
54
157
|
@connected = true
|
55
158
|
log "connected to '#{team_name}'"
|
56
159
|
run_callbacks(:start)
|
57
160
|
end
|
58
161
|
|
59
|
-
@
|
162
|
+
@client.on :message do |data|
|
60
163
|
begin
|
61
|
-
debug
|
62
|
-
handle_message(
|
164
|
+
debug message: data
|
165
|
+
handle_message(data)
|
63
166
|
rescue => e
|
64
167
|
log error: e
|
65
168
|
log backtrace: e.backtrace
|
66
169
|
end
|
67
170
|
end
|
68
171
|
|
69
|
-
@
|
172
|
+
@client.on :im_created do |data|
|
173
|
+
log "Adding new IM channel", data['channel']
|
174
|
+
@im_channels << data['channel']
|
175
|
+
end
|
176
|
+
|
177
|
+
@client.on :channel_joined do |data|
|
178
|
+
log "Adding new channel", data['channel']
|
179
|
+
@channels << data['channel']
|
180
|
+
end
|
181
|
+
|
182
|
+
@client.on :channel_left do |data|
|
183
|
+
channel_id = data['channel']
|
184
|
+
log "Removing channel: #{channel_id}"
|
185
|
+
@channels.delete_if { |c| c['id'] == channel_id }
|
186
|
+
end
|
187
|
+
|
188
|
+
@client.on :close do |event|
|
70
189
|
log "disconnected"
|
71
190
|
@connected = false
|
72
|
-
|
73
|
-
start
|
74
|
-
end
|
191
|
+
run_callbacks(:finish)
|
75
192
|
end
|
193
|
+
|
194
|
+
@client.start_async
|
76
195
|
end
|
77
196
|
|
197
|
+
# Stops the bot from running. You should not call this method; instead
|
198
|
+
# send the server a +remote_bot+ message
|
199
|
+
# @see Server#remove_bot
|
78
200
|
def stop
|
79
201
|
log "closing connection"
|
80
202
|
@running = false
|
81
|
-
@
|
203
|
+
@client.stop!
|
82
204
|
log "closed"
|
83
205
|
end
|
84
206
|
|
207
|
+
# Returns +true+ if this bot is (or should be) running
|
208
|
+
def running?
|
209
|
+
@running
|
210
|
+
end
|
211
|
+
|
212
|
+
# Returns +true+ if this bot is currently connected to Slack
|
85
213
|
def connected?
|
86
214
|
@connected
|
87
215
|
end
|
@@ -89,44 +217,117 @@ class SlackBotServer::Bot
|
|
89
217
|
class << self
|
90
218
|
attr_reader :mention_keywords
|
91
219
|
|
220
|
+
# Sets the username this bot should use
|
221
|
+
#
|
222
|
+
# class MyBot < SlackBotServer::Bot
|
223
|
+
# username 'My Bot'
|
224
|
+
#
|
225
|
+
# # etc
|
226
|
+
# end
|
227
|
+
#
|
228
|
+
# will result in the friendly name 'My Bot' appearing beside
|
229
|
+
# the messages in your Slack rooms
|
92
230
|
def username(name)
|
93
231
|
default_message_options[:username] = name
|
94
232
|
end
|
95
233
|
|
234
|
+
# Sets the image to use as an avatar for this bot
|
235
|
+
#
|
236
|
+
# class MyBot < SlackBotServer::Bot
|
237
|
+
# icon_url 'http://example.com/bot.png'
|
238
|
+
#
|
239
|
+
# # etc
|
240
|
+
# end
|
96
241
|
def icon_url(url)
|
97
242
|
default_message_options[:icon_url] = url
|
98
243
|
end
|
99
244
|
|
245
|
+
# Sets the keywords in messages that will trigger the
|
246
|
+
# +on_mention+ callback
|
247
|
+
#
|
248
|
+
# class MyBot < SlackBotServer::Bot
|
249
|
+
# mention_as 'hey', 'bot'
|
250
|
+
#
|
251
|
+
# # etc
|
252
|
+
# end
|
253
|
+
#
|
254
|
+
# will mean the +on_mention+ callback fires for messages
|
255
|
+
# like "hey you!" and "bot, what are you thinking".
|
256
|
+
#
|
257
|
+
# Mention keywords are only matched at the start of messages,
|
258
|
+
# so the text "I love you, bot" won't trigger this callback.
|
259
|
+
# To implement general keyword spotting, use a custom
|
260
|
+
# +on :message+ callback.
|
261
|
+
#
|
262
|
+
# If this is not called, the default mention keyword is the
|
263
|
+
# bot username, e.g. +simple_bot+
|
100
264
|
def mention_as(*keywords)
|
101
265
|
@mention_keywords = keywords
|
102
266
|
end
|
103
267
|
|
268
|
+
# Holds default options to send with each message to Slack
|
104
269
|
def default_message_options
|
105
|
-
@default_message_options ||= {}
|
270
|
+
@default_message_options ||= {type: 'message'}
|
106
271
|
end
|
107
272
|
|
273
|
+
# All callbacks defined on this class
|
108
274
|
def callbacks
|
109
275
|
@callbacks ||= {}
|
110
276
|
end
|
111
277
|
|
278
|
+
# Returns all callbacks (including those in superclasses) for a given
|
279
|
+
# event type
|
112
280
|
def callbacks_for(type)
|
113
|
-
matching_callbacks = callbacks[type.to_sym] || []
|
114
281
|
if superclass.respond_to?(:callbacks_for)
|
115
|
-
matching_callbacks
|
282
|
+
matching_callbacks = superclass.callbacks_for(type)
|
283
|
+
else
|
284
|
+
matching_callbacks = []
|
116
285
|
end
|
117
|
-
matching_callbacks.
|
286
|
+
matching_callbacks += callbacks[type.to_sym] if callbacks[type.to_sym]
|
287
|
+
matching_callbacks
|
118
288
|
end
|
119
289
|
|
290
|
+
# Register a callback
|
291
|
+
#
|
292
|
+
# class MyBot < SlackBotServer::Bot
|
293
|
+
# on :message do
|
294
|
+
# reply text: 'I heard a message, so now I am responding!'
|
295
|
+
# end
|
296
|
+
# end
|
297
|
+
#
|
298
|
+
# Possible callbacks are:
|
299
|
+
# +:start+ :: fires when the bot establishes a connection to Slack
|
300
|
+
# +:finish+ :: fires when the bot is disconnected from Slack
|
301
|
+
# +:message+ :: fires when any message is sent in any channel the bot is
|
302
|
+
# connected to
|
303
|
+
#
|
304
|
+
# Multiple blocks for each type can be registered; they will be run
|
305
|
+
# in the order they are defined.
|
306
|
+
#
|
307
|
+
# If any block returns +false+, later blocks will not be fired.
|
120
308
|
def on(type, &block)
|
121
309
|
callbacks[type.to_sym] ||= []
|
122
310
|
callbacks[type.to_sym] << block
|
123
311
|
end
|
124
312
|
|
313
|
+
# Define a callback to run when any of the mention keywords are
|
314
|
+
# present in a message.
|
315
|
+
#
|
316
|
+
# Typically this will be for messages in open channels, where as
|
317
|
+
# user directs a message to this bot, e.g. "@simple_bot hello"
|
318
|
+
#
|
319
|
+
# By default, the mention keyword is simply the bot's username
|
320
|
+
# e.g. +simple_bot+
|
321
|
+
#
|
322
|
+
# As well as the raw Slack data about the message, the data +Hash+
|
323
|
+
# yielded to the given block will contain a +'message'+ key,
|
324
|
+
# which holds the text sent with the keyword removed.
|
125
325
|
def on_mention(&block)
|
126
326
|
on(:message) do |data|
|
327
|
+
debug on_message: data, bot_message: bot_message?(data)
|
127
328
|
if !bot_message?(data) &&
|
128
329
|
(data['text'] =~ /\A(#{mention_keywords.join('|')})[\s\:](.*)/i ||
|
129
|
-
data['text'] =~ /\A(<@#{
|
330
|
+
data['text'] =~ /\A(<@#{bot_user_id}>)[\s\:](.*)/)
|
130
331
|
message = $2.strip
|
131
332
|
@last_received_data = data.merge('message' => message)
|
132
333
|
instance_exec(@last_received_data, &block)
|
@@ -134,8 +335,11 @@ class SlackBotServer::Bot
|
|
134
335
|
end
|
135
336
|
end
|
136
337
|
|
338
|
+
# Define a callback to run when any a user sends a direct message
|
339
|
+
# to this bot
|
137
340
|
def on_im(&block)
|
138
341
|
on(:message) do |data|
|
342
|
+
debug on_im: data, bot_message: bot_message?(data), is_im_channel: is_im_channel?(data['channel'])
|
139
343
|
if is_im_channel?(data['channel']) && !bot_message?(data)
|
140
344
|
@last_received_data = data.merge('message' => data['text'])
|
141
345
|
instance_exec(@last_received_data, &block)
|
@@ -148,100 +352,83 @@ class SlackBotServer::Bot
|
|
148
352
|
load_channels
|
149
353
|
end
|
150
354
|
|
151
|
-
on :
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
end
|
156
|
-
|
157
|
-
on :channel_joined do |data|
|
158
|
-
channel_id = data['channel']['id']
|
159
|
-
log "Adding new channel: #{channel_id}"
|
160
|
-
@channel_ids << channel_id
|
161
|
-
end
|
162
|
-
|
163
|
-
on :channel_left do |data|
|
164
|
-
channel_id = data['channel']
|
165
|
-
log "Removing channel: #{channel_id}"
|
166
|
-
@channel_ids.delete(channel_id)
|
355
|
+
on :finish do
|
356
|
+
if @running
|
357
|
+
start
|
358
|
+
end
|
167
359
|
end
|
168
360
|
|
361
|
+
# Returns a String representation of this {Bot}
|
362
|
+
# @return String
|
169
363
|
def to_s
|
170
364
|
"<#{self.class.name} key:#{key}>"
|
171
365
|
end
|
172
366
|
|
173
367
|
private
|
174
368
|
|
175
|
-
def handle_message(
|
176
|
-
data
|
177
|
-
run_callbacks(data["type"], data) if data["type"]
|
369
|
+
def handle_message(data)
|
370
|
+
run_callbacks(data['type'], data)
|
178
371
|
end
|
179
372
|
|
180
373
|
def run_callbacks(type, data=nil)
|
181
374
|
relevant_callbacks = self.class.callbacks_for(type)
|
182
375
|
relevant_callbacks.each do |c|
|
183
|
-
instance_exec(data, &c)
|
376
|
+
response = instance_exec(data, &c)
|
377
|
+
break if response == false
|
184
378
|
end
|
185
379
|
end
|
186
380
|
|
187
|
-
def log(
|
188
|
-
|
189
|
-
text = "[BOT/#{user}] #{text}"
|
190
|
-
SlackBotServer.logger.info(message)
|
191
|
-
end
|
192
|
-
|
193
|
-
def debug(message)
|
194
|
-
text = message.is_a?(String) ? message : message.inspect
|
195
|
-
text = "[BOT/#{user}] #{text}"
|
196
|
-
SlackBotServer.logger.debug(message)
|
197
|
-
end
|
198
|
-
|
199
|
-
def user
|
200
|
-
rtm_start_data['self']['name']
|
381
|
+
def log(*args)
|
382
|
+
SlackBotServer.logger.info(log_string(*args))
|
201
383
|
end
|
202
384
|
|
203
|
-
def
|
204
|
-
|
385
|
+
def debug(*args)
|
386
|
+
SlackBotServer.logger.debug(log_string(*args))
|
205
387
|
end
|
206
388
|
|
207
|
-
def
|
208
|
-
|
389
|
+
def log_string(*args)
|
390
|
+
text = if args.length == 1 && args.first.is_a?(String)
|
391
|
+
args.first
|
392
|
+
else
|
393
|
+
args.map { |a| a.is_a?(String) ? a : a.inspect }.join(", ")
|
394
|
+
end
|
395
|
+
"[BOT/#{bot_user_name}] #{text}"
|
209
396
|
end
|
210
397
|
|
211
398
|
def load_channels
|
212
399
|
log "Loading channels"
|
213
|
-
@
|
214
|
-
log im_channels: @
|
215
|
-
@
|
216
|
-
log channels: @
|
400
|
+
@im_channels = @client.ims
|
401
|
+
log im_channels: @im_channels
|
402
|
+
@channels = @client.channels.select { |d| d['is_member'] == true }
|
403
|
+
log channels: @channels
|
217
404
|
end
|
218
405
|
|
219
|
-
def
|
220
|
-
|
221
|
-
end
|
222
|
-
|
223
|
-
def rtm_start_data
|
224
|
-
@rtm_start_data ||= @api.post('rtm.start')
|
406
|
+
def channel(id)
|
407
|
+
(@channels + @im_channels).find { |c| c['id'] == id }
|
225
408
|
end
|
226
409
|
|
227
410
|
def is_im_channel?(id)
|
228
|
-
|
411
|
+
channel(id)['is_im'] == true
|
229
412
|
end
|
230
413
|
|
231
414
|
def bot_message?(data)
|
232
415
|
data['subtype'] == 'bot_message' ||
|
233
416
|
data['user'] == SLACKBOT_USER_ID ||
|
234
|
-
data['user'] ==
|
417
|
+
data['user'] == bot_user_id ||
|
235
418
|
change_to_previous_bot_message?(data)
|
236
419
|
end
|
237
420
|
|
238
421
|
def change_to_previous_bot_message?(data)
|
239
422
|
data['subtype'] == 'message_changed' &&
|
240
|
-
data['previous_message']['user'] ==
|
423
|
+
data['previous_message']['user'] == bot_user_id
|
241
424
|
end
|
242
425
|
|
243
|
-
def
|
244
|
-
|
426
|
+
def rtm_incompatible_message?(data)
|
427
|
+
data[:attachments].nil? ||
|
428
|
+
data[:username].nil? ||
|
429
|
+
data[:icon_url].nil? ||
|
430
|
+
data[:icon_emoji].nil? ||
|
431
|
+
data[:channel].match(/^#/).nil?
|
245
432
|
end
|
246
433
|
|
247
434
|
def default_message_options
|
@@ -249,6 +436,13 @@ class SlackBotServer::Bot
|
|
249
436
|
end
|
250
437
|
|
251
438
|
def mention_keywords
|
252
|
-
self.class.mention_keywords || [
|
439
|
+
self.class.mention_keywords || [bot_user_name]
|
440
|
+
end
|
441
|
+
|
442
|
+
def symbolize_keys(hash)
|
443
|
+
hash.keys.each do |key|
|
444
|
+
hash[key.to_sym] = hash.delete(key)
|
445
|
+
end
|
446
|
+
hash
|
253
447
|
end
|
254
448
|
end
|
@@ -1,12 +1,22 @@
|
|
1
|
+
# A local implementation of a queue.
|
2
|
+
#
|
3
|
+
# Obviously this can't be used to communicate between
|
4
|
+
# multiple processes, let alone multiple machines, but
|
5
|
+
# it serves to demonstrate the expected API.
|
1
6
|
class SlackBotServer::LocalQueue
|
7
|
+
# Creates a new local in-memory queue
|
2
8
|
def initialize
|
3
9
|
@queue = Queue.new
|
4
10
|
end
|
5
11
|
|
12
|
+
# Push a value onto the back of the queue
|
6
13
|
def push(value)
|
7
14
|
@queue << value
|
8
15
|
end
|
9
16
|
|
17
|
+
# Pop a value from the front of the queue
|
18
|
+
# @return [Object, nil] returns the object from the front of the
|
19
|
+
# queue, or nil if the queue is empty
|
10
20
|
def pop
|
11
21
|
value = @queue.pop(true) rescue ThreadError
|
12
22
|
value == ThreadError ? nil : value
|
@@ -1,8 +1,14 @@
|
|
1
1
|
require 'multi_json'
|
2
2
|
|
3
|
+
# An implementation of the quue interface that uses
|
4
|
+
# Redis as a data conduit.
|
3
5
|
class SlackBotServer::RedisQueue
|
4
|
-
|
5
|
-
|
6
|
+
# Creates a new queue
|
7
|
+
# @param redis [Redis] an instance of the ruby +Redis+ client. If
|
8
|
+
# nil, one will be created using the default hostname and port
|
9
|
+
# @param key [String] the key to store the queue against
|
10
|
+
def initialize(redis: nil, key: 'slack_bot_server:queue')
|
11
|
+
@key = key
|
6
12
|
@redis = if redis
|
7
13
|
redis
|
8
14
|
else
|
@@ -11,10 +17,15 @@ class SlackBotServer::RedisQueue
|
|
11
17
|
end
|
12
18
|
end
|
13
19
|
|
20
|
+
# Push a value onto the back of the queue.
|
21
|
+
# @param value [Object] this will be turned into JSON when stored
|
14
22
|
def push(value)
|
15
23
|
@redis.rpush @key, MultiJson.dump(value)
|
16
24
|
end
|
17
25
|
|
26
|
+
# Pop a value from the front of the queue
|
27
|
+
# @return [Object] the object on the queue, reconstituted from its
|
28
|
+
# JSON string
|
18
29
|
def pop
|
19
30
|
json_value = @redis.lpop @key
|
20
31
|
if json_value
|
@@ -3,32 +3,57 @@
|
|
3
3
|
# This should be initialized with a queue that is shared with the
|
4
4
|
# targetted server (e.g. the same local queue instance, or a
|
5
5
|
# redis queue instance that points at the same redis server).
|
6
|
-
|
7
6
|
class SlackBotServer::RemoteControl
|
7
|
+
# Create a new instance of a remote control
|
8
|
+
# @param queue [Object] any Object conforming to the queue API
|
9
|
+
# (i.e. with #push and #pop methods)
|
8
10
|
def initialize(queue:)
|
9
11
|
@queue = queue
|
10
12
|
end
|
11
13
|
|
14
|
+
# Sends an +add_bot+ command to the {SlackBotServer::Server server}.
|
15
|
+
# See {SlackBotServer::Server#add_bot} for arguments.
|
12
16
|
def add_bot(*args)
|
13
17
|
@queue.push([:add_bot, *args])
|
14
18
|
end
|
15
19
|
|
20
|
+
# Sends a +remove_bot+ command to the server.
|
21
|
+
# @param key [String] the key of the bot to remove.
|
16
22
|
def remove_bot(key)
|
17
23
|
@queue.push([:remove_bot, key])
|
18
24
|
end
|
19
25
|
|
26
|
+
# Sends an +broadcast+ command to the {SlackBotServer::Server server}.
|
27
|
+
# @param key [String] the key of the bot which should send the message
|
28
|
+
# @param message_data [Hash] passed directly to
|
29
|
+
# {SlackBotServer::Bot#broadcast}; see there for argument details.
|
20
30
|
def broadcast(key, message_data)
|
21
31
|
@queue.push([:broadcast, key, message_data])
|
22
32
|
end
|
23
33
|
|
34
|
+
# Sends an +say+ command to the {SlackBotServer::Server server}.
|
35
|
+
# @param key [String] the key of the bot which should send the message.
|
36
|
+
# @param message_data [Hash] passed directly to
|
37
|
+
# {SlackBotServer::Bot#say}; see there for argument details.
|
24
38
|
def say(key, message_data)
|
25
39
|
@queue.push([:say, key, message_data])
|
26
40
|
end
|
27
41
|
|
42
|
+
# Sends an +say_to+ command to the {SlackBotServer::Server server}.
|
43
|
+
# @param key [String] the key of the bot which should send the message.
|
44
|
+
# @param user_id [String] the Slack user ID of the person who should
|
45
|
+
# receive the message.
|
46
|
+
# @param message_data [Hash] passed directly to
|
47
|
+
# {SlackBotServer::Bot#say_to}; see there for argument details.
|
28
48
|
def say_to(key, user_id, message_data)
|
29
49
|
@queue.push([:say_to, key, user_id, message_data])
|
30
50
|
end
|
31
51
|
|
52
|
+
# Sends a message to be called directly on the slack web API. Generally
|
53
|
+
# for debugging only.
|
54
|
+
# @param key [String] the key of the bot which should send the message.
|
55
|
+
# @param method [String, Symbol] the name of the method to call
|
56
|
+
# @param args [Array] the arguments for the method to call
|
32
57
|
def call(key, method, args)
|
33
58
|
@queue.push([:call, [key, method, args]])
|
34
59
|
end
|
@@ -1,10 +1,58 @@
|
|
1
1
|
require 'slack_bot_server/bot'
|
2
2
|
require 'slack_bot_server/simple_bot'
|
3
3
|
require 'slack_bot_server/redis_queue'
|
4
|
+
require 'eventmachine'
|
4
5
|
|
6
|
+
# Implements a server for running multiple Slack bots. Bots can be
|
7
|
+
# dynamically added and removed, and can be interacted with from
|
8
|
+
# external services (like your application).
|
9
|
+
#
|
10
|
+
# To use this, you should create a script to run along side your
|
11
|
+
# application. A simple example:
|
12
|
+
#
|
13
|
+
# #!/usr/bin/env ruby
|
14
|
+
#
|
15
|
+
# require 'slack_bot_server'
|
16
|
+
# require 'slack_bot_server/redis_queue'
|
17
|
+
# require 'slack_bot_server/simple_bot'
|
18
|
+
#
|
19
|
+
# # Use a Redis-based queue to add/remove bots and to trigger
|
20
|
+
# # bot messages to be sent
|
21
|
+
# queue = SlackBotServer::RedisQueue.new
|
22
|
+
#
|
23
|
+
# # Create a new server using that queue
|
24
|
+
# server = SlackBotServer::Server.new(queue: queue)
|
25
|
+
#
|
26
|
+
# # How your application-specific should be created when the server
|
27
|
+
# # is told about a new slack api token to connect with
|
28
|
+
# server.on_add do |token|
|
29
|
+
# # Return a new bot instance to the server. `SimpleBot` is a provided
|
30
|
+
# # example bot with some very simple behaviour.
|
31
|
+
# SlackBotServer::SimpleBot.new(token: token)
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# # Start the server. This method blocks, and will not return until
|
35
|
+
# # the server is killed.
|
36
|
+
# server.start
|
37
|
+
#
|
38
|
+
# The key features are:
|
39
|
+
#
|
40
|
+
# * creating a queue as a conduit for commands from your app
|
41
|
+
# * creating an instance of {Server} with that queue
|
42
|
+
# * defining an #on_add block, which is run whenever you need to
|
43
|
+
# start a new bot. This block contains the custom code relevant to
|
44
|
+
# your particular service, most typically the instantiation of a bot
|
45
|
+
# class that implements the logic you want
|
46
|
+
# * calling {Server#start}, to actually run the server and start
|
47
|
+
# listening for commands from the queue and connecting bots to Slack
|
48
|
+
# itself
|
49
|
+
#
|
5
50
|
class SlackBotServer::Server
|
6
51
|
attr_reader :queue
|
7
52
|
|
53
|
+
# Creates a new {Server}
|
54
|
+
# @param queue [Object] anything that implements the queue protocol
|
55
|
+
# (e.g. #push and #pop)
|
8
56
|
def initialize(queue: SlackBotServer::LocalQueue.new)
|
9
57
|
@queue = queue
|
10
58
|
@bots = {}
|
@@ -12,41 +60,43 @@ class SlackBotServer::Server
|
|
12
60
|
@running = false
|
13
61
|
end
|
14
62
|
|
15
|
-
# Define the block which should be called when the
|
16
|
-
# called, or the
|
63
|
+
# Define the block which should be called when the #add_bot method is
|
64
|
+
# called, or the +add_bot+ message is sent via a queue. This block
|
17
65
|
# should return a bot (which responds to start), in which case it will
|
18
66
|
# be added and started. If anything else is returned, it will be ignored.
|
19
67
|
def on_add(&block)
|
20
68
|
@add_proc = block
|
21
69
|
end
|
22
70
|
|
71
|
+
# Starts the server. This method will not return; call it at the
|
72
|
+
# end of your server script. It will start all bots it knows about
|
73
|
+
# (i.e. bots added via #add_bot before the server was started),
|
74
|
+
# and then listen for new instructions.
|
75
|
+
# @see Bot#start
|
23
76
|
def start
|
24
77
|
EM.run do
|
25
78
|
@running = true
|
26
79
|
@bots.each { |key, bot| bot.start }
|
27
|
-
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def add_timers
|
32
|
-
EM.add_periodic_timer(1) do
|
33
|
-
begin
|
34
|
-
next_message = queue.pop
|
35
|
-
process_instruction(next_message) if next_message
|
36
|
-
rescue => e
|
37
|
-
log_error(e)
|
38
|
-
end
|
80
|
+
listen_for_instructions if queue
|
39
81
|
end
|
40
82
|
end
|
41
83
|
|
84
|
+
# Starts the server in the background, via a Thread
|
42
85
|
def start_in_background
|
43
86
|
Thread.start { start }
|
44
87
|
end
|
45
88
|
|
89
|
+
# Find a bot added to this server. Returns nil if no bot was found
|
90
|
+
# @param key [String] the key of the bot we're looking for
|
91
|
+
# @return Bot
|
46
92
|
def bot(key)
|
47
93
|
@bots[key.to_sym]
|
48
94
|
end
|
49
95
|
|
96
|
+
# Adds a bot to this server
|
97
|
+
# Calls the block given to {#on_add} with the arguments given. The block
|
98
|
+
# should yield a bot, typically a subclass of {Bot}.
|
99
|
+
# @see #on_add
|
50
100
|
def add_bot(*args)
|
51
101
|
bot = @add_proc.call(*args)
|
52
102
|
if bot.respond_to?(:start)
|
@@ -58,6 +108,9 @@ class SlackBotServer::Server
|
|
58
108
|
log_error(e)
|
59
109
|
end
|
60
110
|
|
111
|
+
# Stops and removes a bot from the server
|
112
|
+
# @param key [String] the key of the bot to remove
|
113
|
+
# @see SlackBotServer::Bot#stop
|
61
114
|
def remove_bot(key)
|
62
115
|
if (bot = bot(key))
|
63
116
|
bot.stop
|
@@ -69,36 +122,45 @@ class SlackBotServer::Server
|
|
69
122
|
|
70
123
|
private
|
71
124
|
|
125
|
+
def listen_for_instructions
|
126
|
+
EM.add_periodic_timer(1) do
|
127
|
+
begin
|
128
|
+
next_message = queue.pop
|
129
|
+
process_instruction(next_message) if next_message
|
130
|
+
rescue => e
|
131
|
+
log_error(e)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
72
136
|
def process_instruction(instruction)
|
73
137
|
type, *args = instruction
|
74
|
-
|
75
|
-
|
76
|
-
log "adding bot: #{args.inspect}"
|
77
|
-
add_bot(*args)
|
78
|
-
when :remove_bot
|
79
|
-
key = args.first
|
80
|
-
remove_bot(key)
|
81
|
-
when :broadcast
|
82
|
-
key, message_data = args
|
83
|
-
log "[#{key}] broadcast: #{message_data}"
|
84
|
-
bot = bot(key)
|
85
|
-
bot.broadcast(message_data)
|
86
|
-
when :say
|
87
|
-
key, message_data = args
|
88
|
-
log "[#{key}] say: #{message_data}"
|
89
|
-
bot = bot(key)
|
90
|
-
bot.say(message_data)
|
91
|
-
when :say_to
|
92
|
-
key, user_id, message_data = args
|
93
|
-
log "[#{key}] say_to: (#{user_id}) #{message_data}"
|
94
|
-
bot = bot(key)
|
95
|
-
bot.say_to(user_id, message_data)
|
96
|
-
when :call
|
97
|
-
key, method, method_args = args
|
98
|
-
bot = bot(key)
|
99
|
-
bot.call(method, method_args)
|
138
|
+
bot_key = args.shift
|
139
|
+
if type == :add_bot
|
140
|
+
log "adding bot: #{bot_key} #{args.inspect}"
|
141
|
+
add_bot(bot_key, *args)
|
100
142
|
else
|
101
|
-
|
143
|
+
with_bot(bot_key) do |bot|
|
144
|
+
case type.to_sym
|
145
|
+
when :remove_bot
|
146
|
+
remove_bot(bot_key)
|
147
|
+
when :broadcast
|
148
|
+
log "[#{bot_key}] broadcast: #{args}"
|
149
|
+
bot.broadcast(*args)
|
150
|
+
when :say
|
151
|
+
log "[#{bot_key}] say: #{args}"
|
152
|
+
bot.say(*args)
|
153
|
+
when :say_to
|
154
|
+
user_id, message_data = args
|
155
|
+
log "[#{bot_key}] say_to: (#{user_id}) #{message_data}"
|
156
|
+
bot.say_to(user_id, message_data)
|
157
|
+
when :call
|
158
|
+
method, method_args = args
|
159
|
+
bot.call(method, method_args)
|
160
|
+
else
|
161
|
+
log unknown_command: instruction
|
162
|
+
end
|
163
|
+
end
|
102
164
|
end
|
103
165
|
end
|
104
166
|
|
@@ -111,4 +173,12 @@ class SlackBotServer::Server
|
|
111
173
|
SlackBotServer.logger.warn("Error in server: #{e} - #{e.message}")
|
112
174
|
SlackBotServer.logger.warn(e.backtrace.join("\n"))
|
113
175
|
end
|
176
|
+
|
177
|
+
def with_bot(key)
|
178
|
+
if bot = bot(key)
|
179
|
+
yield bot
|
180
|
+
else
|
181
|
+
log("Unknown bot: #{key}")
|
182
|
+
end
|
183
|
+
end
|
114
184
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'slack_bot_server/bot'
|
2
2
|
|
3
|
+
# A simple demonstration of a bot
|
3
4
|
class SlackBotServer::SimpleBot < SlackBotServer::Bot
|
4
5
|
# Set the username displayed in Slack
|
5
6
|
username 'SimpleBot'
|
@@ -10,7 +11,11 @@ class SlackBotServer::SimpleBot < SlackBotServer::Bot
|
|
10
11
|
# When a user sends 'simple_bot: how are you?', the `message` data contains
|
11
12
|
# only 'how are you'.
|
12
13
|
on_mention do |data|
|
13
|
-
|
14
|
+
if data['message'] == 'who are you'
|
15
|
+
reply text: "I am #{user} (id: #{user_id}), connected to team #{team} (id #{team_id})"
|
16
|
+
else
|
17
|
+
reply text: "You said '#{data['message']}', and I'm frankly fascinated."
|
18
|
+
end
|
14
19
|
end
|
15
20
|
|
16
21
|
# Respond to messages sent via IM communication directly with the bot.
|
data/slack_bot_server.gemspec
CHANGED
@@ -19,7 +19,8 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
-
spec.add_dependency "slack-
|
22
|
+
spec.add_dependency "slack-ruby-client", "~> 0.5"
|
23
|
+
spec.add_dependency "faye-websocket", "~> 0.10"
|
23
24
|
spec.add_dependency "multi_json"
|
24
25
|
|
25
26
|
spec.add_development_dependency "bundler", "~> 1.10"
|
@@ -27,4 +28,5 @@ Gem::Specification.new do |spec|
|
|
27
28
|
spec.add_development_dependency "redis"
|
28
29
|
spec.add_development_dependency "rspec"
|
29
30
|
spec.add_development_dependency "rspec-eventmachine"
|
31
|
+
spec.add_development_dependency "yard"
|
30
32
|
end
|
metadata
CHANGED
@@ -1,29 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: slack-bot-server
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- James Adam
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-01-
|
11
|
+
date: 2016-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
|
-
name: slack-
|
14
|
+
name: slack-ruby-client
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0.5'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0.5'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: faye-websocket
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.10'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.10'
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: multi_json
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +122,20 @@ dependencies:
|
|
108
122
|
- - ">="
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: yard
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
111
139
|
description: This software lets you write and host multiple slack bots, potentially
|
112
140
|
for multiple different teams or even services.
|
113
141
|
email:
|
@@ -159,3 +187,4 @@ signing_key:
|
|
159
187
|
specification_version: 4
|
160
188
|
summary: A server for hosting slack bots.
|
161
189
|
test_files: []
|
190
|
+
has_rdoc:
|