slack-smart-bot 1.10.0 → 1.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +134 -23
  3. data/lib/slack/smart-bot/comm/delete.rb +13 -0
  4. data/lib/slack/smart-bot/comm/dont_understand.rb +2 -2
  5. data/lib/slack/smart-bot/comm/get_channel_members.rb +7 -3
  6. data/lib/slack/smart-bot/comm/get_presence.rb +20 -0
  7. data/lib/slack/smart-bot/comm/get_users.rb +1 -1
  8. data/lib/slack/smart-bot/comm/respond.rb +24 -13
  9. data/lib/slack/smart-bot/comm/send_msg_user.rb +12 -11
  10. data/lib/slack/smart-bot/comm/set_status.rb +21 -0
  11. data/lib/slack/smart-bot/comm.rb +3 -0
  12. data/lib/slack/smart-bot/commands/general/add_admin.rb +51 -0
  13. data/lib/slack/smart-bot/commands/general/add_announcement.rb +1 -1
  14. data/lib/slack/smart-bot/commands/general/add_memo_team.rb +117 -0
  15. data/lib/slack/smart-bot/commands/general/add_team.rb +80 -0
  16. data/lib/slack/smart-bot/commands/general/add_vacation.rb +51 -0
  17. data/lib/slack/smart-bot/commands/general/allow_access.rb +67 -0
  18. data/lib/slack/smart-bot/commands/general/bot_help.rb +20 -11
  19. data/lib/slack/smart-bot/commands/general/delete_announcement.rb +1 -1
  20. data/lib/slack/smart-bot/commands/general/delete_memo_team.rb +69 -0
  21. data/lib/slack/smart-bot/commands/general/delete_share.rb +1 -1
  22. data/lib/slack/smart-bot/commands/general/delete_team.rb +54 -0
  23. data/lib/slack/smart-bot/commands/general/deny_access.rb +36 -0
  24. data/lib/slack/smart-bot/commands/general/ping_team.rb +100 -0
  25. data/lib/slack/smart-bot/commands/general/poster.rb +116 -0
  26. data/lib/slack/smart-bot/commands/general/remove_admin.rb +58 -0
  27. data/lib/slack/smart-bot/commands/general/remove_vacation.rb +27 -0
  28. data/lib/slack/smart-bot/commands/general/see_access.rb +24 -0
  29. data/lib/slack/smart-bot/commands/general/see_admins.rb +33 -0
  30. data/lib/slack/smart-bot/commands/general/see_announcements.rb +7 -5
  31. data/lib/slack/smart-bot/commands/general/see_command_ids.rb +29 -0
  32. data/lib/slack/smart-bot/commands/general/see_favorite_commands.rb +3 -4
  33. data/lib/slack/smart-bot/commands/general/see_statuses.rb +34 -21
  34. data/lib/slack/smart-bot/commands/general/see_teams.rb +402 -0
  35. data/lib/slack/smart-bot/commands/general/see_vacations.rb +58 -0
  36. data/lib/slack/smart-bot/commands/general/see_vacations_team.rb +136 -0
  37. data/lib/slack/smart-bot/commands/general/set_memo_status.rb +58 -0
  38. data/lib/slack/smart-bot/commands/general/share_messages.rb +1 -1
  39. data/lib/slack/smart-bot/commands/general/update_team.rb +130 -0
  40. data/lib/slack/smart-bot/commands/general_bot_commands.rb +442 -13
  41. data/lib/slack/smart-bot/commands/on_bot/add_shortcut.rb +2 -1
  42. data/lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb +2 -1
  43. data/lib/slack/smart-bot/commands/on_bot/admin/extend_rules.rb +2 -1
  44. data/lib/slack/smart-bot/commands/on_bot/admin/pause_bot.rb +2 -1
  45. data/lib/slack/smart-bot/commands/on_bot/admin/pause_routine.rb +2 -1
  46. data/lib/slack/smart-bot/commands/on_bot/admin/remove_routine.rb +2 -1
  47. data/lib/slack/smart-bot/commands/on_bot/admin/run_routine.rb +3 -2
  48. data/lib/slack/smart-bot/commands/on_bot/admin/see_result_routine.rb +2 -1
  49. data/lib/slack/smart-bot/commands/on_bot/admin/see_routines.rb +10 -9
  50. data/lib/slack/smart-bot/commands/on_bot/admin/start_bot.rb +2 -1
  51. data/lib/slack/smart-bot/commands/on_bot/admin/start_routine.rb +2 -1
  52. data/lib/slack/smart-bot/commands/on_bot/admin/stop_using_rules_on.rb +2 -1
  53. data/lib/slack/smart-bot/commands/on_bot/admin_master/delete_message.rb +25 -0
  54. data/lib/slack/smart-bot/commands/on_bot/admin_master/get_bot_logs.rb +1 -0
  55. data/lib/slack/smart-bot/commands/on_bot/admin_master/react_to.rb +3 -1
  56. data/lib/slack/smart-bot/commands/on_bot/admin_master/send_message.rb +15 -2
  57. data/lib/slack/smart-bot/commands/on_bot/delete_repl.rb +2 -1
  58. data/lib/slack/smart-bot/commands/on_bot/delete_shortcut.rb +5 -4
  59. data/lib/slack/smart-bot/commands/on_bot/general/bot_stats.rb +416 -0
  60. data/lib/slack/smart-bot/commands/{general → on_bot/general}/bot_status.rb +1 -0
  61. data/lib/slack/smart-bot/commands/{general → on_bot/general}/leaderboard.rb +1 -0
  62. data/lib/slack/smart-bot/commands/{general → on_bot/general}/stop_using_rules.rb +1 -0
  63. data/lib/slack/smart-bot/commands/{general → on_bot/general}/suggest_command.rb +6 -0
  64. data/lib/slack/smart-bot/commands/{general → on_bot/general}/use_rules.rb +1 -0
  65. data/lib/slack/smart-bot/commands/{general → on_bot/general}/whats_new.rb +2 -1
  66. data/lib/slack/smart-bot/commands/on_bot/get_repl.rb +2 -1
  67. data/lib/slack/smart-bot/commands/on_bot/kill_repl.rb +32 -0
  68. data/lib/slack/smart-bot/commands/on_bot/repl.rb +73 -15
  69. data/lib/slack/smart-bot/commands/on_bot/ruby_code.rb +1 -0
  70. data/lib/slack/smart-bot/commands/on_bot/run_repl.rb +117 -28
  71. data/lib/slack/smart-bot/commands/on_bot/see_repls.rb +2 -1
  72. data/lib/slack/smart-bot/commands/on_bot/see_shortcuts.rb +3 -2
  73. data/lib/slack/smart-bot/commands/on_master/admin/kill_bot_on_channel.rb +5 -4
  74. data/lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb +3 -2
  75. data/lib/slack/smart-bot/commands/on_master/admin_master/notify_message.rb +2 -1
  76. data/lib/slack/smart-bot/commands/on_master/admin_master/publish_announcements.rb +6 -3
  77. data/lib/slack/smart-bot/commands/on_master/admin_master/set_general_message.rb +2 -1
  78. data/lib/slack/smart-bot/commands/on_master/admin_master/set_maintenance.rb +2 -1
  79. data/lib/slack/smart-bot/commands/on_master/create_bot.rb +1 -0
  80. data/lib/slack/smart-bot/commands/on_master/where_smartbot.rb +41 -0
  81. data/lib/slack/smart-bot/commands.rb +30 -7
  82. data/lib/slack/smart-bot/listen.rb +30 -30
  83. data/lib/slack/smart-bot/process.rb +53 -23
  84. data/lib/slack/smart-bot/process_first.rb +2 -2
  85. data/lib/slack/smart-bot/treat_message.rb +23 -17
  86. data/lib/slack/smart-bot/utils/build_help.rb +1 -1
  87. data/lib/slack/smart-bot/utils/check_vacations.rb +43 -0
  88. data/lib/slack/smart-bot/utils/create_routine_thread.rb +1 -1
  89. data/lib/slack/smart-bot/utils/get_access_channels.rb +13 -0
  90. data/lib/slack/smart-bot/utils/get_admins_channels.rb +33 -0
  91. data/lib/slack/smart-bot/utils/get_bots_created.rb +27 -10
  92. data/lib/slack/smart-bot/utils/get_channels_name_and_id.rb +7 -2
  93. data/lib/slack/smart-bot/utils/get_command_ids.rb +84 -0
  94. data/lib/slack/smart-bot/utils/get_help.rb +36 -19
  95. data/lib/slack/smart-bot/utils/get_repls.rb +22 -2
  96. data/lib/slack/smart-bot/utils/get_routines.rb +22 -2
  97. data/lib/slack/smart-bot/utils/get_teams.rb +22 -0
  98. data/lib/slack/smart-bot/utils/get_vacations.rb +22 -0
  99. data/lib/slack/smart-bot/utils/has_access.rb +25 -9
  100. data/lib/slack/smart-bot/utils/is_admin.rb +27 -0
  101. data/lib/slack/smart-bot/utils/save_stats.rb +52 -42
  102. data/lib/slack/smart-bot/utils/save_status.rb +22 -7
  103. data/lib/slack/smart-bot/utils/update_access_channels.rb +8 -0
  104. data/lib/slack/smart-bot/utils/update_admins_channels.rb +25 -0
  105. data/lib/slack/smart-bot/utils/update_bots_file.rb +28 -7
  106. data/lib/slack/smart-bot/utils/update_repls.rb +7 -4
  107. data/lib/slack/smart-bot/utils/update_routines.rb +9 -3
  108. data/lib/slack/smart-bot/utils/update_shortcuts_file.rb +13 -6
  109. data/lib/slack/smart-bot/utils/update_teams.rb +16 -0
  110. data/lib/slack/smart-bot/utils/update_vacations.rb +16 -0
  111. data/lib/slack/smart-bot/utils.rb +11 -0
  112. data/lib/slack-smart-bot.rb +50 -12
  113. data/lib/slack-smart-bot_general_commands.rb +16 -1
  114. data/whats_new.txt +12 -30
  115. metadata +78 -21
  116. data/lib/slack/smart-bot/commands/general/bot_stats.rb +0 -314
