Class: SlackSmartBot

Inherits:
Object
  • Object
show all
Defined in:
lib/slack-smart-bot.rb,
lib/slack/smart-bot/listen.rb,
lib/slack/smart-bot/process.rb,
lib/slack/smart-bot/comm/ask.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/utils/decrypt.rb,
lib/slack/smart-bot/utils/encrypt.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/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/save_stats.rb,
lib/slack/smart-bot/comm/get_presence.rb,
lib/slack/smart-bot/utils/save_status.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_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/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/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/get_rules_imported.rb,
lib/slack/smart-bot/commands/general/add_team.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/commands/general/add_admin.rb,
lib/slack/smart-bot/commands/general/ping_team.rb,
lib/slack/smart-bot/commands/general/see_teams.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/utils/create_routine_thread.rb,
lib/slack/smart-bot/utils/encryption_get_key_iv.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/delete_team.rb,
lib/slack/smart-bot/commands/general/deny_access.rb,
lib/slack/smart-bot/commands/general/update_team.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/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/on_bot/see_shortcuts.rb,
lib/slack/smart-bot/commands/on_master/create_bot.rb,
lib/slack/smart-bot/commands/general/add_memo_team.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/commands/general/see_memos_team.rb,
lib/slack/smart-bot/commands/general/share_messages.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/commands/general/remove_vacation.rb,
lib/slack/smart-bot/commands/general/see_command_ids.rb,
lib/slack/smart-bot/commands/general/set_memo_status.rb,
lib/slack/smart-bot/commands/general/add_announcement.rb,
lib/slack/smart-bot/commands/general/delete_memo_team.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/commands/general/see_announcements.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/general/see_vacations_team.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/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/commands/on_bot/general/suggest_command.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/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/on_bot/admin_master/delete_message.rb,
lib/slack/smart-bot/commands/on_bot/admin_master/update_message.rb,
lib/slack/smart-bot/commands/on_master/admin/kill_bot_on_channel.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/on_master/admin_master/set_general_message.rb,
lib/slack/smart-bot/commands/on_master/admin_master/publish_announcements.rb

Constant Summary collapse

VERSION =
version

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ SlackSmartBot

Returns a new instance of SlackSmartBot.



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
# File 'lib/slack-smart-bot.rb', line 34

def initialize(config)
  if config.key?(:path) and config[:path] != ''
    config.path.chop! if config.path[-1]=="/"
  else
    config[:path] = '.'
  end
  config[:silent] = false unless config.key?(:silent)
  config[:testing] = false unless config.key?(:testing)
  config[:simulate] = false unless config.key?(:simulate)
  config[:stats] = false unless config.key?(:stats)
  config[:allow_access] = Hash.new unless config.key?(:allow_access)
  config[:on_maintenance] = false unless config.key?(:on_maintenance)
  config[:on_maintenance_message] = "Sorry I'm on maintenance so I cannot attend your request." unless config.key?(:on_maintenance_message)
  config[:general_message] = "" unless config.key?(:general_message)
  config[:logrtm] = false unless config.key?(:logrtm)
  config[:status_channel] = 'smartbot-status' unless config.key?(:status_channel)
  config[:stats_channel] = 'smartbot-stats' unless config.key?(:stats_channel)

  config[:jira] = { host: '', user: '', password: '' } unless config.key?(:jira) and config[:jira].key?(:host) and config[:jira].key?(:user) and config[:jira].key?(:password)
  config[:jira][:host] = "https://#{config[:jira][:host]}" unless config[:jira][:host] == '' or config[:jira][:host].match?(/^http/)
  config[:github] = {token: '' } unless config.key?(:github) and config[:github].key?(:token)
  config[:github][:host] ||= "https://api.github.com"
  config[:github][:host] = "https://#{config[:github][:host]}" unless config[:github][:host] == '' or config[:github][:host].match?(/^http/)
  config[:public_holidays] = { api_key: '' } unless config.key?(:public_holidays) and config[:public_holidays].key?(:api_key)
  config[:public_holidays][:host] ||= "https://calendarific.com"
  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")
  File.delete("#{config.path}/config_tmp.status") if File.exist?("#{config.path}/config_tmp.status")

  config.masters = MASTER_USERS if config.masters.to_s=='' and defined?(MASTER_USERS)
  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.to_s!=''
    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
  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.key?(:token) or config.token.to_s == '') and !config.simulate
    abort "You need to supply a valid token key on the settings. key: :token"
  elsif !config.key?(:masters) or !config.masters.is_a?(Array) or config.masters.size == 0
    abort "You need to supply a masters array on the settings containing the user names of the master admins. key: :masters"
  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
  
  config_log = config.dup
  config_log.delete(:token)
  config_log.delete(:user_token)
  @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
    end
  else
    self.client_user = 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

  @listening = Hash.new()

  @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 = (Date.today - 1)
  @announcements_activity_after = Hash.new()
  @public_holidays = Hash.new()
  @loops = Hash.new()

  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.admins.each do |au|
       = ("@#{au}")
      @admin_users_id << .user.id
      if config.masters.include?(au)
        @master_admin_users_id << .user.id
      end
      sleep 1
    end
    (config.masters-config.admins).each do |au|
       = ("@#{au}")
      @master_admin_users_id << .user.id
      sleep 1
    end
  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()

  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.



25
26
27
# File 'lib/slack-smart-bot.rb', line 25

def channel_id
  @channel_id
end

#clientObject

Returns the value of attribute client.



24
25
26
# File 'lib/slack-smart-bot.rb', line 24

def client
  @client
end

#client_userObject

Returns the value of attribute client_user.



24
25
26
# File 'lib/slack-smart-bot.rb', line 24

def client_user
  @client_user
end

#configObject

Returns the value of attribute config.



24
25
26
# File 'lib/slack-smart-bot.rb', line 24

def config
  @config
end

#master_bot_idObject (readonly)

Returns the value of attribute master_bot_id.



