slack-bot-server 0.3.0 → 0.4.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.
- 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
|
-
[](https://travis-ci.org/exciting-io/slack-bot-server)
|
3
|
+
[](https://travis-ci.org/exciting-io/slack-bot-server) [](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:
|