@@ -17,7 +17,7 @@ class SlackSmartBot
17
17
  # help: If 'clean' specified the repl won't pre execute the code written on the .smart-bot-repl file
18
18
  # help: To avoid a message to be treated, start the message with '-'.
19
19
  # help: Send _quit_, _bye_ or _exit_ to finish the session.
20
- # help: Send puts, print, p or pp if you want to print out something when using `run repl` later.
20
+ # help: Send puts, print, p or pp if you want to print out something when using _run repl_ later.
21
21
  # help: After 30 minutes of no communication with the Smart Bot the session will be dismissed.
22
22
  # help: If you declare on your rules file a method called 'project_folder' returning the path for the project folder, the code will be executed from that folder.
23
23
  # help: By default it will be automatically loaded the gems: string_pattern, nice_hash and nice_http
@@ -30,6 +30,7 @@ class SlackSmartBot
30
30
  # help: _repl delete_logs_
31
31
  # help: _private repl random-ssn_
32
32
  # help: <https://github.com/MarioRuiz/slack-smart-bot#repl|more info>
33
+ # help: command_id: :repl
33
34
  # help:
34
35
  def repl(dest, user, session_name, env_vars, rules_file, command, description, type)
35
36
  #todo: add more tests
@@ -78,11 +79,14 @@ class SlackSmartBot
78
79
  update_repls()
