slack-smart-bot 1.15.0 → 1.15.25

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -1
  3. data/img/chat_gpt_attach_image.png +0 -0
  4. data/lib/slack/smart-bot/ai/open_ai/models.rb +19 -10
  5. data/lib/slack/smart-bot/ai/open_ai/send_gpt_chat.rb +14 -10
  6. data/lib/slack/smart-bot/comm/dont_understand.rb +23 -6
  7. data/lib/slack/smart-bot/comm/get_user_info.rb +9 -10
  8. data/lib/slack/smart-bot/comm/respond.rb +56 -28
  9. data/lib/slack/smart-bot/comm/send_file.rb +17 -6
  10. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat.rb +885 -129
  11. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_add_collaborator.rb +3 -3
  12. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_copy_session.rb +132 -15
  13. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_delete_session.rb +1 -1
  14. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_get_prompts.rb +50 -12
  15. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_list_sessions.rb +99 -34
  16. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_share_session.rb +12 -2
  17. data/lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_use_model.rb +36 -25
  18. data/lib/slack/smart-bot/commands/general/bot_help.rb +29 -24
  19. data/lib/slack/smart-bot/commands/general/poster.rb +0 -1
  20. data/lib/slack/smart-bot/commands/general/see_announcements.rb +1 -1
  21. data/lib/slack/smart-bot/commands/general/summarize.rb +22 -8
  22. data/lib/slack/smart-bot/commands/general_bot_commands.rb +156 -55
  23. data/lib/slack/smart-bot/commands/on_bot/general/bot_stats.rb +13 -11
  24. data/lib/slack/smart-bot/commands/on_extended/bot_rules.rb +21 -17
  25. data/lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb +2 -2
  26. data/lib/slack/smart-bot/commands.rb +19 -19
  27. data/lib/slack/smart-bot/process_first.rb +38 -35
  28. data/lib/slack/smart-bot/treat_message.rb +57 -56
  29. data/lib/slack/smart-bot/utils/download_http_content.rb +91 -0
  30. data/lib/slack/smart-bot/utils/get_authorizations.rb +41 -0
  31. data/lib/slack/smart-bot/utils/get_keywords.rb +33 -0
  32. data/lib/slack/smart-bot/utils/get_openai_sessions.rb +46 -6
  33. data/lib/slack/smart-bot/utils/get_teams.rb +9 -1
  34. data/lib/slack/smart-bot/utils/save_stats.rb +13 -5
  35. data/lib/slack/smart-bot/utils/transform_to_slack_markdown.rb +36 -0
  36. data/lib/slack/smart-bot/utils/update_openai_sessions.rb +9 -4
  37. data/lib/slack/smart-bot/utils.rb +48 -44
  38. data/lib/slack-smart-bot.rb +10 -10
  39. data/whats_new.txt +27 -1
  40. metadata +49 -8
@@ -1,11 +1,11 @@
1
1
  class SlackSmartBot
2
- def bot_rules(dest, help_command, typem, rules_file, user, send_to_file: false)
3
- save_stats(__method__)
2
+ def bot_rules(dest, help_command, typem, rules_file, user, send_to_file: false, savestats: true, return_output: false)
3
+ save_stats(__method__) if savestats
4
4
  if has_access?(__method__, user)
5
5
  if typem == :on_extended or typem == :on_call #for the other cases above.
6
6
  output = []
7
- if help_command.to_s != ''
8
- help_command = '' if help_command.to_s.match?(/^\s*expanded\s*$/i) or help_command.to_s.match?(/^\s*extended\s*$/i)
7
+ if help_command.to_s != ""
8
+ help_command = "" if help_command.to_s.match?(/^\s*expanded\s*$/i) or help_command.to_s.match?(/^\s*extended\s*$/i)
9
9
  expanded = true
10
10
  else
11
11
  expanded = false
@@ -25,7 +25,7 @@ class SlackSmartBot
25
25
  commands << h
26
26
  elsif !h.match?(/\A\s*\*/) and !h.match?(/\A\s*=+/) #to avoid general messages for bot help *General rules...*
27
27
  all_found = true
28
- help_command.to_s.split(' ') do |hc|
28
+ help_command.to_s.split(" ") do |hc|
29
29
  unless hc.match?(/^\s*\z/)
