slack-smart-bot 1.10.0 → 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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