79
80
  end
80
81
  react :running
82
+ @ts_react ||= {}
81
83
  if Thread.current[:ts].to_s == ''
82
- @ts_react = Thread.current[:thread_ts]
84
+ @ts_react[session_name] = Thread.current[:thread_ts]
83
85
  else
84
- @ts_react = Thread.current[:ts]
86
+ @ts_react[session_name] = Thread.current[:ts]
85
87
  end
88
+ @ts_repl ||= {}
89
+ @ts_repl[session_name] = ''
86
90
 
87
91
  message = "Session name: *#{session_name}*
88
92
  From now on I will execute all you write as a Ruby command and I will keep the session open until you send `quit` or `bye` or `exit`.
@@ -115,14 +119,15 @@ class SlackSmartBot
115
119
  end
116
120
 
117
121
  process_to_run = '
118
- ruby -e "' + env_vars.join("\n") + '
122
+ ' + env_vars.join("\n") + '
119
123
  require \"amazing_print\"
124
+ require \"stringio\"
120
125
  bindme' + serialt + ' = binding
121
126
  eval(\"require \'nice_http\'\" , bindme' + serialt + ')
122
127
  def ls(obj)
123
128
  (obj.methods - Object.methods)
124
129
  end
125
-
130
+ file_run_path = \"' + + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.rb\"
126
131
  file_input_repl = File.open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.input\", \"r\")
127
132
  ' + pre_execute + '
128
133
  while true do
@@ -141,18 +146,40 @@ class SlackSmartBot
141
146
  end
142
147
  error = false
143
148
  begin
144
- resp_repl = eval(code_to_run_repl.gsub(/^\s*(puts|print|p|pp)\s/, \"\"), bindme' + serialt + ')
149
+ begin
150
+ original_stdout = $stdout
151
+ $stdout = StringIO.new
152
+ resp_repl = eval(code_to_run_repl, bindme' + serialt + ')
153
+ stdout_repl = $stdout.string
154
+ ensure
155
+ $stdout = original_stdout
156
+ end
145
157
  rescue Exception => resp_repl
146
158
  error = true
147
159
  end
148
- unless error
160
+ if error
161
+ open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.output\", \"a+\") {|f|
162
+ f.puts \"\`\`\`\n#{resp_repl.to_s.gsub(/^.+' + session_name + '\.rb:\d+:/,\"\")}\`\`\`\"
163
+ }
164
+ else
149
165
  if code_to_run_repl.match?(/^\s*p\s+/i)
166
+ resp_repl = stdout_repl unless stdout_repl.to_s == \'\'
167
+ if stdout_repl.to_s == \'\'
168
+ resp_repl = resp_repl.inspect
169
+ else
170
+ resp_repl = stdout_repl
171
+ end
150
172
  open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.output\", \"a+\") {|f|
151
- f.puts \"\`\`\`\n#{resp_repl.inspect}\n\`\`\`\"
173
+ f.puts \"\`\`\`\n#{resp_repl}\`\`\`\"
152
174
  }
153
175
  else
176
+ if stdout_repl.to_s == \'\'
177
+ resp_repl = resp_repl.ai
178
+ else
179
+ resp_repl = stdout_repl
180
+ end
154
181
  open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.output\", \"a+\") {|f|
155
- f.puts \"\`\`\`\n#{resp_repl.ai}\n\`\`\`\"
182
+ f.puts \"\`\`\`\n#{resp_repl}\`\`\`\"
156
183
  }
157
184
  end
158
185
  unless !add_to_run_repl
@@ -163,16 +190,35 @@ class SlackSmartBot
163
190
  end
164
191
  end
165
192
  end
166
- end"
193
+ end
167
194
  '
168
195
  unless rules_file.empty? # to get the project_folder
169
196
  begin
170
197
  eval(File.new(config.path+rules_file).read) if File.exist?(config.path+rules_file)
171
198
  end
172
199
  end
173
- started = Time.now
174
- process_to_run = ("cd #{project_folder} &&" + process_to_run) if defined?(project_folder)
200
+ process_to_run.gsub!('\"','"')
201
+ file_run_path = "./tmp/repl/#{@channel_id}/#{session_name}.rb"
202
+ if defined?(project_folder)
203
+ Dir.mkdir("#{project_folder}/tmp/") unless Dir.exist?("#{project_folder}/tmp/")
204
+ Dir.mkdir("#{project_folder}/tmp/repl") unless Dir.exist?("#{project_folder}/tmp/repl")
205
+ Dir.mkdir("#{project_folder}/tmp/repl/#{@channel_id}/") unless Dir.exist?("#{project_folder}/tmp/repl/#{@channel_id}/")
206
+ file_run = File.open(file_run_path.gsub('./',"#{project_folder}/"), "w")
207
+ file_run.write process_to_run
208
+ file_run.close
209
+ else
210
+ Dir.mkdir("./tmp/") unless Dir.exist?("./tmp/")
211
+ Dir.mkdir("./tmp/repl") unless Dir.exist?("./tmp/repl")
212
+ Dir.mkdir("./tmp/repl/#{@channel_id}/") unless Dir.exist?("./tmp/repl/#{@channel_id}/")
213
+ file_run = File.open(file_run_path, "w")
214
+ file_run.write process_to_run
215
+ file_run.close
216
+ end
175
217
 