30
30
  if !h.match?(/#{hc}/i)
31
31
  all_found = false
@@ -35,9 +35,9 @@ class SlackSmartBot
35
35
  commands_search << h if all_found
36
36
  end
37
37
  end
38
- if commands.size < 10 and help_command.to_s!='' and commands_search.size > 0
38
+ if commands.size < 10 and help_command.to_s != "" and commands_search.size > 0
39
39
  commands_search.shuffle!
40
- (10-commands.size).times do |n|
40
+ (10 - commands.size).times do |n|
41
41
  unless commands_search[n].nil?
42
42
  output << commands_search[n]
43
43
  help_found = true
@@ -47,7 +47,6 @@ class SlackSmartBot
47
47
  unless help_found
48
48
  output << "*#{config.channel}*: I didn't find any command with `#{help_command}`"
49
49
  end
50
-
51
50
  else
52
51
  message = "-\n\n\n===================================\n*Rules from channel #{config.channel}*\n"
53
52
  if typem == :on_extended
@@ -59,7 +58,7 @@ class SlackSmartBot
59
58
 
60
59
  unless rules_file.empty?
61
60
  begin
62
- eval(File.new(config.path+rules_file).read) if File.exist?(config.path+rules_file)
61
+ eval(File.new(config.path + rules_file).read) if File.exist?(config.path + rules_file)
63
62
  end
64
63
  end
65
64
  if defined?(git_project) and git_project.to_s != "" and help_command.to_s == ""
@@ -73,28 +72,33 @@ class SlackSmartBot
73
72
  message_not_expanded += "Also to get specific *expanded* help for a specific command or rule call *`bot rules COMMAND`*\n"
74
73
  output << message_not_expanded
75
74
  end
76
- if output.join("\n").lines.count > 50 and dest[0]!='D'
75
+ if output.join("\n").lines.count > 50 and dest[0] != "D"
77
76
  dest = :on_thread
78
- output.unshift('Since there are many lines returned the results are returned on a thread by default.')
77
+ output.unshift("Since there are many lines returned the results are returned on a thread by default.")
79
78
  end
80
79
  if send_to_file
81
80
  content = output.join("\n\n")
82
81
  content.gsub!(/\*<([^>]*)\|([^>]*)>\*/, '## [\2](\1)')
83
82
  content.gsub!(/^\s*(\*.+\*)\s*$/, '# \1')
84
- content.gsub!(/command_id:\s+:/, '### :')
85
- content = content.gsub("\n", " \n").gsub(/\|[\w\s]*>/i,">").gsub(/^\s*\-\-\-\-\-\-/, "\n------")
83
+ content.gsub!(/command_id:\s+:/, "### :")
84
+ content = content.gsub("\n", " \n").gsub(/\|[\w\s]*>/i, ">").gsub(/^\s*\-\-\-\-\-\-/, "\n------")
86
85
  dest == :on_thread ? dest_file = dchannel : dest_file = dest
87
- send_file(dest_file, "SmartBot Rules", "", 'smartbot_rules.md', "text/markdown", "markdown", content: content)
86
+ send_file(dest_file, "SmartBot Rules", "", "smartbot_rules.md", "text/markdown", "markdown", content: content)
87
+ elsif return_output
88
+ output.each do |h|
89
+ h.gsub!(/^\s*command_id:\s+:\w+\s*$/, "")
90
+ h.gsub!(/^\s*>.+$/, "") if help_command.to_s != ""
91
+ end
92
+ return output
88
93
  else
89
94
  output.each do |h|
90
- msg = h.gsub(/^\s*command_id:\s+:\w+\s*$/,'')
91
- msg.gsub!(/^\s*>.+$/,'') if help_command.to_s != ''
95
+ msg = h.gsub(/^\s*command_id:\s+:\w+\s*$/, "")
96
+ msg.gsub!(/^\s*>.+$/, "") if help_command.to_s != ""
92
97
  unless msg.match?(/\A\s*\z/)
93
98
  respond msg, dest, unfurl_links: false, unfurl_media: false
94
99
  end
95
100
  end
96
101
  end
97
-
98
102
  end
99
103
  end
100
104
  end
@@ -37,7 +37,7 @@ class SlackSmartBot
37
37
  respond "Game over!", dest
38
38
  @listening[:threads].each do |thread_ts, channel_thread|
39
39
  unreact :running, thread_ts, channel: channel_thread
40
- respond "ChatGPT session closed since SmartBot is going to be closed", channel_thread, thread_ts: thread_ts
40
+ respond "ChatGPT session closed since SmartBot is going to be closed.\nCheck <##{@channels_id[config.status_channel]}>", channel_thread, thread_ts: thread_ts
41
41
  end
42
42
  if config.simulate
43
43
  sleep 2
@@ -45,7 +45,7 @@ class SlackSmartBot
45
45
  config.simulate = false
46
46
  Thread.exit
47
47
  else
48
- respond 'Ok, It will take around 40s to close all the bots, all routines and the master bot.'
48
+ respond "Ok, It will take around 40s to close all the bots, all routines and the master bot."
49
49
  sleep 35
50
50
  respond "Ciao #{display_name}!", dest
51
51
  unreact :runner
@@ -79,25 +79,25 @@ require_relative "commands/general/set_public_holidays"
79
79
  require_relative "commands/general/personal_settings"
80
80
  require_relative "commands/general/teams/memos/add_memo_team_comment"
81
81
  require_relative "commands/general/teams/memos/see_memo_team"
82
- require_relative 'commands/general/ai/open_ai/open_ai_chat'
83
- require_relative 'commands/general/ai/open_ai/open_ai_chat_get_prompts'
84
- require_relative 'commands/general/ai/open_ai/open_ai_chat_delete_session'
85
- require_relative 'commands/general/ai/open_ai/open_ai_chat_share_session'
86
- require_relative 'commands/general/ai/open_ai/open_ai_chat_list_sessions'
87
- require_relative 'commands/general/ai/open_ai/open_ai_chat_add_collaborator'
88
- require_relative 'commands/general/ai/open_ai/open_ai_chat_use_model'
89
- require_relative 'commands/general/ai/open_ai/open_ai_chat_copy_session'
90
- require_relative 'commands/general/ai/open_ai/open_ai_generate_image'
91
- require_relative 'commands/general/ai/open_ai/open_ai_variations_image'
92
- require_relative 'commands/general/ai/open_ai/open_ai_edit_image'
93
- require_relative 'commands/general/ai/open_ai/open_ai_models'
94
- require_relative 'commands/general/ai/open_ai/open_ai_whisper'
95
- require_relative 'commands/general/recap'
96
- require_relative 'commands/general/summarize'
97
- require_relative 'commands/general/get_smartbot_readme'
82
+ require_relative "commands/general/ai/open_ai/open_ai_chat"
83
+ require_relative "commands/general/ai/open_ai/open_ai_chat_get_prompts"
84
+ require_relative "commands/general/ai/open_ai/open_ai_chat_delete_session"
85
+ require_relative "commands/general/ai/open_ai/open_ai_chat_share_session"
86
+ require_relative "commands/general/ai/open_ai/open_ai_chat_list_sessions"
87
+ require_relative "commands/general/ai/open_ai/open_ai_chat_add_collaborator"
88
+ require_relative "commands/general/ai/open_ai/open_ai_chat_use_model"
89
+ require_relative "commands/general/ai/open_ai/open_ai_chat_copy_session"
90
+ require_relative "commands/general/ai/open_ai/open_ai_generate_image"
91
+ require_relative "commands/general/ai/open_ai/open_ai_variations_image"
92
+ require_relative "commands/general/ai/open_ai/open_ai_edit_image"
93
+ require_relative "commands/general/ai/open_ai/open_ai_models"
94
+ require_relative "commands/general/ai/open_ai/open_ai_whisper"
95
+ require_relative "commands/general/recap"
96
+ require_relative "commands/general/summarize"
97
+ require_relative "commands/general/get_smartbot_readme"
98
98
 
99
99
  class SlackSmartBot
100
- include SlackSmartBot::Commands::General::AI::OpenAI
101
- include SlackSmartBot::Commands::General::Teams
102
- include SlackSmartBot::Commands::General::Teams::Memos
100
+ include SlackSmartBot::Commands::General::AI::OpenAI
101
+ include SlackSmartBot::Commands::General::Teams
102
+ include SlackSmartBot::Commands::General::Teams::Memos
103
103
  end
@@ -7,12 +7,12 @@ class SlackSmartBot
7
7
  Thread.current[:user] = user
8
8
  Thread.current[:team_id_user] = team_id_user
9
9
  if text.match(/\A\s*(stop|quit|exit|kill)\s+(iterator|iteration|loop)\s+(\d+)\s*\z/i)
10
- save_stats :quit_loop, forced: true, data: {dest: dest, typem: typem, user: user, files: false, command: text, routine: routine}
10
+ save_stats :quit_loop, forced: true, data: { dest: dest, typem: typem, user: user, files: false, command: text, routine: routine }
11
11
  num_iteration = $3.to_i
12
12
  if config.team_id_admins.include?(team_id_user) or @loops.key?(team_id_user)
13
13
  if config.team_id_admins.include?(team_id_user)
14
- name_loop = ''
15
- @loops.each do |k,v|
14
+ name_loop = ""
15
+ @loops.each do |k, v|
16
16
  if v.include?(num_iteration)
17
17
  name_loop = k
18
18
  break
@@ -37,20 +37,20 @@ class SlackSmartBot
37
37
  text.gsub!(encdata, "********")
38
38
  end
39
39
  text = "********" if !found
40
- text+= " (encrypted #{Thread.current[:command_id]})"
40
+ text += " (encrypted #{Thread.current[:command_id]})"
41
41
  end
42
42
  @logger.info "command: #{user.team_id}/#{nick}> #{text}"
43
43
  return :next #jal
44
44
  end
45
45
  if text.match(/\A\s*!*^?\s*(for\s*)?(\d+)\s+times\s+every\s+(\d+)\s*(m|minute|minutes|s|sc|second|seconds)\s+(.+)\s*\z/i)
46
- save_stats :create_loop, forced: true, data: {dest: dest, typem: typem, user: user, files: false, command: text, routine: routine}
46
+ save_stats :create_loop, forced: true, data: { dest: dest, typem: typem, user: user, files: false, command: text, routine: routine }
47
47
  # min every 10s, max every 60m, max times 24
48
48
  command_every = text.dup
49
49
  text = $5
50
50
  num_times = $2.to_i
51
51
  type_every = $4.downcase
52
52
  every_seconds = $3.to_i
53
- command_every.gsub!(/^\s*!*^?\s*/, '')
53
+ command_every.gsub!(/^\s*!*^?\s*/, "")
54
54
  every_seconds = (every_seconds * 60) if type_every[0] == "m"
55
55
  if num_times > 24 or every_seconds < 10 or every_seconds > 3600
56
56
  respond "You can't do that. Maximum times is 24, minimum every is 10 seconds, maximum every is 60 minutes.", dest, thread_ts: thread_ts
@@ -61,10 +61,10 @@ class SlackSmartBot
61
61
  @num_loops += 1
62
62
  loop_id = @num_loops
63
63
  @loops[team_id_user] << loop_id
64
- respond "Loop #{loop_id} started. To stop the loop use: `#{['stop','quit','exit', 'kill'].sample} #{['iteration','iterator','loop'].sample} #{loop_id}`", dest, thread_ts: thread_ts
64
+ respond "Loop #{loop_id} started. To stop the loop use: `#{["stop", "quit", "exit", "kill"].sample} #{["iteration", "iterator", "loop"].sample} #{loop_id}`", dest, thread_ts: thread_ts
65
65
  #todo: command_orig should be reasigned maybe to remove for N times every X seconds. Check.
66
66
  else
67
- command_every = ''
67
+ command_every = ""
68
68
  num_times = 1
69
69
  every_seconds = 0
70
70
  end
@@ -119,6 +119,10 @@ class SlackSmartBot
119
119
  when /^Bot has been (closed|killed) by/i
120
120
  if config.channel == @channels_name[dchannel]
121
121
  @logger.info "#{nick}: #{text}"
122
+ @listening[:threads].each do |thread_ts, channel_thread|
123
+ unreact :running, thread_ts, channel: channel_thread
124
+ respond "ChatGPT session closed since SmartBot is going to be closed.\nCheck <##{@channels_id[config.status_channel]}>", channel_thread, thread_ts: thread_ts
125
+ end
122
126
  if config.simulate
123
127
  @status = :off
124
128
  config.simulate = false
@@ -151,7 +155,7 @@ class SlackSmartBot
151
155
  from_name = $1
152
156
  to_name = $2
153
157
  if config.on_master_bot and @bots_created.key?(@channels_id[from_name]) and
154
- @bots_created[@channels_id[from_name]][:cloud]
158
+ @bots_created[@channels_id[from_name]][:cloud]
155
159
  @bots_created[@channels_id[from_name]][:extended].delete(to_name)
156
160
  update_bots_file()
157
161
  end
@@ -252,15 +256,15 @@ class SlackSmartBot
252
256
  t = Thread.new do
253
257
  begin
254
258
  sleep every_seconds * i if every_seconds > 0
255
- Thread.exit if command_every!='' and @loops.key?(team_id_user) and !@loops[team_id_user].include?(loop_id)
256
- @logger.info "i: #{i}, num_times: #{num_times}, every_seconds: #{every_seconds}, command: #{command_thread}" if command_every!=''
259
+ Thread.exit if command_every != "" and @loops.key?(team_id_user) and !@loops[team_id_user].include?(loop_id)
260
+ @logger.info "i: #{i}, num_times: #{num_times}, every_seconds: #{every_seconds}, command: #{command_thread}" if command_every != ""
257
261
  processed = false
258
262
  processed_rules = false
259
263
 
260
264
  if command_thread.match?(/\A.+\s+\?\?\s+.+\z/im) and !command_thread.match?(/\A\s*(add|create)\s+(silent\s+)?(bgroutine|routine)\s+([\w\.]+)/im)
261
265
  pos = command_thread.index("??")
262
- Thread.current[:prompt] = command_thread[pos+2..-1].strip
263
- command_thread = command_thread[0..pos-1].strip
266
+ Thread.current[:prompt] = command_thread[pos + 2..-1].strip
267
+ command_thread = command_thread[0..pos - 1].strip
264
268
  Thread.current[:stdout] = ""
265
269
  else
266
270
  Thread.current[:prompt] = ""
@@ -318,17 +322,16 @@ class SlackSmartBot
318
322
  end
319
323
 
320
324
  if !config.on_maintenance and @listening.key?(team_id_user) and @listening[team_id_user].key?(Thread.current[:thread_ts]) and !Thread.current[:thread_ts].empty? and
321
- ((@active_chat_gpt_sessions.key?(team_id_user) and @active_chat_gpt_sessions[team_id_user].key?(Thread.current[:thread_ts])) or
322
- (@chat_gpt_collaborating.key?(team_id_user) and @chat_gpt_collaborating[team_id_user].key?(Thread.current[:thread_ts])))
325
+ ((@active_chat_gpt_sessions.key?(team_id_user) and @active_chat_gpt_sessions[team_id_user].key?(Thread.current[:thread_ts])) or
326
+ (@chat_gpt_collaborating.key?(team_id_user) and @chat_gpt_collaborating[team_id_user].key?(Thread.current[:thread_ts])))
323
327
  @listening[team_id_user][Thread.current[:thread_ts]] = Time.now
324
328
  command_thread = "? #{command_thread}" #chatgpt
325
329
  end
326
330
 
327
-
328
331
  unless config.on_maintenance or @status != :on
329
332
  if typem == :on_pub or typem == :on_pg or typem == :on_extended
330
333
  if command_thread.match(/\A(<)?\s*(#{@salutations.join("|")})\s+(rules|help)\s*(.+)?$/i) or command_thread.match(/\A(<)?\s*(#{@salutations.join("|")}),? what can I do/i)
331
- $1.to_s=='' ? send_to_file = false : send_to_file = true
334
+ $1.to_s == "" ? send_to_file = false : send_to_file = true
332
335
  $3.to_s.match?(/rules/i) ? specific = true : specific = false
333
336
  help_command = $4
334
337
  react :runner
@@ -343,6 +346,7 @@ class SlackSmartBot
343
346
  end
344
347
  processed = (processed || general_bot_commands(user, command_thread, dest, files))
345
348
  processed = (processed || general_commands(user, command_thread, dest, files)) if defined?(general_commands)
349
+
346
350
  if processed
347
351
  text_to_log = command_thread.dup
348
352
 
@@ -353,7 +357,7 @@ class SlackSmartBot
353
357
  text_to_log.gsub!(encdata, "********")
354
358
  end
355
359
  text_to_log = "********" if !found
356
- text_to_log+= " (encrypted #{Thread.current[:command_id]})"
360
+ text_to_log += " (encrypted #{Thread.current[:command_id]})"
357
361
  end
358
362
  @logger.info "command: #{user.team_id}/#{nick}> #{text_to_log}" unless user.id == config.nick_id_granular
359
363
  end
@@ -369,19 +373,19 @@ class SlackSmartBot
369
373
  ((@listening[team_id_user].key?(dest) and !Thread.current[:on_thread]) or
370
374
  (@listening[team_id_user].key?(thread_ts) and Thread.current[:on_thread]))) or
371
375
  dest[0] == "D" or on_demand)
372
- unless processed
373
- text_to_log = command_thread.dup
374
- found = false
375
- if Thread.current.key?(:encrypted) and Thread.current[:encrypted].size > 0
376
- Thread.current[:encrypted].each do |encdata|
377
- found = true if !found and text_to_log.include?(encdata)
378
- text_to_log.gsub!(encdata, "********")
379
- end
380
- text_to_log = "********" if !found
381
- text_to_log+= " (encrypted #{Thread.current[:command_id]})"
376
+ unless processed
377
+ text_to_log = command_thread.dup
378
+ found = false
379
+ if Thread.current.key?(:encrypted) and Thread.current[:encrypted].size > 0
380
+ Thread.current[:encrypted].each do |encdata|
381
+ found = true if !found and text_to_log.include?(encdata)
382
+ text_to_log.gsub!(encdata, "********")
382
383
  end
383
- @logger.info "command: #{user.team_id}/#{nick}> #{text_to_log}" unless user.id == config.nick_id_granular
384
+ text_to_log = "********" if !found
385
+ text_to_log += " (encrypted #{Thread.current[:command_id]})"
384
386
  end
387
+ @logger.info "command: #{user.team_id}/#{nick}> #{text_to_log}" unless user.id == config.nick_id_granular
388
+ end
385
389
  #todo: verify this
386
390
 
387
391
  if dest[0] == "C" or dest[0] == "G" or (dest[0] == "D" and typem == :on_call)
@@ -493,10 +497,10 @@ class SlackSmartBot
493
497
  end
494
498
  end
495
499
 
496
- if Thread.current[:prompt].to_s != ''
500
+ if Thread.current[:prompt].to_s != ""
497
501
  prompt = "#{Thread.current[:command]}\n\n#{Thread.current[:prompt]}\n\n#{Thread.current[:stdout]}\n\n"
498
- Thread.current[:prompt] = ''
499
- Thread.current[:stdout] = ''
502
+ Thread.current[:prompt] = ""
503
+ Thread.current[:stdout] = ""
500
504
  if processed
501
505
  if @active_chat_gpt_sessions.key?(team_id_user) and @active_chat_gpt_sessions[team_id_user].key?(Thread.current[:thread_ts])
502
506
  open_ai_chat(prompt, false, :temporary)
@@ -508,9 +512,8 @@ class SlackSmartBot
508
512
  if processed and config.general_message != "" and !routine
509
513
  respond eval("\"" + config.general_message + "\"")
510
514
  end
511
- respond "_*Loop #{loop_id}* (#{i+1}/#{num_times}) <@#{user.name}>: #{command_every}_" if command_every!='' and processed
512
- @loops[team_id_user].delete(loop_id) if command_every!='' and !processed and @loops.key?(team_id_user) and @loops[team_id_user].include?(loop_id)
513
-
515
+ respond "_*Loop #{loop_id}* (#{i + 1}/#{num_times}) <@#{user.name}>: #{command_every}_" if command_every != "" and processed
516
+ @loops[team_id_user].delete(loop_id) if command_every != "" and !processed and @loops.key?(team_id_user) and @loops[team_id_user].include?(loop_id)
514
517
  rescue Exception => stack
515
518
  @logger.fatal stack
516
519
  end