slack-smart-bot 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5ad8c9b6d08d27ac0fa0f03b054b39032a2228f25082433b2d58d400dfabbc99
4
+ data.tar.gz: 7a37e23992b269849aa522a5f25865f8e143a22ced0777b6fb08fc1cec05bc3f
5
+ SHA512:
6
+ metadata.gz: 1388d4e8eff5990665fcf8da616eec0164d242b9b87fdb667c018894e465c76bd00cd709aebaf5bf26a0421f29692d81e1d3f80c46388f559c46023176762890
7
+ data.tar.gz: 00e0ba16a2b50d6bf64582bc3e8443694a473732a6c02175097be935c1ee252984ecbc151fe225aab0ceaa9aaf0b7d3ab286d0c7992cfe4794fcfdaa95267dc9
@@ -0,0 +1,5 @@
1
+ --readme README.md
2
+ --title 'slack-smart-bot - Create a Slack bot that is smart and easy to expand.'
3
+ --charset utf-8
4
+ --markup markdown
5
+ 'lib/**/*.rb' - '*.md' - 'LICENSE'
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019 Mario Ruiz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,248 @@
1
+ # Slack Smart Bot
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/slack-smart-bot.svg)](https://rubygems.org/gems/slack-smart-bot)
4
+
5
+ Create a Slack bot that is really smart and so easy to expand.
6
+
7
+ The main scope of this ruby gem is to be used internally in your company so teams can create team channels with their own bot to help them on their daily work, almost everything is suitable to be automated!!
8
+
9
+ slack-smart-bot can create bots on demand, create shortcuts, run ruby code... just on a chat channel, you can access it just from your mobile phone if you want and run those tests you forgot to run, get the results, restart a server... no limits.
10
+
11
+ ## Installation and configuration
12
+
13
+ $ gem install slack-smart-bot
14
+
15
+ After you install it you will need just a couple of things to configure it.
16
+
17
+ Create a file like this on the folder you want:
18
+
19
+ ```ruby
20
+ # the channel that will act like the master channel, main channel
21
+ MASTER_CHANNEL="my_master_channel"
22
+ #names of the master users
23
+ MASTER_USERS=["mario"]
24
+
25
+ require 'slack-smart-bot'
26
+
27
+ settings = {
28
+ nick: 'my_smart_bot', # the smart bot name
29
+ token: 'xxxxxxxxxxxxxxxxxx' # the API Slack token
30
+ }
31
+
32
+ SlackSmartBot.new(settings).listen
33
+ ```
34
+
35
+ The MASTER_CHANNEL will be the channel where you will be able to create other bots and will have special treatment.
36
+
37
+ The MASTER_USERS will have full access to everything. The names should be written exactly the same like they appear on Slack.
38
+
39
+ For the token remember you need to generate a token on the Slack web for the bot user.
40
+
41
+ This is something done in Slack, under [integrations](https://my.slack.com/services). Create a [new bot](https://my.slack.com/services/new/bot), and note its API token.
42
+
43
+ *Remember to invite the smart bot to the channels where they will be accessible before creating the bot*
44
+
45
+ ## Usage
46
+
47
+ ### creating the MASTER BOT
48
+ Let's guess the file you created was called my_smart_bot.rb so, just run it:
49
+ ```
50
+ ruby my_smart_bot.rb
51
+ ```
52
+
53
+ After the run, it will be generated a rules file with the same name but adding _rules, in this example: my_smart_bot_rules.rb
54
+
55
+ The rules file can be edited and will be only affecting this particular bot.
56
+
57
+ You can add all the rules you want for your bot in the rules file, this is an example:
58
+
59
+ ```ruby
60
+ def rules(from, command, processed)
61
+ firstname = from.split(" ").first
62
+ case command
63
+
64
+ # help: `echo SOMETHING`
65
+ # help: repeats SOMETHING
66
+ # help:
67
+ when /echo\s(.+)/i
68
+ respond $1
69
+
70
+ # help: `go to sleep`
71
+ # help: it will sleep the bot for 10 seconds
72
+ # help:
73
+ when /go\sto\ssleep/i
74
+ unless @questions.keys.include?(from)
75
+ ask("do you want me to take a siesta?", command, from)
76
+ else
77
+ case @questions[from]
78
+ when /yes/i, /yep/i, /sure/i
79
+ respond "zZzzzzzZZZZZZzzzzzzz!"
80
+ respond "I'll be sleeping for 10 secs... just for you"
81
+ sleep 10
82
+ when /no/i, /nope/i, /cancel/i
83
+ @questions.delete(from)
84
+ respond "Thanks, I'm happy to be awake"
85
+ else
86
+ respond "I don't understand"
87
+ ask("are you sure do you want me to sleep? (yes or no)", "go to sleep", from)
88
+ end
89
+ end
90
+ else
91
+ unless processed
92
+ resp = %w{ what huh sorry }.sample
93
+ respond "#{firstname}: #{resp}?"
94
+ end
95
+ end
96
+ end
97
+
98
+ ```
99
+ ### How to access the smart bot
100
+ You can access the bot directly on the MASTER CHANNEL, on a secondary channel where the bot is running and directly by opening a private chat with the bot, in this case the conversation will be just between you and the bot.
101
+
102
+ ### Available commands even when the bot is not listening to you
103
+ Some of the commands are available always even when the bot is not listening to you but it is running
104
+
105
+ **_`bot help`_**
106
+
107
+ **_`bot what can I do?`_**
108
+
109
+ >It will display all the commands we can use
110
+ >What is displayed by this command is what is written on your rules file like this: #help: THE TEXT TO SHOW
111
+
112
+ **_`Hello Bot`_**
113
+
114
+ **_`Hello THE_NAME_OF_THE_BOT`_**
115
+
116
+ >Also apart of Hello you can use Hallo, Hi, Hola, What's up, Hey, Hæ
117
+
118
+ >Bot starts listening to you
119
+
120
+ **_`Bye Bot`_**
121
+
122
+ **_`Bye THE_NAME_OF_THE_BOT`_**
123
+
124
+ >Also apart of Bye you can use Bæ, Good Bye, Adiós, Ciao, Bless, Bless Bless, Adeu
125
+
126
+ >Bot stops listening to you
127
+
128
+ **_`exit bot`_**
129
+
130
+ **_`quit bot`_**
131
+
132
+ **_`close bot`_**
133
+
134
+ >The bot stops running and also stops all the bots created from this master channel
135
+
136
+ >You can use this command only if you are an admin user and you are on the master channel
137
+
138
+ **_`start bot`_**
139
+
140
+ **_`start this bot`_**
141
+
142
+ >The bot will start to listen
143
+
144
+ >You can use this command only if you are an admin user
145
+
146
+ **_`pause bot`_**
147
+
148
+ **_`pause this bot`_**
149
+
150
+ >The bot will pause so it will listen only to admin commands
151
+
152
+ >You can use this command only if you are an admin user
153
+
154
+ **_`bot status`_**
155
+
156
+ >Displays the status of the bot
157
+
158
+ >If on master channel and admin user also it will display info about bots created
159
+
160
+ **_`create bot on CHANNEL_NAME`_**
161
+
162
+ >Creates a new bot on the channel specified.
163
+
164
+ >slack-smart-bot will create a default rules file specific for your channel.
165
+ You can edit it and add the rules you want.
166
+ As soon as you save the file after editing it will become available on your channel.
167
+
168
+ >It will work only if you are on Master channel
169
+
170
+ **_`kill bot on CHANNEL_NAME`_**
171
+
172
+ >Kills the bot on the specified channel
173
+
174
+ >Only works if you are on Master channel and you created that bot or you are an admin user
175
+
176
+ ### Available commands only when listening to you or on demand
177
+
178
+ All the commands described on here or on your specific Rules file can be used when the bot is listening to you or on demand.
179
+
180
+ For the bot to start listening to you you need to use the "Hi bot" command or one of the aliases
181
+
182
+ Also you can call any of these commands on demand by using:
183
+
184
+ **_`!THE_COMMAND`_**
185
+
186
+ **_`@bot THE_COMMAND`_**
187
+
188
+ **_`@smart THE_COMMAND`_**
189
+
190
+ **_`@BOT_NAME THE_COMMAND`_**
191
+
192
+ **_`BOT_NAME THE_COMMAND`_**
193
+
194
+ Apart of the specific commands you define on the rules file of the channel, you can use:
195
+
196
+ **_`ruby RUBY_CODE`_**
197
+
198
+ **_`code RUBY_CODE`_**
199
+
200
+ >runs the code supplied and returns the output. Examples:
201
+
202
+ >code puts (34344/99)*(34+14)
203
+
204
+ >ruby require 'json'; res=[]; 20.times {res<<rand(100)}; my_json={result: res}; puts my_json.to_json
205
+
206
+
207
+ **_`add shortcut NAME: COMMAND`_**
208
+
209
+ **_`add shortcut for all NAME: COMMAND`_**
210
+
211
+ **_`shortchut NAME: COMMAND`_**
212
+
213
+ **_`shortchut for all NAME: COMMAND`_**
214
+
215
+ >It will add a shortcut that will execute the command we supply.
216
+
217
+ >In case we supply 'for all' then the shorcut will be available for everybody
218
+
219
+ >Example:
220
+ >add shortcut for all Spanish account: /code require 'iso/iban'; 10.times {puts ISO::IBAN.random('ES')}
221
+
222
+ >Then to call this shortcut:
223
+
224
+ >sc spanish account
225
+
226
+ >shortcut Spanish Account
227
+
228
+ **_`delete shortcut NAME`_**
229
+
230
+ >It will delete the shortcut with the supplied name
231
+
232
+ **_`see shortcuts`_**
233
+
234
+ >It will display the shortcuts stored for the user and for :all
235
+
236
+ **_`id channel CHANNEL_NAME`_**
237
+ >shows the id of a channel name
238
+
239
+
240
+ ## Contributing
241
+
242
+ Bug reports and pull requests are welcome on GitHub at https://github.com/marioruiz/slack-smart-bot.
243
+
244
+
245
+ ## License
246
+
247
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
248
+
@@ -0,0 +1,681 @@
1
+ require "slack-ruby-client"
2
+ require "open-uri"
3
+ require "cgi"
4
+ require "json"
5
+ require "logger"
6
+ require "fileutils"
7
+ require "open3"
8
+
9
+ if ARGV.size == 0
10
+ CHANNEL = MASTER_CHANNEL
11
+ ON_MASTER_CHANNEL = true
12
+ ADMIN_USERS = MASTER_USERS
13
+ RULES_FILE = "#{$0.gsub(".rb", "_rules.rb")}" unless defined?(RULES_FILE)
14
+ unless File.exist?(RULES_FILE)
15
+ default_rules = (__FILE__).gsub(/\.rb$/, "_rules.rb")
16
+ FileUtils.copy_file(default_rules, RULES_FILE)
17
+ end
18
+ STATUS_INIT = :on
19
+ else
20
+ ON_MASTER_CHANNEL = false
21
+ CHANNEL = ARGV[0]
22
+ ADMIN_USERS = ARGV[1].split(",")
23
+ RULES_FILE = ARGV[2]
24
+ STATUS_INIT = ARGV[3].to_sym
25
+ end
26
+
27
+ SHORTCUTS_FILE = "slack-smart-bot_shortcuts_#{CHANNEL}.rb".gsub(" ", "_")
28
+
29
+ class SlackSmartBot
30
+ attr_accessor :config, :client, :wclient
31
+
32
+ def initialize(config)
33
+ Dir.mkdir("./logs") unless Dir.exist?("./logs")
34
+ Dir.mkdir("./shortcuts") unless Dir.exist?("./shortcuts")
35
+ logfile = File.basename(RULES_FILE.gsub("_rules_", "_logs_"), ".rb") + ".log"
36
+ @logger = Logger.new("./logs/#{logfile}")
37
+ config_log = config.dup
38
+ config_log.delete(:token)
39
+ @logger.info "Initializing bot: #{config_log.inspect}"
40
+
41
+ config[:channel] = CHANNEL
42
+ self.config = config
43
+
44
+ Slack.configure do |conf|
45
+ conf.token = config[:token]
46
+ end
47
+ self.wclient = Slack::Web::Client.new
48
+ self.client = Slack::RealTime::Client.new
49
+
50
+ @listening = Array.new
51
+
52
+ @bots_created = Hash.new()
53
+ @shortcuts = Hash.new()
54
+ @shortcuts[:all] = Hash.new()
55
+
56
+ if File.exist?("./shortcuts/#{SHORTCUTS_FILE}")
57
+ file_sc = IO.readlines("./shortcuts/#{SHORTCUTS_FILE}").join
58
+ unless file_sc.to_s() == ""
59
+ @shortcuts = eval(file_sc)
60
+ end
61
+ end
62
+
63
+ if ON_MASTER_CHANNEL and File.exist?($0.gsub(".rb", "_bots.rb"))
64
+ file_conf = IO.readlines($0.gsub(".rb", "_bots.rb")).join
65
+ unless file_conf.to_s() == ""
66
+ @bots_created = eval(file_conf)
67
+ if @bots_created.kind_of?(Hash)
68
+ @bots_created.each { |key, value|
69
+ @logger.info "ruby #{$0} \"#{value[:channel_name]}\" \"#{value[:admins]}\" \"#{value[:rules_file]}\" #{value[:status].to_sym}"
70
+ t = Thread.new do
71
+ `ruby #{$0} \"#{value[:channel_name]}\" \"#{value[:admins]}\" \"#{value[:rules_file]}\" #{value[:status].to_sym}`
72
+ end
73
+ value[:thread] = t
74
+ }
75
+ end
76
+ end
77
+ end
78
+
79
+ wclient.auth_test
80
+
81
+ begin
82
+ user_info = wclient.users_info(user: "#{"@" if config[:nick][0] != "@"}#{config[:nick]}")
83
+ config[:nick_id] = user_info.user.id
84
+ rescue Exception => stack
85
+ @logger.fatal stack
86
+ abort("The bot user specified on settings: #{config[:nick]}, doesn't exist on Slack. Execution aborted")
87
+ end
88
+
89
+ client.on :hello do
90
+ m = "Successfully connected, welcome '#{client.self.name}' to the '#{client.team.name}' team at https://#{client.team.domain}.slack.com."
91
+ puts m
92
+ @logger.info m
93
+ respond "Smart Bot started\nIf you want to know what I can do for you: *`bot help`*\nYou can send me also a direct message."
94
+ end
95
+
96
+ @status = STATUS_INIT
97
+ @questions = Hash.new()
98
+ @channels_id = Hash.new()
99
+ @channels_name = Hash.new()
100
+ get_channels_name_and_id()
101
+ self
102
+ end
103
+
104
+ def update_bots_file
105
+ file = File.open($0.gsub(".rb", "_bots.rb"), "w")
106
+ bots_created = @bots_created.dup
107
+ bots_created.each { |k, v| v[:thread] = "" }
108
+ file.write bots_created.inspect
109
+ file.close
110
+ end
111
+
112
+ def update_shortcuts_file
113
+ file = File.open("./shortcuts/#{SHORTCUTS_FILE}", "w")
114
+ file.write @shortcuts.inspect
115
+ file.close
116
+ end
117
+
118
+ def get_channels_name_and_id
119
+ channels = wclient.channels_list.channels
120
+ @channels_id = Hash.new()
121
+ @channels_name = Hash.new()
122
+ channels.each do |ch|
123
+ unless ch.is_archived
124
+ @channels_id[ch.name] = ch.id
125
+ @channels_name[ch.id] = ch.name
126
+ end
127
+ end
128
+ end
129
+
130
+ def listen
131
+ @salutations = [config[:nick], config[:nick_id], "bot", "smart"]
132
+ client.on :message do |data|
133
+ if data.channel[0] == "D" #Direct message
134
+ id_user = data.user
135
+ else
136
+ id_user = nil
137
+ end
138
+ user_info = wclient.users_info(user: data.user)
139
+ if !id_user.nil? or @channels_id[CHANNEL] == data.channel or user_info.user.name == config[:nick]
140
+ res = process_first(user_info.user.name, data.text, id_user)
141
+ next if res.to_s == "next"
142
+ end
143
+ end
144
+
145
+ @logger.info "Bot listening"
146
+ client.start!
147
+ end
148
+
149
+ def process_first(nick, text, id_user)
150
+ #todo: verify if on slack on anytime nick == config[:nick]
151
+ if nick == config[:nick] or nick == (config[:nick] + " · Bot") #if message is coming from the bot
152
+ begin
153
+ @logger.info "#{nick}: #{text}"
154
+ case text
155
+ when /^Bot has been killed by/
156
+ exit!
157
+ when /^Changed status on (.+) to :(.+)/i
158
+ channel = $1
159
+ status = $2
160
+ @bots_created[channel][:status] = status.to_sym
161
+ update_bots_file()
162
+ end
163
+ return :next #don't continue analyzing
164
+ rescue Exception => stack
165
+ @logger.fatal stack
166
+ return :next
167
+ end
168
+ end
169
+
170
+ if text.match?(/^!?(shortcut|sc)\s(.+)/i)
171
+ shortcut = text.scan(/!?\w+\s*(.+)\s*/i).join.downcase
172
+ if text[0] == "!"
173
+ addexcl = true
174
+ else
175
+ addexcl = false
176
+ end
177
+ if @shortcuts.keys.include?(nick) and @shortcuts[nick].keys.include?(shortcut)
178
+ text = @shortcuts[nick][shortcut].dup
179
+ elsif @shortcuts.keys.include?(:all) and @shortcuts[:all].keys.include?(shortcut)
180
+ text = @shortcuts[:all][shortcut].dup
181
+ else
182
+ respond "Shortcut not found", id_user
183
+ return :next
184
+ end
185
+ text = "!" + text if addexcl and text[0] != "!"
186
+ end
187
+
188
+ if @questions.keys.include?(nick)
189
+ command = @questions[nick]
190
+ @questions[nick] = text
191
+ else
192
+ command = text
193
+ end
194
+ begin
195
+ t = Thread.new do
196
+ begin
197
+ processed = process(nick, command, id_user)
198
+ @logger.info "command: #{nick}> #{command}" if processed
199
+ if @status == :on and
200
+ ((@questions.keys.include?(nick) or
201
+ @listening.include?(nick) or
202
+ !id_user.nil? or
203
+ command.match?(/^@?#{@salutations.join("|")}:*\s+(.+)$/i) or
204
+ command.match?(/^<@#{@salutations.join("|")}>\s+(.+)$/i) or
205
+ command.match?(/^!(.+)$/)))
206
+ @logger.info "command: #{nick}> #{command}" unless processed
207
+ begin
208
+ eval(File.new(RULES_FILE).read) if File.exist?(RULES_FILE)
209
+ rescue Exception => stack
210
+ @logger.fatal "ERROR ON RULES FILE: #{RULES_FILE}"
211
+ @logger.fatal stack
212
+ end
213
+ if defined?(rules)
214
+ command[0] = "" if command[0] == "!"
215
+ command.gsub!(/^@\w+:*\s*/, "")
216
+ rules(nick, command, processed, id_user)
217
+ else
218
+ @logger.warn "It seems like rules method is not defined"
219
+ end
220
+ end
221
+ rescue Exception => stack
222
+ @logger.fatal stack
223
+ end
224
+ end
225
+ rescue => e
226
+ @logger.error "exception: #{e.inspect}"
227
+ end
228
+ end
229
+
230
+ #help: *Commands you can use*:
231
+ #help:
232
+ def process(from, command, id_user)
233
+ firstname = from.split(/ /).first
234
+ processed = true
235
+
236
+ case command
237
+
238
+ #help: `Hello Bot`
239
+ #help: `Hello Smart`
240
+ #help: `Hello THE_NAME_OF_THE_BOT`
241
+ #help: Also apart of Hello you can use _Hallo, Hi, Hola, What's up, Hey, Hæ_
242
+ #help: Bot starts listening to you
243
+ #help:
244
+ when /^(Hello|Hallo|Hi|Hola|What's\sup|Hey|Hæ)\s(#{@salutations.join("|")})\s*$/i
245
+ if @status == :on
246
+ greetings = ["Hello", "Hallo", "Hi", "Hola", "What's up", "Hey", "Hæ"].sample
247
+ respond "#{greetings} #{firstname}", id_user
248
+ @listening << from unless @listening.include?(from)
249
+ end
250
+
251
+ #help: `Bye Bot`
252
+ #help: `Bye Smart`
253
+ #help: `Bye NAME_OF_THE_BOT`
254
+ #help: Also apart of Bye you can use _Bæ, Good Bye, Adiós, Ciao, Bless, Bless Bless, Adeu_
255
+ #help: Bot stops listening to you
256
+ #help:
257
+ when /^(Bye|Bæ|Good\sBye|Adiós|Ciao|Bless|Bless\sBless|Adeu)\s(#{@salutations.join("|")})\s*$/i
258
+ if @status == :on
259
+ bye = ["Bye", "Bæ", "Good Bye", "Adiós", "Ciao", "Bless", "Bless bless", "Adeu"].sample
260
+ respond "#{bye} #{firstname}", id_user
261
+ @listening.delete(from)
262
+ end
263
+
264
+ #help: `exit bot`
265
+ #help: `quit bot`
266
+ #help: `close bot`
267
+ #help: The bot stops running and also stops all the bots created from this master channel
268
+ #help: You can use this command only if you are an admin user and you are on the master channel
269
+ #help:
270
+ when /^exit\sbot/i, /^quit\sbot/i, /^close\sbot/i
271
+ if ON_MASTER_CHANNEL
272
+ if ADMIN_USERS.include?(from) #admin user
273
+ unless @questions.keys.include?(from)
274
+ ask("are you sure?", command, from, id_user)
275
+ else
276
+ case @questions[from]
277
+ when /yes/i, /yep/i, /sure/i
278
+ respond "Game over!", id_user
279
+ respond "Ciao #{firstname}!", id_user
280
+ @bots_created.each { |key, value|
281
+ value[:thread] = ""
282
+ send_msg_channel(key, "Bot has been closed by #{from}")
283
+ sleep 0.5
284
+ }
285
+ update_bots_file()
286
+ sleep 0.5
287
+ exit!
288
+ when /no/i, /nope/i, /cancel/i
289
+ @questions.delete(from)
290
+ respond "Thanks, I'm happy to be alive", id_user
291
+ else
292
+ respond "I don't understand", id_user
293
+ ask("are you sure do you want me to close? (yes or no)", "quit bot", from, id_user)
294
+ end
295
+ end
296
+ else
297
+ respond "Only admin users can kill me", id_user
298
+ end
299
+ else
300
+ respond "To do this you need to be an admin user in the master channel", id_user
301
+ end
302
+
303
+ #help: `start bot`
304
+ #help: `start this bot`
305
+ #help: the bot will start to listen
306
+ #help: You can use this command only if you are an admin user
307
+ #help:
308
+ when /^start\s(this\s)?bot$/i
309
+ if ADMIN_USERS.include?(from) #admin user
310
+ respond "This bot is running and listening from now on. You can pause again: pause this bot", id_user
311
+ @status = :on
312
+ unless ON_MASTER_CHANNEL
313
+ get_channels_name_and_id() unless @channels_name.keys.include?(MASTER_CHANNEL) and @channels_name.keys.include?(CHANNEL)
314
+ send_msg_channel @channels_name[MASTER_CHANNEL], "Changed status on #{@channels_name[CHANNEL]} to :on"
315
+ end
316
+ else
317
+ respond "Only admin users can change my status", id_user
318
+ end
319
+
320
+ #help: `pause bot`
321
+ #help: `pause this bot`
322
+ #help: the bot will pause so it will listen only to admin commands
323
+ #help: You can use this command only if you are an admin user
324
+ #help:
325
+ when /^pause\s(this\s)?bot$/i
326
+ if ADMIN_USERS.include?(from) #admin user
327
+ respond "This bot is paused from now on. You can start it again: start this bot", id_user
328
+ respond "zZzzzzZzzzzZZZZZZzzzzzzzz", id_user
329
+ @status = :paused
330
+ unless ON_MASTER_CHANNEL
331
+ get_channels_name_and_id() unless @channels_name.keys.include?(MASTER_CHANNEL) and @channels_name.keys.include?(CHANNEL)
332
+ send_msg_channel @channels_name[MASTER_CHANNEL], "Changed status on #{@channels_name[CHANNEL]} to :paused"
333
+ end
334
+ else
335
+ respond "Only admin users can put me on pause", id_user
336
+ end
337
+
338
+ #help: `bot status`
339
+ #help: Displays the status of the bot
340
+ #help: If on master channel and admin user also it will display info about bots created
341
+ #help:
342
+ when /^bot\sstatus/i
343
+ respond "Status: #{@status}. Rules file: #{File.basename RULES_FILE} ", id_user
344
+ if @status == :on
345
+ respond "I'm listening to [#{@listening.join(", ")}]", id_user
346
+ if ON_MASTER_CHANNEL and ADMIN_USERS.include?(from)
347
+ @bots_created.each { |key, value|
348
+ respond "#{key}: #{value}", id_user
349
+ }
350
+ end
351
+ end
352
+
353
+ #help: `create bot on CHANNEL_NAME`
354
+ #help: creates a new bot on the channel specified
355
+ #help: it will work only if you are on Master channel
356
+ #help:
357
+ when /^create\sbot\son\s(.+)\s*/i
358
+ if ON_MASTER_CHANNEL
359
+ channel = $1
360
+ if @bots_created.keys.include?(channel)
361
+ respond "There is already a bot in this channel: #{channel}, kill it before", id_user
362
+ else
363
+ get_channels_name_and_id() unless @channels_name.keys.include?(channel) or @channels_id.keys.include?(channel)
364
+ channel_id = nil
365
+ if @channels_name.key?(channel) #it is an id
366
+ channel_id = channel
367
+ elsif @channels_id.key?(channel) #it is a channel name
368
+ channel_id = @channels_id[channel]
369
+ end
370
+
371
+ if !channel_id.nil?
372
+ if channel_id != config[:channel]
373
+ begin
374
+ rules_file = "slack-smart-bot_rules_#{channel_id}_#{from.gsub(" ", "_")}.rb"
375
+ if defined?(RULES_FOLDER)
376
+ rules_file = RULES_FOLDER + rules_file
377
+ else
378
+ Dir.mkdir("rules") unless Dir.exist?("rules")
379
+ Dir.mkdir("rules/#{channel_id}") unless Dir.exist?("rules/#{channel_id}")
380
+ rules_file = "./rules/#{channel_id}/" + rules_file
381
+ end
382
+ default_rules = (__FILE__).gsub(/\.rb$/, "_rules.rb")
383
+ File.delete(rules_file) if File.exist?(rules_file)
384
+ FileUtils.copy_file(default_rules, rules_file) unless File.exist?(rules_file)
385
+ admin_users = Array.new()
386
+ admin_users = [from] + MASTER_USERS
387
+ admin_users.uniq!
388
+ @logger.info "ruby #{$0} \"#{channel}\" \"#{admin_users.join(",")}\" \"#{rules_file}\" on"
389
+ t = Thread.new do
390
+ `ruby #{$0} \"#{channel}\" \"#{admin_users.join(",")}\" \"#{rules_file}\" on`
391
+ end
392
+ @bots_created[channel] = {
393
+ creator_name: from,
394
+ channel_id: channel_id,
395
+ channel_name: @channels_name[channel_id],
396
+ status: :on,
397
+ created: Time.now.strftime("%Y-%m-%dT%H:%M:%S.000Z")[0..18],
398
+ rules_file: rules_file,
399
+ admins: admin_users.join(","),
400
+ thread: t,
401
+ }
402
+ respond "The bot has been created on channel: #{channel}. Rules file: #{File.basename rules_file}", id_user
403
+ update_bots_file()
404
+ rescue Exception => stack
405
+ @logger.fatal stack
406
+ message = "Problem creating the bot on channel #{channel}. Error: <#{stack}>."
407
+ @logger.error message
408
+ respond message, id_user
409
+ end
410
+ else
411
+ respond "There is already a bot in this channel: #{channel}, and it is the Master Channel!", id_user
412
+ end
413
+ else
414
+ respond "There is no channel with that name: #{channel}, please be sure is written exactly the same", id_user
415
+ end
416
+ end
417
+ else
418
+ respond "Sorry I cannot create bots from this channel, please visit the master channel", id_user
419
+ end
420
+
421
+ #help: `kill bot on CHANNEL_NAME`
422
+ #help: kills the bot on the specified channel
423
+ #help: Only works if you are on Master channel and you created that bot or you are an admin user
424
+ #help:
425
+ when /^kill\sbot\son\s(.+)\s*/i
426
+ if ON_MASTER_CHANNEL
427
+ channel = $1
428
+ if @bots_created.keys.include?(channel)
429
+ if @bots_created[channel][:admins].split(",").include?(from)
430
+ if @bots_created[channel][:thread].kind_of?(Thread) and @bots_created[channel][:thread].alive?
431
+ @bots_created[channel][:thread].kill
432
+ end
433
+ @bots_created.delete(channel)
434
+ update_bots_file()
435
+ respond "Bot on channel: #{channel}, has been killed and deleted.", id_user
436
+ send_msg_channel(channel, "Bot has been killed by #{from}")
437
+ else
438
+ respond "You need to be the creator or an admin of that channel", id_user
439
+ end
440
+ else
441
+ respond "There is no bot in this channel: #{channel}", id_user
442
+ end
443
+ else
444
+ respond "Sorry I cannot kill bots from this channel, please visit the master channel", id_user
445
+ end
446
+
447
+ #help: `bot help`
448
+ #help: `bot what can I do?`
449
+ #help: it will display this help
450
+ #help:
451
+ when /^bot help/i, /^bot,? what can I do/i
452
+ help_message = IO.readlines(__FILE__).join
453
+ help_message_rules = IO.readlines(RULES_FILE).join
454
+ respond help_message.scan(/#\s*help\s*:(.*)/).join("\n"), id_user
455
+ respond help_message_rules.scan(/#\s*help\s*:(.*)/).join("\n"), id_user
456
+ else
457
+ processed = false
458
+ end
459
+
460
+ #only when :on and (listening or on demand or direct message)
461
+ if @status == :on and
462
+ ((@questions.keys.include?(from) or
463
+ @listening.include?(from) or
464
+ !id_user.nil? or
465
+ command.match?(/^@?#{@salutations.join("|")}:*\s+(.+)$/i) or
466
+ command.match?(/^!(.+)$/)))
467
+ processed2 = true
468
+
469
+ # help:
470
+ # help: *These commands will run only when the smart bot is listening to you or on demand*, for example:
471
+ # help: `!THE_COMMAND`
472
+ # help: `@bot THE_COMMAND`
473
+ # help: `@NAME_OF_BOT THE_COMMAND`
474
+ # help: `NAME_OF_BOT THE_COMMAND`
475
+ # help:
476
+ case command
477
+
478
+ #help: `add shortcut NAME: COMMAND`
479
+ #help: `add shortcut for all NAME: COMMAND`
480
+ #help: `shortchut NAME: COMMAND`
481
+ #help: `shortchut for all NAME: COMMAND`
482
+ #help: It will add a shortcut that will execute the command we supply.
483
+ #help: In case we supply 'for all' then the shorcut will be available for everybody
484
+ #help: Example:
485
+ #help: `add shortcut for all Spanish account: code require 'iso/iban'; 10.times {puts ISO::IBAN.random('ES')}`
486
+ #help: Then to call this shortcut:
487
+ #help: `sc spanish account`
488
+ #help: `shortcut Spanish Account`
489
+ #help:
490
+ when /(add\s)?shortcut\s(for\sall)?\s*(.+):\s(.+)/i
491
+ for_all = $2
492
+ shortcut_name = $3.to_s.downcase
493
+ command_to_run = $4
494
+ @shortcuts[from] = Hash.new() unless @shortcuts.keys.include?(from)
495
+
496
+ if !ADMIN_USERS.include?(from) and @shortcuts[:all].include?(shortcut_name) and !@shortcuts[from].include?(shortcut_name)
497
+ respond "Only the creator of the shortcut or an admin user can modify it", id_user
498
+ elsif !@shortcuts[from].include?(shortcut_name)
499
+ #new shortcut
500
+ @shortcuts[from][shortcut_name] = command_to_run
501
+ @shortcuts[:all][shortcut_name] = command_to_run if for_all.to_s != ""
502
+ update_shortcuts_file()
503
+ respond "shortcut added", id_user
504
+ else
505
+
506
+ #are you sure? to avoid overwriting existing
507
+ unless @questions.keys.include?(from)
508
+ ask("The shortcut already exists, are you sure you want to overwrite it?", command, from, id_user)
509
+ else
510
+ case @questions[from]
511
+ when /^(yes|yep)/i
512
+ @shortcuts[from][shortcut_name] = command_to_run
513
+ @shortcuts[:all][shortcut_name] = command_to_run if for_all.to_s != ""
514
+ update_shortcuts_file()
515
+ respond "shortcut added", id_user
516
+ @questions.delete(from)
517
+ when /^no/i
518
+ respond "ok, I won't add it", id_user
519
+ @questions.delete(from)
520
+ else
521
+ respond "I don't understand, yes or no?", id_user
522
+ end
523
+ end
524
+ end
525
+
526
+ #help: `delete shortcut NAME`
527
+ #help: It will delete the shortcut with the supplied name
528
+ #help:
529
+ when /delete\sshortcut\s(.+)/i
530
+ shortcut = $1.to_s.downcase
531
+ deleted = false
532
+
533
+ if !ADMIN_USERS.include?(from) and @shortcuts[:all].include?(shortcut) and !@shortcuts[from].include?(shortcut)
534
+ respond "Only the creator of the shortcut or an admin user can delete it", id_user
535
+ elsif (@shortcuts.keys.include?(from) and @shortcuts[from].keys.include?(shortcut)) or
536
+ (ADMIN_USERS.include?(from) and @shortcuts[:all].include?(shortcut))
537
+ #are you sure? to avoid deleting by mistake
538
+ unless @questions.keys.include?(from)
539
+ ask("are you sure you want to delete it?", command, from, id_user)
540
+ else
541
+ case @questions[from]
542
+ when /^(yes|yep)/i
543
+ respond "shortcut deleted!", id_user
544
+ respond "#{shortcut}: #{@shortcuts[from][shortcut]}", id_user
545
+ @shortcuts[from].delete(shortcut)
546
+ @shortcuts[:all].delete(shortcut)
547
+ @questions.delete(from)
548
+ update_shortcuts_file()
549
+ when /^no/i
550
+ respond "ok, I won't delete it", id_user
551
+ @questions.delete(from)
552
+ else
553
+ respond "I don't understand, yes or no?", id_user
554
+ end
555
+ end
556
+ else
557
+ respond "shortcut not found", id_user
558
+ end
559
+
560
+ #help: `see shortcuts`
561
+ #help: It will display the shortcuts stored for the user and for :all
562
+ #help:
563
+ when /see\sshortcuts/i
564
+ msg = ""
565
+ if @shortcuts[:all].keys.size > 0
566
+ msg = "*Available shortcuts for all:*\n"
567
+ @shortcuts[:all].each { |name, value|
568
+ msg += " _#{name}: #{value}_\n"
569
+ }
570
+ respond msg, id_user
571
+ end
572
+
573
+ if @shortcuts.keys.include?(from) and @shortcuts[from].keys.size > 0
574
+ new_hash = @shortcuts[from].dup
575
+ @shortcuts[:all].keys.each { |k| new_hash.delete(k) }
576
+ if new_hash.keys.size > 0
577
+ msg = "*Available shortcuts for #{from}:*\n"
578
+ new_hash.each { |name, value|
579
+ msg += " _#{name}: #{value}_\n"
580
+ }
581
+ respond msg, id_user
582
+ end
583
+ end
584
+ respond "No shortcuts found", id_user if msg == ""
585
+
586
+ #help: `id channel CHANNEL_NAME`
587
+ #help: shows the id of a channel name
588
+ #help:
589
+ when /id channel (.+)/
590
+ channel_name = $1
591
+ get_channels_name_and_id()
592
+ if @channels_id.keys.include?(channel_name)
593
+ respond "the id of #{channel_name} is #{@channels_id[channel_name]}", id_user
594
+ else
595
+ respond "channel: #{channel_name} not found", id_user
596
+ end
597
+
598
+ # help: `ruby RUBY_CODE`
599
+ # help: `code RUBY_CODE`
600
+ # help: runs the code supplied and returns the output. Examples:
601
+ # help: `code puts (34344/99)*(34+14)`
602
+ # help: `ruby require 'json'; res=[]; 20.times {res<<rand(100)}; my_json={result: res}; puts my_json.to_json`
603
+ # help:
604
+ when /ruby\s(.+)/im, /code\s(.+)/im
605
+ code = $1
606
+ code.gsub!("\\n", "\n")
607
+ unless code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File") or
608
+ code.include?("`") or code.include?("exec") or code.include?("spawn") or code.include?("IO") or
609
+ code.match?(/open3/i) or code.match?(/bundle/i) or code.match?(/gemfile/i) or code.include?("%x") or
610
+ code.include?("ENV")
611
+ begin
612
+ stdout, stderr, status = Open3.capture3("ruby -e \"#{code.gsub('"', '\"')}\"")
613
+ if stderr == ""
614
+ if stdout == ""
615
+ respond "Nothing returned. Remember you need to use p or puts to print", id_user
616
+ else
617
+ respond stdout, id_user
618
+ end
619
+ else
620
+ respond stderr, id_user
621
+ end
622
+ rescue Exception => exc
623
+ respond exc, id_user
624
+ end
625
+ else
626
+ respond "Sorry I cannot run this due security issues", id_user
627
+ end
628
+ else
629
+ processed2 = false
630
+ end
631
+ processed = true if processed or processed2
632
+ end
633
+
634
+ return processed
635
+ end
636
+
637
+ def respond(msg, id_user = nil)
638
+ if id_user.nil?
639
+ client.message(channel: @channels_id[CHANNEL], text: msg, as_user: true)
640
+ else #private message
641
+ send_msg_user(id_user, msg)
642
+ end
643
+ end
644
+
645
+ #context: previous message
646
+ #to: user that should answer
647
+ def ask(question, context, to, id_user = nil)
648
+ if id_user.nil?
649
+ client.message(channel: @channels_id[CHANNEL], text: "#{to}: #{question}", as_user: true)
650
+ else #private message
651
+ send_msg_user(id_user, "#{to}: #{question}")
652
+ end
653
+ @questions[to] = context
654
+ end
655
+
656
+ # to: (String) Channel name or id
657
+ # msg: (String) message to send
658
+ def send_msg_channel(to, msg)
659
+ unless msg == ""
660
+ get_channels_name_and_id() unless @channels_name.key?(to) or @channels_id.key?(to)
661
+ if @channels_name.key?(to) #it is an id
662
+ channel_id = to
663
+ elsif @channels_id.key?(to) #it is a channel name
664
+ channel_id = @channels_id[to]
665
+ else
666
+ @logger.fatal "Channel: #{to} not found. Message: #{msg}"
667
+ end
668
+ client.message(channel: channel_id, text: msg, as_user: true)
669
+ end
670
+ end
671
+
672
+ #to send messages without listening for a response to users
673
+ def send_msg_user(id_user, msg)
674
+ unless msg == ""
675
+ im = wclient.im_open(user: id_user)
676
+ client.message(channel: im["channel"]["id"], as_user: true, text: msg)
677
+ end
678
+ end
679
+
680
+ private :update_bots_file, :get_channels_name_and_id, :update_shortcuts_file
681
+ end
@@ -0,0 +1,82 @@
1
+ #for the case of testing, just run this file adding in the end a call to rules with the parameters you want
2
+ if defined?(respond)
3
+ @testing = false
4
+ else
5
+ @testing = true
6
+ @questions = Hash.new()
7
+
8
+ def respond(message, id_user)
9
+ puts message
10
+ end
11
+
12
+ #context: previous message
13
+ #to: user that should answer
14
+ def ask(question, context, to, id_user)
15
+ puts "Bot: #{question}"
16
+ @questions[to] = context
17
+ end
18
+ end
19
+
20
+ # from: Full name of the person sending the message
21
+ # command: command to run
22
+ # processed: in case the command has been already processed on Bot class, by default false
23
+ # help:
24
+ # help: *These are specific commands on this bot.*
25
+ # help: They will be accessible only when the bot is listening to you just writing the command
26
+ # help: or the bot is not listening to you but requested on demand, for example:
27
+ # help: `!THE_COMMAND`
28
+ # help: `@bot THE_COMMAND`
29
+ # help: `@NAME_OF_BOT THE_COMMAND`
30
+ # help: `NAME_OF_BOT THE_COMMAND`
31
+ # help:
32
+ def rules(from, command, processed, id_user)
33
+ if @testing
34
+ puts "#{from}: #{command}"
35
+ if @questions.keys.include?(from)
36
+ context = @questions[from]
37
+ @questions[from] = command
38
+ command = context
39
+ end
40
+ end
41
+ firstname = from.split(" ").first
42
+ case command
43
+
44
+ # help: `echo SOMETHING`
45
+ # help: repeats SOMETHING
46
+ # help:
47
+ when /echo\s(.+)/i
48
+ respond $1, id_user
49
+
50
+ # help: `go to sleep`
51
+ # help: it will sleep the bot for 10 seconds
52
+ # help:
53
+ when /go\sto\ssleep/i
54
+ unless @questions.keys.include?(from)
55
+ ask("do you want me to take a siesta?", command, from, id_user)
56
+ else
57
+ case @questions[from]
58
+ when /yes/i, /yep/i, /sure/i
59
+ respond "zZzzzzzZZZZZZzzzzzzz!", id_user
60
+ respond "I'll be sleeping for 10 secs... just for you", id_user
61
+ sleep 10
62
+ when /no/i, /nope/i, /cancel/i
63
+ @questions.delete(from)
64
+ respond "Thanks, I'm happy to be awake", id_user
65
+ else
66
+ respond "I don't understand", id_user
67
+ ask("are you sure do you want me to sleep? (yes or no)", "go to sleep", from, id_user)
68
+ end
69
+ end
70
+ else
71
+ unless processed
72
+ resp = %w{ what huh sorry }.sample
73
+ respond "#{firstname}: #{resp}?", id_user
74
+ end
75
+ end
76
+ end
77
+
78
+ #for the case of testing just running this file, write the dialogue in here:
79
+ if @testing
80
+ rules "Peter Johson", "go to sleep, you look tired", false
81
+ rules "Peter Johson", "yes", false
82
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: slack-smart-bot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Mario Ruiz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-05-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: slack-ruby-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.14'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.14.2
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.14'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.14.2
33
+ - !ruby/object:Gem::Dependency
34
+ name: celluloid-io
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '0.17'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 0.17.3
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '0.17'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 0.17.3
53
+ description: "Create a Slack bot that is smart and so easy to expand, create new bots
54
+ on demand, run ruby code on chat, create shortcuts... \n The main scope of this
55
+ gem is to be used internally in the company so teams can create team channels with
56
+ their own bot to help them on their daily work, almost everything is suitable to
57
+ be automated!! \n slack-smart-bot can create bots on demand, create shortcuts,
58
+ run ruby code... just on a chat channel. \n You can access it just from your mobile
59
+ phone if you want and run those tests you forgot to run, get the results, restart
60
+ a server... no limits."
61
+ email: marioruizs@gmail.com
62
+ executables: []
63
+ extensions: []
64
+ extra_rdoc_files:
65
+ - LICENSE
66
+ - README.md
67
+ files:
68
+ - ".yardopts"
69
+ - LICENSE
70
+ - README.md
71
+ - lib/slack-smart-bot.rb
72
+ - lib/slack-smart-bot_rules.rb
73
+ homepage: https://github.com/MarioRuiz/slack-smart-bot
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message: Thanks for installing! Visit us on https://github.com/MarioRuiz/slack-smart-bot
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '2.4'
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.7.6
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Create a Slack bot that is smart and so easy to expand, create new bots on
97
+ demand, run ruby code on chat, create shortcuts...
98
+ test_files: []