hipchat_smart 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: da292e2d66a6bbbf1edf2ebc2fd28232d3cfeb29
4
+ data.tar.gz: 62d4d3b1363829d97e3b867c2a973212011c1fe3
5
+ SHA512:
6
+ metadata.gz: b5e72a6e8782ced566bbbe365e95c281f8c7166e9898d538692c148ff466e0a2a8cbe45ef3c9db427d4aed01c04fc88528d1dba1f423e56902d8e470a630c87c
7
+ data.tar.gz: ef0691f01534b9e91394fec3bff846dd5d9612dc07ef17bc947d33b81b818cd968bbc58a8954a83363d23e68590003dabc78a2080c7c78a3b745cd31d982b42e
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 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,249 @@
1
+ # HipChat Smart
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/hipchat_smart.svg)](https://rubygems.org/gems/hipchat_smart)
4
+
5
+ Create a hipchat 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 rooms with their own bot to help them on their daily work, almost everything is suitable to be automated!!
8
+
9
+ hipchat_smart can create bots on demand, create shortcuts, run ruby code... just on a chat room, 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 hipchat_smart
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
+ #jid of the room that will act like the master room, main room
21
+ MASTER_ROOM="1_my_master_room"
22
+ #names of the master users
23
+ MASTER_USERS=["Mario Ruiz Sanchez"]
24
+
25
+ require 'hipchat_smart'
26
+
27
+ settings = {
28
+ jid: 'bot_user_jid@your_company_hipchat_domain.com/bot',
29
+ nick: 'bot user name',
30
+ password: "xxxxxxxxxxxx",
31
+ token: 'xxxxxxxxxxxxxxxxxxxxxxx',
32
+ }
33
+
34
+ Bot.new(settings).listen.always
35
+ ```
36
+
37
+ To enable XMPP/Jabber on your Hipchat and be able to get the jids you need, go to: https://hipchat.yourCompanyDomain.com/account/xmpp
38
+
39
+ The MASTER_ROOM will be the room where you will be able to create other bots and will have special treatment.
40
+
41
+ The MASTER_USERS will have full access to everything. The names should be written exactly the same like they appear on hipchat.
42
+
43
+ I recommend to create an specific user on hipchat to be the bot so less risks.
44
+
45
+ Add the jid for that user specifying the hipchat domain in your company and finishing with /bot
46
+
47
+ For the token remember you need to generate a token on the hipchat app for the bot user.
48
+ To generate the token go to: https://hipchat.yourCompanyDomain.com/account/api
49
+
50
+ ## Usage
51
+
52
+ ### creating the MASTER BOT
53
+ Let's guess the file you created was called my_smart_bot.rb so, just run it:
54
+ ```
55
+ ruby my_smart_bot.rb
56
+ ```
57
+
58
+ 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
59
+
60
+ The rules file can be edited and will be only affecting this particular bot.
61
+
62
+ You can add all the rules you want for your bot in the rules file, this is an example:
63
+
64
+ ```ruby
65
+ def rules(from, command, processed)
66
+ firstname = from.split(" ").first
67
+ case command
68
+
69
+ # help: echo SOMETHING
70
+ # help: repeats SOMETHING
71
+ # help:
72
+ when /echo\s(.+)/i
73
+ respond $1
74
+
75
+ # help: go to sleep
76
+ # help: it will sleep the bot for 10 seconds
77
+ # help:
78
+ when /go\sto\ssleep/i
79
+ unless @questions.keys.include?(from)
80
+ ask("do you want me to take a siesta?", command, from)
81
+ else
82
+ case @questions[from]
83
+ when /yes/i, /yep/i, /sure/i
84
+ respond "zZzzzzzZZZZZZzzzzzzz!"
85
+ respond "I'll be sleeping for 10 secs... just for you"
86
+ sleep 10
87
+ when /no/i, /nope/i, /cancel/i
88
+ @questions.delete(from)
89
+ respond "Thanks, I'm happy to be awake"
90
+ else
91
+ respond "I don't understand"
92
+ ask("are you sure do you want me to sleep? (yes or no)", "go to sleep", from)
93
+ end
94
+ end
95
+ else
96
+ unless processed
97
+ resp = %w{ what huh sorry }.sample
98
+ respond "#{firstname}: #{resp}?"
99
+ end
100
+ end
101
+ end
102
+
103
+ ```
104
+
105
+ ### Available commands even when the bot is not listening to you
106
+ Some of the commands are available always even when the bot is not listening to you but it is running
107
+
108
+ **_`bot help`_**
109
+
110
+ **_`bot what can I do?`_**
111
+
112
+ >It will display all the commands we can use
113
+ >What is displayed by this command is what is written on your rules file like this: #help: THE TEXT TO SHOW
114
+
115
+ **_`Hello Bot`_**
116
+
117
+ **_`Hello THE_FIRSTNAME_OF_THE_BOT`_**
118
+
119
+ >Also apart of Hello you can use Hallo, Hi, Hola, What's up, Hey, Zdravo, Molim, Hæ
120
+
121
+ >Bot starts listening to you
122
+
123
+ **_`Bye Bot`_**
124
+
125
+ **_`Bye THE_FIRST_NAME_OF_THE_BOT`_**
126
+
127
+ >Also apart of Bye you can use Bæ, Good Bye, Adiós, Ciao, Bless, Bless Bless, Zbogom, Adeu
128
+
129
+ >Bot stops listening to you
130
+
131
+ **_`exit bot`_**
132
+
133
+ **_`quit bot`_**
134
+
135
+ **_`close bot`_**
136
+
137
+ >The bot stops running and also stops all the bots created from this master room
138
+
139
+ >You can use this command only if you are an admin user and you are on the master room
140
+
141
+ **_`start bot`_**
142
+
143
+ **_`start this bot`_**
144
+
145
+ >The bot will start to listen
146
+
147
+ >You can use this command only if you are an admin user
148
+
149
+ **_`pause bot`_**
150
+
151
+ **_`pause this bot`_**
152
+
153
+ >The bot will pause so it will listen only to admin commands
154
+
155
+ >You can use this command only if you are an admin user
156
+
157
+ **_`bot status`_**
158
+
159
+ >Displays the status of the bot
160
+
161
+ >If on master room and admin user also it will display info about bots created
162
+
163
+ **_`create bot on ROOM_NAME`_**
164
+
165
+ >Creates a new bot on the room specified.
166
+
167
+ >hipchat_smart will create a default rules file specific for your room.
168
+ You can edit it and add the rules you want.
169
+ As soon as you save the file after editing it will become available on your room.
170
+
171
+ >It will work only if you are on Master room
172
+
173
+ **_`kill bot on ROOM_NAME`_**
174
+
175
+ >Kills the bot on the specified room
176
+
177
+ >Only works if you are on Master room and you created that bot or you are an admin user
178
+
179
+ ### Available commands only when listening to you or on demand
180
+
181
+ 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.
182
+
183
+ For the bot to start listening to you you need to use the "Hi bot" command or one of the aliases
184
+
185
+ Also you can call any of these commands on demand by using:
186
+
187
+ **_`!THE_COMMAND`_**
188
+
189
+ **_`@bot THE_COMMAND`_**
190
+
191
+ **_`@FIRST_NAME_BOT THE_COMMAND`_**
192
+
193
+ **_`FIRST_NAME_BOT THE_COMMAND`_**
194
+
195
+ Apart of the specific commands you define on the rules file of the room, you can use:
196
+
197
+ **_`ruby RUBY_CODE`_**
198
+
199
+ **_`/code RUBY_CODE`_**
200
+
201
+ >runs the code supplied and returns the output. Examples:
202
+
203
+ >ruby require 'json'; res=[]; 20.times {res<<rand(100)}; my_json={result: res}; puts my_json.to_json
204
+
205
+ >/code puts (34344/99)*(34+14)
206
+
207
+
208
+ **_`add shortcut NAME: COMMAND`_**
209
+
210
+ **_`add shortcut for all NAME: COMMAND`_**
211
+
212
+ **_`shortchut NAME: COMMAND`_**
213
+
214
+ **_`shortchut for all NAME: COMMAND`_**
215
+
216
+ >It will add a shortcut that will execute the command we supply.
217
+
218
+ >In case we supply 'for all' then the shorcut will be available for everybody
219
+
220
+ >Example:
221
+ >add shortcut for all Spanish account: /code require 'iso/iban'; 10.times {puts ISO::IBAN.random('ES')}
222
+
223
+ >Then to call this shortcut:
224
+
225
+ >/sc spanish account
226
+
227
+ >/shortcut Spanish Account
228
+
229
+ **_`delete shortcut NAME`_**
230
+
231
+ >It will delete the shortcut with the supplied name
232
+
233
+ **_`see shortcuts`_**
234
+
235
+ >It will display the shortcuts stored for the user and for :all
236
+
237
+ **_`jid room ROOM_NAME`_**
238
+ >shows the jid of a room name
239
+
240
+
241
+ ## Contributing
242
+
243
+ Bug reports and pull requests are welcome on GitHub at https://github.com/marioruiz/hipchat_smart.
244
+
245
+
246
+ ## License
247
+
248
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
249
+
@@ -0,0 +1,652 @@
1
+ require 'xmpp4r'
2
+ require 'xmpp4r/muc/helper/simplemucclient'
3
+ require 'xmpp4r/muc/helper/mucbrowser'
4
+ require 'open-uri'
5
+ require 'cgi'
6
+ require 'json'
7
+ require 'hipchat'
8
+ require 'logger'
9
+
10
+ if ARGV.size==0
11
+ ROOM = MASTER_ROOM
12
+ ON_MASTER_ROOM = true
13
+ ADMIN_USERS = MASTER_USERS
14
+ RULES_FILE = "#{$0.gsub('.rb', '_rules.rb')}" unless defined?(RULES_FILE)
15
+ STATUS_INIT = :on
16
+ else
17
+ ON_MASTER_ROOM = false
18
+ ROOM = ARGV[0]
19
+ ADMIN_USERS=ARGV[1].split(",")
20
+ RULES_FILE=ARGV[2]
21
+ STATUS_INIT = ARGV[3].to_sym
22
+ end
23
+
24
+ SHORTCUTS_FILE = "hipchat_smart_shortcuts_#{ROOM}.rb".gsub(" ", "_")
25
+
26
+ class Bot
27
+
28
+ attr_accessor :config, :client, :muc, :muc_browser
29
+
30
+ def initialize(config)
31
+ Dir.mkdir("./logs") unless Dir.exist?("./logs")
32
+ Dir.mkdir("./shortcuts") unless Dir.exist?("./shortcuts")
33
+ logfile=File.basename(RULES_FILE.gsub("_rules_", "_logs_"), ".rb")+".log"
34
+ @logger = Logger.new("./logs/#{logfile}")
35
+ config_log=config.dup
36
+ config_log.delete(:password)
37
+ @logger.info "Initializing bot: #{config_log.inspect}"
38
+
39
+ #XMPP namespace for Hipchat Server by default, if not supplied with room
40
+ config[:room] = ROOM
41
+ if config[:room].include?("@")
42
+ @xmpp_namespace = config[:room].scan(/.+@(.+)/).join
43
+ else
44
+ @xmpp_namespace = "conf.btf.hipchat.com"
45
+ end
46
+
47
+ config[:room]=config[:room]+"@"+@xmpp_namespace
48
+
49
+ self.config = config
50
+ self.client = Jabber::Client.new(config[:jid])
51
+ self.muc = Jabber::MUC::SimpleMUCClient.new(client)
52
+ self.muc_browser = Jabber::MUC::MUCBrowser.new(client)
53
+
54
+ if Jabber.logger = config[:debug]
55
+ Jabber.debug = true
56
+ end
57
+
58
+ @listening = Array.new
59
+
60
+ @bots_created=Hash.new()
61
+ @shortcuts=Hash.new()
62
+ @shortcuts[:all]=Hash.new()
63
+
64
+ if File.exist?("./shortcuts/#{SHORTCUTS_FILE}")
65
+ file_sc = IO.readlines("./shortcuts/#{SHORTCUTS_FILE}").join
66
+ unless file_sc.to_s() == ""
67
+ @shortcuts = eval(file_sc)
68
+ end
69
+ end
70
+
71
+ if ON_MASTER_ROOM and File.exist?($0.gsub(".rb", "_bots.rb"))
72
+ file_conf = IO.readlines($0.gsub(".rb", "_bots.rb")).join
73
+ unless file_conf.to_s() == ""
74
+ @bots_created = eval(file_conf)
75
+ if @bots_created.kind_of?(Hash)
76
+ @bots_created.each {|key, value|
77
+ @logger.info "ruby #{$0} \"#{value[:jid].gsub(/@.+/, '')}\" \"#{value[:admins]}\" \"#{value[:rules_file]}\" #{value[:status]}"
78
+ t = Thread.new do
79
+ `ruby #{$0} \"#{value[:jid].gsub(/@.+/, '')}\" \"#{value[:admins]}\" \"#{value[:rules_file]}\" #{value[:status]}`
80
+ end
81
+ value[:thread]=t
82
+ }
83
+ end
84
+ end
85
+ end
86
+
87
+ client.connect
88
+ client.auth(config[:password])
89
+ client.on_exception do |exc, jab, where|
90
+ @logger.fatal "CLIENT EXCEPTION on #{where}: #{exc}"
91
+ sleep 10
92
+ #todo: reconnect
93
+ end
94
+ config.delete(:password)
95
+ client.send(Jabber::Presence.new.set_type(:available))
96
+ @status = STATUS_INIT
97
+ @questions = Hash.new()
98
+ @rooms_jid=Hash.new()
99
+ @rooms_name=Hash.new()
100
+ self
101
+ end
102
+
103
+ def update_bots_file
104
+ file = File.open($0.gsub(".rb", "_bots.rb"), 'w')
105
+ bots_created=@bots_created.dup
106
+ bots_created.each {|k, v| v[:thread]=""}
107
+ file.write bots_created.inspect
108
+ file.close
109
+ end
110
+
111
+ def update_shortcuts_file
112
+ file = File.open("./shortcuts/#{SHORTCUTS_FILE}", 'w')
113
+ file.write @shortcuts.inspect
114
+ file.close
115
+ end
116
+
117
+ def get_rooms_name_and_jid
118
+ @rooms_jid=Hash.new()
119
+ @rooms_name=Hash.new()
120
+ muc_browser.muc_rooms(@xmpp_namespace).each {|jid, name|
121
+ jid=jid.to_s.gsub("@#{@xmpp_namespace}", "")
122
+ @rooms_jid[name]=jid
123
+ @rooms_name[jid]=name
124
+ }
125
+ end
126
+
127
+ def listen
128
+ @salutations = [config[:nick].split(/\s+/).first, "bot"]
129
+
130
+ muc.on_message do |time, nick, text|
131
+ if nick==config[:nick] or nick==(config[:nick] + " · Bot") #if message is coming from the bot
132
+ begin
133
+ @logger.info "#{nick}: #{text}"
134
+ case text
135
+ when /^Bot has been killed by/
136
+ exit!
137
+ when /^Changed status on (.+) to :(.+)/i
138
+ room=$1
139
+ status=$2
140
+ @bots_created[room][:status]=status.to_sym
141
+ update_bots_file()
142
+ end
143
+ next #don't continue analyzing
144
+ rescue Exception => stack
145
+ @logger.fatal stack
146
+ next
147
+ end
148
+
149
+ end
150
+
151
+ if text.match?(/^\/(shortcut|sc)\s(.+)/i)
152
+ shortcut=text.scan(/\/\w+\s*(.+)\s*/i).join.downcase
153
+ if @shortcuts.keys.include?(nick) and @shortcuts[nick].keys.include?(shortcut)
154
+ text=@shortcuts[nick][shortcut]
155
+ elsif @shortcuts.keys.include?(:all) and @shortcuts[:all].keys.include?(shortcut)
156
+ text=@shortcuts[:all][shortcut]
157
+ else
158
+ respond "Shortcut not found"
159
+ next
160
+ end
161
+
162
+ end
163
+
164
+ if @questions.keys.include?(nick)
165
+ command=@questions[nick]
166
+ @questions[nick]=text
167
+ else
168
+ command=text
169
+ end
170
+
171
+ begin
172
+ t = Thread.new do
173
+ begin
174
+ processed = process(nick, command)
175
+ @logger.info "command: #{nick}> #{command}" if processed
176
+ if @status==:on and
177
+ ((@questions.keys.include?(nick) or
178
+ @listening.include?(nick) or
179
+ command.match?(/^@?#{@salutations.join("|")}:*\s+(.+)$/i) or
180
+ command.match?(/^!(.+)$/)))
181
+ @logger.info "command: #{nick}> #{command}" unless processed
182
+ begin
183
+ eval(File.new(RULES_FILE).read) if File.exist?(RULES_FILE)
184
+ rescue Exception => stack
185
+ @logger.fatal "ERROR ON RULES FILE: #{RULES_FILE}"
186
+ @logger.fatal stack
187
+ end
188
+ if defined?(rules)
189
+ command[0]="" if command[0]=="!"
190
+ command.gsub!(/^@\w+:*\s*/, "")
191
+ rules(nick, command, processed)
192
+ else
193
+ @logger.warn "It seems like rules method is not defined"
194
+ end
195
+ end
196
+ rescue Exception => stack
197
+ @logger.fatal stack
198
+ end
199
+
200
+ end
201
+
202
+ rescue => e
203
+ @logger.error "exception: #{e.inspect}"
204
+ end
205
+ end
206
+
207
+ muc.join(config[:room] + '/' + config[:nick])
208
+ respond "Bot started"
209
+ @logger.info "Bot listening"
210
+ self
211
+ end
212
+
213
+ #help: Commands you can use:
214
+ #help:
215
+ def process(from, command)
216
+ firstname = from.split(/ /).first
217
+ processed=true
218
+
219
+ case command
220
+
221
+ #help: Hello Bot
222
+ #help: Hello THE_FIRSTNAME_OF_THE_BOT.
223
+ #help: Also apart of Hello you can use Hallo, Hi, Hola, What's up, Hey, Zdravo, Molim, Hæ
224
+ #help: Bot starts listening to you
225
+ #help:
226
+ when /^(Hello|Hallo|Hi|Hola|What's\sup|Hey|Zdravo|Molim|Hæ)\s(#{@salutations.join("|")})\s*$/i
227
+ if @status==:on
228
+ greetings=['Hello', 'Hallo', 'Hi', 'Hola', "What's up", "Hey", "Zdravo", "Molim", "Hæ"].sample
229
+ respond "#{greetings} #{firstname}"
230
+ @listening<<from unless @listening.include?(from)
231
+ end
232
+
233
+ #help: Bye Bot
234
+ #help: Bye THE_FIRST_NAME_OF_THE_BOT
235
+ #help: Also apart of Bye you can use Bæ, Good Bye, Adiós, Ciao, Bless, Bless Bless, Zbogom, Adeu
236
+ #help: Bot stops listening to you
237
+ #help:
238
+ when /^(Bye|Bæ|Good\sBye|Adiós|Ciao|Bless|Bless\sBless|Zbogom|Adeu)\s(#{@salutations.join("|")})\s*$/i
239
+ if @status==:on
240
+ bye=['Bye', 'Bæ', 'Good Bye', 'Adiós', "Ciao", "Bless", "Bless bless", "Zbogom", "Adeu"].sample
241
+ respond "#{bye} #{firstname}"
242
+ @listening.delete(from)
243
+ end
244
+
245
+ #help: exit bot
246
+ #help: quit bot
247
+ #help: close bot
248
+ #help: The bot stops running and also stops all the bots created from this master room
249
+ #help: You can use this command only if you are an admin user and you are on the master room
250
+ #help:
251
+ when /^exit\sbot/i, /^quit\sbot/i, /^close\sbot/i
252
+ if ON_MASTER_ROOM
253
+ if ADMIN_USERS.include?(from) #admin user
254
+ unless @questions.keys.include?(from)
255
+ ask("are you sure?", command, from)
256
+ else
257
+ case @questions[from]
258
+ when /yes/i, /yep/i, /sure/i
259
+ respond "Game over!"
260
+ respond "Ciao #{firstname}!"
261
+ @bots_created.each {|key, value|
262
+ value[:thread]=""
263
+ send_msg(key, "Bot has been killed by #{from}")
264
+ sleep 0.5
265
+ }
266
+ update_bots_file()
267
+ sleep 0.5
268
+ exit!
269
+ when /no/i, /nope/i, /cancel/i
270
+ @questions.delete(from)
271
+ respond "Thanks, I'm happy to be alive"
272
+ else
273
+ respond "I don't understand"
274
+ ask("are you sure do you want me to close? (yes or no)", "quit bot", from)
275
+ end
276
+ end
277
+ else
278
+ respond "Only admin users can kill me"
279
+ end
280
+
281
+ else
282
+ respond "To do this you need to be an admin user in the master room"
283
+ end
284
+
285
+ #help: start bot
286
+ #help: start this bot
287
+ #help: the bot will start to listen
288
+ #help: You can use this command only if you are an admin user
289
+ #help:
290
+ when /^start\s(this\s)?bot$/i
291
+ if ADMIN_USERS.include?(from) #admin user
292
+ respond "This bot is running and listening from now on. You can pause again: pause this bot"
293
+ @status=:on
294
+ unless ON_MASTER_ROOM
295
+ get_rooms_name_and_jid() unless @rooms_name.keys.include?(MASTER_ROOM) and @rooms_name.keys.include?(ROOM)
296
+ send_msg @rooms_name[MASTER_ROOM], "Changed status on #{@rooms_name[ROOM]} to :on"
297
+ end
298
+ else
299
+ respond "Only admin users can change my status"
300
+ end
301
+
302
+
303
+ #help: pause bot
304
+ #help: pause this bot
305
+ #help: the bot will pause so it will listen only to admin commands
306
+ #help: You can use this command only if you are an admin user
307
+ #help:
308
+ when /^pause\s(this\s)?bot$/i
309
+ if ADMIN_USERS.include?(from) #admin user
310
+ respond "This bot is paused from now on. You can start it again: start this bot"
311
+ respond "zZzzzzZzzzzZZZZZZzzzzzzzz"
312
+ @status=:paused
313
+ unless ON_MASTER_ROOM
314
+ get_rooms_name_and_jid() unless @rooms_name.keys.include?(MASTER_ROOM) and @rooms_name.keys.include?(ROOM)
315
+ send_msg @rooms_name[MASTER_ROOM], "Changed status on #{@rooms_name[ROOM]} to :paused"
316
+ end
317
+ else
318
+ respond "Only admin users can put me on pause"
319
+ end
320
+
321
+
322
+ #help: bot status
323
+ #help: Displays the status of the bot
324
+ #help: If on master room and admin user also it will display info about bots created
325
+ #help:
326
+ when /^bot\sstatus/i
327
+ respond "Status: #{@status}. Rules file: #{File.basename RULES_FILE} "
328
+ if @status==:on
329
+ respond "I'm listening to [#{@listening.join(", ")}]"
330
+ if ON_MASTER_ROOM and ADMIN_USERS.include?(from)
331
+ @bots_created.each {|key, value|
332
+ respond "#{key}: #{value}"
333
+ }
334
+ end
335
+ end
336
+
337
+ #help: create bot on ROOM_NAME
338
+ #help: creates a new bot on the room specified
339
+ #help: it will work only if you are on Master room
340
+ #help:
341
+ when /^create\sbot\son\s(.+)\s*/i
342
+ if ON_MASTER_ROOM
343
+ room=$1
344
+ if @bots_created.keys.include?(room)
345
+ respond "There is already a bot in this room: #{room}, kill it before"
346
+ else
347
+ rooms=Hash.new()
348
+ muc_browser.muc_rooms(@xmpp_namespace).each {|jid, name|
349
+ rooms[name]=jid
350
+ }
351
+ if rooms.keys.include?(room)
352
+ jid=rooms[room]
353
+ if jid!=config[:room]
354
+ jid=jid.to_s.gsub(/@.+/, '')
355
+ begin
356
+ rules_file="hipchat_smart_rules_#{jid}_#{from.gsub(" ", "_")}.rb"
357
+ if defined?(RULES_FOLDER)
358
+ rules_file=RULES_FOLDER+rules_file
359
+ else
360
+ Dir.mkdir("rules") unless Dir.exist?("rules")
361
+ Dir.mkdir("rules/#{jid}") unless Dir.exist?("rules/#{jid}")
362
+ rules_file="./rules/#{jid}/" + rules_file
363
+ end
364
+ require 'fileutils'
365
+ default_rules=(__FILE__).gsub(".rb", "_rules.rb")
366
+ File.delete(rules_file) if File.exist?(rules_file)
367
+ FileUtils.copy_file(default_rules, rules_file) unless File.exist?(rules_file)
368
+ admin_users=Array.new()
369
+ admin_users=[from]+MASTER_USERS
370
+ admin_users.uniq!
371
+ @logger.info "ruby #{$0} \"#{jid}\" \"#{admin_users.join(",")}\" \"#{rules_file}\" :on"
372
+ t = Thread.new do
373
+ `ruby #{$0} \"#{jid}\" \"#{admin_users.join(",")}\" \"#{rules_file}\" :on`
374
+ end
375
+ @bots_created[room]={
376
+ creator_name: from,
377
+ jid: jid,
378
+ status: :on,
379
+ created: Time.now.strftime('%Y-%m-%dT%H:%M:%S.000Z')[0..18],
380
+ rules_file: rules_file,
381
+ admins: admin_users.join(","),
382
+ thread: t
383
+ }
384
+ respond "The bot has been created on room: #{room}. Rules file: #{File.basename rules_file}"
385
+ update_bots_file()
386
+ rescue Exception => stack
387
+ @logger.fatal stack
388
+ message="Problem creating the bot on room #{room}. Error: <#{stack}>."
389
+ @logger.error message
390
+ respond message
391
+ end
392
+ else
393
+ respond "There is already a bot in this room: #{room}, and it is the Master Room!"
394
+ end
395
+
396
+ else
397
+ respond "There is no room with that name: #{room}, please be sure is written exactly the same"
398
+ end
399
+ end
400
+ else
401
+ respond "Sorry I cannot create bots from this room, please visit the master room"
402
+ end
403
+
404
+ #help: kill bot on ROOM_NAME
405
+ #help: kills the bot on the specified room
406
+ #help: Only works if you are on Master room and you created that bot or you are an admin user
407
+ #help:
408
+ when /^kill\sbot\son\s(.+)\s*/i
409
+ if ON_MASTER_ROOM
410
+ room=$1
411
+ if @bots_created.keys.include?(room)
412
+ if @bots_created[room][:admins].split(",").include?(from)
413
+ if @bots_created[room][:thread].kind_of?(Thread) and @bots_created[room][:thread].alive?
414
+ @bots_created[room][:thread].kill
415
+ end
416
+ @bots_created.delete(room)
417
+ update_bots_file()
418
+ respond "Bot on room: #{room}, has been killed and deleted."
419
+ send_msg(room, "Bot has been killed by #{from}")
420
+ else
421
+ respond "You need to be the creator or an admin of that room"
422
+ end
423
+ else
424
+ respond "There is no bot in this room: #{room}"
425
+ end
426
+ else
427
+ respond "Sorry I cannot kill bots from this room, please visit the master room"
428
+ end
429
+
430
+ #help: bot help
431
+ #help: bot what can I do?
432
+ #help: it will display this help
433
+ #help:
434
+ when /^bot help/i, /^bot,? what can I do/i
435
+ help_message = IO.readlines(__FILE__).join
436
+ help_message_rules = IO.readlines(RULES_FILE).join
437
+ respond "/quote " + help_message.scan(/#\s*help\s*:(.*)/).join("\n")
438
+ respond "/quote " + help_message_rules.scan(/#\s*help\s*:(.*)/).join("\n")
439
+
440
+ else
441
+ processed = false
442
+ end
443
+
444
+ #only when :on and (listening or on demand)
445
+ if @status==:on and
446
+ ((@questions.keys.include?(from) or
447
+ @listening.include?(from) or
448
+ command.match?(/^@?#{@salutations.join("|")}:*\s+(.+)$/i) or
449
+ command.match?(/^!(.+)$/)))
450
+ processed2=true
451
+
452
+ # help:
453
+ # help: These commands will run only when bot is listening to you or on demand, for example:
454
+ # help: !THE_COMMAND
455
+ # help: @bot THE_COMMAND
456
+ # help: @FIRST_NAME_BOT THE_COMMAND
457
+ # help: FIRST_NAME_BOT THE_COMMAND.
458
+ # help:
459
+ case command
460
+
461
+ #help: add shortcut NAME: COMMAND
462
+ #help: add shortcut for all NAME: COMMAND
463
+ #help: shortchut NAME: COMMAND
464
+ #help: shortchut for all NAME: COMMAND
465
+ #help: It will add a shortcut that will execute the command we supply.
466
+ #help: In case we supply 'for all' then the shorcut will be available for everybody
467
+ #help: Example:
468
+ #help: add shortcut for all Spanish account: /code require 'iso/iban'; 10.times {puts ISO::IBAN.random('ES')}
469
+ #help: Then to call this shortcut:
470
+ #help: /sc spanish account
471
+ #help: /shortcut Spanish Account
472
+ #help:
473
+ when /(add\s)?shortcut\s(for\sall)?\s*(.+):\s(.+)/i
474
+ for_all=$2
475
+ shortcut_name=$3.to_s.downcase
476
+ command_to_run=$4
477
+ @shortcuts[from]=Hash.new() unless @shortcuts.keys.include?(from)
478
+
479
+ if !ADMIN_USERS.include?(from) and @shortcuts[:all].include?(shortcut_name) and !@shortcuts[from].include?(shortcut_name)
480
+ respond "Only the creator of the shortcut or an admin user can modify it"
481
+ elsif !@shortcuts[from].include?(shortcut_name)
482
+ #new shortcut
483
+ @shortcuts[from][shortcut_name]=command_to_run
484
+ @shortcuts[:all][shortcut_name]=command_to_run if for_all.to_s!=""
485
+ update_shortcuts_file()
486
+ respond "shortcut added"
487
+ else
488
+
489
+ #are you sure? to avoid overwriting existing
490
+ unless @questions.keys.include?(from)
491
+ ask("The shortcut already exists, are you sure you want to overwrite it?", command, from)
492
+ else
493
+ case @questions[from]
494
+ when /^(yes|yep)/i
495
+ @shortcuts[from][shortcut_name]=command_to_run
496
+ @shortcuts[:all][shortcut_name]=command_to_run if for_all.to_s!=""
497
+ update_shortcuts_file()
498
+ respond "shortcut added"
499
+ @questions.delete(from)
500
+ when /^no/i
501
+ respond "ok, I won't add it"
502
+ @questions.delete(from)
503
+ else
504
+ respond "I don't understand, yes or no?"
505
+ end
506
+ end
507
+
508
+ end
509
+
510
+ #help: delete shortcut NAME
511
+ #help: It will delete the shortcut with the supplied name
512
+ #help:
513
+ when /delete\sshortcut\s(.+)/i
514
+ shortcut=$1.to_s.downcase
515
+ deleted=false
516
+
517
+ if !ADMIN_USERS.include?(from) and @shortcuts[:all].include?(shortcut) and !@shortcuts[from].include?(shortcut)
518
+ respond "Only the creator of the shortcut or an admin user can delete it"
519
+ elsif (@shortcuts.keys.include?(from) and @shortcuts[from].keys.include?(shortcut)) or
520
+ (ADMIN_USERS.include?(from) and @shortcuts[:all].include?(shortcut))
521
+ #are you sure? to avoid deleting by mistake
522
+ unless @questions.keys.include?(from)
523
+ ask("are you sure you want to delete it?", command, from)
524
+ else
525
+ case @questions[from]
526
+ when /^(yes|yep)/i
527
+ respond "shortcut deleted!"
528
+ respond "#{shortcut}: #{@shortcuts[from][shortcut]}"
529
+ @shortcuts[from].delete(shortcut)
530
+ @shortcuts[:all].delete(shortcut)
531
+ @questions.delete(from)
532
+ update_shortcuts_file()
533
+ when /^no/i
534
+ respond "ok, I won't delete it"
535
+ @questions.delete(from)
536
+ else
537
+ respond "I don't understand, yes or no?"
538
+ end
539
+ end
540
+ else
541
+ respond "shortcut not found"
542
+ end
543
+
544
+ #help: see shortcuts
545
+ #help: It will display the shortcuts stored for the user and for :all
546
+ #help:
547
+ when /see\sshortcuts/i
548
+ msg=""
549
+ if @shortcuts[:all].keys.size>0
550
+ msg="Available shortcuts for all:\n"
551
+ @shortcuts[:all].each {|name, value|
552
+ msg+="#{name}: #{value}\n"
553
+ }
554
+ respond msg
555
+ end
556
+
557
+ if @shortcuts.keys.include?(from) and @shortcuts[from].keys.size>0
558
+ new_hash=@shortcuts[from].dup
559
+ @shortcuts[:all].keys.each {|k| new_hash.delete(k)}
560
+ if new_hash.keys.size>0
561
+ msg="Available shortcuts for #{from}:\n"
562
+ new_hash.each {|name, value|
563
+ msg+="#{name}: #{value}\n"
564
+ }
565
+ respond msg
566
+ end
567
+ end
568
+ respond "No shortcuts found" if msg==""
569
+
570
+ #help: jid room ROOM_NAME
571
+ #help: shows the jid of a room name
572
+ #help:
573
+ when /jid room (.+)/
574
+ room_name=$1
575
+ get_rooms_name_and_jid()
576
+ if @rooms_jid.keys.include?(room_name)
577
+ respond "the jid of #{room_name} is #{@rooms_jid[room_name]}"
578
+ else
579
+ respond "room: #{room_name} not found"
580
+ end
581
+
582
+ # help: ruby RUBY_CODE
583
+ # help: /code RUBY_CODE
584
+ # help: runs the code supplied and returns the output. Examples:
585
+ # help: ruby require 'json'; res=[]; 20.times {res<<rand(100)}; my_json={result: res}; puts my_json.to_json
586
+ # help: /code puts (34344/99)*(34+14)
587
+ # help:
588
+ when /ruby\s(.+)/im, /\/code\s(.+)/im
589
+ code=$1
590
+ code.gsub!("\\n", "\n")
591
+ unless code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File") or
592
+ code.include?("`") or code.include?("exec") or code.include?("spawn") or code.include?("IO") or
593
+ code.match?(/open3/i) or code.match?(/bundle/i) or code.match?(/gemfile/i) or code.include?("%x") or
594
+ code.include?("ENV")
595
+ begin
596
+ require 'open3'
597
+ stdout, stderr, status = Open3.capture3("ruby -e \"#{code.gsub('"', '\"')}\"")
598
+ if stderr==""
599
+ if stdout==""
600
+ respond "Nothing returned. Remember you need to use p or puts to print"
601
+ else
602
+ respond stdout
603
+ end
604
+ else
605
+ respond stderr
606
+ end
607
+ rescue Exception => exc
608
+ respond exc
609
+ end
610
+ else
611
+ respond "Sorry I cannot run this due security issues"
612
+ end
613
+
614
+ else
615
+ processed2=false
616
+ end
617
+ processed=true if processed or processed2
618
+ end
619
+
620
+ return processed
621
+ end
622
+
623
+ def respond(msg)
624
+ muc.send Jabber::Message.new(muc.room, msg)
625
+ end
626
+
627
+ #context: previous message
628
+ #to: user that should answer
629
+ def ask(question, context, to)
630
+ muc.send Jabber::Message.new(muc.room, "#{to}: #{question}")
631
+ @questions[to]=context
632
+ end
633
+
634
+ # Uses the hipchat gem (REST)
635
+ # to: (String) Room name
636
+ # msg: (String) message to send
637
+ def send_msg(to, msg)
638
+ unless msg==""
639
+ hc_client=HipChat::Client.new(config[:token], :server_url => config[:jid].to_s.scan(/.+@(.+)\/.+/).join)
640
+ hc_client[to].send("Bot", msg)
641
+ end
642
+
643
+ end
644
+
645
+
646
+ def always
647
+ loop {sleep 1}
648
+ end
649
+
650
+ private :update_bots_file, :get_rooms_name_and_jid, :update_shortcuts_file
651
+ end
652
+
@@ -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)
9
+ puts message
10
+ end
11
+
12
+ #context: previous message
13
+ #to: user that should answer
14
+ def ask(question, context, to)
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: These are specific commands on this bot.
24
+ # help: They will be accessible only when the bot is listening to you just writing the command
25
+ # help: or the bot is not listening to you but requested on demand, for example:
26
+ # help: !THE_COMMAND
27
+ # help: @bot THE_COMMAND
28
+ # help: @FIRST_NAME_BOT THE_COMMAND
29
+ # help: FIRST_NAME_BOT THE_COMMAND
30
+ # help:
31
+ def rules(from, command, processed)
32
+ if @testing
33
+ puts "#{from}: #{command}"
34
+ if @questions.keys.include?(from)
35
+ context = @questions[from]
36
+ @questions[from] = command
37
+ command = context
38
+ end
39
+ end
40
+ firstname = from.split(" ").first
41
+ case command
42
+
43
+ # help: echo SOMETHING
44
+ # help: repeats SOMETHING
45
+ # help:
46
+ when /echo\s(.+)/i
47
+ respond $1
48
+
49
+ # help: go to sleep
50
+ # help: it will sleep the bot for 10 seconds
51
+ # help:
52
+ when /go\sto\ssleep/i
53
+ unless @questions.keys.include?(from)
54
+ ask("do you want me to take a siesta?", command, from)
55
+ else
56
+ case @questions[from]
57
+ when /yes/i, /yep/i, /sure/i
58
+ respond "zZzzzzzZZZZZZzzzzzzz!"
59
+ respond "I'll be sleeping for 10 secs... just for you"
60
+ sleep 10
61
+ when /no/i, /nope/i, /cancel/i
62
+ @questions.delete(from)
63
+ respond "Thanks, I'm happy to be awake"
64
+ else
65
+ respond "I don't understand"
66
+ ask("are you sure do you want me to sleep? (yes or no)", "go to sleep", from)
67
+ end
68
+ end
69
+ else
70
+ unless processed
71
+ resp = %w{ what huh sorry }.sample
72
+ respond "#{firstname}: #{resp}?"
73
+ end
74
+ end
75
+ end
76
+
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,95 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hipchat_smart
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Mario Ruiz
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-03-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: xmpp4r
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.5'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.5.6
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.5'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.5.6
33
+ - !ruby/object:Gem::Dependency
34
+ name: hipchat
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.6'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.6.0
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '1.6'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 1.6.0
53
+ description: The main scope of this gem is to be used internally in your company so
54
+ teams can create team rooms with their own bot to help them on their daily work,
55
+ almost everything is suitable to be automated!! hipchat_smart can create bots on
56
+ demand, create shortcuts, run ruby code... just on a chat room, you can access it
57
+ just from your mobile phone if you want and run those tests you forgot to run, get
58
+ the results, restart a server... no limits.
59
+ email: marioruizs@gmail.com
60
+ executables: []
61
+ extensions: []
62
+ extra_rdoc_files:
63
+ - LICENSE
64
+ - README.md
65
+ files:
66
+ - LICENSE
67
+ - README.md
68
+ - lib/hipchat_smart.rb
69
+ - lib/hipchat_smart_rules.rb
70
+ homepage: https://github.com/MarioRuiz/hipchat_smart
71
+ licenses:
72
+ - MIT
73
+ metadata: {}
74
+ post_install_message:
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '2.4'
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 2.6.11
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Create a hipchat bot that is really smart and so easy to expand, create new
94
+ bots on demand, run ruby code on chat, create shortcuts...
95
+ test_files: []