25
26
27
# File 'lib/slack-smart-bot.rb', line 25

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
# 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
    channels = get_channels()
    channel_found = channels.detect { |c| c.id == channel }
    if !channel_found.nil? and channel_found.creator.to_s != ''
      creator_info = @users.select{|u| u.id == channel_found.creator or (u.key?(:enterprise_user) and u.enterprise_user.id == channel_found.creator)}[-1]
      admins << creator_info.name
    end
    if Thread.current[:typem] == :on_bot or Thread.current[:typem] == :on_master
      admins << config.admins.dup
    end
    if @admins_channels.key?(channel) and @admins_channels[channel].size > 0
      admins << @admins_channels[channel]
    end
    admins.flatten!
    admins.uniq!
    admins.delete(nil)
    if admins.include?(user.name)
      admin_info = @users.select{|u| u.id == admin_user or (u.key?(:enterprise_user) and u.enterprise_user.id == admin_user)}[-1]
      if admins.include?(admin_info.name)
        messages << "This user is already an admin of this channel."
      else
        @admins_channels[channel] ||= []
        @admins_channels[channel] << admin_info.name
        update_admins_channels()
        messages << "The user is an admin of this channel from now on."
        admins << 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_deleted', '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_deleted', 'user_created', 'date', 'time', 'type', 'message'])
      num = 1
      @announcements[channel] = t
    else
      num = @announcements[channel][:message_id].max + 1
    end
    values = [num, '', 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_memo_team(user, privacy, team_name, topic, type, message) ⇒ 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
# File 'lib/slack/smart-bot/commands/general/add_memo_team.rb', line 2

def add_memo_team(user, privacy, team_name, topic, type, message)
  save_stats(__method__)

  get_teams()
  if @teams.key?(team_name.to_sym)
    assigned_members = @teams[team_name.to_sym].members.values.flatten
    assigned_members.uniq!
    all_team_members = assigned_members.dup
    team_members = []
    if @teams[team_name.to_sym].channels.key?("members")
      @teams[team_name.to_sym].channels["members"].each do |ch|
        get_channels_name_and_id() unless @channels_id.key?(ch)
        tm = get_channel_members(@channels_id[ch])
        tm.each do |m|
           = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) }[-1]
          team_members << .name unless .is_app_user or .is_bot
        end
      end
    end
    team_members.flatten!
    team_members.uniq!
    all_team_members += team_members
    all_team_members.uniq!
  end
  if type == "jira"
    able_to_connect_jira = false
    begin
      http = NiceHttp.new(config.jira.host)
      http.headers.authorization = NiceHttpUtils.basic_authentication(user: config.jira.user, password: config.jira.password)
      message.gsub!(/^\s*</, "")
      message.gsub!(/\>$/, "")
      message.gsub!(/\|.+$/, "")
      message.gsub!(/^#{config.jira.host}/, "")
      if message.include?("/browse/")
        message = message.scan(/\/browse\/(.+)/).join
        resp = http.get("/rest/api/latest/issue/#{message}")
      else
        message.gsub!(/^\/issues\/\?jql=/, "")
        message.gsub!(" ", "%20")
        resp = http.get("/rest/api/latest/search/?jql=#{message}")
      end
      if resp.code == 200
        able_to_connect_jira = true
      else
        error_code = resp.code
        if resp.code == 400
          error_message = resp.data.json(:errorMessages)[-1]
        else
          error_message = ""
        end
      end
      http.close
    rescue => exception
      @logger.fatal exception
    end
  elsif type == "github"
    able_to_connect_github = false
    begin
      http = NiceHttp.new(config.github.host)
      http.headers.authorization = "token #{config.github.token}"
      message.gsub!(/^\s*</, "")
      message.gsub!(/\>$/, "")
      message.gsub!(/\|.+$/, "")
      message.gsub!(/^#{config.github.host}/, "")
      message.gsub!("https://github.com", "")
      message.slice!(0) if message[0] == "/"
      resp = http.get("/repos#{message}")
      if resp.code == 200
        able_to_connect_github = true
      else
        error_code = resp.code
        if resp.code == 401
          error_message = resp.data.json(:message)[-1]
        else
          error_message = ""
        end
      end
      http.close
    rescue => exception
      @logger.fatal exception
    end
  end

  if !@teams.key?(team_name.to_sym)
    respond "It seems like the team *#{team_name}* doesn't exist\nRelated commands `add team TEAM_NAME PROPERTIES`, `see team TEAM_NAME`, `see teams`"
  elsif !(all_team_members + config.masters).flatten.include?(user.name)
    respond "You have to be a member of the team or a Master admin to be able to add a memo to the team."
  elsif type == "jira" and !able_to_connect_jira
    if error_message == ""
      respond "You need to supply the correct credentials for JIRA on the SmartBot settings: `jira: { host: HOST, user: USER, password: PASSWORD }` and a correct JQL string or JQL url"
    else
      respond "You need to supply a correct JQL string or JQL url: #{error_message}"
    end
  else
    topic = :no_topic if topic == ""
    @teams[team_name.to_sym][:memos] ||= []
    if @teams[team_name.to_sym][:memos].empty?
      memo_id = 1
    else
      memo_id = @teams[team_name.to_sym][:memos].memo_id.flatten.max + 1
    end
    @teams[team_name.to_sym][:memos] << {
      memo_id: memo_id,
      topic: topic,
      type: type,
      privacy: privacy,
      user: user.name,
      date: Time.now.strftime("%Y-%m-%dT%H:%M:%S.000Z")[0..18],
      message: message,
      status: ':new: '
    }
    update_teams()
    respond "The memo has been added to *#{team_name}* team."
  end
end

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

helpadmin: ---------------------------------------------- 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:



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
# File 'lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb', line 42

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.masters.include?(from))
    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
        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: 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
      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: 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:



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
# File 'lib/slack/smart-bot/commands/on_bot/add_shortcut.rb', line 24

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
    if has_access?(__method__, user)
      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[from] = Hash.new() unless @shortcuts_global.keys.include?(from)
          found_other = false
          if for_all.to_s != ""
            @shortcuts_global.each { |sck, scv|
              if sck != :all and sck != from and scv.key?(shortcut_name)
                found_other = true
              end
            }
          end
          if @shortcuts_global[:all].include?(shortcut_name) or @shortcuts_global[from].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[from].include?(shortcut_name)
            #new shortcut
            @shortcuts_global[from][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[from] = Hash.new() unless @shortcuts.keys.include?(from)

        found_other = false
        if for_all.to_s != ""
          @shortcuts.each { |sck, scv|
            if sck != :all and sck != from and scv.key?(shortcut_name)
              found_other = true
            end
          }
        end
        if !is_admin?(from) and @shortcuts[:all].include?(shortcut_name) and !@shortcuts[from].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[from].include?(shortcut_name)
          #new shortcut
          @shortcuts[from][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, from, dest)
          else
            case answer
            when /^(yes|yep)/i
              @shortcuts[from][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(from)
            when /^no/i
              respond "ok, I won't add it", dest
              answer_delete(from)
            else
              ask "I don't understand, yes or no?", command, from, dest
            end
          end
        end
      end
    end
  end
end

#add_team(user, name, options, info) ⇒ 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
# File 'lib/slack/smart-bot/commands/general/add_team.rb', line 2

def add_team(user, name, options, info)
  save_stats(__method__)

  get_teams()
  if @teams.key?(name.to_sym)
    respond "It seems like the team *#{name}* already exists.\nRelated commands `update team TEAM_NAME PROPERTIES`, `delete team TEAM_NAME`, `see team TEAM_NAME`, `see teams`"
  else
    wrong = false
    team = { members: {}, channels: {} }
    last_type = nil
    type_detected = false
    options.split(/\s+/).each do |opt|
      type_detected = false
      if opt.match?(/^\s*$/)
        #blank
      elsif opt.match?(/^[\w\-]+$/i)
        last_type = opt
        type_detected = true
      elsif opt.match(/<@(\w+)>/i)
        team[:members][last_type] ||= []
        if last_type.nil?
          wrong = true
          respond "You need to specify the TYPE for the member."
          break
        else
          member_id = $1
          member_info = @users.select { |u| u.id == member_id or (u.key?(:enterprise_user) and u.enterprise_user.id == member_id) }[-1]
          if member_info.nil?
            @users = get_users()
            member_info = @users.select { |u| u.id == member_id or (u.key?(:enterprise_user) and u.enterprise_user.id == member_id) }[-1]
          end
          team[:members][last_type] << member_info.name
        end
      elsif opt.match(/<#(\w+)\|[^>]*>/i)
        team[:channels][last_type] ||= []
        if last_type.nil?
          wrong = true
          respond "You need to specify the TYPE for the channel."
          break
        else
          channel_id = $1
          get_channels_name_and_id() unless @channels_name.keys.include?(channel_id)
          channel = @channels_name[channel_id]
          channel_members = get_channel_members(channel_id) unless channel.nil?
          if channel.nil? or !channel_members.include?(config.nick_id)
            respond ":exclamation: Add the Smart Bot to *<##{channel_id}>* channel first."
            wrong = true
            break
          else
            team[:channels][last_type] << channel
          end
        end
      else
        respond "It seems like the members or channel list is not correct. Please double check."
        wrong = true
        break
      end
    end
    if type_detected #type added but not added a channel or user
      respond "It seems like the parameters supplied are not correct. Please double check."
      wrong = true
    end

    unless wrong
      get_teams()
      team[:info] = info
      team[:status] = :added
      team[:user] = user.name
      team[:creator] = user.name
      team[:date] = Time.now.strftime("%Y-%m-%dT%H:%M:%S.000Z")[0..18]
      new_team = {}
      team[:name] = name
      new_team[name.to_sym] = team
      update_teams(new_team)
      respond "The *#{name}* team has been added."
      see_teams(user, name, add_stats: false)
    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
# 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

  if from=='today'
    from = Date.today.strftime("%Y/%m/%d")
  elsif from =='tomorrow'
    from = (Date.today+1).strftime("%Y/%m/%d")
  elsif from.match?(/next\s+week/)
    from = Date.today + ((1 - Date.today.wday) % 7)
    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[user.name] ||= { user_id: user.id, periods: [] }
      if !vacations[user.name].key?(:periods)
        vacations[user.name][:user_id] = user.id
        vacations[user.name][:periods] = []
      end

      if vacations[user.name].periods.empty?
        vacation_id = 1
      else
        vacation_id = vacations[user.name].periods[-1].vacation_id + 1
      end
      vacations[user.name].periods << { vacation_id: vacation_id, type: type.downcase, from: from, to: to }
      update_vacations({user.name => vacations[user.name]})
      respond "Period has been added   ##{vacation_id}"
      check_vacations(date: Date.today, 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
# 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.name)
    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_users.each do |us|
             = @users.select { |u| u.id == us or (u.key?(:enterprise_user) and u.enterprise_user.id == us) }[-1]
            access_users_names << .name unless .nil?
          end
          @access_channels[channel][command_id] += access_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
          respond "These users will have access to this command on this channel: <@#{@access_channels[channel][command_id].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(from = Thread.current[:user].name, dest = Thread.current[:dest]) ⇒ Object



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

def answer(from = Thread.current[:user].name, dest = Thread.current[:dest])
    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(from = Thread.current[:user].name, dest = Thread.current[:dest]) ⇒ Object



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

def answer_delete(from = Thread.current[:user].name, dest = Thread.current[:dest])
    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
# 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[:user].name
    end
    if context.nil?
      context = Thread.current[:command]
    end
    message = "#{to}: #{question}"
    if dest.nil?
      if config[:simulate]
        open("#{config.path}/buffer_complete.log", "a") { |f|
          f.puts "|#{@channel_id}|#{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}|#{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}|#{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}|#{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) ⇒ 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
# File 'lib/slack/smart-bot/commands/general/bot_help.rb', line 3

def bot_help(user, from, dest, dchannel, specific, help_command, rules_file, savestats: true, strict: 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
    help_message = get_help(rules_file, dest, from, specific, expanded)
    commands = []
    commands_search = []
    if help_command.to_s != ""
      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'
    dest = :on_thread
    output.unshift('Since there are many lines returned the results are returned on a thread by default.')
  end
  output.each do |h|
    msg = h.gsub(/^\s*command_id:\s+:\w+\s*$/,'')
    unless msg.match?(/\A\s*\z/)
      respond msg, dest, unfurl_links: false, unfurl_media: false
    end
  end
  return output.join("\n")
end

#bot_rules(dest, help_command, typem, rules_file, 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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/slack/smart-bot/commands/on_extended/bot_rules.rb', line 2

def bot_rules(dest, help_command, typem, rules_file, user)
  save_stats(__method__)
  from = user.name
  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 

      help_filtered = get_help(rules_file, dest, from, 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
      output.each do |h|
        msg = h.gsub(/^\s*command_id:\s+:\w+\s*$/,'')
        unless msg.match?(/\A\s*\z/)
          respond msg, dest, unfurl_links: false, unfurl_media: false
        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) ⇒ 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: 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: 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:



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
# File 'lib/slack/smart-bot/commands/on_bot/general/bot_stats.rb', line 47

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)
  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.masters.include?(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?
      message << ":exclamation: Add the Smart Bot to *<##{channel_members}>* channel first."
      wrong = true
    else
      tm.each do |m|
         = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) }[-1]
        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"]
    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 = {}
  unless wrong
    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
        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_month = {}
        users_month = {}
        commands_month = {}
        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 != ""
           = @users.select { |u| u.id == user or (u.key?(:enterprise_user) and u.enterprise_user.id == user) }[-1]
          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|
              if (include_channel_members and members_list.include?(row[:user_name])) or
                 (exclude_channel_members and !members_list.include?(row[:user_name])) or
                 (!include_channel_members and !exclude_channel_members)
                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\//))
                    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 monthly
                                  rows_month[row[:date][0..6]] = 0 unless rows_month.key?(row[:date][0..6])
                                  users_month[row[:date][0..6]] = [] unless users_month.key?(row[:date][0..6])
                                  commands_month[row[:date][0..6]] = [] unless commands_month.key?(row[:date][0..6])
                                  rows_month[row[:date][0..6]] += 1
                                  users_month[row[:date][0..6]] << row[:user_id]
                                  commands_month[row[:date][0..6]] << 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 monthly
            if on_dm_master
              message << "*Totals by month / commands / users (%new)*"
            else
              message << "*Totals by month / commands*"
            end

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

          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 do |time_zone|
                unless time_zone == ""
                  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] == ""
                    tzone_users[rows[i].values[11]] ||= 0
                    tzone_users[rows[i].values[11]] += 1
                  end
                else
                   = @users.select { |u| u.id == usr or (u.key?(:enterprise_user) and u.enterprise_user.id == usr) }[-1]
                  unless .nil? or .is_app_user or .is_bot
                    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 == ""
                  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
                     = @users.select { |u| u.id == usr or (u.key?(:enterprise_user) and u.enterprise_user.id == usr) }[-1]
                    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_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).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
    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
# 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
      respond "I'm listening to [#{@listening.keys.join(", ")}]", dest
      if config.on_master_bot and config.admins.include?(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, from, display_name) ⇒ Object



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

def bye_bot(dest, from, display_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?(from)
      if Thread.current[:on_thread]
        @listening[from].delete(Thread.current[:thread_ts])
      else
        @listening[from].delete(dest)
      end
      @listening.delete(from) if @listening[from].empty?
    end
  end
end

#check_vacations(date: Date.today, 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
# File 'lib/slack/smart-bot/utils/check_vacations.rb', line 2

def check_vacations(date: Date.today, user: nil, set_status: true, only_first_day: true)
  get_vacations()
  if user.nil?
    users = @vacations.keys
  else
    users = [user]
  end
  on_vacation = []
  users.each do |user|
    type = nil
    expiration = nil
    @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 = @users.select{|u| u.id == channel_found.creator or (u.key?(:enterprise_user) and u.enterprise_user.id == channel_found.creator)}[-1]
            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)
      @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 == ''
           = @users.select{|u| u.name == @routines[@channel_id][name][:creator]}[-1]
          @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

#decrypt(data) ⇒ Object



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

def decrypt(data)
  require "openssl"
  require "base64"

  key, iv = encryption_get_key_iv()
  encrypted = Base64.decode64(data)
  cipher = OpenSSL::Cipher.new("AES-256-CBC")
  cipher.decrypt
  cipher.key = key
  cipher.iv = iv
  plain = cipher.update(encrypted) + cipher.final
  return plain
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
# 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_deleted', '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_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_memo_team(user, team_name, memo_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
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
# File 'lib/slack/smart-bot/commands/general/delete_memo_team.rb', line 2

def delete_memo_team(user, team_name, memo_id)
  save_stats(__method__) if answer.empty?

  get_teams()
  if @teams.key?(team_name.to_sym)
    assigned_members = @teams[team_name.to_sym].members.values.flatten
    assigned_members.uniq!
    all_team_members = assigned_members.dup
    team_members = []
    if @teams[team_name.to_sym].channels.key?("members")
      @teams[team_name.to_sym].channels["members"].each do |ch|
        get_channels_name_and_id() unless @channels_id.key?(ch)
        tm = get_channel_members(@channels_id[ch])
        tm.each do |m|
           = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) }[-1]
          team_members << .name unless .is_app_user or .is_bot
        end
      end
    end
    team_members.flatten!
    team_members.uniq!
    all_team_members += team_members
    all_team_members.uniq!
  end

  if !@teams.key?(team_name.to_sym)
    respond "It seems like the team *#{team_name}* doesn't exist.\nRelated commands `add team TEAM_NAME PROPERTIES`, `see team TEAM_NAME`, `see teams`"
  elsif !(all_team_members + config.masters).flatten.include?(user.name)
    respond "You have to be a member of the team or a Master admin to be able to delete a memo of the team."
  elsif !@teams[team_name.to_sym].key?(:memos) or @teams[team_name.to_sym][:memos].empty? or !@teams[team_name.to_sym][:memos].memo_id.include?(memo_id.to_i)
    respond "It seems like there is no memo with id #{memo_id}"
  elsif @teams[team_name.to_sym][:memos].memo_id.include?(memo_id.to_i)
    memo_selected = @teams[team_name.to_sym][:memos].select { |m| m.memo_id == memo_id.to_i }[-1]
    if memo_selected.privacy == 'personal' and memo_selected.user != user.name
      respond "Only the creator can delete a personal memo."
    else
      if answer.empty?
        message = @teams[team_name.to_sym][:memos].select { |memo| memo.memo_id == memo_id.to_i }.message.join
        ask "do you really want to delete the memo #{memo_id} (#{message}) from #{team_name} team? (yes/no)"
      else
        case answer
        when /yes/i, /yep/i, /sure/i
          answer_delete
          memos = []
          message = ""
          get_teams()
          @teams[team_name.to_sym][:memos].each do |memo|
            if memo.memo_id != memo_id.to_i
              memos << memo
            else
              message = memo.message
            end
          end
          @teams[team_name.to_sym][:memos] = memos
          update_teams()
          respond "The memo has been deleted from team #{team_name}: #{message}"
        when /no/i, /nope/i, /cancel/i
          answer_delete
          respond "Ok, the memo was not deleted"
        else
          respond "I don't understand"
          ask "do you really want to delete the memo from #{team_name} team? (yes/no)"
        end
      end
    end
  end
end

#delete_message(from, 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(from, typem, url)
  save_stats(__method__)
  channel, ts = url.scan(/\/archives\/(\w+)\/(\w\d+)/)[0]
  if config.masters.include?(from) 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.name) or @repls[session_name].creator_name == user.name
        @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
