slack-smart-bot 1.8.1 → 1.10.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 (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