slack-smart-bot 1.8.1 → 1.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +127 -21
  3. data/lib/slack/smart-bot/comm/ask.rb +55 -42
  4. data/lib/slack/smart-bot/comm/dont_understand.rb +2 -2
  5. data/lib/slack/smart-bot/comm/event_hello.rb +34 -0
  6. data/lib/slack/smart-bot/comm/get_channel_members.rb +13 -0
  7. data/lib/slack/smart-bot/comm/get_channels.rb +35 -0
  8. data/lib/slack/smart-bot/comm/get_user_info.rb +20 -0
  9. data/lib/slack/smart-bot/comm/get_users.rb +24 -0
  10. data/lib/slack/smart-bot/comm/react.rb +38 -8
  11. data/lib/slack/smart-bot/comm/respond.rb +219 -48
  12. data/lib/slack/smart-bot/comm/respond_direct.rb +2 -3
  13. data/lib/slack/smart-bot/comm/respond_thread.rb +5 -0
  14. data/lib/slack/smart-bot/comm/send_file.rb +38 -34
  15. data/lib/slack/smart-bot/comm/send_msg_channel.rb +27 -22
  16. data/lib/slack/smart-bot/comm/send_msg_user.rb +58 -33
  17. data/lib/slack/smart-bot/comm/unreact.rb +24 -7
  18. data/lib/slack/smart-bot/comm.rb +7 -1
  19. data/lib/slack/smart-bot/commands/general/add_announcement.rb +32 -0
  20. data/lib/slack/smart-bot/commands/general/bot_help.rb +68 -28
  21. data/lib/slack/smart-bot/commands/general/bot_stats.rb +314 -0
  22. data/lib/slack/smart-bot/commands/general/bot_status.rb +3 -5
  23. data/lib/slack/smart-bot/commands/general/bye_bot.rb +0 -7
  24. data/lib/slack/smart-bot/commands/general/delete_announcement.rb +34 -0
  25. data/lib/slack/smart-bot/commands/general/delete_share.rb +34 -0
  26. data/lib/slack/smart-bot/commands/general/hi_bot.rb +16 -11
  27. data/lib/slack/smart-bot/commands/general/leaderboard.rb +200 -0
  28. data/lib/slack/smart-bot/commands/general/see_announcements.rb +113 -0
  29. data/lib/slack/smart-bot/commands/general/see_favorite_commands.rb +54 -0
  30. data/lib/slack/smart-bot/commands/general/see_shares.rb +41 -0
  31. data/lib/slack/smart-bot/commands/general/see_statuses.rb +78 -0
  32. data/lib/slack/smart-bot/commands/general/share_messages.rb +58 -0
  33. data/lib/slack/smart-bot/commands/general/stop_using_rules.rb +11 -6
  34. data/lib/slack/smart-bot/commands/general/suggest_command.rb +30 -0
  35. data/lib/slack/smart-bot/commands/general/use_rules.rb +13 -16
  36. data/lib/slack/smart-bot/commands/general/whats_new.rb +19 -0
  37. data/lib/slack/smart-bot/commands/general_bot_commands.rb +243 -0
  38. data/lib/slack/smart-bot/commands/on_bot/add_shortcut.rb +67 -38
  39. data/lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb +49 -14
  40. data/lib/slack/smart-bot/commands/on_bot/admin/extend_rules.rb +5 -7
  41. data/lib/slack/smart-bot/commands/on_bot/admin/pause_bot.rb +4 -1
  42. data/lib/slack/smart-bot/commands/on_bot/admin/pause_routine.rb +1 -0
  43. data/lib/slack/smart-bot/commands/on_bot/admin/remove_routine.rb +2 -3
  44. data/lib/slack/smart-bot/commands/on_bot/admin/run_routine.rb +6 -1
  45. data/lib/slack/smart-bot/commands/on_bot/admin/see_result_routine.rb +32 -0
  46. data/lib/slack/smart-bot/commands/on_bot/admin/see_routines.rb +12 -4
  47. data/lib/slack/smart-bot/commands/on_bot/admin/start_bot.rb +4 -1
  48. data/lib/slack/smart-bot/commands/on_bot/admin/start_routine.rb +1 -0
  49. data/lib/slack/smart-bot/commands/on_bot/admin/stop_using_rules_on.rb +2 -0
  50. data/lib/slack/smart-bot/commands/on_bot/admin_master/react_to.rb +32 -0
  51. data/lib/slack/smart-bot/commands/on_bot/admin_master/send_message.rb +24 -0
  52. data/lib/slack/smart-bot/commands/on_bot/delete_repl.rb +3 -5
  53. data/lib/slack/smart-bot/commands/on_bot/delete_shortcut.rb +54 -25
  54. data/lib/slack/smart-bot/commands/on_bot/get_repl.rb +7 -9
  55. data/lib/slack/smart-bot/commands/on_bot/repl.rb +55 -25
  56. data/lib/slack/smart-bot/commands/on_bot/ruby_code.rb +36 -13
  57. data/lib/slack/smart-bot/commands/on_bot/run_repl.rb +5 -7
  58. data/lib/slack/smart-bot/commands/on_bot/see_repls.rb +4 -6
  59. data/lib/slack/smart-bot/commands/on_bot/see_shortcuts.rb +29 -13
  60. data/lib/slack/smart-bot/commands/on_extended/bot_rules.rb +55 -9
  61. data/lib/slack/smart-bot/commands/on_master/admin/kill_bot_on_channel.rb +4 -1
  62. data/lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb +5 -3
  63. data/lib/slack/smart-bot/commands/on_master/admin_master/notify_message.rb +2 -1
  64. data/lib/slack/smart-bot/commands/on_master/admin_master/publish_announcements.rb +32 -0
  65. data/lib/slack/smart-bot/commands/on_master/admin_master/set_general_message.rb +38 -0
  66. data/lib/slack/smart-bot/commands/on_master/admin_master/set_maintenance.rb +49 -0
  67. data/lib/slack/smart-bot/commands/on_master/create_bot.rb +30 -21
  68. data/lib/slack/smart-bot/commands.rb +19 -1
  69. data/lib/slack/smart-bot/listen.rb +7 -8
  70. data/lib/slack/smart-bot/process.rb +373 -192
  71. data/lib/slack/smart-bot/process_first.rb +202 -104
  72. data/lib/slack/smart-bot/treat_message.rb +325 -186
  73. data/lib/slack/smart-bot/utils/answer.rb +18 -0
  74. data/lib/slack/smart-bot/utils/answer_delete.rb +15 -0
  75. data/lib/slack/smart-bot/utils/build_help.rb +57 -5
  76. data/lib/slack/smart-bot/utils/create_routine_thread.rb +83 -30
  77. data/lib/slack/smart-bot/utils/get_bots_created.rb +4 -1
  78. data/lib/slack/smart-bot/utils/get_channels_name_and_id.rb +1 -7
  79. data/lib/slack/smart-bot/utils/get_help.rb +87 -35
  80. data/lib/slack/smart-bot/utils/get_shares.rb +12 -0
  81. data/lib/slack/smart-bot/utils/has_access.rb +12 -0
  82. data/lib/slack/smart-bot/utils/save_stats.rb +23 -8
  83. data/lib/slack/smart-bot/utils/save_status.rb +52 -0
  84. data/lib/slack/smart-bot/utils/update_shortcuts_file.rb +6 -0
  85. data/lib/slack/smart-bot/utils.rb +5 -0
  86. data/lib/slack-smart-bot.rb +88 -47
  87. data/lib/slack-smart-bot_general_commands.rb +46 -0
  88. data/lib/slack-smart-bot_general_rules.rb +5 -2
  89. data/lib/slack-smart-bot_rules.rb +49 -23
  90. data/whats_new.txt +36 -0
  91. metadata +44 -13
  92. data/lib/slack/smart-bot/commands/on_bot/admin_master/bot_stats.rb +0 -195