# 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_deleted', '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_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
# 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
    if has_access?(__method__, user)
      deleted = false

      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?(from) and @shortcuts_global[:all].include?(shortcut) and 
            (!@shortcuts_global.key?(from) or !@shortcuts_global[from].include?(shortcut))
            respond "Only the creator of the shortcut or an admin user can delete it"
          elsif (@shortcuts_global.key?(from) and @shortcuts_global[from].keys.include?(shortcut)) or
            (is_admin?(from) and @shortcuts_global[:all].include?(shortcut))
            
            respond "global shortcut deleted!", dest
            if @shortcuts_global.key?(from) and @shortcuts_global[from].key?(shortcut)
              respond("#{shortcut}: #{@shortcuts_global[from][shortcut]}", dest) 
            elsif @shortcuts_global.key?(:all) and @shortcuts_global[:all].key?(shortcut)
              respond("#{shortcut}: #{@shortcuts_global[:all][shortcut]}", dest)
            end
            @shortcuts_global[from].delete(shortcut) if @shortcuts_global.key?(from) and @shortcuts_global[from].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?(from) and @shortcuts[:all].include?(shortcut) and 
          (!@shortcuts.key?(from) or !@shortcuts[from].include?(shortcut))
          respond "Only the creator of the shortcut or an admin user can delete it", dest
        elsif (@shortcuts.keys.include?(from) and @shortcuts[from].keys.include?(shortcut)) or
              (is_admin?(from) 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, from, dest)
          else
            case answer
            when /^(yes|yep)/i
              answer_delete(from)
              respond "shortcut deleted!", dest
              if @shortcuts.key?(from) and @shortcuts[from].key?(shortcut)
                respond("#{shortcut}: #{@shortcuts[from][shortcut]}", dest) 
              elsif @shortcuts.key?(:all) and @shortcuts[:all].key?(shortcut)
                respond("#{shortcut}: #{@shortcuts[:all][shortcut]}", dest)
              end
              @shortcuts[from].delete(shortcut) if @shortcuts.key?(from) and @shortcuts[from].key?(shortcut)
              @shortcuts[:all].delete(shortcut) if @shortcuts.key?(:all) and @shortcuts[:all].key?(shortcut)
              update_shortcuts_file()
            when /^no/i
              answer_delete(from)
              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, from, dest)
            end
          end
        else
          respond "shortcut not found", dest
        end
      end
    end
  end
end

#delete_team(user, team_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
# File 'lib/slack/smart-bot/commands/general/delete_team.rb', line 2

def delete_team(user, team_name)
  save_stats(__method__) if answer.empty?

  if Thread.current[:dest][0] == "D"
    respond "This command cannot be called from a DM"
  else
    get_teams()
    if @teams.key?(team_name.to_sym)
      assigned_members = @teams[team_name.to_sym].members.values.flatten
      assigned_members.uniq!
      all_team_members = assigned_members.dup
      team_members = []
      if @teams[team_name.to_sym].channels.key?("members")
        @teams[team_name.to_sym].channels["members"].each do |ch|
          get_channels_name_and_id() unless @channels_id.key?(ch)
          tm = get_channel_members(@channels_id[ch])
          tm.each do |m|
             = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) }[-1]
            team_members << .name unless .is_app_user or .is_bot
          end
        end
      end
      team_members.flatten!
      team_members.uniq!
      all_team_members += team_members
      all_team_members.uniq!
    end
    if !@teams.key?(team_name.to_sym)
      respond "It seems like the team *#{team_name}* doesn't exist.\nRelated commands `add team TEAM_NAME PROPERTIES`, `see team TEAM_NAME`, `see teams`"
    elsif !(all_team_members + [@teams[team_name.to_sym].creator] + config.masters).flatten.include?(user.name)
      respond "You have to be a member of the team, the creator or a Master admin to be able to delete this team."
    else
      if answer.empty?
        ask "do you really want to delete the #{team_name} team? (yes/no)"
      else
        case answer
        when /yes/i, /yep/i, /sure/i
          answer_delete
          @teams.delete(team_name.to_sym)
          File.delete(File.join(config.path, "teams", "t_#{team_name}.yaml"))
          update_teams()
          respond "The team #{team_name} has been deleted."
        when /no/i, /nope/i, /cancel/i
          answer_delete
          respond "Ok, the team was not deleted"
        else
          respond "I don't understand"
          ask "do you really want to delete the #{team_name} team? (yes/no)"  
        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.name)
    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) ⇒ 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
# File 'lib/slack/smart-bot/utils/display_calendar.rb', line 2

def display_calendar(from_user_name, year)
  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
  country, location = country_region.split("/")
  public_holidays(country.to_s, location.to_s, year, "", "", add_stats: false, publish_results: false)
  messages = ["*Time off #{year}*"]
  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)
            month_line += ":large_red_square: "
            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"
                    month_line += ":face_with_thermometer: "
                  elsif period[:type] == "sick child"
                    month_line += ":baby: "
                  elsif period[:type] == "vacation"
                    month_line += ":palm_tree: "
                    if wday <= 5
                      days_of_vacations += 1
                    end
                  end
                  vacations_set = true
                  break
                end
              end
            end
            if !vacations_set
              if wday == 6 || wday == 7
                month_line += ":large_yellow_square: "
              else
                month_line += ":white_square: "
              end
            end
          end
          date += 1
        else
          month_line += ":white_small_square: "
        end
      end
    end
    messages << "#{month_line}    #{month_name}\n"
  end
  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 != ""
    messages << "Your public holidays are set for #{country_region.downcase}. 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"
  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



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/comm/dont_understand.rb', line 3

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.name, 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
end

#encrypt(data) ⇒ Object



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

def encrypt(data)
  require "openssl"
  require "base64"

  key, iv = encryption_get_key_iv()
  cipher = OpenSSL::Cipher::Cipher.new "AES-256-CBC"
  cipher.encrypt
  cipher.key = key
  cipher.iv = iv
  encrypted = cipher.update(data) + cipher.final
  encrypted = Base64.encode64(encrypted)
  return encrypted
end

#encryption_get_key_ivObject



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/encryption_get_key_iv.rb', line 2

def encryption_get_key_iv
    if defined?(@encryption_key_built)
      key = @encryption_key_built
      iv = @encryption_iv_built
    else
      if config.key?(:encryption) and config.encryption.key?(:key) and config.encryption.key?(:iv)
        key = config[:encryption][:key]
        iv = config[:encryption][:iv]
      else
        key = (Socket.gethostname + config.token.reverse)[0..49]
        iv = config.token[0..15]
      end

      #Convert from hex to raw bytes:
      key = [key].pack('H*')
      #Pad with zero bytes to correct length:
      key << ("\x00" * (32 - key.length))
    
      #Convert from hex to raw bytes:
      iv = [iv].pack('H*')
      #Pad with zero bytes to correct length:
      iv << ("\x00" * (16 - iv.length))  
      @encryption_key_built = key
      @encryption_iv_built = iv
    end
    return key, iv
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
# File 'lib/slack/smart-bot/comm/event_hello.rb', line 2

def event_hello()
  @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
  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, from, dest, display_name) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: exit bot helpadmin: quit bot helpadmin: close bot 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:



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
# File 'lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb', line 12

def exit_bot(command, from, dest, display_name)
  save_stats(__method__)
  if config.on_master_bot
    if config.masters.include?(from) #admin user
      if answer.empty?
        ask("are you sure?", command, from, dest)
      else
        case answer
        when /yes/i, /yep/i, /sure/i
          respond "Game over!", dest
          respond "Ciao #{display_name}!", dest
          @bots_created.each { |key, value|
            value[:thread] = ""
            send_msg_channel(key, "Bot has been closed by #{from}")
            save_status :off, :exited, "The admin closed SmartBot on *##{value.channel_name}*"
            sleep 0.5
          }
          update_bots_file()
          sleep 0.5
          if config.simulate
            @status = :off
            config.simulate = false
            Thread.exit
          else
            exit!
          end
        when /no/i, /nope/i, /cancel/i
          answer_delete(from)
          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, from, 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

#get_access_channelsObject



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

def get_access_channels()
  if File.exist?("#{config.path}/rules/#{@channel_id}/access_channels.rb")
    file_conf = IO.readlines("#{config.path}/rules/#{@channel_id}/access_channels.rb").join
    unless file_conf.to_s() == ""
      @access_channels = eval(file_conf)
    end
  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_bot_logs(dest, from, 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, from, typem)
  save_stats(__method__)
  if config.masters.include?(from) 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_channels_name_and_idObject



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 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()
  @users = get_users() if @users.empty?
  @channels_list.each do |ch|
    unless ch.is_archived
      @channels_id[ch.name] = ch.id
      @channels_name[ch.id] = ch.name
       = @users.select{|u| u.id == ch.creator or (u.key?(:enterprise_user) and u.enterprise_user.id == ch.creator)}[-1]
      @channels_creator[ch.id] = .name unless .nil?
      @channels_creator[ch.name] = .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.name)

  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.masters.include?(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.masters.include?(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_help(rules_file, dest, from, 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
# File 'lib/slack/smart-bot/utils/get_help.rb', line 2

def get_help(rules_file, dest, from, only_rules, expanded, descriptions: true, only_normal_user: false)
  order = {
    general: [:bot_help, :hi_bot, :bye_bot, :add_admin, :remove_admin, :see_admins, :poster, :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, :create_loop, :quit_loop],
    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.masters.include?(from)
    user_type = :master # master admin
  elsif is_admin?(from)
    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:*\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_presence(user) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 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
  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 and 
        !is_admin?(user.name)
        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
# File 'lib/slack/smart-bot/utils/get_rules_imported.rb', line 3

def get_rules_imported
  if File.exist?("#{config.path}/rules/rules_imported.rb")
    if !defined?(@datetime_rules_imported) or @datetime_rules_imported != File.mtime("#{config.path}/rules/rules_imported.rb")        
      @datetime_rules_imported = File.mtime("#{config.path}/rules/rules_imported.rb")
      file_conf = IO.readlines("#{config.path}/rules/rules_imported.rb").join
      unless file_conf.to_s() == ""
        @rules_imported = eval(file_conf)
      end
    end
  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_deleted', 'user_created', 'date', 'time', 'type', 'to_channel', 'condition'])
    t.delete_if {|row| row[:user_deleted] != ''}
    @shares[channel] = t
  end
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|
     = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) or u.name == m or (u.key?(:enterprise_user) and u.enterprise_user.name == m) }[-1]
    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|
           = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) }[-1]
          team_members << .name unless .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.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