218
+ process_to_run = "ruby #{file_run_path}"
219
+
220
+ started = Time.now
221
+ process_to_run = ("cd #{project_folder} && " + process_to_run) if defined?(project_folder)
176
222
  stdin, stdout, stderr, wait_thr = Open3.popen3(process_to_run)
177
223
  timeout = 30 * 60 # 30 minutes
178
224
 
@@ -185,7 +231,7 @@ class SlackSmartBot
185
231
  f.puts 'quit'
186
232
  }
187
233
  respond "REPL session finished: #{@repl_sessions[from][:name]}", dest
188
- unreact :running, @ts_react
234
+ unreact :running, @ts_react[@repl_sessions[from].name]
189
235
  pids = `pgrep -P #{@repl_sessions[from][:pid]}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
190
236
  pids.each do |pid|
191
237
  begin
@@ -199,6 +245,10 @@ class SlackSmartBot
199
245
  sleep 0.2
200
246
  resp_repl = file_output_repl.read
201
247
  if resp_repl.to_s!=''
248
+ if @ts_repl[@repl_sessions[from].name].to_s != ''
249
+ unreact(:running, @ts_repl[@repl_sessions[from].name])
250
+ @ts_repl[@repl_sessions[from].name] = ''
251
+ end
202
252
  if resp_repl.to_s.lines.count < 60 and resp_repl.to_s.size < 3500
203
253
  respond resp_repl, dest
204
254
  else
@@ -211,6 +261,8 @@ class SlackSmartBot
211
261
  @logger.fatal excp
212
262
  end
213
263
  end
264
+ elsif @repl_sessions.key?(from) and @repl_sessions[from][:command].to_s == ''
265
+ respond 'You are already in a repl on this SmartBot. You need to quit that one before starting a new one.'
214
266
  else
215
267
  @repl_sessions[from][:finished] = Time.now
216
268
  code = @repl_sessions[from][:command]
@@ -224,7 +276,9 @@ class SlackSmartBot
224
276
  code.match?(/open3/i) or code.match?(/bundle/i) or code.match?(/gemfile/i) or code.include?("%x") or
225
277
  code.include?("ENV") or code.match?(/=\s*IO/) or code.include?("Dir.") or
226
278
  code.match?(/=\s*File/) or code.match?(/=\s*Dir/) or code.match?(/<\s*File/) or code.match?(/<\s*Dir/) or
227
- code.match?(/\w+:\s*File/) or code.match?(/\w+:\s*Dir/)
279
+ code.match?(/\w+:\s*File/) or code.match?(/\w+:\s*Dir/) or
280
+ code.match?(/=?\s*(require|load)(\(|\s)/i)
281
+
228
282
  respond "Sorry I cannot run this due security reasons", dest
229
283
  else
230
284
  @repl_sessions[from][:input]<<code
@@ -234,7 +288,7 @@ class SlackSmartBot
234
288
  f.puts code
235
289
  }
236
290
  respond "REPL session finished: #{@repl_sessions[from][:name]}", dest
237
- unreact :running, @ts_react
291
+ unreact :running, @ts_react[@repl_sessions[from].name]
238
292
  pids = `pgrep -P #{@repl_sessions[from][:pid]}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
239
293
  pids.each do |pid|
240
294
  begin
@@ -246,6 +300,10 @@ class SlackSmartBot
246
300
  when /^\s*-/i
247
301
  #ommit
248
302
  else
303
+ if @ts_repl[@repl_sessions[from].name].to_s == ''
304
+ @ts_repl[@repl_sessions[from].name] = Thread.current[:ts]
305
+ react :running
306
+ end
249
307
  open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", 'a+') {|f|
250
308
  f.puts code
251
309
  }
@@ -6,6 +6,7 @@ class SlackSmartBot
6
6
  # help: _code puts (34344/99)*(34+14)_
7
7
  # help: _ruby require 'json'; res=[]; 20.times {res<<rand(100)}; my_json={result: res}; puts my_json.to_json_
8
8
  # help: <https://github.com/MarioRuiz/slack-smart-bot#running-ruby-code-on-a-conversation|more info>
9
+ # help: command_id: :ruby_code
9
10
  # help:
10
11
 
11
12
  def ruby_code(dest, user, code, rules_file)
@@ -2,27 +2,39 @@ class SlackSmartBot
2
2
  # help: ----------------------------------------------
3
3
  # help: `run repl SESSION_NAME`
4
4
  # help: `run repl SESSION_NAME ENV_VAR=VALUE ENV_VAR=VALUE`
5
+ # help: `run repl SESSION_NAME PARAMS`
5
6
  # help: `run live SESSION_NAME`
6
7
  # help: `run irb SESSION_NAME`
7
- # help: Will run the repl session specified and return the output.
8
+ # help: Will run the repl session specified and return the output.
8
9
  # help: You can supply the Environmental Variables you need for the Session