@@ -7,6 +7,7 @@ class SlackSmartBot
7
7
  # helpadmin: NAME: one word to identify the routine
8
8
  # helpadmin: Examples:
9
9
  # helpadmin: _start routine example_
10
+ # helpadmin: <https://github.com/MarioRuiz/slack-smart-bot#routines|more info>
10
11
  # helpadmin:
11
12
 
12
13
  def start_routine(dest, from, name)
@@ -3,6 +3,7 @@ class SlackSmartBot
3
3
  # helpadmin: ----------------------------------------------
4
4
  # helpadmin: `stop using rules on CHANNEL_NAME`
5
5
  # helpadmin: it will stop using the extended rules on the specified channel.
6
+ # helpadmin: <https://github.com/MarioRuiz/slack-smart-bot#extending-rules-to-other-channels|more info>
6
7
  # helpadmin:
7
8
 
8
9
  def stop_using_rules_on(dest, user, from, channel, typem)
@@ -12,6 +13,7 @@ class SlackSmartBot
12
13
  respond "Only admins can extend or stop using the rules. Admins on this channel: #{config.admins}", dest
13
14
  else
14
15
  get_bots_created()
16
+ channel = @channels_name[channel] if @channels_name.key?(channel)
15
17
  if @bots_created[@channel_id][:extended].include?(channel)