# 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"), encrypt(value.to_yaml))
    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(decrypt(File.read(file)))
      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
end

#get_user_info(user) ⇒ Object



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

def (user)
  begin
    if user.to_s.length>0
      if config.simulate and config.key?(:client)
        if user[0]=='@' #name
          client.web_client.users_info.select{|k, v| v[:user][:name] == user[1..-1]}.values[-1]
        else #id
          client.web_client.users_info[user.to_sym]
        end
      else
        client.web_client.users_info(user: user)
      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
# 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
    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
# 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"), encrypt(value.to_yaml))
    end
    @logger.info "Deleting old_vacations_file: #{old_vacations_file}"
    File.delete(old_vacations_file)
  end
  files = Dir.glob(File.join(config.path, "vacations", "v_*.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(decrypt(File.read(file)))
      @vacations[File.basename(file).gsub("v_","").gsub(".yaml","")] = vacations_user
      @datetime_vacations_file[file] = File.mtime(file)
    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
# File 'lib/slack/smart-bot/utils/has_access.rb', line 2

def has_access?(method, user = nil)
  user = Thread.current[:user] if user.nil?
  if config[:allow_access].key?(method) and !config[:allow_access][method].include?(user.name) and !config[:allow_access][method].include?(user.id) and
     (!user.key?(:enterprise_user) or (user.key?(:enterprise_user) and !config[:allow_access][method].include?(user[:enterprise_user].id)))
    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)
      return true
    else
      if @admins_channels.key?(channel) and !@admins_channels[channel].empty?
          respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{@admins_channels[channel].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
# 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__)
    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] == ''
      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`"
    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[from] = {} unless @listening.key?(from)
    if Thread.current[:on_thread]
      @listening[from][Thread.current[:thread_ts]] = Time.now
    else
      @listening[from][dest] = Time.now
    end
  end
end

#is_admin?(from = 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
# File 'lib/slack/smart-bot/utils/is_admin.rb', line 2

def is_admin?(from=nil)
    if from.nil?
        user = Thread.current[:user]
        from = user.name
    end
    
    if (Thread.current[:dchannel].to_s!='' 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?(:using_channel))
        get_channels_name_and_id() 
    end

    if config.masters.include?(from) or 
       config.admins.include?(from) or 
       (Thread.current[:typem] == :on_call and @admins_channels.key?(Thread.current[:dchannel]) and @admins_channels[Thread.current[:dchannel]].include?(from)) or
       (Thread.current[:using_channel].to_s == '' and @admins_channels.key?(Thread.current[:dest]) and @admins_channels[Thread.current[:dest]].include?(from)) or
       (@admins_channels.key?(Thread.current[:using_channel]) and @admins_channels[Thread.current[:using_channel]].include?(from)) or 
       (Thread.current[:using_channel].to_s=='' and @channels_creator.key?(Thread.current[:dest]) and from == @channels_creator[Thread.current[:dest]]) or 
       (Thread.current[:typem] == :on_call  and @channels_creator.key?(Thread.current[:dchannel]) and from == @channels_creator[Thread.current[:dchannel]]) or 
       (@channels_creator.key?(Thread.current[:using_channel]) and from == @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 and !config.masters.include?(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



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/listen.rb', line 42

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.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.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
# 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+)\|([^~]+)~~~/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
      user = message[1].strip
      user_name = message[2].strip
      command = message[3].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, 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, user: user, text: cmd, user_name: user_name }, false)
          end
        end
      else
        treat_message({ channel: channel, user: user, text: command, user_name: user_name })
      end
    end
  end
end

#notify_message(dest, from, 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, from, where, message)
  save_stats(__method__)
  if config.on_master_bot
    if config.masters.include?(from) #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

#ping_team(user, type, team_name, member_type, message) ⇒ 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
# File 'lib/slack/smart-bot/commands/general/ping_team.rb', line 2

def ping_team(user, type, team_name, member_type, message)
  if type == :ping
    save_stats(:ping_team)
    icon = ':large_green_circle:'
  else
    save_stats(:contact_team)
    icon = ':email:'
  end
  if Thread.current[:dest][0] == "D"
    respond "This command cannot be called from a DM"
  else
    get_teams()
    if !@teams.key?(team_name.to_sym)
      respond "It seems like the team *#{team_name}* doesn't exist.\nRelated commands `add team TEAM_NAME PROPERTIES`, `see team TEAM_NAME`, `see teams`"
    else
      team = @teams[team_name.to_sym].deep_copy

      assigned_members = team.members.values.flatten
      assigned_members.uniq!
      assigned_members.dup.each do |m|
         = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) or u.name == m or (u.key?(:enterprise_user) and u.enterprise_user.name == m) }[-1]
        assigned_members.delete(m) if .nil? or .deleted
      end
      unassigned_members = []
      not_on_team_channel = []

      if ((!team.members.key?(member_type) or member_type == 'all') and 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])
          tm.each do |m|
             = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) }[-1]
            team_members << .name unless .nil? or .is_app_user or .is_bot or .deleted
          end
        end
        team_members.flatten!
        team_members.uniq!
        unassigned_members = team_members - assigned_members
        unassigned_members.delete(config.nick)

        unless unassigned_members.empty?
          um = unassigned_members.dup
          um.each do |m|
             = @users.select { |u| u.name == m or (u.key?(:enterprise_user) and u.enterprise_user.name == m) }[-1]
            unless .nil? or .profile.title.to_s=='' or .deleted
              team.members[.profile.title.to_snake_case] ||= []
              team.members[.profile.title.to_snake_case] << m
              unassigned_members.delete(m)
            end
          end
          unless unassigned_members.empty?
            team.members["unassigned"] ||= []
            team.members["unassigned"] += unassigned_members
            team.members["unassigned"].sort!
          end
        end
      end

      if team.members.key?(member_type) or member_type=='all'
        if member_type == 'all'
          members_list = team.members.values.flatten.uniq.shuffle
        else
          members_list = team.members[member_type].shuffle
        end
        if type == :ping
          active_members = []
          members_list.each do |member|
            member_info = @users.select { |u| u.name == member }[-1]
            unless member_info.nil? or member_info.deleted
              active = (get_presence(member_info.id).presence.to_s == "active")
              active_members << member if active
            end
          end
          members = active_members
        else
          members = members_list
        end
        members.dup.each do |m|
           = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) or u.name == m or (u.key?(:enterprise_user) and u.enterprise_user.name == m) }[-1]
          members.delete(m) if .nil? or .deleted
        end

        if members.size > 0
          respond "#{icon} *#{type} #{team_name} team #{member_type}*\nfrom <@#{user.name}>\nto <@#{members[0..9].join('>, <@')}>#{", #{members[10..-1].join(', ')}" if members.size > 10} \n> #{message.split("\n").join("\n> ")}"
        elsif type == :ping
          respond "It seems like there are no available #{member_type} members on #{team_name} team. Please call `see team #{team_name}`"
        else
          respond "It seems like there are no #{member_type} members on #{team_name} team. Please call `see team #{team_name}`"
        end
      else
        respond "The member type #{member_type} doesn't exist, please call `see team #{team_name}`"
      end        


    end
  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
# File 'lib/slack/smart-bot/process.rb', line 2

def process(user, command, dest, dchannel, rules_file, typem, files, ts)
  from = 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?(from) and (@listening[from].key?(dest) or @listening[from].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(#{@salutations.join("|")}),? what can I do/i
        $2.to_s.match?(/rules/i) ? specific = true : specific = false
        help_command = $3
        bot_help(user, from, dest, dchannel, specific, help_command, rules_file)
      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(from, 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*$/i, /\A\s*quit\s+bot\s*$/i, /\A\s*close\s+bot\s*$/i
        exit_bot(command, from, dest, display_name)
      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, from, 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
        create_bot(dest, user, type, channel)
      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<#(C\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<#(C\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<#(C\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<#(C\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, from, 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, from, 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(from, 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(from, 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, from, 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
        monthly = false
        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
        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)
      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(from, 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(from, 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?(from) and dest==@repl_sessions[from][:dest] and 
        ((@repl_sessions[from][:on_thread] and Thread.current[:thread_ts] == @repl_sessions[from][:thread_ts]) or
        (!@repl_sessions[from][:on_thread] and !Thread.current[:on_thread]))) or 
        (@listening.key?(from) and typem != :on_extended and 
        ((@listening[from].key?(dest) and !Thread.current[:on_thread]) or 
          (@listening[from].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
# 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
  rules_file = ""
  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.admins.include?(user.name) or @loops.key?(user.name)
      if config.admins.include?(user.name)
        name_loop = ''
        @loops.each do |k,v|
          if v.include?(num_iteration)
            name_loop = k
            break
          end
        end
      else
        name_loop = user.name
      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
    @logger.info "command: #{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[user.name] ||= []
    @num_loops ||= 0
    @num_loops += 1
    loop_id = @num_loops
    @loops[user.name] << 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?(user.name) and @rules_imported[user.name].key?(dchannel)
      unless @bots_created.key?(@rules_imported[user.name][dchannel])
        get_bots_created()
      end
      if @bots_created.key?(@rules_imported[user.name][dchannel])
        rules_file = @bots_created[@rules_imported[user.name][dchannel]][:rules_file]
      end
    end
  elsif dest[0] == "D" and @rules_imported.key?(user.name) and @rules_imported[user.name].key?(user.name) #direct message
    unless @bots_created.key?(@rules_imported[user.name][user.name])
      get_bots_created()
    end
    if @bots_created.key?(@rules_imported[user.name][user.name])
      rules_file = @bots_created[@rules_imported[user.name][user.name]][:rules_file]
    end
  elsif dest[0] == "D" and (!@rules_imported.key?(user.name) or (@rules_imported.key?(user.name) and !@rules_imported[user.name].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}"
          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[@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?(nick) and @shortcuts[nick].keys.include?(sc)
        command.gsub!("$#{sc}", @shortcuts[nick][sc])
      elsif @shortcuts.key?(:all) and @shortcuts[:all].keys.include?(sc)
        command.gsub!("$#{sc}", @shortcuts[:all][sc])
      elsif @shortcuts_global.key?(nick) and @shortcuts_global[nick].keys.include?(sc)
        command.gsub!("$#{sc}", @shortcuts_global[nick][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?(nick) and @shortcuts[nick].keys.include?(sc)
        command.gsub!("$#{sc}", @shortcuts[nick][sc])
      elsif @shortcuts.key?(:all) and @shortcuts[:all].keys.include?(sc)
        command.gsub!("$#{sc}", @shortcuts[:all][sc])
      elsif @shortcuts_global.key?(nick) and @shortcuts_global[nick].keys.include?(sc)
        command.gsub!("$#{sc}", @shortcuts_global[nick][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?(nick) and @shortcuts[nick].keys.include?(command)) or
     (@shortcuts_global.keys.include?(:all) and @shortcuts_global[:all].keys.include?(command)) or
     (@shortcuts_global.keys.include?(nick) and @shortcuts_global[nick].keys.include?(command))
    command = $2.downcase unless $2.nil?
    if @shortcuts.keys.include?(nick) and @shortcuts[nick].keys.include?(command)
      text = @shortcuts[nick][command].dup
    elsif @shortcuts.keys.include?(:all) and @shortcuts[:all].keys.include?(command)
      text = @shortcuts[:all][command].dup
    elsif @shortcuts_global.keys.include?(nick) and @shortcuts_global[nick].keys.include?(command)
      text = @shortcuts_global[nick][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?(user.name) and !@loops[user.name].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

          Thread.current[:dest] = dest
          Thread.current[:user] = user
          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?(user.name) &&
                                                      @rules_imported[user.name].key?(dchannel) && @bots_created.key?(@rules_imported[user.name][dchannel])
            Thread.current[:using_channel] = @rules_imported[user.name][dchannel]
          elsif dest[0] == "D" && @rules_imported.key?(user.name) && @rules_imported[user.name].key?(user.name) and
                @bots_created.key?(@rules_imported[user.name][user.name])
            Thread.current[:using_channel] = @rules_imported[user.name][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
          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(#{@salutations.join("|")}),? what can I do/i)
                $2.to_s.match?(/rules/i) ? specific = true : specific = false
                help_command = $3
                if typem == :on_extended and specific
                  bot_rules(dest, help_command, typem, rules_file, user)
                else
                  bot_help(user, user.name, dest, dchannel, specific, help_command, rules_file)
                end
                processed = true
              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)
            @logger.info "command: #{nick}> #{command_thread}" if processed
          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?(nick) and dest == @repl_sessions[nick][:dest] and
                 ((@repl_sessions[nick][:on_thread] and thread_ts == @repl_sessions[nick][:thread_ts]) or
                  (!@repl_sessions[nick][:on_thread] and !Thread.current[:on_thread]))) or
                (@listening.key?(nick) and typem != :on_extended and
                 ((@listening[nick].key?(dest) and !Thread.current[:on_thread]) or
                  (@listening[nick].key?(thread_ts) and Thread.current[:on_thread]))) or
                dest[0] == "D" or on_demand)
              @logger.info "command: #{nick}> #{command_thread}" unless processed
              #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?(user.name) and @rules_imported[user.name].key?(dchannel)
                  if @bots_created.key?(@rules_imported[user.name][dchannel])
                    if @bots_created[@rules_imported[user.name][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?(user.name) and @rules_imported[user.name].key?(user.name)
                if @bots_created.key?(@rules_imported[user.name][user.name])
                  if @bots_created[@rules_imported[user.name][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[user.name][user.name]}|#{@bots_created[@rules_imported[user.name][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?(user.name) or (@rules_imported.key?(user.name) and !@rules_imported[user.name].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?(nick)
                if Thread.current[:on_thread] and @listening[nick].key?(Thread.current[:thread_ts])
                  @listening[nick][Thread.current[:thread_ts]] = Time.now
                elsif !Thread.current[:on_thread] and @listening[nick].key?(dest)
                  @listening[nick][dest] = Time.now
                end
              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[user.name].delete(loop_id) if command_every!='' and !processed and @loops.key?(user.name) and @loops[user.name].include?(loop_id)
        rescue Exception => stack
          @logger.fatal stack
        end
      end
    rescue => e
      @logger.error "exception: #{e.inspect}"
    end
  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.masters.include?(user.name) #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, from, 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, from, typem, to, thread_ts, emojis)
  save_stats(__method__)
  if config.masters.include?(from) 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

#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
# 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
    channels = get_channels()
    channel_found = channels.detect { |c| c.id == channel }
    if !channel_found.nil? and channel_found.creator.to_s != ''
      creator_info = @users.select{|u| u.id == channel_found.creator or (u.key?(:enterprise_user) and u.enterprise_user.id == channel_found.creator)}[-1]
      admins << creator_info.name
    end
    if Thread.current[:typem] == :on_bot or Thread.current[:typem] == :on_master
      admins << config.admins.dup
    end
    if @admins_channels.key?(channel) and @admins_channels[channel].size > 0
      admins << @admins_channels[channel]
    end
    admins.flatten!
    admins.uniq!
    admins.delete(nil)
    if admins.include?(user.name)
      admin_info = @users.select{|u| u.id == admin_user or (u.key?(:enterprise_user) and u.enterprise_user.id == admin_user)}[-1]
      if creator_info.name == admin_info.name
        messages << "This user created the channel and cannot be removed as an admin."
      elsif config.masters.include?(admin_info.name) or config.masters.include?(admin_user)
        messages << "Master admins cannot be removed as admins of this channel."
      elsif config.admins.include?(admin_info.name) or config.admins.include?(admin_user)
        messages << "This user is a defaulted admin for this channel and cannot be removed using this command."
      elsif !admins.include?(admin_info.name)
        messages << "This user is not an admin of this channel."
      else
        @admins_channels[channel] ||= []
        @admins_channels[channel].delete(admin_info.name)
        update_admins_channels()
        messages << "The user is not an admin of this channel from now on."
        admins.delete(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
# 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)
      @routines[@channel_id][name][:thread].exit
      @routines[@channel_id].delete(name)
      update_routines()
      respond "The routine *`#{name}`* has been removed.", 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 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
# File 'lib/slack/smart-bot/commands/general/remove_vacation.rb', line 2

def remove_vacation(user, vacation_id)
  save_stats(__method__)

  get_vacations()
  if !@vacations.key?(user.name)
    respond "It seems like you don't have any time off added."
  elsif @vacations[user.name].periods.empty? or !@vacations[user.name].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
    vacations = @vacations[user.name].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({user.name => vacations})
    respond "Your time off has been removed."
    if vacation.from <= Date.today.strftime("%Y/%m/%d") and vacation.to >= Date.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: Date.today, 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: 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: 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:



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
# File 'lib/slack/smart-bot/commands/on_bot/repl.rb', line 36

def repl(dest, user, session_name, env_vars, rules_file, command, description, type)
  #todo: add more tests
  from = user.name
  if has_access?(__method__, user)
    if !@repl_sessions.key?(from)
      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[from] = {
        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: from
      }

      unless temp_repl
        @repls[session_name] = {
            created: @repl_sessions[from][:started].to_s,
            accessed: @repl_sessions[from][:started].to_s,
            creator_name: user.name,
            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] = ''

      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_
      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, dest
      
      File.write("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", "", mode: "a+")
      File.write("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.output", "", mode: "a+")
      File.write("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][: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\"
          bindme' + serialt + ' = binding
          eval(\"require \'nice_http\'\" , bindme' + serialt + ')
          def ls(obj)
            (obj.methods - Object.methods)
          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
                if code_to_run_repl.match?(/^\s*ls\s+(.+)/)
                  add_to_run_repl = false
                end
                error = false
                begin
                  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|
                      f.puts \"\`\`\`\n#{resp_repl}\`\`\`\"
                    }
                  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
      '
      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
      process_to_run.gsub!('\"','"')
      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 = 30 * 60 # 30 minutes
      
      file_output_repl = File.open("#{config.path}/repl/#{@channel_id}/#{session_name}.output", "r")
      @repl_sessions[from][:pid] = wait_thr.pid
      while (wait_thr.status == 'run' or wait_thr.status == 'sleep') and @repl_sessions.key?(from)
        begin
          if (Time.now-@repl_sessions[from][:finished]) > timeout
              open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", 'a+') {|f|
                f.puts 'quit'
              }
              respond "REPL session finished: #{@repl_sessions[from][:name]}", dest
              unreact :running, @ts_react[@repl_sessions[from].name]
              pids = `pgrep -P #{@repl_sessions[from][: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[from][:collaborators].each do |collaborator|
                @repl_sessions.delete(collaborator)
              end  
              @repl_sessions.delete(from)
              break
          end
          sleep 0.2
          resp_repl = file_output_repl.read
          if resp_repl.to_s!=''
            if @ts_repl[@repl_sessions[from].name].to_s != ''
              unreact(:running, @ts_repl[@repl_sessions[from].name]) 
              @ts_repl[@repl_sessions[from].name] = ''
            end
            if resp_repl.to_s.lines.count < 60 and resp_repl.to_s.size < 3500
              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
          end
        rescue Exception => excp
          @logger.fatal excp
        end
      end
    elsif @repl_sessions.key?(from) and @repl_sessions[from][: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[from][:finished] = Time.now
      code = @repl_sessions[from][:command]
      @repl_sessions[from][: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?(/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
           = @users.select{|u| u.id == collaborator or (u.key?(:enterprise_user) and u.enterprise_user.id == collaborator)}[-1]
          collaborator_name = .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[from][:user_creator]
            @repl_sessions[creator][:collaborators] << collaborator_name
            @repl_sessions[collaborator_name] = {
              name: @repl_sessions[from][: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[from][:user_type] == :collaborator
          @repl_sessions[@repl_sessions[from][:user_creator]][:input] << code
        else
          @repl_sessions[from][:input] << code
        end
        case code
        when /^\s*(quit|exit|bye|bye\s+bot)\s*$/i
          if @repl_sessions[from][:user_type] == :collaborator
            respond "Collaborator <@#{user.id}> removed.", dest
            @repl_sessions[@repl_sessions[from][:user_creator]][:collaborators].delete(from)
            @repl_sessions.delete(from)
          else
            open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", 'a+') {|f|
              f.puts code
            }
            respond "REPL session finished: #{@repl_sessions[from][:name]}", dest
            unreact :running, @ts_react[@repl_sessions[from].name]
            pids = `pgrep -P #{@repl_sessions[from][: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[from][:collaborators].each do |collaborator|
              @repl_sessions.delete(collaborator)
            end
            @repl_sessions.delete(from)
          end
        when /\A\s*-/i
          #ommit
        else
          if @ts_repl[@repl_sessions[from].name].to_s == ''
            @ts_repl[@repl_sessions[from].name] = Thread.current[:ts]
            react :running 
          end
          open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", 'a+') {|f|
            f.puts code
          }
        end
      end
    end
  end
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) ⇒ 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
# 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)
  result = true
  resp = nil
  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 = ""
          msg.split("\n").each do |m|
            if (m + txt).size > max_chars_per_message
              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
                t = txt.chars.each_slice(max_chars_per_message).map(&:join)
                msgs << t
                txt = ""
              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}|#{config[:nick_id]}|#{config[:nick]}|#{txt_on_thread}#{msg}~~~"
            }
          else
            if on_thread
              msgs.each do |msg|
                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
                sleep wait
              end
            else
              msgs.each do |msg|
                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
                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}|#{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}|#{config[:nick_id]}|#{config[:nick]}|#{txt_on_thread}#{msg}~~~"
            }
          else
            if on_thread
              msgs.each do |msg|
                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
                sleep wait
              end
            else
              msgs.each do |msg|
                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
                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}|#{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|
            resp = send_msg_user(dest, msg, on_thread, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
            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|
              resp = send_msg_user(.id, msg, on_thread, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
              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}|#{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}|#{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}|#{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}|#{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)
            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)
              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

#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) ⇒ 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)
  respond(msg, :on_thread, unfurl_links: unfurl_links, unfurl_media: unfurl_media)
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 and
         !is_admin?(user.name)
        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
            @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] = { 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
# 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],
          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
# File 'lib/slack/smart-bot/utils/save_stats.rb', line 2

def save_stats(method, data: {}, forced: false)
  if has_access?(method, Thread.current[:user]) or forced
    if config.stats
      begin
        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"]
          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
         = @users.select { |u| u.id == user_id or (u.key?(:enterprise_user) and u.enterprise_user.id == user_id) }[-1]
        if .nil? or .is_app_user or .is_bot
          time_zone = ''
          job_title = ''
        else
          time_zone = .tz_label
          job_title = .profile.title
        end

        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]
        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
# 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
      respond "Only these users have access to `#{command_id}` in this channel: <@#{@access_channels[channel][command_id].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
# 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 = []
  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 = @users.select{|u| u.id == channel_found.creator or (u.key?(:enterprise_user) and u.enterprise_user.id == channel_found.creator)}[-1]
  else
    creator_info = {name: []}
  end
  messages << "*Master admins*: <@#{config.masters.join('>, <@')}>"
  if Thread.current[:typem] == :on_bot or Thread.current[:typem] == :on_master
    admins = config.admins.dup
  end
  if @admins_channels.key?(channel) and @admins_channels[channel].size > 0
    admins = (@admins_channels[channel] + admins).uniq
  end
  admins = admins - config.masters - [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.masters.include?(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.masters.include?(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_deleted', '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]
                 = @users.select { |u| u.name == user_created or (u.key?(:enterprise_user) and u.enterprise_user.name == user_created) }[-1]
                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[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.masters.include?(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_memos_team(user, type: "all", name: nil, topic: "", add_stats: true, team: 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
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
# File 'lib/slack/smart-bot/commands/general/see_memos_team.rb', line 2

def see_memos_team(user, type: "all", name: nil, topic: "", add_stats: true, team: nil)
  save_stats(__method__) if add_stats

  get_teams()
  type = "all" if type.match?(/all\s+memo/i)
  message = []
  if @teams.size > 0
    if team.nil?
      @teams.each do |team_name, teamv|
        if (team_name.to_s == name.to_s) or (name.to_s.gsub("-", "").gsub("_", "") == team_name.to_s)
          if teamv.key?(:memos) and teamv[:memos].size > 0
            team = teamv.deep_copy
          else
            respond "There are no memos for the team #{name}." unless !add_stats
          end
          break
        end
      end
    end
    if team
      all_memos = {}
      assigned_members, unassigned_members, not_on_team_channel, channels_members, all_team_members = get_team_members(team)
      users_link = (Thread.current[:dest][0] == "D")
      memos_filtered = []
      all_topics = []
      team[:memos].each do |memo|
        if (type == "all" or type.to_s == memo[:type].to_s or type == "") and (topic == "" or memo[:topic].to_s.downcase == topic.to_s.downcase)
          memos_filtered << memo
          all_topics << memo.topic
        end
      end
      all_topics.uniq!
      all_topics.delete(:no_topic)
      if memos_filtered.size >= 10 and !add_stats
        message << "   > *_memos_*"
        message << "        There are too many memos to show. "
        message << "        Please use the `see MEMO_TYPE team #{team.name} TOPIC` command."
        message << "        Available topics: #{all_topics.join(", ")}" if all_topics.size > 0
        message << "        Examples: `see bugs #{team.name} team`, `see all memos #{team.name} team #{all_topics.sample}`, `see tasks #{team.name} team #{all_topics.sample}`"
      elsif memos_filtered.size > 0
        memos_filtered.each do |memo|
          if memo.privacy.empty? or
             (memo.privacy == "private" and (all_team_members.include?(user.name) and (users_link or channels_members.include?(Thread.current[:dest])))) or
             (memo.privacy == "personal" and memo.user == user.name and users_link)
            if memo.type == "jira" and config.jira.host != ""
              http = NiceHttp.new(config.jira.host)
              http.headers.authorization = NiceHttpUtils.basic_authentication(user: config.jira.user, password: config.jira.password)
              if memo.message.match?(/^\w+\-\d+$/)
                resp = http.get("/rest/api/latest/issue/#{memo.message}")
                issues = [resp.data.json] if resp.code == 200
              else
                resp = http.get("/rest/api/latest/search/?jql=#{memo.message}")
                issues = resp.data.json().issues if resp.code == 200
              end
              if resp.code == 200
                unless issues.empty?
                  issues.each do |issue|
                    jira_memo = { jira: true, github: false, status: "", memo_id: memo.memo_id, topic: memo.topic, privacy: memo.privacy, user: memo.user, date: memo.date, message: "", type: memo.type }
                    jira_memo.message = issue.fields.summary
                    jira_memo.user = issue.fields.reporter.name
                    jira_memo.date = issue.fields.created
                    if memo.topic == :no_topic and !issue.fields.labels.empty?
                      jira_memo.topic = issue.fields.labels.sort.join("_").split(" ").join("_")
                    end
                    case issue.fields.issuetype.name
                    when "Story"; jira_memo.type = ":abc:"
                    when "Bug"; jira_memo.type = ":bug:"
                    when "Task"; jira_memo.type = ":clock1:"
                    when "New Feature", "Improvement"; jira_memo.type = ":sunny:"
                    else jira_memo.type = ":memo:"
                    end
                    case issue.fields.status.statusCategory.name
                    when "Done"; jira_memo.status = ":heavy_check_mark:"
                    when "To Do"; jira_memo.status = ":new:"
                    when "In Progress"; jira_memo.status = ":runner:"
                    else jira_memo.status = ":heavy_minus_sign:"
                    end
                    #todo: check if possible to add link to status instead of jira issue
                    #jira_memo.status = " <#{config.jira.host}/browse/#{issue[:key]}|#{jira_memo.status}> "
                    jira_memo.status += " <#{config.jira.host}/browse/#{issue[:key]}|#{issue[:key]}>"

                    all_memos[jira_memo.topic] ||= []
                    all_memos[jira_memo.topic] << jira_memo
                  end
                end
              end
              http.close
            elsif memo.type == "github" and config.github.host != ""
              http = NiceHttp.new(config.github.host)
              http.headers.authorization = "token #{config.github.token}"
              memo.message += "?" unless memo.message.include?("?")
              memo.message += "&per_page=50"

              resp = http.get("/repos/#{memo.message}")
              issues = resp.data.json()
              issues = [issues] unless issues.is_a?(Array)
              if resp.code == 200
                unless issues.empty?
                  issues.each do |issue|
                    github_memo = { jira: false, github: true, status: "", memo_id: memo.memo_id, topic: memo.topic, privacy: memo.privacy, user: memo.user, date: memo.date, message: "", type: memo.type }
                    github_memo.message = issue.title
                    github_memo.user = issue.user.
                    github_memo.date = issue.created_at
                    if issue.labels.empty?
                      labels = ""
                    else
                      labels = issue.labels.name.sort.join("_").split(" ").join("_")
                    end
                    if memo.topic == :no_topic and !issue.labels.empty?
                      github_memo.topic = labels
                    end
                    case labels
                    when /bug/i; github_memo.type = ":bug:"
                    when /docum/i; github_memo.type = ":abc:"
                    when /task/i; github_memo.type = ":clock1:"
                    when /enhancem/i, /improvement/i; github_memo.type = ":sunny:"
                    else github_memo.type = ":memo:"
                    end
                    if issue.key?(:events_url)
                      resp_events = http.get(issue.events_url)
                      events = resp_events.data.json(:event)
                      issue.state = "in progress" if events.include?("referenced")
                    end
                    case issue.state
                    when "closed"; github_memo.status = ":heavy_check_mark:"
                    when "open"; github_memo.status = ":new:"
                    when "in progress"; github_memo.status = ":runner:"
                    else github_memo.status = ":heavy_minus_sign:"
                    end
                    #todo: check if possible to add link to status instead of github issue
                    github_memo.status += " <#{issue.html_url}|##{issue.number}>"

                    all_memos[github_memo.topic] ||= []
                    all_memos[github_memo.topic] << github_memo
                  end
                end
              end
              http.close
            else
              memo.jira = false
              memo.github = false
              all_memos[memo.topic] ||= []
              case memo.type
              when "memo"; memo.type = ":memo:"
              when "note"; memo.type = ":abc:"
              when "bug"; memo.type = ":bug:"
              when "task"; memo.type = ":clock1:"
              when "feature"; memo.type = ":sunny:"
              when "issue"; memo.type = ":hammer:"
              else memo.type = ":heavy_minus_sign:"
              end
              all_memos[memo.topic] << memo
            end
          end
        end
        if !add_stats
          message << "   > *_memos_*" unless all_memos.empty?
        else
          message << "  > *_#{team.name} team #{type}_*"
        end

        if all_memos.key?(:no_topic)
          all_memos[:no_topic].sort_by { |memo| memo[:date] }.each do |memo|
            case memo.privacy
            when "private"; priv = " `private`"
            when "personal"; priv = " `personal`"
            else priv = ""
            end
            message << "        #{memo.type} #{memo.date.gsub("-", "/")[0..9]}:  #{memo.status} #{memo.message} (#{memo.user} #{memo.memo_id})#{priv}"
          end
        end
        all_memos[:no_topic] = []
        all_memos.each do |topic, mems|
          unless mems.empty?
            message << "        _`#{topic}`_:"
            mems.sort_by { |m| m[:date] }.each do |memo|
              case memo.privacy
              when "private"; priv = " `private`"
              when "personal"; priv = " `personal`"
              else priv = ""
              end
              message << "            #{memo.type} #{memo.date.gsub("-", "/")[0..9]}:  #{memo.status} #{memo.message} (#{memo.user} #{memo.memo_id})#{priv}"
            end
          end
        end
      else
        message << "There are no memos #{type} #{topic}." unless !add_stats
      end
    else
      respond "There is no team named #{name}." unless !add_stats
    end
    if add_stats
      respond message.join("\n")
    else
      return message
    end
  else
    respond "There are no teams added yet\. Use `add team` command to add a team" unless !add_stats
  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
23
# 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__)
  from = user.name
  if has_access?(__method__, user)
    message = ""
    @repls.sort.to_h.each do |session_name, repl|
      if (repl.creator_name == user.name or repl.type == :public or repl.type == :public_clean) or (is_admin?(user.name) 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
# 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?
    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.name) and @rules_imported[user.name].key?(user.name) and dest[0] == "D"
        routines = YAML.load(File.read("#{config.path}/routines/routines_#{@rules_imported[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
  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_deleted', '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
# 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
  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?(from) and @shortcuts[from].keys.size > 0
        new_hash = @shortcuts[from].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?(from) and @shortcuts_global[from].keys.size > 0
        new_hash = @shortcuts_global[from].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_teams(user, team_name, search = "", add_stats: 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
# File 'lib/slack/smart-bot/commands/general/see_teams.rb', line 2

def see_teams(user, team_name, search = "", add_stats: true)
  save_stats(__method__) if add_stats

  get_teams()
  teams = @teams.deep_copy
  if teams.empty?
    respond "There are no teams added yet. Use `add team` command to add a team."
  elsif team_name.to_s != "" and !teams.key?(team_name.to_sym) and (teams.keys.select { |t| (t.to_s.gsub("-", "").gsub("_", "") == team_name.to_s) }).empty?
    respond "It seems like the team *#{team_name}* doesn't exist.\nRelated commands `add team TEAM_NAME PROPERTIES`, `see team TEAM_NAME`, `see teams`"
  else
    users_link = (Thread.current[:dest][0] == "D")
    filter = (search != "")
    react :runner
    @users = get_users() if add_stats

    messages = []
    search_members = []
    search_channels = []
    search_info = []
    if filter
      search.split(" ").each do |s|
        if s.match(/<@(\w+)>/i)
          m = $1
           = @users.select { |u| u.id.downcase == m.downcase or (u.key?(:enterprise_user) and u.enterprise_user.id.downcase == m.downcase) }[-1]
          search_members << .name unless .nil?
        elsif s.match(/<#(\w+)\|[^>]*>/i)
          c = $1.upcase
          search_channels << @channels_name[c] if @channels_name.key?(c)
        else
          search_info << s
        end
      end
    end
    if team_name.to_s == "" and search.to_s == ""
      dest = :on_thread
      messages.unshift("Since there are many lines returned the results are returned on a thread by default.")
    else
      dest = Thread.current[:dest]
    end

    teams.each do |name, team|
      filter ? add = false : add = true
      if team_name.to_s == "" or (team_name.to_s == name.to_s) or (name.to_s.gsub("-", "").gsub("_", "") == team_name.to_s)
        message = []
        message << "*#{name.capitalize}*"

        if filter and search_info.size > 0
          all_info = true
          search_info.each do |s|
            if (team.members.keys.find { |e| /#{s}/i =~ e })
              add = true
              break
            end
            if !name.match?(/#{s}/i)
              all_info = false
              break
            end
          end
          add = true if all_info
        end

        message << "   > *_members_*"

        assigned_members, unassigned_members, not_on_team_channel, channels_members, all_team_members = get_team_members(team)

        unless unassigned_members.empty?
          um = unassigned_members.dup
          um.each do |m|
             = @users.select { |u| u.name == m or (u.key?(:enterprise_user) and u.enterprise_user.name == m) }[-1]
            unless .nil? or .profile.title.to_s == ""
              team.members[.profile.title.to_snake_case] ||= []
              team.members[.profile.title.to_snake_case] << m
              unassigned_members.delete(m)
            end
          end
          unless unassigned_members.empty?
            team.members["unassigned"] ||= []
            team.members["unassigned"] += unassigned_members
            team.members["unassigned"].sort!
          end
        end
        unless not_on_team_channel.empty?
          team.members["not on members channel"] = not_on_team_channel
          team.members["not on members channel"].sort!
        end
        add = true if (team.members.values.flatten & search_members).size > 0
        add = true if (team.channels.values.flatten & search_channels).size > 0
        if filter and search_info.size > 0
          all_info = true
          search_info.each do |s|
            if (team.members.keys.find { |e| /#{s}/i =~ e })
              add = true
              break
            end
            if !team.info.match?(/#{s}/i)
              all_info = false
              break
            end
          end
          add = true if all_info
        end

        if add
          if team_name.to_s != ""
            team.members.each do |type, members|
              message << "        _`#{type}`_:  "
              members.each do |member|
                types = [":palm_tree:", ":spiral_calendar_pad:", ":face_with_thermometer:", ":baby:"]
                member_info = @users.select { |u| u.name == member }[-1]
                if !member_info.nil? and !member_info.deleted
                  member_id = member_info.id
                  info = (member_id)
                  emoji = info.user.profile.status_emoji
                  if types.include?(emoji)
                    status = emoji
                  else
                    active = (get_presence(member_id).presence.to_s == "active")
                    if active
                       = @users.select { |u| u.id == member_id or (u.key?(:enterprise_user) and u.enterprise_user.name == member_id) }[-1]
                      if (.tz_offset - user.tz_offset).abs <= (4 * 3600)
                        status = ":large_green_circle:"
                      else
                        status = ":large_yellow_circle:"
                      end
                    else
                      status = ":white_circle:"
                    end
                  end
                else
                  status = ":exclamation:"
                end
                unless status == ":exclamation:"
                  if users_link
                    message[-1] += "  #{status}<@#{member}>, "
                  else
                     = @users.select { |u| u.name == member or (u.key?(:enterprise_user) and u.enterprise_user.name == member) }[-1]
                    unless .nil?
                      if .profile.display_name == ""
                        name = .name
                      else
                        name = .profile.display_name
                      end
                      message[-1] += "  #{status} #{name}, "
                    end
                  end
                end
              end
              message[-1].chop!
              message[-1].chop!
            end
          else
            team.members.each do |type, members|
              if users_link
                message << "        _`#{type}`_:  <@#{members.join(">,  <@")}>"
              else
                membersn = []
                members.each do |m|
                   = @users.select { |u| u.name == m or (u.key?(:enterprise_user) and u.enterprise_user.name == m) }[-1]
                  unless .nil? or .deleted
                    if .profile.display_name == ""
                      name = .name
                    else
                      name = .profile.display_name
                    end
                    membersn << name
                  end
                end
                message << "        _`#{type}`_:  #{membersn.join("  /  ")}"
              end
            end
          end
        end

        if add
          message << "   > *_channels_*"
          team.channels.each do |type, channels|
            channel_ids = []
            channels.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 or (channel_info.is_private and (team.members.values + [team.creator]).flatten.include?(user.name)))
                channel_ids << @channels_id[ch]
              end
            end
            message << "        _`#{type}`_:  <##{channel_ids.join("> <#")}>" unless channel_ids.empty?
          end

          unless !team.key?(:memos) or team.memos.empty? or (team_name.to_s == "" and search.to_s == "")
            message += see_memos_team(user, type: "all", add_stats: false, team: team)
          end

          unless team.info.empty?
            team.info.split("\n").each do |m|
              message << ">#{m}"
            end
            message << "> "
            message << "> "
          end
          messages << message.join("\n")
        end
      end
    end
    unreact :runner
    if messages.empty?
      if filter
        respond "It seems like we didn't find any team with the criteria supplied. Call `see teams` for a full list of teams."
      else
        respond "It seems like there are no teams added.\nUse `add team TEAM_NAME PROPERTIES` to add one. Call `bot help add team` for extended info."
      end
    else
      if team_name.to_s != ""
        message = "\n\n:palm_tree: On vacation / "
        message += ":spiral_calendar_pad: In a meeting / "
        message += ":face_with_thermometer: :baby: Sick leave / "
        message += ":white_circle: Away / "
        message += ":large_yellow_circle: Available in remote timezone / "
        message += ":large_green_circle: Available"
        messages[-1] << message
        messages[-1] << "\n:information_source: Remote Time zone is >4h away from your current (#{user.tz_label})"
      end
      messages.each do |msg|
        respond msg, dest, unfurl_links: false, unfurl_media: false
      end
      unless team_name.to_s.empty?
        see_vacations_team(user, team_name, Date.today.strftime("%Y/%m/%d"), add_stats: false)
      end
    end
  end
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
# 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()
  
  from_user_name = ''
  year = Date.today.year if year.to_s == ''

  if from_user.empty?
    from_user_name = user.name
  else
    @users = get_users() if @users.empty?
     = @users.select{|u| u.id == from_user or (u.key?(:enterprise_user) and u.enterprise_user.id == from_user)}[-1]
    from_user_name = .name
  end
  from_user = '' if from_user_name == user.name
  if !@vacations.key?(from_user_name) or !@vacations[from_user_name].key?(:periods) or @vacations[from_user_name].periods.empty?
    if from_user.empty?
      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 == user.name and dest[0] == 'D'

    today = Date.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 
        messages << "*Current and future periods*" 
        current_added = true
      end
      if !past_added and vac.to < today 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
        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 != user.name
      messages << "Not possible to see past periods for another user"
    end
    respond messages.join("\n")
  end
end

#see_vacations_team(user, team_name, date, add_stats: 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
# File 'lib/slack/smart-bot/commands/general/see_vacations_team.rb', line 2

def see_vacations_team(user, team_name, date, add_stats: true)
  save_stats(__method__) if add_stats

  get_teams()
  teams = @teams.deep_copy
  if teams.empty?
    respond "There are no teams added yet. Use `add team` command to add a team."
  elsif team_name.to_s != "" and !teams.key?(team_name.to_sym) and (teams.keys.select { |t| (t.to_s.gsub("-", "").gsub("_", "") == team_name.to_s) }).empty?
    respond "It seems like the team *#{team_name}* doesn't exist.\nRelated commands `add team TEAM_NAME PROPERTIES`, `see team TEAM_NAME`, `see teams`"
  else
    teams.each do |name, team|
      if team_name==name.to_s or (name.to_s.gsub("-", "").gsub("_", "") == team_name.to_s)
        team_name = name.to_s
        break
      end
    end
    date.gsub!('-','/')
    get_vacations()
    team = teams[team_name.to_sym]
    assigned_members = team.members.values.flatten
    assigned_members.uniq!
    assigned_members.dup.each do |m|
       = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) or u.name == m or (u.key?(:enterprise_user) and u.enterprise_user.name == m) }[-1]
      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|
             = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) }[-1]
            team_members << .name unless .is_app_user or .is_bot
          end
        end
      end
      team_members.flatten!
      all_team_members += team_members
      all_team_members.uniq!
    end
    unless all_team_members.empty?
      blocks_header = 
        {
                  "type": "context",
                  elements: [
                    {
                        type: "mrkdwn",
                        text: "*Time Off #{team_name} team* from #{date} ",
                      },
                  ],
                }
      
      from = Date.parse(date, "%Y/%m/%d")
      blocks = []
      all_team_members.each do |m|
        @users = get_users() if @users.empty?
        info = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) or u.name == m or (u.key?(:enterprise_user) and u.enterprise_user.name == m) }[-1]
        unless info.nil?
          info = (info.id)
          if @vacations.key?(m)
            v = ""
            (from..(from+20)).each do |d|
              v+="#{d.strftime("%d")} " if d.wday==1 or d==from
              on_vacation = false
              @vacations[m].periods.each do |p|
                if p.from <= d.strftime("%Y/%m/%d") and p.to >= d.strftime("%Y/%m/%d")
                  if d.wday == 0 or d.wday == 6
                    v+=":large_orange_square: "
                  else
                    v+=":large_red_square: "
                  end
                  on_vacation=true
                  break
                end 
              end
              unless on_vacation
                if d.wday == 0 or d.wday == 6
                  v += ":large_yellow_square: "
                else
                  v+= ":white_square: " 
                end
              end
            end
          else
            v = ""
            (from..(from+20)).each do |d|
              if d.wday==1 or d==from
                v += "#{d.strftime("%d")} " 
              end
              if d.wday == 0 or d.wday == 6
                v += ":large_yellow_square: "
              else
                v += ":white_square: "
              end
            end
          end

          blocks << {
            type: "context",
            elements: [
              {
                          type: "image",
                          image_url: info.user.profile.image_24,
                          alt_text: info.user.name,
                        },
                        {
                          type: "plain_text",
                          text: v
                        }  
            ],
          }
        end
      end
      first = true
      blocks.each_slice(10).each do |b|
        if first 
          b.unshift(blocks_header)
          first = false
        end
        respond blocks: b
      end

    end


  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
# 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
        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, from, 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
# File 'lib/slack/smart-bot/commands/on_bot/admin_master/send_message.rb', line 16

def send_message(dest, from, typem, to, thread_ts, stats_from, stats_to, stats_channel_filter, stats_command_filter, message)
  save_stats(__method__)
  if config.masters.include?(from) 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|
        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*$/)
          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}|#{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}|#{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) ⇒ 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
# 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)
  resp = nil
  unless msg == "" and blocks.empty?
    begin
      on_thread = Thread.current[:on_thread] if on_thread.nil?
      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}|#{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.current[: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.current[: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}|#{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}|#{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.current[: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.current[: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"]}|#{config[:nick_id]}|#{config[:nick]}|#{msg}#{blocks.join}"
          }
        end
      end
    rescue Exception => stack
      @logger.warn stack
    end
  end
  return resp
end

#set_general_message(from, 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(from, status, message)
  save_stats(__method__)
  if config.on_master_bot
    if config.masters.include?(from) #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
      
      file = File.open("#{config.path}/config_tmp.status", "w")
      file.write config.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(from, 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(from, status, message)
  save_stats(__method__)
  if config.on_master_bot
    if config.masters.include?(from) #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
      
      file = File.open("#{config.path}/config_tmp.status", "w")
      file.write config.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_memo_status(user, team_name, memo_id, status) ⇒ 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/commands/general/set_memo_status.rb', line 2

def set_memo_status(user, team_name, memo_id, status)
  save_stats(__method__) if answer.empty?

  get_teams()
  if @teams.key?(team_name.to_sym)
    assigned_members = @teams[team_name.to_sym].members.values.flatten
    assigned_members.uniq!
    all_team_members = assigned_members.dup
    team_members = []
    if @teams[team_name.to_sym].channels.key?("members")
      @teams[team_name.to_sym].channels["members"].each do |ch|
        get_channels_name_and_id() unless @channels_id.key?(ch)
        tm = get_channel_members(@channels_id[ch])
        tm.each do |m|
           = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) }[-1]
          team_members << .name unless .is_app_user or .is_bot
        end
      end
    end
    team_members.flatten!
    team_members.uniq!
    all_team_members += team_members
    all_team_members.uniq!
  end

  if !@teams.key?(team_name.to_sym)
    respond "It seems like the team *#{team_name}* doesn't exist.\nRelated commands `add team TEAM_NAME PROPERTIES`, `see team TEAM_NAME`, `see teams`"
  elsif !(all_team_members + config.masters).flatten.include?(user.name)
    respond "You have to be a member of the team or a Master admin to be able to set the status of a memo."
  elsif !@teams[team_name.to_sym].key?(:memos) or @teams[team_name.to_sym][:memos].empty? or !@teams[team_name.to_sym][:memos].memo_id.include?(memo_id.to_i)
    respond "It seems like there is no memo with id #{memo_id}"
  elsif @teams[team_name.to_sym][:memos].memo_id.include?(memo_id.to_i)
    memo_selected = @teams[team_name.to_sym][:memos].select { |m| m.memo_id == memo_id.to_i }[-1]
    if memo_selected.type == 'jira' or memo_selected.type == 'github'
      #todo: add tests for jira and github
      respond "The memo specified is a #{memo_selected.type} and the status in those cases are linked to the specific issues in #{memo_selected.type}."
    elsif memo_selected.privacy == "personal" and memo_selected.user != user.name
      respond "Only the creator can set the status of a personal memo."
    else
      answer_delete
      memos = []
      message = ""
      get_teams()
      @teams[team_name.to_sym][:memos].each do |memo|
        if memo.memo_id == memo_id.to_i
          memo.status = status
          message = memo.message
        end
        memos << memo
      end
      @teams[team_name.to_sym][:memos] = memos
      update_teams()
      respond "The memo status has been updated on team #{team_name}: #{message}"
    end
  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
# 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
      if state == ""
          country_region = country
      else
          country_region = "#{country}/#{state}"
      end
      respond "Public holidays for *#{country_region}* set."
      get_vacations()
      @vacations[user.name] ||= {}
      @vacations[user.name][:public_holidays] = country_region
      update_vacations()
  else
      respond "Be sure the country and state are correct."
  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_deleted', '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_deleted', '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.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
# 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

  if dest[0] == "C" or dest[0] == "G" #channel
    if @rules_imported.key?(user.name) and @rules_imported[user.name].key?(dchannel)
      if @rules_imported[user.name][dchannel] != channel_id
        respond "You are not using those rules.", dest
      else
        @rules_imported[user.name].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?(user.name) and @rules_imported[user.name].key?(user.name)
      if @rules_imported[user.name][user.name] != channel_id
        respond "You are not using those rules.", dest
      else
        @rules_imported[user.name].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(from, 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
# File 'lib/slack/smart-bot/commands/on_bot/general/suggest_command.rb', line 15

def suggest_command(from, dest, dchannel, specific, rules_file)
  save_stats(__method__)
  dont_suggest = []
  help_message = get_help(rules_file, dest, from, 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*$/,'')
  message = "*Command suggestion*:\n#{command}"
  respond message, dest, unfurl_links: false, unfurl_media: false
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
# 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}\|[^\>]*>)/).join
                          data_text += tch
                        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.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
    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 != Date.today
      @vacations_check = Date.today
      t = Thread.new do
        check_vacations(only_first_day: true)
      end
    end
    typem = :dont_treat
    if data.nil? or data.user.nil? or data.user.to_s==''
       = nil
      @users = get_users() if @users.empty?
    else
      #todo: when changed @questions user_id then move user_info inside the ifs to avoid calling it when not necessary
       = @users.select{|u| u.id == data.user or (u.key?(:enterprise_user) and u.enterprise_user.id == data.user)}[-1]
      if .nil? or .empty?
        @users = get_users() 
         = @users.select{|u| u.id == data.user or (u.key?(:enterprise_user) and u.enterprise_user.id == data.user)}[-1]
      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?(.name) && @rules_imported[.name].key?(.name) and
          @bots_created.key?(@rules_imported[.name][.name])
          if @channel_id == @rules_imported[.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) > 60 * 30 #every 30 minutes
        @last_activity_check = Time.now
        @listening.each do |k,v|
          v.each do |kk, vv|
            @listening[k].delete(kk) if (Time.now - vv) > 60 * 30
          end
          @listening.delete(k) if @listening[k].empty?
        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
        if data.thread_ts.nil?
          qdest = dest
        else
          qdest = data.thread_ts
        end
        if !answer(.name, 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(.name, qdest)
            command = data.text
          else
            command = answer(.name, qdest)
            @answer[.name][qdest] = data.text
            @questions[.name] = data.text # to be backwards compatible #todo remove it when 2.0
          end
        elsif @repl_sessions.key?(.name) and data.channel==@repl_sessions[.name][:dest] and 
          ((@repl_sessions[.name][:on_thread] and data.thread_ts == @repl_sessions[.name][:thread_ts]) or
          (!@repl_sessions[.name][:on_thread] and data.thread_ts.to_s == '' ))
          
          if data.text.match(/^\s*```(.*)```\s*$/im)
              @repl_sessions[.name][:command] = $1
          else   
            @repl_sessions[.name][: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
          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
      @logger.warn "Pay attention there is no user on users with id #{data.user}" if .nil? and data.user.to_s!=''
      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
        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) ⇒ 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)
  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: Thread.current[:dest], 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
# File 'lib/slack/smart-bot/utils/update_access_channels.rb', line 3

def update_access_channels()
  file = File.open("#{config.path}/rules/#{@channel_id}/access_channels.rb", "w")
  file.write (@access_channels.inspect)
  file.close
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(from, 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(from, typem, url, text)
  save_stats(__method__)
  channel, ts = url.scan(/\/archives\/(\w+)\/(\w\d+)/)[0]
  if config.masters.include?(from) 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_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
# File 'lib/slack/smart-bot/utils/update_rules_imported.rb', line 3

def update_rules_imported
  file = File.open("#{config.path}/rules/rules_imported.rb", "w")
  file.write @rules_imported.inspect
  file.close
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_team(user, team_name, new_name: "", new_info: "", delete_opts: "", add_opts: "") ⇒ 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
# File 'lib/slack/smart-bot/commands/general/update_team.rb', line 2

def update_team(user, team_name, new_name: "", new_info: "", delete_opts: "", add_opts: "")
  save_stats(__method__)
  get_teams()
  if @teams.key?(team_name.to_sym)
    assigned_members = @teams[team_name.to_sym].members.values.flatten
    assigned_members.uniq!
    all_team_members = assigned_members.dup
    team_members = []
    if @teams[team_name.to_sym].channels.key?("members")
      @teams[team_name.to_sym].channels["members"].each do |ch|
        get_channels_name_and_id() unless @channels_id.key?(ch)
        tm = get_channel_members(@channels_id[ch])
        tm.each do |m|
           = @users.select { |u| u.id == m or (u.key?(:enterprise_user) and u.enterprise_user.id == m) }[-1]
          team_members << .name unless .is_app_user or .is_bot
        end
      end
    end
    team_members.flatten!
    team_members.uniq!
    all_team_members += team_members
    all_team_members.uniq!
  end
  if !@teams.key?(team_name.to_sym)
    respond "It seems like the team *#{team_name}* doesn't exist.\nRelated commands `add team TEAM_NAME PROPERTIES`, `see team TEAM_NAME`, `see teams`"
  elsif !(all_team_members + [@teams[team_name.to_sym].creator] + config.masters).flatten.include?(user.name)
    respond "You have to be a member of the team, the creator or a Master admin to be able to update this team."
  else
    wrong = false
    if new_name != ""
      team = @teams[team_name.to_sym].deep_copy
      @teams[new_name.to_sym] = team
      @teams.delete(team_name.to_sym)
      File.delete(File.join(config.path, "teams", "t_#{team_name}.yaml"))
      message = "The *#{team_name}* team has been renamed #{new_name}."
      team_name = new_name
    elsif new_info != ""
      @teams[team_name.to_sym].info = new_info
    elsif delete_opts != ""
      last_type = nil
      delete_opts.split(" ").each do |opt|
        if opt.match?(/^\s*$/)
          #blank
        elsif opt.match?(/^[\w\-]+$/i)
          last_type = opt
        elsif opt.match(/<?@(\w+)>?/i) #accepts also @username for the case the user has been deactivated
          member_id = $1
          member_info = @users.select { |u| u.id == member_id or u.name == member_id or (u.key?(:enterprise_user) and u.enterprise_user.id == member_id) }[-1]
          if last_type.nil?
            @teams[team_name.to_sym].members.each do |type, members|
              @teams[team_name.to_sym].members[type].delete(member_info.name)
            end
          else
            @teams[team_name.to_sym].members[last_type] ||= []
            @teams[team_name.to_sym].members[last_type].delete(member_info.name)
          end
        elsif opt.match(/<#(\w+)\|[^>]*>/i)
          channel_id = $1
          get_channels_name_and_id() unless @channels_name.keys.include?(channel_id)
          channel = @channels_name[channel_id]
          if last_type.nil?
            @teams[team_name.to_sym].channels.each do |type, channels|
              @teams[team_name.to_sym].channels[type].delete(channel)
            end
          else
            @teams[team_name.to_sym].channels[last_type] ||= []
            @teams[team_name.to_sym].channels[last_type].delete(channel)
          end
        else
          respond "It seems like the members or channel list is not correct. Please double check."
          wrong = true
          break
        end
      end
      tmembers = @teams[team_name.to_sym].members.deep_copy
      tmembers.each do |type, members|
        @teams[team_name.to_sym].members.delete(type) if members.empty?
      end
      tchannels = @teams[team_name.to_sym].channels.deep_copy
      tchannels.each do |type, channels|
        @teams[team_name.to_sym].channels.delete(type) if channels.empty?
      end
    elsif add_opts != ""
      last_type = nil
      add_opts.split(" ").each do |opt|
        if opt.match?(/^\s*$/)
          #blank
        elsif opt.match?(/^[\w\-]+$/i)
          last_type = opt
        elsif opt.match(/<@(\w+)>/i)
          member_id = $1
          last_type = 'no_type' if last_type.nil?
          member_info = @users.select { |u| u.id == member_id or (u.key?(:enterprise_user) and u.enterprise_user.id == member_id) }[-1]
          @teams[team_name.to_sym].members[last_type] ||= []
          @teams[team_name.to_sym].members[last_type] << member_info.name
          @teams[team_name.to_sym].members[last_type].uniq!
        elsif opt.match(/<#(\w+)\|[^>]*>/i)
          channel_id = $1
          get_channels_name_and_id() unless @channels_name.keys.include?(channel_id)
          channel = @channels_name[channel_id]
          @teams[team_name.to_sym].channels[last_type] ||= []
          @teams[team_name.to_sym].channels[last_type] << channel
          @teams[team_name.to_sym].channels[last_type].uniq!
        else
          respond "It seems like the members or channel list is not correct. Please double check."
          wrong = true
          break
        end
      end
      tmembers = @teams[team_name.to_sym].members.deep_copy
      tmembers.each do |type, members|
        @teams[team_name.to_sym].members.delete(type) if members.empty?
      end
      tchannels = @teams[team_name.to_sym].channels.deep_copy
      tchannels.each do |type, channels|
        @teams[team_name.to_sym].channels.delete(type) if channels.empty?
      end
    end
    unless wrong
      message ||= "The *#{team_name}* team has been updated."
      @teams[team_name.to_sym].status = :updated
      @teams[team_name.to_sym].user = user.name
      @teams[team_name.to_sym].date = Time.now.strftime("%Y-%m-%dT%H:%M:%S.000Z")[0..18]
      update_teams()
    end
    respond message
    see_teams(user, team_name, add_stats: false) unless wrong
  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(encrypt(@teams[team].to_yaml))
      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
# 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]
  vacations_file = File.join(config.path, "vacations", "v_#{user.name}.yaml")

  File.open(vacations_file, 'w') {|file|
    file.flock(File::LOCK_EX)
    file.write(encrypt(@vacations[user.name].to_yaml))
    file.flock(File::LOCK_UN)
  }
  @datetime_vacations_file[vacations_file] = File.mtime(vacations_file)
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
# 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)
        @rules_imported[user.name] = {} unless @rules_imported.key?(user.name)
        if dest[0] == "C" or dest[0] == "G" #todo: take in consideration bots that are not master
          @rules_imported[user.name][dchannel] = channel_found.id
        else
          @rules_imported[user.name][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