10
+ # help: PARAMS: Also it is possible to supply code that will be run before the repl code on the same session.
9
11
  # help: It will return only the values that were print out on the repl with puts, print, p or pp
10
12
  # help: Example:
11
13
  # help: _run repl CreateCustomer LOCATION=spain HOST='https://10.30.40.50:8887'_
12
14
  # help: <https://github.com/MarioRuiz/slack-smart-bot#repl|more info>
15
+ # help: command_id: :run_repl
13
16
  # help:
14
- def run_repl(dest, user, session_name, env_vars, rules_file)
17
+ def run_repl(dest, user, session_name, env_vars, prerun, rules_file)
15
18
  #todo: add tests
16
19
  from = user.name
17
20
  if has_access?(__method__, user)
18
21
  save_stats(__method__)
19
22
  Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
20
23
  Dir.mkdir("#{config.path}/repl/#{@channel_id}") unless Dir.exist?("#{config.path}/repl/#{@channel_id}")
24
+ code = prerun.join("\n")
21
25
  if File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.run")
22
- if @repls.key?(session_name) and (@repls[session_name][:type] == :private or @repls[session_name][:type] == :private_clean) and
23
- @repls[session_name][:creator_name]!=user.name and
24
- !config.admins.include?(user.name)
26
+ if @repls.key?(session_name) and (@repls[session_name][:type] == :private or @repls[session_name][:type] == :private_clean) and
27
+ @repls[session_name][:creator_name] != user.name and
28
+ !is_admin?(user.name)
25
29
  respond "The REPL with session name: #{session_name} is private", dest
