Class: SlackSmartBot

Inherits:
Object
  • Object
show all
Includes:
Commands::General::AI::OpenAI, Commands::General::Teams, Commands::General::Teams::Memos
Defined in:
lib/slack-smart-bot.rb,
lib/slack/smart-bot/config.rb,
lib/slack/smart-bot/listen.rb,
lib/slack/smart-bot/process.rb,
lib/slack/smart-bot/comm/ask.rb,
lib/slack/smart-bot/commands.rb,
lib/slack/smart-bot/comm/react.rb,
lib/slack/smart-bot/comm/delete.rb,
lib/slack/smart-bot/comm/update.rb,
lib/slack/smart-bot/comm/respond.rb,
lib/slack/smart-bot/comm/unreact.rb,
lib/slack/smart-bot/utils/answer.rb,
lib/slack/smart-bot/process_first.rb,
lib/slack/smart-bot/treat_message.rb,
lib/slack/smart-bot/comm/get_users.rb,
lib/slack/smart-bot/comm/send_file.rb,
lib/slack/smart-bot/utils/get_help.rb,
lib/slack/smart-bot/utils/is_admin.rb,
lib/slack/smart-bot/comm/set_status.rb,
lib/slack/smart-bot/utils/find_user.rb,
lib/slack/smart-bot/utils/get_repls.rb,
lib/slack/smart-bot/utils/get_teams.rb,
lib/slack/smart-bot/comm/event_hello.rb,
lib/slack/smart-bot/utils/build_help.rb,
lib/slack/smart-bot/utils/get_shares.rb,
lib/slack/smart-bot/utils/has_access.rb,
lib/slack/smart-bot/utils/local_time.rb,
lib/slack/smart-bot/utils/save_stats.rb,
lib/slack/smart-bot/ai/open_ai/models.rb,
lib/slack/smart-bot/comm/get_channels.rb,
lib/slack/smart-bot/comm/get_presence.rb,
lib/slack/smart-bot/utils/save_status.rb,
lib/slack/smart-bot/ai/open_ai/connect.rb,
lib/slack/smart-bot/comm/get_user_info.rb,
lib/slack/smart-bot/comm/send_msg_user.rb,
lib/slack/smart-bot/utils/get_keywords.rb,
lib/slack/smart-bot/utils/get_routines.rb,
lib/slack/smart-bot/utils/update_repls.rb,
lib/slack/smart-bot/utils/update_teams.rb,
lib/slack/smart-bot/comm/respond_direct.rb,
lib/slack/smart-bot/comm/respond_thread.rb,
lib/slack/smart-bot/utils/answer_delete.rb,
lib/slack/smart-bot/utils/get_vacations.rb,
lib/slack/smart-bot/comm/dont_understand.rb,
lib/slack/smart-bot/commands/on_bot/repl.rb,
lib/slack/smart-bot/comm/send_msg_channel.rb,
lib/slack/smart-bot/utils/check_vacations.rb,
lib/slack/smart-bot/utils/get_command_ids.rb,
lib/slack/smart-bot/utils/update_routines.rb,
lib/slack/smart-bot/commands/general/recap.rb,
lib/slack/smart-bot/utils/display_calendar.rb,
lib/slack/smart-bot/utils/get_bots_created.rb,
lib/slack/smart-bot/utils/get_team_members.rb,
lib/slack/smart-bot/utils/remove_hash_keys.rb,
lib/slack/smart-bot/utils/update_bots_file.rb,
lib/slack/smart-bot/utils/update_vacations.rb,
lib/slack/smart-bot/commands/general/hi_bot.rb,
lib/slack/smart-bot/commands/general/poster.rb,
lib/slack/smart-bot/ai/open_ai/send_gpt_chat.rb,
lib/slack/smart-bot/comm/get_channel_members.rb,
lib/slack/smart-bot/commands/general/bye_bot.rb,
lib/slack/smart-bot/commands/on_bot/get_repl.rb,
lib/slack/smart-bot/commands/on_bot/run_repl.rb,
lib/slack/smart-bot/utils/encryption/decrypt.rb,
lib/slack/smart-bot/utils/encryption/encrypt.rb,
lib/slack/smart-bot/utils/get_authorizations.rb,
lib/slack/smart-bot/utils/get_rules_imported.rb,
lib/slack/smart-bot/commands/general/bot_help.rb,
lib/slack/smart-bot/commands/on_bot/kill_repl.rb,
lib/slack/smart-bot/commands/on_bot/ruby_code.rb,
lib/slack/smart-bot/commands/on_bot/see_repls.rb,
lib/slack/smart-bot/utils/get_access_channels.rb,
lib/slack/smart-bot/utils/get_admins_channels.rb,
lib/slack/smart-bot/utils/get_openai_sessions.rb,
lib/slack/smart-bot/ai/open_ai/send_image_edit.rb,
lib/slack/smart-bot/commands/general/add_admin.rb,
lib/slack/smart-bot/commands/general/summarize.rb,
lib/slack/smart-bot/comm/get_smartbot_team_info.rb,
lib/slack/smart-bot/commands/general/see_access.rb,
lib/slack/smart-bot/commands/general/see_admins.rb,
lib/slack/smart-bot/commands/general/see_shares.rb,
lib/slack/smart-bot/commands/on_bot/delete_repl.rb,
lib/slack/smart-bot/commands/on_bot/repl_client.rb,
lib/slack/smart-bot/utils/create_routine_thread.rb,
lib/slack/smart-bot/utils/download_http_content.rb,
lib/slack/smart-bot/utils/get_personal_settings.rb,
lib/slack/smart-bot/utils/update_rules_imported.rb,
lib/slack/smart-bot/utils/update_shortcuts_file.rb,
lib/slack/smart-bot/commands/general/deny_access.rb,
lib/slack/smart-bot/commands/on_bot/add_shortcut.rb,
lib/slack/smart-bot/utils/update_access_channels.rb,
lib/slack/smart-bot/utils/update_admins_channels.rb,
lib/slack/smart-bot/utils/update_openai_sessions.rb,
lib/slack/smart-bot/ai/open_ai/whisper_transcribe.rb,
lib/slack/smart-bot/commands/general/add_vacation.rb,
lib/slack/smart-bot/commands/general/allow_access.rb,
lib/slack/smart-bot/commands/general/delete_share.rb,
lib/slack/smart-bot/commands/general/remove_admin.rb,
lib/slack/smart-bot/commands/general/see_statuses.rb,
lib/slack/smart-bot/commands/general_bot_commands.rb,
lib/slack/smart-bot/commands/on_bot/see_shortcuts.rb,
lib/slack/smart-bot/commands/on_master/create_bot.rb,
lib/slack/smart-bot/utils/upgrade_to_use_team_ids.rb,
lib/slack/smart-bot/commands/general/see_vacations.rb,
lib/slack/smart-bot/commands/on_extended/bot_rules.rb,
lib/slack/smart-bot/utils/get_channels_name_and_id.rb,
lib/slack/smart-bot/utils/update_personal_settings.rb,
lib/slack/smart-bot/ai/open_ai/send_image_variation.rb,
lib/slack/smart-bot/commands/general/share_messages.rb,
lib/slack/smart-bot/commands/general/teams/add_team.rb,
lib/slack/smart-bot/commands/on_bot/admin/pause_bot.rb,
lib/slack/smart-bot/commands/on_bot/admin/start_bot.rb,
lib/slack/smart-bot/commands/on_bot/delete_shortcut.rb,
lib/slack/smart-bot/ai/open_ai/send_image_generation.rb,
lib/slack/smart-bot/commands/general/public_holidays.rb,
lib/slack/smart-bot/commands/general/remove_vacation.rb,
lib/slack/smart-bot/commands/general/see_command_ids.rb,
lib/slack/smart-bot/commands/general/teams/ping_team.rb,
lib/slack/smart-bot/commands/general/teams/see_teams.rb,
lib/slack/smart-bot/utils/get_countries_candelarific.rb,
lib/slack/smart-bot/commands/general/add_announcement.rb,
lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb,
lib/slack/smart-bot/commands/on_bot/admin/run_routine.rb,
lib/slack/smart-bot/commands/on_bot/general/bot_stats.rb,
lib/slack/smart-bot/commands/on_bot/general/use_rules.rb,
lib/slack/smart-bot/commands/on_bot/general/whats_new.rb,
lib/slack/smart-bot/commands/on_master/where_smartbot.rb,
lib/slack/smart-bot/utils/transform_to_slack_markdown.rb,
lib/slack/smart-bot/commands/general/personal_settings.rb,
lib/slack/smart-bot/commands/general/see_announcements.rb,
lib/slack/smart-bot/commands/general/teams/delete_team.rb,
lib/slack/smart-bot/commands/general/teams/update_team.rb,
lib/slack/smart-bot/commands/on_bot/admin/extend_rules.rb,
lib/slack/smart-bot/commands/on_bot/admin/see_routines.rb,
lib/slack/smart-bot/commands/on_bot/general/bot_status.rb,
lib/slack/smart-bot/commands/on_bot/admin/pause_routine.rb,
lib/slack/smart-bot/commands/on_bot/admin/start_routine.rb,
lib/slack/smart-bot/commands/on_bot/general/leaderboard.rb,
lib/slack/smart-bot/commands/general/delete_announcement.rb,
lib/slack/smart-bot/commands/general/get_smartbot_readme.rb,
lib/slack/smart-bot/commands/general/set_public_holidays.rb,
lib/slack/smart-bot/commands/on_bot/admin/remove_routine.rb,
lib/slack/smart-bot/commands/on_bot/admin_master/react_to.rb,
lib/slack/smart-bot/commands/general/see_favorite_commands.rb,
lib/slack/smart-bot/utils/encryption/encryption_get_key_iv.rb,
lib/slack/smart-bot/commands/on_bot/general/suggest_command.rb,
lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat.rb,
lib/slack/smart-bot/commands/on_bot/admin/see_result_routine.rb,
lib/slack/smart-bot/commands/on_bot/general/stop_using_rules.rb,
lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb,
lib/slack/smart-bot/commands/general/teams/see_vacations_team.rb,
lib/slack/smart-bot/commands/on_bot/admin/stop_using_rules_on.rb,
lib/slack/smart-bot/commands/on_bot/admin_master/get_bot_logs.rb,
lib/slack/smart-bot/commands/on_bot/admin_master/send_message.rb,
lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_models.rb,
lib/slack/smart-bot/commands/general/teams/memos/add_memo_team.rb,
lib/slack/smart-bot/commands/general/teams/memos/see_memo_team.rb,
lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_whisper.rb,
lib/slack/smart-bot/commands/general/teams/memos/see_memos_team.rb,
lib/slack/smart-bot/commands/on_bot/admin_master/delete_message.rb,
lib/slack/smart-bot/commands/on_bot/admin_master/update_message.rb,
lib/slack/smart-bot/commands/general/teams/memos/set_memo_status.rb,
lib/slack/smart-bot/commands/on_master/admin/kill_bot_on_channel.rb,
lib/slack/smart-bot/commands/general/teams/memos/delete_memo_team.rb,
lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_edit_image.rb,
lib/slack/smart-bot/commands/on_master/admin_master/notify_message.rb,
lib/slack/smart-bot/commands/on_master/admin_master/set_maintenance.rb,
lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_use_model.rb,
lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_generate_image.rb,
lib/slack/smart-bot/commands/general/teams/memos/add_memo_team_comment.rb,
lib/slack/smart-bot/commands/on_master/admin_master/set_general_message.rb,
lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_get_prompts.rb,
lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_variations_image.rb,
lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_copy_session.rb,
lib/slack/smart-bot/commands/on_master/admin_master/publish_announcements.rb,
lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_list_sessions.rb,
lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_share_session.rb,
lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_delete_session.rb,
lib/slack/smart-bot/commands/general/ai/open_ai/open_ai_chat_add_collaborator.rb

Defined Under Namespace

Modules: AI, Commands, Utils Classes: Config

Constant Summary collapse

VERSION =
version
TIMEOUT_LISTENING =

30 minutes

60 * 30

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Commands::General::Teams::Memos

#add_memo_team, #add_memo_team_comment, #delete_memo_team, #see_memo_team, #see_memos_team, #set_memo_status

Methods included from Commands::General::Teams

#add_team, #delete_team, #ping_team, #see_teams, #see_vacations_team, #update_team

Methods included from Commands::General::AI::OpenAI

#open_ai_chat, #open_ai_chat_add_collaborator, #open_ai_chat_copy_session, #open_ai_chat_delete_session, #open_ai_chat_get_prompts, #open_ai_chat_list_sessions, #open_ai_chat_share_session, #open_ai_chat_use_model, #open_ai_edit_image, #open_ai_generate_image, #open_ai_models, #open_ai_variations_image, #open_ai_whisper

Constructor Details

#initialize(config) ⇒ SlackSmartBot

Initializes the Slack Smart Bot with the given configuration. Check the README for more information on the configuration options. and lib/slack/smart-bot/config.rb for the default values.



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
# File 'lib/slack-smart-bot.rb', line 46

def initialize(config)
  config_defaults = SlackSmartBot::Config.new()
  abort "The config supplied is not correct. You need to supply a hash with the correct keys and values. The keys are: #{config_defaults.to_h.keys.join(", ")}" unless config.is_a?(Hash)
  # when simulate true for testing purposes: client, web_client, nick, nick_id, git
  config_check = config.deep_copy
  [:client, :web_client, :nick, :nick_id, :git, :allow_access, :authorizations].each do |key|
    config_check.delete(key)
  end
  result_config = NiceHash.compare_structure(config_check, config_defaults.to_h)
  abort "The config supplied is not correct. You need to supply a hash with the correct keys and values. The keys are: #{config_defaults.to_h.keys.join(", ")}" unless result_config

  config = config_defaults.to_h.nice_merge(config)

  config.path.chop! if config.path[-1] == "/"
  config[:jira][:host] = "https://#{config[:jira][:host]}" unless config[:jira][:host] == "" or config[:jira][:host].match?(/^http/)
  config[:github][:host] = "https://#{config[:github][:host]}" unless config[:github][:host] == "" or config[:github][:host].match?(/^http/)
  config[:public_holidays][:host] = "https://#{config[:public_holidays][:host]}" unless config[:public_holidays][:host] == "" or config[:public_holidays][:host].match?(/^http/)

  if config.path.to_s != "" and config.file.to_s == ""
    config.file = File.basename($0)
  end
  if config.key?(:file) and config.file != ""
    config.file_path = "#{config.path}/#{config.file}"
  else
    config.file_path = $0
    config.file = File.basename(config.file_path)
    config.path = File.dirname(config.file_path)
  end
  if config.stats
    Dir.mkdir("#{config.path}/stats") unless Dir.exist?("#{config.path}/stats")
    config.stats_path = "#{config.path}/stats/#{config.file.gsub(".rb", ".stats")}"
  end
  Dir.mkdir("#{config.path}/logs") unless Dir.exist?("#{config.path}/logs")
  Dir.mkdir("#{config.path}/shortcuts") unless Dir.exist?("#{config.path}/shortcuts")
  Dir.mkdir("#{config.path}/routines") unless Dir.exist?("#{config.path}/routines")
  Dir.mkdir("#{config.path}/announcements") unless Dir.exist?("#{config.path}/announcements")
  Dir.mkdir("#{config.path}/shares") unless Dir.exist?("#{config.path}/shares")
  Dir.mkdir("#{config.path}/rules") unless Dir.exist?("#{config.path}/rules")
  Dir.mkdir("#{config.path}/vacations") unless Dir.exist?("#{config.path}/vacations")
  Dir.mkdir("#{config.path}/teams") unless Dir.exist?("#{config.path}/teams")
  Dir.mkdir("#{config.path}/personal_settings") unless Dir.exist?("#{config.path}/personal_settings")
  Dir.mkdir("#{config.path}/openai") unless Dir.exist?("#{config.path}/openai")

  File.delete("#{config.path}/config_tmp.status") if File.exist?("#{config.path}/config_tmp.status")

  if !config.simulate
    if (!config.key?(:token) or config.token.to_s == "")
      abort "You need to supply a valid token key on the settings. key: :token"
    elsif (!config.key?(:user_token) or config.user_token.to_s == "")
      abort "You need to supply a valid user_token key on the settings. key: :user_token"
    elsif (!config.key?(:granular_token) or config.granular_token.to_s == "")
      abort "You need to supply a valid granular_token key on the settings. key: :granular_token"
    end
  end

  resp = get_smartbot_team_info(config[:token])
  if resp.key?(:team) and resp[:team].key?(:enterprise_id)
    config.team_id = resp[:team][:enterprise_id]
  else
    config.team_id = resp.team.id
  end

  config.masters = MASTER_USERS if config.masters.to_s == "" and defined?(MASTER_USERS)
  config.team_id_masters ||= []
  config.team_id_admins ||= []

  config.master_channel = MASTER_CHANNEL if config.master_channel.to_s == "" and defined?(MASTER_CHANNEL)

  if ARGV.size == 0 or (config.file.to_s != "" and config.file.to_s != File.basename($0))
    config.rules_file = "#{config.file.gsub(".rb", "_rules.rb")}" unless config.rules_file.to_s != ""
    unless File.exist?(config.path + "/" + config.rules_file)
      default_rules = (__FILE__).gsub(/\.rb$/, "_rules.rb")
      FileUtils.copy_file(default_rules, config.path + "/" + config.rules_file)
    end
    config.admins = config.masters.dup unless !config.admins.empty?
    config.team_id_admins = config.team_id_masters.dup unless !config.team_id_admins.empty?
    config.channel = config.master_channel unless config.channel.to_s != ""
    config.status_init = :on unless config.status_init.to_s != ""
  else
    config.rules_file = ARGV[2]
    config.admins = ARGV[1].split(",")
    config.channel = ARGV[0]
    config.status_init = ARGV[3].to_sym
  end
  if config.team_id_admins.size != config.admins.size and !config.admins.empty?
    config.admins.each do |name|
      if name.match?(/^[A-Z0-9]{7,11}_/)
        config.team_id_admins << name
      else
        config.team_id_admins << "#{config.team_id}_#{name}"
      end
    end
  end
  config.team_id_admins.uniq!
  config.admins.uniq!

  config.rules_file[0] = "" if config.rules_file[0] == "."
  config.rules_file = "/" + config.rules_file if config.rules_file[0] != "/"

  config.shortcuts_file = "slack-smart-bot_shortcuts_#{config.channel}.yaml".gsub(" ", "_")
  if config.channel == config.master_channel
    config.on_master_bot = true
    config.start_bots = true unless config.key?(:start_bots)
  else
    config.on_master_bot = false
  end

  if (!config.masters.is_a?(Array) or !config.team_id_masters.is_a?(Array)) or
     (config.masters + config.team_id_masters).empty?
    message = "You need to supply a masters array on the settings containing the user names of the master admins, for example: [peter]. key: :masters"
    message += " or a team_id_masters array containing the team_id and user names of the master admins, for example: [TJDFJKD34_peter]. key: :team_id_masters"
    abort message
  elsif !config.key?(:master_channel) or config.master_channel.to_s == ""
    abort "You need to supply a master_channel on the settings. key: :master_channel"
  elsif !config.key?(:channel) or config.channel.to_s == ""
    abort "You need to supply a bot channel name on the settings. key: :channel"
  end

  logfile = File.basename(config.rules_file.gsub("_rules_", "_logs_"), ".rb") + ".log"
  config.log_file = logfile
  @logger = Logger.new("#{config.path}/logs/#{logfile}")
  @last_respond = Time.now

  #todo: consider putting this on a method
  config_log = config.deep_copy
  config_log.delete(:token)
  config_log.delete(:user_token)
  config_log.delete(:granular_token)
  config_log.delete(:authorizations)
  config_log.jira.password = "********" if config_log.key?(:jira) and config_log.jira.key?(:password)
  config_log.github.token = "********" if config_log.key?(:github) and config_log.github.key?(:token)
  config_log.public_holidays.api_key = "********" if config_log.key?(:public_holidays) and config_log.public_holidays.key?(:api_key)
  config_log.encryption[:key] = "********" if config_log.key?(:encryption) and config_log.encryption[:key].to_s != ""
  config_log.encryption.iv = "********" if config_log.key?(:encryption) and config_log.encryption.iv.to_s != ""
  config_log.ai.open_ai.access_token = "********" if config_log.key?(:ai) and config_log.ai.key?(:open_ai) and config_log.ai.open_ai.key?(:access_token)
  config_log.ai.open_ai.chat_gpt.access_token = "********" if config_log.key?(:ai) and config_log.ai.key?(:open_ai) and config_log.ai.open_ai.key?(:chat_gpt) and config_log.ai.open_ai.chat_gpt.key?(:access_token)
  config_log.ai.open_ai.dall_e.access_token = "********" if config_log.key?(:ai) and config_log.ai.key?(:open_ai) and config_log.ai.open_ai.key?(:dall_e) and config_log.ai.open_ai.dall_e.key?(:access_token)
  config_log.ai.open_ai.whisper.access_token = "********" if config_log.key?(:ai) and config_log.ai.key?(:open_ai) and config_log.ai.open_ai.key?(:whisper) and config_log.ai.open_ai.whisper.key?(:access_token)
  config_log.ai.open_ai.models.access_token = "********" if config_log.key?(:ai) and config_log.ai.key?(:open_ai) and config_log.ai.open_ai.key?(:models) and config_log.ai.open_ai.models.key?(:access_token)
  config_log.ldap.auth.password = "********" if config_log.key?(:ldap) and config_log.ldap.key?(:auth) and config_log.ldap.auth.key?(:password)
  @config_log = config_log.deep_copy
  @logger.info "Initializing bot: #{@config_log.inspect}"

  File.new("#{config.path}/buffer.log", "w") if config[:testing] and config.on_master_bot
  File.new("#{config.path}/buffer_complete.log", "w") if config[:simulate] and config.on_master_bot

  self.config = config

  save_status :off, :initializing, "Initializing bot: #{config_log.inspect}"

  unless config.simulate and config.key?(:client)
    Slack.configure do |conf|
      conf.token = config[:token]
    end
  end
  unless (config.simulate and config.key?(:client)) or config.user_token.nil? or config.user_token.empty?
    begin
      self.client_user = Slack::Web::Client.new(token: config.user_token)
      self.client_user.auth_test
    rescue Exception => e
      @logger.fatal "*" * 50
      @logger.fatal "Rescued on creation client_user: #{e.inspect}"
      self.client_user = nil
      abort "Rescued on creation client_user. You need to supply a valid user_token key on the settings. key: :user_token.\n#{e.inspect}"
    end
  else
    self.client_user = nil
  end

  unless (config.simulate and config.key?(:client)) or config.granular_token.nil? or config.granular_token.empty?
    begin
      self.client_granular = Slack::Web::Client.new(token: config.granular_token)
      self.client_granular.auth_test
    rescue Exception => e
      @logger.fatal "*" * 50
      @logger.fatal "Rescued on creation client_granular: #{e.inspect}"
      self.client_granular = nil
      abort "Rescued on creation client_granular. You need to supply a valid granular_token key on the settings. key: :granular_token.\n#{e.inspect}"
    end
  else
    self.client_granular = nil
  end

  restarts = 0
  created = false
  while restarts < 200 and !created
    begin
      @logger.info "Connecting #{config_log.inspect}"
      save_status :off, :connecting, "Connecting #{config_log.inspect}"
      if config.simulate and config.key?(:client)
        self.client = config.client
      else
        if config.logrtm
          logrtmname = "#{config.path}/logs/rtm_#{config.channel}.log"
          File.delete(logrtmname) if File.exist?(logrtmname)
          @logrtm = Logger.new(logrtmname)
          self.client = Slack::RealTime::Client.new(start_method: :rtm_connect, logger: @logrtm)
        else
          self.client = Slack::RealTime::Client.new(start_method: :rtm_connect)
        end
      end
      created = true
    rescue Exception => e
      restarts += 1
      if restarts < 200
        @logger.fatal "*" * 50
        @logger.fatal "Rescued on creation: #{e.inspect}"
        @logger.info "Waiting 60 seconds to retry. restarts: #{restarts}"
        puts "#{Time.now}: Not able to create client. Waiting 60 seconds to retry: #{config_log.inspect}"
        save_status :off, :waiting, "Not able to create client. Waiting 60 seconds to retry: #{config_log.inspect}"

        sleep 60
      else
        exit!
      end
    end
  end

  if config.team_id_masters.empty?
    config.masters.each_with_index do |name, i|
      if name.match?(/^[A-Z0-9]{7,11}_/)
        config.team_id_masters << name
        config.masters[i] = name.split("_")[1..-1].join("_")
      else
        config.team_id_masters << "#{config.team_id}_#{name}"
      end
    end
  else
    config.masters = []
    config.team_id_masters.each_with_index do |tid_name, i|
      name = tid_name.split("_")[1..-1].join("_")
      config.masters << name
    end
  end

  @listening = Hash.new()
  @listening[:threads] = Hash.new() #[thread_ts] => channel_id

  @bots_created = Hash.new()
  @shortcuts = Hash.new()
  @shortcuts[:all] = Hash.new()
  @shortcuts_global = Hash.new()
  @shortcuts_global[:all] = Hash.new()
  @rules_imported = Hash.new()
  @routines = Hash.new()
  @repls = Hash.new()
  @run_repls = Hash.new()
  @users = Hash.new()
  @announcements = Hash.new()
  @shares = Hash.new()
  @last_status_change = Time.now
  @vacations_check = (Time.now - 3600).strftime("%Y%m%d%H")
  @announcements_activity_after = Hash.new()
  @public_holidays = Hash.new()
  @loops = Hash.new()
  @ai_gpt ||= {}
  @active_chat_gpt_sessions = Hash.new()
  @chat_gpt_collaborating = Hash.new()
  @open_ai = Hash.new()
  @open_ai_models = []
  @slack_bots = Hash.new()

  @ldap = nil
  begin
    if config.ldap.key?(:host) and config.ldap[:host].to_s != ""
      require "net/ldap"
      if config.ldap.key?(:auth) and config.ldap[:auth].key?(:user) and config.ldap[:auth][:user].to_s != ""
        @ldap = Net::LDAP.new(host: config.ldap.host, port: config.ldap.port, auth: config.ldap.auth)
      else
        @ldap = Net::LDAP.new(host: config.ldap.host, port: config.ldap.port)
      end
    end
  rescue Exception => e
    @logger.fatal "Rescued on creation ldap: #{e.inspect}"
    @ldap = nil
  end

  if (config.on_master_bot and !File.exist?("#{config.path}/status/version.txt")) or
     (config.on_master_bot and File.exist?("#{config.path}/status/version.txt") and
      Gem::Version.new(File.read("#{config.path}/status/version.txt").to_s) <= Gem::Version.new("1.15.0"))
    upgrade_to_use_team_ids()
  end

  if File.exist?("#{config.path}/shortcuts/#{config.shortcuts_file}".gsub(".yaml", ".rb")) #backwards compatible
    file_conf = IO.readlines("#{config.path}/shortcuts/#{config.shortcuts_file}".gsub(".yaml", ".rb")).join
    if file_conf.to_s() == ""
      @shortcuts = {}
    else
      @shortcuts = eval(file_conf)
    end
    File.open("#{config.path}/shortcuts/#{config.shortcuts_file}", "w") { |file| file.write(@shortcuts.to_yaml) }
    File.delete("#{config.path}/shortcuts/#{config.shortcuts_file}".gsub(".yaml", ".rb"))
  elsif File.exist?("#{config.path}/shortcuts/#{config.shortcuts_file}")
    @shortcuts = YAML.load(File.read("#{config.path}/shortcuts/#{config.shortcuts_file}"))
  end
  if File.exist?("#{config.path}/shortcuts/shortcuts_global.rb") #backwards compatible
    file_sc = IO.readlines("#{config.path}/shortcuts/shortcuts_global.rb").join
    @shortcuts_global = {}
    unless file_sc.to_s() == ""
      @shortcuts_global = eval(file_sc)
    end
    File.open("#{config.path}/shortcuts/shortcuts_global.yaml", "w") { |file| file.write(@shortcuts_global.to_yaml) }
    File.delete("#{config.path}/shortcuts/shortcuts_global.rb")
  elsif File.exist?("#{config.path}/shortcuts/shortcuts_global.yaml")
    @shortcuts_global = YAML.load(File.read("#{config.path}/shortcuts/shortcuts_global.yaml"))
  end

  get_routines()
  get_repls()

  if config.on_master_bot and (File.exist?(config.file_path.gsub(".rb", "_bots.rb")) or File.exist?(config.file_path.gsub(".rb", "_bots.yaml")))
    get_bots_created()
    if @bots_created.kind_of?(Hash) and config.start_bots
      @bots_created.each { |key, value|
        if !value.key?(:cloud) or (value.key?(:cloud) and value[:cloud] == false)
          if value.key?(:silent) and value.silent != config.silent
            silent = value.silent
          else
            silent = config.silent
          end
          @logger.info "BOT_SILENT=#{silent} ruby #{config.file_path} \"#{value[:channel_name]}\" \"#{value[:admins]}\" \"#{value[:rules_file]}\" #{value[:status].to_sym}"
          puts "Starting #{value[:channel_name]} Smart Bot"
          save_status :off, :starting, "Starting #{value[:channel_name]} Smart Bot"

          t = Thread.new do
            `BOT_SILENT=#{silent} ruby #{config.file_path} \"#{value[:channel_name]}\" \"#{value[:admins]}\" \"#{value[:rules_file]}\" #{value[:status].to_sym}`
          end
          value[:thread] = t
          sleep value[:admins].size
        end
      }
    end
  end
  general_rules_file = "/rules/general_rules.rb"
  general_commands_file = "/rules/general_commands.rb"
  default_general_rules = (__FILE__).gsub(/\/slack-smart-bot\.rb$/, "/slack-smart-bot_general_rules.rb")
  default_general_commands = (__FILE__).gsub(/\/slack-smart-bot\.rb$/, "/slack-smart-bot_general_commands.rb")
  FileUtils.copy_file(default_general_rules, config.path + general_rules_file) unless File.exist?(config.path + general_rules_file)
  FileUtils.copy_file(default_general_commands, config.path + general_commands_file) unless File.exist?(config.path + general_commands_file)

  get_rules_imported()
  begin
    #todo: take in consideration the case that the value supplied on config.masters and config.admins are the ids and not the user names
    @admin_users_id = []
    @master_admin_users_id = []
    config.team_id_admins.each do |au|
       = ("@#{au}")
      @admin_users_id << .user.id
      if config.team_id_masters.include?(au)
        @master_admin_users_id << .user.id
      end
      sleep 1
    end
    (config.team_id_masters - config.team_id_admins).each do |au|
       = ("@#{au}")
      @master_admin_users_id << .user.id unless .nil?
      sleep 1
    end
    @admin_users_id.uniq!
    @master_admin_users_id.uniq!
  rescue Slack::Web::Api::Errors::TooManyRequestsError
    @logger.fatal "TooManyRequestsError"
    save_status :off, :TooManyRequestsError, "TooManyRequestsError please re run the bot and be sure of executing first: killall ruby"
    abort("TooManyRequestsError please re run the bot and be sure of executing first: killall ruby")
  rescue Exception => stack
    pp stack if config.testing
    save_status :off, :wrong_admin_user, "The admin user specified on settings: #{config.admins.join(", ")}, doesn't exist on Slack. Execution aborted"
    abort("The admin user specified on settings: #{config.admins.join(", ")}, doesn't exist on Slack. Execution aborted")
  end

  if config.simulate and config.key?(:client)
    event_hello()
  else
    client.on :hello do
      event_hello()
    end
  end

  @status = config.status_init
  @questions = Hash.new()
  @answer = Hash.new()
  @repl_sessions = Hash.new()
  @datetime_general_commands = 0
  @channels_id = Hash.new()
  @channels_name = Hash.new()
  @channels_creator = Hash.new()
  @channels_list = Hash.new()
  get_channels_name_and_id()
  @channel_id = @channels_id[config.channel].dup
  @master_bot_id = @channels_id[config.master_channel].dup

  Dir.mkdir("#{config.path}/rules/#{@channel_id}") unless Dir.exist?("#{config.path}/rules/#{@channel_id}/")

  get_routines()
  get_repls()
  get_shares()
  get_admins_channels()
  get_access_channels()
  get_vacations()
  get_openai_sessions()
  get_personal_settings()

  if @routines.key?(@channel_id)
    @routines[@channel_id].each do |k, v|
      @routines[@channel_id][k][:running] = false
    end
  end
  update_routines()

  if config.simulate #not necessary to wait until bot started (client.on :hello)
    @routines.each do |ch, rout|
      rout.each do |k, v|
        if !v[:running] and v[:channel_name] == config.channel
          create_routine_thread(k, v)
        end
      end
    end
  else
    client.on :close do |_data|
      m = "Connection closing, exiting. #{Time.now}"
      @logger.info m
      @logger.info _data
      #save_status :off, :closing, "Connection closing, exiting." #todo: don't notify for the moment, remove when checked
    end

    client.on :closed do |_data|
      m = "Connection has been disconnected. #{Time.now}"
      @logger.info m
      @logger.info _data
      save_status :off, :disconnected, "Connection has been disconnected."
    end
  end
  self
end

Instance Attribute Details

#channel_idObject (readonly)

Returns the value of attribute channel_id.



33
34
35
# File 'lib/slack-smart-bot.rb', line 33

def channel_id
  @channel_id
end

#clientObject

Returns the value of attribute client.



32
33
34
# File 'lib/slack-smart-bot.rb', line 32

def client
  @client
end

#client_granularObject

Returns the value of attribute client_granular.



32
33
34
# File 'lib/slack-smart-bot.rb', line 32

def client_granular
  @client_granular
end

#client_userObject

Returns the value of attribute client_user.



32
33
34
# File 'lib/slack-smart-bot.rb', line 32

def client_user
  @client_user
end

#configObject

Returns the value of attribute config.



32
33
34
# File 'lib/slack-smart-bot.rb', line 32

def config
  @config
end

#master_bot_idObject (readonly)

Returns the value of attribute master_bot_id.



33
34
35
# File 'lib/slack-smart-bot.rb', line 33

def master_bot_id
  @master_bot_id
end

Instance Method Details

#add_admin(user, admin_user) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/slack/smart-bot/commands/general/add_admin.rb', line 2

def add_admin(user, admin_user)
  save_stats(__method__)
  if Thread.current[:dest][0]=='D'
    respond "This command cannot be called from a DM"
  else
    if Thread.current[:typem] == :on_call
      channel = Thread.current[:dchannel]
    elsif Thread.current[:using_channel].to_s==''
      channel = Thread.current[:dest]
    else
      channel = Thread.current[:using_channel]
    end
    messages = []
    admins = config.masters.dup
    team_id_admins = config.team_id_masters.dup

    channels = get_channels()
    channel_found = channels.detect { |c| c.id == channel }
    if !channel_found.nil? and channel_found.creator.to_s != ''
      creator_info = find_user(channel_found.creator)
      admins << creator_info.name
      team_id_admins << "#{creator_info.team_id}_#{creator_info.name}"
    end
    if Thread.current[:typem] == :on_bot or Thread.current[:typem] == :on_master
      admins << config.admins.dup
      team_id_admins << config.team_id_admins.dup
    end
    if @admins_channels.key?(channel) and @admins_channels[channel].size > 0
      team_id_admins << @admins_channels[channel]
      #remove the team_id from the names on @admins_channels
      admins << @admins_channels[channel].map { |a| a.split('_')[1..-1].join('_') }
    end
    admins.flatten!
    admins.uniq!
    admins.delete(nil)
    team_id_admins.flatten!
    team_id_admins.uniq!
    team_id_admins.delete(nil)
    if team_id_admins.include?("#{user.team_id}_#{user.name}")
      admin_info = find_user(admin_user)
      if team_id_admins.include?("#{admin_info.team_id}_#{admin_info.name}")
        messages << "This user is already an admin of this channel."
      else
        @admins_channels[channel] ||= []
        @admins_channels[channel] << "#{admin_info.team_id}_#{admin_info.name}"
        update_admins_channels()
        messages << "The user is an admin of this channel from now on."
        admins << admin_info.name
        team_id_admins << "#{admin_info.team_id}_#{admin_info.name}"
      end
      messages << "*Admins*: <@#{admins.join('>, <@')}>"
    else
      messages << "Only the creator of the channel, Master admins or admins can add a new admin for this channel."
      messages << "*Admins*: <@#{admins.join('>, <@')}>"
    end

    respond messages.join("\n")
  end
end

#add_announcement(user, type, message) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/slack/smart-bot/commands/general/add_announcement.rb', line 3

def add_announcement(user, type, message)
  save_stats(__method__)
  if has_access?(__method__, user)
    if Thread.current[:typem] == :on_call
      channel = Thread.current[:dchannel]
    else
      channel = Thread.current[:dest]
    end
    if File.exist?("#{config.path}/announcements/#{channel}.csv") and !@announcements.key?(channel)
      t = CSV.table("#{config.path}/announcements/#{channel}.csv", headers: ['message_id', 'user_team_id_deleted', 'user_deleted', 'user_team_id_created', 'user_created', 'date', 'time', 'type', 'message'])
      @announcements[channel] = t
      num = t[:message_id].max + 1
    elsif !@announcements.key?(channel)
      File.open("#{config.path}/announcements/#{channel}.csv","w")
      t = CSV.table("#{config.path}/announcements/#{channel}.csv", headers: ['message_id', 'user_team_id_deleted', 'user_deleted', 'user_team_id_created', 'user_created', 'date', 'time', 'type', 'message'])
      num = 1
      @announcements[channel] = t
    else
      num = @announcements[channel][:message_id].max + 1
    end
    values = [num, '', '', user.team_id, user.name, Time.now.strftime("%Y/%m/%d"), Time.now.strftime("%H:%M"), type, message]
    @announcements[channel] << values
    CSV.open("#{config.path}/announcements/#{channel}.csv", "a+") do |csv|
      csv << values
    end
    respond "The announcement has been added. (id: #{num}).\nRelated commands `see announcements`, `delete announcement ID`"

  end
end

#add_routine(dest, from, user, name, type, number_time, period, command_to_run, files, silent, channel, routine_type) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: >https://github.com/MarioRuiz/slack-smart-bot#routines|ROUTINES helpadmin: add routine NAME every NUMBER PERIOD COMMAND helpadmin: add routine NAME every NUMBER PERIOD #CHANNEL COMMAND helpadmin: add routine NAME every NUMBER PERIOD helpadmin: add bgroutine NAME every NUMBER PERIOD helpadmin: add silent routine NAME every NUMBER PERIOD helpadmin: add silent bgroutine NAME every NUMBER PERIOD helpadmin: create routine NAME every NUMBER PERIOD helpadmin: add routine NAME at TIME COMMAND helpadmin: add routine NAME at TIME #CHANNEL COMMAND helpadmin: add routine NAME on DAYWEEK at TIME COMMAND helpadmin: add routine NAME on DAYWEEK at TIME #CHANNEL COMMAND helpadmin: add routine NAME on the DAY_OF_MONTH at TIME COMMAND helpadmin: add routine NAME at TIME helpadmin: add silent routine NAME at TIME helpadmin: create routine NAME at TIME helpadmin: It will execute the command/rule supplied. Only for Admin and Master Admins. helpadmin: If no COMMAND supplied, then it will be necessary to attach a file with the code to be run and add this command as message to the file. ONLY for MASTER ADMINS. helpadmin: In case bgroutine then the results of the run won't be published. To see the results call: see result routine NAME helpadmin: In case silent provided then when executed will be only displayed if the routine returns a message helpadmin: NAME: one word to identify the routine helpadmin: NUMBER: Integer helpadmin: PERIOD: days, d, hours, h, minutes, mins, min, m, seconds, secs, sec, s helpadmin: TIME: time at format HH:MM:SS helpadmin: DAYWEEK: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday. And their plurals. Also possible to be used 'weekdays' and 'weekends' helpadmin: #CHANNEL: the destination channel where the results will be published. If not supplied then the bot channel by default or a DM if the command is run from a DM. helpadmin: COMMAND: any valid smart bot command or rule helpadmin: It is possible to add a script directly. Only master admins can do it. helpadmin: Examples: helpadmin: add routine example every 30s !ruby puts 'a' helpadmin: add bgroutine example every 3 days !ruby puts 'a' helpadmin: add routine example at 17:05 !ruby puts 'a' helpadmin: create silent routine Example every 12 hours !Run customer tests helpadmin: add bgroutine example on Mondays at 05:00 !run customer tests helpadmin: add routine example on Tuesdays at 09:00 #SREChannel !run db cleanup helpadmin: add routine example on weekdays at 22:00 suggest command helpadmin: add routine example on the 5th at 22:00 suggest command helpadmin: helpadmin: command_id: :add_routine helpadmin:



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb', line 43

def add_routine(dest, from, user, name, type, number_time, period, command_to_run, files, silent, channel, routine_type)
  save_stats(__method__)
  if files.nil? or files.size == 0 or (files.size > 0 and config.team_id_masters.include?("#{user.team_id}_#{user.name}"))
    if is_admin?
      if @routines.key?(@channel_id) && @routines[@channel_id].key?(name)
        respond "I'm sorry but there is already a routine with that name.\nCall `see routines` to see added routines", dest
      else
        react :running
        number_time += ":00" if number_time.split(":").size == 2
        if (type != "every") && !number_time.match?(/^[01][0-9]:[0-5][0-9]:[0-5][0-9]$/) &&
           !number_time.match?(/^2[0-3]:[0-5][0-9]:[0-5][0-9]$/)
          respond "Wrong time specified: *#{number_time}*"
        else
          file_path = ""
          every = ""
          at = ""
          dayweek = ''
          daymonth = ''
          next_run = Time.now
          case period.downcase
          when "days", "d"
            every = "#{number_time} days"
            every_in_seconds = number_time.to_i * 24 * 60 * 60
          when "hours", "h"
            every = "#{number_time} hours"
            every_in_seconds = number_time.to_i * 60 * 60
          when "minutes", "mins", "min", "m"
            every = "#{number_time} minutes"
            every_in_seconds = number_time.to_i * 60
          when "seconds", "secs", "sec", "s"
            every = "#{number_time} seconds"
            every_in_seconds = number_time.to_i
          else # time
            if type != 'at' and type.match?(/^\d+$/) # day of month
              day = type.to_i
              daymonth = type
              if day > 31
                respond "Wrong day of month specified: *#{day}*", dest
                return
              end
              if Date.today.day > day
                  next_month = Date.new(Date.today.year, Date.today.month, 1) >> 1
              else
                  next_month = Date.new(Date.today.year, Date.today.month, 1)
              end
              next_month_last_day = Date.new(next_month.year, next_month.month, -1)
              if day > next_month_last_day.day
                  next_time = Date.new(next_month.year, next_month.month, next_month_last_day.day)
              else
                  next_time = Date.new(next_month.year, next_month.month, day)
              end
              days = (next_time - Date.today).to_i
              every_in_seconds = days * 24 * 60 * 60 # one day

            elsif type != 'at' and type!='weekday' and type!='weekend'
              dayweek = type.downcase

              days = ['sunday','monday','tuesday','wednesday','thursday','friday','saturday']
              incr = days.index(dayweek) - Time.now.wday
              if incr < 0
                incr = (7+incr)*24*60*60
              else
                incr = incr * 24 * 60 * 60
              end
              days = incr/(24*60*60)
              every_in_seconds = 7 * 24 * 60 * 60 # one week
            elsif type=='weekend'
              dayweek = type.downcase
              days = 0
              every_in_seconds = 24 * 60 * 60 # one day
            elsif type=='weekday'
              dayweek = type.downcase
              days = 0
              every_in_seconds = 24 * 60 * 60 # one day
            else
              days = 0
              every_in_seconds = 24 * 60 * 60 # one day
            end

            at = number_time
            if next_run.strftime("%H:%M:%S") < number_time and days == 0
              nt = number_time.split(":")
              next_run = Time.new(next_run.year, next_run.month, next_run.day, nt[0], nt[1], nt[2])
            else
              next_run += ((24 * 60 * 60) * days) # one or more days
              nt = number_time.split(":")
              next_run = Time.new(next_run.year, next_run.month, next_run.day, nt[0], nt[1], nt[2])
            end
          end
          Dir.mkdir("#{config.path}/routines/#{@channel_id}") unless Dir.exist?("#{config.path}/routines/#{@channel_id}")

          if !files.nil? && (files.size == 1)
            @logger.info files[0].inspect if config.testing
            file_path = "#{config.path}/routines/#{@channel_id}/#{name}#{files[0].name.scan(/[^\.]+(\.\w+$)/).join}"
            if files[0].filetype == "ruby" and files[0].name.scan(/[^\.]+(\.\w+$)/).join == ''
              file_path += ".rb"
            end
            if files[0].key?(:content)
              File.open(file_path, 'w') do |f|
                f.write files[0].content
              end
            else
              http = NiceHttp.new(host: "https://files.slack.com", headers: { "Authorization" => "Bearer #{config[:token]}" }, log_headers: :partial)
              http.get(files[0].url_private_download, save_data: file_path)
            end
            system("chmod +x #{file_path}")
          end
          get_channels_name_and_id() unless @channels_name.keys.include?(channel) or @channels_id.keys.include?(channel)
          channel_id = nil
          if @channels_name.key?(channel) #it is an id
            channel_id = channel
            channel = @channels_name[channel_id]
          elsif @channels_id.key?(channel) #it is a channel name
            channel_id = @channels_id[channel]
          end

          channel_id = dest if channel_id.to_s == ''
          @routines[@channel_id] = {} unless @routines.key?(@channel_id)
          @routines[@channel_id][name] = { channel_name: config.channel, creator_team_id: user.team_id, creator: from, creator_id: user.id, status: :on,
                                           every: every, every_in_seconds: every_in_seconds, at: at, dayweek: dayweek, daymonth: daymonth, file_path: file_path,
                                           command: command_to_run.to_s.strip, silent: silent,
                                           next_run: next_run.to_s, dest: channel_id, last_run: "", last_elapsed: "",
                                           running: false, routine_type: routine_type}
          update_routines()
          respond "Added routine *`#{name}`* to the channel", dest
          create_routine_thread(name, @routines[@channel_id][name])
        end
        unreact :running
      end
    else
      respond "Only admin users can use this command", dest
    end
  else
    respond "Only master admin users can add files to routines", dest
  end
end

#add_shortcut(dest, user, typem, for_all, shortcut_name, command, command_to_run, global) ⇒ Object

help: ---------------------------------------------- help: >https://github.com/MarioRuiz/slack-smart-bot#shortcuts|SHORTCUTS help: add shortcut NAME: COMMAND help: add sc NAME: COMMAND help: add shortcut for all NAME: COMMAND help: add sc for all NAME: COMMAND help: shortcut NAME: COMMAND help: shortcut for all NAME: COMMAND help: add global sc for all NAME: COMMAND help: It will add a shortcut that will execute the command we supply. help: In case we supply 'for all' then the shorcut will be available for everybody help: If 'global' or 'generic' supplied and in Master channel then the shortcut will be available in all Bot channels. help: If you want to use a shortcut as a inline shortcut inside a command you can do it by adding a $ fex: !run tests $cust1 help: Example: help: add shortcut for all Spanish account: code require 'iso/iban'; 10.times ISO::IBAN.random('ES') help: Then to call this shortcut: help: sc spanish account help: shortcut Spanish Account help: Spanish Account help: help: command_id: :add_shortcut help:



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/slack/smart-bot/commands/on_bot/add_shortcut.rb', line 25

def add_shortcut(dest, user, typem, for_all, shortcut_name, command, command_to_run, global)
  save_stats(__method__)
  unless typem == :on_extended
    from = user.name
    team_id_user = user.team_id + '_' + from
    if has_access?(__method__, user)
      shortcut_name.strip!
      if global
        if !config.on_master_bot or typem != :on_master
          respond "It is only possible to add global shortcuts from Master channel"
        else
          @shortcuts_global[team_id_user] = Hash.new() unless @shortcuts_global.keys.include?(team_id_user)
          found_other = false
          if for_all.to_s != ""
            @shortcuts_global.each { |sck, scv|
              if sck != :all and sck != team_id_user and scv.key?(shortcut_name)
                found_other = true
              end
            }
          end
          if @shortcuts_global[:all].include?(shortcut_name) or @shortcuts_global[team_id_user].include?(shortcut_name)
            respond "Global shortcut name already in use. Please use another shortcut name."
          elsif found_other
            respond "You cannot create a global shortcut for all with the same name than other user is using."
          elsif !@shortcuts_global[team_id_user].include?(shortcut_name)
            #new shortcut
            @shortcuts_global[team_id_user][shortcut_name] = command_to_run
            @shortcuts_global[:all][shortcut_name] = command_to_run if for_all.to_s != ""
            update_shortcuts_file()
            respond "global shortcut added"
          else
            respond "Not possible to add the global shortcut" #todo: check if this is ever gonna be the case
          end
        end
      else
        @shortcuts[team_id_user] = Hash.new() unless @shortcuts.keys.include?(team_id_user)

        found_other = false
        if for_all.to_s != ""
          @shortcuts.each { |sck, scv|
            if sck != :all and sck != team_id_user and scv.key?(shortcut_name)
              found_other = true
            end
          }
        end
        if !is_admin?(user) and @shortcuts[:all].include?(shortcut_name) and !@shortcuts[team_id_user].include?(shortcut_name)
          respond "Only the creator of the shortcut can modify it", dest
        elsif found_other
          respond "You cannot create a shortcut for all with the same name than other user is using", dest
        elsif !@shortcuts[team_id_user].include?(shortcut_name)
          #new shortcut
          @shortcuts[team_id_user][shortcut_name] = command_to_run
          @shortcuts[:all][shortcut_name] = command_to_run if for_all.to_s != ""
          update_shortcuts_file()
          respond "shortcut added", dest
        else
          #are you sure? to avoid overwriting existing
          if answer.empty?
            ask("The shortcut already exists, are you sure you want to overwrite it?", command, team_id_user, dest)
          else
            case answer
            when /^(yes|yep)/i
              @shortcuts[team_id_user][shortcut_name] = command_to_run
              @shortcuts[:all][shortcut_name] = command_to_run if for_all.to_s != ""
              update_shortcuts_file()
              respond "shortcut added", dest
              answer_delete(team_id_user)
            when /^no/i
              respond "ok, I won't add it", dest
              answer_delete(team_id_user)
            else
              ask "I don't understand, yes or no?", command, team_id_user, dest
            end
          end
        end
      end
    end
  end
end

#add_vacation(user, type, from, to) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/slack/smart-bot/commands/general/add_vacation.rb', line 3

def add_vacation(user, type, from, to)
  save_stats(__method__)
  get_vacations()
  from.gsub!('-','/')
  to.gsub!('-','/')
  if type.match?(/sick\s+baby/i) or type.match?(/sick\s+child/i)
    type = 'sick child'
  end
  team_id_user = "#{user.team_id}_#{user.name}"
  if @vacations.key?(team_id_user) and @vacations[team_id_user][:public_holidays].to_s != ""
    country_region = @vacations[team_id_user][:public_holidays].downcase
  elsif config[:public_holidays].key?(:default_calendar)
    country_region = config[:public_holidays][:default_calendar].downcase
  else
    country_region = ''
  end

  local_day_time = local_time(country_region)
  if local_day_time.nil?
    today = Date.today
  else
    today = local_day_time.to_date
  end

  if from=='today'
    from = today.strftime("%Y/%m/%d")
  elsif from =='tomorrow'
    from = (today+1).strftime("%Y/%m/%d")
  elsif from.match?(/next\s+week/)
    from = today + ((8 - today.wday) % 7)
    from += 7 if from == today
    to = (from + 6).strftime("%Y/%m/%d")
    from = from.strftime("%Y/%m/%d")
  end

  to = from if to.empty?
  wrong = false
  begin
    from_date = Date.parse(from)
    to_date = Date.parse(to)
  rescue
    wrong = true
    respond "It seems like the date is not in the correct format: YYYY/MM/DD or is a wrong date."      
  end
  unless wrong
    if Date.parse(from).strftime("%Y/%m/%d") != from
      respond "It seems like the date #{from} is not in the correct format: YYYY/MM/DD or is a wrong date."
    elsif Date.parse(to).strftime("%Y/%m/%d") != to
      respond "It seems like the date #{to} is not in the correct format: YYYY/MM/DD or is a wrong date."
    else
      vacations = @vacations.deep_copy
      vacations[team_id_user] ||= { user_id: user.id, periods: [] }
      if !vacations[team_id_user].key?(:periods)
        vacations[team_id_user][:user_id] = user.id
        vacations[team_id_user][:periods] = []
      end

      if vacations[team_id_user].periods.empty?
        vacation_id = 1
      else
        vacation_id = vacations[team_id_user].periods[-1].vacation_id + 1
      end
      vacations[team_id_user].periods << { vacation_id: vacation_id, type: type.downcase, from: from, to: to }
      update_vacations({team_id_user => vacations[team_id_user]})
      respond "Period has been added   ##{vacation_id}"
      check_vacations(date: today, team_id: user.team_id, user: user.name, set_status: true, only_first_day: false)
    end
  end
end

#allow_access(user, command_id, opt) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/slack/smart-bot/commands/general/allow_access.rb', line 2

def allow_access(user, command_id, opt)
  save_stats(__method__)
  not_allowed = ["hi_bot", "bye_bot", "allow_access", "deny_access", "get_bot_logs", "add_routine", "pause_bot", "pause_routine", "remove_routine", "run_routine", "start_bot",
                 "start_routine", "delete_message", "update_message", "send_message", "kill_bot_on_channel", "exit_bot", "notify_message", "publish_announcements", "set_general_message",
                 "set_maintenance", "bot_help", "bot_rules"]
  if !is_admin?(user)
    respond "Only admins of this channel can use this command. Take a look who is an admin of this channel by calling `see admins`"
  elsif Thread.current[:dest][0] == "D"
    respond "This command cannot be called from a DM"
  elsif not_allowed.include?(command_id)
    respond "Sorry but the access for `#{command_id}` cannot be changed."
  else
    if Thread.current[:typem] == :on_call
      channel = Thread.current[:dchannel]
    elsif Thread.current[:using_channel].to_s == ""
      channel = Thread.current[:dest]
    else
      channel = Thread.current[:using_channel]
    end
    command_ids = get_command_ids()
    if command_ids.values.flatten.include?(command_id)
      wrong_user = false
      access_users = []
      opt.each do |o|
        if o.match(/\A\s*<@([^>]+)>\s*\z/)
          access_users << $1
        else
          respond "Hmm, I've done some research on this and it looks like #{o} is not a valid Slack user.\nMake sure you are writing @USER and it is recognized by *Slack*\n"
          wrong_user = true
          break
        end
      end
      unless wrong_user
        if !@access_channels.key?(channel)
          @access_channels[channel] = {}
        end

        if access_users.empty? # all users will be able to access
          @access_channels[channel].delete(command_id)
        else
          if @access_channels.key?(channel) and !@access_channels[channel].key?(command_id)
            @access_channels[channel][command_id] = []
          end
          access_users_names = []
          access_team_id_users_names = []
          access_users.each do |us|
             = find_user(us)
            access_users_names << .name unless .nil?
            access_team_id_users_names << "#{.team_id}_#{.name}" unless .nil?
          end
          @access_channels[channel][command_id] += access_team_id_users_names
          @access_channels[channel][command_id].flatten!
          @access_channels[channel][command_id].uniq!
          @access_channels[channel][command_id].delete(nil)
        end
        update_access_channels()
        if !@access_channels[channel].key?(command_id)
          respond "All users will have access to this command on this channel."
        else
          # get the names of the users. The value of the hash is the team_id_user_name
          names = @access_channels[channel][command_id].map { |u| u.split("_")[1..-1].join("_") }
          respond "These users will have access to this command on this channel: <@#{names.join(">, <@")}>"
        end
      end
    else
      respond "It seems like #{command_id} is not valid. Please be sure that exists by calling `see command ids`"
    end
  end
end

#answer(user = Thread.current[:user], dest = Thread.current[:dest]) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/slack/smart-bot/utils/answer.rb', line 2

def answer(user = Thread.current[:user], dest = Thread.current[:dest])
    if user.is_a?(String)
        if user.match?(/^[A-Z0-9]{7,11}_/)
            from = user
        else
            from = "#{config.team_id}_#{user}"
        end
    else
        from = "#{user.team_id}_#{user.name}"
    end
    if @answer.key?(from)
        if Thread.current[:on_thread]
            dest = Thread.current[:thread_ts]
        end
        if @answer[from].key?(dest)
            return @answer[from][dest]
        else
            return ''
        end
    else
        return ''
    end
end

#answer_delete(user = Thread.current[:user], dest = Thread.current[:dest]) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/slack/smart-bot/utils/answer_delete.rb', line 2

def answer_delete(user = Thread.current[:user], dest = Thread.current[:dest])
    if user.is_a?(String)
        if user.match?(/^[A-Z0-9]{7,11}_/)
            from = user
        else
            from = "#{config.team_id}_#{user}"
        end
    else
        from = "#{user.team_id}_#{user.name}"
    end
    if @answer.key?(from)
        if Thread.current[:on_thread]
            dest = Thread.current[:thread_ts]
        end
        if @answer[from].key?(dest)
            @answer[from].delete(dest)
        end
        @questions.delete(from) # to be backwards compatible #todo: remove when 2.0
    end
end

#ask(question, context = nil, to = nil, dest = nil) ⇒ Object

context: previous message to: user that should answer



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/slack/smart-bot/comm/ask.rb', line 5

def ask(question, context = nil, to = nil, dest = nil)
  begin
    if dest.nil? and Thread.current.key?(:dest)
      dest = Thread.current[:dest]
    end
    if to.nil?
      to = Thread.current[:team_id_user]
    end
    if context.nil?
      context = Thread.current[:command]
    end
    #remove the team_id from the user to
    if to.is_a?(String)
      if to.match?(/^[A-Z0-9]{7,11}_/)
        to_name = to.split('_')[1..-1].join('_')
      else
        to_name = to
        to = "#{config.team_id}_#{to}"
      end
    else
      to_name = to.name
      to = "#{to.team_id}_#{to.name}"
    end
    message = "#{to_name}: #{question}"
    if dest.nil?
      if config[:simulate]
        open("#{config.path}/buffer_complete.log", "a") { |f|
          f.puts "|#{@channel_id}|#{Thread.current[:thread_ts]}|#{config[:nick_id]}|#{config[:nick]}|#{message}~~~"
        }
      else
        if Thread.current[:on_thread]
          client.message(channel: @channel_id, text: message, as_user: true, thread_ts: Thread.current[:thread_ts])
        else
          client.message(channel: @channel_id, text: message, as_user: true)
        end
      end
      if config[:testing] and config.on_master_bot and !@buffered
        @buffered = true
        open("#{config.path}/buffer.log", "a") { |f|
          f.puts "|#{@channel_id}|#{Thread.current[:thread_ts]}|#{config[:nick_id]}|#{config[:nick]}|#{message}"
        }
      end
    elsif dest[0] == "C" or dest[0] == "G" # channel
      if config[:simulate]
        open("#{config.path}/buffer_complete.log", "a") { |f|
          f.puts "|#{dest}|#{Thread.current[:thread_ts]}|#{config[:nick_id]}|#{config[:nick]}|#{message}~~~"
        }
      else
        if Thread.current[:on_thread]
          client.message(channel: dest, text: message, as_user: true, thread_ts: Thread.current[:thread_ts])
        else
          client.message(channel: dest, text: message, as_user: true)
        end
      end
      if config[:testing] and config.on_master_bot and !@buffered
        @buffered = true
        open("#{config.path}/buffer.log", "a") { |f|
          f.puts "|#{dest}|#{Thread.current[:thread_ts]}|#{config[:nick_id]}|#{config[:nick]}|#{message}"
        }
      end
    elsif dest[0] == "D" #private message
      send_msg_user(dest, message)
    end
    if Thread.current[:on_thread]
      qdest = Thread.current[:thread_ts]
    else
      qdest = dest
    end
    @answer[to] = {} unless @answer.key?(to)
    @answer[to][qdest] = context
    @questions[to] = context # to be backwards compatible #todo remove it when 2.0
  rescue Exception => stack
    @logger.warn stack
  end
end

#bot_help(user, from, dest, dchannel, specific, help_command, rules_file, savestats: true, strict: false, send_to_file: false, return_output: false) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/slack/smart-bot/commands/general/bot_help.rb', line 2

def bot_help(user, from, dest, dchannel, specific, help_command, rules_file, savestats: true, strict: false, send_to_file: false, return_output: false)
  save_stats(__method__) if savestats
  output = []
  if has_access?(__method__, user)
    help_found = false
    message = ""
    if help_command.to_s != ""
      help_command = "" if help_command.to_s.match?(/^\s*expanded\s*$/i) or help_command.to_s.match?(/^\s*extended\s*$/i)
      expanded = true
      message_not_expanded = ""
    else
      expanded = false
      message_not_expanded = "If you want to see the *expanded* version of *`bot help`* or *`bot rules`*, please call *`bot help expanded`* or *`bot rules expanded`*\n"
      message_not_expanded += "Also to get specific *expanded* help for a specific command or rule call *`bot help COMMAND`*\n"
    end
    expanded = true if Thread.current[:prompt].to_s != ""
    if send_to_file
      expanded = true
      message_not_expanded = ""
    end
    help_message = get_help(rules_file, dest, user, specific, expanded)
    commands = []
    commands_search = []
    if help_command.to_s != ""
      help_command.gsub!("?", "\\?") # for open ai commands
      help_message.gsub(/====+/, "-" * 30).split(/^\s*-------*$/).each do |h|
        if strict
          if h.match?(/`#{help_command}`/i) or h.match?(/^\s*command_id:\s+:#{help_command.gsub(" ", "_")}\s*$/)
            output << h
            help_found = true
            commands << h
            break
          end
        else
          if h.match?(/[`_]#{help_command}/i) or h.match?(/^\s*command_id:\s+:#{help_command.gsub(" ", "_")}\s*$/)
            output << h
            help_found = true
            commands << h
          elsif !h.match?(/\A\s*\*/) and !h.match?(/\A\s*=+/) #to avoid general messages for bot help *General commands...*
            all_found = true
            help_command.to_s.split(" ") do |hc|
              unless hc.match?(/^\s*\z/)
                if !h.match?(/#{hc}/i)
                  all_found = false
                end
              end
            end
          end
          commands_search << h if all_found
        end
      end
    else
      if Thread.current[:using_channel] != ""
        message += "*You are using rules from another channel: <##{Thread.current[:using_channel]}>. These are the specific commands for that channel:*"
      end
      output << message
    end

    if (help_command.to_s == "")
      help_message.split(/^\s*=========*$/).each do |h|
        unless h.match?(/\A\s*\z/)
          output << "#{"=" * 35}\n#{h}"
        end
      end
      if Thread.current[:typem] == :on_pg or Thread.current[:typem] == :on_pub
        if @bots_created.size > 0
          txt = "\nThese are the *SmartBots* running on this Slack workspace: *<##{@master_bot_id}>, <##{@bots_created.keys.join(">, <#")}>*\n"
          txt += "Join one channel and call *`bot rules`* to see specific commands for that channel or *`bot help`* to see all commands for that channel.\n"
          output << txt
        end
      end
    else
      if commands.size < 10 and help_command.to_s != "" and commands_search.size > 0
        commands_search.shuffle!
        (10 - commands.size).times do |n|
          unless commands_search[n].nil?
            output << commands_search[n]
            help_found = true
          end
        end
      end
      unless help_found
        if specific
          output << "I didn't find any rule with `#{help_command}`"
        else
          output << "I didn't find any command with `#{help_command}`"
        end
      end
    end

    if specific
      unless rules_file.empty?
        begin
          eval(File.new(config.path + rules_file).read) if File.exist?(config.path + rules_file)
        end
      end
      if defined?(git_project) && (git_project.to_s != "") && (help_command.to_s == "")
        output << "Git project: #{git_project}"
      else
        def git_project
          ""
        end

        def project_folder
          ""
        end
      end
    elsif help_command.to_s == ""
      output << "Slack Smart Bot Github project: https://github.com/MarioRuiz/slack-smart-bot"
    end
    unless expanded
      output << message_not_expanded
    end
  end
  if output.join("\n").lines.count > 50 and dest[0] != "D" and !send_to_file
    dest = :on_thread
    output.unshift("Since there are many lines returned the results are returned on a thread by default.")
  end
  if send_to_file
    content = output.join("\n\n")
    content.gsub!(/\*<([^>]*)\|([^>]*)>\*/, '## [\2](\1)')
    content.gsub!(/^\s*(\*.+\*)\s*$/, '# \1')
    content.gsub!(/command_id:\s+:/, "### :")
    content = content.gsub("\n", "  \n").gsub(/\|[\w\s]*>/i, ">").gsub(/^\s*\-\-\-\-\-\-/, "\n------")
    dest == :on_thread ? dest_file = dchannel : dest_file = dest
    send_file(dest_file, "SmartBot Help", "", "smartbot_help.md", "text/markdown", "markdown", content: content)
  elsif return_output
    output.each do |h|
      h.gsub!(/^\s*command_id:\s+:\w+\s*$/, "")
      h.gsub!(/^\s*>.+$/, "") if help_command.to_s != ""
    end
    return output
  else
    output.each do |h|
      msg = h.gsub(/^\s*command_id:\s+:\w+\s*$/, "")
      msg.gsub!(/^\s*>.+$/, "") if help_command.to_s != ""
      unless msg.match?(/\A\s*\z/)
        respond msg, dest, unfurl_links: false, unfurl_media: false
      end
    end
  end
  return output.join("\n")
end

#bot_rules(dest, help_command, typem, rules_file, user, send_to_file: false, savestats: true, return_output: false) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/slack/smart-bot/commands/on_extended/bot_rules.rb', line 2

def bot_rules(dest, help_command, typem, rules_file, user, send_to_file: false, savestats: true, return_output: false)
  save_stats(__method__) if savestats
  if has_access?(__method__, user)
    if typem == :on_extended or typem == :on_call #for the other cases above.
      output = []
      if help_command.to_s != ""
        help_command = "" if help_command.to_s.match?(/^\s*expanded\s*$/i) or help_command.to_s.match?(/^\s*extended\s*$/i)
        expanded = true
      else
        expanded = false
      end
      expanded = true if send_to_file

      help_filtered = get_help(rules_file, dest, user, true, expanded)

      commands = []
      commands_search = []
      if help_command.to_s != ""
        help_found = false
        help_filtered.split(/^\s*-------*$/).each do |h|
          if h.match?(/[`_]#{help_command}/i)
            output << "*#{config.channel}*:#{h}"
            help_found = true
            commands << h
          elsif !h.match?(/\A\s*\*/) and !h.match?(/\A\s*=+/) #to avoid general messages for bot help *General rules...*
            all_found = true
            help_command.to_s.split(" ") do |hc|
              unless hc.match?(/^\s*\z/)
                if !h.match?(/#{hc}/i)
                  all_found = false
                end
              end
            end
            commands_search << h if all_found
          end
        end
        if commands.size < 10 and help_command.to_s != "" and commands_search.size > 0
          commands_search.shuffle!
          (10 - commands.size).times do |n|
            unless commands_search[n].nil?
              output << commands_search[n]
              help_found = true
            end
          end
        end
        unless help_found
          output << "*#{config.channel}*: I didn't find any command with `#{help_command}`"
        end
      else
        message = "-\n\n\n===================================\n*Rules from channel #{config.channel}*\n"
        if typem == :on_extended
          message += "To run the commands on this extended channel, add `!`, `!!` or `^` before the command.\n"
        end
        message += help_filtered
        output << message
      end

      unless rules_file.empty?
        begin
          eval(File.new(config.path + rules_file).read) if File.exist?(config.path + rules_file)
        end
      end
      if defined?(git_project) and git_project.to_s != "" and help_command.to_s == ""
        output << "Git project: #{git_project}"
      else
        def git_project() "" end
        def project_folder() "" end
      end
      unless expanded
        message_not_expanded = "If you want to see the *expanded* version of *`bot rules`*, please call  *`bot rules expanded`*\n"
        message_not_expanded += "Also to get specific *expanded* help for a specific command or rule call *`bot rules COMMAND`*\n"
        output << message_not_expanded
      end
      if output.join("\n").lines.count > 50 and dest[0] != "D"
        dest = :on_thread
        output.unshift("Since there are many lines returned the results are returned on a thread by default.")
      end
      if send_to_file
        content = output.join("\n\n")
        content.gsub!(/\*<([^>]*)\|([^>]*)>\*/, '## [\2](\1)')
        content.gsub!(/^\s*(\*.+\*)\s*$/, '# \1')
        content.gsub!(/command_id:\s+:/, "### :")
        content = content.gsub("\n", "  \n").gsub(/\|[\w\s]*>/i, ">").gsub(/^\s*\-\-\-\-\-\-/, "\n------")
        dest == :on_thread ? dest_file = dchannel : dest_file = dest
        send_file(dest_file, "SmartBot Rules", "", "smartbot_rules.md", "text/markdown", "markdown", content: content)
      elsif return_output
        output.each do |h|
          h.gsub!(/^\s*command_id:\s+:\w+\s*$/, "")
          h.gsub!(/^\s*>.+$/, "") if help_command.to_s != ""
        end
        return output
      else
        output.each do |h|
          msg = h.gsub(/^\s*command_id:\s+:\w+\s*$/, "")
          msg.gsub!(/^\s*>.+$/, "") if help_command.to_s != ""
          unless msg.match?(/\A\s*\z/)
            respond msg, dest, unfurl_links: false, unfurl_media: false
          end
        end
      end
    end
  end
end

#bot_stats(dest, from_user, typem, channel_id, from, to, user, st_command, exclude_masters, exclude_routines, exclude_command, monthly, all_data, members_channel, exclude_members_channel, header, regexp, type_group: '', only_graph: false) ⇒ Object

help: ---------------------------------------------- help: bot stats helpmaster: bot stats USER_NAME help: bot stats exclude masters help: bot stats exclude routines help: bot stats from YYYY/MM/DD help: bot stats from YYYY/MM/DD to YYYY/MM/DD help: bot stats CHANNEL help: bot stats CHANNEL from YYYY/MM/DD help: bot stats CHANNEL from YYYY/MM/DD to YYYY/MM/DD help: bot stats command COMMAND helpmaster: bot stats USER_NAME from YYYY/MM/DD to YYYY/MM/DD helpmaster: bot stats CHANNEL USER_NAME from YYYY/MM/DD to YYYY/MM/DD help: bot stats CHANNEL exclude masters from YYYY/MM/DD to YYYY/MM/DD help: bot stats HEADER /REGEXP/ help: bot stats members #CHANNEL help: bot stats exclude members #CHANNEL help: bot stats today help: bot stats yesterday help: bot stats last month help: bot stats this month help: bot stats last week help: bot stats this week help: bot stats exclude COMMAND_ID help: bot stats monthly help: bot stats alldata help: bot stats weekly graph help: To see the bot stats helpmaster: You can use this command only if you are a Master admin user and if you are in a private conversation with the bot, or you are on the Smartbot-stats channel helpmaster: You need to set stats to true to generate the stats when running the bot instance. help: members #CHANNEL will return stats for only members of the channel supplied help: exclude members #CHANNEL will return stats for only members that are not members of the channel supplied help: HEADER /REGEXP/ will return stats for only the rows that match the regexp on the stats header supplied help: If 'alldata' option supplied then it will be attached files including all data and not only the top 10. help: If 'graph' option supplied then it will be displaying only a graph. help: Examples: help: bot stats #sales helpmaster: bot stats @peter.wind help: bot stats #sales from 2019/12/15 to 2019/12/31 help: bot stats #sales today help: bot stats #sales from 2020-01-01 monthly help: bot stats exclude routines masters from 2021/01/01 monthly help: bot stats members #development from 2022/01/01 to 2022/01/31 help: bot stats type_message /(on_pub|on_pg)/ help: help: command_id: :bot_stats help:



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
# File 'lib/slack/smart-bot/commands/on_bot/general/bot_stats.rb', line 49

def bot_stats(dest, from_user, typem, channel_id, from, to, user, st_command, exclude_masters, exclude_routines, exclude_command, monthly, all_data, members_channel, exclude_members_channel, header, regexp, type_group: '', only_graph: false)
  require "csv"
  if config.stats
    message = []
  else
    message = ["You need to set stats to true to generate the stats when running the bot instance."]
  end
  save_stats(__method__)
  react :runner
  get_channels_name_and_id() unless @channels_name.keys.include?(dest) or dest[0] == "D"
  master_admin_users_id = @master_admin_users_id.dup
  if dest == @channels_id[config.stats_channel]
    #master_admin_users_id << user
    user = "" # for the case we are on the stats channel
  end
  if (from_user.id != user and
      (config.team_id_masters.include?("#{from_user.team_id}_#{from_user.name}") or master_admin_users_id.include?(from_user.id) or dest == @channels_id[config.stats_channel]) and
      (typem == :on_dm or dest[0] == "D" or dest == @channels_id[config.stats_channel]))
    on_dm_master = true #master admin user
  else
    on_dm_master = false
  end
  wrong = false
  exclude_channel_members = false
  include_channel_members = false
  members_list = []
  if exclude_members_channel != "" or members_channel != ""
    if members_channel != ""
      channel_members = members_channel
      include_channel_members = true
    else
      channel_members = exclude_members_channel
      exclude_channel_members = true
    end
    get_channels_name_and_id() unless @channels_id.keys.include?(channel_members)

    tm = get_channel_members(channel_members)
    if tm.nil? or tm.size == 0
      message << ":exclamation: Add the Smart Bot to *<##{channel_members}>* channel first."
      wrong = true
    else
      tm.each do |m|
         = find_user(m)
        members_list << .name unless .is_app_user or .is_bot
      end
    end
  end
  if header.size > 0
    headers = ["date", "bot_channel", "bot_channel_id", "dest_channel", "dest_channel_id", "type_message", "user_name", "user_id", "text", "command", "files", "time_zone", "job_title", "team_id"]
    header.each do |h|
      if !headers.include?(h.downcase)
        message << ":exclamation: Wrong header #{h}. It should be one of the following: #{headers.join(", ")}"
        wrong = true
      end
    end
    if regexp.size > 0
      regexp.each do |r|
        begin
          Regexp.new(r)
        rescue
          message << ":exclamation: Wrong regexp #{r}."
          wrong = true
        end
      end
    end
  end

  tzone_users = {}
  job_title_users = {}
  users_by_job_title = {}
  users_by_team = {}
  total_calls_by_team = {}
  unless wrong
    type_group = :monthly if only_graph and type_group == ""
    if on_dm_master or (from_user.id == user) # normal user can only see own stats
      if !File.exist?("#{config.stats_path}.#{Time.now.strftime("%Y-%m")}.log")
        message << "No stats"
      else
        from = "#{Time.now.strftime("%Y-%m")}-01" if from == ""
        to = "#{Time.now.strftime("%Y-%m-%d")}" if to == ""
        from_short = from
        to_short = to
        days = Date.parse(to_short) - Date.parse(from_short)
        weeks = days/7
        if type_group == :daily and days > 60
          message << ":exclamation: You can only see daily stats for a maximum of 60 days."
          wrong = true
        elsif type_group == :weekly and weeks > 60
          message << ":exclamation: You can only see weekly stats for a maximum of 60 weeks."
          wrong = true
        end
        unless wrong
          from_file = from[0..3] + "-" + from[5..6]
          to_file = to[0..3] + "-" + to[5..6]
          from += " 00:00:00 +0000"
          to += " 23:59:59 +0000"
          rows = []
          rows_group = {}
          users_group = {}
          commands_group = {}
          users_id_name = {}
          users_name_id = {}
          count_users = {}
          count_channels_dest = {}
          # to translate global and enterprise users since sometimes was returning different names/ids
          #if from[0..3]=='2020' # this was an issue only on that period
          Dir["#{config.stats_path}.*.log"].sort.each do |file|
            if file >= "#{config.stats_path}.#{from_file}.log" and file <= "#{config.stats_path}.#{to_file}.log"
              CSV.foreach(file, headers: true, header_converters: :symbol, converters: :numeric) do |row|
                unless users_id_name.key?(row[:user_id])
                  users_id_name[row[:user_id]] = row[:user_name]
                  users_name_id[row[:user_name]] = row[:user_id]
                end
              end
            end
          end
          #end
          if user != ""
             = find_user(user)
            if .nil? # for the case the user is populated from outside of slack
              user_name = user
              user_id = user
            else
              if users_id_name.key?(.id)
                user_name = users_id_name[.id]
              else
                user_name = .name
              end
              if users_name_id.key?(.name)
                user_id = users_name_id[.name]
              else
                user_id = .id
              end
            end
          end
          master_admins = config.masters.dup
          if users_id_name.size > 0
            config.masters.each do |u|
              if users_id_name.key?(u)
                master_admins << users_id_name[u]
              elsif users_name_id.key?(u)
                master_admins << users_name_id[u]
              end
            end
          end
          Dir["#{config.stats_path}.*.log"].sort.each do |file|
            if file >= "#{config.stats_path}.#{from_file}.log" and file <= "#{config.stats_path}.#{to_file}.log"
              CSV.foreach(file, headers: true, header_converters: :symbol, converters: :numeric) do |row|
                clean_user_name = row[:user_name].gsub('routine/','')
                if (include_channel_members and members_list.include?(clean_user_name)) or
                  (exclude_channel_members and !members_list.include?(clean_user_name)) or
                  (!include_channel_members and !exclude_channel_members)
                  row[:date] = row[:date].to_s
                  row[:team_id] = config.team_id if row[:team_id].to_s == ''
                  if row[:dest_channel_id].to_s[0] == "D"
                    row[:dest_channel] = "DM"
                  elsif row[:dest_channel].to_s == ""
                    row[:dest_channel] = row[:dest_channel_id]
                  end
                  if users_name_id.size > 0
                    row[:user_name] = users_id_name[row[:user_id]]
                    row[:user_id] = users_name_id[row[:user_name]]
                  else
                    users_id_name[row[:user_id]] ||= row[:user_name]
                  end
                  if !exclude_masters or (exclude_masters and !master_admins.include?(row[:user_name]) and
                                          !master_admins.include?(row[:user_id]) and
                                          !master_admin_users_id.include?(row[:user_id]))
                    if !exclude_routines or (exclude_routines and !row[:user_name].match?(/^routine\//))
                      unless header.empty?
                        add = true
                        header.each_with_index do |h, i|
                          if !row[h.downcase.to_sym].to_s.match?(/#{regexp[i]}/i)
                            add = false
                            break
                          end
                        end
                      end
                      if header.empty? or (header.size > 0 and add)
                        if exclude_command == "" or (exclude_command != "" and row[:command] != exclude_command)
                          if st_command == "" or (st_command != "" and row[:command] == st_command)
                            if row[:bot_channel_id] == channel_id or channel_id == "" or row[:dest_channel_id] == channel_id
                              if row[:date] >= from and row[:date] <= to
                                count_users[row[:user_id]] ||= 0
                                count_users[row[:user_id]] += 1
                                if user == "" or (user != "" and row[:user_name] == user_name) or (user != "" and row[:user_id] == user_id)
                                  rows << row.to_h
                                  count_channels_dest[row[:dest_channel]] ||= 0
                                  count_channels_dest[row[:dest_channel]] += 1
                                  if type_group != ''#Ja
                                  case type_group
                                    when :monthly
                                      group_range = row[:date][0..6]
                                    when :weekly
                                      group_range = Date.parse(row[:date]).strftime('%Y-%V')
                                    when :daily
                                      group_range = row[:date][0..9]
                                    when :yearly
                                      group_range = row[:date][0..3]
                                    end
                                    rows_group[group_range] = 0 unless rows_group.key?(group_range)
                                    users_group[group_range] = [] unless users_group.key?(group_range)
                                    commands_group[group_range] = [] unless commands_group.key?(group_range)
                                    rows_group[group_range] += 1
                                    users_group[group_range] << row[:user_id]
                                    commands_group[group_range] << row[:command]
                                  end
                                end
                              end
                            end
                          end
                        end
                      end
                    end
                  end
                end
              end
            end
          end
          total = rows.size
          if exclude_masters
            message << "Excluding master admins"
          end
          if exclude_routines
            message << "Excluding routines"
          end
          if exclude_command != ""
            message << "Excluding command #{exclude_command}"
          end
          if st_command != ""
            message << "Including only command #{st_command}"
          end
          if include_channel_members
            message << "Including only members of <##{members_channel}>"
          end
          if exclude_channel_members
            message << "Including only members that are not members of <##{exclude_members_channel}>"
          end
          if header.size > 0
            header.each_with_index do |h, i|
              message << "Including only #{h} that match /#{regexp[i]}/i"
            end
          end
          if user != ""
            if user == from_user.id
              message << "Bot stats for <@#{user}>"
            else
              message << "Showing only user <@#{user}>"
            end
          end
          if channel_id == ""
            message << "*Total calls*: #{total} from #{from_short} to #{to_short}"
          else
            message << "*Total calls <##{channel_id}>*: #{total} from #{from_short} to #{to_short}"
          end
          unless count_users.size == 0 or total == 0 or user == ""
            my_place = (count_users.sort_by(&:last).reverse.to_h.keys.index(user_id) + 1)
            message << "\tYou are the *\# #{my_place}* of *#{count_users.size}* users"
          end
          if total > 0
            if type_group != ''
              if on_dm_master
                message << "*Totals #{type_group} / commands / users (%new)*"
              else
                message << "*Totals #{type_group} / commands*"
              end

              all_users = []
              new_users = []
              rows_group.each do |k, v|
                if all_users.empty?
                  message_new_users = ""
                else
                  new_users = (users_group[k] - all_users).uniq
                  message_new_users = "(#{new_users.size * 100 / users_group[k].uniq.size}%)"
                end
                all_users += users_group[k]
                graph = ":large_yellow_square: " * (v.to_f * (10*rows_group.size) / total).round(2)
                if on_dm_master
                  message << "\t#{k}: #{graph} #{v} (#{(v.to_f * 100 / total).round(2)}%) / #{commands_group[k].uniq.size} / #{users_group[k].uniq.size} #{message_new_users}"
                else
                  message << "\t#{k}: #{graph} #{v} (#{(v.to_f * 100 / total).round(2)}%) / #{commands_group[k].uniq.size}"
                end
              end
            end

            if channel_id == ""
              message << "*SmartBots*"
              channels = rows.bot_channel.uniq.sort
              channels.each do |channel|
                count = rows.count { |h| h.bot_channel == channel }
                channel_info = @channels_list.select { |c| c.name.to_s.downcase == channel.to_s.downcase }[-1]
                if @channels_id.key?(channel) and !channel_info.is_private
                  c = "<##{@channels_id[channel]}>"
                else
                  c = channel
                end
                message << "\t#{c}: #{count} (#{(count.to_f * 100 / total).round(2)}%)"
              end
            end
            channels_dest_attachment = []
            count_channels_dest = count_channels_dest.sort_by(&:last).reverse.to_h
            if count_channels_dest.size > 10
              message << "*From Channel* - #{count_channels_dest.size} (Top 10)"
            else
              message << "*From Channel* - #{count_channels_dest.size}"
            end

            count_channels_dest.keys[0..9].each do |ch|
              channel_info = @channels_list.select { |c| c.name.to_s.downcase == ch.to_s.downcase }[-1]
              if @channels_id.key?(ch) and !channel_info.is_private
                c = "<##{@channels_id[ch]}>"
              else
                c = ch
              end
              message << "\t#{c}: #{count_channels_dest[ch]} (#{(count_channels_dest[ch].to_f * 100 / total).round(2)}%)"
            end
            if count_channels_dest.size > 10 and all_data
              count_channels_dest.each do |ch, value|
                channel_info = @channels_list.select { |c| c.name.to_s.downcase == ch.to_s.downcase }[-1]
                channels_dest_attachment << "\t##{ch}: #{value} (#{(value.to_f * 100 / total).round(2)}%)"
              end
            end

            team_ids = rows.team_id.uniq
            client_web = Slack::Web::Client.new(token: config.token)
            client_web.auth_test
            team_ids.each do |team_id|
              resp = client_web.team_info(team: team_id)
              team_name = resp.team.name
              users_by_team[team_name] ||= []
              users_by_team[team_name] += rows.select { |h| h.team_id == team_id }.map { |h| h.user_id }.uniq
              total_calls_by_team[team_name] ||= 0
              total_calls_by_team[team_name] += rows.count { |h| h.team_id == team_id }
            end
            client_web = nil

            #print users by team
            if users_by_team.size > 0
              total_users = rows.user_id.uniq.size
              message << "*Users by Space*"
              users_by_team.each do |team_name, users|
                message << "\t#{team_name}: #{users.size} (#{(users.size.to_f * 100 / total_users).round(2)}%)"
              end
            end

            #print total calls by user team
            if total_calls_by_team.size > 0
              message << "*Total calls by User Space*"
              total_calls_by_team.each do |team_name, total_calls|
                message << "\t#{team_name}: #{total_calls} (#{(total_calls.to_f * 100 / total).round(2)}%)"
              end
            end

            if !only_graph
              users_attachment = []
              if user == ""
                users = rows.user_id.uniq.sort
                if rows[0].key?(:time_zone) #then save_stats is saving the time zone already
                  rows.time_zone.each_with_index do |time_zone, idx|
                    unless time_zone == "" or rows.user_name[idx].match?(/^routine\//)
                      tzone_users[time_zone] ||= 0
                      tzone_users[time_zone] += 1
                    end
                  end
                else
                  rows.user_id.each_with_index do |usr, i|
                    if rows[i].values.size >= 12 #then save_stats is saving the time zone already but not all the data
                      unless rows[i].values[11] == "" or rows[i].values[11].match?(/^routine\//)
                        tzone_users[rows[i].values[11]] ||= 0
                        tzone_users[rows[i].values[11]] += 1
                      end
                    else
                       = find_user(usr)
                      unless .nil? or .is_app_user or .is_bot or .tz_label.to_s == "" or rows.user_name[i].match?(/^routine\//)
                        tzone_users[.tz_label] ||= 0
                        tzone_users[.tz_label] += 1
                      end
                    end
                  end
                end
                if rows[0].key?(:job_title) #then save_stats is saving the job title already
                  rows.job_title.each_with_index do |job_title, idx|
                    unless job_title.to_s == "" or rows.user_name[idx].match?(/^routine\//)
                      unless job_title_users.key?(job_title)
                        job_title = job_title.to_s.split.map { |x| x[0].upcase + x[1..-1] }.join(" ")
                        job_title_users[job_title] ||= 0
                        users_by_job_title[job_title] ||= []
                      end
                      job_title_users[job_title] += 1
                      users_by_job_title[job_title] << rows.user_name[idx]
                    end
                  end
                else
                  rows.user_id.each_with_index do |usr, i|
                    unless usr.include?("routine/")
                      if rows[i].values.size >= 13 #then save_stats is saving the job_title already but not all the data
                        unless rows[i].values[12].to_s == ""
                          if job_title_users.key?(rows[i].values[12].to_s)
                            job_title = rows[i].values[12]
                          else
                            job_title = rows[i].values[12].to_s.split.map { |x| x[0].upcase + x[1..-1] }.join(" ")
                            job_title_users[job_title] ||= 0
                            users_by_job_title[job_title] ||= []
                          end
                          job_title_users[job_title] += 1
                          users_by_job_title[job_title] << rows.user_name[i]
                        end
                      else
                         = find_user(usr)
                        unless .nil? or .is_app_user or .is_bot
                          if job_title_users.key?(.profile.title)
                            job_title = .profile.title
                          else
                            job_title = .profile.title.split.map { |x| x[0].upcase + x[1..-1] }.join(" ")
                          end
                          unless job_title.to_s == ""
                            job_title_users[job_title] ||= 0
                            job_title_users[job_title] += 1
                            users_by_job_title[job_title] ||= []
                            users_by_job_title[job_title] << rows.user_name[i]
                          end
                        end
                      end
                    end
                  end
                end
                users_by_job_title.each do |job_title, users|
                  users.uniq!
                end

                if users.size > 10
                  message << "*Users* - #{users.size} (Top 10)"
                else
                  message << "*Users* - #{users.size}"
                end
                count_user = {}
                users.each do |user|
                  count = rows.count { |h| h.user_id == user }
                  count_user[user] = count
                end
                i = 0
                total_without_routines = total
                count_user.sort_by { |k, v| -v }.each do |user, count|
                  i += 1
                  if user.include?("routine/")
                    user_link = users_id_name[user]
                    total_without_routines -= count
                  else
                    user_link = "<@#{user}>"
                  end
                  if i <= 10
                    message << "\t#{user_link}: #{count} (#{(count.to_f * 100 / total).round(2)}%)"
                  end
                  if users.size > 10 and all_data
                    users_attachment << "\t#{users_id_name[user]}: #{count} (#{(count.to_f * 100 / total).round(2)}%)"
                  end
                end
                if tzone_users.size > 0
                  message << "*Time Zones*"
                  total_known = 0
                  tzone_users.each do |tzone, num|
                    unless tzone.to_s == ""
                      abb_tzone = tzone.split.map{|i| i[0,1].upcase}.join
                      message << "\t#{abb_tzone} _#{tzone}_: #{num} (#{(num.to_f * 100 / total_without_routines).round(2)}%)"
                      total_known += num
                    end
                  end
                  total_unknown = total_without_routines - total_known
                  message << "\tUnknown: #{total_unknown} (#{(total_unknown.to_f * 100 / total_without_routines).round(2)}%)" if total_unknown > 0
                end
                if users.size > 0
                  if job_title_users.size > 10
                    message << "*Job Titles* - #{job_title_users.size} (Top 10)"
                  else
                    message << "*Job Titles* - #{job_title_users.size}"
                  end
                  total_known = 0
                  i = 0
                  job_title_users.sort_by { |k, v| -v }.each do |jtitle, num|
                    unless jtitle.to_s == ""
                      i += 1
                      if i <= 10
                        message << "\t#{jtitle}: #{num} (#{(num.to_f * 100 / total_without_routines).round(2)}%)"
                      end
                      total_known += num
                    end
                  end
                  total_unknown = total_without_routines - total_known
                  message << "\tUnknown: #{total_unknown} (#{(total_unknown.to_f * 100 / total_without_routines).round(2)}%)" if total_unknown > 0
                end
                if users.size > 0
                  if users_by_job_title.size > 10
                    message << "*Num Users by Job Title* (Top 10)"
                  else
                    message << "*Num Users by Job Title*"
                  end
                  i = 0
                  users_size_without_routines = users.delete_if { |u| u.include?("routine/") }.size
                  users_by_job_title.sort_by { |k, v| -v.size }.each do |jtitle, usersj|
                    i += 1
                    if i <= 10
                      message << "\t#{jtitle}: #{usersj.size} (#{(usersj.size.to_f * 100 / users_size_without_routines).round(2)}%)"
                    end
                  end
                end
              end
              commands_attachment = []

              if st_command == ""
                commands = rows.command.uniq.sort
                count_command = {}
                commands.each do |command|
                  count = rows.count { |h| h.command == command }
                  count_command[command] = count
                end

                if commands.size > 10
                  message << "*Commands* - #{commands.size} (Top 10)"
                else
                  message << "*Commands* - #{commands.size}"
                end

                i = 0
                count_command.sort_by { |k, v| -v }.each do |command, count|
                  i += 1
                  if i <= 10
                    message << "\t#{command}: #{count} (#{(count.to_f * 100 / total).round(2)}%)"
                  end
                  if commands.size > 10 and all_data
                    commands_attachment << "\t#{command}: #{count} (#{(count.to_f * 100 / total).round(2)}%)"
                  end
                end
              end

              message << "*Message type*"
              types = rows.type_message.uniq.sort
              types.each do |type|
                count = rows.count { |h| h.type_message == type }
                message << "\t#{type}: #{count} (#{(count.to_f * 100 / total).round(2)}%)"
              end

              if on_dm_master
                message << "*Last activity*: #{rows[-1].date} #{rows[-1].bot_channel} #{rows[-1].type_message} #{rows[-1].user_name} #{rows[-1].command}"
              end
              if users_attachment.size > 0
                send_file(dest, "", "users.txt", "", "text/plain", "text", content: users_attachment.join("\n"))
              end
              if commands_attachment.size > 0
                send_file(dest, "", "commands.txt", "", "text/plain", "text", content: commands_attachment.join("\n"))
              end
              if channels_dest_attachment.size > 0
                send_file(dest, "", "channels_dest.txt", "", "text/plain", "text", content: channels_dest_attachment.join("\n"))
              end
            end
          end
        end
      end
    else
      message << "Only Master admin users on a private conversation with the bot can see this kind of bot stats."
    end
  end
  unreact :runner
  respond "#{message.join("\n")}", dest
end

#bot_status(dest, user) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: bot status helpadmin: Displays the status of the bot helpadmin: If on master channel and admin user also it will display info about bots created helpadmin: helpadmin: command_id: :bot_status helpadmin:



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/slack/smart-bot/commands/on_bot/general/bot_status.rb', line 9

def bot_status(dest, user)
  save_stats(__method__)
  get_bots_created()
  if has_access?(__method__, user)
    gems_remote = `gem list slack-smart-bot --remote`
    version_remote = gems_remote.to_s().scan(/slack-smart-bot \((\d+\.\d+\.\d+)/).join
    version_message = ""
    if Gem::Version.new(version_remote) > Gem::Version.new(VERSION)
      version_message = " There is a new available version: #{version_remote}."
    end
    require "socket"
    ip_address = Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address
    respond "*#{Socket.gethostname} (#{ip_address})*\n\tStatus: #{@status}.\n\tVersion: #{VERSION}.#{version_message}\n\tRules file: #{File.basename config.rules_file}\n\tExtended: #{@bots_created[@channel_id][:extended] unless config.on_master_bot}\n\tAdmins: #{config.admins}\n\tBot time: #{Time.now}", dest
    if @status == :on
      #@listening.keys delete :threads key
      listening_keys = @listening.keys - [:threads]
      #remove team id from keys, key is a symbol
      listening_keys = listening_keys.map{|k| k.to_s.gsub(user.team_id+"_",'').to_sym}
      respond "I'm listening to [#{listening_keys.join(", ")}]", dest
      if config.on_master_bot and config.team_id_admins.include?("#{user.team_id}_#{user.name}")
        sleep 5
        @bots_created.each do |k, v|
          msg = []
          msg << "`#{v[:channel_name]}` (#{k}):"
          msg << "\tcreator: #{v[:creator_name]}"
          msg << "\tadmins: #{v[:admins]}"
          msg << "\tstatus: #{v[:status]} #{" *(not responded)*" unless @pings.include?(v[:channel_name])}"
          msg << "\tcreated: #{v[:created]}"
          msg << "\trules: #{v[:rules_file]}"
          msg << "\textended: #{v[:extended]}"
          msg << "\tcloud: #{v[:cloud]}"
          if config.on_master_bot and v.key?(:cloud) and v[:cloud]
            msg << "\trunner: `ruby #{config.file} \"#{v[:channel_name]}\" \"#{v[:admins]}\" \"#{v[:rules_file]}\" on&`"
          end
          respond msg.join("\n"), dest
        end
        @pings = []
      end
    end
  end
end

#build_help(path, expanded) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/slack/smart-bot/utils/build_help.rb', line 3

def build_help(path, expanded)
  help_message = {normal: {}, admin: {}, master: {}}
  if Dir.exist?(path)
    files = Dir["#{path}/*"]
  elsif File.exist?(path)
    files = [path]
  else
    return help_message
  end

  files.each do |t|
    if Dir.exist?(t)
      res = build_help(t, expanded)
      help_message[:master][t.scan(/\/(\w+)$/).join.to_sym] = res[:master]
      help_message[:admin][t.scan(/\/(\w+)$/).join.to_sym] = res[:admin]
      help_message[:normal][t.scan(/\/(\w+)$/).join.to_sym] = res[:normal]
    else
      lines = IO.readlines(t, encoding: 'UTF-8')
      data = {master:{}, admin:{}, normal:{}}
      data.master = lines.join #normal user help
      data.admin = lines.reject {|l| l.match?(/^\s*#\s*help\s*master\s*:.+$/i)}.join #not master help
      data.normal = lines.reject {|l| l.match?(/^\s*#\s*help\s*(admin|master)\s*:.+$/i)}.join #not admin or master help
      if expanded
        help_message[:master][t.scan(/\/(\w+)\.rb$/).join.to_sym] = data.master.scan(/#\s*help\s*\w*:(.*)/i).join("\n")
        help_message[:admin][t.scan(/\/(\w+)\.rb$/).join.to_sym] = data.admin.scan(/#\s*help\s*\w*:(.*)/i).join("\n")
        help_message[:normal][t.scan(/\/(\w+)\.rb$/).join.to_sym] = data.normal.scan(/#\s*help\s*\w*:(.*)/i).join("\n") 
      else
        data.keys.each do |key|
          res = data[key].scan(/#\s*help\s*\w*:(.*)/i).join("\n")
          resf = ""
          command_done = false
          explanation_done = false
          example_done = false
          
          res.split("\n").each do |line|
            if line.match?(/^\s*======+$/)
              command_done = true
              explanation_done = true
              example_done = true
            elsif line.match?(/^\s*\-\-\-\-+\s*$/i)
              resf += "\n#{line}"
              command_done = false
              explanation_done = false
              example_done = false
            elsif !command_done and line.match?(/^\s*`.+`\s*/i)
              resf += "\n#{line}"
              command_done = true
            elsif !explanation_done and line.match?(/^\s+[^`].+\s*/i)
              resf += "\n#{line}"
              explanation_done = true
            elsif !example_done and line.match?(/^\s*>?\s*_.+_\s*$/i)
              resf += "\n     Example: #{line.gsub(/^\s*>/,'')}"
              example_done = true
            end
          end
          resf += "\n\n"
          help_message[key][t.scan(/\/(\w+)\.rb$/).join.to_sym] = resf
        end
      end
    end
  end
  return help_message
end

#bye_bot(dest, user, display_name) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/slack/smart-bot/commands/general/bye_bot.rb', line 3

def bye_bot(dest, user, display_name)
  user_name = user.name
  team_id = user.team_id 
  team_id_user = team_id + "_" + user_name

  if @status == :on
    save_stats(__method__)
    bye = ["Bye", "", "Good Bye", "Adiós", "Ciao", "Bless", "Bless bless", "Adeu"].sample
    respond "#{bye} #{display_name}", dest

    if @listening.key?(team_id_user)
      if Thread.current[:on_thread]
        @listening[team_id_user].delete(Thread.current[:thread_ts])
      else
        @listening[team_id_user].delete(dest)
      end
      @listening.delete(team_id_user) if @listening[team_id_user].empty?
    end
  end
end

#check_vacations(date: Date.today, team_id: nil, user: nil, set_status: true, only_first_day: true) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/slack/smart-bot/utils/check_vacations.rb', line 2

def check_vacations(date: Date.today, team_id: nil, user: nil, set_status: true, only_first_day: true)
  get_vacations()
  if user.nil?
    users = @vacations.keys
  else
    users = ["#{team_id}_#{user}"]
  end
  on_vacation = []    
  
  users.each do |user|
    type = nil
    expiration = nil

    if date.nil?
      if @vacations.key?(user) and @vacations[user][:public_holidays].to_s != ""
        country_region = @vacations[user][:public_holidays].downcase
      elsif config[:public_holidays].key?(:default_calendar)
        country_region = config[:public_holidays][:default_calendar].downcase
      else
        country_region = ''
      end
  
      local_day_time = local_time(country_region)
      if local_day_time.nil?
        date = Date.today
      else
        date = local_day_time.to_date
      end
    end
    @vacations[user].periods ||= []
    @vacations[user].periods.each do |p|
      if only_first_day and p.from == date.strftime("%Y/%m/%d")
        type = p.type
        on_vacation << user
        expiration = p.to
        break
      elsif !only_first_day and p.from <= date.strftime("%Y/%m/%d") and p.to >= date.strftime("%Y/%m/%d")
        type = p.type
        on_vacation << user
        expiration = p.to
        break
      end
    end
    unless type.nil? or !set_status
      icon = ''
      if type == 'vacation'
        icon = ':palm_tree:'
      elsif type == 'sick'
        icon = ':face_with_thermometer:'
      elsif type == 'sick child'
        icon = ':baby:'
      end
      unless icon.empty?
        expiration_date = Date.parse(expiration,'%Y/%m/%d') + 1 #next day at 0:00
        set_status(@vacations[user].user_id, status: icon, expiration: expiration_date, message: "#{type} until #{expiration}")
      end
    end
  end
  return on_vacation
end

#create_bot(dest, user, type, channel) ⇒ Object

helpmaster: ---------------------------------------------- helpmaster: create bot on CHANNEL_NAME helpmaster: create cloud bot on CHANNEL_NAME helpmaster: create silent bot on CHANNEL_NAME helpmaster: Creates a new bot on the channel specified helpmaster: It will work only if you are on Master channel helpmaster: The admins will be the master admins, the creator of the bot and the creator of the channel helpmaster: Follow the instructions in case creating cloud bots helpmaster: In case 'silent' won't display the Bot initialization message on the CHANNEL_NAME helpmaster: helpmaster: command_id: :create_bot helpmaster:



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/slack/smart-bot/commands/on_master/create_bot.rb', line 14

def create_bot(dest, user, type, channel)
  cloud = type.include?('cloud')
  silent = type.include?('silent')
  save_stats(__method__)
  from = user.name
  if has_access?(__method__, user)
    if config.on_master_bot
      get_channels_name_and_id() unless @channels_name.keys.include?(channel) or @channels_id.keys.include?(channel)
      channel_id = nil
      if @channels_name.key?(channel) #it is an id
        channel_id = channel
        channel = @channels_name[channel_id]
      elsif @channels_id.key?(channel) #it is a channel name
        channel_id = @channels_id[channel]
      end
      #todo: add pagination for case more than 1000 channels on the workspace
      channels = get_channels()
      channel = @channels_name[channel] if @channels_name.key?(channel)
      channel_found = channels.detect { |c| c.name == channel }
      members = get_channel_members(@channels_id[channel]) unless channel_found.nil?

      if channel_id.nil?
        respond "There is no channel with that name: #{channel}, please be sure is written exactly the same", dest
      elsif channel == config.master_channel
        respond "There is already a bot in this channel: #{channel}", dest
      elsif @bots_created.keys.include?(channel_id)
        respond "There is already a bot in this channel: #{channel}, kill it before", dest
      elsif config[:nick_id] != channel_found.creator and !members.include?(config[:nick_id])
        respond "You need to add first to the channel the smart bot user: <@#{config[:nick_id]}>", dest
      else
        if channel_id != config[:channel]
          begin
            rules_file = "slack-smart-bot_rules_#{channel_id}_#{from.gsub(" ", "_")}.rb"
            if defined?(RULES_FOLDER) # consider removing RULES_FOLDER since we are not using it anywhere else
              rules_file = RULES_FOLDER + rules_file
              general_rules_file = RULES_FOLDER + 'general_rules.rb'
              general_commands_file = RULES_FOLDER + 'general_commands.rb'
            else
              Dir.mkdir("#{config.path}/rules") unless Dir.exist?("#{config.path}/rules")
              Dir.mkdir("#{config.path}/rules/#{channel_id}") unless Dir.exist?("#{config.path}/rules/#{channel_id}")
              rules_file = "/rules/#{channel_id}/" + rules_file
              general_rules_file = "/rules/general_rules.rb"
              general_commands_file = "/rules/general_commands.rb"
            end
            default_rules = (__FILE__).gsub(/slack\/smart-bot\/commands\/on_master\/create_bot\.rb$/, "slack-smart-bot_rules.rb")
            default_general_rules = (__FILE__).gsub(/slack\/smart-bot\/commands\/on_master\/create_bot\.rb$/, "slack-smart-bot_general_rules.rb")
            default_general_commands = (__FILE__).gsub(/slack\/smart-bot\/commands\/on_master\/create_bot\.rb$/, "slack-smart-bot_general_commands.rb")

            File.delete(config.path + rules_file) if File.exist?(config.path + rules_file)
            FileUtils.copy_file(default_rules, config.path + rules_file) unless File.exist?(config.path + rules_file)
            FileUtils.copy_file(default_general_rules, config.path + general_rules_file) unless File.exist?(config.path + general_rules_file)
            FileUtils.copy_file(default_general_commands, config.path + general_commands_file) unless File.exist?(config.path + general_commands_file)
            admin_users = Array.new()
            creator_info = find_user(channel_found.creator)
            if creator_info.nil? or creator_info.empty? or creator_info.user.nil?
              admin_users = [from] + config.masters
            else
              admin_users = [from, creator_info.user.name] + config.masters
            end
            admin_users.uniq!
            @logger.info "BOT_SILENT=#{silent} ruby #{config.file_path} \"#{channel}\" \"#{admin_users.join(",")}\" \"#{rules_file}\" on"

            if cloud
              respond "Copy the bot folder to your cloud location and run `ruby #{config.file} \"#{channel}\" \"#{admin_users.join(",")}\" \"#{rules_file}\" on&`", dest
            else
              t = Thread.new do
                `BOT_SILENT=#{silent} ruby #{config.file_path} \"#{channel}\" \"#{admin_users.join(",")}\" \"#{rules_file}\" on`
              end
            end
            @bots_created[channel_id] = {
              creator_name: from,
              channel_id: channel_id,
              channel_name: @channels_name[channel_id],
              status: :on,
              created: Time.now.strftime("%Y-%m-%dT%H:%M:%S.000Z")[0..18],
              rules_file: rules_file,
              admins: admin_users.join(","),
              extended: [],
              cloud: cloud,
              thread: t,
            }
            @bots_created[channel_id].silent = true if silent

            respond "The bot has been created on channel: #{channel}. Rules file: #{File.basename rules_file}. Admins: #{admin_users.join(", ")}", dest
            update_bots_file()
          rescue Exception => stack
            @logger.fatal stack
            message = "Problem creating the bot on channel #{channel}. Error: <#{stack}>."
            @logger.error message
            respond message, dest
          end
        else
          respond "There is already a bot in this channel: #{channel}, and it is the Master Channel!", dest
        end
      end
    else
      respond "Sorry I cannot create bots from this channel, please visit the master channel: <##{@master_bot_id}>", dest
    end
  end
end

#create_routine_thread(name, hroutine) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/slack/smart-bot/utils/create_routine_thread.rb', line 3

def create_routine_thread(name, hroutine)
  t = Thread.new do
    while @routines.key?(@channel_id) and @routines[@channel_id].key?(name) and @status != :exit
      @routines[@channel_id][name][:thread] = Thread.current
      started = Time.now
      if @status == :on and @routines[@channel_id][name][:status] == :on
        if !@routines[@channel_id][name].key?(:creator_id) or @routines[@channel_id][name][:creator_id].to_s == ''
           = find_user(@routines[@channel_id][name][:creator])
          @routines[@channel_id][name][:creator_id] = .id unless .nil? or .empty?
        end
        @logger.info "Routine #{name}: #{@routines[@channel_id][name].inspect}"
        if @routines[@channel_id][name][:file_path].match?(/\.rb$/i)
          ruby = "ruby "
        else
          ruby = ""
        end
        @routines[@channel_id][name][:silent] = false if !@routines[@channel_id][name].key?(:silent)
        if @routines[@channel_id][name][:at] == "" or
           (@routines[@channel_id][name][:at] != "" and @routines[@channel_id][name][:running] and
            @routines[@channel_id][name][:next_run] != "" and Time.now.to_s >= @routines[@channel_id][name][:next_run])

          if !@routines[@channel_id][name].key?(:dayweek) or
            (@routines[@channel_id][name].key?(:dayweek) and @routines[@channel_id][name][:dayweek].to_s!='weekday' and @routines[@channel_id][name][:dayweek].to_s!='weekend') or
            (@routines[@channel_id][name].key?(:dayweek) and @routines[@channel_id][name][:dayweek].to_s=='weekday' and Date.today.wday>=1 and Date.today.wday<=5) or
            (@routines[@channel_id][name].key?(:dayweek) and @routines[@channel_id][name][:dayweek].to_s=='weekend' and (Date.today.wday==6 or Date.today.wday==0))
            File.delete "#{config.path}/routines/#{@channel_id}/#{name}_output.txt" if File.exist?("#{config.path}/routines/#{@channel_id}/#{name}_output.txt")
            if @routines[@channel_id][name][:file_path] != ""
              process_to_run = "#{ruby}#{Dir.pwd}#{@routines[@channel_id][name][:file_path][1..-1]}"
              process_to_run = ("cd #{project_folder} &&" + process_to_run) if defined?(project_folder)
              data = {
                dest: @routines[@channel_id][name][:dest],
                typem: 'routine_file',
                user: {id: @routines[@channel_id][name][:creator_id], name: @routines[@channel_id][name][:creator]},
                files: false,
                command: @routines[@channel_id][name][:file_path],
                routine: true,
                routine_name: name,
                routine_type: hroutine[:routine_type]
              }
              save_stats(name, data: data)
              stdout, stderr, status = Open3.capture3(process_to_run)
              if !@routines[@channel_id][name][:silent]
                unless config.on_maintenance
                  if @routines[@channel_id][name][:dest]!=@channel_id
                    respond "routine from <##{@channel_id}> *`#{name}`*: #{@routines[@channel_id][name][:file_path]}", @routines[@channel_id][name][:dest]
                  else
                    respond "routine *`#{name}`*: #{@routines[@channel_id][name][:file_path]}", @routines[@channel_id][name][:dest]
                  end
                end
              end
              if hroutine[:routine_type].to_s!='bgroutine'
                if stderr == ""
                  unless stdout.match?(/\A\s*\z/)
                    respond stdout, @routines[@channel_id][name][:dest]
                  end
                else
                  respond "#{stdout} #{stderr}", @routines[@channel_id][name][:dest]
                end
              else
                File.write("#{config.path}/routines/#{@channel_id}/#{name}_output.txt", stdout.to_s+stderr.to_s, mode: "a+")
              end
            else #command
              message = nil
              if !@routines[@channel_id][name][:silent] and !config.on_maintenance
                if @routines[@channel_id][name][:dest]!=@channel_id
                  message = respond "routine from <##{@channel_id}> *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest], return_message: true
                else
                  message = respond "routine *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest], return_message: true
                end
              end
              started = Time.now
              data = { channel: @channel_id,
                dest: @routines[@channel_id][name][:dest],
                user: @routines[@channel_id][name][:creator_id],
                text: @routines[@channel_id][name][:command],
                files: nil,
                routine: true,
                routine_name: name,
                routine_type: hroutine[:routine_type] }
              if !message.nil? and (@routines[@channel_id][name][:command].match?(/^!!/) or @routines[@channel_id][name][:command].match?(/^\^/))
                data[:ts] = message.ts
              end
              treat_message(data)
            end
            # in case the routine was deleted while running the process
            if !@routines.key?(@channel_id) or !@routines[@channel_id].key?(name)
              Thread.exit
            end
            @routines[@channel_id][name][:last_run] = started.to_s
          elsif (@routines[@channel_id][name].key?(:dayweek) and @routines[@channel_id][name][:dayweek].to_s=='weekday' and (Date.today.wday==6 or Date.today.wday==0)) or
            (@routines[@channel_id][name].key?(:dayweek) and @routines[@channel_id][name][:dayweek].to_s=='weekend' and Date.today.wday>=1 and Date.today.wday<=5)
            @routines[@channel_id][name][:last_run] = started.to_s
          end
        end
        if @routines[@channel_id][name][:last_run] == "" and @routines[@channel_id][name][:next_run] != "" #for the first create_routine of one routine with at
          elapsed = 0
          require "time"
          every_in_seconds = Time.parse(@routines[@channel_id][name][:next_run]) - Time.now
        elsif @routines[@channel_id][name][:at] != "" #coming from start after pause for 'at'
          if @routines[@channel_id][name].key?(:daymonth) and @routines[@channel_id][name][:daymonth].to_s!='' # day of month
            weekly = false
            daymonth = @routines[@channel_id][name][:daymonth]
            day = daymonth.to_i
            if Date.today.day > day
                next_month = Date.new(Date.today.year, Date.today.month, 1) >> 1
            else
                next_month = Date.new(Date.today.year, Date.today.month, 1)
            end
            next_month_last_day = Date.new(next_month.year, next_month.month, -1)
            if day > next_month_last_day.day
                next_time = Date.new(next_month.year, next_month.month, next_month_last_day.day)
            else
                next_time = Date.new(next_month.year, next_month.month, day)
            end
            days = (next_time - Date.today).to_i
            every_in_seconds = Time.parse(@routines[@channel_id][name][:next_run]) - Time.now
          elsif @routines[@channel_id][name].key?(:dayweek) and @routines[@channel_id][name][:dayweek].to_s!='' and
            @routines[@channel_id][name][:dayweek].to_s!='weekend' and @routines[@channel_id][name][:dayweek].to_s!='weekday'

            day = @routines[@channel_id][name][:dayweek]
            days = ['sunday','monday','tuesday','wednesday','thursday','friday','saturday']
            incr = days.index(day) - Time.now.wday
            if incr < 0
              incr = (7+incr)*24*60*60
            else
              incr = incr * 24 * 60 * 60
            end
            days = incr/(24*60*60)
            weekly = true
          elsif @routines[@channel_id][name].key?(:dayweek) and @routines[@channel_id][name][:dayweek].to_s!='' and
            @routines[@channel_id][name][:dayweek].to_s=='weekend'

            weekly = false
            days = 0
          elsif @routines[@channel_id][name].key?(:dayweek) and @routines[@channel_id][name][:dayweek].to_s!='' and
            @routines[@channel_id][name][:dayweek].to_s=='weekday'

            weekly = false
            days = 0
          else
            days = 0
            weekly = false
          end

          if started.strftime("%H:%M:%S") < @routines[@channel_id][name][:at] and days == 0
            nt = @routines[@channel_id][name][:at].split(":")
            next_run = Time.new(started.year, started.month, started.day, nt[0], nt[1], nt[2])
          else
            if days == 0 and started.strftime("%H:%M:%S") >= @routines[@channel_id][name][:at]
              if weekly
                  days = 7
              elsif @routines[@channel_id][name].key?(:daymonth) and @routines[@channel_id][name][:daymonth].to_s!=''
                daymonth = @routines[@channel_id][name][:daymonth]
                day = daymonth.to_i
                if Date.today.day >= day
                    next_month = Date.new(Date.today.year, Date.today.month, 1) >> 1
                else
                    next_month = Date.new(Date.today.year, Date.today.month, 1)
                end
                next_month_last_day = Date.new(next_month.year, next_month.month, -1)
                if day > next_month_last_day.day
                    next_time = Date.new(next_month.year, next_month.month, next_month_last_day.day)
                else
                    next_time = Date.new(next_month.year, next_month.month, day)
                end
                days = (next_time - Date.today).to_i
              else
                  days = 1
              end
            end
            next_run = started + (days * 24 * 60 * 60) # one more day/week
            nt = @routines[@channel_id][name][:at].split(":")
            next_run = Time.new(next_run.year, next_run.month, next_run.day, nt[0], nt[1], nt[2])
          end
          @routines[@channel_id][name][:next_run] = next_run.to_s
          elapsed = 0
          every_in_seconds = next_run - started
        else
          every_in_seconds = @routines[@channel_id][name][:every_in_seconds]
          elapsed = Time.now - started
          @routines[@channel_id][name][:last_elapsed] = elapsed
          @routines[@channel_id][name][:next_run] = (started + every_in_seconds).to_s
        end
        @routines[@channel_id][name][:running] = true
        @routines[@channel_id][name][:sleeping] = (every_in_seconds - elapsed).ceil
        update_routines()
        sleep(@routines[@channel_id][name][:sleeping]) unless elapsed > every_in_seconds
      else
        if !@routines[@channel_id][name][:running]
          @routines[@channel_id][name][:running] = true
          update_routines()
        end
        sleep 30
      end
    end
  end
end

#delete(channel, ts) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
# File 'lib/slack/smart-bot/comm/delete.rb', line 2

def delete(channel, ts)
  result = true
  begin
    resp = client.web_client.chat_delete(channel: channel, as_user: true, ts: ts)
    result = resp.ok.to_s == 'true'
  rescue Exception => exc
    result = false
    @logger.fatal exc.inspect
  end
  return result
end

#delete_announcement(user, message_id) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/slack/smart-bot/commands/general/delete_announcement.rb', line 3

def delete_announcement(user, message_id)
  save_stats(__method__)
  if has_access?(__method__, user)
    if Thread.current[:typem] == :on_call
      channel = Thread.current[:dchannel]
    else
      channel = Thread.current[:dest]
    end
    if File.exist?("#{config.path}/announcements/#{channel}.csv") and !@announcements.key?(channel)
      t = CSV.table("#{config.path}/announcements/#{channel}.csv", headers: ['message_id', 'user_team_id_deleted', 'user_deleted', 'user_team_id_created', 'user_created', 'date', 'time', 'type', 'message'])
      @announcements[channel] = t
    end
    found = false
    message = ''
    if @announcements.key?(channel) and @announcements[channel][:message_id].include?(message_id.to_i)
      CSV.open("#{config.path}/announcements/#{channel}.csv", "w") do |csv|
        @announcements[channel].each do |row|
          if row[:message_id].to_i == message_id.to_i
            message = row[:message]
            row[:user_team_id_deleted] = user.team_id
            row[:user_deleted] = user.name
          end    
          csv << row
        end
      end
      respond "The announcement has been deleted: #{message}"
    else
      respond "Sorry but I didn't find the message id #{message_id}. Call `see announcements` to see the ids."
    end

  end
end

#delete_message(user, typem, url) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: delete message URL helpadmin: It will delete the SmartBot message supplied 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 helpadmin: command_id: :delete_message helpadmin:



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/slack/smart-bot/commands/on_bot/admin_master/delete_message.rb', line 9

def delete_message(user, typem, url)
  save_stats(__method__)
  channel, ts = url.scan(/\/archives\/(\w+)\/(\w\d+)/)[0]
  if config.team_id_masters.include?("#{user.team_id}_#{user.name}") and typem==:on_dm and !channel.nil? #master admin user
    ts = "#{ts[0..-7]}.#{ts[-6..-1]}"
    succ = delete(channel, ts)
    if succ
      react :heavy_check_mark
    else
      react :x
    end
  else
    respond "Only master admin users on a private conversation with the SmartBot can delete SmartBot messages"
  end
end

#delete_repl(dest, user, session_name) ⇒ Object

help: ---------------------------------------------- help: delete repl SESSION_NAME help: delete irb SESSION_NAME help: remove repl SESSION_NAME help: Will delete the specified REPL help: Only the creator of the REPL or an admin can delete REPLs help: help: command_id: :delete_repl help:



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/slack/smart-bot/commands/on_bot/delete_repl.rb', line 11

def delete_repl(dest, user, session_name)
  #todo: add tests
  save_stats(__method__)
  if has_access?(__method__, user)
    if @repls.key?(session_name)
      Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
      if is_admin?(user) or (@repls[session_name].creator_name == user.name and @repls[session_name].creator_team_id == user.team_id)
        @repls.delete(session_name)
        update_repls()
        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")
        File.delete("#{config.path}/repl/#{@channel_id}/#{session_name}.output") if File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.output")
        File.delete("#{config.path}/repl/#{@channel_id}/#{session_name}.run") if File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.run")
        respond "REPL #{session_name} deleted"
      else
        respond "Only admins or the creator of this REPL can delete it", dest
      end

    else
      respond "The REPL with session name: #{session_name} doesn't exist on this Smart Bot Channel", dest
    end
  end
end

#delete_share(user, share_id) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/slack/smart-bot/commands/general/delete_share.rb', line 3

def delete_share(user, share_id)
  save_stats(__method__)
  if has_access?(__method__, user)
    if Thread.current[:typem] == :on_call
      channel = Thread.current[:dchannel]
    else
      channel = Thread.current[:dest]
    end
    if File.exist?("#{config.path}/shares/#{@channels_name[channel]}.csv") and !@shares.key?(@channels_name[channel])
      t = CSV.table("#{config.path}/shares/#{@channels_name[channel]}.csv", headers: ['share_id', 'user_team_id_deleted', 'user_deleted', 'user_team_id_created', 'user_created', 'date', 'time', 'type', 'to_channel', 'condition'])
      @shares[@channels_name[channel]] = t
    end
    found = false
    message = ''
    if @shares[@channels_name[channel]][:share_id].include?(share_id.to_i)
      CSV.open("#{config.path}/shares/#{@channels_name[channel]}.csv", "w") do |csv|
        @shares[@channels_name[channel]].each do |row|
          if row[:share_id].to_i == share_id.to_i
            message = row[:condition]
            row[:user_team_id_deleted] = user.team_id
            row[:user_deleted] = user.name
          end    
          csv << row
        end
      end
      respond "The share has been deleted: #{message}"
    else
      respond "Sorry but I didn't find the share id #{share_id}. Call `see shares` to see all the ids."
    end

  end
end

#delete_shortcut(dest, user, shortcut, typem, command, global) ⇒ Object

help: ---------------------------------------------- help: delete shortcut NAME help: delete sc NAME help: delete global sc NAME help: It will delete the shortcut with the supplied name help: 'global' or 'generic' can only be used on Master channel. help: help: command_id: :delete_shortcut help:



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/slack/smart-bot/commands/on_bot/delete_shortcut.rb', line 12

def delete_shortcut(dest, user, shortcut, typem, command, global)
  save_stats(__method__)
  unless typem == :on_extended
    from = user.name
    team_id_user = user.team_id + '_' + from
    if has_access?(__method__, user)
      deleted = false
      shortcut.strip!
      if global
        if !config.on_master_bot or typem != :on_master
          respond "It is only possible to delete global shortcuts from Master channel"
        else
          if !is_admin?(user) and @shortcuts_global[:all].include?(shortcut) and
            (!@shortcuts_global.key?(team_id_user) or !@shortcuts_global[team_id_user].include?(shortcut))
            respond "Only the creator of the shortcut or an admin user can delete it"
          elsif (@shortcuts_global.key?(team_id_user) and @shortcuts_global[team_id_user].keys.include?(shortcut)) or
            (is_admin?(user) and @shortcuts_global[:all].include?(shortcut))

            respond "global shortcut deleted!", dest
            if @shortcuts_global.key?(team_id_user) and @shortcuts_global[team_id_user].key?(shortcut)
              respond("#{shortcut}: #{@shortcuts_global[team_id_user][shortcut]}", dest)
            elsif @shortcuts_global.key?(:all) and @shortcuts_global[:all].key?(shortcut)
              respond("#{shortcut}: #{@shortcuts_global[:all][shortcut]}", dest)
            end
            @shortcuts_global[team_id_user].delete(shortcut) if @shortcuts_global.key?(team_id_user) and @shortcuts_global[team_id_user].key?(shortcut)
            @shortcuts_global[:all].delete(shortcut) if @shortcuts_global.key?(:all) and @shortcuts_global[:all].key?(shortcut)
            update_shortcuts_file()
          else
            respond 'shortcut not found'
          end
        end
      else
        if !is_admin?(user) and @shortcuts[:all].include?(shortcut) and
          (!@shortcuts.key?(team_id_user) or !@shortcuts[team_id_user].include?(shortcut))
          respond "Only the creator of the shortcut or an admin user can delete it", dest
        elsif (@shortcuts.keys.include?(team_id_user) and @shortcuts[team_id_user].keys.include?(shortcut)) or
              (is_admin?(user) and @shortcuts[:all].include?(shortcut))
          #are you sure? to avoid deleting by mistake
          if answer.empty?
            ask("are you sure you want to delete it?", command, team_id_user, dest)
          else
            case answer
            when /^(yes|yep)/i
              answer_delete(team_id_user)
              respond "shortcut deleted!", dest
              if @shortcuts.key?(team_id_user) and @shortcuts[team_id_user].key?(shortcut)
                respond("#{shortcut}: #{@shortcuts[team_id_user][shortcut]}", dest)
              elsif @shortcuts.key?(:all) and @shortcuts[:all].key?(shortcut)
                respond("#{shortcut}: #{@shortcuts[:all][shortcut]}", dest)
              end
              @shortcuts[team_id_user].delete(shortcut) if @shortcuts.key?(team_id_user) and @shortcuts[team_id_user].key?(shortcut)
              @shortcuts[:all].delete(shortcut) if @shortcuts.key?(:all) and @shortcuts[:all].key?(shortcut)
              update_shortcuts_file()
            when /^no/i
              answer_delete(team_id_user)
              respond "ok, I won't delete it", dest
            else
              ask("I don't understand, are you sure you want to delete it? (yes or no)", command, team_id_user, dest)
            end
          end
        else
          respond "shortcut not found", dest
        end
      end
    end
  end
end

#deny_access(user, command_id) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/slack/smart-bot/commands/general/deny_access.rb', line 2

def deny_access(user, command_id)
  save_stats(__method__)
  not_allowed = ['hi_bot', 'bye_bot', "allow_access", "deny_access", "get_bot_logs", "add_routine", "pause_bot", "pause_routine", "remove_routine", "run_routine", "start_bot",
                 "start_routine", "delete_message", "update_message", "send_message", "kill_bot_on_channel", "exit_bot", "notify_message", "publish_announcements", "set_general_message",
                 "set_maintenance", 'bot_help', 'bot_rules']
  if !is_admin?(user)
    respond "Only admins of this channel can use this command. Take a look who is an admin of this channel by calling `see admins`"
  elsif Thread.current[:dest][0] == "D"
    respond "This command cannot be called from a DM"
  elsif not_allowed.include?(command_id)
    respond "Sorry but the access for `#{command_id}` cannot be changed."
  else
    if Thread.current[:typem] == :on_call
      channel = Thread.current[:dchannel]
    elsif Thread.current[:using_channel].to_s == ""
      channel = Thread.current[:dest]
    else
      channel = Thread.current[:using_channel]
    end
    command_ids = get_command_ids()
    if command_ids.values.flatten.include?(command_id)
      if !@access_channels.key?(channel)
        @access_channels[channel] = {}
      end

      @access_channels[channel][command_id] = []

      update_access_channels()
      respond "The command `#{command_id}` won't be available in this channel. Call `allow access #{command_id}` if you want it back."
    else
      respond "It seems like #{command_id} is not valid. Please be sure that exists by calling `see command ids`"
    end
  end
end

#display_calendar(from_user_name, year, country_region: '') ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/slack/smart-bot/utils/display_calendar.rb', line 2

def display_calendar(from_user_name, year, country_region: '')
  if @vacations.key?(from_user_name) and @vacations[from_user_name][:public_holidays].to_s != ""
    country_region = @vacations[from_user_name][:public_holidays].downcase
  elsif config[:public_holidays].key?(:default_calendar) and country_region.empty?
    country_region = config[:public_holidays][:default_calendar].downcase
  end
  country, location = country_region.split("/")
  local_day_time = local_time(country_region)
  if local_day_time.nil?
    today = Date.today
  else
    today = local_day_time.to_date
  end
  public_holidays(country.to_s, location.to_s, year, "", "", add_stats: false, publish_results: false)
  if from_user_name.empty?
    messages = ["*Calendar #{year} #{country_region}*"]
  else
    messages = ["*Time off #{year}*"]
  end
  days_of_vacations = 0
  (1..12).each do |m|
    date = Date.parse("#{year}/#{m}/1")
    month_name = date.strftime("%B")
    month_line = ""
    (1..6).each do |w|
      if date.month == m
        month_line += "#{date.strftime("%d")} "
      else
        month_line += ":white_small_square: "
      end

      if @public_holidays.key?(country_region) and @public_holidays[country_region].key?(year.to_s)
        phd = @public_holidays[country_region][year.to_s].date.iso
      else
        phd = []
      end
      (1..7).each do |d|
        wday = date.wday
        wday = 7 if wday == 0
        break if d >= 3 and w == 6 # week 6 cannot be more than wednesday
        date_text = date.strftime("%Y-%m-%d")
        if wday == d and date.month == m
          vacations_set = false
          public_holiday_set = false
          if phd.include?(date_text)
            if date == today
              month_line += ":large_red_circle: "
            else
              month_line += ":large_red_square: "
            end
            public_holiday_set = true
          end
          if !public_holiday_set
            if @vacations.key?(from_user_name) and @vacations[from_user_name].key?(:periods)
              @vacations[from_user_name][:periods].each do |period|
                if date >= Date.parse(period[:from]) and date <= Date.parse(period[:to])
                  if period[:type] == "sick"
                    if date == today
                      month_line += ":thermometer: "
                    else
                      month_line += ":face_with_thermometer: "
                    end
                  elsif period[:type] == "sick child"
                    if date == today
                      month_line += ":baby_bottle: "
                    else
                      month_line += ":baby: "
                    end
                  elsif period[:type] == "vacation"
                    if date == today
                      month_line += ":evergreen_tree: "
                    else
                      month_line += ":palm_tree: "
                    end
                    if wday <= 5
                      days_of_vacations += 1
                    end
                  end
                  vacations_set = true
                  break
                end
              end
            end
            if !vacations_set
              if wday == 6 || wday == 7
                if date == today
                  month_line += ":large_yellow_circle: "
                else
                  month_line += ":large_yellow_square: "
                end
              else
                if date == today
                  month_line += ":white_circle: "
                else
                  month_line += ":white_square: "
                end
              end
            end
          end
          date += 1
        else
          month_line += ":white_small_square: "
        end
      end
    end
    messages << "#{month_line}    #{month_name}\n"
  end
  if !from_user_name.empty?
    messages << "\n\n:large_yellow_square: weekend / :white_square: weekday / :white_small_square: not in month / :large_red_square: Public Holiday / :palm_tree: Vacation / :face_with_thermometer: Sick / :baby: Sick child"
    if country_region != ""
      if local_day_time.nil?
        local_str = "local time not found"
      else
        local_str = local_day_time.strftime("%Y-%m-%d %H:%M")
      end
      messages << "Your public holidays are set for #{country_region.downcase} (#{local_str}). Call `set public holidays to COUNTRY/REGION` if you want to change it.\n"
    else
      messages << "Your public holidays are not set. Call `set public holidays to COUNTRY/REGION` to set it.\n"
    end
    messages << "You have spent #{days_of_vacations} days of vacations in #{year} considering only weekdays.\n\n"
  else
    messages << "\n\n:large_yellow_square: weekend / :white_square: weekday / :white_small_square: not in month / :large_red_square: Public Holiday"
  end
  respond messages.join("\n")
end

#dont_understand(rules_file = nil, command = nil, user = nil, dest = nil, answer = ["what?", "huh?", "sorry?", "what do you mean?", "I don't understand"], channel_rules: config.channel, typem: nil) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/slack/smart-bot/comm/dont_understand.rb', line 2

def dont_understand(rules_file = nil, command = nil, user = nil, dest = nil, answer = ["what?", "huh?", "sorry?", "what do you mean?", "I don't understand"], channel_rules: config.channel, typem: nil)
  save_stats(:dont_understand)
  command = Thread.current[:command] if command.nil?
  user = Thread.current[:user] if user.nil?
  dest = Thread.current[:dest] if dest.nil?
  rules_file = Thread.current[:rules_file] if rules_file.nil?
  typem = Thread.current[:typem] if typem.nil?
  if typem == :on_extended
    get_bots_created()
  end
  text = get_help(rules_file, dest, user, typem == :on_extended, true)

  ff = text.scan(/^\s*`\s*([^`]+)\s*`\s*$/i).flatten
  ff.delete("!THE_COMMAND")
  ff.delete("@NAME_OF_BOT THE_COMMAND")
  ff.delete("NAME_OF_BOT THE_COMMAND")
  ff.delete("@BOT_NAME on #CHANNEL_NAME COMMAND")

  ff2 = {}
  acommand = command.split(/\s+/)
  ff.each do |f|
    ff2[f] = ""
    af = f.split(/\s+/)
    af.each_with_index do |word, i|
      if acommand.size >= (i - 1) and word.match?(/[A-Z_\-#@]+/)
        ff2[f] += "#{acommand[i]} "
      else
        ff2[f] += "#{word} "
      end
    end
    ff2[f].rstrip!
  end

  spell_checker = DidYouMean::SpellChecker.new(dictionary: ff2.values)
  res = spell_checker.correct(command).uniq
  res_final = []
  res.each do |r|
    res_final << (ff2.select { |k, v| v == r }).keys
  end
  res_final.flatten!

  if typem == :on_extended
    if @extended_from[@channels_name[dest]].size == 1
      respond "#{user.profile.display_name}, I don't understand.", dest
    end
    unless res_final.empty?
      respond "Similar rules on : *#{channel_rules}*\n`#{res_final[0..4].join("`\n`")}`", dest
    end
  else
    message = ""
    message = "\nTake in consideration when on external calls, not all the commands are available." if typem == :on_call
    if res_final.empty?
      resp = answer.sample
      respond "#{user.profile.display_name}, #{resp}#{message}", dest
    else
      respond "#{user.profile.display_name}, I don't understand. Maybe you are trying to say:\n`#{res_final[0..4].join("`\n`")}`#{message}", dest
    end
  end

  ai_conn, message = SlackSmartBot::AI::OpenAI.connect({}, config, {}, service: :chat_gpt)
  if message.empty?
    react :speech_balloon
    chatgpt = ai_conn[Thread.current[:team_id_user]].chat_gpt
    model = chatgpt.smartbot_model if model.nil?
    prompt = "I sent this command to Slack SmartBot: `#{command}` and it seems that is wrong\n\n"
    prompt += "These are the available SmartBot commands:\n#{text}\n\n"
    prompt += "Please, can you suggest the command that I mean?\n"
    prompt += "Return just like 5 lines of text max. If you supply a command do it like this: `the command`"
    success, res = SlackSmartBot::AI::OpenAI.send_gpt_chat(chatgpt.client, model, prompt, chatgpt)
    if success
      response_message = "*ChatGPT*: Maybe you are trying to say:\n#{res.to_s.strip}\n\n"
      response_message += "Remember you can always ask for help by calling `bot help ?? YOUR QUESTION`."
      respond transform_to_slack_markdown(response_message), dest
    end
    unreact :speech_balloon
  end
end

#download_http_content(url, authorizations, team_id_user_creator = nil, session_name = nil) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/slack/smart-bot/utils/download_http_content.rb', line 2

def download_http_content(url, authorizations, team_id_user_creator = nil, session_name = nil)
  begin
    url_message = ""
    parsed_url = URI.parse(url)

    headers = {}

    authorizations.each do |key, value|
      if key.match?(/^(https:\/\/|http:\/\/)?#{parsed_url.host.gsub(".", '\.')}/)
        value.each do |k, v|
          headers[k.to_sym] = v unless k == :host
        end
      end
    end

    extra_message = ""
    domain = "#{parsed_url.scheme}://#{parsed_url.host}"
    if parsed_url.host.match?(/(drive|docs)\.google\.com/) #download the file
      if url.include?("/file/d/")
        gdrive_id = url.split("/d/")[1].split("/")[0]
        url = "https://drive.google.com/uc?id=#{gdrive_id}&export=download"
      end
      io = URI.open(url, headers)
      is_pdf = io.meta["content-type"].to_s == "application/pdf" || io.meta["content-disposition"].to_s.include?("pdf")
      if is_pdf
        require "pdf-reader"
        if io.meta["content-disposition"].to_s.include?("pdf")
          pdf_filename = io.meta["content-disposition"].split("filename=")[1].strip
          extra_message = " PDF file: #{pdf_filename}"
        end
        reader = PDF::Reader.new(io)
        text = reader.pages.map(&:text).join("\n")
      else
        is_docx = io.meta["content-type"].to_s == "application/vnd.openxmlformats-officedocument.wordprocessingml.document" || io.meta["content-disposition"].to_s.include?("docx")
        if is_docx
          require "docx"
          if io.meta["content-disposition"].to_s.include?("docx")
            docx_filename = io.meta["content-disposition"].split("filename=")[1].strip
            extra_message = " DOCX file: #{docx_filename}"
          end
          doc = Docx::Document.open(io)
          text = doc.paragraphs.map(&:to_s).join("\n")
        else #text
          text = io.read
          text_filename = io.meta["content-disposition"].split("filename=")[1].strip if io.meta["content-disposition"].to_s.include?("filename=")
          extra_message = " Text file: #{text_filename}"
        end
      end
      io.close
    elsif parsed_url.path.match?(/\.pdf$/)
      require "pdf-reader"
      io = URI.open(url, headers)
      reader = PDF::Reader.new(io)
      text = reader.pages.map(&:text).join("\n")
      io.close
    elsif parsed_url.path.match?(/\.docx?$/)
      require "docx"
      io = URI.open(url, headers)
      doc = Docx::Document.open(io)
      text = doc.paragraphs.map(&:to_s).join("\n")
      io.close
    else
      parsed_url += "/" if parsed_url.path == ""
      http = NiceHttp.new(host: domain, headers: headers, log: :no)
      path = parsed_url.path
      path += "?#{parsed_url.query}" if parsed_url.query
      response = http.get(path)
      html_doc = Nokogiri::HTML(response.body)
      html_doc.search("script, style").remove
      text = html_doc.text.strip
      text.gsub!(/^\s*$/m, "")
      http.close
    end
    if !session_name.nil? and !team_id_user_creator.nil?
      if (!@open_ai[team_id_user_creator][:chat_gpt][:sessions][session_name].key?(:live_content) or
          @open_ai[team_id_user_creator][:chat_gpt][:sessions][session_name][:live_content].nil? or
          !@open_ai[team_id_user_creator][:chat_gpt][:sessions][session_name][:live_content].include?(url)) and
         (!@open_ai[team_id_user_creator][:chat_gpt][:sessions][session_name].key?(:static_content) or
          @open_ai[team_id_user_creator][:chat_gpt][:sessions][session_name][:static_content].nil? or
          !@open_ai[team_id_user_creator][:chat_gpt][:sessions][session_name][:static_content].include?(url))
        url_message = "> #{url}#{extra_message}: content extracted and added to prompt"
      end
    end
  rescue Exception => e
    text = "Error: #{e.message}"
    url_message = "> #{url}: #{text}\n"
  end
  return text, url_message
end

#event_helloObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/slack/smart-bot/comm/event_hello.rb', line 2

def event_hello()

  if config.on_master_bot
    File.open("#{config.path}/status/version.txt", 'w') {|f| f.write(VERSION) }
  end

  @first_time_bot_started ||= true
  unless config.simulate
    m = "Successfully connected, welcome '#{client.self.name}' to the '#{client.team.name}' team at https://#{client.team.domain}.slack.com."
    puts m
    save_status :on, :connected, m

    @logger.info m
    config.nick = client.self.name
    config.nick_id = client.self.id
    if config.granular_token.empty?
      config.nick_granular = ""
      config.nick_id_granular = ""
    else
      conn_granular = NiceHttp.new(host: "https://slack.com", log: :no)
      conn_granular.headers = { authorization: "Bearer #{config.granular_token}" }
      resp = conn_granular.get("/api/auth.test")
      if resp.code.to_s == '200' and resp.body.json(:ok) == true
        config.nick_granular = resp.body.json(:user)
        config.nick_id_granular = resp.body.json(:user_id)
      else
        config.nick_granular = ""
        config.nick_id_granular = ""
      end
      conn_granular.close
    end
    if client.team.key?(:enterprise_id)
      config.team_id = client.team.enterprise_id
      config.team_name = client.team.enterprise_name
    else
      config.team_id = client.team.id
      config.team_name = client.team.name
    end
    config.team_domain = client.team.domain
  end
  @salutations = [config[:nick], "<@#{config[:nick_id]}>", "@#{config[:nick]}", "bot", "smart", "smartbot", "smart-bot", "smart bot"]

  gems_remote = `gem list slack-smart-bot --remote`
  version_remote = gems_remote.to_s().scan(/slack-smart-bot \((\d+\.\d+\.\d+)/).join
  version_message = ""
  if Gem::Version.new(version_remote) > Gem::Version.new(VERSION)
    version_message = ". There is a new available version: #{version_remote}."
  end
  if (!config[:silent] or ENV['BOT_SILENT'].to_s == 'false') and !config.simulate
    unless ENV['BOT_SILENT']=='true' or !@first_time_bot_started
      respond "Smart Bot started v#{VERSION}#{version_message}\nIf you want to know what I can do for you: `bot help`.\n`bot rules` if you want to display just the specific rules of this channel.\nYou can talk to me privately if you prefer it."
    end
    ENV['BOT_SILENT'] = 'true' if config[:silent] and ENV['BOT_SILENT'].to_s != 'true'
  end
  @routines.each do |ch, rout|
    rout.each do |k, v|
      if !v[:running] and v[:channel_name] == config.channel
        create_routine_thread(k, v)
      end
    end
  end
  @first_time_bot_started = false
end

#exit_bot(command, user, dest, display_name, silent: false) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: exit bot helpadmin: quit bot helpadmin: close bot helpadmin: exit bot silent helpadmin: The bot stops running and also stops all the bots created from this master channel helpadmin: You can use this command only if you are an admin user and you are on the master channel helpadmin: helpadmin: command_id: :exit_bot helpadmin:



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb', line 13

def exit_bot(command, user, dest, display_name, silent: false)
  save_stats(__method__)
  if config.on_master_bot
    if config.team_id_masters.include?("#{user.team_id}_#{user.name}") #master admin user
      if answer.empty?
        ask("are you sure?", command, user, dest)
      else
        case answer
        when /yes/i, /yep/i, /sure/i
          react :runner
          @bots_created.each { |key, value|
            value[:thread] = ""
            send_msg_channel(key, "Bot has been closed by #{user.name}") unless silent
            save_status :off, :exited, "The admin closed SmartBot on *##{value.channel_name}*"
            sleep 0.5
          }
          update_bots_file()
          sleep 0.5
          file = File.open("#{config.path}/config_tmp.status", "w")
          config.exit_bot = true
          @config_log.exit_bot = true
          file.write @config_log.inspect
          file.close
          @status = :exit
          respond "Game over!", dest
          @listening[:threads].each do |thread_ts, channel_thread|
            unreact :running, thread_ts, channel: channel_thread
            respond "ChatGPT session closed since SmartBot is going to be closed.\nCheck <##{@channels_id[config.status_channel]}>", channel_thread, thread_ts: thread_ts
          end
          if config.simulate
            sleep 2
            @status = :off
            config.simulate = false
            Thread.exit
          else
            respond "Ok, It will take around 40s to close all the bots, all routines and the master bot."
            sleep 35
            respond "Ciao #{display_name}!", dest
            unreact :runner
            react :beach_with_umbrella
            sleep 1
            exit!
          end
        when /no/i, /nope/i, /cancel/i
          answer_delete(user)
          respond "Thanks, I'm happy to be alive", dest
        else
          ask("I don't understand, are you sure do you want me to close? (yes or no)", command, user, dest)
        end
      end
    else
      respond "Only admin users can kill me", dest
    end
  else
    respond "To do this you need to be an admin user in the master channel: <##{@master_bot_id}>", dest
  end
end

#extend_rules(dest, user, from, channel, typem) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: extend rules to CHANNEL_NAME helpadmin: use rules on CHANNEL_NAME helpadmin: It will allow to use the specific rules from this channel on the CHANNEL_NAME helpadmin: helpadmin: command_id: :extend_rules helpadmin:



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/slack/smart-bot/commands/on_bot/admin/extend_rules.rb', line 10

def extend_rules(dest, user, from, channel, typem)
  save_stats(__method__)
  unless typem == :on_extended
    if config.on_master_bot
      respond "You cannot use the rules from Master Channel on any other channel.", dest
    elsif !is_admin? #not admin
      respond "Only admins can extend the rules. Admins on this channel: #{config.admins}", dest
    else
      #todo: add pagination for case more than 1000 channels on the workspace
      channels = get_channels()
      channel = @channels_name[channel] if @channels_name.key?(channel)

      channel_found = channels.detect { |c| c.name == channel }
      get_channels_name_and_id()
      members = get_channel_members(@channels_id[channel]) unless channel_found.nil?
      get_bots_created()
      channels_in_use = []
      @bots_created.each do |k, v|
        if v.key?(:extended) and v[:extended].include?(channel)
          channels_in_use << v[:channel_name]
        end
      end
      if channel_found.nil?
        respond "The channel you specified doesn't exist or I don't have access to it. Be sure I was invited to that channel.", dest
      elsif @bots_created.key?(@channels_id[channel]) or channel == config.master_channel
        respond "There is a bot already running on that channel.", dest
      elsif @bots_created[@channel_id][:extended].include?(channel)
        respond "The rules are already extended to that channel.", dest
      elsif !members.include?(user.id)
        respond "You need to join that channel first", dest
      elsif !members.include?(config[:nick_id])
        respond "You need to add first to the channel the smart bot user: <@#{config[:nick_id]}>", dest
      else
        channels_in_use.each do |channel_in_use|
          respond "The rules from channel <##{@channels_id[channel_in_use]}> are already in use on that channel", dest
        end
        @bots_created[@channel_id][:extended] = [] unless @bots_created[@channel_id].key?(:extended)
        @bots_created[@channel_id][:extended] << channel
        update_bots_file()
        respond "<@#{user.id}> extended the rules from #{config.channel} to be used on #{channel}.", @master_bot_id
        if @channels_id[channel][0] == "G"
          respond "Now the rules from <##{@channel_id}> are available on *#{channel}*", dest
        else
          respond "Now the rules from <##{@channel_id}> are available on *<##{@channels_id[channel]}>*", dest
        end
        respond "<@#{user.id}> extended the rules from <##{@channel_id}> to this channel so now you can talk to the Smart Bot on demand using those rules.", @channels_id[channel]
        respond "Use `!` or `^` or `!!` before the command you want to run", @channels_id[channel]
        respond "To see the specific rules for this bot on this channel: `!bot rules` or `!bot rules COMMAND`", @channels_id[channel]
      end
    end
  end
end

#find_user(user, get_sso_user_name: false) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/slack/smart-bot/utils/find_user.rb', line 2

def find_user(user, get_sso_user_name: false)
  @users = get_users() if @users.empty?
  userh = { id: "", name: "" }
   = nil
  if user.to_s.length > 0 and user != "@"
    if user[0] == "@" #name
      user = user[1..-1]
      is_name = true
    else
      is_name = false
    end
    if user.match?(/^[A-Z0-9]{7,11}_/) #team_id_user_name
      team_id = user.split("_")[0]
      user = user.split("_")[1..-1].join("_")
      is_name = true
    else
      team_id = config.team_id
      is_name = false
    end

    if is_name
      userh[:name] = user
    elsif user.match?(/[a-z]/)
      # if not is name and user contains any downcase letter, then we guess it is a name
      userh[:name] = user
    else
      userh[:id] = user
    end
     = @users.select { |u|
      # for user id we don't check team_id as for the moment according to Slack API, user id is unique
      ((userh[:id].to_s != "" and u.id == userh[:id]) or (u.key?(:enterprise_user) and u.enterprise_user.id == userh[:id])) or
      ((userh[:name].to_s != "" and u.name == userh[:name] and u.team_id == team_id) or (u.key?(:enterprise_user) and u.enterprise_user.name == userh[:name] and u.enterprise_user.enterprise_id == team_id))
    }[-1]

    if .nil? #other workspace
       = (user)
      unless .nil? or .empty?
        @users << .user
         = @users.select { |u|
          # for user id we don't check team_id as for the moment according to Slack API, user id is unique
          ((userh[:id].to_s != "" and u.id == userh[:id]) or (u.key?(:enterprise_user) and u.enterprise_user.id == userh[:id])) or
          ((userh[:name].to_s != "" and u.name == userh[:name] and u.team_id == team_id) or (u.key?(:enterprise_user) and u.enterprise_user.name == userh[:name] and u.enterprise_user.enterprise_id == team_id))
        }[-1]
      end
    end
  end
  if get_sso_user_name and defined?(@ldap) and !@ldap.nil? and !.nil? and [:sso_user_name].to_s.empty? and ![:profile].email.to_s.empty?
    begin
      if @ldap.bind
        email = [:profile].email
        filter1 = Net::LDAP::Filter.eq("mail", email)
        filter2 = Net::LDAP::Filter.eq("mailAlternateAddress", email)
        filter3 = Net::LDAP::Filter.eq("mail", email.gsub(/@.+$/, ""))
        filter4 = Net::LDAP::Filter.eq("mailAlternateAddress", email.gsub(/@.+$/, ""))
        filter = filter1 | filter2 | filter3 | filter4
        @ldap.search(:base => config.ldap.treebase, :filter => filter) do |entry|
          [:sso_user_name] = entry.uid[0]
        end
      end
    rescue => exception
      if defined?(@logger)
        @logger.fatal exception
      else
        puts exception
      end
    end
  end
  return 
end

#general_bot_commands(user, command, dest, files = []) ⇒ Object

add here the general commands you will be using in any channel where The SmartBot is part of. Not necessary to use ! or ^, it will answer directly.



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
# File 'lib/slack/smart-bot/commands/general_bot_commands.rb', line 4

def general_bot_commands(user, command, dest, files = [])
  begin
    if config.simulate
      display_name = user.profile.display_name
    else
      if user.profile.display_name.to_s.match?(/\A\s*\z/)
        user.profile.display_name = user.profile.real_name
      end
      display_name = user.profile.display_name
    end

    case command
    # help: ----------------------------------------------
    # help: `bot help`
    # help: `bot help COMMAND`
    # help: `bot rules`
    # help: `bot rules COMMAND`
    # help: `bot help expanded`
    # help: `bot rules expanded`
    # help: `bot what can I do?`
    # help: `<bot help`
    # help: `<bot rules`
    # help:    it will display this help. For a more detailed help call `bot help expanded` or `bot rules expanded`.
    # help:    if COMMAND supplied just help for that command
    # help:    you can use the option 'expanded' or the alias 'extended'
    # help:    `bot rules` will show only the specific rules for this channel.
    # help:    if added '<' at the beginning the help will be sent as a snippet on MD format. Click on the icon for 'Open in new window' to see it on a browser.
    # help:      You can install this extension in Google Chrome to view it: https://chromewebstore.google.com/detail/medapdbncneneejhbgcjceippjlfkmkg
    # help:    <https://github.com/MarioRuiz/slack-smart-bot#bot-help|more info>
    # help: command_id: :bot_help
    # help:

    # help: ----------------------------------------------
    # help: `get smartbot readme`
    # help: `smartbot readme`
    # help: `readme smartbot`
    # help:      It will send the README.md file to the channel.
    # help:      To open the file on a browser click on the icon for 'Open in new window'
    # help:      You can install this extension in Google Chrome to view it: https://chromewebstore.google.com/detail/medapdbncneneejhbgcjceippjlfkmkg
    # help: command_id: :get_smartbot_readme
    # help:
    when /^\A*(get\s+)?smartbot\s+readme\s*\z/i, /^\A*readme\s+smartbot\s*\z/i
      get_smartbot_readme(dest)

      # help: ----------------------------------------------
      # help: `for NUMBER times every NUMBER minutes COMMAND`
      # help: `for NUMBER times every NUMBER seconds COMMAND`
      # help: `NUMBER times every NUMBER minutes COMMAND`
      # help: `NUMBER times every NUMBER seconds COMMAND`
      # help:    It will run the command every NUMBER minutes or seconds for NUMBER times.
      # help:    max 24 times. min every 10 seconds. max every 60 minutes.
      # help:    Call `quit loop LOOP_ID` to stop the loop.
      # help:       aliases for minutes: m, minute, minutes
      # help:       aliases for seconds: s, sc, second, seconds
      # help:  Examples:
      # help:       _for 5 times every 1 minute ^ruby puts Time.now_
      # help:       _10 times every 30s !ruby puts Time.now_
      # help:       _24 times every 60m !get sales today_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#loops|more info>
      # help: command_id: :create_loop
      # help:

      # help: ----------------------------------------------
      # help: `quit loop LOOP_ID`
      # help:    It will stop the loop with the id LOOP_ID.
      # help:    Only the user who created the loop or an admin can stop it.
      # help:       aliases for loop: iterator, iteration
      # help:       aliases for quit: stop, exit, kill
      # help:  Examples:
      # help:       _quit loop 1_
      # help:       _stop iterator 12_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#loops|more info>
      # help: command_id: :quit_loop
      # help:

      # help: ----------------------------------------------
      # help: `Hi Bot`
      # help: `Hi Smart`
      # help: `Hello Bot` `Hola Bot` `Hallo Bot` `What's up Bot` `Hey Bot` `Hæ Bot`
      # help: `Hello THE_NAME_OF_THE_BOT`
      # help:    Bot starts listening to you if you are on a Bot channel
      # help:    After that if you want to avoid a single message to be treated by the smart bot, start the message by -
      # help:    Also apart of Hello you can use _Hallo, Hi, Hola, What's up, Hey, Hæ_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#how-to-access-the-smart-bot|more info>
      # help: command_id: :hi_bot
      # help:
    when /\A\s*(Hello|Hallo|Hi|Hola|What's\sup|Hey|Hæ)\s+(#{@salutations.join("|")})\s*$/i
      hi_bot(user, dest, user.name, display_name)

      # help: ----------------------------------------------
      # help: `Bye Bot`
      # help: `Bye Smart`
      # help: `Bye NAME_OF_THE_BOT`
      # help:    Bot stops listening to you if you are on a Bot channel
      # help:    Also apart of Bye you can use _Bæ, Good Bye, Adiós, Ciao, Bless, Bless Bless, Adeu_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#how-to-access-the-smart-bot|more info>
      # help: command_id: :bye_bot
      # help:
    when /\A\s*(Bye|Bæ|Good\s+Bye|Adiós|Ciao|Bless|Bless\sBless|Adeu)\s+(#{@salutations.join("|")})\s*$/i
      bye_bot(dest, user, display_name)

      # help: ----------------------------------------------
      # help: `poster MESSAGE`
      # help: `poster :EMOTICON_TEXT: MESSAGE`
      # help: `poster :EMOTICON_TEXT: :EMOTICON_BACKGROUND: MESSAGE`
      # help: `poster MINUTESm MESSAGE`
      # help:     It will create a poster with the message supplied. By default will be autodeleted 1 minute later.
      # help:     If you want the poster to be permanent then use the command `pposter`
      # help:     If minutes supplied then it will be deleted after the minutes specified. Maximum value 60.
      # help:     To see the messages on a mobile phone put the phone on landscape mode
      # help:     Max 15 characters. If the message is longer than that won't be treat it.
      # help:     Only letters from a to z, 0 to 9 and the chars: ? ! - + =
      # help:     To be displayed correctly use words with no more than 6 characters
      # help:     Examples:
      # help:            _poster nice work!_
      # help:            _poster :heart: nice work!_
      # help:            _poster :mac-spinning-wheel: :look: love!_
      # help:            _poster 25m :heart: woah!_
      # help: command_id: :poster
      # help:
    when /\A()poster\s+(\d+m\s+)?(:[^:]+:)\s+(:[^:]+:)(.+)\s*\z/i, /\A()poster\s+(\d+m\s+)?(:.+:)\s+()(.+)\s*\z/i, /\A()poster\s+(\d+m\s+)?()()(.+)\s*\z/i,
         /\A(p)poster\s+()(:[^:]+:)\s+(:[^:]+:)(.+)\s*\z/i, /\A(p)poster\s+()(:.+:)\s+()(.+)\s*\z/i, /\A(p)poster\s+()()()(.+)\s*\z/i
      permanent = $1.to_s != ""
      minutes = $2.to_s
      emoticon_text = $3
      emoticon_bg = $4
      text = $5
      minutes = minutes.scan(/(\d+)/).join

      if minutes == ""
        minutes = 1
      elsif minutes.to_i > 60
        minutes = 60
      end

      save_stats :poster
      if text.to_s.gsub(/\s+/, "").length > 15
        respond "Too long. Max 15 chars", :on_thread
      else
        poster(permanent, emoticon_text, emoticon_bg, text, minutes)
      end

      # help: ----------------------------------------------
      # help: >*<https://github.com/MarioRuiz/slack-smart-bot#announcements|ANNOUNCEMENTS>*
      # help: `add announcement MESSAGE`
      # help: `add red announcement MESSAGE`
      # help: `add green announcement MESSAGE`
      # help: `add yellow announcement MESSAGE`
      # help: `add white announcement MESSAGE`
      # help: `add EMOJI announcement MESSAGE`
      # help:     It will store the message on the announcement list labeled with the color or emoji specified, white by default.
      # help:        aliases for announcement: statement, declaration, message
      # help:  Examples:
      # help:     _add green announcement :heavy_check_mark: All customer services are up and running_
      # help:     _add red declaration Customers db is down :x:_
      # help:     _add yellow statement Don't access the linux server without VPN_
      # help:     _add message `*Party* will start at *20:00* :tada:`_
      # help:     _add :heavy_exclamation_mark: message Pay attention all DB are on maintenance until 20:00 GMT_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#announcements|more info>
      # help: command_id: :add_announcement
      # help:
    when /\A\s*(add|create)\s+(red\s+|green\s+|white\s+|yellow\s+)?(announcement|statement|declaration|message)\s+(.+)\s*\z/i,
         /\A\s*(add|create)\s+(:[\w\-]+:)\s+(announcement|statement|declaration|message)\s+(.+)\s*\z/i
      type = $2.to_s.downcase.strip
      type = "white" if type == ""
      message = $4
      add_announcement(user, type, message)

      # help: ----------------------------------------------
      # help: `delete announcement ID`
      # help:     It will delete the message on the announcement list.
      # help:        aliases for announcement: statement, declaration, message
      # help:  Examples:
      # help:     _delete announcement 24_
      # help:     _delete message 645_
      # help:     _delete statement 77_
      # help:     _delete declaration 334_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#announcements|more info>
      # help: command_id: :delete_announcement
      # help:
    when /\A\s*(delete|remove)\s+(announcement|statement|declaration|message)\s+(\d+)\s*\z/i
      message_id = $3
      delete_announcement(user, message_id)

      # help: ----------------------------------------------
      # help: `see announcements`
      # help: `see red announcements`
      # help: `see green announcements`
      # help: `see yellow announcements`
      # help: `see white announcements`
      # help: `see EMOJI announcements`
      # helpmaster: `see announcements #CHANNEL`
      # helpmaster: `see all announcements`
      # help:     It will display the announcements for the channel.
      # help:        aliases for announcements: statements, declarations, messages
      # helpmaster:        In case #CHANNEL it will display the announcements for that channel. Only master admins can use it from a DM with the Smartbot.
      # helpmaster:        In case 'all' it will display all the announcements for all channels. Only master admins can use it from a DM with the Smartbot.
      # help:  Examples:
      # help:     _see announcements_
      # help:     _see white messages_
      # help:     _see red statements_
      # help:     _see yellow declarations_
      # help:     _see messages_
      # help:     _see :heavy_exclamation_mark: messages_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#announcements|more info>
      # help: command_id: :see_announcements
      # help:
    when /\A\s*see\s+(red\s+|green\s+|white\s+|yellow\s+|:[\w\-]+:\s+)?(announcements|statements|declarations|messages)()\s*\z/i,
         /\A\s*see\s+(all\s+)?(announcements|statements|declarations|messages)()\s*\z/i,
         /\A\s*see\s+(red\s+|green\s+|white\s+|yellow\s+|:[\w\-]+:\s+)?(announcements|statements|declarations|messages)\s+#([\w\-]+)\s*\z/i,
         /\A\s*see\s+(red\s+|green\s+|white\s+|yellow\s+|:[\w\-]+:\s+)?(announcements|statements|declarations|messages)\s+<#(\w+)\|.*>\s*\z/i
      type = $1.to_s.downcase.strip
      channel = $3.to_s

      see_announcements(user, type, channel)

      # help: ----------------------------------------------
      # help: >*<https://github.com/MarioRuiz/slack-smart-bot#share-messages|SHARE MESSAGES>*
      # help: `share messages /REGEXP/ on #CHANNEL`
      # help: `share messages "TEXT" on #CHANNEL`
      # xhelp: `share messages :EMOJI: on #CHANNEL`
      # help:     It will automatically share new messages published that meet the specified criteria.
      # xhelp:     In case :EMOJI: it will share the messages with the indicated reaction.
      # help:     SmartBot user and user adding the share need to be members on both channels.
      # help:     The Regexp will automatically add the parameters /im
      # help:     Only available on public channels.
      # help:  Examples:
      # help:     _share messages /(last\s+|previous\s+)sales\s+results\s+/ on #sales_
      # help:     _share messages "share post" on #announcements_
      # xhelp:     _share messages :tada: on #announcements_
      # xhelp:     _share messages :moneybag: from #sales_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#share-messages|more info>
      # help: command_id: :share_messages
      # help:
    when /\A\s*share\s+messages\s+(\/.+\/|".+"|'.+')\s+on\s+<#\w+\|(.+)>\s*\z/i,
         /\A\s*share\s+messages\s+(\/.+\/|".+"|'.+')\s+on\s+<#(\w+)\|>\s*\z/,
         /\A\s*share\s+messages\s+(\/.+\/|".+"|'.+')\s+on\s+(.+)\s*\z/i
      condition = $1
      channel = $2
      channel.gsub!("#", "") # for the case the channel name is in plain text including #
      channel = @channels_name[channel] if @channels_name.key?(channel)
      channel_from = @channels_name[dest]
      channel_to = channel
      share_messages(user, channel_from, channel_to, condition)

      # help: ----------------------------------------------
      # help: `see shares`
      # help:     It will display the active shares from this channel.
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#share-messages|more info>
      # help: command_id: :see_shares
      # help:
    when /\A\s*see\s+shares\s*\z/i
      see_shares()

      # help: ----------------------------------------------
      # help: `delete share ID`
      # help:     It will delete the share id specified.
      # help:  Examples:
      # help:     _delete share 24_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#share-messages|more info>
      # help: command_id: :delete_share
      # help:
    when /\A\s*(delete|remove)\s+share\s+(\d+)\s*\z/i
      share_id = $2
      delete_share(user, share_id)

      # help: ----------------------------------------------
      # help: >*<https://github.com/MarioRuiz/slack-smart-bot#see-statuses|SEE STATUSES>*
      # help: `see statuses`
      # help: `see statuses #CHANNEL`
      # help: `see status EMOJI`
      # help: `see status EMOJI #CHANNEL`
      # help: `see status EMOJI1 EMOJI99`
      # help: `who is on vacation?`
      # help: `who is on EMOJI`
      # help: `who is on EMOJI #CHANNEL`
      # help: `who is on EMOJI1 EMOJI99`
      # help: `who is not on vacation?`
      # help: `who is not on EMOJI`
      # help: `who is available?`
      # help:     It will display the current statuses of the members of the channel where you are calling the command or on the channel you supply.
      # help:     In case of `who is available?` will show members of the channel that are on line and not on a meeting or vacation or sick.
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#see-statuses|more info>
      # help: command_id: :see_statuses
      # help:
    when /\A\s*(see|get)\s+(statuses)()\s*\z/i,
         /\A\s*(see\s+status|get\s+status|who\s+is\s+on|who\s+are\s+on|who\s+is\s+not\s+on|who\s+are\s+not\s+on)\s+(:[\w\-\:\s]+:)\s*\??()\s*\z/i,
         /\A\s*(who\s+is\s+on|who\s+are\s+on|who\s+is\s+not\s+on|who\s+are\s+not\s+on)\s+(vacation|holiday)\s*\??()\s*\z/i,
         /\A\s*(who\s+is|who\s+are)\s+(available|active)\s*\??()\s*\z/i,
         /\A\s*(see|get)\s+(statuses)\s+#([\w\-]+)\s*\z/i,
         /\A\s*(see\s+status|get\s+status|who\s+is\s+on|who\s+are\s+on|who\s+is\s+not\s+on|who\s+are\s+not\s+on)\s+(:[\w\-\:\s]+:)\s*\??\s+#([\w\-]+)\s*\z/i,
         /\A\s*(who\s+is|who\s+are)\s+(available|active)\s*\??\s+#([\w\-]+)\s*\z/i,
         /\A\s*(who\s+is|who\s+are)\s+(available|active)\s*\??\s+<#(\w+)\|.*>\s*\z/i,
         /\A\s*(who\s+is\s+on|who\s+are\s+on|who\s+is\s+not\s+on|who\s+are\s+not\s+on)\s+(vacation|holiday)\s*\??\s+#([\w\-]+)\s*\z/i,
         /\A\s*(see|get)\s+(statuses)\s+<#(\w+)\|.*>\s*\z/i,
         /\A\s*(see\s+status|get\s+status|who\s+is\s+on|who\s+is\s+not\s+on|who\s+are\s+on|who\s+are\s+not\s+on)\s+(:[\w\-\:\s]+:)\s*\??\s+<#(\w+)\|.*>\s*\z/i,
         /\A\s*(who\s+is\s+on|who\s+is\s+not\s+on|who\s+are\s+on|who\s+are\s+not\s+on)\s+(vacation|holiday)\s*\??\s+<#(\w+)\|.*>\s*\z/i
      not_on = $1.match?(/who\s+(is|are)\s+not\s+on/i)
      type = $2.downcase
      channel = $3.to_s
      if type == "statuses"
        types = []
      elsif type == "vacation" or type == "holiday"
        types = [":palm_tree:"]
      elsif type == "available" or type == "active"
        types = ["available"]
      else
        type.gsub!(" ", "")
        type.gsub!("::", ": :")
        types = type.split(" ")
      end
      see_statuses(user, channel, types, dest, not_on)

      # help: ----------------------------------------------
      # help: >*<https://github.com/MarioRuiz/slack-smart-bot#see-favorite-commands|SEE FAVORITE COMMANDS>*
      # help: `see favorite commands`
      # help: `see my favorite commands`
      # help: `favorite commands`
      # help: `my favorite commands`
      # help:     It will display the favorite commands.
      # help:     aliases for favorite: favourite, most used, fav
      # helpmaster:    You need to set stats to true to generate the stats when running the bot instance and get this info.
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#see-favorite-commands|more info>
      # help: command_id: :see_favorite_commands
      # help:
    when /\A\s*(see\s+)?(my\s+)?(fav|favorite|favourite|most\s+used)\s+commands\s*\z/i
      only_mine = $2.to_s != ""
      see_favorite_commands(user, only_mine)

      # help: ----------------------------------------------
      # help: >*<https://github.com/MarioRuiz/slack-smart-bot#control-who-has-access-to-a-command|CONTROL ACCESS TO COMMANDS>*
      # helpadmin: `add admin @user`
      # helpadmin:     It will add @user as an admin of the channel.
      # helpadmin:     Only creator of the channel, admins and master admins can use this command.
      # helpadmin:    <https://github.com/MarioRuiz/slack-smart-bot#bot-management|more info>
      # helpadmin: command_id: :add_admin
      # helpadmin:
    when /\A\s*add\s+admin\s+<@(\w+)>\s*\z/i
      admin_user = $1
      add_admin(user, admin_user)

      # help: ----------------------------------------------
      # help: `see admins`
      # help: `show admins`
      # help: `who are admins?`
      # help:     It will show who are the admins of the channel.
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#bot-management|more info>
      # help: command_id: :see_admins
      # help:
    when /\A\s*(see|show)\s+admins\s*\z/i, /\A\s*who\s+are\s+(the\s+)?admins\??\s*\z/i
      see_admins()

      # helpadmin: ----------------------------------------------
      # helpadmin: `remove admin @user`
      # helpadmin:     It will remove the admin privileges for @user on the channel.
      # helpadmin:     Only creator of the channel, admins and master admins can use this command.
      # helpadmin:    <https://github.com/MarioRuiz/slack-smart-bot#bot-management|more info>
      # helpadmin: command_id: :remove_admin
      # helpadmin:
    when /\A\s*(remove|delete)\s+admin\s+<@(\w+)>\s*\z/i
      admin_user = $2
      remove_admin(user, admin_user)

      # helpadmin: ----------------------------------------------
      # helpadmin: `see command ids`
      # helpadmin:     It will display all available command ids.
      # helpadmin:     The command id can be used on `bot stats command COMMAND_ID`, `allow access COMMAND_ID` and `deny access COMMAND_ID`
      # helpadmin:     Only creator of the channel, admins and master admins can use this command.
      # helpadmin:    <https://github.com/MarioRuiz/slack-smart-bot#bot-management|more info>
      # helpadmin: command_id: :see_command_ids
      # helpadmin:
    when /\A\s*(see|display|get)\s+command(\s+|_)ids?\s*\z/i
      see_command_ids()

      # helpadmin: ----------------------------------------------
      # helpadmin: `allow access COMMAND_ID`
      # helpadmin: `allow access COMMAND_ID @user1 @user99`
      # helpadmin:     It will allow the specified command to be used on the channel.
      # helpadmin:     If @user specified, only those users will have access to the command.
      # helpadmin:     Only admins of the channel can use this command
      # helpadmin:    <https://github.com/MarioRuiz/slack-smart-bot#control-who-has-access-to-a-command|more info>
      # helpadmin: command_id: :allow_access
      # helpadmin:
    when /\A\s*(allow|give)\s+access\s+(\w+)\s+(.+)\s*\z/i, /\A\s*(allow|give)\s+access\s+(\w+)()\s*\z/i
      command_id = $2.downcase
      opt = $3.to_s.split(" ")
      allow_access(user, command_id, opt)

      # helpadmin: ----------------------------------------------
      # helpadmin: `deny access COMMAND_ID`
      # helpadmin:     It won't allow the specified command to be used on the channel.
      # helpadmin:     Only admins of the channel can use this command
      # helpadmin:    <https://github.com/MarioRuiz/slack-smart-bot#control-who-has-access-to-a-command|more info>
      # helpadmin: command_id: :deny_access
      # helpadmin:
    when /\A\s*deny\s+access(\s+rights)?\s+(\w+)\s*\z/i
      command_id = $2.downcase
      deny_access(user, command_id)

      # help: ----------------------------------------------
      # help: `see access COMMAND_ID`
      # help:     It will show the access rights for the specified command.
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#control-who-has-access-to-a-command|more info>
      # help: command_id: :see_access
      # help:
    when /\A\s*(see|show)\s+access(\s+rights)?\s+(.+)\s*\z/i
      command_id = $3.downcase
      see_access(command_id)

      # help: ----------------------------------------------
      # help: >*<https://github.com/MarioRuiz/slack-smart-bot#teams|TEAMS>*
      # help: `add team TEAM_NAME members #TEAM_CHANNEL CHANNEL_TYPE #CHANNEL1 #CHANNEL99 : INFO`
      # help: `add team TEAM_NAME MEMBER_TYPE @USER1 @USER99 CHANNEL_TYPE #CHANNEL1 #CHANNEL99 : INFO`
      # help: `add team TEAM_NAME MEMBER_TYPE1 @USER1 @USER99 MEMBER_TYPE99 @USER1 @USER99 CHANNEL_TYPE1 #CHANNEL1 #CHANNEL99 CHANNEL_TYPE99 #CHANNEL1 #CHANNEL99 : INFO`
      # help:     It will add a team with the info supplied.
      # help:     TEAM_NAME, TYPE: one word, a-z, A-Z, 0-9, - and _
      # help:     In case it is supplied a channel with type 'members' the members of that channel would be considered members of the team. The SmartBot needs to be a member of the channel.
      # help:  Examples:
      # help:     _add team sales members #sales support #sales-support public #sales-ff : Contact us if you need anything related to Sales. You can also send us an email at [email protected]_
      # help:     _add team Sales manager @ann qa @peter @berglind dev @john @sam @marta members #sales support #sales-support public #sales-ff : Contact us if you need anything related to Sales. You can also send us an email at [email protected]_
      # help:     _add team devweb qa @jim dev @johnja @cooke @luisa members #devweb support #devweb-support : We take care of the website_
      # help:     _add team sandex manager @sarah members #sandex : We take care of the sand_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
      # help: command_id: :add_team
      # help:
    when /\A\s*add\s+team\s+([\w\-]+)\s+([^:]+)\s*:\s+(.+)\s*\z/im, /\A\s*add\s+([\w\-]+)\s+team\s+([^:]+)\s*:\s+(.+)\s*\z/im
      name = $1.downcase
      options = $2
      info = Thread.current[:command_orig].to_s.gsub("\u00A0", " ").scan(/^[^:]+:\s*(.+)\s*$/im).join
      add_team(user, name, options, info)

      # help: ----------------------------------------------
      # help: `add TYPE to TEAM_NAME team : MESSAGE`
      # help: `add private TYPE to TEAM_NAME team : MESSAGE`
      # help: `add personal TYPE to TEAM_NAME team : MESSAGE`
      # help: `add TYPE to TEAM_NAME team TOPIC : MESSAGE`
      # help: `add private TYPE to TEAM_NAME team TOPIC : MESSAGE`
      # help: `add personal TYPE to TEAM_NAME team TOPIC : MESSAGE`
      # help:     It will add a memo to the team. The memos will be displayed with the team info.
      # help:     Only team members can add a memo.
      # help:     TYPE: memo, note, issue, task, feature, bug, jira, github
      # help:     TOPIC: one word, a-z, A-Z, 0-9, - and _
      # help:     If private then the memo will be only displayed to team members on a DM or the members channel.
      # help:     If personal then the memo will be only displayed to the creator on a DM.
      # help:     In case jira type supplied:
      # help:       The message should be an JQL URL, JQL string or an issue URL.
      # help:       To be able to use it you need to specify on the SmartBot settings the credentials.
      # help:       In case no TOPIC is supplied then it will create automatically the topics from the labels specified on every JIRA issue
      # help:     In case github type supplied:
      # help:       The message should be a github URL. You can filter by state (open/closed/all) and labels
      # help:       To be able to use it you need to specify on the SmartBot settings the github token.
      # help:       In case no TOPIC is supplied then it will create automatically the topics from the labels specified on every Github issue
      # help:  Examples:
      # help:     _add memo to sales team : Add tests for Michigan feature_
      # help:     _add private note to sales team : Bills will need to be deployed before Friday_
      # help:     _add memo to dev team web : Check last version_
      # help:     _add private bug to dev team SRE : Logs should not be accessible from outside VPN_
      # help:     _add memo sales team : Add tests for Michigan feature_
      # help:     _add memo team sales: Add tests for Michigan feature_
      # help:     _add personal memo team sales: Check my vacations_
      # help:     _add jira to sales team : labels = SalesT AND status != Done_
      # help:     _add github to sales team : PeterBale/SalesBoom/issues?state=open&labels=bug_
      # help:     _add github to sales team dev: PeterBale/DevProject/issues/71_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
      # help: command_id: :add_memo_team
      # help:
    when /\A\s*add\s+(private\s+|personal\s+)?(memo|note|issue|task|feature|bug|jira|github)\s+(to\s+)?team\s+([\w\-]+)\s*([^:]+)?\s*:\s+(.+)\s*\z/im,
         /\A\s*add\s+(private\s+|personal\s+)?(memo|note|issue|task|feature|bug|jira|github)\s+(to\s+)?([\w\-]+)\s+team\s*([^:]+)?\s*:\s+(.+)\s*\z/im
      privacy = $1.to_s.strip.downcase
      type = $2.downcase
      team_name = $4.downcase
      topic = $5.to_s.strip
      message = Thread.current[:command_orig].to_s.gsub("\u00A0", " ").scan(/^[^:]+:\s*(.+)\s*$/im).join
      add_memo_team(user, privacy, team_name, topic, type, message)

      # help: ----------------------------------------------
      # help: `delete memo ID from TEAM_NAME team`
      # help:     It will delete the supplied memo ID on the team specified.
      # help:     aliases for memo: note, issue, task, feature, bug, jira, github
      # help:     You have to be a member of the team, the creator or a Master admin to be able to delete a memo.
      # help:  Examples:
      # help:     _delete memo 32 from sales team_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
      # help: command_id: :delete_memo_team
      # help:
    when /\A\s*(delete|remove)\s+(memo|note|issue|task|feature|bug|jira|github)\s+(\d+)\s+(from|on)\s+team\s+([\w\-]+)\s*\z/i,
         /\A\s*(delete|remove)\s+(memo|note|issue|task|feature|bug|jira|github)\s+(\d+)\s+(from|on)\s+([\w\-]+)\s+team\s*\z/i
      memo_id = $3
      team_name = $5.downcase
      delete_memo_team(user, team_name, memo_id)

      # help: ----------------------------------------------
      # help: `set memo ID on TEAM_NAME team STATUS`
      # help: `set STATUS on memo ID TEAM_NAME team`
      # help:     It will assign to the ID specified the emoticon status indicated.
      # help:     aliases for memo: note, issue, task, feature, bug
      # help:     This command will be only for memo, note, issue, task, feature, bug. Not for jira or github.
      # help:     You have to be a member of the team, the creator or a Master admin to be able to set a status.
      # help:  Examples:
      # help:     _set memo 32 on sales team :runner:_
      # help:     _set bug 7 on team sales :heavy_check_mark:_
      # help:     _set :runner: on memo 6 sales team_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
      # help: command_id: :set_memo_status
      # help:
    when /\A\s*(set)\s+(memo|note|issue|task|feature|bug)\s+(\d+)\s+on\s+team\s+([\w\-]+)\s+(:[\w\-]+:)\s*\z/i,
         /\A\s*(set)\s+(memo|note|issue|task|feature|bug)\s+(\d+)\s+on\s+([\w\-]+)\s+team\s+(:[\w\-]+:)\s*\z/i
      memo_id = $3
      team_name = $4.downcase
      status = $5
      set_memo_status(user, team_name, memo_id, status)
    when /\A\s*(set)\s+(:[\w\-]+:)\s+on\s+(memo|note|issue|task|feature|bug)\s+(\d+)\s+team\s+([\w\-]+)\s*\z/i,
         /\A\s*(set)\s+(:[\w\-]+:)\s+on\s+(memo|note|issue|task|feature|bug)\s+(\d+)\s+([\w\-]+)\s+team\s*\z/i
      memo_id = $4
      team_name = $5.downcase
      status = $2
      set_memo_status(user, team_name, memo_id, status)

      # help: ----------------------------------------------
      # help: `team TEAM_NAME memo ID MESSAGE`
      # help: `TEAM_NAME team memo ID MESSAGE`
      # help:     It will add a comment to the memo ID specified on the team specified.
      # help:     aliases for memo: note, issue, task, feature, bug
      # help:  Examples:
      # help:     _sales team memo 32 I have a question, is there any public data published?_
      # help:     _dev team task 77 putting this on hold until we get more info_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
      # help: command_id: :add_memo_team_comment
      # help:
    when /\A\s*team\s+([\w\-]+)\s+(memo|note|issue|task|feature|bug)\s+(\d+)\s+(.+)\s*\z/im,
         /\A\s*([\w\-]+)\s+team\s+(memo|note|issue|task|feature|bug)\s+(\d+)\s+(.+)\s*\z/im
      team_name = $1.downcase
      memo_id = $3
      message = $4.to_s.strip
      add_memo_team_comment(user, team_name, memo_id, message)

      # help: ----------------------------------------------
      # help: `team TEAM_NAME memo ID`
      # help: `TEAM_NAME team memo ID`
      # help:     It will show the memo ID specified on the team specified.
      # help:     aliases for memo: note, issue, task, feature, bug
      # help:  Examples:
      # help:     _sales team memo 32_
      # help:     _dev team task 77_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
      # help: command_id: :see_memo_team
      # help:
    when /\A\s*team\s+([\w\-]+)\s+(memo|note|issue|task|feature|bug)\s+(\d+)\s*\z/i,
         /\A\s*([\w\-]+)\s+team\s+(memo|note|issue|task|feature|bug)\s+(\d+)\s*\z/i
      team_name = $1.downcase
      memo_id = $3
      see_memo_team(user, team_name, memo_id)

      # help: ----------------------------------------------
      # help: `see teams`
      # help: `see team TEAM_NAME`
      # help: `team TEAM_NAME`
      # help: `TEAM_NAME team`
      # help: `TEAM_NAME team TOPIC`
      # help: `which team @USER`
      # help: `which team #CHANNEL`
      # help: `which team TEXT_TO_SEARCH_ON_INFO`
      # help: `which team does @USER belongs to?`
      # help:     It will display all teams or the team specified.
      # help:     In case a specific team it will show also the availability of the members.
      # help:     TOPIC: It will filter members, channels and memos by topic.
      # help:  Examples:
      # help:     _see teams_
      # help:     _see team Sales_
      # help:     _Dev team_
      # help:     _Sales team dev_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
      # help: command_id: :see_teams
      # help:

      # help: ----------------------------------------------
      # help: `update team TEAM_NAME NEW_TEAM_NAME`
      # help: `update team TEAM_NAME : NEW_INFO`
      # help: `update team TEAM_NAME add MEMBER_TYPE @USER`
      # help: `update team TEAM_NAME add CHANNEL_TYPE #CHANNEL`
      # help: `update team TEAM_NAME delete MEMBER_TYPE @USER`
      # help: `update team TEAM_NAME delete CHANNEL_TYPE #CHANNEL`
      # help: `update team TEAM_NAME delete @USER`
      # help: `update team TEAM_NAME delete #CHANNEL`
      # help:     It will update a team with the info supplied.
      # help:     You have to be a member of the team, the creator or a Master admin to be able to update a team.
      # help:  Examples:
      # help:     _update team sales salesff_
      # help:     _update team salesff : Support for customers_
      # help:     _update sales team delete @sarah @peter_
      # help:     _update team sales delete public #sales_
      # help:     _update team sales delete #sales_support_
      # help:     _update sales team add public #salesff_
      # help:     _update sales team add qa @john @ben @ana_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
      # help: command_id: :update_team
      # help:
    when /\A\s*update\s+team\s+([\w\-]+)\s+([\w\-]+)\s*\z/i, /\A\s*update\s+([\w\-]+)\s+team\s+([\w\-]+)\s*\z/i
      name = $1.downcase
      new_name = $2.downcase
      update_team(user, name, new_name: new_name)
    when /\A\s*update\s+team\s+([\w\-]+)\s*:\s+(.+)\s*\z/im, /\A\s*update\s+([\w\-]+)\s+team\s*:\s+(.+)\s*\z/im
      name = $1.downcase
      new_info = Thread.current[:command_orig].to_s.gsub("\u00A0", " ").scan(/^[^:]+:\s*(.+)\s*$/im).join
      update_team(user, name, new_info: new_info)
    when /\A\s*update\s+team\s+([\w\-]+)\s+(delete|remove)\s+(.+)\s*\z/i, /\A\s*update\s+([\w\-]+)\s+team\s+(delete|remove)\s+(.+)\s*\z/i
      name = $1.downcase
      delete_opts = $3
      update_team(user, name, delete_opts: delete_opts)
    when /\A\s*update\s+team\s+([\w\-]+)\s+add\s+(.+)\s*\z/i, /\A\s*update\s+([\w\-]+)\s+team\s+add\s+(.+)\s*\z/i
      name = $1.downcase
      add_opts = $2
      update_team(user, name, add_opts: add_opts)

      # help: ----------------------------------------------
      # help: `ping team TEAM_NAME MEMBER_TYPE MESSAGE`
      # help: `contact team TEAM_NAME MEMBER_TYPE MESSAGE`
      # help:     ping: It will send the MESSAGE naming all available members of the MEMBER_TYPE supplied.
      # help:     contact: It will send the MESSAGE naming all members of the MEMBER_TYPE supplied.
      # help:     In case MEMBER_TYPE is 'all' it will name 10 random members of the team
      # help:  Examples:
      # help:     _ping team sales development How is the status on the last feature_
      # help:     _contact team sales qa Please finish testing of dev02 feature before noon_
      # help:     _contact team qa all Check the test failures please_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
      # help: command_id: :ping_team
      # help: command_id: :contact_team
      # help:
    when /\A\s*(contact|ping)\s+team\s+([\w\-]+)\s+([\w\-]+)\s+(.+)\s*\z/im, /\A\s*(contact|ping)\s+([\w\-]+)\s+team\s+([\w\-]+)\s+(.+)\s*\z/im
      type = $1.downcase.to_sym
      name = $2.downcase
      member_type = $3.downcase
      message = Thread.current[:command_orig].to_s.gsub("\u00A0", " ").gsub(/\A\^/, "").gsub(/\A!!/, "").gsub(/\A!/, "").scan(/\A\s*[\w]+\s+\w+\s+team\s+[\w\-]+\s+(.+)\s*\z/im).join
      if message == ""
        message = Thread.current[:command_orig].to_s.gsub("\u00A0", " ").gsub(/\A\^/, "").gsub(/\A!!/, "").gsub(/\A!/, "").scan(/\A\s*[\w]+\s+team\s+\w+\s+[\w\-]+\s+(.+)\s*\z/im).join
      end
      ping_team(user, type, name, member_type, message)

      # help: ----------------------------------------------
      # help: `delete team TEAM_NAME`
      # help:     It will delete the supplied team.
      # help:     You have to be a member of the team, the creator or a Master admin to be able to delete a team.
      # help:  Examples:
      # help:     _delete team sales_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
      # help: command_id: :delete_team
      # help:
    when /\A\s*(delete|remove)\s+team\s+([\w\-]+)\s*\z/i, /\A\s*(delete|remove)\s+([\w\-]+)\s+team\s*\z/i
      name = $2.downcase
      delete_team(user, name)
    when /\A\s*(see\s+)?(vacations|time\s+off)\s+team\s+([\w\-]+)\s*(\d\d\d\d\/\d\d\/\d\d)?\s*\z/i,
         /\A\s*(see\s+)?(vacations|time\s+off)\s+([\w\-]+)\s+team\s*(\d\d\d\d\/\d\d\/\d\d)?\s*\z/i,
         /\A\s*(see\s+)?(vacations|time\s+off)\s+team\s+([\w\-]+)\s*(\d\d\d\d-\d\d-\d\d)?\s*\z/i,
         /\A\s*(see\s+)?(vacations|time\s+off)\s+([\w\-]+)\s+team\s*(\d\d\d\d-\d\d-\d\d)?\s*\z/i
      team_name = $3.downcase
      date = $4.to_s
      date = Date.today.strftime("%Y/%m/%d") if date.empty?
      react :running
      see_vacations_team(user, team_name, date)
      unreact :running
    when /\A\s*(which|search)\s+teams?\s+(is\s+)?(.+)\??\s*\z/i, /\A\s*which\s+team\s+does\s+()()(.+)\s+belongs\s+to\??\s*\z/i
      search = $3.to_s.downcase
      see_teams(user, "", search)
    when /\A\s*see\s+teams?\s*([\w\-]+)?\s*()\z/i,
         /\A\s*team\s+([\w\-]+)\s*()\z/i, /\A\s*([\w\-]+)\s+team\s*()\z/i,
         /\A\s*team\s+([\w\-]+)\s+([\w\-]+)\z/i, /\A\s*([\w\-]+)\s+team\s+([\w\-]+)\z/i,
         /\A\s*see\s+all\s+teams\s*()()\z/i
      name = $1.to_s.downcase
      type = $2.to_s.downcase
      see_teams(user, name, ttype: type)

      # help: ----------------------------------------------
      # help: `see MEMO_TYPE from TEAM_NAME team`
      # help: `see MEMO_TYPE from TEAM_NAME team TOPIC`
      # help: `see all memos from TEAM_NAME team`
      # help: `see all memos from TEAM_NAME team TOPIC`
      # help:     It will show the memos of the team.
      # help:     If TOPIC is supplied it will show the memos of the topic.
      # help:     MEMO_TYPE: memos, notes, issues, tasks, features, bugs, jira, github. In case of 'all memos' will display all of any type.
      # help:  Examples:
      # help:     _see memos from sales team_
      # help:     _see bugs from sales team_
      # help:     _see all memos from sales team webdev_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#teams|more info>
      # help: command_id: :see_memos_team
      # help:
    when /\A\s*(see\s+)?(memo|note|issue|task|feature|bug|jira|github|all\s+memo)s?\s+(from\s+)?([\w\-]+)\s+team(.*)\s*\z/i,
         /\A\s*(see\s+)?(memo|note|issue|task|feature|bug|jira|github|all\s+memo)s?\s+(from\s+)?team\s+([\w\-]+)(.*)\s*\z/i
      type = $2.downcase.to_sym
      name = $4.downcase
      topic = $5.strip
      see_memos_team(user, type: type, name: name, topic: topic)

      # help: ----------------------------------------------
      # help: >*<https://github.com/MarioRuiz/slack-smart-bot#time-off-management|TIME OFF MANAGEMENT>*
      # help: `add vacation from YYYY/MM/DD to YYYY/MM/DD`
      # help: `add vacation YYYY/MM/DD`
      # help: `add sick from YYYY/MM/DD to YYYY/MM/DD`
      # help: `add sick YYYY/MM/DD`
      # help: `add sick child YYYY/MM/DD`
      # help: `I'm sick today`
      # help: `I'm on vacation today`
      # help:     It will add the supplied period to your plan.
      # help:     Instead of YYYY/MM/DD you can use 'today' or 'tomorrow' or 'next week'
      # help:     To see your plan call `see my time off`
      # help:     If you want to see the vacation plan for the team `see team NAME`
      # help:     Also you can see the vacation plan for the team for a specific period: `vacations team NAME YYYY/MM/DD`
      # help:     The SmartBot will automatically set the users status to :palm_tree:, :baby: or :face_with_thermometer: and the expiration date.
      # help:  Examples:
      # help:     _add vacation from 2022/10/01 to 2022/10/22_
      # help:     _add sick 2022/08/22_
      # help:     _add vacation tomorrow_
      # help:     _I'll be on vacation next week_
      # help:     _add sick baby today_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#time-off-management|more info>
      # help: command_id: :add_vacation
      # help:
    when /\A\s*(add)\s+(sick|vacation|sick\s+baby|sick\s+child)\s+from\s+(\d\d\d\d\/\d\d\/\d\d)\s+to\s+(\d\d\d\d\/\d\d\/\d\d)\s*\z/i,
         /\A\s*(add)\s+(sick|vacation|sick\s+baby|sick\s+child)\s+from\s+(\d\d\d\d-\d\d-\d\d)\s+to\s+(\d\d\d\d-\d\d-\d\d)\s*\z/i,
         /\A\s*(add)\s+(sick|vacation|sick\s+baby|sick\s+child)\s+(\d\d\d\d-\d\d-\d\d)()\s*\z/i,
         /\A\s*(add)\s+(sick|vacation|sick\s+baby|sick\s+child)\s+(\d\d\d\d\/\d\d\/\d\d)()\s*\z/i,
         /\A\s*(add)\s+(sick|vacation|sick\s+baby|sick\s+child)\s+(today|tomorrow|next\sweek)()\s*\z/i,
         /\A\s*(I'm|I\s+am|I'll\s+be|I\s+will\s+be)\s+(sick|on\s+vacation)\s+(today|tomorrow|next\sweek)()\s*\z/i
      type = $2
      from = $3.downcase
      to = $4
      type = "vacation" if type.match?(/vacation/)
      add_vacation(user, type, from, to)

      # help: ----------------------------------------------
      # help: `remove vacation ID`
      # help: `remove vacation period ID`
      # help: `remove sick period ID`
      # help: `remove time off period ID`
      # help: `delete vacation ID`
      # help:     It will remove the specified period from your vacations/sick periods.
      # help:  Examples:
      # help:     _remove vacation 20_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#time-off-management|more info>
      # help: command_id: :remove_vacation
      # help:
    when /\A\s*(delete|remove)\s+(vacation|sick|time\s+off)(\s+period)?\s+(\d+)\s*\z/i
      vacation_id = $4.to_i
      remove_vacation(user, vacation_id)

      # help: ----------------------------------------------
      # help: `see my vacations`
      # help: `see my time off`
      # help: `see vacations @USER`
      # help: `see my vacations YEAR`
      # help: `time off`
      # help:     It will display current and past time off.
      # help:     If you call this command on a DM, it will show your vacations for the year on a calendar.
      # help:     see: (optional)
      # help: Examples:
      # help:     _see my vacations_
      # help:     _see my time off 2022_
      # help:     _see vacations @john_
      # help:     _time off 2023_
      # help:     _time off @johan 2024_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#time-off-management|more info>
      # help: command_id: :see_vacations
      # help:
    when /\A\s*(see\s+)?(my\s+)?vacations\s*()\s*(\d{4})?\s*\z/i,
         /\A\s*(see\s+)?(my\s+)?time\s*off\s*()\s*(\d{4})?\s*\z/i,
         /\A\s*(see\s+)()?time\s*off\s+<@(\w+)>\s*\s*(\d{4})?\s*\z/i,
         /\A\s*(see\s+)()?vacations\s+<@(\w+)>\s*(\d{4})?\s*\z/i
      from_user = $3
      year = $4
      react :running
      see_vacations(user, dest, from_user: from_user, year: year)
      unreact :running

      # help: ----------------------------------------------
      # help: `vacations team NAME`
      # help: `time off team NAME`
      # help: `vacations team NAME YYYY/MM/DD`
      # help: `time off team NAME YYYY/MM/DD`
      # help:     It will display the time off plan for the team specified.
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#time-off-management|more info>
      # help: command_id: :see_vacations_team
      # help:

      # help: ----------------------------------------------
      # help: `public holidays COUNTRY`
      # help: `public holidays COUNTRY/STATE DATE`
      # help: `calendar COUNTRY`
      # help: `calendar COUNTRY/STATE DATE`
      # help:     It will display the public holidays for the country specified.
      # help:     If calendar then it will show the calendar for the country specified.
      # help:     STATE: optional. If not specified, it will return all the holidays for the country.
      # help:     DATE: optional. It can be supplied as YYYY or YYYY-MM or YYYY-MM-DD. If not specified, it will return all the holidays for current year.
      # help: Examples:
      # help:     _public holidays United States_
      # help:     _public holidays United States/California_
      # help:     _public holidays United States/California 2023_
      # help:     _public holidays Iceland 2023-12_
      # help:     _public holidays India 2023-12-25_
      # help:     _calendar United States/California_
      # help: command_id: :public_holidays
      # help: command_id: :calendar_country
      # help:
    when /\A\s*(public\s+)?(holiday?|vacation|calendar)s?\s+(in\s+|on\s+)?([a-zA-Z\s]+)()()()()\s*\z/i,
         /\A\s*(public\s+)?(holiday?|vacation|calendar)s?\s+(in\s+|on\s+)?([a-zA-Z\s]+)\/([a-zA-Z\s]+)()()()\s*\z/i,
         /\A\s*(public\s+)?(holiday?|vacation|calendar)s?\s+(in\s+|on\s+)?([a-zA-Z\s]+)\/([a-zA-Z\s]+)\s+(\d{4})[\/\-]?(\d\d)?[\/\-]?(\d\d)?\s*\z/i,
         /\A\s*(public\s+)?(holiday?|vacation|calendar)s?\s+(in\s+|on\s+)?([a-zA-Z\s]+)()\s+(\d{4})[\/\-]?(\d\d)?[\/\-]?(\d\d)?\s*\z/i
      type = $2.downcase
      country = $4
      state = $5.to_s
      year = $6.to_s
      month = $7.to_s
      day = $8.to_s
      year = Date.today.year if year.to_s == ""
      if type == "calendar"
        save_stats :calendar_country
        state = "/#{state.downcase}" if state.to_s != ""
        display_calendar("", year, country_region: "#{country.downcase}#{state}")
      else
        public_holidays(country, state, year, month, day)
      end

      # help: ----------------------------------------------
      # help: `set public holidays to COUNTRY/STATE`
      # help:     It will set the public holidays for the country and state specified.
      # help:     If STATE is not specified, it will set the public holidays for the country.
      # help: Examples:
      # help:     _set public holidays to Iceland_
      # help:     _set public holidays to United States/California_
      # help: command_id: :set_public_holidays
      # help:
    when /\A\s*set\s+public\s+(holiday?|vacation)s?\s+to\s+([^\/]+)\/([^\/]+)\s*\z/i,
         /\A\s*set\s+public\s+(holiday?|vacation)s?\s+to\s+([^\/]+)\s*\z/i
      country = $2
      state = $3.to_s
      set_public_holidays(country, state, user)

      # help: ----------------------------------------------
      # help: >*<https://github.com/MarioRuiz/slack-smart-bot#personal-settings|PERSONAL SETTINGS>*
      # help: `set personal settings SETTINGS_ID VALUE`
      # help: `delete personal settings SETTINGS_ID`
      # help: `get personal settings SETTINGS_ID`
      # help: `get personal settings`
      # help:     It will set/delete/get the personal settings supplied for the user.
      # help: Examples:
      # help:     _set personal settings ai.open_ai.access_token Xd33-343sAAddd42-3JJkjC0_
      # help: command_id: :set_personal_settings
      # help: command_id: :delete_personal_settings
      # help: command_id: :get_personal_settings
      # help:
    when /\A\s*(set)\s+personal\s+(setting|config)s?\s+([\w\.]+)(\s*=?\s*)(.+)\s*\z/i,
         /\A\s*(delete|get)\s+personal\s+(setting|config)s?\s+([\w\.]+)()()\s*\z/i,
         /\A\s*(get)\s+personal\s+(setting|config)s?()()()\s*\z/i
      settings_type = $1.downcase.to_sym
      settings_id = $3.downcase
      settings_value = $5.to_s
      personal_settings(user, settings_type, settings_id, settings_value)

      # help: ----------------------------------------------
      # help: >*<https://github.com/MarioRuiz/slack-smart-bot#openai|OpenAI>*
      # help: `?w`
      # help: `?w PROMPT`
      # help:     OpenAI: It will transcribe the audio file attached and performed the PROMPT indicated if supplied.
      # help: Examples:
      # help:     _?w_
      # help:     _?w translate to spanish_
      # help:     _?w what is this about?_
      # help:     _?w translate to english and tell me what is about_
      # help: command_id: :open_ai_whisper
      # help:
    when /\A\s*\?w()\s*\z/im, /\A\s*\?w\s+(.+)\s*\z/im
      message = $1.to_s.downcase
      open_ai_whisper(message, files)

      # help: ----------------------------------------------
      # help: `?m`
      # help: `?m MODEL`
      # help: `chatgpt models`
      # help: `?m chatgpt`
      # help:     OpenAI: It will return the list of models available or the details of the model indicated.
      # help:             If chatgpt models is used, it will return the list of chatgpt models available.
      # help: Examples:
      # help:     _?m_
      # help:     _?m gpt-3.5-turbo_
      # help:    _chatgpt models_
      # help: command_id: :open_ai_models
      # help:
    when /\A\s*\?m()\s*\z/im, /\A\s*\?m\s+(.+)\s*\z/im, /\A\s*(chatgpt)\s+models\s*\z/im
      model = $1.to_s.downcase
      open_ai_models(model)

      # help: ----------------------------------------------
      # help: `??i PROMPT`
      # help: `?i PROMPT`
      # help: `?ir`
      # help: `?iv`
      # help: `?ivNUMBER`
      # help: `?ie PROMPT`
      # help:     OpenAI: i PROMPT -> It will generate an image based on the PROMPT indicated.
      # help:             If ??i is used, it will start from zero the session. If not all the previous prompts from the session will be used to generate the image.
      # help:     OpenAI: ir -> It will generate a new image using the session prompts.
      # help:     OpenAI: iv -> It will generate a variation of the last image generated.
      # help:             ivNUMBER -> It will generate the NUMBER of variations of the last image generated. NUMBER need to be between 1 and 9.
      # help:             If an image attached then it will generate the variations of the attached image.
      # help:     OpenAI: ie PROMPT -> It will edit the attached image with the supplied PROMPT. The supplied image need to be an image with a transparent area.
      # help:             The PROMPT need to explain the final result of the image.
      # help: Examples:
      # help:     _??i man jumping from a red bridge_
      # help:     _?i wearing a red hat and blue shirt_
      # help:     _?ir_
      # help:     _?iv5_
      # help:     _?ie woman eating a blue apple_
      # help: command_id: :open_ai_generate_image
      # help: command_id: :open_ai_variations_image
      # help: command_id: :open_ai_edit_image
      # help:
    when /\A\s*()\?i()\s\s*(.+)\s*\z/im, /\A\s*(\?\?)i()\s\s*(.+)\s*\z/im, /\A\s*()\?i(r)()\s*\z/im,
         /\A\s*()\?i(v)()\s*\z/im, /\A\s*()\?i(v\d+)()\s*\z/im,
         /\A\s*()\?i(e)\s+(.+)\s*\z/im,
         /\A\s*()\?i(e)()\s*\z/im,
         /\A\s*(\?\?)i()()\s*\z/im
      delete_history = $1.to_s != ""
      image_opt = $2.to_s.downcase
      message = $3.to_s
      if image_opt == "" or image_opt == "r"
        open_ai_generate_image(message, delete_history, repeat: image_opt == "r")
      elsif image_opt.match?(/v\d*/i)
        variations = image_opt.match(/v(\d*)/i)[1].to_i
        open_ai_variations_image(message, variations, files)
      elsif image_opt == "e"
        open_ai_edit_image(message, files)
      end

      # help: ----------------------------------------------
      # help: `?? PROMPT`
      # help: `? PROMPT`
      # help: `?? use model MODEL_NAME`
      # help: `chatGPT SESSION_NAME`
      # help: `chatGPT SESSION_NAME "DESCRIPTION"`
      # help: `chatGPT SESSION_NAME >TAG_NAME`
      # help: `chatGPT SESSION_NAME >TAG_NAME "DESCRIPTION"`
      # help: `chatGPT SESSION_NAME MODEL_NAME`
      # help: `chatGPT SESSION_NAME MODEL_NAME >TAG_NAME`
      # help: `chatGPT SESSION_NAME MODEL_NAME "DESCRIPTION"`
      # help: `chatGPT SESSION_NAME MODEL_NAME >TAG_NAME "DESCRIPTION"`
      # help:     OpenAI: Chat GPT will generate a response based on the PROMPT indicated.
      # help:             SESSION_NAME: is the name of the session to use. Only a-Z, 0-9, hyphens, and underscore are allowed.
      # help:             When using 'chatGPT SESSION_NAME' in case the name doesn't exist it will create a new session with the name indicated, if exists it will continue the session.
      # help:             When starting a new session, if you ask SmartBot to answer on a Thread by using !! or ^, then it won't be necessary to send ? before the prompt.
      # help:                In this case, every single message you send will be considered a prompt to be treated.
      # help:                After 30 minutes of inactivity SmartBot will stop listening to the thread. You will need to continue the session after that.
      # help:                If you want to avoid a message to be treated then start it with a hyphen '-'.
      # help:                To add a collaborator when on a thread, you can use directly `add collaborator @USER`
      # help:                To change the model when on a thread, you can use directly `use model MODEL_NAME`
      # help:             If ?? is used, it will start from zero the temporary session. If not all the previous prompts from the session will be used to generate the response.
      # help:             You can share a message and use it as input for the supplied PROMPT.
      # help:             If you include in the prompt !URL then it will download and use the content of the URL as input for the prompt.
      # help:             If the URL is a Google Drive link, it will download the content of the file.
      # help:             If the URL is a PDF, DOCX or text file (txt, json, yaml...) SmartBot will extract the content of the file and use it as input for the prompt.
      # help:             Add static content to your session by using `add static content URL1 URL99`. The content will be downloaded and added to your session only once.
      # help:             Add live content to your session by using `add live content URL1 URL99`. The content will be downloaded and added to your session every time you send a new prompt.
      # help:             Add messages from a channel to your session by using `add history #CHANNEL_NAME`.
      # help:             You can add authorizations on a specific ChatGPT session: `add authorization HOST HEADER VALUE`, for example: `add authorization api.example.com Authorization Bearer 123456`.
      # help:             You can attach any image to the message and it will be used as input for the prompt.
      # help:             If you want to delete the last ChatGPT response and send again last prompt, you can use `resend prompt`.
      # help:             You can set the context of your ChatGPT session: `set context CONTEXT`. Example: `set context You are a funny comedian who tells dad jokes. The output should be in JSON format.`
      # help:             MODEL_NAME can be a substring of the model name, SmartBot will search the model name that contains the substring supplied.
      # help:             When "DESCRIPTION" is used it will be used as a description for the session.
      # help:                If a previous DESCRIPTION for the session exists it will be replaced.
      # help:             When >TAG_NAME is used it will be used as a tag for the session.
      # help:                If a previous TAG_NAME for the session exists it will be replaced.
      # help:                Then you can list sessions by TAG_NAME, for example: `chatGPT sessions >dev`, `chatGPT public sessions >test`
      # help: Examples:
      # help:     _?? How much is two plus two_
      # help:     _? multiply it by pi number_
      # help:     _^chatgpt SpanishTeacher_
      # help:     _!!chatgpt data_analyst gpt-3.5-turbo_
      # help:     _^chatgpt SpanishTeacher "I will teach you Spanish"_
      # help:     _^chatgpt SpanishTeacher gpt-3.5-turbo "I will teach you Spanish"_
      # help:     _^chatgpt SpanishTeacher 32k >dev "I will teach you Spanish"_
      # help:     _^chatgpt data_analyst >science_
      # help: command_id: :open_ai_chat
      # help: command_id: :open_ai_chat_add_collaborator
      # help: command_id: :open_ai_chat_use_model
      # help: command_id: :open_ai_chat_add_live_content
      # help: command_id: :open_ai_chat_add_static_content
      # help: command_id: :open_ai_chat_add_authorization
      # help: command_id: :open_ai_chat_resend_prompt
      # help: command_id: :open_ai_chat_set_context
      # help: command_id: :open_ai_chat_delete_live_content
      # help: command_id: :open_ai_chat_delete_static_content
      # help: command_id: :open_ai_chat_delete_authorization
      # help:

      # help: ----------------------------------------------
      # help: `chatGPT get SESSION_NAME`
      # help: `??g SESSION_NAME`
      # help:     OpenAI: SmartBot will get all the prompts from the ChatGPT session indicated.
      # help: Examples:
      # help:     _chatgpt get SpanishTeacher_
      # help:     _??g SpanishTeacher_
      # help: command_id: :open_ai_chat_get_prompts
      # help:

      # help: ----------------------------------------------
      # help: `chatGPT delete SESSION_NAME`
      # help: `??d SESSION_NAME`
      # help:     OpenAI: SmartBot will delete the ChatGPT session indicated.
      # help: Examples:
      # help:     _chatgpt delete SpanishTeacher_
      # help:     _??d SpanishTeacher_
      # help: command_id: :open_ai_chat_delete_session
      # help:

      # help: ----------------------------------------------
      # help: `chatGPT clean SESSION_NAME`
      # help:     OpenAI: SmartBot will delete all the prompts from the ChatGPT session indicated.
      # help: Examples:
      # help:     _chatgpt clean SpanishTeacher_
      # help: command_id: :open_ai_chat_clean_session
      # help:

      # help: ----------------------------------------------
      # help: `chatGPT share SESSION_NAME`
      # help: `chatGPT share SESSION_NAME #CHANNEL`
      # help: `chatGPT stop sharing SESSION_NAME`
      # help: `chatGPT stop sharing SESSION_NAME #CHANNEL`
      # help:     OpenAI: It will share your ChatGPT session with everyone in your workspace if no channel supplied.
      # help:             If channel supplied it will be shared with that channel.
      # help:             If 'stop sharing' is used it will stop sharing the session.
      # help: Examples:
      # help:     _chatgpt share SpanishTeacher_
      # help:     _chatgpt share SpanishTeacher #sales_
      # help:     _chatgpt stop sharing SpanishTeacher_
      # help:     _chatgpt stop sharing SpanishTeacher #sales_
      # help: command_id: :open_ai_chat_share_session
      # help:

      # help: ----------------------------------------------
      # help: `chatGPT sessions`
      # help: `chatGPT public sessions`
      # help: `chatGPT shared sessions`
      # help: `chatGPT sessions >TAG_NAME`
      # help: `chatGPT public sessions >TAG_NAME`
      # help: `chatGPT shared sessions >TAG_NAME`
      # help: `??l`
      # help:     OpenAI: SmartBot will list the ChatGPT sessions.
      # help:             'sessions' will list the sessions you have created.
      # help:             If 'public sessions' is used it will list the public sessions.
      # help:             If 'shared sessions' is used it will list the shared sessions on the channel.
      # help:             If TAG_NAME is supplied it will list the sessions with that tag.
      # help: Examples:
      # help:     _chatgpt sessions_
      # help:     _chatgpt public sessions_
      # help:     _chatgpt shared sessions >testing_
      # help:     _chatgpt sessions >dev_
      # help: command_id: :open_ai_chat_list_sessions
      # help:

      # help: ----------------------------------------------
      # help: `chatGPT copy SESSION_NAME`
      # help: `chatGPT copy SESSION_NAME NEW_SESSION_NAME`
      # help: `chatGPT copy temporary session NEW_SESSION_NAME`
      # help: `chatGPT use USER_NAME SESSION_NAME`
      # help: `chatGPT use USER_NAME SESSION_NAME NEW_SESSION_NAME`
      # help: `?? use SESSION_NAME`
      # help: `?? use SESSION_NAME NEW_PROMPT`
      # help: `?? use USER_NAME SESSION_NAME`
      # help: `?? use USER_NAME SESSION_NAME NEW_PROMPT`
      # help:     OpenAI: SmartBot will copy the ChatGPT session indicated.
      # help:             If NEW_SESSION_NAME is supplied it will be used as the name for the new session.
      # help:             If USER_NAME is supplied it will copy the session from that user on shared sessions.
      # help:             In case no NEW_SESSION_NAME is supplied it will use the same name as the original session plus a number.
      # help:             If ?? is used it will start a new temporary session with the prompts from the session indicated.
      # help: Examples:
      # help:     _chatgpt copy SpanishTeacher_
      # help:     _chatgpt copy SpanishTeacher spanish_lessons_
      # help:     _chatgpt use peterw SpanishTeacher_
      # help:     _chatgpt use susanssd dataAnalysis DataSales_
      # help:     _?? use peterw SpanishTeacher_
      # help:     _?? use stats_sales can you project the sales for next month?_
      # help:     _?? use susanssd dataAnalysis How much is the total?_
      # help: command_id: :open_ai_chat_copy_session
      # help: command_id: :open_ai_chat_copy_session_from_user
      # help:

      #chatgpt get
    when /\A\s*\?\?(g)\s+([\w\-0-9]+)()\s*\z/im,
         /\A\s*chatgpt\s+(get)\s+([\w\-0-9]+)()\s*\z/im
      session_name = $2.to_s
      open_ai_chat_get_prompts(session_name)

      #chatgpt delete
    when /\A\s*\?\?(d)\s+([\w\-0-9]+)()\s*\z/im,
         /\A\s*chatgpt\s+(delete)\s+([\w\-0-9]+)()\s*\z/im
      session_name = $2.to_s
      open_ai_chat_delete_session(session_name)

      #chatgpt share
    when /\A\s*chatgpt\s+(share)\s+([\w\-0-9]+)\s+(on\s+|in\s+)?<#(\w+)\|.*>\s*\z/im,
         /\A\s*chatgpt\s+(share)\s+([\w\-0-9]+)()()\s*\z/im,
         /\A\s*chatgpt\s+(stop)\s+sharing\s+([\w\-0-9]+)\s+(on\s+|in\s+)?<#(\w+)\|.*>\s*\z/im,
         /\A\s*chatgpt\s+(stop)\s+sharing\s+([\w\-0-9]+)()()\s*\z/im
      type = $1.to_s.downcase.to_sym
      session_name = $2.to_s
      channel_id = $4.to_s
      open_ai_chat_share_session(type, session_name, channel_id)

      #chatgpt list
    when /\A\s*chatgpt\s+(list\s+)?sessions()()\s*\z/im,
         /\A\s*chatgpt\s+(list\s+)?sessions()\s+>([\w\-0-9]+)\s*\z/im,
         /\A\s*chatgpt\s+(list\s+)?()>([\w\-0-9]+)\s+sessions\s*\z/im,
         /\A\s*chatgpt\s+(list\s+)?(public)\s+sessions()\s*\z/im,
         /\A\s*chatgpt\s+(list\s+)?(public)\s+sessions\s+>([\w\-0-9]+)\s*\z/im,
         /\A\s*chatgpt\s+(list\s+)?(public)\s+>([\w\-0-9]+)\s+sessions\s*\z/im,
         /\A\s*chatgpt\s+(list\s+)?(shared)\s+sessions()\s*\z/im,
         /\A\s*chatgpt\s+(list\s+)?(shared)\s+sessions\s+>([\w\-0-9]+)\s*\z/im,
         /\A\s*chatgpt\s+(list\s+)?(shared)\s+>([\w\-0-9]+)\s+sessions\s*\z/im,
         /\A\s*\?\?l\s*\z()()/im,
         /\A\s*\?\?l\s*\z()\s+>([\w\-0-9]+)/im
      type = $2.to_s.downcase.to_sym
      tag = $3.to_s.downcase
      type = :own if type.to_s == ""
      open_ai_chat_list_sessions(type, tag: tag)

      #chatgpt copy
    when /\A\s*chatgpt\s+copy\s+temporary\s+session\s+()([\w\-0-9]+)\s*\z/im,
         /\A\s*chatgpt\s+copy\s+([\w\-0-9]+)()\s*\z/im,
         /\A\s*chatgpt\s+copy\s+([\w\-0-9]+)\s+([\w\-0-9]+)\s*\z/im
      session_name = $1.to_s
      new_session_name = $2.to_s
      open_ai_chat_copy_session("", "", session_name, new_session_name)

      #chatgpt use session
    when /\A\s*chatgpt\s+use\s+([\w\-0-9]+)\s+([\w\-0-9]+)()\s*\z/im,
         /\A\s*chatgpt\s+use\s+([\w\-0-9]+)\s+([\w\-0-9]+)\s+([\w\-0-9]+)\s*\z/im
      user_name = $1.to_s
      session_name = $2.to_s
      new_session_name = $3.to_s
      #if user_name include the team_id + "_" in the begining then assign to user_team_id and remove it from user_name
      if user_name.match?(/\A[A-Z0-9]{7,11}_/)
        user_team_id = user_name.match(/\A([A-Z0-9]{7,11})_/)[1]
        user_name = user_name.gsub(/\A[A-Z0-9]{7,11}_/, "")
      else
        user_team_id = ""
      end
      open_ai_chat_copy_session(user_team_id, user_name, session_name, new_session_name)

      #?? use model
    when /\A\s*(\?\?)()\s+use\s+model\s+([\w\-0-9\.]+)()()\s*\z/im,
         /\A\s*\?\s+(use)\s+model\s+()([\w\-0-9\.]+)()()\s*\z/im
      model = $3.to_s
      if $1.to_s == "??"
        open_ai_chat("", true, :temporary, model: model)
      else
        open_ai_chat_use_model(model)
      end

      #chatgpt use session for temporary session
    when /\A\s*\?\?\s*use\s+(session\s+)?([\w\-0-9]+)()()\s*\z/im, #use SESSION_NAME
         /\A\s*\?\?\s*use\s+(session\s+)?([\w\-0-9]+)\s+([\w\-0-9]+)()\s*\z/im, #use USER_NAME SESSION_NAME
         /\A\s*\?\?\s*use\s+(session\s+)?([\w\-0-9]+)\s+([\w\-0-9]+)\s+(.+)\s*\z/im, #use USER_NAME SESSION_NAME NEW_PROMPT
         /\A\s*\?\?\s*use\s+(session\s+)?([\w\-0-9]+)\s+(.+)()\s*\z/im #use SESSION_NAME NEW_PROMPT
      user_name = $2.to_s
      session_name = $3.to_s
      new_prompt = $4.to_s
      session_found = false
      more_than_one = false

      if !@open_ai.keys.any? { |team_id_user| team_id_user.match?(/\A[A-Z0-9]{7,11}_#{user_name}$/) }
        session_name += " " + new_prompt
        new_prompt = ""
      end
      short_command = false
      if new_prompt == "" #use SESSION_NAME or use SESSION_NAME PROMPT
        short_command = true
        get_openai_sessions()
        new_prompt = session_name
        session_name = user_name
        @open_ai.keys.each do |team_id_user|
          if @open_ai[team_id_user][:chat_gpt].key?(:sessions) and
             @open_ai[team_id_user][:chat_gpt][:sessions].key?(session_name)
            if @open_ai[team_id_user][:chat_gpt][:sessions][session_name][:shared].size > 0
              members_shared = []
              @open_ai[team_id_user][:chat_gpt][:sessions][session_name][:shared].each do |shared|
                members_shared += get_channel_members(shared)
              end
              members_shared.uniq!
            else
              members_shared = []
            end

            if team_id_user == user.team_id_user or
               @open_ai[team_id_user][:chat_gpt][:sessions][session_name][:public] or
               @open_ai[team_id_user][:chat_gpt][:sessions][session_name][:shared].include?(dest) or
               members_shared.include?(user.id)
              user_name = @open_ai[team_id_user][:chat_gpt][:sessions][session_name][:user_creator]
              more_than_one = true if session_found
              session_found = true
            end
            break if more_than_one
          end
        end
      end
      if more_than_one
        respond "There is more than one session with that name. Please specify the user name."
        save_stats(:open_ai_chat_copy_session_from_user)
      elsif !session_found and short_command
        respond ":exclamation: Session not found.\nCall `chatGPT sessions` to see the list of your sessions\n`chatGPT public sessions` to see the public sessions.\n`chatGPT shared sessions` to see the shared sessions."
      else
        success = open_ai_chat_copy_session("", user_name, session_name, "", temporary_session: true)
        open_ai_chat(new_prompt, false, :temporary) if new_prompt != "" and success
      end

      # chatgpt chat
    when /\A\s*\?\s+(add\s+collaborator)\s+<@(\w+)>()()()\s*\z/im,
         /\A\s*\?\?(s|c)\s+([\w\-0-9]+)()()()\s*\z/im,
         /\A\s*chatgpt\s+(start|continue|load|clean)\s+([\w\-0-9]+)()()()\s*\z/im,
         /\A\s*(chatgpt)\s+([\w\-0-9]+)()()()\s*\z/im, #chatgpt session_name
         /\A\s*(chatgpt)\s+([\w\-0-9]+)()\s+>([\w\-0-9]+)()\s*\z/im, #chatgpt session_name >tag_name
         /\A\s*(chatgpt)\s+([\w\-0-9]+)\s+([\w\-0-9\.]+)()()\s*\z/im, #chatgpt session_name model_name
         /\A\s*(chatgpt)\s+([\w\-0-9]+)\s+([\w\-0-9\.]+)\s+>([\w\-0-9\.]+)()\s*\z/im, #chatgpt session_name model_name >tag_name
         /\A\s*(chatgpt)\s+([\w\-0-9]+)()\s+>([\w\-0-9]+)\s+(".+")\s*\z/im, #chatgpt session_name >tag_name "description"
         /\A\s*(chatgpt)\s+([\w\-0-9]+)()()\s+(".+")\s*\z/im, #chatgpt session_name "description"
         /\A\s*(chatgpt)\s+([\w\-0-9]+)\s+([\w\-0-9\.]+)()\s+(".+")\s*\z/im, #chatgpt session_name model_name "description"
         /\A\s*(chatgpt)\s+([\w\-0-9]+)\s+([\w\-0-9\.]+)\s+>([\w\-0-9]+)\s+(".+")\s*\z/im, #chatgpt session_name model_name >tag_name "description"
         /\A\s*(\?\?)\s*()()()()\z/im, /\A\s*(\?\?)\s*(.+)()\s*()()\z/im, /\A\s*()(\?\s*.+)()\s*()()\z/im
      type = $1.to_s.downcase
      text = $2.to_s
      model = $3.to_s
      tag = $4.to_s
      description = $5.to_s
      delete_history = type == "??"
      type.strip!
      if type == "s" or type == "start" or type == "chatgpt"
        type = :start
      elsif type == "c" or type == "continue" or type == "load"
        type = :continue
      elsif type == "clean"
        type = :clean
        delete_history = true
      elsif type.match?(/add\s+collaborator/)
        type = :add_collaborator
      else
        if text.match?(/\A\?[^\?]/im) and !text.match?(/\A\?\s+\?\?/im)
          type = :continue_session
          #remove first ? from the prompt
          text = text[1..-1]
        else
          type = :temporary
          if text == "??" or text.match?(/\A\?\s+\?\?/)
            delete_history = true
            text = ""
          end
        end
      end
      if type == :add_collaborator
        open_ai_chat_add_collaborator(text)
      else
        open_ai_chat(text, delete_history, type, model: model, tag: tag, description: description, files: files)
      end

      # help: ----------------------------------------------
      # help: >*<https://github.com/MarioRuiz/slack-smart-bot#recap|RECAP>*
      # help: `recap`
      # help: `my recap`
      # help: `recap from YYYY/MM/DD`
      # help: `recap from YYYY/MM/DD to YYYY/MM/DD`
      # help: `recap YYYY`
      # help: `recap #CHANNEL`
      # help: `my recap #CHANNEL`
      # help: `recap from YYYY/MM/DD #CHANNEL`
      # help: `recap from YYYY/MM/DD to YYYY/MM/DD #CHANNEL`
      # help: `recap YYYY #CHANNEL`
      # help:     It will show a recap of the channel. If channel not supplied, it will show the recap of the current channel
      # help:     If 'my' is added, it will show also a recap of your messages
      # help:     If only one date is added, it will show the recap from that day to 31st of December of that year
      # help:     If only one year is added, it will show the recap from 1st of January to 31st of December of that year
      # help:  Examples:
      # help:     _recap_
      # help:     _my recap_
      # help:     _recap 2022_
      # help:     _my recap from 2023/07/01 to 2023/12/31_
      # help:     _recap from 2023/07/01 to 2023/12/31 #sales_
      # help:     _my recap from 2023/07/01 #sales_
      # help:     _recap 2023 #sales_
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#recap|more info>
      # help: command_id: :recap
      # help:
    when /\A\s*(my\s+)?recap\s*()(\d\d\d\d)?()()()\z/i, /\A\s*(my\s+)?recap\s*()(\d\d\d\d)?()()\s*<#(\w+)\|?.*>\z/i,
         /\A\s*(my\s+)?recap\s*(from\s+)?(\d\d\d\d[\.\-\/]\d\d[\.\-\/]\d\d)\s*(to\s+)?(\d\d\d\d[\.\-\/]\d\d[\.\-\/]\d\d)?()\z/i,
         /\A\s*(my\s+)?recap\s*(from\s+)?(\d\d\d\d[\.\-\/]\d\d[\.\-\/]\d\d)\s*(to\s+)?(\d\d\d\d[\.\-\/]\d\d[\.\-\/]\d\d)?\s*<#(\w+)\|?.*>\z/i
      my_recap = $1.to_s != ""
      date_start = $3.to_s
      date_end = $5.to_s
      channel = $6.to_s
      channel = dest if channel == ""
      recap(user, dest, channel, date_start, date_end, my_recap)

      # help: ----------------------------------------------
      # help: >*<https://github.com/MarioRuiz/slack-smart-bot#summarize|SUMMARIZE>*
      # help: `summarize`
      # help: `summarize since YYYY/MM/DD`
      # help: `summarize #CHANNEL`
      # help: `summarize #CHANNEL since YYYY/MM/DD`
      # help: `summarize URL_THREAD`
      # help:     It will summarize using ChatGPT the messages in the channel since the date specified.
      # help:     If no date is specified it will summarize the last 30 days.
      # help:     If time off added using Time Off command it will summarize since your last time off date started.
      # help:     If no channel is specified it will summarize the current channel.
      # help:     If a thread URL is specified it will summarize the thread.
      # help:     If the command is used in a thread it will summarize the thread.
      # help: Examples:
      # help:    _summarize_
      # help:    _summarize since 2024/01/22_
      # help:    _summarize #sales_
      # help:    _summarize #sales since 2024/01/22_
      # help:    _summarize https://yourcompany.slack.com/archives/C111JG4V4DZ/p1622549264010700 _
      # help:    <https://github.com/MarioRuiz/slack-smart-bot#summarize|more info>
      # help:  command_id: :summarize
      # help:
    when /\A\s*summarize()()()\s*\z/i, /\A\s*summarize\s+<#\w+\|(.+)>()()\s*\z/i, /\A\s*summarize\s+<#(\w+)\|>\s*()()\z/i,
         /\A\s*summarize()\s+since\s+(\d{4}[-\/]\d{2}[-\/]\d{2})()\s*\z/i, /\A\s*summarize\s+<#\w+\|(.+)>\s+since\s+(\d{4}[-\/]\d{2}[-\/]\d{2})()\s*\z/i,
         /\A\s*summarize\s+<#(.+)\|>\s+since\s+(\d{4}[-\/]\d{2}[-\/]\d{2})()\s*\z/i,
         /\A\s*summarize\s+https:\/\/\w+\.slack\.com\/archives\/(\w+)()\/p(\d+)\s*\z/i,
         /\A\s*summarize\s+https:\/\/\w+\.slack\.com\/archives\/(\w+)()\/(\d+\.\d+)\s*\z/i
      channel = $1.to_s
      from = $2.to_s
      thread_ts = $3.to_s
      channel = dest if channel == ""
      summarize(user, dest, channel, from, thread_ts)

      # help: >*<|OTHER GENERAL COMMANDS>*

    else
      return false
    end
    return true
  rescue => exception
    if defined?(@logger)
      @logger.fatal exception
      respond "Unexpected error!! Please contact an admin to solve it: <@#{config.admins.join(">, <@")}>"
    else
      puts exception
    end
    return false
  end
end

#get_access_channelsObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/slack/smart-bot/utils/get_access_channels.rb', line 3

def get_access_channels()
  require 'yaml'
  access_ch_file = "#{config.path}/rules/#{@channel_id}/access_channels"
  if File.exist?("#{access_ch_file}.rb") #backwards compatible
    file_conf = IO.readlines("#{access_ch_file}.rb").join
    if file_conf.to_s() == ""
      @access_channels = {}
    else
      @access_channels = eval(file_conf)
    end
    File.open("#{access_ch_file}.yaml", 'w') {|file| file.write(@access_channels.to_yaml) }
    File.delete("#{access_ch_file}.rb")
  end

  if File.exist?("#{access_ch_file}.yaml")      
    access_channels = @access_channels
    10.times do
      access_channels = YAML.load(File.read("#{access_ch_file}.yaml"))
      if access_channels.is_a?(Hash)
        break
      else
        sleep (0.1*(rand(2)+1))
      end
    end
    @access_channels = access_channels unless access_channels.is_a?(FalseClass)
  else
    @access_channels = {}
  end
end

#get_admins_channelsObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/slack/smart-bot/utils/get_admins_channels.rb', line 3

def get_admins_channels()
  require 'yaml'
  admins_file = "#{config.path}/rules/#{@channel_id}/admins_channels.yaml"

  if File.exist?(admins_file.gsub(".yaml", ".rb")) #backwards compatible
    file_conf = IO.readlines(admins_file.gsub(".yaml", ".rb")).join
    if file_conf.to_s() == ""
      @admins_channels = {}
    else
      @admins_channels = eval(file_conf)
    end
    File.open(admins_file, 'w') {|file| file.write(@admins_channels.to_yaml) }
    File.delete(admins_file.gsub(".yaml", ".rb"))
  end

  if File.exist?(admins_file)      
    admins_channels = @admins_channels
    10.times do
      admins_channels = YAML.load(File.read(admins_file))
      if admins_channels.is_a?(Hash)
        break
      else
        sleep (0.1*(rand(2)+1))
      end
    end
    @admins_channels = admins_channels unless admins_channels.is_a?(FalseClass)
  else
    @admins_channels = {}
  end
end

#get_authorizations(session_name, team_id_user_creator) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/slack/smart-bot/utils/get_authorizations.rb', line 2

def get_authorizations(session_name, team_id_user_creator)
  team_id_user = Thread.current[:team_id_user]

  authorizations = {}

  if config.key?(:authorizations)
    config[:authorizations].each do |key, value|
      if value.key?(:host)
        authorizations[value[:host]] = value
      end
    end
  end

  if @personal_settings_hash.key?(team_id_user) and @personal_settings_hash[team_id_user].key?(:authorizations)
    @personal_settings_hash[team_id_user][:authorizations].each do |key, value|
      if value.key?(:host)
        authorizations[value[:host]] = value
      end
    end
  end

  if !session_name.nil?
    if team_id_user != team_id_user_creator
      team_id_to_use = team_id_user_creator
    else
      team_id_to_use = team_id_user
    end
    if @open_ai[team_id_to_use][:chat_gpt][:sessions].key?(session_name) and
       @open_ai[team_id_to_use][:chat_gpt][:sessions][session_name].key?(:authorizations) and
       !@open_ai[team_id_to_use][:chat_gpt][:sessions][session_name][:authorizations].nil?
      @open_ai[team_id_to_use][:chat_gpt][:sessions][session_name][:authorizations].each do |host, header|
        authorizations[host] ||= {}
        authorizations[host].merge!(header)
      end
    end
  end

  return authorizations
end

#get_bot_logs(dest, user, typem) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: get bot logs helpadmin: To see the bot logs 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 helpadmin: command_id: :get_bot_logs helpadmin:



9
10
11
12
13
14
15
16
17
# File 'lib/slack/smart-bot/commands/on_bot/admin_master/get_bot_logs.rb', line 9

def get_bot_logs(dest, user, typem)
  save_stats(__method__)
  if config.team_id_masters.include?("#{user.team_id}_#{user.name}") and typem==:on_dm #master admin user
    respond 'Remember this data is private'
    send_file(dest, "Logs for #{config.channel}", "#{config.path}/logs/#{config.log_file}", 'Remember this data is private', 'text/plain', "text")
  else
    respond "Only master admin users on a private conversation with the bot can get the bot logs.", dest
  end
end

#get_bots_createdObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/slack/smart-bot/utils/get_bots_created.rb', line 2

def get_bots_created
  require 'yaml'
  bots_file = config.file_path.gsub(".rb", "_bots.yaml")

  if File.exist?(config.file_path.gsub(".rb", "_bots.rb")) #backwards compatible
    file_conf = IO.readlines(config.file_path.gsub(".rb", "_bots.rb")).join
    if file_conf.to_s() == ""
      @bots_created = {}
    else
      @bots_created = eval(file_conf)
    end
    File.open(bots_file, 'w') {|file| file.write(@bots_created.to_yaml) }
    File.delete(config.file_path.gsub(".rb", "_bots.rb"))
  end

  if File.exist?(bots_file)
    
    if !defined?(@datetime_bots_created) or @datetime_bots_created != File.mtime(bots_file)
      bots_created = @bots_created
      10.times do
        bots_created = YAML.load(File.read(bots_file))
        if bots_created.is_a?(Hash)
          break
        else
          sleep (0.1*(rand(2)+1))
        end
      end
      @bots_created = bots_created unless bots_created.is_a?(FalseClass)
      @datetime_bots_created = File.mtime(bots_file)
      @extended_from = {}
      @bots_created.each do |k, v|
        v[:extended] = [] unless v.key?(:extended)
        v[:extended].each do |ch|
          @extended_from[ch] = [] unless @extended_from.key?(ch)
          @extended_from[ch] << k
        end
        v[:rules_file] ||= ''
        v[:rules_file].gsub!(/^\./, '')
      end
    end
  end
end

#get_channel_members(channel_id) ⇒ Object

todo: add pagination for case more than 1000 members in the channel



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/slack/smart-bot/comm/get_channel_members.rb', line 3

def get_channel_members(channel_id)
    begin
        if channel_id.nil?
            return nil
        else
            if config.simulate and config.key?(:client)
                client.web_client.conversations_members[channel_id.to_sym].members
            else
                client.web_client.conversations_members(channel: channel_id, limit: 1000).members
            end
        end
    rescue Exception => stack
        @logger.warn stack
        return []
    end

end

#get_channels(bot_is_in: false, types: 'private_channel,public_channel') ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/slack/smart-bot/comm/get_channels.rb', line 2

def get_channels(bot_is_in: false, types: 'private_channel,public_channel')
  begin
    if config.simulate and config.key?(:client)
      if bot_is_in
        client.web_client.conversations_members.reject { |r, v| !v.members.include?(config.nick_id) }.values
      else
        client.web_client.conversations_members.values
      end
    else
      if bot_is_in
        client.web_client.users_conversations(exclude_archived: true, limit: 1000, types: "im, public_channel,private_channel").channels
      else
        resp = client.web_client.conversations_list(types: types, limit: "600", exclude_archived: "true")
        channels = resp.channels
        begin
          while resp..next_cursor.to_s != ""
            resp = client.web_client.conversations_list(
              cursor: resp..next_cursor.to_s,
              types: types,
              limit: "600",
              exclude_archived: "true",
            )
            channels += resp.channels
          end
        rescue Exception => stack
          @logger.warn stack
        end
        return channels
      end
    end
  rescue Exception => stack
    @logger.warn stack
    return []
  end
end

#get_channels_name_and_idObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# File 'lib/slack/smart-bot/utils/get_channels_name_and_id.rb', line 3

def get_channels_name_and_id
  @channels_list = get_channels()
  @channels_id = Hash.new()
  @channels_name = Hash.new()
  @channels_creator = Hash.new()
  @channels_list.each do |ch|
    unless ch.is_archived
      @channels_id[ch.name] = ch.id
      @channels_name[ch.id] = ch.name
       = find_user(ch.creator)
      @channels_creator[ch.id] = "#{.team_id}_#{.name}" unless .nil?
      @channels_creator[ch.name] = "#{.team_id}_#{.name}" unless .nil?
    end
  end
end

#get_command_idsObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/slack/smart-bot/utils/get_command_ids.rb', line 2

def get_command_ids
  commands = {
    general: [],
    on_bot_general: [],
    on_bot_on_demand: [],
    on_bot_admin: [],
    on_bot_master_admin: [],
    on_extended: [],
    on_master: [],
    on_master_admin: [],
    on_master_master_admin: [],
    general_commands: [],
    general_rules: [],
    rules: []
  }
  typem = Thread.current[:typem]
  user = Thread.current[:user]
  # :on_call, :on_bot, :on_extended, :on_dm, :on_master, :on_pg, :on_pub
  admin = is_admin?(user)

  commands[:general] = (Dir.entries("#{__dir__}/../commands/general/").select { |e| e.match?(/\.rb/) }).sort.join('|').gsub('.rb','').split('|')
  general = File.read("#{__dir__}/../commands/general_bot_commands.rb")
  commands[:general] += general.scan(/^\s*#\s*help\w*:\s+command_id:\s+:(\w+)\s*$/i).flatten
  commands[:general].uniq!

  if typem == :on_bot or typem == :on_master
    commands[:on_bot_general] = (Dir.entries("#{__dir__}/../commands/on_bot/general/").select { |e| e.match?(/\.rb/) }).sort.join('|').gsub('.rb','').split('|')
  end

  if typem == :on_bot or typem == :on_master
    commands[:on_bot_on_demand] = (Dir.entries("#{__dir__}/../commands/on_bot/").select { |e| e.match?(/\.rb/) }).sort.join('|').gsub('.rb','').split('|')
  end

  if (typem == :on_bot or typem == :on_master) and admin
    commands[:on_bot_admin] = (Dir.entries("#{__dir__}/../commands/on_bot/admin/").select { |e| e.match?(/\.rb/) }).sort.join('|').gsub('.rb','').split('|')
  end

  if (typem == :on_bot or typem == :on_master) and config.team_id_masters.include?("#{user.team_id}_#{user.name}")
    commands[:on_bot_master_admin] = (Dir.entries("#{__dir__}/../commands/on_bot/admin_master/").select { |e| e.match?(/\.rb/) }).sort.join('|').gsub('.rb','').split('|')
  end

  if typem == :on_extended
    commands[:on_extended] = (Dir.entries("#{__dir__}/../commands/on_extended/").select { |e| e.match?(/\.rb/) }).sort.join('|').gsub('.rb','').split('|')
    commands[:on_extended]+= ['repl', 'see_repls', 'get_repl', 'run_repl', 'delete_repl', 'kill_repl', 'ruby_code']
  end

  if typem == :on_master
    commands[:on_master] = (Dir.entries("#{__dir__}/../commands/on_master/").select { |e| e.match?(/\.rb/) }).sort.join('|').gsub('.rb','').split('|')
  end

  if typem == :on_master and admin
    commands[:on_master_admin] = (Dir.entries("#{__dir__}/../commands/on_master/admin/").select { |e| e.match?(/\.rb/) }).sort.join('|').gsub('.rb','').split('|')
  end

  if typem == :on_master and config.team_id_masters.include?("#{user.team_id}_#{user.name}")
    commands[:on_master_master_admin] = (Dir.entries("#{__dir__}/../commands/on_master/admin_master/").select { |e| e.match?(/\.rb/) }).sort.join('|').gsub('.rb','').split('|')
  end

  if File.exist?("#{config.path}/rules/general_commands.rb")
    general_commands = File.read("#{config.path}/rules/general_commands.rb")
    commands[:general_commands] = general_commands.scan(/^\s*#\s*help\w*:\s+command_id:\s+:(\w+)\s*$/i).flatten
    commands[:general_commands]+= general_commands.scan(/^\s*save_stats\(?\s*:(\w+)\s*,?/i).flatten
    commands[:general_commands].uniq!
  end

  if typem == :on_extended or typem ==:on_call or typem == :on_bot or typem == :on_master or (typem == :on_dm and Thread.current[:using_channel].to_s != '')
    if Thread.current.key?(:rules_file) and File.exist?(config.path + Thread.current[:rules_file])
      rules = File.read(config.path + Thread.current[:rules_file])
      commands[:rules] = rules.scan(/^\s*#\s*help\w*:\s+command_id:\s+:(\w+)\s*$/i).flatten
      commands[:rules]+= rules.scan(/^\s*save_stats\(?\s*:(\w+)\s*,?/i).flatten
      commands[:rules].uniq!

      if File.exist?("#{config.path}/rules/general_rules.rb")
        general_rules = File.read("#{config.path}/rules/general_rules.rb")
        commands[:general_rules] = general_rules.scan(/^\s*#\s*help\w*:\s+command_id:\s+:(\w+)\s*$/i).flatten
        commands[:general_rules]+= general_rules.scan(/^\s*save_stats\(?\s*:(\w+)\s*,?/i).flatten
        commands[:general_rules].uniq!
      end
    end
  end
  return commands
end

#get_countries_candelarificObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# File 'lib/slack/smart-bot/utils/get_countries_candelarific.rb', line 2

def get_countries_candelarific()
  http = NiceHttp.new("#{config[:public_holidays][:host]}/api/v2")
  if File.exist?("#{config.path}/vacations/countries_candelarific.json")
    @countries_candelarific = JSON.parse(File.read("#{config.path}/vacations/countries_candelarific.json"))
  else
    response = http.get "/countries?api_key=#{config[:public_holidays][:api_key]}"
    countries_candelarific = response.data.json(:countries)
    if countries_candelarific.is_a?(Array)
      File.write("#{config.path}/vacations/countries_candelarific.json", countries_candelarific.to_json)
      @countries_candelarific = JSON.parse(countries_candelarific.to_json)
    else
      @countries_candelarific = []
    end
  end
  http.close
end

#get_help(rules_file, dest, user, only_rules, expanded, descriptions: true, only_normal_user: false) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/slack/smart-bot/utils/get_help.rb', line 2

def get_help(rules_file, dest, user, only_rules, expanded, descriptions: true, only_normal_user: false)
  order = {
    general: [:bot_help, :hi_bot, :bye_bot, :add_admin, :remove_admin, :see_admins, :add_announcement, :delete_announcement,
              :see_announcements, :see_command_ids, :share_messages, :see_shares, :delete_share, :see_favorite_commands, :see_statuses,
              :allow_access, :see_access, :deny_access, :add_team, :add_memo_team, :delete_memo_team, :set_memo_status, :see_teams, :update_team, :ping_team, :delete_team,
              :see_memos_team, :add_vacation, :remove_vacation, :see_vacations, :see_vacations_team, :public_holidays, :set_public_holidays, :create_loop, :quit_loop,
              :get_personal_settings, :delete_personal_settings, :set_personal_settings,
              :open_ai_chat, :open_ai_chat_get_prompts, :open_ai_chat_delete_session, :open_ai_chat_share_session, :open_ai_chat_list_sessions,
              :open_ai_chat_list_sessions, :open_ai_chat_copy_session,
              :open_ai_generate_image, :open_ai_edit_image, :open_ai_variations_image, :open_ai_whisper, :open_ai_models, :recap, :summarize, :get_smartbot_readme, :poster ],
    on_bot_general: [:whats_new, :suggest_command, :bot_status, :use_rules, :stop_using_rules, :bot_stats, :leaderboard],
    on_bot: [:ruby_code, :repl, :get_repl, :run_repl, :delete_repl, :see_repls, :kill_repl, :add_shortcut, :delete_shortcut, :see_shortcuts],
    on_bot_admin: [:extend_rules, :stop_using_rules_on, :start_bot, :pause_bot, :add_routine,
      :see_routines, :start_routine, :pause_routine, :remove_routine, :see_result_routine, :run_routine]
  }
  if config.team_id_masters.include?("#{user.team_id}_#{user.name}")
    user_type = :master # master admin
  elsif is_admin?(user)
    user_type = :admin
  else
    user_type = :normal #normal user
  end

  # channel_type: :bot, :master_bot, :direct, :extended, :external
  if dest[0] == "D"
    channel_type = :direct
  elsif config.on_master_bot
    channel_type = :master_bot
  elsif @channel_id != dest
    channel_type = :extended
  else
    channel_type = :bot
  end

  if Thread.current[:typem] == :on_pg or Thread.current[:typem] == :on_pub
    channel_type = :external
  end

  if only_normal_user
    user_type = :normal
    channel_type = :bot
  end

  @help_messages_expanded ||= build_help("#{__dir__}/../commands", true)
  @help_messages_not_expanded ||= build_help("#{__dir__}/../commands", false)
  if only_rules
    help = {}
  elsif expanded
    help = @help_messages_expanded.deep_copy[user_type]
  else
    help = @help_messages_not_expanded.deep_copy[user_type]
  end
  if rules_file != ""
    help[:rules_file] = build_help(config.path+rules_file, expanded)[user_type].values.join("\n") + "\n"

    # to get all the help from other rules files added to the main rules file by using require or load. For example general_rules.rb
    res = IO.readlines(config.path+rules_file).join.scan(/$\s*(load|require)\s("|')(.+)("|')/)
    rules_help = []
    txt = ''
    if res.size>0
      res.each do |r|
        begin
          eval("txt = \"#{r[2]}\"")
          rules_help << txt if File.exist?(txt)
        rescue
        end
      end
    end
    rules_help.each do |rh|
      rhelp = build_help(rh, expanded)
      help[:rules_file] += rhelp[user_type].values.join("\n") + "\n"
    end
  end

  help[:general_commands_file] = build_help("#{__dir__}/../commands/general_bot_commands.rb", expanded)[user_type].values.join("\n") + "\n" unless only_rules
  if File.exist?(config.path + '/rules/general_commands.rb') and !only_rules
    help[:general_commands_file] += build_help(config.path+'/rules/general_commands.rb', expanded)[user_type].values.join("\n") + "\n"
  end
  if help.key?(:on_bot)
    commands_on_extended_from_on_bot = [:repl, :see_repls, :get_repl, :run_repl, :delete_repl, :kill_repl, :ruby_code]
    commands_on_extended_from_on_bot.each do |cm|
      help[:on_extended][cm] = help[:on_bot][cm] if help[:on_bot].key?(cm)
    end
  end
  help = remove_hash_keys(help, :admin_master) unless user_type == :master
  help = remove_hash_keys(help, :admin) unless user_type == :admin or user_type == :master
  help = remove_hash_keys(help, :on_master) unless channel_type == :master_bot
  help = remove_hash_keys(help, :on_extended) unless channel_type == :extended
  help = remove_hash_keys(help, :on_dm) unless channel_type == :direct
  txt = ""

  if (channel_type == :bot or channel_type == :master_bot) and expanded and descriptions
    txt += "===================================
    For the Smart Bot start listening to you say *hi bot*
    To run a command on demand even when the Smart Bot is not listening to you:
          `!THE_COMMAND`
          `@NAME_OF_BOT THE_COMMAND`
          `NAME_OF_BOT THE_COMMAND`
    To run a command on demand and add the respond on a thread:
          `^THE_COMMAND`
          `!!THE_COMMAND`\n"
  end
  if channel_type == :direct and expanded and descriptions
    txt += "===================================
    When on a private conversation with the Smart Bot, I'm always listening to you.\n"
  end
  unless channel_type == :master_bot or channel_type == :extended or !expanded or !descriptions
    txt += "===================================
    *Commands from Channels without a bot:*
    ----------------------------------------------
    `@BOT_NAME on #CHANNEL_NAME COMMAND`
    `@BOT_NAME #CHANNEL_NAME COMMAND`
      It will run the supplied command using the rules on the channel supplied.
      You need to join the specified channel to be able to use those rules.
      Also you can use this command to call another bot from a channel with a running bot.
    \n"
  end

  if help.key?(:general_commands_file)
    txt += "===================================
      *General commands on any channel where the Smart Bot is a member:*\n" if descriptions
    txt += help.general_commands_file
  end

  if help.key?(:on_bot) and help.on_bot.key?(:general) and channel_type != :external and channel_type != :extended
    if descriptions
      if channel_type == :direct
        txt += "===================================
        *General commands on Bot channel:*\n"
      else
        txt += "===================================
        *General commands on Bot channel even when the Smart Bot is not listening to you:*\n"
      end
    end
    order.on_bot_general.each do |o|
      txt += help.on_bot.general[o]
    end
    if channel_type == :master_bot
      txt += help.on_master.create_bot
      txt += help.on_master.where_smartbot
    end
  end

  if help.key?(:on_bot) and channel_type != :external and channel_type != :extended
    if descriptions
      if channel_type == :direct
        txt += "===================================
        *General commands on bot, DM or on external call on demand:*\n"
      else
        txt += "===================================
        *General commands on Bot channel only when the Smart Bot is listening to you or on demand:*\n"
      end
    end
    order.on_bot.each do |o|
      txt += help.on_bot[o]
    end
  end
  if help.key?(:on_extended) and channel_type == :extended and help[:on_extended].keys.size > 0
    if descriptions
      txt += "===================================
      *General commands on Extended channel only on demand:*\n"
    end
    commands_on_extended_from_on_bot.each do |o|
      txt += help.on_extended[o]
    end
  end

  if help.key?(:on_bot) and help.on_bot.key?(:admin) and channel_type != :external and channel_type != :extended
    txt += "===================================
      *Admin commands:*\n\n" if descriptions
    order.on_bot_admin.each do |o|
      txt += help.on_bot.admin[o]
    end
    if help.key?(:on_master) and help.on_master.key?(:admin)
      help.on_master.admin.each do |k, v|
        txt += v if v.is_a?(String)
      end
    end
  end

  if help.key?(:on_bot) and help.on_bot.key?(:admin_master) and help.on_bot.admin_master.size > 0 and channel_type != :external and channel_type != :extended
    txt += "===================================
      *Master Admin commands:*\n" if descriptions
    help.on_bot.admin_master.each do |k, v|
      txt += v if v.is_a?(String)
    end
  end

  if help.key?(:on_master) and help.on_master.key?(:admin_master) and help.on_master.admin_master.size > 0 and channel_type != :external and channel_type != :extended
    txt += "===================================
      *Master Admin commands:*\n" unless txt.include?('*Master Admin commands*') or !descriptions
    help.on_master.admin_master.each do |k, v|
      txt += v if v.is_a?(String)
    end
  end

  if help.key?(:rules_file) and channel_type != :external
    @logger.info channel_type if config.testing
    if channel_type == :extended or channel_type == :direct
      @logger.info help.rules_file if config.testing
      help.rules_file = help.rules_file.gsub(/^\s*\*These are specific commands.+NAME_OF_BOT THE_COMMAND`\s*$/im, "")
    end

    if !help.rules_file.to_s.include?('These are specific commands') and help.rules_file!=''
      txt += "===================================
       *Specific commands on this Channel, call them !THE_COMMAND or !!THE_COMMAND:*\n" if descriptions
    end

    txt += help.rules_file
  end
  return txt
end

#get_keywords(sentence, list_avoid: []) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/slack/smart-bot/utils/get_keywords.rb', line 2

def get_keywords(sentence, list_avoid: [])
  require "engtagger"
  keywords = []
  unless sentence.to_s.strip.empty?
    # Initialize the POS tagger
    tagger = EngTagger.new
    tagged_sentence = tagger.add_tags(sentence)
    unless tagged_sentence.nil?

      # Extract nouns and proper nouns from the sentence
      nouns = tagger.get_nouns(tagged_sentence).keys
      proper_nouns = tagger.get_proper_nouns(tagged_sentence).keys
      adjectives = tagger.get_adjectives(tagged_sentence).keys
      ids = sentence.scan(/([\w]+\-[\w\-]+)/)

      # Combine nouns and proper nouns to create the list of keywords
      keywords = (nouns + proper_nouns + adjectives + ids.flatten).uniq

      #delete all keywords that are one or two characters long
      keywords.delete_if { |keyword| keyword.length < 3 }
      # delete all keywords that are in the list_avoid /word/i
      if !list_avoid.empty?
        keywords.delete_if { |keyword| list_avoid.any? { |avoid| keyword.match?(/#{avoid}/i) } }
      end

      #remove special characters from the keywords
      keywords.map! { |keyword| keyword.gsub(/[^\w\-_]/i, "") }
    end
  end
  return keywords
end

#get_openai_sessions(session_name = '', team_id: '', user_name: '') ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/slack/smart-bot/utils/get_openai_sessions.rb', line 2

def get_openai_sessions(session_name='', team_id: '', user_name: '')
  require 'yaml'
  unless Thread.current[:user].nil?
    user_name = Thread.current[:user].name if user_name == ''
    team_id = Thread.current[:user].team_id if team_id == ''
    team_id_user = team_id + "_" + user_name
  end

  # get folders on openai folder
  folders = Dir.glob(File.join(config.path, "openai", "*")).select {|f| File.directory? f}
  # get files on every team folder
  files = []
  folders.each do |folder|
    files += Dir.glob(File.join(folder, "o_*.yaml"))
  end
  @datetime_open_ai_file ||= {}

  files.each do |file|
    if !defined?(@datetime_open_ai_file) or !@datetime_open_ai_file.key?(file) or @datetime_open_ai_file[file] != File.mtime(file)
      open_ai_user = YAML.load(Utils::Encryption.decrypt(File.read(file), config))
      #user_file will be the team_id _ + the user name
      user_team_id = File.basename(File.dirname(file))
      user_file = user_team_id + "_" + File.basename(file).gsub("o_","").gsub(".yaml","")

      if @open_ai.key?(user_file) and @open_ai[user_file].key?(:chat_gpt) and @open_ai[user_file][:chat_gpt].key?(:sessions) and
        @open_ai[user_file][:chat_gpt][:sessions].key?("")
        temp_session = @open_ai[user_file][:chat_gpt][:sessions][""].deep_copy
      else
        temp_session = nil
      end
      @open_ai[user_file] = open_ai_user
      @open_ai[user_file][:chat_gpt][:sessions][""] = temp_session unless temp_session.nil?
      @datetime_open_ai_file[file] = File.mtime(file)
    end
  end
  if session_name != ''
    file_name = File.join(config.path, "openai", "#{team_id}/#{user_name}/session_#{session_name}.txt")
    if File.exist?(file_name)
      @ai_gpt ||= {}
      @ai_gpt[team_id_user] ||= {}
      content = File.read(file_name)
      #The file contains an array of hashes with the messages
      #read it and store it as ruby code
      session = Utils::Encryption.decrypt(content, config).force_encoding("UTF-8")
      session_new_format = []
      if session.to_s.match?(/^Me>/) #old format to be backwards compatible
        session = session.split("\n")
        new_value = ""
        type = ""
        session.each do |s|
          s.gsub!('"', '\"')
          if s.match?(/^(Me|chatGPT)> /)
            if new_value != ""
              #escape new_value to be able to store it as json
              session_new_format << "{\"role\": \"#{type}\", \"content\": [{\"type\": \"text\", \"text\": #{new_value.to_json}}]}"
              new_value = ""
            end
            if s.match?(/^Me> /)
              type = "user"
            else
              type = "assistant"
            end
            s.gsub!(/^(Me|chatGPT)> /, "")
            #s.gsub!("'", "\\'")
            new_value += s + "\n"
          else
            #s.gsub!("'", "\\'")
            new_value += s + "\n"
          end
        end
        session_new_format << "{\"role\": \"#{type}\", \"content\": [{\"type\": \"text\", \"text\": #{new_value.to_json}}]}"
      end
      if session_new_format.empty?
        #each line is json, so we need to convert it to ruby using json parser
        session = session.split("\n").map{|s| JSON.parse(s, symbolize_names: true)}
      else
        session = []
        session_new_format.each do |s|
          session << JSON.parse(s, symbolize_names: true)
        end
      end
      @ai_gpt[team_id_user][session_name] = session.deep_copy
    end
  end
end

#get_personal_settingsObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# File 'lib/slack/smart-bot/utils/get_personal_settings.rb', line 2

def get_personal_settings()
  @personal_settings ||= {}
  @datetime_personal_settings_file ||= {}
  @personal_settings_hash ||= {}

  folders = Dir.glob(File.join(config.path, "personal_settings", "*")).select {|f| File.directory? f}
  files = []
  folders.each do |folder|
    files += Dir.glob(File.join(folder, "ps_*.yaml"))
  end

  files.each do |file|
    if !defined?(@datetime_personal_settings_file) or !@datetime_personal_settings_file.key?(file) or @datetime_personal_settings_file[file] != File.mtime(file)
      user_personal_settings = YAML.load(Utils::Encryption.decrypt(File.read(file),config))

      user_team_id = File.basename(File.dirname(file))
      user_file = user_team_id + "_" + File.basename(file).gsub("ps_","").gsub(".yaml","")

      @personal_settings[user_file] = user_personal_settings
      @datetime_personal_settings_file[file] = File.mtime(file)

      @personal_settings.each do |user, ps|
        @personal_settings_hash[user] = {}
        ps.each do |key, value|
          t = @personal_settings_hash[user]
          key.split('.').each_with_index do |k, i|
            if i == key.split('.').size - 1 #last element
              t[k.to_s.to_sym] = value
            else
              t[k.to_s.to_sym] ||= {}
              t = t[k.to_s.to_sym]
            end
          end
        end
      end
    end
  end
end

#get_presence(user) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/slack/smart-bot/comm/get_presence.rb', line 3

def get_presence(user)
  begin
    if user.to_s.length>0
      if config.simulate and config.key?(:client)
        if user[0]=='@' #name
          client.web_client.users_get_presence.select{|k, v| v[:name] == user[1..-1]}.values[-1]
        else #id
          client.web_client.users_get_presence[user.to_sym]
        end
      else
        client.web_client.users_getPresence(user: user)
      end
    end
  rescue Exception => stack
    @logger.warn stack
    return {presence: "unknown"}
  end
end

#get_repl(dest, user, session_name) ⇒ Object

help: ---------------------------------------------- help: get repl SESSION_NAME help: get irb SESSION_NAME help: get live SESSION_NAME help: Will get the Ruby commands sent on that SESSION_NAME. help: help: command_id: :get_repl help:



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/slack/smart-bot/commands/on_bot/get_repl.rb', line 10

def get_repl(dest, user, session_name)
  #todo: add tests
  save_stats(__method__)
  if has_access?(__method__, user)
    Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
    Dir.mkdir("#{config.path}/repl/#{@channel_id}") unless Dir.exist?("#{config.path}/repl/#{@channel_id}")
    if File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.run")
      if @repls.key?(session_name) and (@repls[session_name][:type] == :private or @repls[session_name][:type] == :private_clean) and
        (@repls[session_name][:creator_name]!=user.name or @repls[session_name][:creator_team_id]!= user.team_id) and
        !is_admin?(user)
        respond "The REPL with session name: #{session_name} is private", dest
      else
        content = "require 'nice_http'\n"
        if @repls.key?(session_name)
          @repls[session_name][:accessed] = Time.now.to_s
          @repls[session_name][:gets] += 1
          update_repls()
        end
        if !@repls.key?(session_name) or
          (File.exist?("#{project_folder}/.smart-bot-repl") and @repls[session_name][:type] != :private_clean and @repls[session_name][:type] != :public_clean)
          content += File.read("#{project_folder}/.smart-bot-repl")
          content += "\n"
        end

        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
        File.write("#{config.path}/repl/#{@channel_id}/#{session_name}.rb", content, mode: "w+")
        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")
      end
    else
      respond "The REPL with session name: #{session_name} doesn't exist on this Smart Bot Channel", dest
    end
  end
end

#get_repls(channel = @channel_id) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/slack/smart-bot/utils/get_repls.rb', line 3

def get_repls(channel = @channel_id)
  require 'yaml'
  repl_file = "#{config.path}/repl/repls_#{channel}.yaml"

  if File.exist?("#{config.path}/repl/repls_#{channel}.rb") #backwards compatible
    file_conf = IO.readlines("#{config.path}/repl/repls_#{channel}.rb").join
    if file_conf.to_s() == ""
      @repls = {}
    else
      @repls = eval(file_conf)
    end
    File.open(repl_file, 'w') {|file| file.write(@repls.to_yaml) }
    File.delete("#{config.path}/repl/repls_#{channel}.rb")
  end

  if File.exist?(repl_file)      
    repls = @repls
    10.times do
      repls = YAML.load(File.read(repl_file))
      if repls.is_a?(Hash)
        break
      else
        sleep (0.1*(rand(2)+1))
      end
    end
    @repls = repls unless repls.is_a?(FalseClass)
  end
end

#get_routines(channel = @channel_id) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/slack/smart-bot/utils/get_routines.rb', line 3

def get_routines(channel = @channel_id)
  require 'yaml'
  routines_file = "#{config.path}/routines/routines_#{channel}.yaml"

  if File.exist?("#{config.path}/routines/routines_#{channel}.rb") #backwards compatible
    file_conf = IO.readlines("#{config.path}/routines/routines_#{channel}.rb").join
    if file_conf.to_s() == ""
      @routines = {}
    else
      @routines = eval(file_conf)
    end
    File.open(routines_file, 'w') {|file| file.write(@routines.to_yaml) }
    File.delete("#{config.path}/routines/routines_#{channel}.rb")
  end

  if File.exist?(routines_file)      
    routines = @routines
    10.times do
      routines = YAML.load(File.read(routines_file))
      if routines.is_a?(Hash)
        break
      else
        sleep (0.1*(rand(2)+1))
      end
    end
    @routines = routines unless routines.is_a?(FalseClass)
  end
end

#get_rules_importedObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/slack/smart-bot/utils/get_rules_imported.rb', line 3

def get_rules_imported
  require 'yaml'
  file_path = "#{config.path}/rules/rules_imported"
  if File.exist?("#{file_path}.rb") #backwards compatible
    file_conf = IO.readlines("#{file_path}.rb").join
    if file_conf.to_s() == ""
      @rules_imported = {}
    else
      @rules_imported = eval(file_conf)
    end
    File.open("#{file_path}.yaml", 'w') {|file| file.write(@rules_imported.to_yaml) }
    File.delete("#{file_path}.rb")
  end

  if File.exist?("#{file_path}.yaml")      
    if !defined?(@datetime_rules_imported) or @datetime_rules_imported != File.mtime("#{file_path}.yaml")        
      @datetime_rules_imported = File.mtime("#{file_path}.yaml")
      rules_imported = @rules_imported
      10.times do
        rules_imported = YAML.load(File.read("#{file_path}.yaml"))
        if rules_imported.is_a?(Hash)
          break
        else
          sleep (0.1*(rand(2)+1))
        end
      end
      @rules_imported = rules_imported unless rules_imported.is_a?(FalseClass)
    end
  else
    @rules_imported = {}
  end
end

#get_sharesObject



3
4
5
6
7
8
9
10
11
# File 'lib/slack/smart-bot/utils/get_shares.rb', line 3

def get_shares()
  channel = @channels_name[@channel_id]
  if File.exist?("#{config.path}/shares/#{channel}.csv")
    "#{config.path}/shares/#{channel}.csv"
    t = CSV.table("#{config.path}/shares/#{channel}.csv", headers: ['share_id', 'user_team_id_deleted', 'user_deleted', 'user_team_id_created', 'user_created', 'date', 'time', 'type', 'to_channel', 'condition'])
    t.delete_if {|row| row[:user_deleted] != ''}
    @shares[channel] = t
  end
end

#get_smartbot_readme(dest) ⇒ Object

todo: Add tests



3
4
5
6
7
8
9
10
11
12
13
14
# File 'lib/slack/smart-bot/commands/general/get_smartbot_readme.rb', line 3

def get_smartbot_readme(dest)
  save_stats(__method__)
  dir = Gem::Specification.latest_spec_for('slack-smart-bot').gem_dir
  file_name = "README.md"
  content = File.read("#{dir}/#{file_name}")
  content.gsub!('"img/', '"https://raw.githubusercontent.com/MarioRuiz/slack-smart-bot/master/img/')
  if File.exist?("#{dir}/#{file_name}")
    send_file(dest, "SmartBot README", "", file_name, "text/markdown", "markdown", content: content)
  else
    respond "There is no README.md"
  end
end

#get_smartbot_team_info(token = nil) ⇒ Object



2
3
4
5
6
7
8
9
# File 'lib/slack/smart-bot/comm/get_smartbot_team_info.rb', line 2

def get_smartbot_team_info(token=nil)
  token = config.token if token.nil?
  client_web = Slack::Web::Client.new(token: token)
  client_web.auth_test
  resp = client_web.team_info
  client_web = nil
  return resp
end

#get_team_members(team) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/slack/smart-bot/utils/get_team_members.rb', line 2

def get_team_members(team)
  assigned_members = team.members.values.flatten
  assigned_members.uniq!
  assigned_members.dup.each do |m|
     = find_user(m)
    assigned_members.delete(m) if .nil? or .deleted
  end
  channels_members = []
  all_team_members = assigned_members.dup
  if team.channels.key?("members")
    team_members = []
    team.channels["members"].each do |ch|
      get_channels_name_and_id() unless @channels_id.key?(ch)
      tm = get_channel_members(@channels_id[ch])
      if tm.nil?
        respond ":exclamation: Add the Smart Bot to *##{ch}* channel to be able to get the list of members.", dest
      else
        channels_members << @channels_id[ch]
        tm.each do |m|
           = find_user(m)
          team_members << "#{.team_id}_#{.name}" unless .nil? or .is_app_user or .is_bot
        end
      end
    end
    team_members.flatten!
    team_members.uniq!
    unassigned_members = team_members - assigned_members
    unassigned_members.delete("#{config.team_id}_#{config.nick}")
    not_on_team_channel = assigned_members - team_members
    all_team_members += team_members
  else
    unassigned_members = []
    not_on_team_channel = []
  end

  return assigned_members, unassigned_members, not_on_team_channel, channels_members, all_team_members
end

#get_teamsObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/slack/smart-bot/utils/get_teams.rb', line 2

def get_teams
  @teams ||= {}
  old_teams_file = config.file_path.gsub(".rb", "_teams.yaml") #to be backward compatible
  require 'yaml'
  if File.exist?(old_teams_file)
    @logger.info 'Migrating teams to new format'
    teams = YAML.load(File.read(old_teams_file))
    @logger.info "@teams: #{teams.inspect}}"
    teams.each do |key, value|
      File.write(File.join(config.path, "teams", "t_#{key}.yaml"), Utils::Encryption.encrypt(value.to_yaml, config))
    end
    @logger.info "Deleting old_teams_file: #{old_teams_file}"
    File.delete(old_teams_file)
  end
  files = Dir.glob(File.join(config.path, "teams", "t_*.yaml"))
  @datetime_teams_file ||= {}
  files.each do |file|
    if !defined?(@datetime_teams_file) or !@datetime_teams_file.key?(file) or @datetime_teams_file[file] != File.mtime(file)
      teams_team = YAML.load(Utils::Encryption.decrypt(File.read(file),config))
      team_name = File.basename(file).gsub("t_","").gsub(".yaml","")
      teams_team[:name] = team_name unless teams_team.key?(:name) #to be backward compatible
      @teams[team_name.to_sym] = teams_team
      @datetime_teams_file[file] = File.mtime(file)
    end
  end
  #remove from @teams and @datetime_teams_file the keys that are not in the files
  #this is to avoid having old data in memory
  @teams.keys.each do |key|
    unless files.include?(File.join(config.path, "teams", "t_#{key}.yaml"))
      @teams.delete(key)
      @datetime_teams_file.delete(File.join(config.path, "teams", "t_#{key}.yaml"))
    end
  end
end

#get_user_info(user, is_bot: false) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/slack/smart-bot/comm/get_user_info.rb', line 2

def (user, is_bot: false)
  begin
    if user.to_s.length > 0
      if user[0] == "@" #name
        user = user[1..-1]
        is_name = true
      else
        is_name = false
      end
      if user.match?(/^[A-Z0-9]{7,11}_/) #team_id_user_name
        team_id = user.split("_")[0]
        user = user.split("_")[1..-1].join("_")
      else
        team_id = config.team_id
      end
      if config.simulate and config.key?(:client) #todo: add support for bots
        if client.web_client.users_info.key?(user.to_sym) #id
          client.web_client.users_info[user.to_sym]
        else #name
          client.web_client.users_info.select { |k, v| v[:user][:name] == user and v[:user][:team_id] == team_id }.values[-1]
        end
      else
        if is_bot
          @logger.info "Getting bot info for <#{user}>"
          begin
            result = client.web_client.bots_info(bot: user)
          rescue Exception => e
            @logger.warn "Failed to get bot info for <#{user}>: #{e.message}"
            return nil
          end
          if !result.nil? and result.key?(:bot) and result[:bot].key?(:user_id)
            user = result[:bot][:user_id]
            is_name = false
          else
            return nil
          end
        end
        #todo: see how to get user info using also the team_id
        if is_name
          result = client.web_client.users_info(user: "@#{user}")
        else
          result = client.web_client.users_info(user: user)
        end
        # in case of Enterprise Grid we use the enterprise_id as team_id and the enterprise_user id as user_id
        if !result.nil? and result.key?(:user) and result[:user].key?(:enterprise_user) and result[:user][:enterprise_user].key?(:enterprise_id)
          result[:user][:team_id] = result[:user][:enterprise_user][:enterprise_id]
          result[:user][:id] = result[:user][:enterprise_user][:id]
        end

        return result
      end
    end
  rescue Exception => stack
    @logger.warn stack
  end
end

#get_usersObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/slack/smart-bot/comm/get_users.rb', line 3

def get_users()
  begin
    users = []
    cursor = nil
    if config.simulate
      users = client.web_client.users_list
    else
      begin
          resp = client.web_client.users_list(limit: 1000, cursor: cursor)
          if resp.key?(:members) and  resp[:members].is_a?(Array) and resp[:members].size > 0
              users << resp[:members]
          end
          cursor = resp.get_values(:next_cursor).values[-1]
      end until cursor.empty?
      users.flatten!
    end
    users.each do |user|
      # in case of Enterprise Grid we use the enterprise_id as team_id and the enterprise_user id as user_id
      if user.key?(:enterprise_user) and user[:enterprise_user].key?(:enterprise_id)
        user[:team_id] = user[:enterprise_user][:enterprise_id]
        user[:id] = user[:enterprise_user][:id]
      end
    end
    return users
  rescue Exception => stack
    @logger.warn stack
  end
end

#get_vacationsObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/slack/smart-bot/utils/get_vacations.rb', line 2

def get_vacations
  @vacations ||= {}
  old_vacations_file = config.file_path.gsub(".rb", "_vacations.yaml") #to be backward compatible
  require 'yaml'
  if File.exist?(old_vacations_file)
    @logger.info 'Migrating vacations to new format'
    vacations = @vacations
    vacations = YAML.load(File.read(old_vacations_file))
    @vacations = vacations unless vacations.is_a?(FalseClass)
    @vacations.each do |key, value|
      File.write(File.join(config.path, "vacations", "v_#{key}.yaml"), Utils::Encryption.encrypt(value.to_yaml, config))
    end
    @logger.info "Deleting old_vacations_file: #{old_vacations_file}"
    File.delete(old_vacations_file)
  end
  # get the yaml files. They will be on /vacations then in there a folder for each team and inside the yaml files for each user
  folders = Dir.glob(File.join(config.path, "vacations", "*"))
  folders.each do |folder|
    if File.directory?(folder)
      files = Dir.glob(File.join(folder, "*.yaml"))
      @datetime_vacations_file ||= {}
      files.each do |file|
        if !defined?(@datetime_vacations_file) or !@datetime_vacations_file.key?(file) or @datetime_vacations_file[file] != File.mtime(file)
          vacations_user = YAML.load(Utils::Encryption.decrypt(File.read(file), config))
          #the key of @vacations will be the team_id_user_name
          team_id = File.basename(folder)
          @vacations["#{team_id}_#{File.basename(file).gsub("v_","").gsub(".yaml","")}"] = vacations_user
          @datetime_vacations_file[file] = File.mtime(file)
        end
      end
    end
  end
end

#has_access?(method, user = nil) ⇒ Boolean

Returns:

  • (Boolean)


2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/slack/smart-bot/utils/has_access.rb', line 2

def has_access?(method, user = nil)
  user = Thread.current[:user] if user.nil?
  team_id_user = "#{user.team_id}_#{user.name}"
  if config[:allow_access].key?(method) and !config[:allow_access][method].include?(user.name) and !config[:allow_access][method].include?(user.id) and
    !config[:allow_access][method].include?(team_id_user) and
     (!user.key?(:enterprise_user) or (user.key?(:enterprise_user) and !config[:allow_access][method].include?(user[:enterprise_user].id) and
     !config[:allow_access][method].include?("#{user[:enterprise_user].enterprise_id}_#{user[:enterprise_user].name}")))
    respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
    return false
  else
    if Thread.current[:typem] == :on_call
      channel = Thread.current[:dchannel]
    elsif Thread.current[:using_channel].to_s == ""
      channel = Thread.current[:dest]
    else
      channel = Thread.current[:using_channel]
    end
    if !@access_channels.key?(channel) or !@access_channels[channel].key?(method.to_s) or
      @access_channels[channel][method.to_s].include?(user.name) or
      @access_channels[channel][method.to_s].include?(team_id_user)
      return true
    else
      if @admins_channels.key?(channel) and !@admins_channels[channel].empty?
          admins = @admins_channels[channel].map { |a| a.split('_')[1..-1].join('_') }
          respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{admins.join(">, <@")}>"
      else
          respond "You don't have access to use this command, please contact an Admin to be able to use it."
      end
      return false
    end
  end
end

#hi_bot(user, dest, from, display_name) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/slack/smart-bot/commands/general/hi_bot.rb', line 3

def hi_bot(user, dest, from, display_name)
  if @status == :on
    save_stats(__method__)
    user_name = user.name
    team_id = user.team_id
    team_id_user = team_id + "_" + user_name

    greetings = ["Hello", "Hallo", "Hi", "Hola", "What's up", "Hey", ""].sample
    respond "#{greetings} #{display_name}", dest
    if Thread.current[:typem] == :on_pub or Thread.current[:typem] == :on_pg
      respond "You are on a channel where the SmartBot is just a member. You can only run general bot commands.\nCall `bot help` to see the commands you can use."
    elsif Thread.current[:typem] == :on_extended
      message = ["You are on an extended channel from <##{@channel_id}> so you can use all specific commands from that channel just adding !, !! or ^ before the command."]
      message << "Call `bot help` to see the commands you can use."
      respond message.join("\n")
    elsif Thread.current[:typem] == :on_dm and Thread.current[:using_channel] == ''
      unless @bots_created.empty?
        respond "To start using the rules from a Bot channel call `use #CHANNEL`.\nAvailable SmartBots: <##{@bots_created.keys.join('>, <#')}>\nIf you want to call just one command from a specific channel: `#CHANNEL COMMAND`"
      end
    else
      respond "You are on <##{@channel_id}> SmartBot channel. Call `bot help` to see all commands you can use or `bot rules` just to see the specific commands for this Bot channel."
    end
    if Thread.current[:using_channel]!=''
      message = ["You are using rules from <##{Thread.current[:using_channel]}>"]
      message << "If you want to change bot channel rules call `use #CHANNEL` or `stop using rules from <##{Thread.current[:using_channel]}>` to stop using rules from this channel."
      message << "You can call a command from any other channel by calling `#CHANNEL COMMAND`" if Thread.current[:typem] == :on_dm
      respond message.join("\n")
    end
    @listening[team_id_user] = {} unless @listening.key?(team_id_user)
    if Thread.current[:on_thread]
      @listening[team_id_user][Thread.current[:thread_ts]] = Time.now
    else
      @listening[team_id_user][dest] = Time.now
    end
  end
end

#is_admin?(user = nil) ⇒ Boolean

Returns:

  • (Boolean)


2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/slack/smart-bot/utils/is_admin.rb', line 2

def is_admin?(user=nil)
    if user.nil?
        user = Thread.current[:user]
        team_id_user_name = "#{user.team_id}_#{user.name}"
    elsif user.is_a?(String) and user.match?(/^[A-Z0-9]{7,11}_/)
        team_id = user.split('_')[0]
        user_name = user.split('_')[1..-1].join('_')
        team_id_user_name = "#{team_id}_#{user_name}"
    elsif user.is_a?(String)
        team_id_user_name = "#{config.team_id}_#{user}"
    else
        team_id_user_name = "#{user.team_id}_#{user.name}"
    end

    if (Thread.current[:dchannel].to_s!='' and Thread.current[:dchannel][0]!='D' and !@channels_creator.key?(Thread.current[:dchannel])) or
        (Thread.current[:dest].to_s!='' and Thread.current[:dest][0]!='D' and !@channels_creator.key?(Thread.current[:dest])) or
        (Thread.current[:using_channel].to_s!='' and !@channels_creator.key?(Thread.current[:using_channel]))
        get_channels_name_and_id()
    end

    if config.team_id_masters.include?(team_id_user_name) or
       config.team_id_admins.include?(team_id_user_name) or
       (Thread.current[:typem] == :on_call and @admins_channels.key?(Thread.current[:dchannel]) and @admins_channels[Thread.current[:dchannel]].include?(team_id_user_name)) or
       (Thread.current[:using_channel].to_s == '' and @admins_channels.key?(Thread.current[:dest]) and @admins_channels[Thread.current[:dest]].include?(team_id_user_name)) or
       (@admins_channels.key?(Thread.current[:using_channel]) and @admins_channels[Thread.current[:using_channel]].include?(team_id_user_name)) or
       (Thread.current[:using_channel].to_s=='' and @channels_creator.key?(Thread.current[:dest]) and team_id_user_name == @channels_creator[Thread.current[:dest]]) or
       (Thread.current[:typem] == :on_call  and @channels_creator.key?(Thread.current[:dchannel]) and team_id_user_name == @channels_creator[Thread.current[:dchannel]]) or
       (@channels_creator.key?(Thread.current[:using_channel]) and team_id_user_name == @channels_creator[Thread.current[:using_channel]])
        return true
    else
        return false
    end
end

#kill_bot_on_channel(dest, from, channel) ⇒ Object

helpmaster: ---------------------------------------------- helpmaster: kill bot on CHANNEL_NAME helpmaster: kills the bot on the specified channel helpmaster: Only works if you are on Master channel and you created that bot or you are an admin user helpmaster: helpmaster: command_id: :kill_bot_on_channel helpmaster:



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/slack/smart-bot/commands/on_master/admin/kill_bot_on_channel.rb', line 9

def kill_bot_on_channel(dest, from, channel)
  save_stats(__method__)
  if config.on_master_bot
    get_channels_name_and_id() unless @channels_name.keys.include?(channel) or @channels_id.keys.include?(channel)
    channel_id = nil
    if @channels_name.key?(channel) #it is an id
      channel_id = channel
      channel = @channels_name[channel_id]
    elsif @channels_id.key?(channel) #it is a channel name
      channel_id = @channels_id[channel]
    end
    if channel_id.nil?
      respond "There is no channel with that name: #{channel}, please be sure is written exactly the same", dest
    elsif @bots_created.keys.include?(channel_id)
      @bots_created[channel_id] ||= {}
      if @bots_created[channel_id][:admins].to_s.split(",").include?(from) # todo: consider adding is_admin?
        if @bots_created[channel_id][:thread].kind_of?(Thread) and @bots_created[channel_id][:thread].alive?
          @bots_created[channel_id][:thread].kill
        end
        @bots_created.delete(channel_id)
        send_msg_channel(channel, "Bot has been killed by #{from}")
        respond "Bot on channel: #{channel}, has been killed and deleted.", dest
        save_status :off, :killed, "The admin killed SmartBot on *##{@channels_name[channel_id]}*"
        update_bots_file()
      else
        respond "You need to be the creator or an admin of that bot channel", dest
      end
    else
      respond "There is no bot in this channel: #{channel}", dest
    end
  else
    respond "Sorry I cannot kill bots from this channel, please visit the master channel: <##{@master_bot_id}>", dest
  end
end

#kill_repl(dest, user, repl_id) ⇒ Object

help: ---------------------------------------------- help: kill repl RUN_REPL_ID help: Will kill a running repl previously executed with 'run repl' command. help: Only the user that run the repl or a master admin can kill the repl. help: Example: help: kill repl X33JK help: help: command_id: :kill_repl help:



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/slack/smart-bot/commands/on_bot/kill_repl.rb', line 11

def kill_repl(dest, user, repl_id)
  #todo: add tests
  if has_access?(__method__, user)
    save_stats(__method__)
    if !@run_repls.key?(repl_id)
      respond "The run repl with id #{repl_id} doesn't exist"
    elsif (@run_repls[repl_id].user != user.name or @run_repls[repl_id].team_id != user.team_id) and !config.team_id_masters.include?("#{user.team_id}_#{user.name}")
      respond "Only #{@run_repls[repl_id].user} or a master admin can kill this repl."
    else
      pids = `pgrep -P #{@run_repls[repl_id].pid}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
      pids.each do |pd|
        begin
          Process.kill("KILL", pd)
        rescue
        end
      end
      respond "The repl #{@run_repls[repl_id].name} (id: #{repl_id}) has been killed."
      @run_repls.delete(repl_id)
    end
  end
end

#leaderboard(from, to, period) ⇒ Object

help: ---------------------------------------------- help: leaderboard help: ranking help: podium help: leaderboard from YYYY/MM/DD help: leaderboard from YYYY/MM/DD to YYYY/MM/DD help: leaderboard PERIOD help: It will present some useful information about the use of the SmartBot in the period specified. help: If no 'from' and 'to' specified then it will be considered 'last week' help: PERIOD: today, yesterday, last week, this week, last month, this month, last year, this year help: The results will exclude master admins and routines. help: For a more detailed data use the command bot stats help: Examples: help: leaderboard help: podium from 2021/05/01 help: leaderboard from 2021/05/01 to 2021/05/31 help: ranking today help: help: command_id: :leaderboard help:



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/slack/smart-bot/commands/on_bot/general/leaderboard.rb', line 23

def leaderboard(from, to, period)
    exclude_masters = true
    exclude_routines = true
    require 'csv'
    if config.stats
        message = []
    else
        message = ["You need to set stats to true to generate the stats when running the bot instance."]
    end
    save_stats(__method__)
    if !File.exist?("#{config.stats_path}.#{Time.now.strftime("%Y-%m")}.log")
        message<<'No stats'
    else
        if period == ''
            message << "*Leaderboard SmartBot <##@channel_id> from #{from} to #{to}*\n"
        else
            message << "*Leaderboard SmartBot <##@channel_id> #{period}* (#{from} -> #{to})\n"
        end
        from_short = from
        to_short = to
        from_file = from[0..3] + '-' + from[5..6]
        to_file = to[0..3] + '-' + to[5..6]
        from+= " 00:00:00 +0000"
        to+= " 23:59:59 +0000"
        rows = []
        users_id_name = {}
        users_name_id = {}
        count_users = {}
        count_channels_dest = {}
        count_commands_uniq_user = {}

        # to translate global and enterprise users since sometimes was returning different names/ids
        if from[0..3]=='2020' # this was an issue only on that period
            Dir["#{config.stats_path}.*.log"].sort.each do |file|
                if file >= "#{config.stats_path}.#{from_file}.log" or file <= "#{config.stats_path}.#{to_file}.log"
                    CSV.foreach(file, headers: true, header_converters: :symbol, converters: :numeric) do |row|
                        unless users_id_name.key?(row[:user_id])
                            users_id_name[row[:user_id]] = row[:user_name]
                        end
                        unless users_name_id.key?(row[:user_name])
                            users_name_id[row[:user_name]] = row[:user_id]
                        end

                    end
                end
            end
        end

        master_admins = config.masters.dup
        if users_id_name.size > 0
            config.masters.each do |u|
                if users_id_name.key?(u)
                    master_admins << users_id_name[u]
                elsif users_name_id.key?(u)
                    master_admins << users_name_id[u]
                end
            end
        end

        Dir["#{config.stats_path}.*.log"].sort.each do |file|
            if file >= "#{config.stats_path}.#{from_file}.log" and file <= "#{config.stats_path}.#{to_file}.log"
                CSV.foreach(file, headers: true, header_converters: :symbol, converters: :numeric) do |row|
                    row[:date] = row[:date].to_s
                    if row[:dest_channel_id].to_s[0]=='D'
                        row[:dest_channel] = 'DM'
                    elsif row[:dest_channel].to_s == ''
                        row[:dest_channel] = row[:dest_channel_id]
                    end
                    if users_name_id.size > 0
                        row[:user_name] = users_id_name[row[:user_id]]
                        row[:user_id] = users_name_id[row[:user_name]]
                    else
                        users_id_name[row[:user_id]] ||= row[:user_name]
                    end
                    if !exclude_masters or (exclude_masters and !master_admins.include?(row[:user_name]) and 
                                            !master_admins.include?(row[:user_id]) and
                                            !@master_admin_users_id.include?(row[:user_id]))
                        if !exclude_routines or (exclude_routines and !row[:user_name].match?(/^routine\//) )
                            if row[:bot_channel_id] == @channel_id
                                if row[:date] >= from and row[:date] <= to
                                    count_users[row[:user_id]] ||= 0
                                    count_users[row[:user_id]] += 1
                                    rows << row.to_h
                                    count_channels_dest[row[:dest_channel]] ||= 0
                                    count_channels_dest[row[:dest_channel]] += 1
                                    count_commands_uniq_user[row[:user_id]] ||= []
                                    count_commands_uniq_user[row[:user_id]] << row[:command] unless count_commands_uniq_user[row[:user_id]].include?(row[:command])
                                end
                            end
                        end
                    end
                end
            end
        end

        total = rows.size

        if total > 0

            users = rows.user_id.uniq.sort
            count_user = {}
            users.each do |user|
                count = rows.count {|h| h.user_id==user}
                count_user[user] = count
            end
            mtc = nil
            mtu = []
            i = 0
            count_user.sort_by {|k,v| -v}.each do |user, count|
                if i >= 3
                    break
                elsif mtc.nil? or mtc == count or i < 3
                    mtu << "*<@#{users_id_name[user]}>* (#{count})"
                    mtc = count
                else 
                    break
                end
                i+=1
            end
            message << "\t :boom: Users that called more commands: \n\t\t\t\t#{mtu.join("\n\t\t\t\t")}"

            mtc = nil
            mtu = []
            i = 0
            count_commands_uniq_user.sort_by {|k,v| -v.size}.each do |user, cmds|
                if i >= 3
                    break
                elsif mtc.nil? or mtc == cmds.size or i < 3
                    mtu << "*<@#{users_id_name[user]}>* (#{cmds.size})"
                    mtc = cmds.size
                else 
                    break
                end
                i+=1
            end
            message << "\t :stethoscope: Users that called more different commands: \n\t\t\t\t#{mtu.join("\n\t\t\t\t")}"
            
            commands_attachment = []

            commands = rows.command.uniq.sort
            count_command = {}
            commands.each do |command|
                count = rows.count {|h| h.command==command}
                count_command[command] = count
            end
            
            mtu = []
            count_command.sort_by {|k,v| -v}[0..2].each do |command, count|
                mtu << "*`#{command.gsub('_',' ')}`* (#{count})"
            end
            message << "\t :four_leaf_clover: Most used commands: \n\t\t\t\t#{mtu.join("\n\t\t\t\t")}"

            count_channels_dest = count_channels_dest.sort_by(&:last).reverse.to_h
            count_channels_dest.keys[0..0].each do |ch|
                if ch=='DM'
                    message << "\t :star: Most used channel: *DM* (#{(count_channels_dest[ch].to_f*100/total).round(2)}%)"
                else
                    message << "\t :star: Most used channel: *<##{@channels_id[ch]}>* (#{(count_channels_dest[ch].to_f*100/total).round(2)}%)"
                end
            end
            
            types = rows.type_message.uniq.sort
            count_type = {}
            types.each do |type|
                count = rows.count {|h| h.type_message==type}
                count_type[type] = count
            end
            
            count_type.sort_by {|k,v| -v}[0..0].each do |type, count|
                message << "\t :house_with_garden: Most calls came from *#{type}* (#{(count.to_f*100/total).round(2)}%)"
            end
        else
            message << 'No data yet'
        end
    end
    respond "#{message.join("\n")}"

end

#listenObject



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/slack/smart-bot/listen.rb', line 43

def listen
  @pings ||= []
  @last_activity_check = Time.now
  get_bots_created()

  client.on :message do |data|
    unless data.user == "USLACKBOT" or data.text.nil?
      if data.text.match?(/^\s*\-!!/) or data.text.match?(/^\s*\-\^/)
        data.text.scan(/`([^`]+)`/).flatten.each do |cmd|
          if cmd.to_s != ""
            datao = data.dup
            datao.text = "^#{cmd}"
            treat_message(datao, false)
          end
        end
      elsif data.text.match?(/^\s*\-!/)
        data.text.scan(/`([^`]+)`/).flatten.each do |cmd|
          if cmd.to_s != ""
            datao = data.dup
            datao.text = "!#{cmd}"
            treat_message(datao, false)
          end
        end
      else
        treat_message(data)
      end
    end
  end

  restarts = 0
  started = false
  while restarts < 200 and !started
    begin
      @logger.info "Bot starting: #{@config_log.inspect}"
      client.start!
    rescue Slack::RealTime::Client::ClientAlreadyStartedError
      @logger.info "ClientAlreadyStarted so we continue with execution"
      started = true
    rescue Exception => e
      started = false
      restarts += 1
      if restarts < 200
        @logger.info "*" * 50
        @logger.fatal "Rescued on starting: #{e.inspect}"
        @logger.info "Waiting 60 seconds to retry. restarts: #{restarts}"
        puts "#{Time.now}: Not able to start client. Waiting 60 seconds to retry: #{@config_log.inspect}"
        sleep 60
      else
        exit!
      end
    end
  end
end

#listen_simulateObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/slack/smart-bot/listen.rb', line 2

def listen_simulate
  @salutations = [config[:nick], "<@#{config[:nick_id]}>", "bot", "smart", "smartbot", "smart-bot", "smart bot"]
  @pings ||= []
  @last_activity_check = Time.now
  get_bots_created()
  @buffer_complete = [] unless defined?(@buffer_complete)
  b = File.read("#{config.path}/buffer_complete.log", encoding: "UTF-8")
  result = b.scan(/^\|(\w+)\|(\w*)\|(\w+)\|(\w+)\|([^~]+)~~~/m)
  result.delete(nil)
  new_messages = result[@buffer_complete.size..-1]
  unless new_messages.nil? or new_messages.empty?
    @buffer_complete = result
    new_messages.each do |message|
      channel = message[0].strip
      thread_ts = message[1].strip
      user = message[2].strip
      user_name = message[3].strip
      command = message[4].to_s.strip
      # take in consideration that on simulation we are treating all messages even those that are not populated on real cases like when the message is not populated to the specific bot connection when message is sent with the bot
      @logger.info "treat message: #{message}" if config.testing
      if command.match?(/^\s*\-!!/) or command.match?(/^\s*\-\^/)
        command.scan(/`([^`]+)`/).flatten.each do |cmd|
          if cmd.to_s != ""
            cmd = "^#{cmd}"
            treat_message({ channel: channel, thread_ts: thread_ts, ts: thread_ts, user: user, text: cmd, user_name: user_name }, false)
          end
        end
      elsif command.match?(/^\s*\-!/)
        command.scan(/`([^`]+)`/).flatten.each do |cmd|
          if cmd.to_s != ""
            cmd = "!#{cmd}"
            treat_message({ channel: channel, thread_ts: thread_ts, ts: thread_ts, user: user, text: cmd, user_name: user_name }, false)
          end
        end
      else
        treat_message({ channel: channel, thread_ts: thread_ts, ts: thread_ts, user: user, text: command, user_name: user_name })
      end
    end
  end
end

#local_time(country_region, return_default_if_not_found = true) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/slack/smart-bot/utils/local_time.rb', line 2

def local_time(country_region, return_default_if_not_found = true)
  require "tzinfo"
  @time_zone_identifiers ||= TZInfo::Timezone.all_identifiers
  country, region = country_region.to_s.split("/")
  identifier = nil
  if region.nil?        
    get_countries_candelarific() if !defined?(@countries_candelarific)  
    found_country = @countries_candelarific.find { |c| c.country_name.match?(/^\s*#{country}\s*$/i) }
    unless found_country.nil?
      country_iso = found_country["iso-3166"]
      found_country = TZInfo::Country.get(country_iso).zone_identifiers
      region = found_country.first.split("/").last unless found_country.empty?
    end
  end
  identifier = @time_zone_identifiers.find { |id| id.downcase.include?(region.to_s.gsub(" ", "_").downcase) } unless region.nil?
  identifier = @time_zone_identifiers.find { |id| id.downcase.include?(country.to_s.gsub(" ", "_").downcase) } if identifier.nil?
  if identifier.nil?
    if return_default_if_not_found
      return local_time(config.public_holidays.default_calendar, false)
    else
      return nil
    end
  else
    tz = TZInfo::Timezone.get(identifier)
    return tz.now
  end
end

#notify_message(dest, user, where, message) ⇒ Object

helpmaster: ---------------------------------------------- helpmaster: notify MESSAGE helpmaster: notify all MESSAGE helpmaster: notify #CHANNEL_NAME MESSAGE helpmaster: It will send a notification message to all bot channels helpmaster: It will send a notification message to all channels the bot joined and private conversations with the bot helpmaster: It will send a notification message to the specified channel and to its extended channels helpmaster: Only works if you are on Master channel and you are a master admin user helpmaster: helpmaster: command_id: :notify_message helpmaster:



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/slack/smart-bot/commands/on_master/admin_master/notify_message.rb', line 13

def notify_message(dest, user, where, message)
  save_stats(__method__)
  if config.on_master_bot
    if config.team_id_masters.include?("#{user.team_id}_#{user.name}") #master admin user
      if where.nil? #not all and not channel
        @bots_created.each do |k, v|
          respond message, k
        end
        respond "Bot channels have been notified", dest
      elsif where == "all" #all
        myconv = get_channels(bot_is_in: true)
        myconv.each do |c|
          respond message, c.id unless c.name == config.master_channel
        end
        respond "Channels and users have been notified", dest
      else #channel
        respond message, where
        @bots_created[where][:extended].each do |ch|
          respond message, @channels_id[ch]
        end
        respond "Bot channel and extended channels have been notified", dest
      end
    end
  end
end

#pause_bot(dest, from) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: pause bot helpadmin: pause this bot helpadmin: the bot will pause so it will listen only to admin commands helpadmin: You can use this command only if you are an admin user helpadmin: helpadmin: command_id: :pause_bot helpadmin:



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/slack/smart-bot/commands/on_bot/admin/pause_bot.rb', line 11

def pause_bot(dest, from)
  save_stats(__method__)
  if is_admin?
    respond "This bot is paused from now on. You can start it again: start this bot", dest
    respond "zZzzzzZzzzzZZZZZZzzzzzzzz", dest
    @status = :paused
    unless config.on_master_bot
      @bots_created[@channel_id][:status] = :paused 
      update_bots_file()
      send_msg_channel config.master_channel, "Changed status on #{config.channel} to :paused"
    end
    save_status :off, :paused, 'The admin paused this bot'
  else
    respond "Only admin users can put me on pause", dest
  end
end

#pause_routine(dest, from, name) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: pause routine NAME helpadmin: It will pause the specified routine helpadmin: You can use this command only if you are an admin user helpadmin: NAME: one word to identify the routine helpadmin: Examples: helpadmin: pause routine example helpadmin: helpadmin: command_id: :pause_routine helpadmin:



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/slack/smart-bot/commands/on_bot/admin/pause_routine.rb', line 12

def pause_routine(dest, from, name)
  save_stats(__method__)
  if is_admin?
    if !config.on_master_bot and dest[0] == "D"
      respond "It's only possible to pause routines from MASTER channel from a direct message with the bot.", dest
    elsif @routines.key?(@channel_id) and @routines[@channel_id].key?(name)
      @routines[@channel_id][name][:status] = :paused
      @routines[@channel_id][name][:next_run] = ""
      update_routines()
      respond "The routine *`#{name}`* has been paused.", dest
    else
      respond "There isn't a routine with that name: *`#{name}`*.\nCall `see routines` to see added routines", dest
    end
  else
    respond "Only admin users can use this command", dest
  end
end

#personal_settings(user, type, settings_id, settings_value) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/slack/smart-bot/commands/general/personal_settings.rb', line 2

def personal_settings(user, type, settings_id, settings_value)
  save_stats "#{type}_personal_settings"
  if Thread.current[:typem] == :on_dm
    user_name = user.name
    team_id = user.team_id
    team_id_user = team_id + "_" + user_name
    get_personal_settings()
    @personal_settings[team_id_user] ||= {}
    if type == :get
      if settings_id.to_s == ''
        personal_settings_txt = ""
        @personal_settings[team_id_user].each do |key, value|
          personal_settings_txt << "`#{key}`:  #{value}\n"
        end
        if personal_settings_txt == ""
          personal_settings_txt = "No personal settings found.\nYou can set personal settings with `set personal settings SETTINGS_ID VALUE`"
        end
        respond "Personal settings for *#{user.name}* are:\n#{personal_settings_txt}"
      else
        if @personal_settings[team_id_user].key?(settings_id)
          respond "Personal settings for *#{settings_id}* is: *#{@personal_settings[team_id_user][settings_id]}*."
        else
          respond "Personal settings for *#{settings_id}* not found."
        end
      end
    elsif type == :delete
      if @personal_settings[team_id_user].key?(settings_id)
        @personal_settings[team_id_user].delete(settings_id)
        update_personal_settings()
        respond "Personal settings deleted for *#{settings_id}*."
      else
        settings_id_deleted = []
        @personal_settings[team_id_user].each do |key, value|
          if key.match?(/^#{settings_id}\./i)
            @personal_settings[team_id_user].delete(key)
            settings_id_deleted << key
          end
        end
        if settings_id_deleted.empty?
          respond "Personal settings for *#{settings_id}* not found."
        else
          update_personal_settings()
          respond "Personal settings deleted for *#{settings_id}*:\n`#{settings_id_deleted.join("`, `")}`"
        end
      end
    else #set
      @personal_settings[team_id_user][settings_id] = settings_value
      update_personal_settings()
      respond "Personal settings set for *#{settings_id}*."
    end
  else
    respond "This command can only be called on a DM with the SmartBot."
  end
end

#poster(permanent, emoticon_text, emoticon_bg, string, minutes) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/slack/smart-bot/commands/general/poster.rb', line 2

def poster(permanent, emoticon_text, emoticon_bg, string, minutes)
  chars = { a: ["0000", "1111", "1001", "1111", "1001", "1001", "0000"],
            b: ["0000", "1111", "1001", "1110", "1001", "1111", "0000"],
            c: ["0000", "0111", "1000", "1000", "1000", "0111", "0000"],
            d: ["0000", "1110", "1001", "1001", "1001", "1110", "0000"],
            e: ["0000", "1111", "1000", "1110", "1000", "1111", "0000"],
            f: ["0000", "1111", "1000", "1110", "1000", "1000", "0000"],
            g: ["0000", "1111", "1000", "1011", "1001", "1111", "0000"],
            h: ["0000", "1001", "1001", "1111", "1001", "1001", "0000"],
            i: ["000", "111", "010", "010", "010", "111", "000"],
            j: ["0000", "0111", "0010", "0010", "1010", "0110", "0000"],
            k: ["0000", "1001", "1010", "1100", "1010", "1001", "0000"],
            l: ["000", "100", "100", "100", "100", "111", "000"],
            m: ["00000", "10001", "11011", "10101", "10001", "10001", "00000"],
            n: ["00000", "10001", "11001", "10101", "10011", "10001", "00000"],
            o: ["0000", "0110", "1001", "1001", "1001", "0110", "0000"],
            p: ["0000", "1110", "1001", "1110", "1000", "1000", "0000"],
            q: ["00000", "01100", "10010", "10010", "10010", "01111", "00000"],
            r: ["0000", "1110", "1001", "1110", "1010", "1001", "0000"],
            s: ["0000", "0111", "1000", "0110", "0001", "1110", "0000"],
            t: ["00000", "11111", "00100", "00100", "00100", "00100", "00000"],
            u: ["00000", "10001", "10001", "10001", "10001", "01110", "00000"],
            v: ["00000", "10001", "10001", "10001", "01010", "00100", "00000"],
            w: ["0000000", "1000001", "1000001", "1001001", "1010101", "0100010", "0000000"],
            x: ["00000", "10001", "01010", "00100", "01010", "10001", "00000"],
            y: ["00000", "10001", "01010", "00100", "00100", "00100", "00000"],
            z: ["00000", "11111", "00010", "00100", "01000", "11111", "00000"],
            '!': ["0", "1", "1", "1", "0", "1", "0"],
            "'": ["0", "1", "0", "0", "0", "0", "0"],
            ".": ["0", "0", "0", "0", "0", "1", "0"],
            "|": ["0", "1", "1", "1", "1", "1", "0"],
            "%": ["1001", "1001", "0010", "0100", "1001", "1001", "0000"],
            "$": ["0110", "1001", "0110", "0010", "1001", "0110", "0000"],
            "*": ["000", "010", "111", "010", "000", "000", "000"],
            "/": ["000000", "000010", "000100", "001000", "010000", "100000", "000000"],
            "(": ["000", "001", "010", "010", "010", "001", "000"],
            ")": ["000", "100", "010", "010", "010", "100", "000"],
            "<": ["000", "001", "010", "100", "010", "001", "000"],
            ">": ["000", "100", "010", "001", "010", "100", "000"],
            "[": ["00", "11", "10", "10", "10", "11", "00"],
            "]": ["00", "11", "01", "01", "01", "11", "00"],
            "{": ["00", "01", "10", "01", "10", "01", "00"],
            "}": ["00", "10", "01", "10", "01", "10", "00"],
            "_": ["000", "000", "000", "000", "000", "000", "111"],
            "°": ["0110", "1001", "1001", "0110", "0000", "0000", "0000"],
            "º": ["0110", "1001", "1001", "0110", "0000", "0000", "0000"],
            "ª": ["0110", "1001", "0110", "0000", "0000", "0000", "0000"],
            "&": ["0000", "0110", "1000", "0101", "1010", "0101", "0000"],
            "#": ["00000", "01010", "11111", "01010", "11111", "01010", "00000"],
            "@": ["01110", "10001", "10101", "10111", "10000", "01110", "00000"],
            '^': ["00100", "01010", "10001", "00000", "00000", "00000", "00000"],
            '"': ["000", "101", "101", "000", "000", "000", "000"],
            '+': ["000", "000", "010", "111", "010", "000", "000"],
            '-': ["000", "000", "000", "111", "000", "000", "000"],
            '=': ["000", "000", "111", "000", "111", "000", "000"],
            '?': ["000", "111", "001", "010", "000", "010", "000"],
            '0': ["000", "111", "101", "101", "101", "111", "000"],
            '1': ["00", "01", "11", "01", "01", "01", "00"],
            '2': ["000", "111", "001", "010", "100", "111", "000"],
            '3': ["000", "111", "001", "111", "001", "111", "000"],
            '4': ["000", "101", "101", "111", "001", "001", "000"],
            '5': ["000", "111", "100", "111", "001", "111", "000"],
            '6': ["000", "111", "100", "111", "101", "111", "000"],
            '7': ["000", "111", "001", "010", "100", "100", "000"],
            '8': ["000", "111", "101", "111", "101", "111", "000"],
            '9': ["000", "111", "101", "111", "001", "111", "000"],
            ' ': ["0", "0", "0", "0", "0", "0", "0"] }

  emoticon_bg = ":transparent_square:" if emoticon_bg.to_s == ""
  emoticon_text = ":black_square:" if emoticon_text.to_s == ""
  string.downcase!
  messages = []

  string = string.strip.split(" ").join(" ")
  lines = [""]
  string.split(" ").each do |t|
    if ("#{lines[-1]}#{t}").size <= 6
      lines[-1] = "#{lines[-1]}#{t} "
    else
      lines[-1].strip!
      lines.pop if lines[-1] == ""
      if t.size > 6
        t.chars.each_slice(6).map(&:join).each do |tt|
          lines << tt.strip
        end
        lines[-1] += " " if lines[-1].size < 6
      else
        lines << t.strip + " "
      end
    end
  end
  lines[-1].strip!
  messages = []
  lines.each do |line|
    results = []
    all_spaces = true
    line.each_char do |char|
      if chars.key?(char.to_sym)
        results << chars[char.to_sym]
        all_spaces = false
      else
        results << chars[:' ']
      end
    end
    unless all_spaces
      rtxt = ""
      7.times do |n|
        results.size.times do |i|
          rtxt += "0#{results[i][n]}0"
        end
        rtxt += "\n"
      end
      rtxt.gsub!("1", emoticon_text)
      rtxt.gsub!("0", emoticon_bg)
      txt = ""
      msgs = []
      rtxt.split("\n").each do |m|
        if (m + txt).size > 4000
          msgs << txt unless txt == ""
          txt = ""
        end
        txt += (m + "\n")
      end
      msgs << txt
      msgs.flatten!
      msgs.each do |msg|
        messages << respond(msg, return_message: true)
      end
    end
  end
  unless permanent
    react :heavy_check_mark
    sleep (minutes.to_i * 60)
    messages.delete(nil)
    messages.each do |message|
      delete(message.channel, message.ts)
    end
    react :heavy_minus_sign
  end
end

#process(user, command, dest, dchannel, rules_file, typem, files, ts) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
# File 'lib/slack/smart-bot/process.rb', line 2

def process(user, command, dest, dchannel, rules_file, typem, files, ts)
  from = user.name
  team_id_user = user.team_id + "_" + user.name
  if config.simulate
    display_name = user.profile.display_name
  else
    if user.profile.display_name.to_s.match?(/\A\s*\z/)
      user.profile.display_name = user.profile.real_name
    end
    display_name = user.profile.display_name
  end

  processed = true

  on_demand = false
  if command.match(/\A@?(#{config[:nick]}):*\s+(.+)/im) or
     command.match(/\A()!!(.+)/im) or
     command.match(/\A()\^(.+)/im) or
     command.match(/\A()!(.+)/im) or
     command.match(/\A()<@#{config[:nick_id]}>\s+(.+)/im)
      command2 = $2
      Thread.current[:command] = command2
      if command2.match?(/^()!!(.+)/im) or
        command.match?(/^()\^(.+)/im)
        Thread.current[:on_thread] = true
      end
      command = command2
      on_demand = true
  end
  if (on_demand or typem == :on_dm or
    (@listening.key?(team_id_user) and (@listening[team_id_user].key?(dest) or @listening[team_id_user].key?(Thread.current[:thread_ts])) )) and
    config.on_maintenance and !command.match?(/\A(set|turn)\s+maintenance\s+off\s*\z/)
    unless Thread.current.key?(:routine) and Thread.current[:routine]
      respond eval("\"" + config.on_maintenance_message + "\"")
    end
    processed = true
  end
  if !config.on_maintenance or (config.on_maintenance and command.match?(/\A(set|turn)\s+maintenance\s+off\s*\z/))
    #todo: check :on_pg in this case
    if typem == :on_master or typem == :on_bot or typem == :on_pg or typem == :on_dm or
      (command.match?(/\A\s*bot\s+stats\s*(.*)\s*$/i) and dest==@channels_id[config.stats_channel])

      case command

      when /\A\s*what's\s+new\s*$/i
        whats_new(user, dest, dchannel, from, display_name)
      when /\A(<)?\s*(#{@salutations.join("|")})\s+(rules|help)\s*(.+)?$/i,
        /\A(<)?\s*(#{@salutations.join("|")})()(),? what can I do/i
        $1.to_s=='' ? send_to_file = false : send_to_file = true
        $3.to_s.match?(/rules/i) ? specific = true : specific = false
        help_command = $4
        react :runner
        bot_help(user, from, dest, dchannel, specific, help_command, rules_file, send_to_file: send_to_file)
        unreact :runner
      when /\A\s*(suggest|random)\s+(command|rule)\s*\z/i, /\A\s*(command|rule)\s+suggestion\s*\z/i
        $2.to_s.match?(/rule/i) || $1.to_s.match?(/rule/i) ? specific = true : specific = false
        suggest_command(user, dest, dchannel, specific, rules_file)
      when /\A\s*use\s+(rules\s+)?(from\s+)?<#C\w+\|(.+)>\s*$/i,
        /\A\s*use\s+(rules\s+)?(from\s+)?<#(\w+)\|>\s*$/i,
        /\Ause\s+(rules\s+)?(from\s+)?([^\s]+\s*$)/i
        channel = $3
        use_rules(dest, channel, user, dchannel)
      when /\A\s*stop\s+using\s+rules\s+(on\s+)<#\w+\|(.+)>/i,
        /\A\s*stop\s+using\s+rules\s+(on\s+)<#(\w+)\|>/i,
        /\A\s*stop\s+using\s+rules\s+(on\s+)(.+)\s*$/i
        channel = $2
        stop_using_rules_on(dest, user, from, channel, typem)
      when /\A\s*stop\s+using\s+rules\s*$()()/i,
        /\A\s*stop\s+using\s+(rules\s+from\s+)?<#\w+\|(.+)>/i,
        /\A\s*stop\s+using\s+(rules\s+from\s+)?<#(\w+)\|>/i,
        /\A\s*stop\s+using\s+(rules\s+from\s+)?(.+)\s*$/i
        channel = $2
        channel = @channel_id if channel.to_s == ''
        stop_using_rules(dest, channel, user, dchannel)
      when /\A\s*extend\s+rules\s+(to\s+)<#C\w+\|(.+)>/i, /\A\s*extend\s+rules\s+(to\s+)<#(\w+)\|>/i,
          /\A\s*extend\s+rules\s+(to\s+)(.+)/i,
          /\A\s*use\s+rules\s+(on\s+)<#C\w+\|(.+)>/i, /\A\s*use\s+rules\s+(on\s+)<#(\w+)\|>/i,
          /\A\s*use\s+rules\s+(on\s+)(.+)/i
        channel = $2
        extend_rules(dest, user, from, channel, typem)
      when /\A\s*exit\s+bot(\s+silent)?\s*$/i, /\A\s*quit\s+bot(\s+silent)?\s*$/i, /\A\s*close\s+bot(\s+silent)?\s*$/i
        silent = $1.to_s != ''
        exit_bot(command, user, dest, display_name, silent: silent)
      when /\A\s*start\s+(this\s+)?bot$/i
        start_bot(dest, from)
      when /\A\s*pause\s+(this\s+)?bot$/i
        pause_bot(dest, from)
      when /\A\s*bot\s+status/i
        bot_status(dest, user)
      when /\Anotify\s+<#(C\w+)\|.+>\s+(.+)\s*\z/im,
        /\Anotify\s+<#(\w+)\|>\s+(.+)\s*\z/im,
        /\Anotify\s+(all)?\s*(.+)\s*\z/im
        where = $1
        message = $2
        notify_message(dest, user, where, message)
      when /\Apublish\s+announcements\s*\z/i
        publish_announcements(user)
      when /\A\s*create\s+(cloud\s+|silent\s+)?bot\s+on\s+<#C\w+\|(.+)>\s*/i,
        /\A\s*create\s+(cloud\s+|silent\s+)?bot\s+on\s+<#(\w+)\|>\s*/i,
        /\Acreate\s+(cloud\s+|silent\s+)?bot\s+on\s+#(.+)\s*/i,
        /\Acreate\s+(cloud\s+|silent\s+)?bot\s+on\s+(.+)\s*/i
        type = $1.to_s.downcase
        channel = $2
        react :runner
        create_bot(dest, user, type, channel)
        unreact :runner
      when /\A\s*kill\s+bot\s+on\s+<#C\w+\|(.+)>\s*$/i,
        /\A\s*kill\s+bot\s+on\s+<#(\w+)\|>\s*$/i,
        /\Akill\s+bot\s+on\s+#(.+)\s*$/i, /\Akill\s+bot\s+on\s+(.+)\s*$/i
        channel = $1
        kill_bot_on_channel(dest, from, channel)
      when /\A\s*(where\s+is|which\s+channels|where\s+is\s+a\s+member)\s+(#{@salutations.join("|")})\??\s*$/i
        where_smartbot(user)
      when /\A\s*(add|create)\s+(silent\s+)?(bgroutine|routine)\s+([\w\.]+)\s+(every)\s+(\d+)\s*(days|hours|minutes|seconds|mins|min|secs|sec|d|h|m|s)\s*(\s#(\w+)\s*)(\s.+)?\s*\z/im,
        /\A\s*(add|create)\s+(silent\s+)?(bgroutine|routine)\s+([\w\.]+)\s+(every)\s+(\d+)\s*(days|hours|minutes|seconds|mins|min|secs|sec|d|h|m|s)\s*(\s<#([CG]\w+)\|[^>]*>\s*)?(\s.+)?\s*\z/im,
        /\A\s*(add|create)\s+(silent\s+)?(bgroutine|routine)\s+([\w\.]+)\s+on\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday|weekend|weekday)s?\s+at\s+(\d+:\d+:?\d+?)\s*()(\s#(\w+)\s*)(\s.+)?\s*\z/im,
        /\A\s*(add|create)\s+(silent\s+)?(bgroutine|routine)\s+([\w\.]+)\s+on\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday|weekend|weekday)s?\s+at\s+(\d+:\d+:?\d+?)\s*()(\s<#([CG]\w+)\|[^>]*>\s*)?(\s.+)?\s*\z/im,
        /\A\s*(add|create)\s+(silent\s+)?(bgroutine|routine)\s+([\w\.]+)\s+on\s+the\s+(\d+)[^\s]*\s+at\s+(\d+:\d+:?\d+?)\s*()(\s#(\w+)\s*)(\s.+)?\s*\z/im,
        /\A\s*(add|create)\s+(silent\s+)?(bgroutine|routine)\s+([\w\.]+)\s+on\s+the\s+(\d+)[^\s]*\s+at\s+(\d+:\d+:?\d+?)\s*()(\s<#([CG]\w+)\|[^>]*>\s*)?(\s.+)?\s*\z/im,
        /\A\s*(add|create)\s+(silent\s+)?(bgroutine|routine)\s+([\w\.]+)\s+(at)\s+(\d+:\d+:?\d+?)\s*()(\s#(\w+)\s*)(\s.+)?\s*\z/im,
        /\A\s*(add|create)\s+(silent\s+)?(bgroutine|routine)\s+([\w\.]+)\s+(at)\s+(\d+:\d+:?\d+?)\s*()(\s<#([GC]\w+)\|[^>]*>\s*)?(\s.+)?\s*\z/im
        silent = $2.to_s!=''
        routine_type = $3.downcase
        name = $4.downcase
        type = $5.to_s.downcase
        number_time = $6
        period = $7
        channel = $9
        command_to_run = $10
        content = command_to_run.to_s.scan(/\A\s*```(.+)```\s*\z/im).join
        unless content == ''
          files = [
            {
              name: name,
              filetype: '',
              content: content
            }
          ]
          command_to_run = ''
        end
        add_routine(dest, from, user, name, type, number_time, period, command_to_run, files, silent, channel, routine_type)
      when /\A\s*(kill|delete|remove)\s+routine\s+([\w\.]+)\s*$/i
        name = $2.downcase
        remove_routine(dest, from, name)
      when /\A\s*see\s+routines?\s+results?\s+([\w\.]+)\s*$/i,
        /\A\s*see\s+results?\s+routines?\s+([\w\.]+)\s*$/i,
        /\A\s*results?\s+routines?\s+([\w\.]+)\s*$/i
        name = $1.downcase
        see_result_routine(dest, from, name)
      when /\A\s*(run|execute)\s+routine\s+([\w\.]+)\s*$/i
        name = $2.downcase
        run_routine(dest, from, name)
      when /\A\s*pause\s+routine\s+([\w\.]+)\s*$/i
        name = $1.downcase
        pause_routine(dest, from, name)
      when /\A\s*start\s+routine\s+([\w\.]+)\s*$/i
        name = $1.downcase
        start_routine(dest, from, name)
      when /\A\s*see\s+(all\s+)?routines\s*()$/i, /\A\s*see\s+(all\s+)?routines\s+(name|creator|status|next_run|last_run|command)\s+\/(.+)\/\s*$/i
        all = $1.to_s != ""
        header = $2.to_s.downcase
        regexp = $3.to_s
        see_routines(dest, from, user, all, header, regexp)
      when /\A\s*get\s+bot\s+logs?\s*$/i
        get_bot_logs(dest, user, typem)
      when /\A\s*send\s+message\s+(on|to|in)\s+<(https?:[^:]+)>\s*:\s*(.+)\s*$/im,
        /\A\s*send\s+message\s+(on|to|in)\s+(https?:[^:]+)\s*:\s*(.+)\s*$/im,
        /\A\s*send\s+message\s+(on|to|in)\s*([^:]+)\s*:\s*(.+)\s*$/im
        opts = $2
        message = $3
        thread_ts = ''
        to_channel = ''
        to = []
        stats_from = ''
        stats_to = ''
        stats_channel_filter = ''
        stats_command_filter = ''

        opts.split(' ').each do |opt|
          if opt.match?(/\Ahttps:/i)
            to_channel, thread_ts = opt.scan(/\/archives\/(\w+)\/(\w\d+)/)[0]
            to << to_channel
          elsif opt.match(/<#([^>]+)\|.*>/) #channel
            if stats_from == ''
              to << $1
            else
              stats_channel_filter = $1
            end
          elsif opt.match(/#([^\s]+)/) #channel
            if stats_from == ''
              to << $1
            else
              stats_channel_filter = $1
            end
          elsif opt.match(/<@(\w+)>/)
            to << $1
          elsif opt.match(/\d{4}[\/\-]\d\d[\/\-]\d\d/)
            if stats_from == ''
              stats_from = opt
            else
              stats_to = opt
            end
          elsif stats_to!=''
            stats_command_filter = opt
          end
        end

        thread_ts.gsub!('.','')
        send_message(dest, user, typem, to, thread_ts, stats_from, stats_to, stats_channel_filter, stats_command_filter, message)
      when /\A\s*delete\s+message\s+(http.+)\s*$/i
        url = $1
        delete_message(user, typem, url)
      when /\A\s*update\s+message\s+(http[^\s]+)\s+(.+)\s*\z/mi
        url = $1
        text = Thread.current[:command_orig].scan(/\A\s*update\s+message\s+<?http[^\s]+\s+(.+)\s*\z/mi).join
        update_message(user, typem, url, text)
      when /\A\s*react\s+(on|to|in)\s*([^\s]+)\s+([p\d\.]+)\s+(.+)\s*$/i,
        /\A\s*react\s+(on|to|in)\s*([^\s]+)\s+()(.+)\s*$/i
        to = $2
        thread_ts = $3.to_s
        emojis = $4

        if to.match?(/\A<?https:/i)
          to_channel, thread_ts = to.scan(/\/archives\/(\w+)\/(\w\d+)/)[0]
        else
          to_channel = to.scan(/<#([^>]+)\|.*>/).join
        end
        if to_channel == ''
          to_channel = to.scan(/#([^\s]+)/).join
          to_channel = @channels_id[to_channel].to_s
        end
        if to_channel == ''
          respond "The channel specified doesn't exist or is in a incorrect format"
        else
          to = to_channel
          react_to(dest, user, typem, to, thread_ts, emojis)
        end

      when /\A\s*(leader\s+board|leaderboard|ranking|podium)()()\s*$/i,
        /\A\s*(leader\s+board|leaderboard|ranking|podium)\s+from\s+(\d\d\d\d[\/\-\.]\d\d[\/\-\.]\d\d)\s+to\s+(\d\d\d\d[\/\-\.]\d\d[\/\-\.]\d\d)\s*$/i,
        /\A\s*(leader\s+board|leaderboard|ranking|podium)\s+from\s+(\d\d\d\d[\/\-\.]\d\d[\/\-\.]\d\d)()\s*$/i,
        /\A\s*(leader\s+board|leaderboard|ranking|podium)\s+(today|yesterday|last\s+week|this\s+week|last\s+month|this\s+month|last\s+year|this year)()\s*$/i
        require 'date'
        opt1 = $2.to_s
        to = $3.to_s
        if opt1.match?(/\d/)
          from = opt1
          period = ''
          from = from.gsub('.','-').gsub('/','-')
          if to.empty?
            to = Date.today.strftime("%Y-%m-%d")
          else
            to = to.gsub('.','-').gsub('/','-')
          end
        elsif opt1.to_s==''
          period = 'last week'
          date = Date.today
          wday = date.wday
          wday = 7 if wday==0
          wday-=1
          from = "#{(date-wday-7).strftime("%Y-%m-%d")}"
          to = "#{(date-wday-1).strftime("%Y-%m-%d")}"
        else
          from = ''
          period = opt1.downcase
          case period
          when 'today'
            from = to = "#{Date.today.strftime("%Y-%m-%d")}"
          when 'yesterday'
            from = to ="#{(Date.today-1).strftime("%Y-%m-%d")}"
          when /this\s+month/
            from = "#{Date.today.strftime("%Y-%m-01")}"
            to = "#{Date.today.strftime("%Y-%m-%d")}"
          when /last\s+month/
            date = Date.today<<1
            from = "#{date.strftime("%Y-%m-01")}"
            to = "#{(Date.new(date.year, date.month, -1)).strftime("%Y-%m-%d")}"
          when /this\s+year/
            from = "#{Date.today.strftime("%Y-01-01")}"
            to = "#{Date.today.strftime("%Y-%m-%d")}"
          when /last\s+year/
            date = Date.today.prev_year
            from = "#{date.strftime("%Y-01-01")}"
            to = "#{(Date.new(date.year, 12, 31)).strftime("%Y-%m-%d")}"
          when /this\s+week/
            date = Date.today
            wday = date.wday
            wday = 7 if wday==0
            wday-=1
            from = "#{(date-wday).strftime("%Y-%m-%d")}"
            to = "#{date.strftime("%Y-%m-%d")}"
          when /last\s+week/
            date = Date.today
            wday = date.wday
            wday = 7 if wday==0
            wday-=1
            from = "#{(date-wday-7).strftime("%Y-%m-%d")}"
            to = "#{(date-wday-1).strftime("%Y-%m-%d")}"
          end
        end

        leaderboard(from, to, period)

      when /\A\s*bot\s+stats\s*(.*)\s*$/i
        opts = $1.to_s
        exclude_members_channel = opts.scan(/exclude\s+members\s+<#(\w+)\|.*>/i).join #todo: add test
        opts.gsub!(/exclude\s+members\s+<#\w+\|.*>/,'')
        members_channel =  opts.scan(/members\s+<#(\w+)\|.*>/i).join #todo: add test
        opts.gsub!(/members\s+<#\w+\|.*>/,'')
        this_month = opts.match?(/this\s+month/i)
        last_month = opts.match?(/last\s+month/i)
        this_year = opts.match?(/this\s+year/i)
        last_year = opts.match?(/last\s+year/i)
        this_week = opts.match?(/this\s+week/i)
        last_week = opts.match?(/last\s+week/i)
        all_opts = opts.downcase.split(' ')
        all_data = all_opts.include?('alldata')
        st_channel = opts.scan(/<#(\w+)\|.*>/).join
        st_from = opts.scan(/from\s+(\d\d\d\d[\/\-\.]\d\d[\/\-\.]\d\d)/).join
        st_from = st_from.gsub('.','-').gsub('/','-')
        st_to = opts.scan(/to\s+(\d\d\d\d[\/\-\.]\d\d[\/\-\.]\d\d)/).join
        st_to = st_to.gsub('.','-').gsub('/','-')
        st_user = opts.scan(/<@([^>]+)>/).join
        st_user = opts.scan(/@([^\s]+)/).join if st_user == ''
        st_command = opts.scan(/\s+command\s+(\w+)/i).join.downcase
        st_command = opts.scan(/^command\s+(\w+)/i).join.downcase if st_command == ''
        exclude_masters = all_opts.include?('exclude') && all_opts.include?('masters')
        exclude_routines = all_opts.include?('exclude') && all_opts.include?('routines')
        if exclude_masters
          opts.gsub!(/\s+masters$/,'')
          opts.gsub!(/\s+masters\s+/,'')
        end
        if exclude_routines
          opts.gsub!(/\s+routines$/,'')
          opts.gsub!(/\s+routines\s+/,'')
        end
        only_graph = all_opts.include?('graph')

        monthly = false
        type_group = ''
        if all_opts.include?('today')
          st_from = st_to = "#{Time.now.strftime("%Y-%m-%d")}"
        elsif all_opts.include?('yesterday')
          st_from = st_to = "#{(Time.now-86400).strftime("%Y-%m-%d")}"
        elsif all_opts.include?('monthly')
          monthly = true
          type_group = :monthly
        elsif all_opts.include?('weekly')
          type_group = :weekly
        elsif all_opts.include?('daily')
          type_group = :daily
        elsif all_opts.include?('yearly')
          type_group = :yearly
        end
        if this_month
          st_from = "#{Date.today.strftime("%Y-%m-01")}"
          st_to = "#{Date.today.strftime("%Y-%m-%d")}"
        elsif last_month
          date = Date.today<<1
          st_from = "#{date.strftime("%Y-%m-01")}"
          st_to = "#{(Date.new(date.year, date.month, -1)).strftime("%Y-%m-%d")}"
        elsif this_year
          st_from = "#{Date.today.strftime("%Y-01-01")}"
          st_to = "#{Date.today.strftime("%Y-%m-%d")}"
        elsif last_year
          date = Date.today.prev_year
          st_from = "#{date.strftime("%Y-01-01")}"
          st_to = "#{(Date.new(date.year, 12, 31)).strftime("%Y-%m-%d")}"
        elsif this_week
          date = Date.today
          wday = date.wday
          wday = 7 if wday==0
          wday-=1
          st_from = "#{(date-wday).strftime("%Y-%m-%d")}"
          st_to = "#{date.strftime("%Y-%m-%d")}"
        elsif last_week
          date = Date.today
          wday = date.wday
          wday = 7 if wday==0
          wday-=1
          st_from = "#{(date-wday-7).strftime("%Y-%m-%d")}"
          st_to = "#{(date-wday-1).strftime("%Y-%m-%d")}"
        end

        exclude_command = opts.scan(/exclude\s+([^\s]+)/i).join
        unless @master_admin_users_id.include?(user.id)
          st_user = user.id
        end
        if (typem == :on_master or typem == :on_bot) and dest[0]!='D' and dest!=@channels_id[config.stats_channel] #routine bot stats to be published on DM
          st_channel = dchannel
        end
        res = opts.scan(/(\w+)\s+\/([^\/]+)\//i)
        header = []
        regexp = []
        res.each do |r|
          header << r[0]
          regexp << r[1]
        end
        bot_stats(dest, user, typem, st_channel, st_from, st_to, st_user, st_command, exclude_masters, exclude_routines, exclude_command, monthly, all_data, members_channel, exclude_members_channel, header, regexp, type_group: type_group, only_graph: only_graph)
      when /\A(set|turn)\s+maintenance\s+(on|off)\s*()\z/im, /\A(set|turn)\s+maintenance\s+(on)\s*(.+)\s*\z/im
        status = $2.downcase
        message = $3.to_s
        set_maintenance(user, status, message)
      when /\A(set|turn)\s+(general|generic)\s+message\s+(off)\s*()\z/im, /\A(set|turn)\s+(general|generic)\s+message\s+(on\s+)?\s*(.+)\s*\z/im
        status = $3.to_s.downcase
        status = 'on' if status == ''
        message = $4.to_s
        set_general_message(user, status, message)
      else
        processed = false
      end
    else
      processed = false
    end

    # only when :on and (listening or on demand or direct message)
    if @status == :on and
      (!answer.empty? or
      (@repl_sessions.key?(team_id_user) and dest==@repl_sessions[team_id_user][:dest] and
        ((@repl_sessions[team_id_user][:on_thread] and Thread.current[:thread_ts] == @repl_sessions[team_id_user][:thread_ts]) or
        (!@repl_sessions[team_id_user][:on_thread] and !Thread.current[:on_thread]))) or
        (@listening.key?(team_id_user) and typem != :on_extended and
        ((@listening[team_id_user].key?(dest) and !Thread.current[:on_thread]) or
          (@listening[team_id_user].key?(Thread.current[:thread_ts]) and Thread.current[:on_thread] ) )) or
        typem == :on_dm or typem == :on_pg or on_demand)
      processed2 = true

      case command

      when /\A\s*(add\s+)?(global\s+|generic\s+)?shortcut\s+(for\sall)?\s*([^:]+)\s*:\s*(.+)/i,
        /\A(add\s+)(global\s+|generic\s+)?sc\s+(for\sall)?\s*([^:]+)\s*:\s*(.+)/i
        for_all = $3
        shortcut_name = $4.to_s.downcase
        command_to_run = $5
        global = $2.to_s != ''
        add_shortcut(dest, user, typem, for_all, shortcut_name, command, command_to_run, global)
      when /\A\s*(delete|remove)\s+(global\s+|generic\s+)?shortcut\s+(.+)/i,
        /\A(delete|remove)\s+(global\s+|generic\s+)?sc\s+(.+)/i
        shortcut = $3.to_s.downcase
        global = $2.to_s != ''

        delete_shortcut(dest, user, shortcut, typem, command, global)
      when /\A\s*see\s+shortcuts/i, /^see\s+sc/i
        see_shortcuts(dest, user, typem)

        #kept to be backwards compatible
      when /\A\s*id\schannel\s<#C\w+\|(.+)>\s*/i, /^id channel (.+)/
        unless typem == :on_extended
          channel_name = $1
          get_channels_name_and_id()
          if @channels_id.keys.include?(channel_name)
            respond "the id of #{channel_name} is #{@channels_id[channel_name]}", dest
          else
            respond "channel: #{channel_name} not found", dest
          end
        end
      when /\A\s*ruby\s+(.+)/im, /\A\s*code\s+(.+)/im
        code = $1
        code.gsub!("\\n", "\n")
        code.gsub!("\\r", "\r")
        code.gsub!(/^\s*```/,'')
        code.gsub!(/```\s*$/,'')
        @logger.info code
        ruby_code(dest, user, code, rules_file)
      when /\A\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s*()()()\z/i,
        /\A\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s+([\w\-]+)()()\s*\z/i,
        /\A\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s+([\w\-]+)\s*:\s+"([^"]+)"()\s*\z/i,
        /\A\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s+([\w\-]+)\s*:\s+"([^"]+)"\s+(.+)\s*\z/i,
        /\A\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s+([\w\-]+)()\s+(.+)\s*\z/i,
        /\A\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)()\s+()(.+)\s*\z/i
        opts_type = $1.to_s.downcase.split(' ')
        opts_type.include?('private') ? type = :private : type = :public
        type = "#{type}_clean".to_sym if opts_type.include?('clean')

        session_name = $3
        description = $4
        opts = " #{$5}"
        env_vars = opts.scan(/\s+[\w\-]+="[^"]+"/i) + opts.scan(/\s+[\w\-]+='[^']+'/i)
        opts.scan(/\s+[\w\-]+=[^'"\s]+/i).flatten.each do |ev|
          env_vars << ev.gsub('=',"='") + "'"
        end
        env_vars.each_with_index do |ev, idx|
            ev.gsub!("=","']=")
            ev.lstrip!
            env_vars[idx] = "ENV['#{ev}"
        end
        repl(dest, user, session_name, env_vars.flatten, rules_file, command, description, type)
      when /\A\s*get\s+(repl|irb|live)\s+([\w\-]+)\s*/i
        session_name = $2
        get_repl(dest, user, session_name)
      when /\A\s*run\s+(repl|irb|live)\s+([\w\-]+)()\s*\z/im,
        /^\s*run\s+(repl|irb|live)\s+([\w\-]+)\s+(.+)\s*$/im
        session_name = $2
        if Thread.current[:command_orig].match(/\s*run\s+(repl|irb|live)\s+([\w\-]+)\s+(.+)\s*$/im)
          opts = " #{$3}"
        else
          opts = ''
        end
        env_vars = opts.scan(/\s+[\w\-]+="[^"]+"/i) + opts.scan(/\s+[\w\-]+='[^']+'/i)
        opts.scan(/\s+[\w\-]+=[^'"\s]+/i).flatten.each do |ev|
          env_vars << ev.gsub('=',"='") + "'"
        end
        env_vars.each_with_index do |ev, idx|
            ev.gsub!("=","']=")
            ev.lstrip!
            env_vars[idx] = "ENV['#{ev}"
        end
        prerun = Thread.current[:command_orig].gsub('```', '`').scan(/\s+`(.+)`/m)
        run_repl(dest, user, session_name, env_vars.flatten, prerun.flatten, rules_file)
      when /\A\s*(delete|remove)\s+(repl|irb|live)\s+([\w\-]+)\s*$/i
        repl_name = $3
        delete_repl(dest, user, repl_name)
      when /\A\s*see\s+(repls|repl|irb|irbs)\s*$/i
        see_repls(dest, user, typem)
      when /\A\s*(kill)\s+(repl|irb|live)\s+([\w]+)\s*$/i
        repl_id = $3
        kill_repl(dest, user, repl_id)
      else
        processed2 = false
      end #of case

      processed = true if processed or processed2
    end
  end
  return processed
end

#process_first(user, text, dest, dchannel, typem, files, ts, thread_ts, routine, routine_name, routine_type, command_orig) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
# File 'lib/slack/smart-bot/process_first.rb', line 2

def process_first(user, text, dest, dchannel, typem, files, ts, thread_ts, routine, routine_name, routine_type, command_orig)
  nick = user.name
  team_id_user = user.team_id + "_" + nick
  user.team_id_user = team_id_user
  rules_file = ""
  Thread.current[:user] = user
  Thread.current[:team_id_user] = team_id_user
  if text.match(/\A\s*(stop|quit|exit|kill)\s+(iterator|iteration|loop)\s+(\d+)\s*\z/i)
    save_stats :quit_loop, forced: true, data: { dest: dest, typem: typem, user: user, files: false, command: text, routine: routine }
    num_iteration = $3.to_i
    if config.team_id_admins.include?(team_id_user) or @loops.key?(team_id_user)
      if config.team_id_admins.include?(team_id_user)
        name_loop = ""
        @loops.each do |k, v|
          if v.include?(num_iteration)
            name_loop = k
            break
          end
        end
      else
        name_loop = team_id_user
      end
      if @loops.key?(name_loop) and @loops[name_loop].include?(num_iteration)
        @loops[name_loop].delete(num_iteration)
        respond "Loop #{num_iteration} stopped", dest, thread_ts: thread_ts
      else
        respond "You don't have any loop with id #{num_iteration}. Only the creator of the loop or an admin can stop the loop.", dest, thread_ts: thread_ts
      end
    else
      respond "Only the creator of the loop or an admin can stop the loop.", dest, thread_ts: thread_ts
    end
    if Thread.current.key?(:encrypted) and Thread.current[:encrypted].size > 0
      found = false
      Thread.current[:encrypted].each do |encdata|
        found = true if !found and text.include?(encdata)
        text.gsub!(encdata, "********")
      end
      text = "********" if !found
      text += " (encrypted #{Thread.current[:command_id]})"
    end
    @logger.info "command: #{user.team_id}/#{nick}> #{text}"
    return :next #jal
  end
  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)
    save_stats :create_loop, forced: true, data: { dest: dest, typem: typem, user: user, files: false, command: text, routine: routine }
    # min every 10s, max every 60m, max times 24
    command_every = text.dup
    text = $5
    num_times = $2.to_i
    type_every = $4.downcase
    every_seconds = $3.to_i
    command_every.gsub!(/^\s*!*^?\s*/, "")
    every_seconds = (every_seconds * 60) if type_every[0] == "m"
    if num_times > 24 or every_seconds < 10 or every_seconds > 3600
      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
      return :next #jal
    end
    @loops[team_id_user] ||= []
    @num_loops ||= 0
    @num_loops += 1
    loop_id = @num_loops
    @loops[team_id_user] << loop_id
    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
    #todo: command_orig should be reasigned maybe to remove for N times every X seconds. Check.
  else
    command_every = ""
    num_times = 1
    every_seconds = 0
  end

  text.gsub!(/^!!/, "^") # to treat it just as ^
  shared = []
  if @shares.key?(@channels_name[dest]) and (ts.to_s != "" or config.simulate) and (user.id != config.nick_id or (user.id == config.nick_id and !text.match?(/\A\*?Shares from channel/)))
    @shares[@channels_name[dest]].each do |row|
      if row[:user_deleted] == ""
        if ((row[:type] == "text" and text.include?(row[:condition][1..-2])) or (row[:type] == "regexp" and text.match?(/#{row[:condition][1..-2]}/im))) and !shared.include?(row[:to_channel])
          if config.simulate
            link = text
          else
            link = client.web_client.chat_getPermalink(channel: dest, message_ts: ts).permalink
          end
          respond "*<#{link}|Shared> by <@#{row[:user_created]}> from <##{dest}>* using share id #{row[:share_id]}", row[:to_channel]
          shared << row[:to_channel]
          sleep 0.2
        end
      end
    end
  end

  if typem == :on_call
    rules_file = config.rules_file
  elsif dest[0] == "C" or dest[0] == "G" # on a channel or private channel
    rules_file = config.rules_file

    if @rules_imported.key?(team_id_user) and @rules_imported[team_id_user].key?(dchannel)
      unless @bots_created.key?(@rules_imported[team_id_user][dchannel])
        get_bots_created()
      end
      if @bots_created.key?(@rules_imported[team_id_user][dchannel])
        rules_file = @bots_created[@rules_imported[team_id_user][dchannel]][:rules_file]
      end
    end
  elsif dest[0] == "D" and @rules_imported.key?(team_id_user) and @rules_imported[team_id_user].key?(user.name) #direct message
    unless @bots_created.key?(@rules_imported[team_id_user][user.name])
      get_bots_created()
    end
    if @bots_created.key?(@rules_imported[team_id_user][user.name])
      rules_file = @bots_created[@rules_imported[team_id_user][user.name]][:rules_file]
    end
  elsif dest[0] == "D" and (!@rules_imported.key?(team_id_user) or (@rules_imported.key?(team_id_user) and !@rules_imported[team_id_user].key?(user.name)))
    if File.exist?("#{config.path}/rules/general_rules.rb")
      rules_file = "/rules/general_rules.rb"
    end
  end
  if nick == config[:nick] #if message is coming from the bot
    begin
      case text
      when /^Bot has been (closed|killed) by/i
        if config.channel == @channels_name[dchannel]
          @logger.info "#{nick}: #{text}"
          @listening[:threads].each do |thread_ts, channel_thread|
            unreact :running, thread_ts, channel: channel_thread
            respond "ChatGPT session closed since SmartBot is going to be closed.\nCheck <##{@channels_id[config.status_channel]}>", channel_thread, thread_ts: thread_ts
          end
          if config.simulate
            @status = :off
            config.simulate = false
            Thread.exit
          else
            exit!
          end
        end
      when /^Changed status on (.+) to :(.+)/i
        channel_name = $1
        status = $2
        if config.on_master_bot or config.channel == channel_name
          @bots_created[@channels_id[channel_name]][:status] = status.to_sym
          update_bots_file()
          if config.channel == channel_name
            @logger.info "#{nick}: #{text}"
          else #on master bot
            @logger.info "Changed status on #{channel_name} to :#{status}"
          end
        end
      when /extended the rules from (.+) to be used on (.+)\.$/i
        from_name = $1
        to_name = $2
        if config.on_master_bot and @bots_created[@channels_id[from_name]][:cloud]
          @bots_created[@channels_id[from_name]][:extended] << to_name
          @bots_created[@channels_id[from_name]][:extended].uniq!
          update_bots_file()
        end
      when /removed the access to the rules of (.+) from (.+)\.$/i
        from_name = $1
        to_name = $2
        if config.on_master_bot and @bots_created.key?(@channels_id[from_name]) and
           @bots_created[@channels_id[from_name]][:cloud]
          @bots_created[@channels_id[from_name]][:extended].delete(to_name)
          update_bots_file()
        end
      end

      return :next #don't continue analyzing #jal
    rescue Exception => stack
      @logger.fatal stack
      return :next #jal
    end
  end

  #only for shortcuts
  if text.match(/^@?(#{config[:nick]}):*\s+(.+)\s*/im) or
     text.match(/^()\^\s*(.+)\s*/im) or
     text.match(/^()!\s*(.+)\s*/im) or
     text.match(/^()<@#{config[:nick_id]}>\s+(.+)\s*/im)
    command2 = $2
    if text.match?(/^()\^\s*(.+)/im)
      add_double_excl = true
      addexcl = false
      if command2.match?(/^![^!]/) or command2.match?(/^\^/)
        command2[0] = ""
      elsif command2.match?(/^!!/)
        command2[0] = ""
        command2[1] = ""
      end
    else
      add_double_excl = false
      addexcl = true
    end
    command = command2
  else
    addexcl = false
    if text.include?("$") #for shortcuts inside commands
      command = text.lstrip.rstrip
    else
      command = text.downcase.lstrip.rstrip
    end
  end

  if command.include?("$") #for adding shortcuts inside commands
    command.scan(/\$([^\$]+)/i).flatten.each do |sc|
      sc.strip!
      if @shortcuts.key?(team_id_user) and @shortcuts[team_id_user].keys.include?(sc)
        command.gsub!("$#{sc}", @shortcuts[team_id_user][sc])
      elsif @shortcuts.key?(:all) and @shortcuts[:all].keys.include?(sc)
        command.gsub!("$#{sc}", @shortcuts[:all][sc])
      elsif @shortcuts_global.key?(team_id_user) and @shortcuts_global[team_id_user].keys.include?(sc)
        command.gsub!("$#{sc}", @shortcuts_global[team_id_user][sc])
      elsif @shortcuts_global.key?(:all) and @shortcuts_global[:all].keys.include?(sc)
        command.gsub!("$#{sc}", @shortcuts_global[:all][sc])
      end
    end
    command.scan(/\$([^\s]+)/i).flatten.each do |sc|
      sc.strip!
      if @shortcuts.key?(team_id_user) and @shortcuts[team_id_user].keys.include?(sc)
        command.gsub!("$#{sc}", @shortcuts[team_id_user][sc])
      elsif @shortcuts.key?(:all) and @shortcuts[:all].keys.include?(sc)
        command.gsub!("$#{sc}", @shortcuts[:all][sc])
      elsif @shortcuts_global.key?(team_id_user) and @shortcuts_global[team_id_user].keys.include?(sc)
        command.gsub!("$#{sc}", @shortcuts_global[team_id_user][sc])
      elsif @shortcuts_global.key?(:all) and @shortcuts_global[:all].keys.include?(sc)
        command.gsub!("$#{sc}", @shortcuts_global[:all][sc])
      end
    end
    text = command
    text = "!" + text if addexcl and text[0] != "!"
    text = "^" + text if add_double_excl
  end
  if command.scan(/^(shortcut|sc)\s+([^:]+)\s*$/i).any? or
     (@shortcuts.keys.include?(:all) and @shortcuts[:all].keys.include?(command)) or
     (@shortcuts.keys.include?(team_id_user) and @shortcuts[team_id_user].keys.include?(command)) or
     (@shortcuts_global.keys.include?(:all) and @shortcuts_global[:all].keys.include?(command)) or
     (@shortcuts_global.keys.include?(team_id_user) and @shortcuts_global[team_id_user].keys.include?(command))
    command = $2.downcase unless $2.nil?
    if @shortcuts.keys.include?(team_id_user) and @shortcuts[team_id_user].keys.include?(command)
      text = @shortcuts[team_id_user][command].dup
    elsif @shortcuts.keys.include?(:all) and @shortcuts[:all].keys.include?(command)
      text = @shortcuts[:all][command].dup
    elsif @shortcuts_global.keys.include?(team_id_user) and @shortcuts_global[team_id_user].keys.include?(command)
      text = @shortcuts_global[team_id_user][command].dup
    elsif @shortcuts_global.keys.include?(:all) and @shortcuts_global[:all].keys.include?(command)
      text = @shortcuts_global[:all][command].dup
    else
      respond "Shortcut not found", dest unless dest[0] == "C" and dchannel != dest #on extended channel
      return :next #jal
    end
    text = "!" + text if addexcl and text[0] != "!"
    text = "^" + text if add_double_excl
  end

  command = text

  num_times.times do |i|
    command_thread = command.dup
    begin
      t = Thread.new do
        begin
          sleep every_seconds * i if every_seconds > 0
          Thread.exit if command_every != "" and @loops.key?(team_id_user) and !@loops[team_id_user].include?(loop_id)
          @logger.info "i: #{i}, num_times: #{num_times}, every_seconds: #{every_seconds}, command: #{command_thread}" if command_every != ""
          processed = false
          processed_rules = false

          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)
            pos = command_thread.index("??")
            Thread.current[:prompt] = command_thread[pos + 2..-1].strip
            command_thread = command_thread[0..pos - 1].strip
            Thread.current[:stdout] = ""
          else
            Thread.current[:prompt] = ""
            Thread.current[:stdout] = ""
          end
          Thread.current[:dest] = dest
          Thread.current[:user] = user
          Thread.current[:team_id_user] = user.team_id + "_" + user.name
          Thread.current[:team_id] = user.team_id
          Thread.current[:command] = command_thread.dup
          Thread.current[:rules_file] = rules_file
          Thread.current[:typem] = typem
          Thread.current[:files?] = !files.nil? && files.size > 0
          Thread.current[:ts] = ts
          Thread.current[:thread_ts] = thread_ts
          Thread.current[:routine] = routine
          Thread.current[:routine_name] = routine_name
          Thread.current[:routine_type] = routine_type
          Thread.current[:dchannel] = dchannel
          Thread.current[:command_orig] = command_orig.dup
          if thread_ts.to_s == ""
            Thread.current[:on_thread] = false
            Thread.current[:thread_ts] = Thread.current[:ts] # to create the thread if necessary
          else
            Thread.current[:on_thread] = true
          end
          if (dest[0] == "C") || (dest[0] == "G") and @rules_imported.key?(team_id_user) &&
                                                      @rules_imported[team_id_user].key?(dchannel) && @bots_created.key?(@rules_imported[team_id_user][dchannel])
            Thread.current[:using_channel] = @rules_imported[team_id_user][dchannel]
          elsif dest[0] == "D" && @rules_imported.key?(team_id_user) && @rules_imported[team_id_user].key?(user.name) and
                @bots_created.key?(@rules_imported[team_id_user][user.name])
            Thread.current[:using_channel] = @rules_imported[team_id_user][user.name]
          else
            Thread.current[:using_channel] = ""
          end
          if (typem == :on_pub or typem == :on_pg) and (!command_thread.match?(/\s*bot\s+stats\s*(.*)\s*$/i) or dest != @channels_id[config.stats_channel])
            processed = false
          else
            processed = process(user, command_thread, dest, dchannel, rules_file, typem, files, Thread.current[:thread_ts])
          end
          on_demand = false
          if command_thread.match(/\A@?(#{config[:nick]}):*\s+(.+)/im) or
             command_thread.match(/\A()!!(.+)/im) or
             command_thread.match(/\A()\^(.+)/im) or
             command_thread.match(/\A()!(.+)/im) or
             command_thread.match(/\A()<@#{config[:nick_id]}>\s+(.+)/im)
            command2 = $2
            Thread.current[:command] = command2
            if command2.match?(/^()!!(.+)/im) or
               command_thread.match?(/^()\^(.+)/im)
              Thread.current[:on_thread] = true
            end
            command_thread = command2
            on_demand = true
          end

          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
             ((@active_chat_gpt_sessions.key?(team_id_user) and @active_chat_gpt_sessions[team_id_user].key?(Thread.current[:thread_ts])) or
              (@chat_gpt_collaborating.key?(team_id_user) and @chat_gpt_collaborating[team_id_user].key?(Thread.current[:thread_ts])))
            @listening[team_id_user][Thread.current[:thread_ts]] = Time.now
            command_thread = "? #{command_thread}" #chatgpt
          end

          unless config.on_maintenance or @status != :on
            if typem == :on_pub or typem == :on_pg or typem == :on_extended
              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)
                $1.to_s == "" ? send_to_file = false : send_to_file = true
                $3.to_s.match?(/rules/i) ? specific = true : specific = false
                help_command = $4
                react :runner
                if typem == :on_extended and specific
                  bot_rules(dest, help_command, typem, rules_file, user, send_to_file: send_to_file)
                else
                  bot_help(user, user.name, dest, dchannel, specific, help_command, rules_file, send_to_file: send_to_file)
                end
                processed = true
                unreact :runner
              end
            end
            processed = (processed || general_bot_commands(user, command_thread, dest, files))
            processed = (processed || general_commands(user, command_thread, dest, files)) if defined?(general_commands)

            if processed
              text_to_log = command_thread.dup

              if Thread.current.key?(:encrypted) and Thread.current[:encrypted].size > 0
                found = false
                Thread.current[:encrypted].each do |encdata|
                  found = true if !found and text_to_log.include?(encdata)
                  text_to_log.gsub!(encdata, "********")
                end
                text_to_log = "********" if !found
                text_to_log += " (encrypted #{Thread.current[:command_id]})"
              end
              @logger.info "command: #{user.team_id}/#{nick}> #{text_to_log}" unless user.id == config.nick_id_granular
            end
          end

          if !config.on_maintenance and !processed and typem != :on_pub and typem != :on_pg
            if @status == :on and
               (!answer.empty? or
                (@repl_sessions.key?(team_id_user) and dest == @repl_sessions[team_id_user][:dest] and
                 ((@repl_sessions[team_id_user][:on_thread] and thread_ts == @repl_sessions[team_id_user][:thread_ts]) or
                  (!@repl_sessions[team_id_user][:on_thread] and !Thread.current[:on_thread]))) or
                (@listening.key?(team_id_user) and typem != :on_extended and
                 ((@listening[team_id_user].key?(dest) and !Thread.current[:on_thread]) or
                  (@listening[team_id_user].key?(thread_ts) and Thread.current[:on_thread]))) or
                dest[0] == "D" or on_demand)
              unless processed
                text_to_log = command_thread.dup
                found = false
                if Thread.current.key?(:encrypted) and Thread.current[:encrypted].size > 0
                  Thread.current[:encrypted].each do |encdata|
                    found = true if !found and text_to_log.include?(encdata)
                    text_to_log.gsub!(encdata, "********")
                  end
                  text_to_log = "********" if !found
                  text_to_log += " (encrypted #{Thread.current[:command_id]})"
                end
                @logger.info "command: #{user.team_id}/#{nick}> #{text_to_log}" unless user.id == config.nick_id_granular
              end
              #todo: verify this

              if dest[0] == "C" or dest[0] == "G" or (dest[0] == "D" and typem == :on_call)
                if typem != :on_call and @rules_imported.key?(team_id_user) and @rules_imported[team_id_user].key?(dchannel)
                  if @bots_created.key?(@rules_imported[team_id_user][dchannel])
                    if @bots_created[@rules_imported[team_id_user][dchannel]][:status] != :on
                      respond "The bot on that channel is not :on", dest
                      rules_file = ""
                    end
                  end
                end
                unless rules_file.empty?
                  begin
                    eval(File.new(config.path + rules_file).read) if File.exist?(config.path + rules_file)
                  rescue Exception => stack
                    @logger.fatal "ERROR ON RULES FILE: #{rules_file}"
                    @logger.fatal stack
                  end
                  if defined?(rules)
                    command_thread[0] = "" if command_thread[0] == "!"
                    command_thread.gsub!(/^@\w+:*\s*/, "")
                    if method(:rules).parameters.size == 4
                      processed_rules = rules(user, command_thread, processed, dest)
                    elsif method(:rules).parameters.size == 5
                      processed_rules = rules(user, command_thread, processed, dest, files)
                    else
                      processed_rules = rules(user, command_thread, processed, dest, files, rules_file)
                    end
                  else
                    @logger.warn "It seems like rules method is not defined"
                  end
                end
              elsif @rules_imported.key?(team_id_user) and @rules_imported[team_id_user].key?(user.name)
                if @bots_created.key?(@rules_imported[team_id_user][user.name])
                  if @bots_created[@rules_imported[team_id_user][user.name]][:status] == :on
                    begin
                      eval(File.new(config.path + rules_file).read) if File.exist?(config.path + rules_file) and ![".", ".."].include?(config.path + rules_file)
                    rescue Exception => stack
                      @logger.fatal "ERROR ON imported RULES FILE: #{rules_file}"
                      @logger.fatal stack
                    end
                  else
                    respond "The bot on <##{@rules_imported[team_id_user][user.name]}|#{@bots_created[@rules_imported[team_id_user][user.name]][:channel_name]}> is not :on", dest
                    rules_file = ""
                  end
                end

                unless rules_file.empty?
                  if defined?(rules)
                    command_thread[0] = "" if command_thread[0] == "!"
                    command_thread.gsub!(/^@\w+:*\s*/, "")
                    if method(:rules).parameters.size == 4
                      processed_rules = rules(user, command_thread, processed, dest)
                    elsif method(:rules).parameters.size == 5
                      processed_rules = rules(user, command_thread, processed, dest, files)
                    else
                      processed_rules = rules(user, command_thread, processed, dest, files, rules_file)
                    end
                  else
                    @logger.warn "It seems like rules method is not defined"
                  end
                end
              elsif dest[0] == "D" and
                    (!@rules_imported.key?(team_id_user) or (@rules_imported.key?(team_id_user) and !@rules_imported[team_id_user].key?(user.name))) and
                    rules_file.include?("general_rules.rb")
                begin
                  eval(File.new(config.path + rules_file).read) if File.exist?(config.path + rules_file) and ![".", ".."].include?(config.path + rules_file)
                rescue Exception => stack
                  @logger.fatal "ERROR ON imported RULES FILE: #{rules_file}"
                  @logger.fatal stack
                end

                if defined?(general_rules)
                  command_thread[0] = "" if command_thread[0] == "!"
                  command_thread.gsub!(/^@\w+:*\s*/, "")
                  #todo: check to change processed > processed_rules
                  if method(:general_rules).parameters.size == 4
                    processed = general_rules(user, command_thread, processed, dest)
                  elsif method(:general_rules).parameters.size == 5
                    processed = general_rules(user, command_thread, processed, dest, files)
                  else
                    processed = general_rules(user, command_thread, processed, dest, files, rules_file)
                  end
                else
                  @logger.warn "It seems like general_rules method is not defined"
                end
                unless processed
                  dont_understand("")
                end
              else
                @logger.info "it is a direct message with no rules file selected so no rules file executed."
                if command_thread.match?(/^\s*bot\s+rules\s*(.*)$/i)
                  respond "No rules running. You can use the command `use rules from CHANNEL` to specify the rules you want to use on this private conversation.\n`bot help` to see available commands.", dest
                end
                unless processed
                  dont_understand("")
                end
              end

              processed = (processed_rules || processed)

              if processed and @listening.key?(team_id_user)
                if Thread.current[:on_thread] and @listening[team_id_user].key?(Thread.current[:thread_ts])
                  @listening[team_id_user][Thread.current[:thread_ts]] = Time.now
                elsif !Thread.current[:on_thread] and @listening[team_id_user].key?(dest)
                  @listening[team_id_user][dest] = Time.now
                end
              end
            end
          end

          if Thread.current[:prompt].to_s != ""
            prompt = "#{Thread.current[:command]}\n\n#{Thread.current[:prompt]}\n\n#{Thread.current[:stdout]}\n\n"
            Thread.current[:prompt] = ""
            Thread.current[:stdout] = ""
            if processed
              if @active_chat_gpt_sessions.key?(team_id_user) and @active_chat_gpt_sessions[team_id_user].key?(Thread.current[:thread_ts])
                open_ai_chat(prompt, false, :temporary)
              else
                open_ai_chat(prompt, true, :temporary, model: config[:ai].open_ai.chat_gpt.smartbot_model)
              end
            end
          end
          if processed and config.general_message != "" and !routine
            respond eval("\"" + config.general_message + "\"")
          end
          respond "_*Loop #{loop_id}* (#{i + 1}/#{num_times}) <@#{user.name}>: #{command_every}_" if command_every != "" and processed
          @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)
        rescue Exception => stack
          @logger.fatal stack
        end
      end
    rescue => e
      @logger.error "exception: #{e.inspect}"
    end
  end
end

#public_holidays(country_name, location, year, month, day, add_stats: true, publish_results: true) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/slack/smart-bot/commands/general/public_holidays.rb', line 3

def public_holidays(country_name, location, year, month, day, add_stats: true, publish_results: true)
  save_stats(__method__) if add_stats
  if config[:public_holidays][:api_key].to_s == ""
    respond "Sorry, I don't have the API key for the public holidays #{config[:public_holidays][:host]}. Set it up on your SmartBot config file."
  else
    begin
      found_location = true
      http = NiceHttp.new("#{config[:public_holidays][:host]}/api/v2")
      if !defined?(@countries_candelarific)
        get_countries_candelarific()
      end
      country = @countries_candelarific.find { |c| c.country_name.match?(/^\s*#{country_name}\s*$/i) }
      if country.nil?
        respond "Country #{country_name} not found"
      else
        country_original_name = country_name.downcase
        country_region_id = country_name.downcase
        country_region_id += "/#{location.downcase}" unless location.empty?
        country_name = country["country_name"]
        country_iso = country["iso-3166"]
        states = []
        if @public_holidays.key?(country_region_id) and @public_holidays[country_region_id].key?(year.to_s)
          holidays = @public_holidays[country_region_id][year.to_s]
        elsif File.exist?(File.join(config.path, "vacations", "#{year}_#{country_region_id.gsub("/", "_").gsub(" ", "_")}.json"))
          holidays = (File.read(File.join(config.path, "vacations", "#{year}_#{country_region_id.gsub("/", "_").gsub(" ", "_")}.json"))).json()
        elsif !location.empty? and File.exist?(File.join(config.path, "vacations", "#{year}_#{country_original_name.gsub("/", "_").gsub(" ", "_")}.json"))
          holidays = (File.read(File.join(config.path, "vacations", "#{year}_#{country_original_name.gsub("/", "_").gsub(" ", "_")}.json"))).json()
          holidays.each do |holiday|
            if holiday.states.is_a?(Array)
              states << holiday.states.name
            else
              states << holiday.states
            end
          end
          holidays = holidays.select { |h| h.states.is_a?(String) and h.states == "All" or (h.states.is_a?(Array) and h.states.name.grep(/^\s*#{location}\s*$/i).length > 0) }
          holidays_specific = holidays.select { |h| h.states.is_a?(Array) and h.states.name.grep(/^\s*#{location}\s*$/i).length > 0 }
          found_location = false if holidays_specific.length == 0
        else
          response = http.get "/holidays?country=#{country_iso}&year=#{year}&day=#{day}&month=#{month}&api_key=#{config[:public_holidays][:api_key]}"
          holidays = response.data.json(:holidays)
          if location != ""
            holidays.each do |holiday|
              if holiday.states.is_a?(Array)
                states << holiday.states.name
              else
                states << holiday.states
              end
            end
            holidays = holidays.select { |h| h.states.is_a?(String) and h.states == "All" or (h.states.is_a?(Array) and h.states.name.grep(/^\s*#{location}\s*$/i).length > 0) }
            holidays_specific = holidays.select { |h| h.states.is_a?(Array) and h.states.name.grep(/^\s*#{location}\s*$/i).length > 0 }
            found_location = false if holidays_specific.length == 0
          end
          if day == "" and month == "" and holidays.is_a?(Array) and holidays.length > 0 and found_location
            File.write(File.join(config.path, "vacations", "#{year}_#{country_region_id.gsub("/", "_").gsub(" ", "_")}.json"), holidays.to_json) if holidays.is_a?(Array)
          end
        end
        if day == "" and month == ""
          date = year
        elsif day == ""
          date = "#{year}-#{"%02d" % month.to_i}"
        else
          date = "#{year}-#{"%02d" % month.to_i}-#{"%02d" % day.to_i}"
        end
        if holidays.is_a?(Array) and holidays.length > 0 and found_location
          date_holiday = ""
          messages = ["*Holidays in #{country_name}#{" #{location.downcase.capitalize}" unless location.empty?} in #{date}*"]
          num_holidays_to_show = 0
          all_holidays = []
          states = []
          holidays_to_add = []
          holidays.each do |holiday|
            if holiday.type.join.match?(/holiday/i)
              if location == "" or (location != "" and (holiday.states.is_a?(String) and holiday.states == "All") or (holiday.states.is_a?(Array) and holiday.states.name.grep(/#{location}/i).length > 0))
                holiday_id = "#{holiday[:date][:datetime][:year]}-#{"%02d" % holiday[:date][:datetime][:month]}-#{"%02d" % holiday[:date][:datetime][:day]} #{holiday[:name]}"
                unless all_holidays.include?(holiday_id) or
                      (day != "" and holiday[:date][:datetime][:day] != day.to_i) or
                      (month != "" and holiday[:date][:datetime][:month] != month.to_i)
                  all_holidays << holiday_id
                  if day == ""
                    m = holiday[:date][:datetime][:month]
                    d = holiday[:date][:datetime][:day]
                    date_holiday = " #{holiday[:date][:datetime][:year]}-#{"%02d" % m}-#{"%02d" % d} "
                  end
                  num_holidays_to_show += 1
                  unless num_holidays_to_show > 30 and publish_results
                      week_day = Date.new(holiday[:date][:datetime][:year], holiday[:date][:datetime][:month], holiday[:date][:datetime][:day]).strftime("%A")
                      messages << "\t:spiral_calendar_pad:#{date_holiday}*#{holiday[:name]}* _(#{holiday[:type].join(", ")}) (#{week_day})_"
                      messages << "\t#{holiday[:description]}"
                      if location == ""
                      if holiday.states.is_a?(Array)
                        messages << "\tLocations: #{holiday.states.name.sort.join(", ")}"
                      else
                        messages << "\tLocations: #{holiday.states}"
                      end
                    end
                    messages << "\n"
                    holidays_to_add << holiday
                  end
                  if holiday.states.is_a?(Array)
                    states << holiday.states.name
                  else
                    states << holiday.states
                  end
                end
              end
            end
          end
          @public_holidays[country_region_id] = {} if !@public_holidays.key?(country_region_id)
          @public_holidays[country_region_id][year.to_s] = holidays_to_add if !@public_holidays[country_region_id].key?(year.to_s) and date == year

          if num_holidays_to_show > 30
            messages = ["*Holidays in #{country_name}#{" #{location.downcase.capitalize}" unless location.empty?} in #{date}*"]
            messages << "Too many holidays to show, please refine your search"
          end
        else
          messages = ["*No holidays found in #{country_name}#{" #{location.downcase.capitalize}" unless location.empty?} in #{date}. Be sure the Country and State are written correctly. Try using just the Country, not all countries are supporting States.*"]
        end
        states.flatten!
        states.uniq!
        states.delete("All")
        if states.length > 1 and (location == "" or !found_location)
          messages << "*All States found in #{date} #{country_name}*: #{states.sort.join(", ")}"
          respond messages[-1] if !publish_results
        end
        respond messages.join("\n") unless !publish_results
      end
    rescue Exception => stack
      respond "Sorry, I can't get the public holidays for #{country_name} #{location} in #{date}. Error: #{stack.message}"
      @logger.fatal stack
    end
    return (found_location==true and !country.nil?) if !publish_results
  end
end

#publish_announcements(user) ⇒ Object

helpmaster: ---------------------------------------------- helpmaster: publish announcements helpmaster: It will publish on all channels the announcements added by using 'add announcement' command. helpmaster: It won't be published if less than 11 messages published on the channel since last time this command was called. helpmaster: Only works if you are on Master channel and you are a master admin user helpmaster: The messages stored on a DM won't be published. helpmaster: This is very convenient to be called from a Routine for example every weekday at 09:00. helpmaster: helpmaster: command_id: :publish_announcements helpmaster:



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# File 'lib/slack/smart-bot/commands/on_master/admin_master/publish_announcements.rb', line 12

def publish_announcements(user)
  save_stats(__method__)
  if config.on_master_bot
    if config.team_id_masters.include?("#{user.team_id}_#{user.name}") #master admin user
      channels = Dir.entries("#{config.path}/announcements/")
      channels.select! {|i| i[/\.csv$/]}
      channels.each do |channel|
        channel.gsub!('.csv','')
        unless channel[0]== 'D' or (@announcements_activity_after.key?(channel) and @announcements_activity_after[channel] <= 10)
          see_announcements(user, '', channel, false, true)
          @announcements_activity_after[channel] = 0
          sleep 0.5 # to avoid reach ratelimit
        end
      end
      react :heavy_check_mark

    else
      respond 'Only master admins on master channel can use this command.'
    end
  else
    respond 'Only master admins on master channel can use this command.'
  end
end

#react(emoji, ts = false, channel = '') ⇒ Object

list of available emojis: https://www.webfx.com/tools/emoji-cheat-sheet/ react(:thumbsup) ts: can be true, false or a specific ts



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/slack/smart-bot/comm/react.rb', line 5

def react(emoji, ts=false, channel='')
  result = true
  channel = Thread.current[:dest] if channel == ''
  if ts.is_a?(TrueClass) or ts.is_a?(FalseClass)
    parent = ts
    ts = nil
  else
    parent = false
  end
  if ts.nil?
    if parent or Thread.current[:ts].to_s == ''
      ts = Thread.current[:thread_ts]
    else
      ts = Thread.current[:ts]
    end
  else
    if ts.to_s.match?(/^\d+\.\d+$/)
      #thread id
    elsif ts.to_s.match?(/^p\d\d\d\d\d+$/)
      #a thread id taken from url fex: p1622549264010700
      ts = ts.scan(/(\d+)/).join
      ts = "#{ts[0..9]}.#{ts[10..-1]}"
    else
      ts = Thread.current[:thread_ts] if ts == ''
    end

  end
  if ts.nil?
    @logger.warn 'react method no ts supplied'
    result = false
  else
    emoji.gsub!(':','') if emoji.is_a?(String)
    begin
      client.web_client.reactions_add(channel: channel, name: emoji.to_sym, timestamp: ts) unless config.simulate
    rescue Exception => stack
      @logger.warn stack
      result = false
    end
  end
  return result
end

#react_to(dest, user, typem, to, thread_ts, emojis) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: react to #CHANNEL_NAME THREAD_ID EMOJIS helpadmin: react to URL EMOJIS helpadmin: It will send the specified reactions as SmartBot 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 helpadmin: Examples: helpadmin: react to #sales 1622550707.012100 :thumbsup: helpadmin: react to #sales p1622550707012100 :thumbsup: helpadmin: react to #sales p1622550707012100 :thumbsup: :heavy_check_mark: :bathtub: helpadmin: command_id: :react_to helpadmin:



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/slack/smart-bot/commands/on_bot/admin_master/react_to.rb', line 14

def react_to(dest, user, typem, to, thread_ts, emojis)
  save_stats(__method__)
  if config.team_id_masters.include?("#{user.team_id}_#{user.name}") and typem==:on_dm #master admin user
    succs = []
    emojis.split(' ').each do |emoji|
      succs << (react emoji, thread_ts, to)
    end
    succs.uniq!
    if succs.size == 1 and succs[0] == true
      react :heavy_check_mark
    elsif succs.size == 2
      react :exclamation
    else
      react :x
    end
  else
    respond "Only master admin users on a private conversation with the SmartBot can send reactions as SmartBot.", dest
  end
end

#recap(user, dest, channel, date_start, date_end, my_recap) ⇒ Object

todo: add tests



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/slack/smart-bot/commands/general/recap.rb', line 4

def recap(user, dest, channel, date_start, date_end, my_recap)
  save_stats(__method__)

  require "date"
  require "time"
  react :runner
  @history_still_running ||= false

  if channel[0] == "D"
    messages = []
    messages << "Sorry, `recap` command is not available in direct messages."
    messages << "Specify a channel instead or call it from a channel."
    messages << "For example: `my recap 2023 #channel`"
    messages << ""
    messages << "You can also call from a DM:"
    messages << "\t\t`bot stats last year`"
    messages << "\t\t`bot stats last year monthly graph`"
    messages << "\t\t`bot stats`"
    messages << ""
    messages << "Call `bot help bot stats` for more information."
    respond messages.join("\n")
  elsif @history_still_running
    respond "Due to Slack API rate limit, `recap` command is limited. Waiting for other `recap` command to finish."
    num_times = 0
    while @history_still_running and num_times < 30
      num_times += 1
      sleep 1
    end
    if @history_still_running
      respond "Sorry, Another `recap` command is still running after 30 seconds. Please try again later."
    end
  end

  if !@history_still_running and channel[0] != "D"
    @history_still_running = true
    begin
      date_start = date_start.gsub(".", "/").gsub("-", "/")
      date_end = date_end.gsub(".", "/").gsub("-", "/")
      if date_start.to_s == ""
        date_start = Date.today.strftime("%Y/01/01")
      elsif date_start.match(/^\d\d\d\d$/)
        date_start = "#{date_start}/01/01"
      end
      if date_end.to_s == ""
        date_end = Date.parse(date_start).strftime("%Y/12/31")
      end
      if date_end < date_start
        respond "Sorry, `recap` command requires `date_start` to be before `date_end`."
      else
        # if more than one year respond max one year
        num_days = (Date.parse(date_end) - Date.parse(date_start)).to_i
        if num_days > 365
          respond "Sorry, `recap` command is limited to one year."
        else
          messages = []
          #if date_start is 1st of January and date_end is 31st of December, then use the year only
          if Date.parse(date_start).strftime("%m/%d") == "01/01" and Date.parse(date_end).strftime("%m/%d") == "12/31"
            messages << "*#{Date.parse(date_start).strftime("%Y")} Recap <##{channel}>*"
          else
            messages << "*Recap for <##{channel}> from #{date_start} to #{date_end}*"
          end
          date_start = date_start + " 00:00:00"
          date_start = Time.strptime(date_start, "%Y/%m/%d %H:%M:%S")
          date_end = date_end + " 23:59:59"
          date_end = Time.strptime(date_end, "%Y/%m/%d %H:%M:%S")

          # get all messages from the channel
          hist = []

          begin
            members = get_channel_members(channel)
          rescue
            members = []
          end
          if !members.include?(config.nick_id_granular) or !members.include?(config.nick_id) #monitor-smartbot #smartbot
            respond "Sorry, `recap` command requires <@#{config.nick_id_granular}> and <@#{config.nick_id}> to be in the channel. Please add them and try again."
          elsif !members.include?(user.id)
            respond "Sorry, `recap` command requires you to be in the channel. Please join the channel and try again."
          else

            client_granular.conversations_history(channel: channel, limit: 200, oldest: date_start.to_f, latest: date_end.to_f) do |response|
              hist << response.messages
            end
            hist_replies = hist.deep_copy()
            hist.flatten!

            # sort hist by reply_count from highest to lowest, reply_count is not always, return a hash
            hist.each do |message|
              if !message.key?("reply_count")
                message[:reply_count] = 0
              end
            end
            num_posts = hist.length
            num_threads = hist.select { |msg| msg.key?("thread_ts") }.length

            hist_replies_count = hist.sort_by { |k| k[:reply_count] }
            # get the top 3 threads
            top_three_threads = []
            #hist_replies_count is not always 3, so we need to check if the index exists
            top_three_threads << hist_replies_count[-1] if hist_replies_count[-1] and hist_replies_count[-1].key?("thread_ts")
            top_three_threads << hist_replies_count[-2] if hist_replies_count[-2] and hist_replies_count[-2].key?("thread_ts")
            top_three_threads << hist_replies_count[-3] if hist_replies_count[-3] and hist_replies_count[-3].key?("thread_ts")

            num_messages_skipped = 0
            num_messages_not_skipped = 0
            hist.each do |message|
              if Time.at(message[:ts].to_f) >= date_start and Time.at(message[:ts].to_f) <= date_end
                if message.key?("thread_ts")
                  if message.reply_users_count == 1 and message.reply_users[0] == config.nick_id
                    #if the thread has only one replier and it is from smartbot, then we don't need to get the replies from the thread
                    num_messages_skipped += 1
                    message[:reply_count].times do
                      hist_replies << { user: config.nick_id, ts: message.thread_ts, text: "" }
                    end
                  else
                    num_messages_not_skipped += 1
                    thread_ts = message.thread_ts
                    passed = false
                    num_tries = 0
                    while !passed and num_tries < 30
                      begin
                        num_tries += 1
                        replies = client_granular.conversations_replies(channel: channel, ts: thread_ts)
                        passed = true
                      rescue => e
                        @logger.info "recap command: threads num_tries: #{num_tries}"
                        @logger.fatal "recap command: threads error: #{e.message}"
                        sleep 1
                        passed = false
                      end
                    end
                    if !passed
                      respond "Sorry, `recap` command failed. <@#{config.admins.join(">,<@")}> please check the logs."
                      @history_still_running = false
                      unreact :runner
                      return
                    end
                    hist_replies << replies.messages
                    sleep 0.5 #to avoid rate limit Tier 3 (50 requests per minute)
                  end
                end
              end
            end
            hist_replies.flatten!

            hist_user = {}
            hist_replies.each do |msg|
              hist_user[msg.user] ||= 0
              hist_user[msg.user] += 1
            end
            #sort hist_user by value from highest to lowest, return a hash
            hist_user = hist_user.sort_by { |k, v| v }.reverse.to_h
            #top_three_users excluding smartbot
            hist_user_wo_smartbot = hist_user.reject { |k, v| k == config.nick_id }

            top_three_users = hist_user_wo_smartbot.first(3).to_h
            messages << "\t :bar_chart: Totals:"
            messages << "\t\t\t messages: *#{hist_replies.length}*"
            messages << "\t\t\t SmartBot messages: *#{hist_user[config.nick_id]}* (#{hist_user[config.nick_id] * 100 / hist_replies.length} %)" if hist_user.key?(config.nick_id)
            messages << "\t\t\t posts: *#{num_posts}*"
            messages << "\t\t\t threads: *#{num_threads}*"

            num_replies = hist_replies.length - num_posts
            messages << "\t\t\t replies: *#{num_replies}*"

            messages << "\t\t\t users: *#{hist_user.length}*"
            if top_three_users.length > 0
              messages << "\n\t :boom: Top 3 users:"
              top_three_users.each do |user, num|
                messages << "\t\t\t<@#{user}>: *#{num}*"
              end
            end

            #top 3 users posting excluding smartbot
            hist_user_posts = {}
            hist.each do |msg|
              hist_user_posts[msg.user] ||= 0
              hist_user_posts[msg.user] += 1
            end
            hist_user_posts = hist_user_posts.reject { |k, v| k == config.nick_id }
            hist_user_posts = hist_user_posts.sort_by { |k, v| v }.reverse.to_h
            top_three_users_posts = hist_user_posts.first(3).to_h
            if top_three_users_posts.length > 0
              messages << "\n\t :bust_in_silhouette: Top 3 users posting:"
              top_three_users_posts.each do |user, num|
                messages << "\t\t\t<@#{user}>: *#{num}*"
              end
            end

            if top_three_threads.length > 0
              messages << "\n\t :star: Top 3 threads:"
              top_three_threads.each do |thread|
                messages << "\t\t\t<@#{thread.user}>: *#{thread.reply_count}* replies. *<https://greenqloud.slack.com/archives/#{channel}/p#{thread.ts.gsub(".", "")}|#{thread.text[0..50].gsub(/[^a-zA-Z0-9\s]/, "").gsub("\n", "").strip}>*"
              end
            end

            #get the stats from the files, they are like smart-bot.stats.2020-01.log, smart-bot.stats.2020-02.log, etc.
            smartbot_stats = []
            Dir["#{config.stats_path}.*.log"].sort.each do |file|
              #read only the files that are in the range of date_start and date_end
              if file.match(/#{config.stats_path}\.(\d\d\d\d-\d\d)\.log/)
                if $1 >= date_start.strftime("%Y-%m") and $1 <= date_end.strftime("%Y-%m")
                  smartbot_stats_file = CSV.read(file, headers: true, converters: :numeric)
                  #convert smartbot_stats_file to array of hashes
                  smartbot_stats_file = smartbot_stats_file.map(&:to_hash)
                  smartbot_stats << smartbot_stats_file
                end
              end
            end
            smartbot_stats.flatten!

            #filter by bot_channel_id==channel and dest_channel_id!=channel and type_message==on_dm
            smartbot_stats_on_dm = smartbot_stats.deep_copy
            smartbot_stats_on_dm = smartbot_stats_on_dm.select { |msg| msg["bot_channel_id"] == channel and msg["dest_channel_id"] != channel and msg["type_message"] == "on_dm" }
            #filter by date_start and date_end. We store the date on "date" field like this: 2020-01-14 16:34:16
            smartbot_stats_on_dm = smartbot_stats_on_dm.select { |msg| Date.parse(msg["date"]) >= Date.parse(date_start.strftime("%Y-%m-%d")) and Date.parse(msg["date"]) <= Date.parse(date_end.strftime("%Y-%m-%d")) }

            #filter by bot_channel_id==channel and dest_channel_id!=channel and type_message!=on_dm
            smartbot_stats_external = smartbot_stats.deep_copy
            smartbot_stats_external = smartbot_stats_external.select { |msg| msg["bot_channel_id"] == channel and msg["dest_channel_id"] != channel and msg["type_message"] != "on_dm" }
            #filter by date_start and date_end. We store the date on "date" field like this: 2020-01-14 16:34:16
            smartbot_stats_external = smartbot_stats_external.select { |msg| Date.parse(msg["date"]) >= Date.parse(date_start.strftime("%Y-%m-%d")) and Date.parse(msg["date"]) <= Date.parse(date_end.strftime("%Y-%m-%d")) }

            # filter by dest_channel_id
            smartbot_stats = smartbot_stats.select { |msg| msg["dest_channel_id"] == channel }
            # filter by date_start and date_end. We store the date on "date" field like this: 2020-01-14 16:34:16
            smartbot_stats = smartbot_stats.select { |msg| Date.parse(msg["date"]) >= Date.parse(date_start.strftime("%Y-%m-%d")) and Date.parse(msg["date"]) <= Date.parse(date_end.strftime("%Y-%m-%d")) }

            #get the number of times SmartBot was called by a user, if field 'user_name' is not including 'routine/'
            smartbot_stats_user = {}
            smartbot_stats.each do |msg|
              if !msg["user_name"].include?("routine/")
                smartbot_stats_user[msg["user_name"]] ||= 0
                smartbot_stats_user[msg["user_name"]] += 1
              end
            end

            smartbot_stats_total = smartbot_stats_user.values.sum
            if smartbot_stats_total > 0
              messages << "\t :robot_face: *SmartBot* Stats:"
              #total of times SmartBot was called
              messages << "\t\t\t called by a user: *#{smartbot_stats_total}*"
              #total of times SmartBot was called by any user including routines
              smartbot_stats_total_all = smartbot_stats.length
              messages << "\t\t\t called on this channel: *#{smartbot_stats_total_all}*" if smartbot_stats_total != smartbot_stats_total_all
              #total of times SmartBot was called using this channel on a DM
              messages << "\t\t\t called on a DM using this channel: *#{smartbot_stats_on_dm.length}*" if smartbot_stats_on_dm.length > 0
              #total of times SmartBot was called using this channel from another channel
              messages << "\t\t\t called from another channel using this channel: *#{smartbot_stats_external.length}*" if smartbot_stats_external.length > 0

              #total of times SmartBot was used
              total_sb_calls = smartbot_stats_total_all + smartbot_stats_on_dm.length + smartbot_stats_external.length
              messages << "\t\t\t Total times SmartBot was used: *#{total_sb_calls}*" if total_sb_calls != smartbot_stats_total_all

              #get the three most used commands. The command is stored on "command" field
              smartbot_stats_commands = {}
              smartbot_stats.each do |msg|
                smartbot_stats_commands[msg["command"]] ||= 0
                smartbot_stats_commands[msg["command"]] += 1
              end

              #total number of different commands
              smartbot_stats_total_commands = smartbot_stats_commands.keys.length
              messages << "\t\t\t Total different commands used: *#{smartbot_stats_total_commands}*"

              #top three commands
              smartbot_stats_commands = smartbot_stats_commands.sort_by { |k, v| v }.reverse.to_h
              smartbot_stats_commands = smartbot_stats_commands.first(3).to_h
              messages << "\t\t\t :speech_balloon: Top 3 commands:"
              smartbot_stats_commands.each do |command, num|
                messages << "\t\t\t\t*#{command}*: *#{num}*"
              end

              #top three commands excluding routines only if the top 3 is different from including routines
              smartbot_stats_commands_include_routines = smartbot_stats_commands.deep_copy

              smartbot_stats_commands = {}
              smartbot_stats.each do |msg|
                if !msg["user_name"].include?("routine/")
                  smartbot_stats_commands[msg["command"]] ||= 0
                  smartbot_stats_commands[msg["command"]] += 1
                end
              end
              smartbot_stats_commands = smartbot_stats_commands.sort_by { |k, v| v }.reverse.to_h
              smartbot_stats_commands = smartbot_stats_commands.first(3).to_h
              if smartbot_stats_commands != smartbot_stats_commands_include_routines
                messages << "\t\t\t :speech_balloon: Top 3 commands excluding routines:"
                smartbot_stats_commands.each do |command, num|
                  messages << "\t\t\t\t*#{command}*: *#{num}*"
                end
              end

              #get the three most used users. The user is stored on "user_name" field
              smartbot_stats_user = smartbot_stats_user.sort_by { |k, v| v }.reverse.to_h
              smartbot_stats_user = smartbot_stats_user.first(3).to_h
              messages << "\t\t\t :bust_in_silhouette: Top 3 users calling SmartBot:"
              smartbot_stats_user.each do |user, num|
                messages << "\t\t\t\t<@#{user}>: *#{num}*"
              end
            end

            #number of messages by month
            hist_month = {}
            hist_replies.each do |msg|
              if Time.at(msg[:ts].to_f) >= date_start and Time.at(msg[:ts].to_f) <= date_end
                date = Time.at(msg[:ts].to_f).strftime("%Y/%m")
                hist_month[date] ||= 0
                hist_month[date] += 1
              end
            end
            hist_month = hist_month.sort_by { |k, v| k }.to_h
            respond messages.join("\n")
            messages = []
            messages << "\t :memo: Number of messages by month:" if hist_month.size > 0
            hist_month.each do |date, num|
              graph = ":large_yellow_square: " * (num.to_f * (10 * hist_month.size) / hist_replies.size).round(2)
              messages << "\t\t\t#{date}: #{graph} #{num} (#{num * 100 / hist_replies.size}%)"
            end

            #number of users by month
            hist_month_user = {}
            hist_replies.each do |msg|
              if Time.at(msg[:ts].to_f) >= date_start and Time.at(msg[:ts].to_f) <= date_end
                date = Time.at(msg[:ts].to_f).strftime("%Y/%m")
                hist_month_user[date] ||= []
                hist_month_user[date] << msg.user
              end
            end
            hist_month_user = hist_month_user.sort_by { |k, v| k }.to_h
            respond messages.join("\n")
            messages = []
            messages << "\t :person_with_crown: Number of users by month:" if hist_month_user.size > 0
            total_users_by_month = 0
            hist_month_user.each do |date, users|
              total_users_by_month += users.uniq.length
            end
            hist_month_user.each do |date, users|
              graph = ":large_orange_square: " * (users.uniq.length.to_f * (10 * hist_month_user.size) / total_users_by_month).round(2)
              messages << "\t\t\t#{date}: #{graph} #{users.uniq.length} "
            end
            if my_recap
              respond messages.join("\n")
              messages = []
              messages << "\t :medal: *Recap for <@#{user[:id]}>*:"
              messages << "\t\t\tTotal messages: *#{hist_user[user[:id]]}*"
              # top three posts of user.name
              hist_user_posts = hist.select { |msg| msg.user == user[:id] }
              hist_user_posts = hist_user_posts.sort_by { |k| k[:reply_count] }
              top_three_posts = [hist_user_posts[-1], hist_user_posts[-2], hist_user_posts[-3]]
              messages << "\t\t\tTop 3 threads:"
              top_three_posts.each do |post|
                if !post.nil?
                  messages << "\t\t\t\t *#{post.reply_count}* replies: *<https://greenqloud.slack.com/archives/#{channel}/p#{post.ts.gsub(".", "")}|#{post.text[0..50].gsub(/[^a-zA-Z0-9\s]/, "").gsub("\n", "").strip}>*"
                end
              end

              #Add also stats from SmartBot commands for this user
              #filter stats by user
              smartbot_stats_user = smartbot_stats.select { |msg| msg["user_id"] == user[:id] }
              #get the number of times SmartBot was called by this user
              smartbot_stats_user_total = smartbot_stats_user.length
              messages << "\t\t\t SmartBot calls: *#{smartbot_stats_user_total}*"
              #get the three most used commands by this user
              smartbot_stats_user_commands = {}
              smartbot_stats_user.each do |msg|
                smartbot_stats_user_commands[msg["command"]] ||= 0
                smartbot_stats_user_commands[msg["command"]] += 1
              end
              if smartbot_stats_user_total > 0
                #total number of different commands
                smartbot_stats_user_total_commands = smartbot_stats_user_commands.keys.length
                messages << "\t\t\t Different commands: *#{smartbot_stats_user_total_commands}*"
                #top three commands
                smartbot_stats_user_commands = smartbot_stats_user_commands.sort_by { |k, v| v }.reverse.to_h
                smartbot_stats_user_commands = smartbot_stats_user_commands.first(3).to_h
                messages << "\t\t\t :speech_balloon: Top 3 commands:"
                smartbot_stats_user_commands.each do |command, num|
                  messages << "\t\t\t\t\t*#{command}*: *#{num}*"
                end
              end
            end

            respond messages.join("\n")
          end
        end
      end
    rescue => e
      @logger.fatal "recap command failed: #{e.message}"
      @logger.fatal e.backtrace.inspect
      respond "Sorry, `recap` command failed. <@#{config.admins.join(">,<@")}> please check the logs."
    end
    @history_still_running = false
  end
  unreact :runner
end

#remove_admin(user, admin_user) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/slack/smart-bot/commands/general/remove_admin.rb', line 2

def remove_admin(user, admin_user)
  save_stats(__method__)
  if Thread.current[:dest][0]=='D'
    respond "This command cannot be called from a DM"
  else

    if Thread.current[:typem] == :on_call
      channel = Thread.current[:dchannel]
    elsif Thread.current[:using_channel].to_s==''
      channel = Thread.current[:dest]
    else
      channel = Thread.current[:using_channel]
    end
    messages = []
    admins = config.masters.dup
    team_id_admins = config.team_id_masters.dup
    channels = get_channels()
    channel_found = channels.detect { |c| c.id == channel }
    if !channel_found.nil? and channel_found.creator.to_s != ''
      creator_info = find_user(channel_found.creator)
      admins << creator_info.name
      team_id_admins << "#{creator_info.team_id}_#{creator_info.name}"
    end
    if Thread.current[:typem] == :on_bot or Thread.current[:typem] == :on_master
      admins << config.admins.dup
      team_id_admins << config.team_id_admins.dup
    end
    if @admins_channels.key?(channel) and @admins_channels[channel].size > 0
      team_id_admins << @admins_channels[channel]
      #remove the team_id from the names on @admins_channels
      admins << @admins_channels[channel].map { |a| a.split('_')[1..-1].join('_') }
    end
    admins.flatten!
    admins.uniq!
    admins.delete(nil)
    team_id_admins.flatten!
    team_id_admins.uniq!
    team_id_admins.delete(nil)

    if team_id_admins.include?("#{user.team_id}_#{user.name}")
      admin_info = find_user(admin_user)
      if creator_info.name == admin_info.name and creator_info.team_id == admin_info.team_id
        messages << "This user created the channel and cannot be removed as an admin."
      elsif config.team_id_masters.include?("#{admin_info.team_id}_#{admin_info.name}") or config.team_id_masters.include?(admin_user)
        messages << "Master admins cannot be removed as admins of this channel."
      elsif config.team_id_admins.include?("#{admin_info.team_id}_#{admin_info.name}") or config.team_id_admins.include?(admin_user)
        messages << "This user is a defaulted admin for this channel and cannot be removed using this command."
      elsif !team_id_admins.include?("#{admin_info.team_id}_#{admin_info.name}")
        messages << "This user is not an admin of this channel."
      else
        @admins_channels[channel] ||= []
        @admins_channels[channel].delete("#{admin_info.team_id}_#{admin_info.name}")
        update_admins_channels()
        messages << "The user is not an admin of this channel from now on."
        admins.delete(admin_info.name)
        team_id_admins.delete("#{admin_info.team_id}_#{admin_info.name}")
      end
      messages << "*Admins*: <@#{admins.join('>, <@')}>"
    else
      messages << "Only the creator of the channel, Master admins or admins can remove an admin of this channel."
      messages << "*Admins*: <@#{admins.join('>, <@')}>"
    end

    respond messages.join("\n")
  end
end

#remove_hash_keys(hash, key) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
# File 'lib/slack/smart-bot/utils/remove_hash_keys.rb', line 3

def remove_hash_keys(hash, key)
  newh = Hash.new
  hash.each do |k, v|
    unless k == key
      if v.is_a?(String)
        newh[k] = v
      else
        newh[k] = remove_hash_keys(v, key)
      end
    end
  end
  return newh
end

#remove_routine(dest, from, name) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: kill routine NAME helpadmin: delete routine NAME helpadmin: remove routine NAME helpadmin: It will kill and remove the specified routine helpadmin: You can use this command only if you are an admin user helpadmin: NAME: one word to identify the routine helpadmin: Examples: helpadmin: kill routine example helpadmin: helpadmin: command_id: :remove_routine helpadmin:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/slack/smart-bot/commands/on_bot/admin/remove_routine.rb', line 15

def remove_routine(dest, from, name)
  save_stats(__method__)
  if is_admin?
    if @routines.key?(@channel_id) and @routines[@channel_id].key?(name)
      react :running
      @routines[@channel_id][name][:thread].exit
      @routines[@channel_id].delete(name)
      update_routines()
      respond "The routine *`#{name}`* has been removed.", dest
      unreact :running
    else
      respond "There isn't a routine with that name: *`#{name}`*.\nCall `see routines` to see added routines", dest
    end
  else
    respond "Only admin users can delete routines", dest
  end
end

#remove_vacation(user, vacation_id) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/slack/smart-bot/commands/general/remove_vacation.rb', line 2

def remove_vacation(user, vacation_id)
  save_stats(__method__)

  get_vacations()
  team_id_user = "#{user.team_id}_#{user.name}"

  if !@vacations.key?(team_id_user)
    respond "It seems like you don't have any time off added."
  elsif @vacations[team_id_user].periods.empty? or !@vacations[team_id_user].periods.vacation_id.include?(vacation_id)
    respond "It seems like the ID supplied doesn't exist. Please call `see my time off` and check the ID."
  else
    if @vacations.key?(team_id_user) and @vacations[team_id_user][:public_holidays].to_s != ""
      country_region = @vacations[team_id_user][:public_holidays].downcase
    elsif config[:public_holidays].key?(:default_calendar)
      country_region = config[:public_holidays][:default_calendar].downcase
    else
      country_region = ''
    end

    local_day_time = local_time(country_region)
    if local_day_time.nil?
      today = Date.today
    else
      today = local_day_time.to_date
    end

    vacations = @vacations[team_id_user].deep_copy
    vacation = vacations.periods.select {|v| v.vacation_id == vacation_id }[-1]
    vacations.periods.delete_if {|v| v.vacation_id == vacation_id }
    update_vacations({team_id_user => vacations})
    respond "Your time off has been removed."
    if vacation.from <= today.strftime("%Y/%m/%d") and vacation.to >= today.strftime("%Y/%m/%d")
      info = (vacations.user_id)
      emoji = info.user.profile.status_emoji
      if (vacation.type == 'vacation' and emoji == ':palm_tree:') or (vacation.type == 'sick' and emoji == ':face_with_thermometer:') or
         (vacation.type == 'sick child' and emoji == ':baby:')
        set_status(vacations.user_id, status: '', expiration: '', message: '')
      end
      check_vacations(date: today, team_id: user.team_id, user: user.name, set_status: true, only_first_day: false)
    end
  end
end

#repl(dest, user, session_name, env_vars, rules_file, command, description, type) ⇒ Object

help: ---------------------------------------------- help: >https://github.com/MarioRuiz/slack-smart-bot#repl|REPLs help: repl help: live help: irb help: repl SESSION_NAME help: private repl SESSION_NAME help: clean repl SESSION_NAME help: repl ENV_VAR=VALUE help: repl SESSION_NAME ENV_VAR=VALUE ENV_VAR='VALUE' help: repl SESSION_NAME: "DESCRIPTION" help: repl SESSION_NAME: "DESCRIPTION" ENV_VAR=VALUE ENV_VAR='VALUE' help: Will run all we write as a ruby command and will keep the session values. help: SESSION_NAME only admits from a to Z, numbers, - and _ help: If no SESSION_NAME supplied it will be treated as a temporary REPL help: If 'private' specified the repl will be accessible only by you and it will be displayed only to you when see repls help: If 'clean' specified the repl won't pre execute the code written on the .smart-bot-repl file help: To avoid a message to be treated, start the message with '-'. help: Send quit, bye or exit to finish the session. help: Send puts, print, p or pp if you want to print out something when using run repl later. help: After 30 minutes of no communication with the Smart Bot the session will be dismissed. 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. help: By default it will be automatically loaded the gems: string_pattern, nice_hash and nice_http 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 help: If you want to see the methods of a class or module you created use ls TheModuleOrClass help: To see the code of a method: code TheModuleOrClass.my_method help: To see the documentation of a method: doc TheModuleOrClass.my_method help: You can ask ChatGPT to help you or suggest any code by sending the message: ? PROMPT help: If you send just ? it will suggest some code to be added. help: You can supply the Environmental Variables you need for the Session help: You can add collaborators by sending add collaborator @USER to the session. help: Examples: help: repl CreateCustomer LOCATION=spain HOST='https://10.30.40.50:8887' help: repl CreateCustomer: "It creates a random customer for testing" LOCATION=spain HOST='https://10.30.40.50:8887' help: repl delete_logs help: private repl random-ssn help: help: command_id: :repl help:



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# File 'lib/slack/smart-bot/commands/on_bot/repl.rb', line 41

def repl(dest, user, session_name, env_vars, rules_file, command, description, type)
  #todo: add more tests
  from = user.name
  team_id_user = "#{user.team_id}_#{user.name}"
  if has_access?(__method__, user)
    if !@repl_sessions.key?(team_id_user)
      save_stats(__method__)
      Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
      Dir.mkdir("#{config.path}/repl/#{@channel_id}") unless Dir.exist?("#{config.path}/repl/#{@channel_id}")

      serialt = Time.now.strftime("%Y%m%d%H%M%S%N")
      if session_name.to_s == ""
        session_name = "#{from}_#{serialt}"
        temp_repl = true
      else
        temp_repl = false
        i = 0
        name = session_name
        while File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.input")
          i += 1
          session_name = "#{name}#{i}"
        end
      end
      @repl_sessions[team_id_user] = {
        name: session_name,
        dest: dest,
        started: Time.now,
        finished: Time.now,
        input: [],
        on_thread: Thread.current[:on_thread],
        thread_ts: Thread.current[:thread_ts],
        collaborators: [],
        user_type: :creator,
        user_creator: team_id_user,
      }

      unless temp_repl
        @repls[session_name] = {
          created: @repl_sessions[team_id_user][:started].to_s,
          accessed: @repl_sessions[team_id_user][:started].to_s,
          creator_name: user.name,
          creator_team_id: user.team_id,
          creator_id: user.id,
          description: description,
          type: type,
          runs_by_creator: 0,
          runs_by_others: 0,
          gets: 0,
        }
        update_repls()
      end
      react :running
      @ts_react ||= {}
      if Thread.current[:ts].to_s == ""
        @ts_react[session_name] = Thread.current[:thread_ts]
      else
        @ts_react[session_name] = Thread.current[:ts]
      end
      @ts_repl ||= {}
      @ts_repl[session_name] = ""
      process_to_run = repl_client(team_id_user, session_name, type, serialt, env_vars)

      unless rules_file.empty? # to get the project_folder
        begin
          eval(File.new(config.path + rules_file).read) if File.exist?(config.path + rules_file)
        end
      end

      file_run_path = "./tmp/repl/#{@channel_id}/#{session_name}.rb"
      if defined?(project_folder)
        Dir.mkdir("#{project_folder}/tmp/") unless Dir.exist?("#{project_folder}/tmp/")
        Dir.mkdir("#{project_folder}/tmp/repl") unless Dir.exist?("#{project_folder}/tmp/repl")
        Dir.mkdir("#{project_folder}/tmp/repl/#{@channel_id}/") unless Dir.exist?("#{project_folder}/tmp/repl/#{@channel_id}/")
        file_run = File.open(file_run_path.gsub("./", "#{project_folder}/"), "w")
        file_run.write process_to_run
        file_run.close
      else
        Dir.mkdir("./tmp/") unless Dir.exist?("./tmp/")
        Dir.mkdir("./tmp/repl") unless Dir.exist?("./tmp/repl")
        Dir.mkdir("./tmp/repl/#{@channel_id}/") unless Dir.exist?("./tmp/repl/#{@channel_id}/")
        file_run = File.open(file_run_path, "w")
        file_run.write process_to_run
        file_run.close
      end

      process_to_run = "ruby #{file_run_path}"

      started = Time.now
      process_to_run = ("cd #{project_folder} && " + process_to_run) if defined?(project_folder)
      stdin, stdout, stderr, wait_thr = Open3.popen3(process_to_run)
      timeout = TIMEOUT_LISTENING # 30 minutes

      file_output_repl = File.open("#{config.path}/repl/#{@channel_id}/#{session_name}.output", "r")
      @repl_sessions[team_id_user][:pid] = wait_thr.pid
      @repl_sessions[team_id_user][:output] ||= []
      @repl_sessions[team_id_user][:input_output] ||= []
      @repl_sessions[team_id_user][:input_output] << "Please chatgpt return code in Ruby language."
      if File.exist?("#{project_folder}/.smart-bot-repl") and type != :private_clean and type != :public_clean
          pre_input = File.read("#{project_folder}/.smart-bot-repl")
          @repl_sessions[team_id_user][:input_output] << "```\n#{pre_input}\n```"
          respond "*Preloaded source code:*\n```\n#{pre_input}\n```"
      end

      while (wait_thr.status == "run" or wait_thr.status == "sleep") and @repl_sessions.key?(team_id_user)
        begin
          if (Time.now - @repl_sessions[team_id_user][:finished]) > timeout
            open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[team_id_user][:name]}.input", "a+") { |f|
              f.puts "quit"
            }
            respond "REPL session finished: #{@repl_sessions[team_id_user][:name]}", dest
            unreact :running, @ts_react[@repl_sessions[team_id_user].name]
            pids = `pgrep -P #{@repl_sessions[team_id_user][:pid]}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
            pids.each do |pid|
              begin
                Process.kill("KILL", pid)
              rescue
              end
            end
            @repl_sessions[team_id_user][:collaborators].each do |collaborator|
              @repl_sessions.delete(collaborator)
            end
            @repl_sessions.delete(team_id_user)
            break
          end
          sleep 0.2
          resp_repl = file_output_repl.read
          if resp_repl.to_s != ""
            if @ts_repl[@repl_sessions[team_id_user].name].to_s != ""
              unreact(:running, @ts_repl[@repl_sessions[team_id_user].name])
              @ts_repl[@repl_sessions[team_id_user].name] = ""
            end
            if (resp_repl.to_s.lines.count < 60 and resp_repl.to_s.size < 3500) or
               resp_repl.match?(/^\s*[_\*]*`\w+`/im)
              respond resp_repl, dest
            else
              resp_repl.gsub!(/^\s*```/, "")
              resp_repl.gsub!(/```\s*$/, "")
              send_file(dest, "", "response.rb", "", "text/plain", "ruby", content: resp_repl)
            end
            @repl_sessions[team_id_user][:output] << resp_repl
            @repl_sessions[team_id_user][:input_output] << resp_repl
          end
        rescue Exception => excp
          @logger.fatal excp
        end
      end
    elsif @repl_sessions.key?(team_id_user) and @repl_sessions[team_id_user][:command].to_s == ""
      respond "You are already in a repl on this SmartBot. You need to quit that one before starting a new one."
    else
      @repl_sessions[team_id_user][:finished] = Time.now
      code = @repl_sessions[team_id_user][:command]
      @repl_sessions[team_id_user][:command] = ""
      code.gsub!("\\n", "\n")
      code.gsub!("\\r", "\r")
      # Disabled for the moment since it is deleting lines with '}'
      #code.gsub!(/^\W*$/, "") #to remove special chars from slack when copy/pasting.
      if code.match?(/\A\s*-/i)
        # don't treat
      elsif code.match(/\A\s*\?\s*(.*)\s*\z/im)
        save_stats :open_ai_chat
        #call chatgpt when: ? prompt
        #if no prompt then suggest next code line
        prompt = $1
        prompt = "suggest next code line" if prompt.to_s == ""
        tid = "#{user.team_id}_#{user.name}"
        @repl_sessions[tid][:chat_gpt] ||= {}
        react :speech_balloon
        if !@repl_sessions[tid][:chat_gpt].key?(:client) or @repl_sessions[tid][:chat_gpt][:client].nil?
          get_personal_settings()
          tmp_repl_sessions, message_connect = SlackSmartBot::AI::OpenAI.connect(@repl_sessions, config, @personal_settings, reconnect: true, service: :chat_gpt)
          @repl_sessions[tid][:chat_gpt] = tmp_repl_sessions[tid][:chat_gpt]
          respond message_connect if message_connect
        end
        @repl_sessions[tid][:chat_gpt][:prompts] ||= []
        unless @repl_sessions[tid][:chat_gpt][:client].nil?
          model ||= @repl_sessions[tid][:chat_gpt][:smartbot_model]
          #todo: add source code to the prompt
          @repl_sessions[tid][:chat_gpt][:prompts] << prompt
          @repl_sessions[team_id_user][:input_output] << prompt
          prompts = @repl_sessions[tid][:input_output].join("\n")
          success, res = SlackSmartBot::AI::OpenAI.send_gpt_chat(@repl_sessions[tid][:chat_gpt][:client], model, prompts, @repl_sessions[tid].chat_gpt)
          if success
            @repl_sessions[tid][:chat_gpt][:prompts] << res
            respond "*ChatGPT>* _#{model}_\n#{res}"
            @repl_sessions[team_id_user][:input_output] << res
          elsif res.to_s.strip!=''
            respond "*ChatGPT>* _#{model}_\n#{res}"
          else
            respond "ChatGPT: Sorry, I cannot chat right now. Please try again later."
          end
        end
        unreact :speech_balloon
      elsif code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File.") or
            code.include?("`") or code.include?("exec") or code.include?("spawn") or code.include?("IO.") or
            code.match?(/open3/i) or code.match?(/bundle/i) or code.match?(/gemfile/i) or code.include?("%x") or
            code.include?("ENV") or code.match?(/=\s*IO/) or code.include?("Dir.") or
            code.match?(/=\s*File/) or code.match?(/=\s*Dir/) or code.match?(/<\s*File/) or code.match?(/<\s*Dir/) or
            code.match?(/\w+:\s*File/) or code.match?(/\w+:\s*Dir/) or
            code.match?(/=?\s*(require|load)(\(|\s)/i)
        respond "Sorry I cannot run this due security reasons", dest
      elsif code.match(/\A\s*add\s+collaborator\s+<@(\w+)>\s*\z/i)
        collaborator = $1
         = find_user(collaborator)
        collaborator_name = "#{.team_id}_#{.name}"
        if @repl_sessions.key?(collaborator_name)
          respond "Sorry, <@#{collaborator}> is already in a repl. Please ask her/him to quit it first.", dest
        else
          respond "Collaborator added. Now <@#{collaborator}> can interact with this repl.", dest
          creator = @repl_sessions[team_id_user][:user_creator]
          @repl_sessions[creator][:collaborators] << collaborator_name
          @repl_sessions[collaborator_name] = {
            name: @repl_sessions[team_id_user][:name],
            dest: dest,
            on_thread: Thread.current[:on_thread],
            thread_ts: Thread.current[:thread_ts],
            user_type: :collaborator,
            user_creator: creator,
          }
        end
      else
        if @repl_sessions[team_id_user][:user_type] == :collaborator
          creator = @repl_sessions[team_id_user][:user_creator]
          @repl_sessions[@repl_sessions[team_id_user][:user_creator]][:input] << code
        else
          creator = team_id_user
          @repl_sessions[team_id_user][:input] << code
        end
        if code.include?("```")
          @repl_sessions[creator][:input_output] << code
        else
          @repl_sessions[creator][:input_output] << "```\n#{code}\n```"
        end
        case code
        when /^\s*(quit|exit|bye|bye\s+bot)\s*$/i
          if @repl_sessions[team_id_user][:user_type] == :collaborator
            respond "Collaborator <@#{user.id}> removed.", dest
            @repl_sessions[creator][:collaborators].delete(team_id_user)
            @repl_sessions.delete(team_id_user)
          else
            open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[team_id_user][:name]}.input", "a+") { |f|
              f.puts code
            }
            respond "REPL session finished: #{@repl_sessions[team_id_user][:name]}", dest
            unreact :running, @ts_react[@repl_sessions[team_id_user].name]
            pids = `pgrep -P #{@repl_sessions[team_id_user][:pid]}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
            pids.each do |pid|
              begin
                Process.kill("KILL", pid)
              rescue
              end
            end
            @repl_sessions[team_id_user][:collaborators].each do |collaborator|
              @repl_sessions.delete(collaborator)
            end
            @repl_sessions.delete(team_id_user)
          end
        else
          if @ts_repl[@repl_sessions[creator].name].to_s == ""
            @ts_repl[@repl_sessions[creator].name] = Thread.current[:ts]
            react :running
          end
          open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[creator][:name]}.input", "a+") { |f|
            f.puts code
          }
        end
      end
    end
  end
end

#repl_client(team_id_user, session_name, type, serialt, env_vars) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/slack/smart-bot/commands/on_bot/repl_client.rb', line 2

def repl_client(team_id_user, session_name, type, serialt, env_vars)
  message = "Session name: *#{session_name}*
      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`.
      In case you need someone to help you with the session you can add collaborators by sending `add collaborator @USER` to the session.
      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`.
      Use `p` to print a message raw, exacly like it is returned.
      If you want to avoid a message to be treated by me, start the message with '-'.
      After 30 minutes of no communication with the Smart Bot the session will be dismissed.
      If you want to see the methods of a class or module you created use _ls TheModuleOrClass_
      To see the code of a method: _code TheModuleOrClass.my_method_. To see the documentation of a method: _doc TheModuleOrClass.my_method_
      You can ask *ChatGPT* to help you or suggest any code by sending the message: `? PROMPT`. If no prompt then it will suggest the next line of code.
      You can supply the Environmental Variables you need for the Session
      Example:
        _repl CreateCustomer LOCATION=spain HOST='https://10.30.40.50:8887'_
      "
  respond message

  File.write("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[team_id_user][:name]}.input", "", mode: "a+")
  File.write("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[team_id_user][:name]}.output", "", mode: "a+")
  File.write("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[team_id_user][:name]}.run", "", mode: "a+")

  if type != :private_clean and type != :public_clean
    pre_execute = '
          if File.exist?(\"./.smart-bot-repl\")
            begin
              eval(File.read(\"./.smart-bot-repl\"), bindme' + serialt + ")
            rescue Exception => resp_repl
            end
          end
        "
  else
    pre_execute = ""
  end

  process_to_run = "
          " + env_vars.join("\n") + '
          require \"amazing_print\"
          require \"stringio\"
          require \"method_source\"
          bindme' + serialt + ' = binding
          eval(\"require \'nice_http\'\" , bindme' + serialt + ')
          def get_met_params(obj, m=nil)
            result = ""
            if m.nil?
              met = obj
            else
              met = obj.method(m)
            end
            met.source.split("\n").each { |line|
              line.gsub!("def", "")
              line.gsub!("self.", "")
              line.strip!
              line.gsub!(/\A(\w+)\(/, \'*`\1`* (\')
              line.gsub!(/\A(\w+)$/, \'*`\1`*\')
              if line.strip[-1] == ")" or (result.empty? and !line.include?("("))
                line.gsub!(/\A(\w+)\s/, \'*`\1`* \') if !line.include?("(")
                result << "#{line}"
                result << " ..." if !line.include?("(") and !line.include?(")")
                result << "\n"
                break
              else
                result << "#{line}\n"
              end
            }
            result
          end
          def ls(obj)
            result = ""
            (obj.methods - Object.methods).sort.each do |m|
              if obj.respond_to?(m)
                pre = "*`#{obj}`*."
              else
                pre = ""
              end
              result << "#{pre}#{get_met_params(obj, m)}"
              result << "\n"
            end
            puts result
          end
          def get_object(obj_txt)
            met = obj_txt.scan(/\.(\w+)/).flatten.last

            if met.nil? and obj_txt[0].match(/[A-Z]/)
              obj = Object
              obj_txt.split("::").each do |cl|
                if obj.const_defined?(cl.to_sym)
                  obj = obj.const_get(cl)
                else
                  obj = nil
                  break
                end
              end
            elsif met.nil? and obj_txt[0].match(/[a-z]/)
              begin
                obj = self.method(obj_txt)
              rescue
                obj = nil
              end
            else
              cl = obj_txt.scan(/([\w\:]+)\./).flatten.first
              obj = Object
              cl.split("::").each do |cl|
                if obj.const_defined?(cl.to_sym)
                  obj = obj.const_get(cl)
                else
                  obj = nil
                  break
                end
              end
              unless obj.nil?
                if obj.respond_to?(met)
                  obj = obj.method(met)
                elsif obj.instance_method(met)
                  obj = obj.instance_method(met)
                else
                  obj = nil
                end
              end

            end
          end

          def doc(obj_txt)
            obj = get_object(obj_txt)
            if !obj.nil? and obj.respond_to?(:source_location) and obj.respond_to?(:comment) and
              !obj.source_location.nil? and !obj.comment.nil?
              result = "_*#{obj.source_location.join(":").gsub(Dir.pwd,"").gsub(Dir.home,"")}*_\n\n"
              comment = obj.comment.gsub(/^\s*\#/, "")
              comment.gsub!(/^\s*#+\s*(\R|$)/, "")
              comment.gsub!(/^\s(\w)([\w\s\-]*):/i, \'*\1\2*:\')
              comment.gsub!(/^(\s+)(\w[\w\s\-]*):/i, \'\1*`\2`*:\')
              result << "#{comment}"
              result << "\n\n"
              result << get_met_params(obj)
            else
              result = "No documentation found for #{obj_txt}. The object doesn\'t exist or it is not accessible."
            end
            puts result
          end

          def source(obj_txt)
            obj = get_object(obj_txt)
            if !obj.nil? and obj.respond_to?(:source_location) and obj.respond_to?(:comment) and
              !obj.source_location.nil? and !obj.comment.nil?
              result = "# #{obj.source_location.join(":").gsub(Dir.pwd,"").gsub(Dir.home,"")}\n\n"
              result << "#{obj.source}"
            else
              result = "No source code found for #{obj_txt}. The object doesn\'t exist or it is not accessible."
            end
            puts result
          end

          def code(obj_txt)
            source(obj_txt)
          end

          file_run_path = \"' + +File.expand_path(config.path) + "/repl/" + @channel_id + "/" + session_name + '.rb\"
          file_input_repl = File.open(\"' + File.expand_path(config.path) + "/repl/" + @channel_id + "/" + session_name + '.input\", \"r\")
          ' + pre_execute + '
          while true do
            sleep 0.2
            code_to_run_repl = file_input_repl.read
            if code_to_run_repl.to_s!=\"\"
              add_to_run_repl = true
              if code_to_run_repl.to_s.match?(/^quit$/i) or
                code_to_run_repl.to_s.match?(/^exit$/i) or
                code_to_run_repl.to_s.match?(/^bye bot$/i) or
                code_to_run_repl.to_s.match?(/^bye$/i)
                exit
              else
                error = false
                as_it_is = false
                begin
                    if code_to_run_repl.match?(/^\s*ls\s+(.+)/)
                      add_to_run_repl = false
                      as_it_is = true
                    elsif code_to_run_repl.match(/^\s*doc\s+(.+)/)
                      add_to_run_repl = false
                      code_to_run_repl = \"doc \\\"#{$1}\\\"\"
                      as_it_is = true
                    elsif code_to_run_repl.match(/^\s*(code|source|src)\s+(.+)/)
                      add_to_run_repl = false
                      code_to_run_repl = \"source \\\"#{$2}\\\"\"
                    end
                  begin
                    original_stdout = $stdout
                    $stdout = StringIO.new
                    resp_repl = eval(code_to_run_repl, bindme' + serialt + ')
                    stdout_repl = $stdout.string
                  ensure
                    $stdout = original_stdout
                  end
                rescue Exception => resp_repl
                  error = true
                end
                if error
                  open(\"' + File.expand_path(config.path) + "/repl/" + @channel_id + "/" + session_name + '.output\", \"a+\") {|f|
                    f.puts \"\`\`\`\n#{resp_repl.to_s.gsub(/^.+' + session_name + '\.rb:\d+:/,\"\")}\`\`\`\"
                  }
                else
                  if code_to_run_repl.match?(/^\s*p\s+/i)
                    resp_repl = stdout_repl unless stdout_repl.to_s == \'\'
                    if stdout_repl.to_s == \'\'
                      resp_repl = resp_repl.inspect
                    else
                      resp_repl = stdout_repl
                    end
                    open(\"' + File.expand_path(config.path) + "/repl/" + @channel_id + "/" + session_name + '.output\", \"a+\") {|f|
                      f.puts \"\`\`\`\n#{resp_repl}\`\`\`\"
                    }
                  else
                    if stdout_repl.to_s == \'\'
                      resp_repl = resp_repl.ai
                    else
                      resp_repl = stdout_repl
                    end
                    open(\"' + File.expand_path(config.path) + "/repl/" + @channel_id + "/" + session_name + '.output\", \"a+\") {|f|
                      if as_it_is
                        f.puts resp_repl
                      else
                        f.puts \"\`\`\`\n#{resp_repl}\`\`\`\"
                      end
                    }
                  end
                  unless !add_to_run_repl
                    open(\"' + File.expand_path(config.path) + "/repl/" + @channel_id + "/" + session_name + '.run\", \"a+\") {|f|
                      f.puts code_to_run_repl
                    }
                  end
                end
              end
            end
          end
      '
  process_to_run.gsub!('\"', '"')
  return process_to_run
end

#respond(msg = "", dest = nil, unfurl_links: true, unfurl_media: true, thread_ts: "", web_client: true, blocks: [], dont_share: false, return_message: false, max_chars_per_message: 4000, split: true) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/slack/smart-bot/comm/respond.rb', line 2

def respond(msg = "", dest = nil, unfurl_links: true, unfurl_media: true, thread_ts: "", web_client: true, blocks: [], dont_share: false, return_message: false, max_chars_per_message: 4000, split: true)
  result = true
  resp = nil

  if Thread.current.key?(:prompt) and !Thread.current[:prompt].empty? #don't send response to slack since chatgpt will get the response from stdout
    Thread.current[:stdout] += "#{msg}\n"
    return true
  else
    if (msg.to_s != "" or !msg.to_s.match?(/^A\s*\z/) or !blocks.empty?) and Thread.current[:routine_type].to_s != "bgroutine"
      if !web_client.is_a?(TrueClass) and !web_client.is_a?(FalseClass)
        (!unfurl_links or !unfurl_media) ? web_client = true : web_client = false
      end
      begin
        msg = msg.to_s
        web_client = true if !blocks.empty?

        on_thread = Thread.current[:on_thread]
        if dest.nil? and Thread.current.key?(:dest)
          dest = Thread.current[:dest]
        elsif dest.is_a?(Symbol) and dest == :on_thread
          on_thread = true
          dest = Thread.current[:dest]
        elsif dest.is_a?(Symbol) and dest == :direct
          dest = Thread.current[:user].id
        end
        if thread_ts.to_s.match?(/^\d+\.\d+$/)
          on_thread = true
          #thread id
        elsif thread_ts.to_s.match?(/^p\d\d\d\d\d+$/)
          on_thread = true
          #a thread id taken from url fex: p1622549264010700
          thread_ts = thread_ts.scan(/(\d+)/).join
          thread_ts = "#{thread_ts[0..9]}.#{thread_ts[10..-1]}"
        else
          thread_ts = Thread.current[:thread_ts] if thread_ts == ""
        end

        dest = @channels_id[dest] if @channels_id.key?(dest) #it is a name of channel

        on_thread ? txt_on_thread = ":on_thread:" : txt_on_thread = ""

        if blocks.empty?
          if !config.simulate #https://api.slack.com/docs/rate-limits
            msg.size > 500 ? wait = 0.5 : wait = 0.1
            sleep wait if Time.now <= (@last_respond + wait)
          else
            wait = 0
          end
          msgs = [] # max of max_chars_per_message characters per message
          if max_chars_per_message.nil?
            txt = msg
          else
            txt = ""
            in_a_code_block = false
            print_previous = false
            all_lines = msg.split("\n")

            all_lines.each_with_index do |m, i|
              if m.match?(/^\s*```/)
                in_a_code_block = !in_a_code_block
                num_chars_code = 0
                print_previous = false
                if in_a_code_block
                  all_lines[i + 1..-1].each do |l|
                    num_chars_code += l.size + 1 # +1 for the \n
                    break if l.match?(/^\s*```/)
                  end
                  if (num_chars_code + (m + txt).size > max_chars_per_message) and txt != ""
                    print_previous = true
                  end
                end
              end
              if ((m + txt).size > max_chars_per_message and !in_a_code_block) or print_previous
                unless txt == ""
                  txt[0] = "." if txt.match?(/\A\s\s\s/) #first line of message in slack don't show spaces at the begining so we force it by changing first char
                  if m.match?(/^\s*```\s*$/) and !in_a_code_block
                    txt += (m + "\n")
                    m = ""
                  end
                  if split
                    t = txt.chars.each_slice(max_chars_per_message).map(&:join) #jalsplit
                    msgs << t
                  else
                    msgs << txt #not necessary to split the message in smaller parts since we are sending it as a file now
                  end
                  txt = ""
                  print_previous = false if print_previous
                end
              end
              txt += (m + "\n")
              txt[0] = "." if txt.match?(/\A\s\s\s/) #first line of message in slack don't show spaces at the begining so we force it by changing first char
              txt[0] = ".   " if txt.match?(/\A\t/)
            end
          end
          msgs << txt
          msgs.flatten!
          msgs.delete_if { |e| e.match?(/\A\s*\z/) }
          if dest.nil?
            if config[:simulate]
              open("#{config.path}/buffer_complete.log", "a") { |f|
                f.puts "|#{@channel_id}|#{thread_ts}|#{config[:nick_id]}|#{config[:nick]}|#{txt_on_thread}#{msg}~~~"
              }
            else
              if on_thread
                msgs.each do |msg|
                  if msg.size > max_chars_per_message
                    resp = send_file(@channel_id, "", "", "", "", "text", content: msg)
                  else
                    if web_client
                      resp = client.web_client.chat_postMessage(channel: @channel_id, text: msg, as_user: true, unfurl_links: unfurl_links, unfurl_media: unfurl_media, thread_ts: thread_ts)
                    else
                      resp = client.message(channel: @channel_id, text: msg, as_user: true, thread_ts: thread_ts, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
                    end
                  end
                  sleep wait
                end
              else
                msgs.each do |msg|
                  if msg.size > max_chars_per_message
                    resp = send_file(@channel_id, "", "", "", "", "text", content: msg)
                  else
                    if web_client
                      resp = client.web_client.chat_postMessage(channel: @channel_id, text: msg, as_user: true, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
                    else
                      resp = client.message(channel: @channel_id, text: msg, as_user: true, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
                    end
                  end
                  sleep wait
                end
              end
            end
            if config[:testing] and config.on_master_bot and !@buffered
              @buffered = true
              open("#{config.path}/buffer.log", "a") { |f|
                f.puts "|#{@channel_id}|#{thread_ts}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
              }
            end
          elsif dest[0] == "C" or dest[0] == "G" # channel
            if config[:simulate]
              open("#{config.path}/buffer_complete.log", "a") { |f|
                f.puts "|#{dest}|#{thread_ts}|#{config[:nick_id]}|#{config[:nick]}|#{txt_on_thread}#{msg}~~~"
              }
            else
              if on_thread
                msgs.each do |msg|
                  if msg.size > max_chars_per_message
                    resp = send_file(dest, "", "", "", "", "text", content: msg)
                  else
                    if web_client
                      resp = client.web_client.chat_postMessage(channel: dest, text: msg, as_user: true, unfurl_links: unfurl_links, unfurl_media: unfurl_media, thread_ts: thread_ts)
                    else
                      resp = client.message(channel: dest, text: msg, as_user: true, thread_ts: thread_ts, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
                    end
                  end
                  sleep wait
                end
              else
                msgs.each do |msg|
                  if msg.size > max_chars_per_message
                    resp = send_file(dest, "", "", "", "", "text", content: msg)
                  else
                    if web_client
                      resp = client.web_client.chat_postMessage(channel: dest, text: msg, as_user: true, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
                    else
                      resp = client.message(channel: dest, text: msg, as_user: true, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
                    end
                  end
                  sleep wait
                end
              end
            end
            if config[:testing] and config.on_master_bot and !@buffered
              @buffered = true
              open("#{config.path}/buffer.log", "a") { |f|
                f.puts "|#{dest}|#{thread_ts}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
              }
            end
          elsif dest[0] == "D" or dest[0] == "U" or dest[0] == "W" # Direct message
            msgs.each do |msg|
              if msg.size > max_chars_per_message
                resp = send_file(dest, "", "", "", "", "text", content: msg)
              else
                resp = send_msg_user(dest, msg, on_thread, unfurl_links: unfurl_links, unfurl_media: unfurl_media, thread_ts: thread_ts)
              end
              sleep wait
            end
          elsif dest[0] == "@"
            begin
               = @users.select { |u| u.id == dest[1..-1] or u.name == dest[1..-1] or (u.key?(:enterprise_user) and u.enterprise_user.id == dest[1..-1]) }[-1]
              msgs.each do |msg|
                if msg.size > max_chars_per_message
                  resp = send_file(.id, "", "", "", "", "text", content: msg)
                else
                  resp = send_msg_user(.id, msg, on_thread, unfurl_links: unfurl_links, unfurl_media: unfurl_media, thread_ts: thread_ts)
                end
                sleep wait
              end
            rescue Exception => stack
              @logger.warn("user #{dest} not found.")
              @logger.warn stack
              if Thread.current.key?(:dest)
                respond("User #{dest} not found.")
              end
              result = false
            end
          else
            @logger.warn("method respond not treated correctly: msg:#{msg} dest:#{dest}")
            result = false
          end
        else
          wait = 0.1
          if dest.nil?
            if config[:simulate]
              open("#{config.path}/buffer_complete.log", "a") { |f|
                f.puts "|#{@channel_id}|#{thread_ts}|#{config[:nick_id]}|#{config[:nick]}|#{txt_on_thread}#{blocks.join}~~~"
              }
            else
              if on_thread
                blocks.each_slice(40).to_a.each do |blockstmp|
                  resp = client.web_client.chat_postMessage(channel: @channel_id, blocks: blockstmp, as_user: true, thread_ts: thread_ts)
                  sleep wait
                end
              else
                blocks.each_slice(40).to_a.each do |blockstmp|
                  resp = client.web_client.chat_postMessage(channel: @channel_id, blocks: blockstmp, as_user: true)
                  sleep wait
                end
              end
            end
            if config[:testing] and config.on_master_bot and !@buffered
              @buffered = true
              open("#{config.path}/buffer.log", "a") { |f|
                f.puts "|#{@channel_id}|#{thread_ts}|#{config[:nick_id]}|#{config[:nick]}|#{blocks.join}"
              }
            end
          elsif dest[0] == "C" or dest[0] == "G" # channel
            if config[:simulate]
              open("#{config.path}/buffer_complete.log", "a") { |f|
                f.puts "|#{dest}|#{thread_ts}|#{config[:nick_id]}|#{config[:nick]}|#{txt_on_thread}#{blocks.join}~~~"
              }
            else
              if on_thread
                blocks.each_slice(40).to_a.each do |blockstmp|
                  resp = client.web_client.chat_postMessage(channel: dest, blocks: blockstmp, as_user: true, thread_ts: thread_ts)
                  sleep wait
                end
              else
                blocks.each_slice(40).to_a.each do |blockstmp|
                  resp = client.web_client.chat_postMessage(channel: dest, blocks: blockstmp, as_user: true)
                  sleep wait
                end
              end
            end
            if config[:testing] and config.on_master_bot and !@buffered
              @buffered = true
              open("#{config.path}/buffer.log", "a") { |f|
                f.puts "|#{dest}|#{thread_ts}|#{config[:nick_id]}|#{config[:nick]}|#{blocks.join}"
              }
            end
          elsif dest[0] == "D" or dest[0] == "U" or dest[0] == "W" # Direct message
            blocks.each_slice(40).to_a.each do |blockstmp|
              resp = send_msg_user(dest, msg, on_thread, unfurl_links: unfurl_links, unfurl_media: unfurl_media, blocks: blockstmp, thread_ts: thread_ts)
              sleep wait
            end
          elsif dest[0] == "@"
            begin
               = @users.select { |u| u.id == dest[1..-1] or (u.key?(:enterprise_user) and u.enterprise_user.id == dest[1..-1]) }[-1]
              blocks.each_slice(40).to_a.each do |blockstmp|
                resp = send_msg_user(.id, msg, on_thread, unfurl_links: unfurl_links, unfurl_media: unfurl_media, blocks: blockstmp, thread_ts: thread_ts)
                sleep wait
              end
            rescue Exception => stack
              @logger.warn("user #{dest} not found.")
              @logger.warn stack
              if Thread.current.key?(:dest)
                respond("User #{dest} not found.")
              end
              result = false
            end
          else
            @logger.warn("method respond not treated correctly: msg:#{msg} dest:#{dest}")
            result = false
          end
        end
        @last_respond = Time.now
      rescue Exception => stack
        @logger.warn stack
        result = false
      end
    end
    if Thread.current.key?(:routine) and Thread.current[:routine]
      File.write("#{config.path}/routines/#{@channel_id}/#{Thread.current[:routine_name]}_output.txt", msg, mode: "a+")
    end
    result = resp if return_message
    return result
  end
end

#respond_direct(msg, unfurl_links: true, unfurl_media: true) ⇒ Object



2
3
4
# File 'lib/slack/smart-bot/comm/respond_direct.rb', line 2

def respond_direct(msg, unfurl_links: true, unfurl_media: true)
  respond(msg, :direct, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
end

#respond_thread(msg = '', unfurl_links: true, unfurl_media: true, blocks: []) ⇒ Object



2
3
4
# File 'lib/slack/smart-bot/comm/respond_thread.rb', line 2

def respond_thread(msg='', unfurl_links: true, unfurl_media: true, blocks: [])
  respond(msg, :on_thread, unfurl_links: unfurl_links, unfurl_media: unfurl_media, blocks: blocks)
end

#ruby_code(dest, user, code, rules_file) ⇒ Object

help: ---------------------------------------------- help: ruby RUBY_CODE help: code RUBY_CODE help: runs the code supplied and returns the output. Also you can send a Ruby file instead. Examples: help: code puts (34344/99)*(34+14) help: ruby require 'json'; res=[]; 20.times res<<rand(100); my_json=res; puts my_json.to_json help: help: command_id: :ruby_code help:



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/slack/smart-bot/commands/on_bot/ruby_code.rb', line 12

def ruby_code(dest, user, code, rules_file)
  save_stats(__method__)
  if has_access?(__method__, user)
    unless code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File.") or
          code.include?("`") or code.include?("exec") or code.include?("spawn") or code.include?("IO.") or
          code.match?(/open3/i) or code.match?(/bundle/i) or code.match?(/gemfile/i) or code.include?("%x") or
          code.include?("ENV") or code.match?(/=\s*IO/) or code.include?("Dir.") or code.match?(/=\s*IO/) or
          code.match?(/=\s*File/) or code.match?(/=\s*Dir/) or code.match?(/<\s*File/) or code.match?(/<\s*Dir/) or
          code.match?(/\w+:\s*File/) or code.match?(/\w+:\s*Dir/)
      react :running
      unless rules_file.empty?
        begin
          eval(File.new(config.path+rules_file).read) if File.exist?(config.path+rules_file)
        end
      end

      respond "Running", dest if code.size > 200

      begin
        #todo: check. Commented next line because it was causing problems with the code, for the case the line was for example any of these chars: { } [ ] ( ) < > | & ; $ ` " ' \ * ? ~ # = ! ^ -
        #code.gsub!(/^\W*$/, "") #to remove special chars from slack when copy/pasting
        code.gsub!('$','\$') #to take $ as literal, fex: puts '$lolo' => puts '\$lolo'
        ruby = "ruby -e \"#{code.gsub('"', '\"')}\""
        if defined?(project_folder) and project_folder.to_s != "" and Dir.exist?(project_folder)
          ruby = ("cd #{project_folder} &&" + ruby)
        else
          def project_folder() "" end
        end

        stdin, stdout, stderr, wait_thr = Open3.popen3(ruby)
        timeout = timeoutt = 20
        procstart = Time.now
        while (wait_thr.status == 'run' or wait_thr.status == 'sleep') and timeout > 0
          timeout -= 0.1
          sleep 0.1
        end
        if timeout > 0
          stdout = stdout.read
          stderr = stderr.read
          if stderr == ""
            if stdout == ""
              respond "Nothing returned. Remember you need to use p or puts to print", dest
            else
              respond stdout, dest
            end
          else
            respond "#{stderr}\n#{stdout}", dest
          end
        else
          respond "The process didn't finish in #{timeoutt} secs so it was aborted. Timeout!"
          pids = `pgrep -P #{wait_thr.pid}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
          pids.each do |pid|
            begin
              Process.kill("KILL", pid)
            rescue
            end
          end
        end
      rescue Exception => exc
        respond exc, dest
      end
      unreact :running
    else
      respond "Sorry I cannot run this due security reasons", dest
    end
  end
end

#run_repl(dest, user, session_name, env_vars, prerun, rules_file) ⇒ Object

help: ---------------------------------------------- help: run repl SESSION_NAME help: run repl SESSION_NAME ENV_VAR=VALUE ENV_VAR=VALUE help: run repl SESSION_NAME PARAMS help: run live SESSION_NAME help: run irb SESSION_NAME help: Will run the repl session specified and return the output. help: You can supply the Environmental Variables you need for the Session help: PARAMS: Also it is possible to supply code that will be run before the repl code on the same session. help: It will return only the values that were print out on the repl with puts, print, p or pp help: Example: help: run repl CreateCustomer LOCATION=spain HOST='https://10.30.40.50:8887' help: help: command_id: :run_repl help:



17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/slack/smart-bot/commands/on_bot/run_repl.rb', line 17

def run_repl(dest, user, session_name, env_vars, prerun, rules_file)
  #todo: add tests
  from = user.name
  if has_access?(__method__, user)
    save_stats(__method__)
    Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
    Dir.mkdir("#{config.path}/repl/#{@channel_id}") unless Dir.exist?("#{config.path}/repl/#{@channel_id}")
    code = prerun.join("\n")
    if File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.run")
      if @repls.key?(session_name) and (@repls[session_name][:type] == :private or @repls[session_name][:type] == :private_clean) and
         (@repls[session_name][:creator_name] != user.name or @repls[session_name][:creator_team_id] != user.team_id) and
         !is_admin?(user)
        respond "The REPL with session name: #{session_name} is private", dest
      elsif !prerun.empty? and (code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File.") or
                                code.include?("`") or code.include?("exec") or code.include?("spawn") or code.include?("IO.") or
                                code.match?(/open3/i) or code.match?(/bundle/i) or code.match?(/gemfile/i) or code.include?("%x") or
                                code.include?("ENV") or code.match?(/=\s*IO/) or code.include?("Dir.") or
                                code.match?(/=\s*File/) or code.match?(/=\s*Dir/) or code.match?(/<\s*File/) or code.match?(/<\s*Dir/) or
                                code.match?(/\w+:\s*File/) or code.match?(/\w+:\s*Dir/) or
                                code.match?(/=?\s*(require|load)(\(|\s)/i))
        respond "Sorry I cannot run this due security reasons", dest
      else
        if @repls.key?(session_name) #not temp
          @repls[session_name][:accessed] = Time.now.to_s
          if @repls[session_name].creator_name == user.name and @repls[session_name].creator_team_id == user.team_id
            @repls[session_name][:runs_by_creator] += 1
          else
            @repls[session_name][:runs_by_others] += 1
          end
          update_repls()
        end

        content = env_vars.join("\n")
        content += "\nrequire 'nice_http'\n"
        unless rules_file.empty? # to get the project_folder
          begin
            eval(File.new(config.path + rules_file).read) if File.exist?(config.path + rules_file)
          end
        end
        if File.exist?("#{project_folder}/.smart-bot-repl") and
           ((@repls.key?(session_name) and @repls[session_name][:type] != :private_clean and @repls[session_name][:type] != :public_clean) or !@repls.key?(session_name))
          content += File.read("#{project_folder}/.smart-bot-repl")
          content += "\n"
        end
        unless prerun.empty?
          content += prerun.join("\n")
          content += "\n"
        end
        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
        Dir.mkdir("#{project_folder}/tmp") unless Dir.exist?("#{project_folder}/tmp")
        Dir.mkdir("#{project_folder}/tmp/repl") unless Dir.exist?("#{project_folder}/tmp/repl")
        if Thread.current[:on_thread]
          # to force stdout.each to be performed every 3 seconds
          content = "Thread.new do
            while true do
              puts ''
              sleep 3
            end
          end
          #{content}
          "
        end
        random = "5:LN&".gen
        File.write("#{project_folder}/tmp/repl/#{session_name}_#{user.name}_#{random}.rb", content, mode: "w+")
        process_to_run = "ruby  ./tmp/repl/#{session_name}_#{user.name}_#{random}.rb"
        process_to_run = ("cd #{project_folder} && #{process_to_run}") if defined?(project_folder)
        respond "Running REPL #{session_name} (id: #{random})"
        @run_repls[random] = { team_id: user.team_id, user: user.name, name: session_name, pid: '' }
        react :running

        require "pty"
        timeout = 60 * 60 * 4 # 4 hours

        started = Time.now
        results = []
        begin
          PTY.spawn(process_to_run) do |stdout, stdin, pid|
            last_result = -1
            last_time = Time.now
            @run_repls[random].pid = pid
            begin
              stdout.each do |line|
                if (Time.now - started) > timeout
                  respond "run REPL session finished. Max time reached: #{session_name} (id: #{random})", dest
                  pids = `pgrep -P #{pid}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
                  pids.each do |pd|
                    begin
                      Process.kill("KILL", pd)
                    rescue
                    end
                  end
                  break
                else
                  results << line
                  if Thread.current[:on_thread]
                    if (Time.now - last_time) > 2
                      if (results.size - last_result) < 60 and results[(last_result + 1)..-1].join.size < 3500
                        output = ""
                        results[(last_result + 1)..-1].each do |li|
                          if li.match?(/^\s*{.+}\s*$/) or li.match?(/^\s*\[.+\]\s*$/)
                            output += "```\n#{li}```\n"
                          else
                            output += li
                          end
                        end
                        respond output
                      else
                        send_file(dest, "", "response.rb", "", "text/plain", "ruby", content: results[(last_result + 1)..-1].join)
                      end
                      last_result = results.size - 1
                      last_time = Time.now
                    end
                  end
                end
              end
            rescue Errno::EIO
              @logger.warn "run_repl PTY Errno:EIO error"
            end
            if results.empty?
              respond "*#{session_name}* (id: #{random}): Nothing returned."
            else
              if last_result != (results.size - 1)
                if (results.size - last_result) < 60 and results[(last_result + 1)..-1].join.size < 3500
                  output = ""
                  results[(last_result + 1)..-1].each do |li|
                    if li.match?(/^\s*{.+}\s*$/) or li.match?(/^\s*\[.+\]\s*$/)
                      output += "```\n#{li}```\n"
                    else
                      output += li
                    end
                  end
                  if Thread.current[:on_thread]
                    respond output
                  else
                    respond "*#{session_name}* (id: #{random}):\n#{output}"
                  end
                else
                  send_file(dest, "", "response.rb", "", "text/plain", "ruby", content: results[(last_result + 1)..-1].join)
                end
              end
            end
          end
        rescue PTY::ChildExited
          @logger.warn "run_repl PTY The child process exited!"
        end
        @run_repls.delete(random) if @run_repls.key?(random)
        unreact :running
      end
    else
      respond "The REPL with session name: #{session_name} doesn't exist on this Smart Bot Channel", dest
    end
  end
end

#run_routine(dest, from, name) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: run routine NAME helpadmin: execute routine NAME helpadmin: It will run the specified routine helpadmin: You can use this command only if you are an admin user helpadmin: NAME: one word to identify the routine helpadmin: Examples: helpadmin: run routine example helpadmin: helpadmin: command_id: :run_routine helpadmin:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/slack/smart-bot/commands/on_bot/admin/run_routine.rb', line 15

def run_routine(dest, from, name)
  save_stats(__method__)
  if is_admin?
    if !config.on_master_bot and dest[0] == "D"
      respond "It's only possible to run routines from MASTER channel from a direct message with the bot.", dest
    elsif @routines.key?(@channel_id) and @routines[@channel_id].key?(name)
      File.delete "#{config.path}/routines/#{@channel_id}/#{name}_output.txt" if File.exist?("#{config.path}/routines/#{@channel_id}/#{name}_output.txt")
      if @routines[@channel_id][name][:file_path] != ""
        if @routines[@channel_id][name][:file_path].match?(/\.rb$/i)
          ruby = "ruby "
        else
          ruby = ""
        end
        started = Time.now
        process_to_run = "#{ruby}#{Dir.pwd}#{@routines[@channel_id][name][:file_path][1..-1]}"
        process_to_run = ("cd #{project_folder} &&" + process_to_run) if defined?(project_folder)

        stdout, stderr, status = Open3.capture3(process_to_run)
        if stderr == ""
          unless stdout.match?(/\A\s*\z/)
            respond "routine *`#{name}`*: #{stdout}", @routines[@channel_id][name][:dest]
          end
        else
          respond "routine *`#{name}`*: #{stdout} #{stderr}", @routines[@channel_id][name][:dest]
        end
      else #command
        message = respond "routine *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest], return_message: true
        started = Time.now
        data = { channel: @routines[@channel_id][name][:dest],
          user: @routines[@channel_id][name][:creator_id],
          team_id: @routines[@channel_id][name][:creator_team_id],
          text: @routines[@channel_id][name][:command],
          files: nil,
          routine_name: name, 
          routine_type: @routines[@channel_id][name][:routine_type],
          routine: true }
        if @routines[@channel_id][name][:command].match?(/^!!/) or @routines[@channel_id][name][:command].match?(/^\^/)
          data[:ts] = message.ts
        end  
        treat_message(data)
      end
      @routines[@channel_id][name][:last_elapsed] = (Time.now - started)
      @routines[@channel_id][name][:last_run] = started.to_s
      update_routines()
    else
      respond "There isn't a routine with that name: `#{name}`.\nCall `see routines` to see added routines", dest
    end
  else
    respond "Only admin users can run routines", dest
  end
end

#save_stats(method, data: {}, forced: false) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/slack/smart-bot/utils/save_stats.rb', line 2

def save_stats(method, data: {}, forced: false)
  if Thread.current[:user].nil? and !data[:user].to_s.empty?
    user_stats = data[:user]
  else
    user_stats = Thread.current[:user]
  end
  if has_access?(method, user_stats) or forced
    if config.stats
      begin
        command_ids_not_to_log = [
          "add_vacation", "remove_vacation", "add_memo_team", "set_personal_settings", "open_ai_chat",
          "open_ai_chat_add_authorization", "open_ai_chat_copy_session_from_user",
        ]
        Thread.current[:command_id] = method.to_s
        require "csv"
        if !File.exist?("#{config.stats_path}.#{Time.now.strftime("%Y-%m")}.log")
          CSV.open("#{config.stats_path}.#{Time.now.strftime("%Y-%m")}.log", "wb") do |csv|
            csv << ["date", "bot_channel", "bot_channel_id", "dest_channel", "dest_channel_id", "type_message", "user_name", "user_id", "text", "command", "files", "time_zone", "job_title", "team_id"]
          end
        end
        if data.empty?
          data = {
            dest: Thread.current[:dest],
            typem: Thread.current[:typem],
            user: Thread.current[:user],
            files: Thread.current[:files?],
            command: Thread.current[:command],
            routine: Thread.current[:routine],
          }
        end
        if method.to_s == "ruby_code" and data.files
          command_txt = "ruby"
        else
          command_txt = data.command
        end
        command_txt.gsub!(/```.+```/m, "```CODE```")
        command_txt = "#{command_txt[0..99]}..." if command_txt.size > 100

        if data.routine
          user_name = "routine/#{data.user.name}"
          user_id = "routine/#{data.user.id}"
        else
          user_name = data.user.name
          user_id = data.user.id
        end
         = find_user(data.user.id)
        if .nil? or .is_app_user or .is_bot
          time_zone = ""
          job_title = ""
          team_id = ""
        else
          time_zone = .tz_label
          job_title = .profile.title
          team_id = .team_id
        end
        command_txt = "#{method} encrypted" if command_ids_not_to_log.include?(method.to_s)
        CSV.open("#{config.stats_path}.#{Time.now.strftime("%Y-%m")}.log", "a+") do |csv|
          csv << [Time.now, config.channel, @channel_id, @channels_name[data.dest], data.dest, data.typem, user_name, user_id, command_txt, method, data.files, time_zone, job_title, team_id]
        end
      rescue Exception => exception
        @logger.fatal "There was a problem on the stats"
        @logger.fatal exception
      end
    end
  else
    sleep 0.2
    Thread.exit
  end
end

#save_status(status, status_id, message) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/slack/smart-bot/utils/save_status.rb', line 3

def save_status(status, status_id, message)
  require 'csv'
  Dir.mkdir("#{config.path}/status") unless Dir.exist?("#{config.path}/status")

  CSV.open("#{config.path}/status/#{config.channel}_status.csv", "a+") do |csv|
    csv << [Time.now.strftime("%Y/%m/%d"), Time.now.strftime("%H:%M:%S"), status, status_id, message]
  end
  if defined?(@channels_list) #wait until the 'client' started
    channel_info = @channels_list.select { |c| c.id == @channel_id}[-1]
    if channel_info.nil? or channel_info.is_private
      channel_link = "##{config.channel}"
    else
      channel_link = "<##{@channel_id}|#{config.channel}>"
    end
  else
    channel_link = "##{config.channel}"
  end

  if status_id == :disconnected
    Thread.new do
      sleep 50
      @logger.info "check disconnection 50 scs later #{@last_notified_status_id}"
      unless @last_notified_status_id == :connected
        respond ":red_circle: The *SmartBot* on *#{channel_link}* is down. An admin will take a look. <@#{config.admins.join(">, <@")}>", config.status_channel
      end
    end
  end
  if @channels_id.is_a?(Hash) and @channels_id.keys.include?(config.status_channel)
    is_back = false
    m = ''
    if (Time.now-@last_status_change) > 20 or !defined?(@last_notified_status_id)
      if status_id == :connected
        if defined?(@last_notified_status_id)
          m = ":exclamation: :large_green_circle: The *SmartBot* on *#{channel_link}* was not available for #{(Time.now-@last_status_change).round(0)} secs. *Now it is up and running again.*" 
        else
          m = ":large_green_circle: The *SmartBot* on *#{channel_link}* is up and running again." 
        end
      end
    end
    if status_id == :paused
      m = ":red_circle: #{message} *#{channel_link}*"
    elsif status_id == :started
      m = ":large_green_circle: #{message} *#{channel_link}*"            
    elsif status_id == :killed or status_id == :exited
      m = ":red_circle: #{message}"
    elsif config.on_master_bot and status_id == :maintenance_on
      if message.to_s == "Sorry I'm on maintenance so I cannot attend your request."
        m = ":red_circle: The *SmartBot* is on maintenance so not possible to attend any request."
      else
        m = ":red_circle: #{message}"
      end
    elsif config.on_master_bot and status_id == :maintenance_off
      m = ":large_green_circle: The *SmartBot* is up and running again."
    end
    @last_status_change = Time.now
    @last_notified_status_id = status_id
    unless m == ''
      respond eval("\"" + m + "\""), config.status_channel
    end
  end


end

#see_access(command_id) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/slack/smart-bot/commands/general/see_access.rb', line 2

def see_access(command_id)
  save_stats(__method__)
  if Thread.current[:typem] == :on_call
    channel = Thread.current[:dchannel]
  elsif Thread.current[:using_channel].to_s == ""
    channel = Thread.current[:dest]
  else
    channel = Thread.current[:using_channel]
  end
  command_ids = get_command_ids()
  if command_ids.values.flatten.include?(command_id)
    if @access_channels.key?(channel) and @access_channels[channel].key?(command_id) and @access_channels[channel][command_id].size > 0
      names = @access_channels[channel][command_id].map { |a| a.split('_')[1..-1].join('_') }
      respond "Only these users have access to `#{command_id}` in this channel: <@#{names.join(">, <@")}>"
    elsif @access_channels.key?(channel) and @access_channels[channel].key?(command_id) and @access_channels[channel][command_id].empty?
      respond "`#{command_id}` is not possible to be used in this channel. Please contact an admin if you want to use it."
    else
      respond "`#{command_id}` seems to be available in this channel."
    end
  else
    respond "It seems like #{command_id} is not valid. Please be sure that exists by calling `see command ids`"
  end
end

#see_adminsObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/slack/smart-bot/commands/general/see_admins.rb', line 2

def see_admins()
  save_stats(__method__)
  if Thread.current[:typem] == :on_call
    channel = Thread.current[:dchannel]
  elsif Thread.current[:using_channel].to_s==''
    channel = Thread.current[:dest]
  else
    channel = Thread.current[:using_channel]
  end

  messages = []
  admins = []
  team_id_admins = []
  channels = get_channels()
  channel_found = channels.detect { |c| c.id == channel }
  if !channel_found.nil? and channel_found.creator.to_s != ''
    messages << "*Channel creator*: <@#{channel_found.creator}>"
    creator_info = find_user(channel_found.creator)
  else
    creator_info = {name: [], team_id: []}
  end
  messages << "*Master admins*: <@#{config.masters.join('>, <@')}>"
  if Thread.current[:typem] == :on_bot or Thread.current[:typem] == :on_master
    admins = config.admins.dup
    team_id_admins = config.team_id_admins.dup
  end
  if @admins_channels.key?(channel) and @admins_channels[channel].size > 0
    team_id_admins = (@admins_channels[channel] + team_id_admins).uniq
    admins = (@admins_channels[channel].map { |a| a.split('_')[1..-1].join('_') } + admins).uniq
  end
  admins = admins - config.masters - [creator_info.name]
  team_id_admins = team_id_admins - config.team_id_masters - ["#{creator_info.team_id}_#{creator_info.name}"]
  messages << "*Admins*: <@#{admins.join('>, <@')}>" unless admins.empty?
  respond messages.join("\n")
end

#see_announcements(user, type, channel, mention = false, publish = false) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/slack/smart-bot/commands/general/see_announcements.rb', line 3

def see_announcements(user, type, channel, mention=false, publish=false)
  save_stats(__method__)
  typem = Thread.current[:typem]
  general_message = ""
  if channel == ''
    if typem == :on_call
      channel = Thread.current[:dchannel]
    else
      channel = Thread.current[:dest]
    end
  end
  if publish
    dest = channel
  else
    dest = Thread.current[:dest]
  end

  if type == 'all'
    if config.team_id_masters.include?("#{user.team_id}_#{user.name}") and typem==:on_dm
      channels = Dir.entries("#{config.path}/announcements/")
      channels.select! {|i| i[/\.csv$/]}
    else
      channels = []
      respond "Only master admins on a DM with the SmarBot can call this command.", dest
    end
  elsif typem == :on_dm and channel == Thread.current[:dest]
    channels = [channel, @channel_id]
  else
    channels = [channel]
  end
  channels.each do |channel|
    channel.gsub!('.csv','')
    if channel[0]== 'D'
      channel_id = channel
    else
      get_channels_name_and_id() unless @channels_name.keys.include?(channel) or @channels_id.keys.include?(channel)
      channel_id = nil
      if @channels_name.key?(channel) #it is an id
        channel_id = channel
        channel = @channels_name[channel_id]
      elsif @channels_id.key?(channel) #it is a channel name
        channel_id = @channels_id[channel]
      end
    end
    if has_access?(__method__, user)
      if (channel_id!=Thread.current[:dest] and config.team_id_masters.include?("#{user.team_id}_#{user.name}") and typem==:on_dm) or publish
        see_announcements_on_demand = true
      else
        see_announcements_on_demand = false
      end
      if channel_id == Thread.current[:dest] or see_announcements_on_demand or publish #master admin user or publish_announcements
        if File.exist?("#{config.path}/announcements/#{channel_id}.csv") and (!@announcements.key?(channel_id) or see_announcements_on_demand) # to force to have the last version that maybe was updated by other SmartBot in case of demand
          t = CSV.table("#{config.path}/announcements/#{channel_id}.csv", headers: ['message_id', 'user_team_id_deleted', 'user_deleted', 'user_team_id_created', 'user_created', 'date', 'time', 'type', 'message'])
          @announcements[channel_id] = t
        end
        if @announcements.key?(channel_id)
          message = []
          @announcements[channel_id].each do |m|
            if m[:user_deleted] == '' and (type == 'all' or type == '' or type==m[:type])
              if m[:type].match?(/:[\w\-]+:/)
                emoji = m[:type]
              elsif m[:type] == 'white'
                emoji = ':white_square:'
              else
                emoji = ":large_#{m[:type]}_square:"
              end
              if mention
                user_created = "<@#{m[:user_created]}>"
              else
                user_created = m[:user_created]
                 = find_user(user_created)
                user_created = .profile.display_name unless .nil?
              end
              if type == 'all' and channel_id[0]=='D'
                message << "\t#{emoji} *private* _(id:#{m[:message_id]} - #{m[:date]} #{m[:time]})_"
              else
                message << "\t#{emoji} #{m[:message]} _(id:#{m[:message_id]} - #{m[:date]} #{m[:time]} #{user_created})_"
              end
            end
          end
          if message.size > 0
            if channel_id[0]=='D'
              if type == 'all'
                message.unshift("*Private messages stored on DM with the SmartBot and <@#{@announcements[channel_id][:user_created][0]}>*")
              else
                message.unshift("*Private messages stored on your DM with the SmartBot*")
              end
            else
              message.unshift("*Announcements for channel <##{channel_id}>*")
            end
            message << general_message unless general_message.empty?
            respond message.join("\n"), dest, unfurl_links: false, unfurl_media: false
          else
            if typem == :on_dm and channel_id[0]=='D'
              respond("There are no #{type} announcements#{general_message}", dest) unless type == 'all'
            else
              respond("There are no #{type} announcements for <##{channel_id}>#{general_message}", dest) unless publish or type == 'all' or (typem==:on_dm and channel_id[0]!='D' and !see_announcements_on_demand)
            end
          end
        else
          if typem == :on_dm and !channel_id.nil? and channel_id[0]=='D'
            respond("There are no announcements#{general_message}", dest) unless type == 'all'
          else
            respond("There are no announcements for <##{channel_id}>#{general_message}", dest) unless publish or type == 'all' or (typem==:on_dm and channel_id[0]!='D' and !see_announcements_on_demand)
          end
        end
      else
        respond "Go to <##{channel_id}> and call the command from there.", dest
      end
    end
  end
end

#see_command_idsObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# File 'lib/slack/smart-bot/commands/general/see_command_ids.rb', line 2

def see_command_ids()
  save_stats(__method__)
  commands = get_command_ids()

  respond "*General Commands*: #{(commands[:general]+commands[:general_commands]).sort.join(' / ')}" unless commands[:general].empty?

  respond "*On Bot general*: #{commands[:on_bot_general].sort.join(' / ')}" unless commands[:on_bot_general].empty?

  respond "*On Bot on demand*: #{commands[:on_bot_on_demand].sort.join(' / ')}" unless commands[:on_bot_on_demand].empty?

  respond "*On Bot admin*: #{commands[:on_bot_admin].sort.join(' / ')}" unless commands[:on_bot_admin].empty?
  
  respond "*On Bot master admin*: #{commands[:on_bot_master_admin].sort.join(' / ')}" unless commands[:on_bot_master_admin].empty?

  respond "*On extended*: #{commands[:on_extended].sort.join(' / ')}" unless commands[:on_extended].empty?

  respond "*On Master*: #{commands[:on_master].sort.join(' / ')}" unless commands[:on_master].empty?

  respond "*On Master admin*: #{commands[:on_master_admin].sort.join(' / ')}" unless commands[:on_master_admin].empty?
  
  respond "*On Master master admin*: #{commands[:on_master_master_admin].sort.join(' / ')}" unless commands[:on_master_master_admin].empty?

  respond "*General Rules*: #{commands[:general_rules].sort.join(' / ')}" unless commands[:general_rules].empty?

  respond "*Rules*: #{commands[:rules].sort.join(' / ')}" unless commands[:rules].empty?

end

#see_favorite_commands(user, only_mine) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/slack/smart-bot/commands/general/see_favorite_commands.rb', line 2

def see_favorite_commands(user, only_mine)
  save_stats(__method__)
  if config.stats
    if Thread.current[:typem] == :on_call
      channel = Thread.current[:dchannel]
    elsif Thread.current[:using_channel].to_s==''
      channel = Thread.current[:dest]
    else
      channel = Thread.current[:using_channel]
    end

    files = Dir["#{config.stats_path}.*.log"].sort.reverse[0..1]
    if files.empty?
      respond "There is no data stored."
    else
      count_commands = {}

      files.each do |file|
        CSV.foreach(file, headers: true, header_converters: :symbol, converters: :numeric) do |row|
          row[:dest_channel_id] = row[:bot_channel_id] if row[:dest_channel_id].to_s[0] == "D"
          if ((only_mine and row[:user_name]==user.name) or (!only_mine and !config.team_id_masters.include?("#{row[:team_id]}_#{row[:user_name]}"))) and
            row[:dest_channel_id] == channel and !row[:user_name].include?('routine/') and
            row[:command] != 'dont_understand'
            row[:command] = 'bot_help' if row[:command] == 'bot_rules'
            count_commands[row[:command]] ||= 0
            count_commands[row[:command]] += 1
          end
        end
      end
      commands = []
      count_commands.sort_by {|k,v| -v}.each do |command, num|
        commands << command
      end
      if commands.empty?
        respond "There is no data stored."
      else
        output = ""
        i = 0
        commands.each do |command|
          unless output.match?(/^\s*command_id:\s+:#{command}\s*$/)
            i+=1
            output += bot_help(user, user.name, Thread.current[:dest], channel, false, command.gsub('_',' '), config.rules_file, savestats: false, strict: true)
            break if i>=5
          end
        end
      end
    end
  else
    respond "Ask an admin to set stats to true to generate the stats when running the bot instance so you can get this command to work."
  end
end

#see_repls(dest, user, typem) ⇒ Object

help: ---------------------------------------------- help: see repls help: see irbs help: It will display the repls help: help: command_id: :see_repls help:



9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/slack/smart-bot/commands/on_bot/see_repls.rb', line 9

def see_repls(dest, user, typem)
  #todo: add tests
  save_stats(__method__)
  if has_access?(__method__, user)
    message = ""
    @repls.sort.to_h.each do |session_name, repl|
      if ((repl.creator_name == user.name and repl.creator_team_id == user.team_id) or repl.type == :public or repl.type == :public_clean) or (is_admin?(user) and typem == :on_dm)
        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"
      end
    end
    message = "No repls created" if message == ''
    respond message
  end
end

#see_result_routine(dest, from, name) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: see routine result NAME helpadmin: see result routine NAME helpadmin: result routine NAME helpadmin: It will display the last result of the routine run. helpadmin: You can use this command only if you are an admin user helpadmin: NAME: one word to identify the routine helpadmin: Examples: helpadmin: see routine result example helpadmin: helpadmin: command_id: :see_result_routine helpadmin:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/slack/smart-bot/commands/on_bot/admin/see_result_routine.rb', line 15

def see_result_routine(dest, from, name)
  save_stats(__method__)
  if is_admin?
    if @routines.key?(@channel_id) and @routines[@channel_id].key?(name)
      if File.exist?("#{config.path}/routines/#{@channel_id}/#{name}_output.txt")
        msg = "*Results from routine run #{File.mtime("#{config.path}/routines/#{@channel_id}/#{name}_output.txt")}*\n"
        msg += File.read("#{config.path}/routines/#{@channel_id}/#{name}_output.txt")
        respond msg, dest
      else
        respond "The routine *`#{name}`* doesn't have any result yet.", dest
      end
    else
      respond "There isn't a routine with that name: *`#{name}`*.\nCall `see routines` to see added routines", dest
    end
  else
    respond "Only admin users can see the routines results", dest
  end
end

#see_routines(dest, from, user, all, header, regexp) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: see routines helpadmin: see routines HEADER /REGEXP/ helpadmin: see all routines helpadmin: see all routines HEADER /REGEXP/ helpadmin: It will show the routines of the channel helpadmin: In case of 'all' and on the master channel, it will show all the routines from all channels helpadmin: If you use HEADER it will show only the routines that match the REGEXP on the header. Available headers: name, creator, status, next_run, last_run, command helpadmin: You can use this command only if you are an admin user helpadmin: helpadmin: command_id: :see_routines helpadmin:



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/slack/smart-bot/commands/on_bot/admin/see_routines.rb', line 14

def see_routines(dest, from, user, all, header, regexp)
  save_stats(__method__)
  if is_admin?
    react :running
    if all
      routines = {}
      if config.on_master_bot
        Dir["#{config.path}/routines/routines_*.yaml"].each do |rout|
          routine = YAML.load(File.read(rout))
          unless routine.is_a?(FalseClass)
            routines.merge!(routine)
          end
        end
      else
        respond "To see all routines on all channels you need to run the command on the master channel.\nI'll display only the routines on this channel.", dest
        routines = @routines
      end
    else
      if @rules_imported.key?("#{user.team_id}_#{user.name}") and @rules_imported["#{user.team_id}_#{user.name}"].key?(user.name) and dest[0] == "D"
        routines = YAML.load(File.read("#{config.path}/routines/routines_#{@rules_imported["#{user.team_id}_#{user.name}"][user.name]}.yaml"))
        routines = {} if routines.is_a?(FalseClass)
      else
        routines = @routines
      end
    end

    if header != ''
      routines_filtered = {}

      routines.each do |ch, rout_ch|
        routines_filtered[ch] = rout_ch.dup
        rout_ch.each do |k, v|
          if header == 'name'
            if k.match(/#{regexp}/i).nil?
              routines_filtered[ch].delete(k)
            end
          elsif v[header.to_sym].to_s.match(/#{regexp}/i).nil?
            routines_filtered[ch].delete(k)
          end
        end
      end
      routines = routines_filtered
    end

    if routines.get_values(:channel_name).size == 0
      if header != ''
        respond "There are no routines added that match the header *#{header}* and the regexp *#{regexp}*.", dest
      else
        respond "There are no routines added.", dest
      end
    else
      routines.each do |ch, rout_ch|
        if header != ''
          respond "Routines on channel *#{rout_ch.get_values(:channel_name).values.flatten.uniq[0]}* that match the header *#{header}* and the regexp *#{regexp}*", dest
        else
          respond "Routines on channel *#{rout_ch.get_values(:channel_name).values.flatten.uniq[0]}*", dest
        end
        rout_ch.each do |k, v|
          msg = []
          if v[:dest][0] == 'D'
            extram = " (*DM to #{v[:creator]}*)"
          elsif v[:dest] != ch
            extram = " (*publish on <##{v[:dest]}>*)"
          else
            extram = ''
          end
          msg << "*`#{k}`*#{extram}"
          msg << "\tCreator: #{v[:creator]}"
          msg << "\tStatus: #{v[:status]}"
          msg << "\tEvery: #{v[:every]}" unless v[:every] == ""
          msg << "\tAt: #{v[:at]}" unless v[:at] == ""
          msg << "\tOn: #{v[:dayweek]}" unless !v.key?(:dayweek) or v[:dayweek].to_s == ""
          msg << "\tNext Run: #{v[:next_run]}"
          msg << "\tLast Run: #{v[:last_run]}"
          msg << "\tTime consumed on last run: #{v[:last_elapsed]}" unless v[:command] !=''
          msg << "\tCommand: #{v[:command]}" unless v[:command].to_s.strip == ''
          msg << "\tFile: #{v[:file_path]}" unless v[:file_path] == ''
          msg << "\tSilent: #{v[:silent]}" unless !v[:silent]
          msg << "\tType: #{v[:routine_type]}" if v[:routine_type].to_s == 'bgroutine'
          respond msg.join("\n"), dest
        end
      end
    end
    unreact :running
  else
    respond "Only admin users can use this command", dest
  end
end

#see_sharesObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/slack/smart-bot/commands/general/see_shares.rb', line 3

def see_shares()
  save_stats(__method__)
  typem = Thread.current[:typem]
  dest = Thread.current[:dest]
  if typem == :on_call
    channel = Thread.current[:dchannel]
  else
    channel = Thread.current[:dest]
  end

  general_message = "\nRelated commands `share messages /RegExp/ on #CHANNEL`, `share messages \"TEXT\" on #CHANNEL`, `delete share ID`"
  if File.exist?("#{config.path}/shares/#{@channels_name[channel]}.csv")
    t = CSV.table("#{config.path}/shares/#{@channels_name[channel]}.csv", headers: ['share_id', 'user_team_id_deleted', 'user_deleted', 'user_team_id_created', 'user_created', 'date', 'time', 'type', 'to_channel', 'condition'])
    message =[]
    t.each do |m|
      if m[:user_deleted] == ''
        if m[:type]=='text'
          emoji = ":abc:"
        elsif m[:type] == 'regexp'
          emoji = ":heavy_plus_sign:"
        else
          emoji = ':white_square:'
        end
        message << "\t#{m[:share_id]} #{emoji} *_#{m[:date]}_* #{m[:time]} *#{m[:user_created]}* <##{@channels_id[m[:to_channel]]}|#{m[:to_channel]}> : \t`#{m[:condition]}`"
      end
    end
    if message.size == 0
      message << "*There are no active shares right now.*"
    else
      message.unshift("*Shares from channel <##{channel}>*")
    end
    message << general_message
    respond message.join("\n"), dest
  else
    respond "*There are no active shares right now.*#{general_message}"
  end

end

#see_shortcuts(dest, user, typem) ⇒ Object

help: ---------------------------------------------- help: see shortcuts help: see sc help: It will display the shortcuts stored for the user and for :all help: help: command_id: :see_shortcuts help:



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/slack/smart-bot/commands/on_bot/see_shortcuts.rb', line 9

def see_shortcuts(dest, user, typem)
  save_stats(__method__)
  from = user.name
  team_id_user = user.team_id + '_' + from
  if has_access?(__method__, user)
    unless typem == :on_extended
      msg = ""
      if @shortcuts[:all].keys.size > 0 or @shortcuts_global[:all].keys.size > 0
        msg = "*Available shortcuts for all:*\n"
        
        if @shortcuts[:all].keys.size > 0
          @shortcuts[:all].each { |name, value|
            msg += "    _#{name}: #{value}_\n"
          }
        end
        if @shortcuts_global[:all].keys.size > 0
          @shortcuts_global[:all].each { |name, value|
            msg += "    _#{name} (global): #{value}_\n"
          }
        end
        respond msg, dest
      end
      msg2 = ''
      if @shortcuts.keys.include?(team_id_user) and @shortcuts[team_id_user].keys.size > 0
        new_hash = @shortcuts[team_id_user].deep_copy
        @shortcuts[:all].keys.each { |k| new_hash.delete(k) }
        if new_hash.keys.size > 0
          msg2 = "*Available shortcuts for #{from}:*\n"
          new_hash.each { |name, value|
            msg2 += "    _#{name}: #{value}_\n"
          }
        end
      end
      if @shortcuts_global.keys.include?(team_id_user) and @shortcuts_global[team_id_user].keys.size > 0
        new_hash = @shortcuts_global[team_id_user].deep_copy
        @shortcuts_global[:all].keys.each { |k| new_hash.delete(k) }
        if new_hash.keys.size > 0
          msg2 = "*Available shortcuts for #{from}:*\n" if msg2 == ''
          new_hash.each { |name, value|
            msg2 += "    _#{name} (global): #{value}_\n"
          }
        end
      end
      respond msg2 unless msg2 == ''
      respond "No shortcuts found" if (msg + msg2) == ""
    end
  end
end

#see_statuses(user, channel, types, dest, not_on) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/slack/smart-bot/commands/general/see_statuses.rb', line 2

def see_statuses(user, channel, types, dest, not_on)
  save_stats(__method__)
  react :runner
  if channel == ""
    if dest[0] == "D"
      cdest = @channel_id
    else
      cdest = dest
    end
  else
    get_channels_name_and_id() unless @channels_name.keys.include?(channel) or @channels_id.keys.include?(channel)
    channel_id = nil
    if @channels_name.key?(channel) #it is an id
      channel_id = channel
      channel = @channels_name[channel_id]
    elsif @channels_id.key?(channel) #it is a channel name
      channel_id = @channels_id[channel]
    end
    cdest = channel_id
  end
  members = get_channel_members(cdest)
  if members.include?(user.id)
    list = {}
    only_active = false
    if types == ['available']
      only_active = true
      not_on = true
      types = [':palm_tree:', ':spiral_calendar_pad:', ':face_with_thermometer:', ':baby:']
    end
    members.each do |member|
      info = (member)
      text = info.user.profile.status_text
      emoji = info.user.profile.status_emoji
      exp = info.user.profile.expiration
      unless (((!types.empty? and !types.include?(emoji)) or (emoji.to_s == "" and text.to_s == "" and exp.to_s == "")) and !not_on) or
             (not_on and types.include?(emoji)) or info.user.deleted or info.user.is_bot or info.user.is_app_user
        if only_active
          active = (get_presence(member).presence.to_s == 'active')
        else
          active = false
        end
        if !only_active or (only_active and active)
          emoji = ":white_square:" if emoji.to_s == ""
          list[emoji] ||= []
          list[emoji] << {
            type: "context",
            elements: [
              {
                          type: "plain_text",
                          text: "\t\t",
                        },
              {
                          type: "image",
                          image_url: info.user.profile.image_24,
                          alt_text: info.user.name,
                        },
              {
                          type: "mrkdwn",
                          text: " *#{info.user.profile.real_name}* (#{info.user.name}) #{text} #{exp}",
                        },
            ],
          }
        end
      end
    end
    if list.size > 0
      list.each do |emoji, users|
        blocks = [
          {
                    "type": "context",
                    elements: [
                      {
                          type: "mrkdwn",
                          text: "#{'*Available* ' if only_active}*Members* #{emoji} on <##{cdest}>",
                        },
                    ],
                  },
        ]
        users = users.sort_by { |hsh| hsh.elements[2].text }
        respond blocks: (blocks+users)
      end
    else
      respond "Nobody on <##{cdest}> with that status"
    end
  else
    respond "You need to join <##{cdest}> to be able to see the statuses on that channel."
  end
  unreact :runner
end

#see_vacations(user, dest, from_user: '', add_stats: true, year: '') ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/slack/smart-bot/commands/general/see_vacations.rb', line 2

def see_vacations(user, dest, from_user: '', add_stats: true, year: '')
  save_stats(__method__) if add_stats

  get_vacations()
  team_id_user = "#{user.team_id}_#{user.name}"

  from_user_name = ''

  if from_user.empty?
    from_user_name = team_id_user
  else
     = find_user(from_user)
    from_user_name = "#{.team_id}_#{.name}"
  end

  if @vacations.key?(from_user_name) and @vacations[from_user_name][:public_holidays].to_s != ""
    country_region = @vacations[from_user_name][:public_holidays].downcase
  elsif config[:public_holidays].key?(:default_calendar)
    country_region = config[:public_holidays][:default_calendar].downcase
  else
    country_region = ''
  end

  local_day_time = local_time(country_region)
  if local_day_time.nil?
    today = Date.today
  else
    today = local_day_time.to_date
  end
  year = today.year.to_s if year.to_s == ''

  from_user = '' if from_user_name == team_id_user
  if !@vacations.key?(from_user_name) or !@vacations[from_user_name].key?(:periods) or @vacations[from_user_name].periods.empty?
    if from_user.empty?
      display_calendar(from_user_name, year) if dest[0] == 'D'
      respond "You didn't add any time off yet. Use `add vacation from YYYY/MM/DD to YYYY/MM/DD`"
    else
      respond "No time off added yet for <@#{from_user}>"
    end
  else
    messages = []
    messages << "*Time off <@#{from_user}> #{year}*" if !from_user.empty?

    display_calendar(from_user_name, year) if from_user_name == team_id_user and dest[0] == 'D'

    today_txt = today.strftime("%Y/%m/%d")
    current_added = false
    past_added = false
    @vacations[from_user_name].periods.sort_by { |v| v[:from]}.reverse.each do |vac|
      if !current_added and vac.to >= today_txt
        messages << "*Current and future periods*"
        current_added = true
      end
      if !past_added and vac.to < today_txt and from_user.empty? and vac.to[0..3] == year
        if dest[0]=='D'
          messages << "\n*Past periods #{year}*"
          past_added = true
        else
          messages << "To see past periods call me from a DM"
          break
        end
      end
      unless !from_user.empty? and vac.to < today_txt
        if vac.to[0..3] == year
          if !from_user.empty?
            icon = ":beach_with_umbrella:"
          elsif vac.type == 'vacation'
            icon = ':palm_tree:'
          elsif vac.type == 'sick'
            icon = ':face_with_thermometer:'
          elsif vac.type == 'sick child'
            icon = ':baby:'
          end
          if vac.from == vac.to
            messages << "     #{icon}    #{vac.from}   ##{vac.vacation_id}"
          else
            messages << "     #{icon}    #{vac.from} -> #{vac.to}   ##{vac.vacation_id}"
          end
        end
      end
    end
    if !past_added and !current_added and dest[0]=='D'
      if from_user.empty?
        messages << "No time off added yet for #{year}"
      else
        messages << "Not possible to see past periods for another user"
      end
    elsif !past_added and dest[0]=='D' and !from_user.empty? and from_user_name != team_id_user
      messages << "Not possible to see past periods for another user"
    end
    respond messages.join("\n")
  end
end

#send_file(to, msg, file, title, format, type = "text", content: "") ⇒ Object

to send a file to an user or channel send_file(dest, 'the message', "##project_folder/temp/logs_ptBI.log", 'message to be sent', 'text/plain', "text") send_file(dest, 'the message', "##project_folder/temp/example.jpeg", 'message to be sent', 'image/jpeg', "jpg") send_file(dest, 'the message', "", 'message to be sent', 'text/plain', "ruby", content: "the content to be sent when no file supplied") send_file(dest, 'the message', "myfile.rb", 'message to be sent', 'text/plain', "ruby", content: "the content to be sent when no file supplied")



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/slack/smart-bot/comm/send_file.rb', line 8

def send_file(to, msg, file, title, format, type = "text", content: "")
  unless config[:simulate]
    begin
      file = "myfile" if file.to_s == "" and content != ""
      if to[0] == "U" or to[0] == "W" #user
        im = client.web_client.conversations_open(users: id_user)
        channel = im["channel"]["id"]
      else
        channel = to
      end

      if Thread.current[:on_thread]
        ts = Thread.current[:thread_ts]
      else
        ts = nil
      end

      if content.to_s == ""
        client.web_client.files_upload(
          channels: channel,
          as_user: true,
          file: Faraday::UploadIO.new(file, format),
          title: title,
          filename: file,
          filetype: type,
          initial_comment: msg,
          thread_ts: ts,
        )
      else
        content.strip!
        #if first line is ```TYPE, then set file_type to TYPE
        if content[0..2] == "```"
          type = content[3..-1].split("\n")[0]
          content = content.split("\n")[1..-1].join("\n")
          #remove the last line if it is ```
          if content[-3..-1] == "```"
            content = content[0..-4]
          end
          content.gsub!(/[\u0080-\uFFFF]/, "?") #replace all non-ascii characters with a question mark
        end
        type = "text" if type.to_s == ""
        client.web_client.files_upload(
          channels: channel,
          as_user: true,
          content: content,
          title: title,
          filename: file,
          filetype: type,
          initial_comment: msg,
          thread_ts: ts,
        )
      end
    rescue Exception => stack
      @logger.warn stack
    end
  end
end

#send_message(dest, user, typem, to, thread_ts, stats_from, stats_to, stats_channel_filter, stats_command_filter, message) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: send message to @USER_NAME : MESSAGE helpadmin: send message to #CHANNEL_NAME : MESSAGE helpadmin: send message to THREAD_ID : MESSAGE helpadmin: send message to URL : MESSAGE helpadmin: send message to @USER1 @USER99 : MESSAGE helpadmin: send message to #CHANNEL1 #CHANNEL99 : MESSAGE helpadmin: send message to users from YYYY/MM/DD to YYYY/MM/DD #CHANNEL COMMAND_ID: MESSAGE helpadmin: It will send the specified message as SmartBot 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 helpadmin: In case from and to specified will send a DM to all users that have been using the SmartBot according to the SmartBot Stats. One message every 5sc. #CHANNEL and COMMAND_ID are optional filters. helpadmin: command_id: :send_message helpadmin:



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/slack/smart-bot/commands/on_bot/admin_master/send_message.rb', line 16

def send_message(dest, user, typem, to, thread_ts, stats_from, stats_to, stats_channel_filter, stats_command_filter, message)
  save_stats(__method__)
  if config.team_id_masters.include?("#{user.team_id}_#{user.name}") and typem==:on_dm #master admin user
    react :runner
    unless Thread.current[:command_orig].to_s == ''
      message_orig = Thread.current[:command_orig].to_s.gsub("\u00A0", " ").scan(/[^:]+\s*:\s+(.+)/im).join
      message = message_orig unless message_orig == ''
    end
    succ = true
    if stats_from!='' and stats_to!=''
      users = []
      user_ids = []
      stats_from.gsub!('/', '-')
      stats_to.gsub!('/', '-')
      stats_from += " 00:00:00 +0000"
      stats_to += " 23:59:59 +0000"
      Dir["#{config.stats_path}.*.log"].sort.each do |file|
        if file >= "#{config.stats_path}.#{stats_from[0..6]}.log" and file <= "#{config.stats_path}.#{stats_to[0..6]}.log"
          CSV.foreach(file, headers: true, header_converters: :symbol, converters: :numeric) do |row|
            if row[:date] >= stats_from and row[:date] <= stats_to and !users.include?(row[:user_name])
              if (stats_channel_filter=='' and stats_command_filter=='') or
                (stats_channel_filter!='' and stats_command_filter=='' and (row[:bot_channel_id]==stats_channel_filter or row[:dest_channel_id]==stats_channel_filter)) or
                (stats_command_filter!='' and stats_channel_filter=='' and row[:command]==stats_command_filter) or
                (stats_channel_filter!='' and stats_command_filter!='' and ((row[:bot_channel_id]==stats_channel_filter or row[:dest_channel_id]==stats_channel_filter) and row[:command]==stats_command_filter))

                user_ids << row[:user_id]
                users << row[:user_name]
              end
            end
          end
        end
      end

      users_success = []
      users_failed = []

      user_ids.each do |u|
        @buffered = false if config.testing
        succ = (respond message, u, thread_ts: thread_ts, web_client: true)
        if succ
          users_success << u
        else
          users_failed << u
        end
        sleep 5
      end
      respond "Users that received the message (#{users_success.size}): <@#{users_success.join('>, <@')}>", dest if users_success.size > 0
      respond "Users that didn't receive the message (#{users_failed.size}): <@#{users_failed.join('>, <@')}>", dest if users_failed.size > 0
      respond "No users selected to send the message.", dest if users_success.size == 0 and users_failed.size == 0
      succ = false if users_failed.size > 0
    else
      to.each do |t|
        unless t.match?(/^\s*$/)
          @buffered = false if config.testing
          succ = (respond message, t, thread_ts: thread_ts, web_client: true) && succ
        end
      end
    end
    unreact :runner
    if succ
      react :heavy_check_mark
    else
      react :x
    end
  else
    respond "Only master admin users on a private conversation with the SmartBot can send messages as SmartBot.", dest
  end
end

#send_msg_channel(to, msg, unfurl_links: true, unfurl_media: true) ⇒ Object

to: (String) Channel name or id msg: (String) message to send



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/slack/smart-bot/comm/send_msg_channel.rb', line 5

def send_msg_channel(to, msg, unfurl_links: true, unfurl_media: true)
  unless msg == ""
    begin
      get_channels_name_and_id() unless @channels_name.key?(to) or @channels_id.key?(to)
      if @channels_name.key?(to) #it is an id
        channel_id = to
      elsif @channels_id.key?(to) #it is a channel name
        channel_id = @channels_id[to]
      else
        @logger.fatal "Channel: #{to} not found. Message: #{msg}"
      end
      if config[:simulate]
        open("#{config.path}/buffer_complete.log", "a") { |f|
          f.puts "|#{channel_id}|#{Thread.current[:thread_ts]}|#{config[:nick_id]}|#{config[:nick]}|#{msg}~~~"
        }
      else  
        if Thread.current[:on_thread]
          client.message(channel: channel_id, text: msg, as_user: true, thread_ts: Thread.current[:thread_ts], unfurl_links: unfurl_links, unfurl_media: unfurl_media)
        else
          client.message(channel: channel_id, text: msg, as_user: true, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
        end
      end
      if config[:testing] and config.on_master_bot and !@buffered
        @buffered = true
        open("#{config.path}/buffer.log", "a") { |f|
          f.puts "|#{channel_id}|#{Thread.current[:thread_ts]}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
        }
      end
    rescue Exception => stack
      @logger.warn stack
    end
  end
end

#send_msg_user(id_user, msg = '', on_thread = nil, unfurl_links: true, unfurl_media: true, blocks: [], web_client: true, thread_ts: nil) ⇒ Object

to send messages without listening for a response to users



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/slack/smart-bot/comm/send_msg_user.rb', line 4

def send_msg_user(id_user, msg='', on_thread=nil, unfurl_links: true, unfurl_media: true, blocks: [], web_client: true, thread_ts: nil)
  resp = nil
  unless msg == "" and blocks.empty?
    begin
      on_thread = Thread.current[:on_thread] if on_thread.nil?
      thread_ts = Thread.current[:thread_ts] if thread_ts.to_s == ""
      web_client = true if !blocks.empty? or !unfurl_links or !unfurl_media
      if id_user[0] == "D"
        if config[:simulate]
          open("#{config.path}/buffer_complete.log", "a") { |f|
            f.puts "|#{id_user}|#{thread_ts}|#{config[:nick_id]}|#{config[:nick]}|#{msg}~~~"
          }
        else
          if web_client
            if on_thread
              resp = client.web_client.chat_postMessage(channel: id_user, text: msg, blocks: blocks, as_user: true, unfurl_links: unfurl_links, unfurl_media: unfurl_media, thread_ts: thread_ts)
            else
              resp = client.web_client.chat_postMessage(channel: id_user, text: msg, blocks: blocks, as_user: true, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
            end
          else
            if on_thread
              resp = client.message(channel: id_user, as_user: true, text: msg, thread_ts: thread_ts, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
            else
              resp = client.message(channel: id_user, as_user: true, text: msg, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
            end
          end
        end
        if config[:testing] and config.on_master_bot and !@buffered
          @buffered = true
          open("#{config.path}/buffer.log", "a") { |f|
            f.puts "|#{id_user}|#{thread_ts}|#{config[:nick_id]}|#{config[:nick]}|#{msg}#{blocks.join}"
          }
        end
      else
        if config[:simulate]
          open("#{config.path}/buffer_complete.log", "a") { |f|
            f.puts "|#{DIRECT[id_user.to_sym].ubot}|#{thread_ts}|#{config[:nick_id]}|#{config[:nick]}|#{msg}#{blocks.join}~~~"
          }
        else  
          im = client.web_client.conversations_open(users: id_user)
          if web_client
            if on_thread
              resp = client.web_client.chat_postMessage(channel: im["channel"]["id"], text: msg, blocks: blocks, as_user: true, unfurl_links: unfurl_links, unfurl_media: unfurl_media, thread_ts: thread_ts)
            else
              resp = client.web_client.chat_postMessage(channel: im["channel"]["id"], text: msg, blocks: blocks, as_user: true, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
            end
          else
            if on_thread
              resp = client.message(channel: im["channel"]["id"], as_user: true, text: msg, thread_ts: thread_ts, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
            else
              resp = client.message(channel: im["channel"]["id"], as_user: true, text: msg, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
            end
          end
        end
        if config[:testing] and config.on_master_bot and !@buffered
          @buffered = true
          open("#{config.path}/buffer.log", "a") { |f|
            f.puts "|#{im["channel"]["id"]}|#{thread_ts}|#{config[:nick_id]}|#{config[:nick]}|#{msg}#{blocks.join}"
          }
        end
      end
    rescue Exception => stack
      @logger.warn stack
    end
  end
  return resp
end

#set_general_message(user, status, message) ⇒ Object

helpmaster: ---------------------------------------------- helpmaster: set general message MESSAGE helpmaster: set general message off helpmaster: The SmartBot will display the specified message after treating every command helpmaster: Only works if you are on Master channel and you are a master admin user helpmaster: You can add interpolation to the message you are adding helpmaster: Examples: helpmaster: set general message We will be on maintenance at 12:00 helpmaster: set general message We will be on maintenance in #SlackSmartBot.((Time((Time.new(2021,6,18,13,30,0)-Time((Time.new(2021,6,18,13,30,0)-Time.now)/60)((Time.new(2021,6,18,13,30,0)-Time.now)/60).to_i minutes helpmaster: set general message We will be on *maintenance* at *12:00* 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* helpmaster: helpmaster: command_id: :set_general_message helpmaster:



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/slack/smart-bot/commands/on_master/admin_master/set_general_message.rb', line 16

def set_general_message(user, status, message)
  save_stats(__method__)
  if config.on_master_bot
    if config.team_id_masters.include?("#{user.team_id}_#{user.name}") #master admin user
      if status == 'on'
        config.general_message = message
        respond "General message has been set."
      else
        config.general_message = ''
        respond "General message won't be displayed anymore."
      end
      @config_log.general_message = config.general_message
      file = File.open("#{config.path}/config_tmp.status", "w")
      file.write @config_log.inspect
      file.close

    else
      respond 'Only master admins on master channel can use this command.'
    end
  else
    respond 'Only master admins on master channel can use this command.'
  end
end

#set_maintenance(user, status, message) ⇒ Object

helpmaster: ---------------------------------------------- helpmaster: set maintenance on helpmaster: set maintenance on MESSAGE helpmaster: set maintenance off helpmaster: turn maintenance on helpmaster: turn maintenance on MESSAGE helpmaster: turn maintenance off helpmaster: The SmartBot will be on maintenance and responding with a generic message helpmaster: Only works if you are on Master channel and you are a master admin user helpmaster: You can add interpolation to the message you are adding helpmaster: Examples: helpmaster: set maintenance on helpmaster: set maintenance on We are on maintenance. We'll be available again in #SlackSmartBot.((Time((Time.new(2021,6,18,13,30,0)-Time((Time.new(2021,6,18,13,30,0)-Time.now)/60)((Time.new(2021,6,18,13,30,0)-Time.now)/60).to_i minutes helpmaster: turn maintenance on We are on *maintenance* until *12:00* helpmaster: helpmaster: command_id: :set_maintenance helpmaster:



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/slack/smart-bot/commands/on_master/admin_master/set_maintenance.rb', line 19

def set_maintenance(user, status, message)
  save_stats(__method__)
  if config.on_master_bot
    if config.team_id_masters.include?("#{user.team_id}_#{user.name}") #master admin user
      if message == ''
        config.on_maintenance_message = "Sorry I'm on maintenance so I cannot attend your request."
      else
        config.on_maintenance_message = message
      end

      if status == 'on'
        config.on_maintenance = true
        respond "From now on I'll be on maintenance status so I won't be responding accordingly."
        save_status :off, :maintenance_on, config.on_maintenance_message
      else
        config.on_maintenance = false
        respond "From now on I won't be on maintenance. Everything is back to normal!"
        save_status :on, :maintenance_off, config.on_maintenance_message
      end
      @config_log.on_maintenance = config.on_maintenance
      file = File.open("#{config.path}/config_tmp.status", "w")
      file.write @config_log.inspect
      file.close

    else
      respond 'Only master admins on master channel can use this command.'
    end
  else
    respond 'Only master admins on master channel can use this command.'
  end
end

#set_public_holidays(country, state, user) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# File 'lib/slack/smart-bot/commands/general/set_public_holidays.rb', line 2

def set_public_holidays(country, state, user)
  save_stats(__method__)

  result = public_holidays(country, state, Date.today.year.to_s, '', '', add_stats: false, publish_results: false)
  if result == true
      team_id_user = "#{user.team_id}_#{user.name}"
      if state == ""
          country_region = country
      else
          country_region = "#{country}/#{state}"
      end
      if state == ''
        respond "Public holidays for *#{country_region}* set. If available States, try with the country and state to be more precise."
      else
        respond "Public holidays for *#{country_region}* set."
      end
      get_vacations()
      @vacations[team_id_user] ||= {}
      @vacations[team_id_user][:public_holidays] = country_region
      update_vacations()
      check_vacations(date: nil, team_id: user.team_id, user: user.name, set_status: true, only_first_day: false)
  else
      respond "Be sure the country and state are correct. If not displayed available states, try with the country only."
  end
end

#set_status(user_id, status: nil, message: nil, expiration: nil) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/slack/smart-bot/comm/set_status.rb', line 2

def set_status(user_id, status: nil, message: nil, expiration: nil)
  unless client_user.nil?
    if expiration.is_a?(String) and expiration.match?(/^\d\d\d\d\/\d\d\/\d\d$/)
      expiration = Date.parse(expiration, '%Y/%m/%d').to_time.to_i
    elsif expiration.is_a?(Date)
      expiration = expiration.to_time.to_i
    end
    params = []
    params << "'status_emoji': '#{status}'" unless status.nil?
    params << "'status_text': '#{message}'" unless message.nil?
    params << "'status_expiration':  '#{expiration}'" unless expiration.nil?
    begin
      resp = client_user.users_profile_set(user: user_id, profile: "{ #{params.join(', ')} }")
    rescue Exception => exc
      @logger.fatal exc.inspect
    end
  end
end

#share_messages(user, from_channel, to_channel, condition) ⇒ Object

todo: reaction type not added yet since RTM doesn't support it. See if we can add it as an event



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/slack/smart-bot/commands/general/share_messages.rb', line 3

def share_messages(user, from_channel, to_channel, condition)
  save_stats(__method__)
  if has_access?(__method__, user)
    #todo: add a @shared variable to control not to be shared more than once when using reactions
    #todo: it is only possible to share if smartbot is a member in both channels and the person adding the share command also
    if Thread.current[:typem] == :on_call or Thread.current[:typem] == :on_dm
      respond "You can use this command only from the source channel."
    elsif from_channel == to_channel
      respond "You cannot share messages on the same channel than source channel."
    else
      channels = get_channels(types: 'public_channel')
      channel_found = channels.detect { |c| c.name == from_channel }
      get_channels_name_and_id() unless @channels_id.key?(to_channel)
      channel_found = false if !@channels_id.key?(to_channel)
      if channel_found 
        members = get_channel_members(@channels_id[to_channel])
        if members.include?(config.nick_id) and members.include?(user.id)
          if condition.match?(/^\/.+\/$/)
            type = :regexp
          elsif condition.match?(/^".+"$/) or condition.match?(/^'.+'$/)
            type = :text
          else
            type = :reaction
          end
          if File.exist?("#{config.path}/shares/#{from_channel}.csv")
            t = CSV.table("#{config.path}/shares/#{from_channel}.csv", headers: ['share_id', 'user_team_id_deleted', 'user_deleted', 'user_team_id_created', 'user_created', 'date', 'time', 'type', 'to_channel', 'condition'])
            @shares[from_channel] = t
            if t.size>0
              num = t[:share_id].max + 1
            else
              num = 1
            end
          elsif !@shares.key?(from_channel)
            File.open("#{config.path}/shares/#{from_channel}.csv","w")
            t = CSV.table("#{config.path}/shares/#{from_channel}.csv", headers: ['share_id', 'user_team_id_deleted', 'user_deleted', 'user_team_id_created', 'user_created', 'date', 'time', 'type', 'to_channel', 'condition'])
            num = 1
            @shares[from_channel] = t
          else
            num = @shares[from_channel][:share_id].max + 1
          end
          values = [num, '', '', user.team_id, user.name, Time.now.strftime("%Y/%m/%d"), Time.now.strftime("%H:%M"), type.to_s, to_channel, condition]
          @shares[from_channel] << values
          CSV.open("#{config.path}/shares/#{from_channel}.csv", "a+") do |csv|
            csv << values
          end
          respond "*Share command*: id:#{num} Messages #{condition} will be shared from now on. Related commands `see shares`, `delete share ID`"
        else
          respond "*Share command*: The channel ##{to_channel} need to exist and the SmartBot and you have to be members."
        end
      else
        respond "*Share command*: The channel <##{@channels_id[from_channel]}|#{from_channel}> has to be a public channel and the destination channel has to be a valid channel."
      end
    end
  end
end

#start_bot(dest, from) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: start bot helpadmin: start this bot helpadmin: the bot will start to listen helpadmin: You can use this command only if you are an admin user helpadmin: helpadmin: command_id: :start_bot helpadmin:



11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/slack/smart-bot/commands/on_bot/admin/start_bot.rb', line 11

def start_bot(dest, from)
  save_stats(__method__)
  if is_admin?
    respond "This bot is running and listening from now on. You can pause again: pause this bot", dest
    @status = :on
    unless config.on_master_bot
      @bots_created[@channel_id][:status] = :on
      update_bots_file()
      send_msg_channel config.master_channel, "Changed status on #{config.channel} to :on"
    end
    save_status :on, :started, 'The admin started this bot'
  else
    respond "Only admin users can change my status", dest
  end
end

#start_routine(dest, from, name) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: start routine NAME helpadmin: It will start a paused routine helpadmin: You can use this command only if you are an admin user helpadmin: NAME: one word to identify the routine helpadmin: Examples: helpadmin: start routine example helpadmin: helpadmin: command_id: :start_routine helpadmin:



14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/slack/smart-bot/commands/on_bot/admin/start_routine.rb', line 14

def start_routine(dest, from, name)
  save_stats(__method__)
  if is_admin?
    if !config.on_master_bot and dest[0] == "D"
      respond "It's only possible to start routines from MASTER channel from a direct message with the bot.", dest
    elsif @routines.key?(@channel_id) and @routines[@channel_id].key?(name)
      @routines[@channel_id][name][:status] = :on
      if @routines[@channel_id][name].key?(:daymonth) and @routines[@channel_id][name][:daymonth] != ''
        started = Time.now
        daymonth = @routines[@channel_id][name][:daymonth]
        day = daymonth.to_i
        nt = @routines[@channel_id][name][:at].split(":")
        if Time.now > Time.new(Time.now.year, Time.now.month, day, nt[0], nt[1], nt[2])
            next_month = Date.new(Date.today.year, Date.today.month, 1) >> 1
        else
            next_month = Date.new(Date.today.year, Date.today.month, 1)
        end
        next_month_last_day = Date.new(next_month.year, next_month.month, -1)
        if day > next_month_last_day.day
            next_time = Date.new(next_month.year, next_month.month, next_month_last_day.day)
        else
            next_time = Date.new(next_month.year, next_month.month, day)
        end
        days = (next_time - Date.today).to_i
        next_run = started + (days * 24 * 60 * 60) # one more day/week
        next_run = Time.new(next_run.year, next_run.month, next_run.day, nt[0], nt[1], nt[2])
        @routines[@channel_id][name][:next_run] = next_run.to_s
        @routines[@channel_id][name][:sleeping] = (next_run - started).ceil
      elsif @routines[@channel_id][name][:at]!=''
        started = Time.now
        if started.strftime("%H:%M:%S") < @routines[@channel_id][name][:at]
          nt = @routines[@channel_id][name][:at].split(":")
          next_run = Time.new(started.year, started.month, started.day, nt[0], nt[1], nt[2])
        else
          next_run = started + (24 * 60 * 60) # one more day
          nt = @routines[@channel_id][name][:at].split(":")
          next_run = Time.new(next_run.year, next_run.month, next_run.day, nt[0], nt[1], nt[2])
        end
        @routines[@channel_id][name][:next_run] = next_run.to_s
        @routines[@channel_id][name][:sleeping] = (next_run - started).ceil
      end
      update_routines()
      respond "The routine *`#{name}`* has been started. The change will take effect in less than 30 secs.", dest
    else
      respond "There isn't a routine with that name: *`#{name}`*.\nCall `see routines` to see added routines", dest
    end
  else
    respond "Only admin users can use this command", dest
  end
end

#stop_using_rules(dest, channel, user, dchannel) ⇒ Object

help: ---------------------------------------------- help: stop using rules from CHANNEL help: stop using CHANNEL help: it will stop using the rules from the specified channel. help: help: command_id: :stop_using_rules help:



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/slack/smart-bot/commands/on_bot/general/stop_using_rules.rb', line 9

def stop_using_rules(dest, channel, user, dchannel)
  save_stats(__method__)
  channel.gsub!('#','') # for the case the channel name is in plain text including #
  if @channels_id.key?(channel)
    channel_id = @channels_id[channel]
  else
    channel_id = channel
  end

  team_id_user = "#{user.team_id}_#{user.name}"
  if dest[0] == "C" or dest[0] == "G" #channel
    if @rules_imported.key?(team_id_user) and @rules_imported[team_id_user].key?(dchannel)
      if @rules_imported[team_id_user][dchannel] != channel_id
        respond "You are not using those rules.", dest
      else
        @rules_imported[team_id_user].delete(dchannel)
        sleep 0.5
        update_rules_imported()
        respond "You won't be using those rules from now on.", dest

        def git_project() "" end
        def project_folder() "" end
      end
    else
      respond "You were not using those rules.", dest
    end
  else #direct message
    if @rules_imported.key?(team_id_user) and @rules_imported[team_id_user].key?(user.name)
      if @rules_imported[team_id_user][user.name] != channel_id
        respond "You are not using those rules.", dest
      else
        @rules_imported[team_id_user].delete(user.name)
        sleep 0.5
        update_rules_imported()
        respond "You won't be using those rules from now on.", dest

        def git_project() "" end
        def project_folder() "" end
      end
    else
      respond "You were not using those rules.", dest
    end
  end
end

#stop_using_rules_on(dest, user, from, channel, typem) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: stop using rules on CHANNEL_NAME helpadmin: it will stop using the extended rules on the specified channel. helpadmin: helpadmin: command_id: :stop_using_rules_on helpadmin:



10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# File 'lib/slack/smart-bot/commands/on_bot/admin/stop_using_rules_on.rb', line 10

def stop_using_rules_on(dest, user, from, channel, typem)
  save_stats(__method__)
  unless typem == :on_extended
    if !is_admin?
      respond "Only admins can extend or stop using the rules. Admins on this channel: #{config.admins}", dest
    else
      get_bots_created()
      channel = @channels_name[channel] if @channels_name.key?(channel)
      if @bots_created[@channel_id][:extended].include?(channel)
        @bots_created[@channel_id][:extended].delete(channel)
        update_bots_file()
        respond "<@#{user.id}> removed the access to the rules of #{config.channel} from #{channel}.", @master_bot_id
        if @channels_id[channel][0] == "G"
          respond "The rules won't be accessible from *#{channel}* from now on.", dest
        else
          respond "The rules won't be accessible from *<##{@channels_id[channel]}>* from now on.", dest
        end
        respond "<@#{user.id}> removed the access to the rules of <##{@channel_id}> from this channel.", @channels_id[channel]
      else
        respond "The rules were not accessible from *#{channel}*", dest
      end
    end
  end
end

#suggest_command(user, dest, dchannel, specific, rules_file) ⇒ Object

help: ---------------------------------------------- help: suggest command help: random command help: command suggestion help: suggest rule help: random rule help: rule suggestion help: it will display the help content for a random command. help: if used 'rule' then it will display a random rule. help: if used 'command' it will show any kind of command or rule. help: command_id: :suggest_command help:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/slack/smart-bot/commands/on_bot/general/suggest_command.rb', line 15

def suggest_command(user, dest, dchannel, specific, rules_file)
  save_stats(__method__)
  dont_suggest = []
  help_message = get_help(rules_file, dest, user, specific, true, descriptions: false, only_normal_user: true)
  commands = help_message.gsub(/====+/,'-'*30).split(/^\s*-------*$/).flatten
  commands.reject!{|c| c.match?(/These are specific commands for this bot on this/i) || c.match?(/\A\s*\z/)}
  dont_suggest.each do |ds|
    commands.reject!{|c| c.match?(/:#{ds}\s*$/i)}
  end
  @last_suggested_commands ||= []
  @last_suggested_commands.shift if @last_suggested_commands.size >=5
  command = ''
  begin
    command = commands.sample
  end until !@last_suggested_commands.include?(command) or commands.size <= 5
  @last_suggested_commands << command
  command.gsub!(/^\s*command_id:\s+:\w+\s*$/,'')
  command.gsub!(/^\s*>.+$/,'')
  message = "*Command suggestion*:\n#{command}"
  respond message, dest, unfurl_links: false, unfurl_media: false
end

#summarize(user, dest, channel, from, thread_ts) ⇒ Object

todo: add tests



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/slack/smart-bot/commands/general/summarize.rb', line 4

def summarize(user, dest, channel, from, thread_ts)
  save_stats(__method__)

  ai_conn, message = SlackSmartBot::AI::OpenAI.connect({}, config, {}, service: :chat_gpt)
  if message.empty?
    ai_models_conn, message = SlackSmartBot::AI::OpenAI.connect({}, config, {}, service: :models)
  end
  if !message.empty? #error connecting
    respond message
  else
    channels_bot_is_in = get_channels(bot_is_in: true)
    if dest[0] == "D" and channel == dest
      respond "Sorry, I can't summarize a direct message. Please use this command in a channel or supply the channel you want to summarize."
    elsif !channels_bot_is_in.id.include?(channel) or !get_channel_members(channel).include?(config.nick_id_granular)
      respond "Sorry, I can't summarize a channel where <@#{config.nick_id_granular}> and <@#{config.nick_id}> are not members. Please invite them to the channel."
    elsif !get_channel_members(channel).include?(user.id)
      respond "Sorry, I can't summarize a channel where you are not a member."
    else
      if (from == "" and channel == dest and Thread.current[:on_thread]) or thread_ts != ""
        summarize_thread = true
        if thread_ts == ""
          thread_ts = Thread.current[:thread_ts]
        else
          if thread_ts.include?(".")
            thread_ts = thread_ts
          else
            thread_ts = thread_ts.scan(/(\d+)/).join
            thread_ts = "#{thread_ts[0..9]}.#{thread_ts[10..-1]}"
          end
        end
      else
        summarize_thread = false
      end
      from_time_off = false
      if from == ""
        from = (Time.now - (60 * 60) * 24 * 30).to_s
        get_vacations()
        if @vacations.key?(user.team_id_user) and @vacations[user.team_id_user].key?(:periods)
          @vacations[user.team_id_user].periods.each do |p|
            #get the last from date
            if p.from > from[0..9].gsub("-", "/") and p.from < Time.now.to_s[0..9].gsub("-", "/")
              from = p.from
              from_time_off = true
            end
          end
          #from will be the day before the last time off
          if from_time_off
            from = (Time.strptime(from, "%Y/%m/%d") - (60 * 60) * 24).to_s
          end
        end
      end

      from.gsub!("-", "/")
      if from.length == 10
        from = from + " 00:00:00"
      elsif from.length == 16
        from = from + ":00"
      end
      from = Time.strptime(from, "%Y/%m/%d %H:%M:%S")
      if summarize_thread
        last_msg = respond("I'm going to summarize the thread messages", return_message: true)
      elsif from_time_off
        last_msg = respond("I'm going to summarize the messages since the day before your last time off #{from.strftime("%Y/%m/%d")} in <##{channel}>. This may take a while.", return_message: true)
      else
        last_msg = respond("I'm going to summarize the messages since #{from.strftime("%Y/%m/%d %H:%M:%S")} in <##{channel}>. This may take a while.", return_message: true)
      end

      @history_still_running ||= false
      if @history_still_running
        respond "Due to Slack API rate limit, `summarize` command is limited. Waiting for other `summarize` command to finish."
        num_times = 0
        while @history_still_running and num_times < 30
          num_times += 1
          sleep 1
        end
        if @history_still_running
          respond "Sorry, Another `summarize` command is still running after 30 seconds. Please try again later."
        end
      end
      unless @history_still_running
        @history_still_running = true
        react :running
        if summarize_thread
          hist = client_granular.conversations_history(channel: channel, oldest: thread_ts, inclusive: true, limit: 1)
        else
          hist = client_granular.conversations_history(channel: channel)
        end
        messages = {} # store the messages by year/month
        act_users = {}
        act_threads = {}
        hist.messages.each do |message|
          if Time.at(message.ts.to_f) >= from or summarize_thread
            year_month = Time.at(message.ts.to_f).strftime("%Y/%m")
            messages[year_month] ||= []
            if message.key?("thread_ts")
              thread_ts_message = message.thread_ts
              replies = client_granular.conversations_replies(channel: channel, ts: thread_ts_message, latest: last_msg.ts)
              sleep 0.5 #to avoid rate limit Tier 3 (50 requests per minute)
              messages_replies = ["Thread Started about last message:"]
              act_threads[message.ts] = replies.messages.size
              replies.messages.each_with_index do |msgrepl, i|
                act_users[msgrepl.user] ||= 0
                act_users[msgrepl.user] += 1
                messages_replies << "<@#{msgrepl.user}> (#{Time.at(msgrepl.ts.to_f)}) wrote:> #{msgrepl.text}" if i > 0
              end
              messages_replies << "Thread ended."
              messages[year_month] += messages_replies.reverse # the order on repls is from older to newer
            end
            act_users[message.user] ||= 0
            act_users[message.user] += 1
            url_to_message = "https://#{client.team.domain}.slack.com/archives/#{channel}/#{message.ts}"
            messages[year_month] << "<@#{message.user}> (#{Time.at(message.ts.to_f)}) (link to the message: #{url_to_message}) wrote:> #{message.text}"
          end
        end
        messages.each do |year_month, msgs|
          messages[year_month] = msgs.reverse # the order on history is from newer to older
        end
        @history_still_running = false
        unreact :running
        if messages.empty?
          respond "There are no Slack Messages since #{from}"
        else
          react :speech_balloon
          chatgpt = ai_conn[user.team_id_user].chat_gpt
          models = ai_models_conn[user.team_id_user].models

          prompt_orig = "Could you please provide a summary of the given conversation, including all key points and supporting details?\n"
          prompt_orig += "The summary should be comprehensive and accurately reflect the main message and arguments presented in the original text, while also being concise and easy to understand.\n"
          prompt_orig += "To ensure accuracy, please read the text carefully and pay attention to any nuances or complexities in the language.\n"
          prompt_orig += "Please also add the most important conversations in the summary.\n"
          prompt_orig += "Additionally, the summary should avoid any personal biases or interpretations and remain objective and factual throughout.\n"
          prompt_orig += "If you name an user remember to name it as <@user_id> so it is not replaced by the user name.\n"
          prompt_orig += "Add the link to the message so it is easy to find it.\n"
          prompt_orig += "Add also the date of the message for relevant conversations.\n"
          prompt_orig += "This is the conversation:\n"

          #sort by year/month from older to newer
          messages = messages.sort_by { |k, v| k }.to_h

          @open_ai_model_info ||= {}
          @open_ai_model_info[chatgpt.smartbot_model] ||= SlackSmartBot::AI::OpenAI.models(models.client, models, chatgpt.smartbot_model, return_response: true)
          if @open_ai_model_info[chatgpt.smartbot_model].key?(:max_input_tokens)
            max_num_tokens = @open_ai_model_info[chatgpt.smartbot_model][:max_input_tokens].to_i
          elsif @open_ai_model_info[chatgpt.smartbot_model].key?(:max_tokens)
            max_num_tokens = @open_ai_model_info[chatgpt.smartbot_model][:max_tokens].to_i
          else
            max_num_tokens = 8000
          end
          #num_tokens = OpenAI.rough_token_count(prompt_orig + messages.values.flatten.join) #jal
          #enc = Tiktoken.encoding_for_model(chatgpt.smartbot_model)
          enc = Tiktoken.encoding_for_model("gpt-4") #jal todo: fixed value since version 0.0.8 and 0.0.9 failed to install on SmartBot VM. Revert when fixed.
          num_tokens = enc.encode(prompt_orig + messages.values.flatten.join).length

          respond ":information_source: ChatGPT model: *#{chatgpt.smartbot_model}*. Max tokens: *#{max_num_tokens}*. Characters: #{messages.values.flatten.join.size}. Messages: #{messages.values.flatten.size}. Threads: #{act_threads.size}. Users: #{act_users.size}. Chatgpt tokens: *#{num_tokens}*"

          prompts = []
          i = 0
          messages.each do |year_month, msgs|
            msgs.each do |msg|
              #num_tokens = OpenAI.rough_token_count(prompts[i].to_s + msg) #jal
              #enc = Tiktoken.encoding_for_model(chatgpt.smartbot_model)
              enc = Tiktoken.encoding_for_model("gpt-4") #jal todo: fixed value since version 0.0.8 and 0.0.9 failed to install on SmartBot VM. Revert when fixed.
              num_tokens = enc.encode(prompts[i].to_s + msg).length
              i += 1 if num_tokens > max_num_tokens
              prompts[i] ||= prompt_orig
              prompts[i] += "#{msg}\n"
            end
          end
          prompts.each_with_index do |prompt, i|
            #num_tokens = OpenAI.rough_token_count(prompt)
            #enc = Tiktoken.encoding_for_model(chatgpt.smartbot_model)
            enc = Tiktoken.encoding_for_model("gpt-4") #jal todo: fixed value since version 0.0.8 and 0.0.9 failed to install on SmartBot VM. Revert when fixed.
            num_tokens = enc.encode(prompt).length
            respond ":information_source: The total number of chatgpt tokens is more than the max allowed for this chatgpt model. *Part #{i + 1} of #{prompts.size}*.\n" if prompts.size > 1
            success, res = SlackSmartBot::AI::OpenAI.send_gpt_chat(chatgpt.client, chatgpt.smartbot_model, prompt, chatgpt)
            result_messages = []
            if success
              result_messages << "*ChatGPT:*\n#{transform_to_slack_markdown(res.to_s.strip)}"
            else
              result_messages << "*ChatGPT:*\nI'm sorry, I couldn't summarize the conversation. This is the issue: #{res}"
            end
            if i == prompts.size - 1
              act_users.delete(config.nick_id_granular)
              act_users.delete(config.nick_id)

              act_users = act_users.sort_by { |k, v| v }.reverse

              result_messages << "\n\t:runner: Most active users: #{act_users[0..2].map { |k, v| "<@#{k}> (#{v})" }.join(", ")}"
              if act_threads.size > 0 and !summarize_thread
                act_threads = act_threads.sort_by { |k, v| v }.reverse
                result_messages << "\t:fire: Most active threads: #{act_threads[0..2].map { |k, v| "<https://#{client.team.domain}.slack.com/archives/#{channel}/#{k}|#{v - 1} replies>" }.join(", ")}"
              end
            end
            respond result_messages.join("\n").gsub("**", "*")
          end
          unreact :speech_balloon
        end
      end
    end
  end
end

#transform_to_slack_markdown(markdown_text) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/slack/smart-bot/utils/transform_to_slack_markdown.rb', line 2

def transform_to_slack_markdown(markdown_text)
  # Regular expressions to match code blocks
  code_block_regex = /```(.*?)```/m

  # Extract code blocks to preserve them
  preserved_code_blocks = markdown_text.scan(code_block_regex).map(&:first)

  # Replace code blocks and inline code with placeholders
  code_block_placeholder_text = ""
  while markdown_text.include?(code_block_placeholder_text)
    code_block_placeholder_text = "CODE_BLOCK_PLACEHOLDER_#{"6:x".gen}"
  end
  transformed_text = markdown_text.gsub(code_block_regex, code_block_placeholder_text)

  # Transform general Markdown to Slack Markdown
  transformed_text.gsub!(/^\* (.*)$/, '• \1')        # Unordered list
  transformed_text.gsub!(/^\s*d+. (.*)$/, '\1.')    # Ordered list
  transformed_text.gsub!(/!\[(.*?)\]\((.*?)\)/, '\1') # Images to alt text
  transformed_text.gsub!(/\[(.*?)\]\((.*?)\)/, '<\2|\1>') # Links
  transformed_text.gsub!(/\*\*(.*?)\*\*/, '*\1*')    # Bold
  transformed_text.gsub!(/__(.*?)__/, '*\1*')       # Bold
  # delete any * character in any position if it is a header
  transformed_text.gsub!(/^\s*#+.*\*/) { |match| match.gsub("*", "") }
  # add for more than 4 # the same than for #### but one :black_small_square: per extra #
  transformed_text.gsub!(/^\s*####(#+) (.*)$/) { "\n" + "#{":black_small_square:" * ($1.length + 1)} *#{$2}*" }
  transformed_text.gsub!(/^\s*#### (.*)$/, "\n" + ':black_small_square: *\1*')       # Header level 4 to bold
  transformed_text.gsub!(/^\s*### (.*)$/, "\n" + ':small_orange_diamond: *\1*')       # Header level 3 to bold
  transformed_text.gsub!(/^\s*## (.*)$/, "\n" + ':small_blue_diamond: *\1*')        # Header level 2 to bold
  transformed_text.gsub!(/^\s*# (.*)$/, "\n" + ':small_red_triangle: *\1*')         # Header level 1 to bold

  # Reinsert preserved code blocks and inline code
  preserved_code_blocks.each { |block| transformed_text.sub!(code_block_placeholder_text, "```#{block}```") }
  return transformed_text
end

#treat_message(data, remove_blocks = true) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/slack/smart-bot/treat_message.rb', line 2

def treat_message(data, remove_blocks = true)
  @buffered = false if config[:testing]
  begin
    begin
      command_orig = data.text
      unless data.text.to_s.match(/\A\s*\z/)
        #to remove italic, bold... from data.text since there is no method on slack api
        if remove_blocks and !data.blocks.nil? and data.blocks.size > 0
          data_text = ""
          data.blocks.each do |b|
            if b.type == "rich_text"
              if b.elements.size > 0
                b.elements.each do |e|
                  if e.type == "rich_text_section" or e.type == "rich_text_preformatted"
                    if e.elements.size > 0 and (e.elements.type.uniq - ["link", "text", "user", "channel"]) == []
                      data_text += "```" if e.type == "rich_text_preformatted"
                      e.elements.each do |el|
                        if el.type == "text"
                          data_text += el.text
                        elsif el.type == "user"
                          data_text += "<@#{el.user_id}>"
                        elsif el.type == "channel"
                          tch = data.text.scan(/(<##{el.channel_id}\|[^\>]*>)/).flatten.first
                          data_text += tch.to_s
                        else
                          data_text += el.url
                        end
                      end
                      data_text += "```" if e.type == "rich_text_preformatted"
                    end
                  end
                end
              end
            end
          end
          data.text = data_text unless data_text == ""
        end
        data.text = CGI.unescapeHTML(data.text)
        data.text.gsub!("\u00A0", " ") #to change &nbsp; (asc char 160) into blank space
      end
      data.text.gsub!("", "'")
      data.text.gsub!("", "'")
      data.text.gsub!("", '"')
      data.text.gsub!("", '"')
    rescue Exception => exc
      @logger.warn "Impossible to unescape or clean format for data.text:#{data.text}"
      @logger.warn exc.inspect
    end

    unless data.key?(:routine)
      data.routine = false
      data.routine_name = ""
      data.routine_type = ""
    end
    if config[:testing] and config.on_master_bot and !@buffered
      @buffered = true
      open("#{config.path}/buffer.log", "a") { |f|
        f.puts "|#{data.channel}|#{data.thread_ts}|#{data.user}|#{data.user_name}|#{data.text}"
      }
    end

    if data.key?(:dest) and data.dest.to_s != "" # for run routines and publish on different channels
      dest = data.dest
    elsif data.channel[0] == "D" or data.channel[0] == "C" or data.channel[0] == "G" #Direct message or Channel or Private Channel
      dest = data.channel
    else # not treated
      dest = nil
    end
    #todo: sometimes data.user is nil, check the problem.
    @logger.warn "!dest is nil. user: #{data.user}, channel: #{data.channel}, message: #{data.text}" if dest.nil?
    if !data.files.nil? and data.files.size == 1 and data.text.to_s == "" and data.files[0].filetype == "ruby"
      data.text = "ruby"
    end

    #open ai chat gpt and shared messages as an input
    if data.text.match?(/\A\s*(^|!!|!)?\s*(\?|\?\?)\s*/im) and !data.attachments.nil? and data.attachments.size > 0 and !data.attachments[0].text.nil? and data.attachments[0].text != ""
      data.attachments.each_with_index do |att, i|
        if !att.text.nil? and att.text != ""
          data.text += "\n#{att.text}"
        end
      end
    end
    if !dest.nil? and config.on_master_bot and !data.text.nil? and data.text.match(/^ping from (.+)\s*$/) and data.user == config[:nick_id]
      @pings << $1
    end

    if config.on_master_bot and @vacations_check != Time.now.strftime("%Y%m%d%H") #every hour since depends on user's time zone
      @vacations_check = Time.now.strftime("%Y%m%d%H")
      t = Thread.new do
        check_vacations(date: nil, only_first_day: true)
      end
    end
    typem = :dont_treat

    @users = get_users() if @users.empty?
    if data.key?(:bot_id) and data.bot_id.to_s != ""
      if @slack_bots.key?(data.bot_id) #bot or workflow
        data.user = @slack_bots[data.bot_id]
      else
        bot_info = (data.bot_id, is_bot: true)
        if bot_info.nil? or bot_info.empty?
          @logger.warn "Bot not found on users with data: #{data.inspect}"
        else
          @slack_bots[data.bot_id] = bot_info.user.id
          data.user = bot_info.user.id
          @users << bot_info.user unless bot_info.nil? or bot_info.empty?
        end
      end
    end

    if data.nil? or data.user.nil? or data.user.to_s == ""
       = nil
    else
       = find_user(data.user, get_sso_user_name: true)
      if .nil?
        @logger.warn "User not found on users with id #{data.user} and user_team #{data.user_team}"
      elsif !.key?(:team_id) or .team_id.to_s.empty?
        @logger.warn "User with id #{data.user} and user_team #{data.user_team} has no team_id. User_info: #{.inspect}"
      end
    end
    if !dest.nil? and !data.text.nil? and !data.text.to_s.match?(/\A\s*\z/)
      get_bots_created()
      if data.channel[0] == "D" and !data.text.to_s.match?(/^\s*<@#{config[:nick_id]}>\s+/) and
         (data.text.to_s.match?(/^\s*(on)?\s*<#\w+\|[^>]*>/i) or data.text.to_s.match?(/^\s*(on)?\s*#\w+/i))
        data.text = "<@#{config[:nick_id]}> " + data.text.to_s
      end
      #todo: we need to add mixed channels: @smart-bot on private1 #bot1cm <#CXDDFRDDF|bot2cu>: echo A
      if data.text.match(/\A\^\^+/) # to open a thread it will be only when starting by single ^
        typem = :dont_treat
      elsif data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?((<#\w+\|[^>]*>\s*)+)\s*:?\s*(.*)/im) or
            data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?((#[a-zA-Z0-9\-\_]+\s*)+)\s*:?\s*(.*)/im) or
            data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?(([a-zA-Z0-9\-\_]+\s*)+)\s*:\s*(.*)/im)
        channels_rules = $2 #multiple channels @smart-bot on #channel1 #channel2 echo AAA
        data_text = $4
        channel_rules_name = ""
        channel_rules = ""
        channels_arr = channels_rules.scan(/<#(\w+)\|([^>]*)>/)
        if channels_arr.size == 0
          channels_arr = []
          channels_rules.scan(/([^\s]+)/).each do |cn|
            cna = cn.join.gsub("#", "")
            if @channels_name.key?(cna)
              channels_arr << [cna, @channels_name[cna]]
            else
              channels_arr << [@channels_id[cna], cna]
            end
          end
        else
          channels_arr.each do |row|
            row[0] = @channels_id[row[1]] if row[0] == ""
            row[1] = @channels_name[row[0]] if row[1] == ""
          end
        end

        # to be treated only on the bots of the requested channels
        channels_arr.each do |tcid, tcname|
          if @channel_id == tcid
            data.text = data_text
            typem = :on_call
            channel_rules = tcid
            channel_rules_name = tcname
            break
          elsif @bots_created.key?(@channel_id) and @bots_created[@channel_id][:extended].include?(tcname)
            data.text = data_text
            typem = :on_call
            channel_rules = @channel_id
            channel_rules_name = @channels_name[@channel_id]
            break
          end
        end
      elsif data.channel == @master_bot_id
        if config.on_master_bot #only to be treated on master bot channel
          typem = :on_master
        end
      elsif @bots_created.key?(data.channel)
        if @channel_id == data.channel #only to be treated by the bot on the channel
          typem = :on_bot
        end
      elsif data.channel[0] == "D" #Direct message
        get_rules_imported()
        if @rules_imported.key?("#{.team_id}_#{.name}") && @rules_imported["#{.team_id}_#{.name}"].key?(.name) and
           @bots_created.key?(@rules_imported["#{.team_id}_#{.name}"][.name])
          if @channel_id == @rules_imported["#{.team_id}_#{.name}"][.name]
            #only to be treated by the channel we are 'using'
            typem = :on_dm
          end
        elsif config.on_master_bot
          #only to be treated by master bot
          typem = :on_dm
        end
      elsif data.channel[0] == "C" or data.channel[0] == "G"
        #only to be treated on the channel of the bot. excluding running ruby
        if !config.on_master_bot and @bots_created.key?(@channel_id) and @bots_created[@channel_id][:extended].include?(@channels_name[data.channel]) and
           !data.text.match?(/^!?\s*(ruby|code)\s+/) and !data.text.match?(/^!?!?\s*(ruby|code)\s+/) and !data.text.match?(/^\^?\s*(ruby|code)\s+/)
          typem = :on_extended
        elsif config.on_master_bot and (data.text.match?(/^!?\s*(ruby|code)\s+/) or data.text.match?(/^!?!?\s*(ruby|code)\s+/) or data.text.match?(/^\^?\s*(ruby|code)\s+/))
          #or in case of running ruby, the master bot
          @bots_created.each do |k, v|
            if v.key?(:extended) and v[:extended].include?(@channels_name[data.channel])
              typem = :on_extended
              break
            end
          end
        end
        extended = false
        @bots_created.each do |k, v|
          if v.key?(:extended) and v[:extended].include?(@channels_name[data.channel])
            extended = true
            break
          end
        end
        if data.channel[0] == "G" and config.on_master_bot and !extended #private group
          typem = :on_pg
        elsif data.channel[0] == "C" and config.on_master_bot and !extended #public group
          typem = :on_pub
        end
      end
    end
    load "#{config.path}/rules/general_commands.rb" if File.exist?("#{config.path}/rules/general_commands.rb") and @datetime_general_commands != File.mtime("#{config.path}/rules/general_commands.rb")
    eval(File.new(config.path + config.rules_file).read) if !defined?(rules) and File.exist?(config.path + config.rules_file) and !config.rules_file.empty?
    unless typem == :dont_treat or .nil?
      if (Time.now - @last_activity_check) > TIMEOUT_LISTENING #every 30 minutes
        @last_activity_check = Time.now
        @listening.each do |k, v|
          unless k == :threads
            v.each do |kk, vv|
              if (Time.now - vv) > TIMEOUT_LISTENING
                if @listening[:threads].key?(kk) && @active_chat_gpt_sessions.key?(k) &&
                   @active_chat_gpt_sessions[k].key?(kk)
                  unreact :running, kk, channel: @listening[:threads][kk]
                  session_name = @active_chat_gpt_sessions[k][kk]
                  chatgpt_message = ":information_source: ChatGPT session has been terminated due to inactivity."
                  if !session_name.to_s.empty?
                    chatgpt_message += "\n\nIf you want to start it again on this thread call `chatgpt #{session_name}`"
                  end
                  respond chatgpt_message, @listening[:threads][kk], thread_ts: kk
                  @listening[:threads].delete(kk)
                end
                @listening[k].delete(kk)
              end
            end
            @listening.delete(k) if @listening[k].empty?
          end
        end
      end
      begin
        #user_info.id = data.user #todo: remove this line when slack issue with Wxxxx Uxxxx fixed
        data.user = .id  #todo: remove this line when slack issue with Wxxxx Uxxxx fixed
        team_id_user = "#{.team_id}_#{.name}"
        if data.thread_ts.to_s.empty?
          qdest = dest
        else
          qdest = data.thread_ts
        end
        if !answer(, qdest).empty?
          if data.text.match?(/\A\s*(Bye|Bæ|Good\sBye|Adiós|Ciao|Bless|Bless\sBless|Adeu)\s(#{@salutations.join("|")})\s*$/i)
            answer_delete(, qdest)
            command = data.text
          else
            command = answer(, qdest)
            @answer[team_id_user][qdest] = data.text
            @questions[team_id_user] = data.text # to be backwards compatible #todo remove it when 2.0
          end
        elsif @repl_sessions.key?(team_id_user) and data.channel == @repl_sessions[team_id_user][:dest] and
              ((@repl_sessions[team_id_user][:on_thread] and data.thread_ts == @repl_sessions[team_id_user][:thread_ts]) or
               (!@repl_sessions[team_id_user][:on_thread] and data.thread_ts.to_s == ""))
          if data.text.match(/^\s*```(.*)```\s*$/im)
            @repl_sessions[team_id_user][:command] = $1
          else
            @repl_sessions[team_id_user][:command] = data.text
          end
          command = "repl"
        else
          command = data.text
        end
        #when added special characters on the message
        if command.match(/\A\s*```(.*)```\s*\z/im)
          command = $1
        elsif command.size >= 2 and
              ((command[0] == "`" and command[-1] == "`") or (command[0] == "*" and command[-1] == "*") or (command[0] == "_" and command[-1] == "_"))
          command = command[1..-2]
        end

        #ruby file attached
        if !data.files.nil? and data.files.size == 1 and
           (command.match?(/^(ruby|code)\s*$/) or (command.match?(/^\s*$/) and data.files[0].filetype == "ruby") or
            (typem == :on_call and data.files[0].filetype == "ruby"))
          res = Faraday.new("https://files.slack.com", headers: { "Authorization" => "Bearer #{config[:token]}" }).get(data.files[0].url_private)
          command += " ruby" if command != "ruby"
          command = "#{command} #{res.body.to_s.force_encoding("UTF-8")}"
        end

        if typem == :on_call
          command = "!" + command unless command[0] == "!" or command.match?(/^\s*$/) or command[0] == "^"

          #todo: add pagination for case more than 1000 channels on the workspace
          channels = get_channels()
          channel_found = channels.detect { |c| c.name == channel_rules_name }
          members = get_channel_members(@channels_id[channel_rules_name]) unless channel_found.nil?
          if channel_found.nil?
            @logger.fatal "Not possible to find the channel #{channel_rules_name}"
          elsif channel_found.name == config.master_channel
            respond "You cannot use the rules from Master Channel on any other channel.", data.channel
          elsif @status != :on
            respond "The bot in that channel is not :on", data.channel
          elsif data.user == channel_found.creator or members.include?(data.user)
            process_first(, command, dest, channel_rules, typem, data.files, data.ts, data.thread_ts, data.routine, data.routine_name, data.routine_type, command_orig)
          else
            respond "You need to join the channel <##{channel_found.id}> to be able to use the rules.", data.channel
          end
        elsif config.on_master_bot and typem == :on_extended and
              command.size > 0 and command[0] != "-"
          # to run ruby only from the master bot for the case more than one extended
          process_first(, command, dest, @channel_id, typem, data.files, data.ts, data.thread_ts, data.routine, data.routine_name, data.routine_type, command_orig)
        elsif !config.on_master_bot and @bots_created[@channel_id].key?(:extended) and
              @bots_created[@channel_id][:extended].include?(@channels_name[data.channel]) and
              command.size > 0 and command[0] != "-"
          process_first(, command, dest, @channel_id, typem, data.files, data.ts, data.thread_ts, data.routine, data.routine_name, data.routine_type, command_orig)
        elsif (dest[0] == "D" or @channel_id == data.channel or data.user == config[:nick_id]) and
              command.size > 0 and command[0] != "-"
          process_first(, command, dest, data.channel, typem, data.files, data.ts, data.thread_ts, data.routine, data.routine_name, data.routine_type, command_orig)
          # if @botname on #channel_rules: do something
        elsif (typem == :on_pub or typem == :on_pg) and command.size > 0 and command[0] != "-"
          process_first(, command, dest, channel_rules, typem, data.files, data.ts, data.thread_ts, data.routine, data.routine_name, data.routine_type, command_orig)
        end
      rescue Exception => stack
        @logger.fatal stack
      end
    else
      if .nil? and data.user.to_s != ""
        @logger.warn "Pay attention there is no user on users with id #{data.user}"
      end
      if !config.on_master_bot and !dest.nil? and (data.channel == @master_bot_id or dest[0] == "D") and
         data.text.match?(/^\s*(!|!!|\^)?\s*bot\s+status\s*$/i) and @admin_users_id.include?(data.user)
        respond "ping from #{config.channel}", dest
      elsif !config.on_master_bot and !dest.nil? and data.user == config[:nick_id] and dest == @master_bot_id
        # to treat on other bots the status messages populated on master bot
        case data.text
        when /General message has been set\./i, /General message won't be displayed anymore./i
          sleep 2
          if File.exist?("#{config.path}/config_tmp.status")
            file_cts = IO.readlines("#{config.path}/config_tmp.status").join
            unless file_cts.to_s() == ""
              file_cts = eval(file_cts)
              if file_cts.is_a?(Hash) and file_cts.key?(:general_message)
                config.general_message = file_cts.general_message
              end
            end
          end
        when /From now on I'll be on maintenance status/i
          sleep 2
          if File.exist?("#{config.path}/config_tmp.status")
            file_cts = IO.readlines("#{config.path}/config_tmp.status").join
            unless file_cts.to_s() == ""
              file_cts = eval(file_cts)
              if file_cts.is_a?(Hash) and file_cts.key?(:on_maintenance)
                config.on_maintenance = file_cts.on_maintenance
                config.on_maintenance_message = file_cts.on_maintenance_message
              end
            end
          end
        when /From now on I won't be on maintenance/i
          sleep 2
          if File.exist?("#{config.path}/config_tmp.status")
            file_cts = IO.readlines("#{config.path}/config_tmp.status").join
            unless file_cts.to_s() == ""
              file_cts = eval(file_cts)
              if file_cts.is_a?(Hash) and file_cts.key?(:on_maintenance)
                config.on_maintenance = file_cts.on_maintenance
                config.on_maintenance_message = file_cts.on_maintenance_message
              end
            end
          end
        when /^Bot has been (closed|killed) by/i
          sleep 2
          get_bots_created()
        when /^Changed status on (.+) to :(.+)/i
          sleep 2
          get_bots_created()
        when /extended the rules from (.+) to be used on (.+)\.$/i
          sleep 2
          get_bots_created()
        when /removed the access to the rules of (.+) from (.+)\.$/i
          sleep 2
          get_bots_created()
        when /global shortcut added/
          sleep 2
          if File.exist?("#{config.path}/shortcuts/shortcuts_global.yaml")
            @shortcuts_global = YAML.load(File.read("#{config.path}/shortcuts/shortcuts_global.yaml"))
          end
        when /global shortcut deleted/
          sleep 2
          if File.exist?("#{config.path}/shortcuts/shortcuts_global.yaml")
            @shortcuts_global = YAML.load(File.read("#{config.path}/shortcuts/shortcuts_global.yaml"))
          end
        when /\AGame\s+over!\z/i
          sleep 2
          get_bots_created()
          if File.exist?("#{config.path}/config_tmp.status")
            file_cts = IO.readlines("#{config.path}/config_tmp.status").join
            unless file_cts.to_s() == ""
              file_cts = eval(file_cts)
              if file_cts.is_a?(Hash) and file_cts.key?(:exit_bot)
                config.exit_bot = file_cts.exit_bot
              end
              @status = :exit if config.exit_bot
            end
          end
          if @status == :exit
            @listening[:threads].each do |thread_ts, channel_thread|
              unreact :running, thread_ts, channel: channel_thread
              respond "ChatGPT session closed since SmartBot is going to be closed.\nCheck <##{@channels_id[config.status_channel]}>", channel_thread, thread_ts: thread_ts
            end
            @logger.info "Game over!"
            sleep 3
            exit!
          end
        end
      end
    end
    unless data.nil? or data.channel.nil? or data.channel.empty?
      @announcements_activity_after[data.channel] ||= 0
      @announcements_activity_after[data.channel] += 1
    end
  rescue Exception => stack
    @logger.fatal stack
  end
end

#unreact(emoji, ts = false, channel: Thread.current[:dest]) ⇒ Object

list of available emojis: https://www.webfx.com/tools/emoji-cheat-sheet/ unreact(:thumbsup) ts: can be true, false or a specific ts



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# File 'lib/slack/smart-bot/comm/unreact.rb', line 5

def unreact(emoji, ts=false, channel: Thread.current[:dest])
  begin
    if ts.is_a?(TrueClass) or ts.is_a?(FalseClass)
      parent = ts
      ts = nil
    else
      parent = false
    end
    if ts.nil?
      if parent or Thread.current[:ts].to_s == ''
        ts = Thread.current[:thread_ts]
      else
        ts = Thread.current[:ts]
      end
    end
    if ts.nil?
      @logger.warn 'unreact method no ts supplied'
    else
      begin
        client.web_client.reactions_remove(channel: channel, name: emoji, timestamp: ts) unless config.simulate
      rescue Exception => stack
        @logger.warn stack
      end
    end
  rescue Exception => stack
    @logger.warn stack
  end
end

#update(channel, ts, text) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
# File 'lib/slack/smart-bot/comm/update.rb', line 2

def update(channel, ts, text)
  result = true
  begin
    resp = client.web_client.chat_update(channel: channel, as_user: true, ts: ts, text: text)
    result = resp.ok.to_s == 'true'
  rescue Exception => exc
    result = false
    @logger.fatal exc.inspect
  end
  return result
end

#update_access_channelsObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/slack/smart-bot/utils/update_access_channels.rb', line 3

def update_access_channels()
  require 'yaml'
  access_ch_file = "#{config.path}/rules/#{@channel_id}/access_channels"

  if File.exist?("#{access_ch_file}.rb") #backwards compatible
    file_conf = IO.readlines("#{access_ch_file}.rb").join
    if file_conf.to_s() == ""
      @access_channels = {}
    else
      @access_channels = eval(file_conf)
    end
    File.open("#{access_ch_file}.yaml", 'w') {|file| file.write(@access_channels.to_yaml) }
    File.delete("#{access_ch_file}.rb")
  end

  File.open("#{access_ch_file}.yaml", 'w') {|file|
    file.flock(File::LOCK_EX)
    file.write(@access_channels.to_yaml) 
    file.flock(File::LOCK_UN)
  }
end

#update_admins_channelsObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# File 'lib/slack/smart-bot/utils/update_admins_channels.rb', line 3

def update_admins_channels()

  require 'yaml'
  admins_file = "#{config.path}/rules/#{@channel_id}/admins_channels.yaml"

  if File.exist?(admins_file.gsub(".yaml", ".rb")) #backwards compatible
    file_conf = IO.readlines(admins_file.gsub(".yaml", ".rb")).join
    if file_conf.to_s() == ""
      @admins_channels = {}
    else
      @admins_channels = eval(file_conf)
    end
    File.open(admins_file, 'w') {|file| file.write(@admins_channels.to_yaml) }
    File.delete(admins_file.gsub(".yaml", ".rb"))
  end

  File.open(admins_file, 'w') {|file|
    file.flock(File::LOCK_EX)
    file.write(@admins_channels.to_yaml) 
    file.flock(File::LOCK_UN)
  }
end

#update_bots_fileObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/slack/smart-bot/utils/update_bots_file.rb', line 2

def update_bots_file
  bots_file = config.file_path.gsub(".rb", "_bots.yaml")

  if File.exist?(config.file_path.gsub(".rb", "_bots.rb")) #backwards compatible
    file_conf = IO.readlines(config.file_path.gsub(".rb", "_bots.rb")).join
    if file_conf.to_s() == ""
      @bots_created = {}
    else
      @bots_created = eval(file_conf)
    end      
    File.open(bots_file, 'w') {|file| 
      file.flock(File::LOCK_EX)
      file.write(@bots_created.to_yaml) 
      file.flock(File::LOCK_UN)
    }
    File.delete(config.file_path.gsub(".rb", "_bots.rb"))
  else
    #not possible to use @bots_created.deep_copy since one of the fields contains a thread
    bots_created = {}
    @bots_created.each do |k,v|
      bots_created[k] = v.dup
      bots_created[k][:thread] = ''
    end
    File.open(bots_file, 'w') {|file|
      file.flock(File::LOCK_EX)
      file.write(bots_created.to_yaml) 
      file.flock(File::LOCK_UN)
    }
  end
end

#update_message(user, typem, url, text) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: update message URL TEXT helpadmin: It will update the SmartBot message supplied 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 helpadmin: command_id: :update_message helpadmin:



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# File 'lib/slack/smart-bot/commands/on_bot/admin_master/update_message.rb', line 9

def update_message(user, typem, url, text)
  save_stats(__method__)
  channel, ts = url.scan(/\/archives\/(\w+)\/(\w\d+)/)[0]
  if config.team_id_masters.include?("#{user.team_id}_#{user.name}") and typem==:on_dm and !channel.nil? #master admin user
    ts = "#{ts[0..-7]}.#{ts[-6..-1]}"
    succ = update(channel, ts, text)
    if succ
      react :heavy_check_mark
    else
      react :x
    end
  else
    respond "Only master admin users on a private conversation with the SmartBot can update SmartBot messages"
  end
end

#update_openai_sessions(session_name = '', team_id: '', user_name: '') ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/slack/smart-bot/utils/update_openai_sessions.rb', line 2

def update_openai_sessions(session_name='', team_id: '', user_name: '')
  require 'yaml'
  user_name = Thread.current[:user].name if user_name == ''
  team_id = Thread.current[:user].team_id if team_id == ''
  team_id_user = team_id + "_" + user_name

  unless Dir.exist?("#{config.path}/openai/#{team_id}")
    Dir.mkdir("#{config.path}/openai/#{team_id}")
  end

  unless Dir.exist?("#{config.path}/openai/#{team_id}/#{user_name}")
    Dir.mkdir("#{config.path}/openai/#{team_id}/#{user_name}")
  end
  file_name = File.join(config.path, "openai/#{team_id}", "o_#{user_name}.yaml")
  data = @open_ai[team_id_user].deep_copy
  if data.key?(:chat_gpt) and data[:chat_gpt].key?(:sessions)
    data[:chat_gpt][:sessions].delete('') #temporary session
  end
  File.open(file_name, 'w') {|file|
    file.flock(File::LOCK_EX)
    file.write(Utils::Encryption.encrypt(data.to_yaml, config))
    file.flock(File::LOCK_UN)
  }
  @datetime_open_ai_file[file_name] = File.mtime(file_name)
  if session_name.to_s != ''
    if !@open_ai[team_id_user][:chat_gpt][:sessions].key?(session_name) #delete file if session is not longer available
      if File.exist?(File.join(config.path, "openai/#{team_id}", "#{user_name}/session_#{session_name}.txt"))
        File.delete(File.join(config.path, "openai/#{team_id}", "#{user_name}/session_#{session_name}.txt"))
      end
    else
      file_name = File.join(config.path, "openai/#{team_id}", "#{user_name}/session_#{session_name}.txt")
      content_txt = ""
      content = []
      @ai_gpt[team_id_user][session_name].each do |line|
        content << line.to_json
      end
      content_txt = content.join("\n").force_encoding("UTF-8")
      File.open(file_name, 'w') {|file|
        file.flock(File::LOCK_EX)
        file.write(Utils::Encryption.encrypt(content_txt, config))
        file.flock(File::LOCK_UN)
      }
    end
  end
end

#update_personal_settings(user_personal_settings = nil) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/slack/smart-bot/utils/update_personal_settings.rb', line 2

def update_personal_settings(user_personal_settings=nil)
  require 'yaml'
  unless user_personal_settings.nil?
    get_personal_settings()
    @personal_settings.merge!(user_personal_settings)
  end
  user = Thread.current[:user].dup
  team_id = user.team_id
  team_id_user = Thread.current[:team_id_user]

  unless Dir.exist?("#{config.path}/personal_settings/#{team_id}")
    Dir.mkdir("#{config.path}/personal_settings/#{team_id}")
  end

  personal_settings_file = File.join(config.path, "personal_settings/#{team_id}", "ps_#{user.name}.yaml")

  File.open(personal_settings_file, 'w') {|file|
    file.flock(File::LOCK_EX)
    file.write(Utils::Encryption.encrypt(@personal_settings[team_id_user].to_yaml, config))
    file.flock(File::LOCK_UN)
  }
  get_personal_settings() #to update the @personal_settings_hash
  @datetime_personal_settings_file[personal_settings_file] = File.mtime(personal_settings_file)
end

#update_repls(channel = @channel_id) ⇒ Object



2
3
4
5
6
7
8
9
10
# File 'lib/slack/smart-bot/utils/update_repls.rb', line 2

def update_repls(channel = @channel_id)
  require 'yaml'
  repl_file = "#{config.path}/repl/repls_#{channel}.yaml"
  File.open(repl_file, 'w') {|file|
    file.flock(File::LOCK_EX)
    file.write(@repls.to_yaml) 
    file.flock(File::LOCK_UN)
  }
end

#update_routines(channel = @channel_id) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/slack/smart-bot/utils/update_routines.rb', line 3

def update_routines(channel = @channel_id)

  require 'yaml'
  routines_file = "#{config.path}/routines/routines_#{channel}.yaml"

  routines = {}
  @routines.each do |k,v|
    routines[k]={}
    v.each do |kk,vv|
      routines[k][kk] = vv.dup
      routines[k][kk][:thread]=""
    end
  end
  File.open(routines_file, 'w') {|file|
    file.flock(File::LOCK_EX)
    file.write(routines.to_yaml) 
    file.flock(File::LOCK_UN)
  }
end

#update_rules_importedObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/slack/smart-bot/utils/update_rules_imported.rb', line 3

def update_rules_imported
  require 'yaml'
  file_path = "#{config.path}/rules/rules_imported"
  if File.exist?("#{file_path}.rb") #backwards compatible
    file_conf = IO.readlines("#{file_path}.rb").join
    if file_conf.to_s() == ""
      @rules_imported = {}
    else
      @rules_imported = eval(file_conf)
    end
    File.open("#{file_path}.yaml", 'w') {|file| file.write(@rules_imported.to_yaml) }
    File.delete("#{file_path}.rb")
  end

  File.open("#{file_path}.yaml", 'w') {|file|
    file.flock(File::LOCK_EX)
    file.write(@rules_imported.to_yaml) 
    file.flock(File::LOCK_UN)
  }
end

#update_shortcuts_fileObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/slack/smart-bot/utils/update_shortcuts_file.rb', line 2

def update_shortcuts_file
  require 'yaml'
  sc_file = "#{config.path}/shortcuts/#{config.shortcuts_file}"
  File.open(sc_file, 'w') {|file|
    file.flock(File::LOCK_EX)
    file.write(@shortcuts.to_yaml) 
    file.flock(File::LOCK_UN)
  }

  if config.on_master_bot
    sc_file = "#{config.path}/shortcuts/shortcuts_global.yaml"
    File.open(sc_file, 'w') {|file|
      file.flock(File::LOCK_EX)
      file.write(@shortcuts_global.to_yaml) 
      file.flock(File::LOCK_UN)
    }
  end
end

#update_teams(team = nil) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/slack/smart-bot/utils/update_teams.rb', line 2

def update_teams(team=nil)
  require 'yaml'
  if team.nil?
    teams = @teams.keys
  else
    get_teams()
    @teams.merge!(team)
    teams = team.keys      
  end

  teams.each do |team|
    team_file = File.join(config.path, "teams", "t_#{team}.yaml")
    File.open(team_file, 'w') {|file|
      file.flock(File::LOCK_EX)
      file.write(Utils::Encryption.encrypt(@teams[team].to_yaml, config))
      file.flock(File::LOCK_UN)
    }
    @datetime_teams_file[team_file] = File.mtime(team_file)
  end
end

#update_vacations(vacation = nil) ⇒ Object



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# File 'lib/slack/smart-bot/utils/update_vacations.rb', line 2

def update_vacations(vacation=nil)
  require 'yaml'
  unless vacation.nil?
    get_vacations()
    @vacations.merge!(vacation)
  end
  user = Thread.current[:user]
  team_id_user = Thread.current[:team_id_user]
  #create folder if doesn't exist
  FileUtils.mkdir_p(File.join(config.path, "vacations/#{user.team_id}")) unless File.exist?(File.join(config.path, "vacations/#{user.team_id}"))
  vacations_file = File.join(config.path, "vacations/#{user.team_id}", "v_#{user.name}.yaml")

  File.open(vacations_file, 'w') {|file|
    file.flock(File::LOCK_EX)
    file.write(Utils::Encryption.encrypt(@vacations[team_id_user].to_yaml, config))
    file.flock(File::LOCK_UN)
  }
  @datetime_vacations_file[vacations_file] = File.mtime(vacations_file)
end

#upgrade_to_use_team_idsObject



2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/slack/smart-bot/utils/upgrade_to_use_team_ids.rb', line 2

def upgrade_to_use_team_ids()
  team_id = config.team_id

  if Dir.exist?("#{config.path}/rules/") #admins_channels.yaml and access_channels.yaml
    files_updated = []
    Dir.glob(config.path + "/rules/**/*").select { |i| i[/admins_channels.yaml$/] }.each do |f|
      t = YAML.load_file(f)
      n = {}
      t.each do |k, v|
        if v.length > 0
          # do it only if it is not already a team_id: /^[A-Z0-9]{7,11}_/
          # if it matches the pattern, it is already with a team_id
          # if it doesn't match the pattern, it is not with a team_id
          n[k] = []
          v.each do |m|
            if !m.to_s.match?(/^[A-Z0-9]{7,11}_/)
              n[k] << team_id.to_s + "_" + m
              files_updated << f unless files_updated.include?(f)
            else
              n[k] << m
            end
          end
        else
          n[k] = v
        end
      end
      File.open(f, "w") { |f| f.write n.to_yaml } if files_updated.include?(f)
    end
    @logger.info "Updated admins_channels.yaml files to use team ids" unless files_updated.empty?

    files_updated = []
    Dir.glob(config.path + "/rules/**/*").select { |i| i[/access_channels.yaml$/] }.each do |f|
      t = YAML.load_file(f)
      n = {}
      t.each do |k, v|
        n[k] = {}
        v.each do |k2, v2|
          if v2.length > 0
            # do it only if it is not already a team_id: /^[A-Z0-9]{7,11}_/
            # if it matches the pattern, it is already with a team_id
            # if it doesn't match the pattern, it is not with a team_id
            n[k][k2] = []
            v2.each do |m|
              if !m.to_s.match?(/^[A-Z0-9]{7,11}_/)
                n[k][k2] << team_id.to_s + "_" + m
                files_updated << f unless files_updated.include?(f)
              else
                n[k][k2] << m
              end
            end
          else
            n[k][k2] = v2
          end
        end
      end
      File.open(f, "w") { |f| f.write n.to_yaml } if files_updated.include?(f)

    end
    @logger.info "Updated access_channels.yaml files to use team ids" unless files_updated.empty?

    files_updated = []

    Dir.glob(config.path + "/rules/*").select { |i| i[/rules_imported.yaml$/] }.each do |f|
      t = YAML.load_file(f)
      n = {}
      t.each do |k, v|
        if !k.to_s.match?(/^[A-Z0-9]{7,11}_/)
          n["#{team_id.to_s}_#{k}"] = v
          files_updated << f unless files_updated.include?(f)
        else
          n[k] = v
        end
      end
      File.open(f, "w") { |f| f.write n.to_yaml } if files_updated.include?(f)
    end
    @logger.info "Updated rules_imported.yaml files to use team ids" unless files_updated.empty?
  end

  if Dir.exist?("#{config.path}/teams/")
    files_updated = []
    #todo: do it also for deleted memos files
    Dir.entries("#{config.path}/teams/").select { |i| i[/\.yaml$/] }.each do |f|
      t = YAML.load(Utils::Encryption.decrypt(File.read("#{config.path}/teams/#{f}"), config))
      t[:members].each do |k, v|
        n = []
        v.each do |m|
          if !m.to_s.match?(/^[A-Z0-9]{7,11}_/)
            n << team_id.to_s + "_" + m
            files_updated << f unless files_updated.include?(f)
          else
            n << m
          end
        end
        t[:members][k] = n
      end

      # update memos.user and memos.comments.user_name
      if t.key?(:memos)
        t[:memos].each do |m|
          if !m[:user].to_s.match?(/^[A-Z0-9]{7,11}_/)
            m[:user] = team_id.to_s + "_" + m[:user]
            files_updated << f unless files_updated.include?(f)
          end
          if m.key?(:comments)
            m[:comments].each do |c|
              if !c[:user_name].to_s.match?(/^[A-Z0-9]{7,11}_/)
                c[:user_name] = team_id.to_s + "_" + c[:user_name]
                files_updated << f unless files_updated.include?(f)
              end
            end
          end
        end
      end

      if !t[:user].to_s.match?(/^[A-Z0-9]{7,11}_/)
        t[:user] = team_id.to_s + "_" + t[:user]
        files_updated << f unless files_updated.include?(f)
      end
      if !t[:creator].to_s.match?(/^[A-Z0-9]{7,11}_/)
        t[:creator] = team_id.to_s + "_" + t[:creator]
        files_updated << f unless files_updated.include?(f)
      end
      if files_updated.include?(f)
        File.open("#{config.path}/teams/#{f}", "w") { |file|
          file.flock(File::LOCK_EX)
          file.write(Utils::Encryption.encrypt(t.to_yaml, config))
          file.flock(File::LOCK_UN)
        }
      end
    end
    @logger.info "Updated teams files to use team ids" unless files_updated.empty?
  end

  #add team_id to the user_name: team_id_user_name, exclude the key :all
  if Dir.exist?("#{config.path}/shortcuts/")
    files_updated = []
    Dir.entries("#{config.path}/shortcuts/").select { |i| i[/\.yaml$/] }.each do |f|
      t = YAML.load_file("#{config.path}/shortcuts/#{f}")
      n = {}
      t.each do |k, v|
        if k != :all and !k.to_s.match?(/^[A-Z0-9]{7,11}_/)
          n[team_id.to_s + "_" + k.to_s] = t[k]
          files_updated << f unless files_updated.include?(f)
        else
          n[k] = t[k]
        end
      end
      File.open("#{config.path}/shortcuts/#{f}", "w") { |f| f.write n.to_yaml } if files_updated.include?(f)
    end
    @logger.info "Updated shortcuts files to use team ids" unless files_updated.empty?
  end

  #repl files are yaml
  if Dir.exist?("#{config.path}/repl/")
    files_updated = []
    Dir.entries("#{config.path}/repl/").select { |i| i[/\.yaml$/] }.each do |f|
      t = YAML.load_file("#{config.path}/repl/#{f}")
      t.each do |k, v|
        if v[:creator_team_id].to_s == ""
          v[:creator_team_id] = team_id
          files_updated << f unless files_updated.include?(f)
        end
      end
      File.open("#{config.path}/repl/#{f}", "w") { |f| f.write t.to_yaml } if files_updated.include?(f)
    end
    @logger.info "Updated repl files to use team ids" unless files_updated.empty?
  end

  if Dir.exist?("#{config.path}/routines/")
    files_updated = []
    Dir.entries("#{config.path}/routines/").select { |i| i[/\.yaml$/] }.each do |f|
      t = YAML.load_file("#{config.path}/routines/#{f}")
      t.each do |k, v|
        v.each do |k2, v2|
          if v2[:creator_team_id].to_s == ""
            v2[:creator_team_id] = team_id
            files_updated << f unless files_updated.include?(f)
          end
        end
      end
      File.open("#{config.path}/routines/#{f}", "w") { |f| f.write t.to_yaml } if files_updated.include?(f)
    end
    @logger.info "Updated routines files to use team ids" unless files_updated.empty?
  end

  #shares files are csv
  if Dir.exist?("#{config.path}/shares/")
    Dir.entries("#{config.path}/shares/").select { |i| i[/\.csv$/] }.each do |f|
      #verify if the file has the team_id already, in that case the number of columns should be 10
      t = CSV.table("#{config.path}/shares/#{f}")
      if t.headers.length == 8
        t = CSV.table("#{config.path}/shares/#{f}", headers: ["share_id", "user_deleted", "user_created", "date", "time", "type", "to_channel", "condition"])
        #save it in this order:
        new_headers = ["share_id", "user_team_id_deleted", "user_deleted", "user_team_id_created", "user_created", "date", "time", "type", "to_channel", "condition"]
        new_t = []
        t.each do |m|
          if m[:user_deleted].to_s == ""
            user_team_id_deleted = ""
          else
            user_team_id_deleted = team_id
          end
          new_t << [m[:share_id], user_team_id_deleted, m[:user_deleted], team_id, m[:user_created], m[:date], m[:time], m[:type], m[:to_channel], m[:condition]]
        end
        CSV.open("#{config.path}/shares/#{f}", "wb") do |csv|
          new_t.each do |row|
            csv << row
          end
        end
        @logger.info "Updated shares to use team ids"
      end
    end
  end

  #announcements
  if Dir.exist?("#{config.path}/announcements/")
    Dir.entries("#{config.path}/announcements/").select { |i| i[/\.csv$/] }.each do |f|
      #verify if the file has the team_id already, in that case the number of columns should be 9
      t = CSV.table("#{config.path}/announcements/#{f}")
      if t.headers.length == 7
        t = CSV.table("#{config.path}/announcements/#{f}", headers: ["message_id", "user_deleted", "user_created", "date", "time", "type", "message"])
        #save it in this order:
        new_headers = ["message_id", "user_team_id_deleted", "user_deleted", "user_team_id_created", "user_created", "date", "time", "type", "message"]
        new_t = []
        t.each do |m|
          if m[:user_deleted].to_s == ""
            user_team_id_deleted = ""
          else
            user_team_id_deleted = team_id
          end
          new_t << [m[:message_id], user_team_id_deleted, m[:user_deleted], team_id, m[:user_created], m[:date], m[:time], m[:type], m[:message]]
        end
        CSV.open("#{config.path}/announcements/#{f}", "wb") do |csv|
          new_t.each do |row|
            csv << row
          end
        end

        @logger.info "Updated announcements to use team ids"
      end
    end
  end

  if Dir.exist?("#{config.path}/vacations/") and !Dir.exist?(config.path + "/vacations/" + team_id.to_s)
    FileUtils.mkdir_p(config.path + "/vacations/" + team_id.to_s)
    files_updated = []
    Dir.glob(config.path + "/vacations/*").select { |i| i[/\.yaml$/] }.each do |f|
      FileUtils.mv(f, config.path + "/vacations/" + team_id.to_s)
      files_updated << f
    end
    @logger.info "Updated vacations to use team ids. All moved to #{config.path}/vacations/#{team_id}/" unless files_updated.empty?
  end

  if Dir.exist?("#{config.path}/openai/") and !Dir.exist?(config.path + "/openai/" + team_id.to_s)
    FileUtils.mkdir_p(config.path + "/openai/" + team_id.to_s)
    files_updated = []
    Dir.glob(config.path + "/openai/*").each do |file_or_dir|
      next if file_or_dir == config.path + "/openai/" + team_id.to_s
      FileUtils.mv(file_or_dir, config.path + "/openai/" + team_id.to_s)
      files_updated << file_or_dir
    end
    @logger.info "Updated openai to use team ids. All moved to #{config.path}/openai/#{team_id}/" unless files_updated.empty?
  end

  if Dir.exist?("#{config.path}/personal_settings/") and !Dir.exist?(config.path + "/personal_settings/" + team_id.to_s)
    FileUtils.mkdir_p(config.path + "/personal_settings/" + team_id.to_s)
    files_updated = []
    Dir.glob(config.path + "/personal_settings/*").each do |file_or_dir|
      next if file_or_dir == config.path + "/personal_settings/" + team_id.to_s
      FileUtils.mv(file_or_dir, config.path + "/personal_settings/" + team_id.to_s)
      files_updated << file_or_dir
    end
    @logger.info "Updated personal_settings to use team ids. All moved to #{config.path}/personal_settings/#{team_id}/" unless files_updated.empty?
  end
end

#use_rules(dest, channel, user, dchannel) ⇒ Object

help: ---------------------------------------------- help: use rules from CHANNEL help: use rules CHANNEL help: use CHANNEL help: it will use the rules from the specified channel. help: you need to be part of that channel to be able to use the rules. help: help: command_id: :use_rules help:



12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/slack/smart-bot/commands/on_bot/general/use_rules.rb', line 12

def use_rules(dest, channel, user, dchannel)
  save_stats(__method__)
  get_bots_created()
  if has_access?(__method__, user)
    #todo: add pagination for case more than 1000 channels on the workspace
    channels = get_channels()
    channel.gsub!('#','') # for the case the channel name is in plain text including #
    channel = @channels_name[channel] if @channels_name.key?(channel)
    channel_found = channels.detect { |c| c.name == channel }
    get_channels_name_and_id() unless @channels_id.key?(channel)
    members = get_channel_members(@channels_id[channel]) unless channel_found.nil? or !@channels_id.key?(channel)

    if channel_found.nil? or !@channels_id.key?(channel)
      respond "The channel you are trying to use doesn't exist or cannot be found.", dest
    elsif channel_found.name == config.master_channel
      respond "You cannot use the rules from Master Channel on any other channel.", dest
    elsif !@bots_created.key?(@channels_id[channel])
      respond "There is no bot running on that channel.", dest
    elsif @bots_created.key?(@channels_id[channel]) and @bots_created[@channels_id[channel]][:status] != :on
      respond "The bot in that channel is not :on", dest
    else
      if user.id == channel_found.creator or members.include?(user.id)
        team_id_user = "#{user.team_id}_#{user.name}"
        @rules_imported[team_id_user] = {} unless @rules_imported.key?(team_id_user)
        if dest[0] == "C" or dest[0] == "G" #todo: take in consideration bots that are not master
          @rules_imported[team_id_user][dchannel] = channel_found.id
        else
          @rules_imported[team_id_user][user.name] = channel_found.id
        end
        sleep 0.5
        update_rules_imported()
        respond "I'm using now the rules from <##{channel_found.id}>", dest

        def git_project() "" end
        def project_folder() "" end
      else
        respond "You need to join the channel <##{channel_found.id}> to be able to use the rules.", dest
      end
    end
  end
end

#whats_new(user, dest, dchannel, from, display_name) ⇒ Object

help: ---------------------------------------------- help: What's new help: It will display the last user changes on Slack Smart Bot help: command_id: :whats_new help:



8
9
10
11
12
13
14
15
16
17
18
# File 'lib/slack/smart-bot/commands/on_bot/general/whats_new.rb', line 8

def whats_new(user, dest, dchannel, from, display_name)
  if @status == :on
    save_stats(__method__)
    whats_new_file = (__FILE__).gsub(/lib\/slack\/smart-bot\/commands\/on_bot\/general\/whats_new\.rb$/, "whats_new.txt")
    whats_new = File.read(whats_new_file)
    whats_new.split(/^\-\-\-\-\-\-+$/).each do |msg|
        respond msg
        sleep 0.3
    end
  end
end

#where_smartbot(user) ⇒ Object

helpmaster: ---------------------------------------------- helpmaster: where is smartbot? helpmaster: which channels smartbot? helpmaster: where is a member smartbot? helpmaster: It will list all channels where the smartbot is a member. helpmaster: command_id: :where_smartbot helpmaster:



9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/slack/smart-bot/commands/on_master/where_smartbot.rb', line 9

def where_smartbot(user)
  #todo: add tests
  save_stats(__method__)
  if has_access?(__method__, user)
    channels = get_channels(bot_is_in: true)
    message = []
    extended = @bots_created.values.extended.flatten
    channels.each do |c|
      type = ''
      unless c.id[0] == "D"
          if @bots_created.key?(c.id)
              type = '_`(SmartBot)`_'
          elsif c.id == @master_bot_id
              type = '_`(Master)`_'
          elsif extended.include?(c.name)
              @bots_created.each do |bot,values|
                  if values.extended.include?(c.name)
                      type += "_`(Extended from ##{values.channel_name})`_ "
                  end
              end
          end
        if c.is_private?
          message << "#{c.id}: *##{c.name}* #{type}"
        else
          message << "#{c.id}: *<##{c.id}>* #{type}"
        end
      end
    end
    message.sort!
    respond "*<@#{config.nick_id}> is a member of:*\n\n#{message.join("\n")}"
  end
end