16
18
  @bots_created[@channel_id][:extended].delete(channel)
17
19
  update_bots_file()
@@ -0,0 +1,32 @@
1
+ class SlackSmartBot
2
+
3
+ # helpadmin: ----------------------------------------------
4
+ # helpadmin: `react to #CHANNEL_NAME THREAD_ID EMOJIS`
5
+ # helpadmin: It will send the specified reactions as SmartBot
6
+ # helpadmin: You can use this command only if you are a Master admin user and if you are in a private conversation with the bot
7
+ # helpadmin: Examples:
8
+ # helpadmin: _react to #sales 1622550707.012100 :thumbsup:_
9
+ # helpadmin: _react to #sales p1622550707012100 :thumbsup:_
10
+ # helpadmin: _react to #sales p1622550707012100 :thumbsup: :heavy_check_mark: :bathtub:_
11
+ # helpadmin:
12
+ def react_to(dest, from, typem, to, thread_ts, emojis)
13
+ save_stats(__method__)
14
+ if config.masters.include?(from) and typem==:on_dm #master admin user
15
+ succs = []
16
+ emojis.split(' ').each do |emoji|
17
+ succs << (react emoji, thread_ts, to)
18
+ end
19
+ succs.uniq!
20
+ if succs.size == 1 and succs[0] == true
21
+ react :heavy_check_mark
22
+ elsif succs.size == 2
23
+ react :exclamation
24
+ else
25
+ react :x
26
+ end
27
+ else
28
+ respond "Only master admin users on a `pr`ivate conversation with the SmartBot can send reactions as SmartBot.", dest
29
+ end
30
+ end
31
+ end
32
+
@@ -0,0 +1,24 @@
1
+ class SlackSmartBot
2
+
3
+ # helpadmin: ----------------------------------------------
4
+ # helpadmin: `send message to @USER_NAME : MESSAGE`
5
+ # helpadmin: `send message to #CHANNEL_NAME : MESSAGE`
6
+ # helpadmin: `send message to #CHANNEL_NAME THREAD_ID : MESSAGE`
7
+ # helpadmin: It will send the specified message as SmartBot
8
+ # helpadmin: You can use this command only if you are a Master admin user and if you are in a private conversation with the bot
9
+ # helpadmin:
10
+ def send_message(dest, from, typem, to, thread_ts, message)
11
+ save_stats(__method__)
12
+ if config.masters.include?(from) and typem==:on_dm #master admin user
13
+ succ = (respond message, to, thread_ts: thread_ts, web_client: true)
14
+ if succ
15
+ react :heavy_check_mark
16
+ else
17
+ react :x
18
+ end
19
+ else
20
+ respond "Only master admin users on a private conversation with the SmartBot can send messages as SmartBot.", dest
21
+ end
22
+ end
23
+ end
24
+
@@ -3,23 +3,21 @@ class SlackSmartBot
3
3
  # help: `delete repl SESSION_NAME`
4
4
  # help: `delete irb SESSION_NAME`
5
5
  # help: `remove repl SESSION_NAME`
6
- # help:
7
6
  # help: Will delete the specified REPL
8
7
  # help: Only the creator of the REPL or an admin can delete REPLs
8
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#repl|more info>
9
9
  # help:
10
10
  def delete_repl(dest, user, session_name)
11
11
  #todo: add tests
12
12
  save_stats(__method__)