30
+ elsif !prerun.empty? and (code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File.") or
31
+ code.include?("`") or code.include?("exec") or code.include?("spawn") or code.include?("IO.") or
32
+ code.match?(/open3/i) or code.match?(/bundle/i) or code.match?(/gemfile/i) or code.include?("%x") or
33
+ code.include?("ENV") or code.match?(/=\s*IO/) or code.include?("Dir.") or
34
+ code.match?(/=\s*File/) or code.match?(/=\s*Dir/) or code.match?(/<\s*File/) or code.match?(/<\s*Dir/) or
35
+ code.match?(/\w+:\s*File/) or code.match?(/\w+:\s*Dir/) or
36
+ code.match?(/=?\s*(require|load)(\(|\s)/i))
37
+ respond "Sorry I cannot run this due security reasons", dest
26
38
  else
27
39
  if @repls.key?(session_name) #not temp
28
40
  @repls[session_name][:accessed] = Time.now.to_s
@@ -31,47 +43,124 @@ class SlackSmartBot
31
43
  else
32
44
  @repls[session_name][:runs_by_others] += 1
33
45
  end
34
- update_repls()
46
+ update_repls()
35
47
  end
36
48
 
37
49
  content = env_vars.join("\n")
38
50
  content += "\nrequire 'nice_http'\n"
39
51
  unless rules_file.empty? # to get the project_folder
40
52
  begin
41
- eval(File.new(config.path+rules_file).read) if File.exist?(config.path+rules_file)
53
+ eval(File.new(config.path + rules_file).read) if File.exist?(config.path + rules_file)
42
54
  end
43
55
  end
44
- if File.exist?("#{project_folder}/.smart-bot-repl") and
45
- ((@repls.key?(session_name) and @repls[session_name][:type] != :private_clean and @repls[session_name][:type] != :public_clean) or !@repls.key?(session_name))
56
+ if File.exist?("#{project_folder}/.smart-bot-repl") and
57
+ ((@repls.key?(session_name) and @repls[session_name][:type] != :private_clean and @repls[session_name][:type] != :public_clean) or !@repls.key?(session_name))
46
58
  content += File.read("#{project_folder}/.smart-bot-repl")
47
59
  content += "\n"
48
60
  end
49
- content += File.read("#{config.path}/repl/#{@channel_id}/#{session_name}.run").gsub(/^(quit|exit|bye)$/i,'') #todo: remove this gsub, it will never contain it
61
+ unless prerun.empty?
62
+ content += prerun.join("\n")
63
+ content += "\n"
64
+ end
65
+ content += File.read("#{config.path}/repl/#{@channel_id}/#{session_name}.run").gsub(/^(quit|exit|bye)$/i, "") #todo: remove this gsub, it will never contain it
50
66
  Dir.mkdir("#{project_folder}/tmp") unless Dir.exist?("#{project_folder}/tmp")
51
67
  Dir.mkdir("#{project_folder}/tmp/repl") unless Dir.exist?("#{project_folder}/tmp/repl")
52
- File.write("#{project_folder}/tmp/repl/#{session_name}.rb", content, mode: "w+")
53
- process_to_run = "ruby ./tmp/repl/#{session_name}.rb"
68
+ if Thread.current[:on_thread]
69
+ # to force stdout.each to be performed every 3 seconds
70
+ content = "Thread.new do
71
+ while true do
72
+ puts ''
73
+ sleep 3
74
+ end
75
+ end
76
+ #{content}
77
+ "
78
+ end
79
+ random = "5:LN&".gen
80
+ File.write("#{project_folder}/tmp/repl/#{session_name}_#{user.name}_#{random}.rb", content, mode: "w+")
81
+ process_to_run = "ruby ./tmp/repl/#{session_name}_#{user.name}_#{random}.rb"
54
82
  process_to_run = ("cd #{project_folder} && #{process_to_run}") if defined?(project_folder)
55
- respond "Running REPL #{session_name}"
56
- stdout, stderr, status = Open3.capture3(process_to_run)
57
- if stderr == ""
58
- if stdout == ""
59
- respond "*#{session_name}*: Nothing returned."
60
- else
61
- if stdout.to_s.lines.count < 60 and stdout.to_s.size < 3500
62
- respond "*#{session_name}*: #{stdout}"
83
+ respond "Running REPL #{session_name} (id: #{random})"
84
+ @run_repls[random] = { user: user.name, name: session_name, pid: '' }
85
+ react :running
86
+
87
+ require "pty"
88
+ timeout = 60 * 60 * 4 # 4 hours
89
+
90
+ started = Time.now
91
+ results = []
92
+ begin
93
+ PTY.spawn(process_to_run) do |stdout, stdin, pid|
94
+ last_result = -1
95
+ last_time = Time.now
96
+ @run_repls[random].pid = pid
97
+ begin
98
+ stdout.each do |line|
99
+ if (Time.now - started) > timeout
100
+ respond "run REPL session finished. Max time reached: #{session_name} (id: #{random})", dest
101
+ pids = `pgrep -P #{pid}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
102
+ pids.each do |pd|
103
+ begin
104
+ Process.kill("KILL", pd)
105
+ rescue
106
+ end
107
+ end
108
+ break
109
+ else
110
+ results << line
111
+ if Thread.current[:on_thread]
112
+ if (Time.now - last_time) > 2
113
+ if (results.size - last_result) < 60 and results[(last_result + 1)..-1].join.size < 3500
114
+ output = ""
115
+ results[(last_result + 1)..-1].each do |li|
116
+ if li.match?(/^\s*{.+}\s*$/) or li.match?(/^\s*\[.+\]\s*$/)
117
+ output += "```\n#{li}```\n"
118
+ else
119
+ output += li
120
+ end
121
+ end
122
+ respond output
123
+ else
124
+ send_file(dest, "", "response.rb", "", "text/plain", "ruby", content: results[(last_result + 1)..-1].join)
125
+ end
126
+ last_result = results.size - 1
127
+ last_time = Time.now
128
+ end
129
+ end
130
+ end
131
+ end
132
+ rescue Errno::EIO
133
+ @logger.warn "run_repl PTY Errno:EIO error"
134
+ end
135
+ if results.empty?
136
+ respond "*#{session_name}* (id: #{random}): Nothing returned."
63
137
  else
64
- send_file(dest, "", 'response.rb', "", 'text/plain', "ruby", content: stdout)
138
+ if last_result != (results.size - 1)
139
+ if (results.size - last_result) < 60 and results[(last_result + 1)..-1].join.size < 3500
140
+ output = ""
141
+ results[(last_result + 1)..-1].each do |li|
142
+ if li.match?(/^\s*{.+}\s*$/) or li.match?(/^\s*\[.+\]\s*$/)
143
+ output += "```\n#{li}```\n"
144
+ else
145
+ output += li
146
+ end
147
+ end
148
+ if Thread.current[:on_thread]
149
+ respond output
150
+ else
151
+ respond "*#{session_name}* (id: #{random}):\n#{output}"
152
+ end
153
+ else
154
+ send_file(dest, "", "response.rb", "", "text/plain", "ruby", content: results[(last_result + 1)..-1].join)
155
+ end
156
+ end
65
157
  end
66
158
  end
67
- else
68
- if (stdout.to_s+stderr.to_s).lines.count < 60
69
- respond "*#{session_name}*: #{stdout} #{stderr}"
70
- else
71
- send_file(dest, "", 'response.rb', "", 'text/plain', "ruby", content: (stdout.to_s+stderr.to_s))
72
- end
73
-
159
+ rescue PTY::ChildExited
160
+ @logger.warn "run_repl PTY The child process exited!"
74
161
  end
162
+ @run_repls.delete(random) if @run_repls.key?(random)
163
+ unreact :running
75
164
  end
76
165
  else
77
166
  respond "The REPL with session name: #{session_name} doesn't exist on this Smart Bot Channel", dest
@@ -4,6 +4,7 @@ class SlackSmartBot
4
4
  # help: `see irbs`
5
5
  # help: It will display the repls
6
6
  # help: <https://github.com/MarioRuiz/slack-smart-bot#repl|more info>
7
+ # help: command_id: :see_repls
7
8
  # help:
8
9
  def see_repls(dest, user, typem)
9
10
  #todo: add tests
@@ -12,7 +13,7 @@ class SlackSmartBot
12
13
  if has_access?(__method__, user)
13
14
  message = ""
14
15
  @repls.sort.to_h.each do |session_name, repl|
15
- if (repl.creator_name == user.name or repl.type == :public or repl.type == :public_clean) or (config.admins.include?(user.name) and typem == :on_dm)
16
+ if (repl.creator_name == user.name or repl.type == :public or repl.type == :public_clean) or (is_admin?(user.name) and typem == :on_dm)
16
17
  message += "(#{repl.type}) *#{session_name}*: #{repl.description} / created: #{repl.created} / accessed: #{repl.accessed} / creator: #{repl.creator_name} / runs: #{repl.runs_by_creator+repl.runs_by_others} / gets: #{repl.gets} \n"
17
18
  end
18
19
  end
@@ -4,6 +4,7 @@ class SlackSmartBot
4
4
  # help: `see sc`
5
5
  # help: It will display the shortcuts stored for the user and for :all
6
6
  # help: <https://github.com/MarioRuiz/slack-smart-bot#shortcuts|more info>
7
+ # help: command_id: :see_shortcuts
7
8
  # help:
8
9
  def see_shortcuts(dest, user, typem)
9
10
  save_stats(__method__)
@@ -28,7 +29,7 @@ class SlackSmartBot
28
29
  end
29
30
  msg2 = ''
30
31
  if @shortcuts.keys.include?(from) and @shortcuts[from].keys.size > 0
31
- new_hash = @shortcuts[from].dup
32
+ new_hash = @shortcuts[from].deep_copy
32
33
  @shortcuts[:all].keys.each { |k| new_hash.delete(k) }
33
34
  if new_hash.keys.size > 0
34
35
  msg2 = "*Available shortcuts for #{from}:*\n"
@@ -38,7 +39,7 @@ class SlackSmartBot
38
39
  end
39
40
  end
40
41
  if @shortcuts_global.keys.include?(from) and @shortcuts_global[from].keys.size > 0
41
- new_hash = @shortcuts_global[from].dup
42
+ new_hash = @shortcuts_global[from].deep_copy
42
43
  @shortcuts_global[:all].keys.each { |k| new_hash.delete(k) }
43
44
  if new_hash.keys.size > 0
44
45
  msg2 = "*Available shortcuts for #{from}:*\n" if msg2 == ''
@@ -4,6 +4,7 @@ class SlackSmartBot
4
4
  # helpmaster: kills the bot on the specified channel
5
5
  # helpmaster: Only works if you are on Master channel and you created that bot or you are an admin user
6
6
  # helpmaster: <https://github.com/MarioRuiz/slack-smart-bot#bot-management|more info>
7
+ # helpmaster: command_id: :kill_bot_on_channel
7
8
  # helpmaster:
8
9
  def kill_bot_on_channel(dest, from, channel)
9
10
  save_stats(__method__)
@@ -20,15 +21,15 @@ class SlackSmartBot
20
21
  respond "There is no channel with that name: #{channel}, please be sure is written exactly the same", dest
21
22
  elsif @bots_created.keys.include?(channel_id)
22
23
  @bots_created[channel_id] ||= {}
23
- if @bots_created[channel_id][:admins].to_s.split(",").include?(from)
24
+ if @bots_created[channel_id][:admins].to_s.split(",").include?(from) # todo: consider adding is_admin?
24
25
  if @bots_created[channel_id][:thread].kind_of?(Thread) and @bots_created[channel_id][:thread].alive?
25
26
  @bots_created[channel_id][:thread].kill
26
27
  end
27
28
  @bots_created.delete(channel_id)
28
- update_bots_file()
29
- respond "Bot on channel: #{channel}, has been killed and deleted.", dest
30
29
  send_msg_channel(channel, "Bot has been killed by #{from}")
31
- save_status :off, :killed, "The admin killed SmartBot on *<##{channel_id}|#{@channels_name[channel_id]}>*"
30
+ respond "Bot on channel: #{channel}, has been killed and deleted.", dest
31
+ save_status :off, :killed, "The admin killed SmartBot on *##{@channels_name[channel_id]}*"
32
+ update_bots_file()
32
33
  else
33
34
  respond "You need to be the creator or an admin of that bot channel", dest
34
35
  end
@@ -7,11 +7,12 @@ class SlackSmartBot
7
7
  # helpadmin: The bot stops running and also stops all the bots created from this master channel
8
8
  # helpadmin: You can use this command only if you are an admin user and you are on the master channel
9
9
  # helpadmin: <https://github.com/MarioRuiz/slack-smart-bot#bot-management|more info>
10
+ # helpadmin: command_id: :exit_bot
10
11
  # helpadmin:
11
12
  def exit_bot(command, from, dest, display_name)
12
13
  save_stats(__method__)
13
14
  if config.on_master_bot
14
- if config.admins.include?(from) #admin user
15
+ if config.masters.include?(from) #admin user
15
16
  if answer.empty?
16
17
  ask("are you sure?", command, from, dest)
17
18
  else
@@ -22,7 +23,7 @@ class SlackSmartBot
22
23
  @bots_created.each { |key, value|
23
24
  value[:thread] = ""
24
25
  send_msg_channel(key, "Bot has been closed by #{from}")
25
- save_status :off, :exited, "The admin closed SmartBot on *<##{key}|#{value.channel_name}>*"
26
+ save_status :off, :exited, "The admin closed SmartBot on *##{value.channel_name}*"
26
27
  sleep 0.5
27
28
  }
28
29
  update_bots_file()
@@ -8,11 +8,12 @@ class SlackSmartBot
8
8
  # helpmaster: It will send a notification message to the specified channel and to its extended channels
9
9
  # helpmaster: Only works if you are on Master channel and you are a master admin user
10
10
  # helpmaster: <https://github.com/MarioRuiz/slack-smart-bot#sending-notifications|more info>
11
+ # helpmaster: command_id: :notify_message
11
12
  # helpmaster:
12
13
  def notify_message(dest, from, where, message)
13
14
  save_stats(__method__)
14
15
  if config.on_master_bot
15
- if config.admins.include?(from) #admin user
16
+ if config.masters.include?(from) #admin user
16
17
  if where.nil? #not all and not channel
17
18
  @bots_created.each do |k, v|
18
19
  respond message, k
@@ -2,21 +2,24 @@ class SlackSmartBot
2
2
  # helpmaster: ----------------------------------------------
3
3
  # helpmaster: `publish announcements`
4
4
  # helpmaster: It will publish on all channels the announcements added by using 'add announcement' command.
5
+ # helpmaster: It won't be published if less than 11 messages published on the channel since last time this command was called.
5
6
  # helpmaster: Only works if you are on Master channel and you are a master admin user
6
7
  # helpmaster: The messages stored on a DM won't be published.
7
8
  # helpmaster: This is very convenient to be called from a *Routine* for example every weekday at 09:00.
8
9
  # helpmaster: <https://github.com/MarioRuiz/slack-smart-bot#announcements|more info>
10
+ # helpmaster: command_id: :publish_announcements
9
11
  # helpmaster:
10
12
  def publish_announcements(user)
11
13
  save_stats(__method__)
12
14
  if config.on_master_bot
13
- if config.admins.include?(user.name) #admin user
15
+ if config.masters.include?(user.name) #admin user
14
16
  channels = Dir.entries("#{config.path}/announcements/")
15
17
  channels.select! {|i| i[/\.csv$/]}
16
18
  channels.each do |channel|
17
19
  channel.gsub!('.csv','')
18
- unless channel[0]== 'D'
19
- see_announcements(user, '', channel, true, true)
20
+ unless channel[0]== 'D' or (@announcements_activity_after.key?(channel) and @announcements_activity_after[channel] <= 10)
21
+ see_announcements(user, '', channel, false, true)
22
+ @announcements_activity_after[channel] = 0
20
23
  sleep 0.5 # to avoid reach ratelimit
21
24
  end
22
25
  end
@@ -11,11 +11,12 @@ class SlackSmartBot
11
11
  # helpmaster: _set general message `We will be on *maintenance* at *12:00*`_
12
12
  # helpmaster: _set general message `:information_source: Pay attention: We will be on *maintenance* in *#{((Time.new(2021,6,18,13,30,0)-Time.now)/60).to_i} minutes*`_
13
13
  # helpmaster: <https://github.com/MarioRuiz/slack-smart-bot#bot-management|more info>
14
+ # helpmaster: command_id: :set_general_message
14
15
  # helpmaster:
15
16
  def set_general_message(from, status, message)
16
17
  save_stats(__method__)
17
18
  if config.on_master_bot
18
- if config.admins.include?(from) #admin user
19
+ if config.masters.include?(from) #admin user
19
20
  if status == 'on'
20
21
  config.general_message = message
21
22
  respond "General message has been set."
@@ -14,11 +14,12 @@ class SlackSmartBot
14
14
  # helpmaster: _set maintenance on We are on maintenance. We'll be available again in #{((Time.new(2021,6,18,13,30,0)-Time.now)/60).to_i} minutes_
15
15
  # helpmaster: _turn maintenance on `We are on *maintenance* until *12:00*`_
16
16
  # helpmaster: <https://github.com/MarioRuiz/slack-smart-bot#bot-management|more info>
17
+ # helpmaster: command_id: :set_maintenance
17
18
  # helpmaster:
18
19
  def set_maintenance(from, status, message)
19
20
  save_stats(__method__)
20
21
  if config.on_master_bot
21
- if config.admins.include?(from) #admin user
22
+ if config.masters.include?(from) #admin user
22
23
  if message == ''
23
24
  config.on_maintenance_message = "Sorry I'm on maintenance so I cannot attend your request."
24
25
  else
@@ -9,6 +9,7 @@ class SlackSmartBot
9
9
  # helpmaster: Follow the instructions in case creating cloud bots
10
10
  # helpmaster: In case 'silent' won't display the Bot initialization message on the CHANNEL_NAME
11
11
  # helpmaster: <https://github.com/MarioRuiz/slack-smart-bot#bot-management|more info>
12
+ # helpmaster: command_id: :create_bot
12
13
  # helpmaster:
13
14
  def create_bot(dest, user, type, channel)
14
15
  cloud = type.include?('cloud')
@@ -0,0 +1,41 @@
1
+ class SlackSmartBot
2
+ # helpmaster: ----------------------------------------------
3
+ # helpmaster: `where is smartbot?`
4
+ # helpmaster: `which channels smartbot?`
5
+ # helpmaster: `where is a member smartbot?`
6
+ # helpmaster: It will list all channels where the smartbot is a member.
7
+ # helpmaster: command_id: :where_smartbot
8
+ # helpmaster:
9
+ def where_smartbot(user)
10
+ #todo: add tests
11
+ save_stats(__method__)
12
+ if has_access?(__method__, user)
13
+ channels = get_channels(bot_is_in: true)
14
+ message = []
15
+ extended = @bots_created.values.extended.flatten
16
+ channels.each do |c|
17
+ type = ''
18
+ unless c.id[0] == "D"
19
+ if @bots_created.key?(c.id)
20
+ type = '_`(SmartBot)`_'
21
+ elsif c.id == @master_bot_id
22
+ type = '_`(Master)`_'
23
+ elsif extended.include?(c.name)
24
+ @bots_created.each do |bot,values|
25
+ if values.extended.include?(c.name)
26
+ type += "_`(Extended from ##{values.channel_name})`_ "
27
+ end
28
+ end
29
+ end
30
+ if c.is_private?
31
+ message << "#{c.id}: *##{c.name}* #{type}"
32
+ else
33
+ message << "#{c.id}: *<##{c.id}>* #{type}"
34
+ end
35
+ end
36
+ end
37
+ message.sort!
38
+ respond "*<@#{config.nick_id}> is a member of:*\n\n#{message.join("\n")}"
39
+ end
40
+ end
41
+ end