13
- if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and
14
- (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
15
- respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
16
- else
13
+ if has_access?(__method__, user)
17
14
  if @repls.key?(session_name)
18
15
  Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
19
16
  if config.admins.include?(user.name) or @repls[session_name].creator_name == user.name
20
17
  @repls.delete(session_name)
21
18
  update_repls()
22
19
  File.rename("#{config.path}/repl/#{@channel_id}/#{session_name}.input", "#{config.path}/repl/#{@channel_id}/#{session_name}_#{Time.now.strftime("%Y%m%d%H%M%S%N")}.deleted")
20
+ File.delete("#{config.path}/repl/#{@channel_id}/#{session_name}.output") if File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.output")
23
21
  File.delete("#{config.path}/repl/#{@channel_id}/#{session_name}.run") if File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.run")
24
22
  respond "REPL #{session_name} deleted"
25
23
  else
@@ -2,45 +2,74 @@ class SlackSmartBot
2
2
  # help: ----------------------------------------------
3
3
  # help: `delete shortcut NAME`
4
4
  # help: `delete sc NAME`
5
+ # help: `delete global sc NAME`
5
6
  # help: It will delete the shortcut with the supplied name
7
+ # help: 'global' or 'generic' can only be used on Master channel.
8
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#shortcuts|more info>
6
9
  # help:
7
10
 
8
- def delete_shortcut(dest, user, shortcut, typem, command)
11
+ def delete_shortcut(dest, user, shortcut, typem, command, global)
9
12
  save_stats(__method__)
10
13
  unless typem == :on_extended
11
14
  from = user.name
12
- if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and
13
- (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
14
- respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
15
- else
15
+ if has_access?(__method__, user)
16
16
  deleted = false
17
17
 
18
- if !config.admins.include?(from) and @shortcuts[:all].include?(shortcut) and !@shortcuts[from].include?(shortcut)
19
- respond "Only the creator of the shortcut or an admin user can delete it", dest
20
- elsif (@shortcuts.keys.include?(from) and @shortcuts[from].keys.include?(shortcut)) or
21
- (config.admins.include?(from) and @shortcuts[:all].include?(shortcut))
22
- #are you sure? to avoid deleting by mistake
23
- unless @questions.keys.include?(from)
24
- ask("are you sure you want to delete it?", command, from, dest)
18
+ if global
19
+ if !config.on_master_bot or typem != :on_master
20
+ respond "It is only possible to delete global shortcuts from Master channel"
25
21
  else
26
- case @questions[from]
27
- when /^(yes|yep)/i
28
- @questions.delete(from)
29
- respond "shortcut deleted!", dest
30
- respond("#{shortcut}: #{@shortcuts[from][shortcut]}", dest) if @shortcuts.key?(from) and @shortcuts[from].key?(shortcut)
31
- respond("#{shortcut}: #{@shortcuts[:all][shortcut]}", dest) if @shortcuts.key?(:all) and @shortcuts[:all].key?(shortcut)
32
- @shortcuts[from].delete(shortcut) if @shortcuts.key?(from) and @shortcuts[from].key?(shortcut)
33
- @shortcuts[:all].delete(shortcut) if @shortcuts.key?(:all) and @shortcuts[:all].key?(shortcut)
22
+ if !config.admins.include?(from) and @shortcuts_global[:all].include?(shortcut) and
23
+ (!@shortcuts_global.key?(from) or !@shortcuts_global[from].include?(shortcut))
24
+ respond "Only the creator of the shortcut or an admin user can delete it"
25
+ elsif (@shortcuts_global.key?(from) and @shortcuts_global[from].keys.include?(shortcut)) or
26
+ (config.admins.include?(from) and @shortcuts_global[:all].include?(shortcut))
27
+
28
+ respond "global shortcut deleted!", dest
29
+ if @shortcuts_global.key?(from) and @shortcuts_global[from].key?(shortcut)
30
+ respond("#{shortcut}: #{@shortcuts_global[from][shortcut]}", dest)
31
+ elsif @shortcuts_global.key?(:all) and @shortcuts_global[:all].key?(shortcut)
32
+ respond("#{shortcut}: #{@shortcuts_global[:all][shortcut]}", dest)
33
+ end
34
+ @shortcuts_global[from].delete(shortcut) if @shortcuts_global.key?(from) and @shortcuts_global[from].key?(shortcut)
35
+ @shortcuts_global[:all].delete(shortcut) if @shortcuts_global.key?(:all) and @shortcuts_global[:all].key?(shortcut)
34
36
  update_shortcuts_file()
35
- when /^no/i
36
- @questions.delete(from)
37
- respond "ok, I won't delete it", dest
38
37
  else
39
- ask("I don't understand, are you sure you want to delete it? (yes or no)", command, from, dest)
38
+ respond 'shortcut not found'
40
39
  end
41
40
  end
42
41
  else
43
- respond "shortcut not found", dest
42
+ if !config.admins.include?(from) and @shortcuts[:all].include?(shortcut) and
43
+ (!@shortcuts.key?(from) or !@shortcuts[from].include?(shortcut))
44
+ respond "Only the creator of the shortcut or an admin user can delete it", dest
45
+ elsif (@shortcuts.keys.include?(from) and @shortcuts[from].keys.include?(shortcut)) or
46
+ (config.admins.include?(from) and @shortcuts[:all].include?(shortcut))
47
+ #are you sure? to avoid deleting by mistake
48
+ if answer.empty?
49
+ ask("are you sure you want to delete it?", command, from, dest)
50
+ else
51
+ case answer
52
+ when /^(yes|yep)/i
53
+ answer_delete(from)
54
+ respond "shortcut deleted!", dest
55
+ if @shortcuts.key?(from) and @shortcuts[from].key?(shortcut)
56
+ respond("#{shortcut}: #{@shortcuts[from][shortcut]}", dest)
57
+ elsif @shortcuts.key?(:all) and @shortcuts[:all].key?(shortcut)
58
+ respond("#{shortcut}: #{@shortcuts[:all][shortcut]}", dest)
59
+ end
60
+ @shortcuts[from].delete(shortcut) if @shortcuts.key?(from) and @shortcuts[from].key?(shortcut)
61
+ @shortcuts[:all].delete(shortcut) if @shortcuts.key?(:all) and @shortcuts[:all].key?(shortcut)
62
+ update_shortcuts_file()
63
+ when /^no/i
64
+ answer_delete(from)
65
+ respond "ok, I won't delete it", dest
66
+ else
67
+ ask("I don't understand, are you sure you want to delete it? (yes or no)", command, from, dest)
68
+ end
69
+ end
70
+ else
71
+ respond "shortcut not found", dest
72
+ end
44
73
  end
45
74
  end
46
75
  end
@@ -3,35 +3,33 @@ class SlackSmartBot
3
3
  # help: `get repl SESSION_NAME`
4
4
  # help: `get irb SESSION_NAME`
5
5
  # help: `get live SESSION_NAME`
6
- # help:
7
6
  # help: Will get the Ruby commands sent on that SESSION_NAME.
7
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#repl|more info>
8
8
  # help:
9
9
  def get_repl(dest, user, session_name)
10
10
  #todo: add tests
11
11
  save_stats(__method__)
12
- if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and
13
- (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
14
- respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
15
- else
12
+ if has_access?(__method__, user)
16
13
  Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
17
14
  Dir.mkdir("#{config.path}/repl/#{@channel_id}") unless Dir.exist?("#{config.path}/repl/#{@channel_id}")
18
15
  if File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.run")
19
- if @repls.key?(session_name) and @repls[session_name][:type] == :private and
16
+ if @repls.key?(session_name) and (@repls[session_name][:type] == :private or @repls[session_name][:type] == :private_clean) and
20
17
  @repls[session_name][:creator_name]!=user.name and
21
18
  !config.admins.include?(user.name)
22
19
  respond "The REPL with session name: #{session_name} is private", dest
23
20
  else
21
+ content = "require 'nice_http'\n"
24
22
  if @repls.key?(session_name)
25
23
  @repls[session_name][:accessed] = Time.now.to_s
26
24
  @repls[session_name][:gets] += 1
27
25
  update_repls()
28
26
  end
29
-
30
- content = "require 'nice_http'\n"
31
- if File.exist?("#{project_folder}/.smart-bot-repl")
27
+ if !@repls.key?(session_name) or
28
+ (File.exist?("#{project_folder}/.smart-bot-repl") and @repls[session_name][:type] != :private_clean and @repls[session_name][:type] != :public_clean)
32
29
  content += File.read("#{project_folder}/.smart-bot-repl")
33
30
  content += "\n"
34
31
  end
32
+
35
33
  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
36
34
  File.write("#{config.path}/repl/#{@channel_id}/#{session_name}.rb", content, mode: "w+")
37
35
  send_file(dest, "REPL #{session_name} on #{config.channel}", "#{config.path}/repl/#{@channel_id}/#{session_name}.rb", " REPL #{session_name} on #{config.channel}", 'text/plain', "ruby")
@@ -5,20 +5,21 @@ class SlackSmartBot
5
5
  # help: `irb`
6
6
  # help: `repl SESSION_NAME`
7
7
  # help: `private repl SESSION_NAME`
8
+ # help: `clean repl SESSION_NAME`
8
9
  # help: `repl ENV_VAR=VALUE`
9
10
  # help: `repl SESSION_NAME ENV_VAR=VALUE ENV_VAR='VALUE'`
10
11
  # help: `repl SESSION_NAME: "DESCRIPTION"`
11
12
  # help: `repl SESSION_NAME: "DESCRIPTION" ENV_VAR=VALUE ENV_VAR='VALUE'`
12
- # help:
13
13
  # help: Will run all we write as a ruby command and will keep the session values.
14
14
  # help: SESSION_NAME only admits from a to Z, numbers, - and _
15
15
  # help: If no SESSION_NAME supplied it will be treated as a temporary REPL
16
16
  # help: If 'private' specified the repl will be accessible only by you and it will be displayed only to you when `see repls`
17
+ # help: If 'clean' specified the repl won't pre execute the code written on the .smart-bot-repl file
17
18
  # help: To avoid a message to be treated, start the message with '-'.
18
19
  # help: Send _quit_, _bye_ or _exit_ to finish the session.
19
20
  # help: Send puts, print, p or pp if you want to print out something when using `run repl` later.
20
21
  # help: After 30 minutes of no communication with the Smart Bot the session will be dismissed.
21
- # 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.
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.
22
23
  # help: By default it will be automatically loaded the gems: string_pattern, nice_hash and nice_http
23
24
  # help: To pre-execute some ruby when starting the session add the code to .smart-bot-repl file on the project root folder defined on project_folder
24
25
  # help: If you want to see the methods of a class or module you created use _ls TheModuleOrClass_
@@ -28,14 +29,12 @@ class SlackSmartBot
28
29
  # help: _repl CreateCustomer: "It creates a random customer for testing" LOCATION=spain HOST='https://10.30.40.50:8887'_
29
30
  # help: _repl delete_logs_
30
31
  # help: _private repl random-ssn_
32
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#repl|more info>
31
33
  # help:
32
34
  def repl(dest, user, session_name, env_vars, rules_file, command, description, type)
33
35
  #todo: add more tests
34
36
  from = user.name
35
- if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and
36
- (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
37
- respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
38
- else
37
+ if has_access?(__method__, user)
39
38
  if !@repl_sessions.key?(from)
40
39
  save_stats(__method__)
41
40
  Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
@@ -78,7 +77,13 @@ class SlackSmartBot
78
77
  }
79
78
  update_repls()
80
79
  end
81
-
80
+ react :running
81
+ if Thread.current[:ts].to_s == ''
82
+ @ts_react = Thread.current[:thread_ts]
83
+ else
84
+ @ts_react = Thread.current[:ts]
85
+ end
86
+
82
87
  message = "Session name: *#{session_name}*
83
88
  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`.
84
89
  I will respond with the result so it is not necessary you send `print`, `puts`, `p` or `pp` unless you want it as the output when calling `run repl`.
@@ -96,6 +101,19 @@ class SlackSmartBot
96
101
  File.write("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.output", "", mode: "a+")
97
102
  File.write("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.run", "", mode: "a+")
98
103
 
104
+ if type != :private_clean and type != :public_clean
105
+ pre_execute = '
106
+ if File.exist?(\"./.smart-bot-repl\")
107
+ begin
108
+ eval(File.read(\"./.smart-bot-repl\"), bindme' + serialt + ')
109
+ rescue Exception => resp_repl
110
+ end
111
+ end
112
+ '
113
+ else
114
+ pre_execute = ''
115
+ end
116
+
99
117
  process_to_run = '
100
118
  ruby -e "' + env_vars.join("\n") + '
101
119
  require \"amazing_print\"
@@ -105,17 +123,12 @@ class SlackSmartBot
105
123
  (obj.methods - Object.methods)
106
124
  end
107
125
 
108
- file_input_repl = File.open(\"' + Dir.pwd + '/repl/' + @channel_id + '/' + session_name + '.input\", \"r\")
109
- if File.exist?(\"./.smart-bot-repl\")
110
- begin
111
- eval(File.read(\"./.smart-bot-repl\"), bindme' + serialt + ')
112
- rescue Exception => resp_repl
113
- end
114
- end
126
+ file_input_repl = File.open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.input\", \"r\")
127
+ ' + pre_execute + '
115
128
  while true do
116
129
  sleep 0.2
117
130
  code_to_run_repl = file_input_repl.read
118
- if code_to_run_repl.to_s!=''
131
+ if code_to_run_repl.to_s!=\"\"
119
132
  add_to_run_repl = true
120
133
  if code_to_run_repl.to_s.match?(/^quit$/i) or
121
134
  code_to_run_repl.to_s.match?(/^exit$/i) or
@@ -132,18 +145,18 @@ class SlackSmartBot
132
145
  rescue Exception => resp_repl
133
146
  error = true
134
147
  end
135
- if resp_repl.to_s != \"\"
148
+ unless error
136
149
  if code_to_run_repl.match?(/^\s*p\s+/i)
137
- open(\"' + Dir.pwd + '/repl/' + @channel_id + '/' + session_name + '.output\", \"a+\") {|f|
150
+ open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.output\", \"a+\") {|f|
138
151
  f.puts \"\`\`\`\n#{resp_repl.inspect}\n\`\`\`\"
139
152
  }
140
153
  else
141
- open(\"' + Dir.pwd + '/repl/' + @channel_id + '/' + session_name + '.output\", \"a+\") {|f|
154
+ open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.output\", \"a+\") {|f|
142
155
  f.puts \"\`\`\`\n#{resp_repl.ai}\n\`\`\`\"
143
156
  }
144
157
  end
145
- unless error or !add_to_run_repl
146
- open(\"' + Dir.pwd + '/repl/' + @channel_id + '/' + session_name + '.run\", \"a+\") {|f|
158
+ unless !add_to_run_repl
159
+ open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.run\", \"a+\") {|f|
147
160
  f.puts code_to_run_repl
148
161
  }
149
162
  end
@@ -152,7 +165,6 @@ class SlackSmartBot
152
165
  end
153
166
  end"
154
167
  '
155
-
156
168
  unless rules_file.empty? # to get the project_folder
157
169
  begin
158
170
  eval(File.new(config.path+rules_file).read) if File.exist?(config.path+rules_file)
@@ -160,12 +172,12 @@ class SlackSmartBot
160
172
  end
161
173
  started = Time.now
162
174
  process_to_run = ("cd #{project_folder} &&" + process_to_run) if defined?(project_folder)
163
-
175
+
164
176
  stdin, stdout, stderr, wait_thr = Open3.popen3(process_to_run)
165
177
  timeout = 30 * 60 # 30 minutes
166
178
 
167
179
  file_output_repl = File.open("#{config.path}/repl/#{@channel_id}/#{session_name}.output", "r")
168
-
180
+ @repl_sessions[from][:pid] = wait_thr.pid
169
181
  while (wait_thr.status == 'run' or wait_thr.status == 'sleep') and @repl_sessions.key?(from)
170
182
  begin
171
183
  if (Time.now-@repl_sessions[from][:finished]) > timeout
@@ -173,6 +185,14 @@ class SlackSmartBot
173
185
  f.puts 'quit'
174
186
  }
175
187
  respond "REPL session finished: #{@repl_sessions[from][:name]}", dest
188
+ unreact :running, @ts_react
189
+ pids = `pgrep -P #{@repl_sessions[from][:pid]}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
190
+ pids.each do |pid|
191
+ begin
192
+ Process.kill("KILL", pid)
193
+ rescue
194
+ end
195
+ end
176
196
  @repl_sessions.delete(from)
177
197
  break
178
198
  end
@@ -199,10 +219,12 @@ class SlackSmartBot
199
219
  code.gsub!("\\r", "\r")
200
220
  # Disabled for the moment since it is deleting lines with '}'
201
221
  #code.gsub!(/^\W*$/, "") #to remove special chars from slack when copy/pasting.
202
- if code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File") or
222
+ if code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File.") or
203
223
  code.include?("`") or code.include?("exec") or code.include?("spawn") or code.include?("IO.") or
204
224
  code.match?(/open3/i) or code.match?(/bundle/i) or code.match?(/gemfile/i) or code.include?("%x") or
205
- code.include?("ENV") or code.match?(/=\s*IO/)
225
+ code.include?("ENV") or code.match?(/=\s*IO/) or code.include?("Dir.") or
226
+ 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/)
206
228
  respond "Sorry I cannot run this due security reasons", dest
207
229
  else
208
230
  @repl_sessions[from][:input]<<code
@@ -212,6 +234,14 @@ class SlackSmartBot
212
234
  f.puts code
213
235
  }
214
236
  respond "REPL session finished: #{@repl_sessions[from][:name]}", dest
237
+ unreact :running, @ts_react
238
+ pids = `pgrep -P #{@repl_sessions[from][:pid]}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
239
+ pids.each do |pid|
240
+ begin
241
+ Process.kill("KILL", pid)
242
+ rescue
243
+ end
244
+ end
215
245
  @repl_sessions.delete(from)
216
246
  when /^\s*-/i
217
247
  #ommit
@@ -5,47 +5,70 @@ class SlackSmartBot
5
5
  # help: runs the code supplied and returns the output. Also you can send a Ruby file instead. Examples:
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
+ # help: <https://github.com/MarioRuiz/slack-smart-bot#running-ruby-code-on-a-conversation|more info>
8
9
  # help:
9
10
 
10
11
  def ruby_code(dest, user, code, rules_file)
11
12
  save_stats(__method__)
12
- if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and
13
- (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
14
- respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
15
- else
16
- unless code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File") or
13
+ if has_access?(__method__, user)
14
+ unless code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File.") or
17
15
  code.include?("`") or code.include?("exec") or code.include?("spawn") or code.include?("IO.") or
18
16
  code.match?(/open3/i) or code.match?(/bundle/i) or code.match?(/gemfile/i) or code.include?("%x") or
19
- code.include?("ENV") or code.match?(/=\s*IO/)
17
+ code.include?("ENV") or code.match?(/=\s*IO/) or code.include?("Dir.") or code.match?(/=\s*IO/) or
18
+ code.match?(/=\s*File/) or code.match?(/=\s*Dir/) or code.match?(/<\s*File/) or code.match?(/<\s*Dir/) or
19
+ code.match?(/\w+:\s*File/) or code.match?(/\w+:\s*Dir/)
20
+ react :running
20
21
  unless rules_file.empty?
21
22
  begin
22
23
  eval(File.new(config.path+rules_file).read) if File.exist?(config.path+rules_file)
23
24
  end
24
25
  end
25
26
 
26
- respond "Running", dest if code.size > 100
27
+ respond "Running", dest if code.size > 200
27
28
 
28
29
  begin
29
30
  code.gsub!(/^\W*$/, "") #to remove special chars from slack when copy/pasting
31
+ code.gsub!('$','\$') #to take $ as literal, fex: puts '$lolo' => puts '\$lolo'
30
32
  ruby = "ruby -e \"#{code.gsub('"', '\"')}\""
31
33
  if defined?(project_folder) and project_folder.to_s != "" and Dir.exist?(project_folder)
32
34
  ruby = ("cd #{project_folder} &&" + ruby)
33
35
  else
34
36
  def project_folder() "" end
35
37
  end
36
- stdout, stderr, status = Open3.capture3(ruby)
37
- if stderr == ""
38
- if stdout == ""
39
- respond "Nothing returned. Remember you need to use p or puts to print", dest
38
+
39
+ stdin, stdout, stderr, wait_thr = Open3.popen3(ruby)
40
+ timeout = timeoutt = 20
41
+ procstart = Time.now
42
+ while (wait_thr.status == 'run' or wait_thr.status == 'sleep') and timeout > 0
43
+ timeout -= 0.1
44
+ sleep 0.1
45
+ end
46
+ if timeout > 0
47
+ stdout = stdout.read
48
+ stderr = stderr.read
49
+ if stderr == ""
50
+ if stdout == ""
51
+ respond "Nothing returned. Remember you need to use p or puts to print", dest
52
+ else
53
+ respond stdout, dest
54
+ end
40
55
  else
41
- respond stdout, dest
56
+ respond "#{stderr}\n#{stdout}", dest
42
57
  end
43
58
  else
44
- respond "#{stderr}\n#{stdout}", dest
59
+ respond "The process didn't finish in #{timeoutt} secs so it was aborted. Timeout!"
60
+ pids = `pgrep -P #{wait_thr.pid}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
61
+ pids.each do |pid|
62
+ begin
63
+ Process.kill("KILL", pid)
64
+ rescue
65
+ end
66
+ end
45
67
  end
46
68
  rescue Exception => exc
47
69
  respond exc, dest
48
70
  end
71
+ unreact :running
49
72
  else
50
73
  respond "Sorry I cannot run this due security reasons", dest
51
74
  end