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/respond.rb,
lib/slack/smart-bot/comm/unreact.rb,
lib/slack/smart-bot/utils/answer.rb,
lib/slack/smart-bot/process_first.rb,
lib/slack/smart-bot/treat_message.rb,
lib/slack/smart-bot/comm/send_file.rb,
lib/slack/smart-bot/utils/get_help.rb,
lib/slack/smart-bot/utils/get_repls.rb,
lib/slack/smart-bot/comm/event_hello.rb,
lib/slack/smart-bot/utils/build_help.rb,
lib/slack/smart-bot/utils/save_stats.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/comm/respond_direct.rb,
lib/slack/smart-bot/utils/answer_delete.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/update_routines.rb,
lib/slack/smart-bot/utils/get_bots_created.rb,
lib/slack/smart-bot/utils/remove_hash_keys.rb,
lib/slack/smart-bot/utils/update_bots_file.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/bot_help.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/commands/general/bot_stats.rb,
lib/slack/smart-bot/commands/general/use_rules.rb,
lib/slack/smart-bot/commands/general/whats_new.rb,
lib/slack/smart-bot/commands/general/bot_status.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/update_rules_imported.rb,
lib/slack/smart-bot/utils/update_shortcuts_file.rb,
lib/slack/smart-bot/commands/on_bot/add_shortcut.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/on_extended/bot_rules.rb,
lib/slack/smart-bot/utils/get_channels_name_and_id.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/stop_using_rules.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/admin/extend_rules.rb,
lib/slack/smart-bot/commands/on_bot/admin/see_routines.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/admin/remove_routine.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_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

Constant Summary collapse

VERSION =
version

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ SlackSmartBot

Returns a new instance of SlackSmartBot.



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

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)

  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")

  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 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}.rb".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)
  @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

  unless config.simulate and config.key?(:client)
    Slack.configure do |conf|
      conf.token = config[:token]
    end
  end
  restarts = 0
  created = false
  while restarts < 200 and !created
    begin
      @logger.info "Connecting #{config_log.inspect}"
      if config.simulate and config.key?(:client)
        self.client = config.client
      else
        self.client = Slack::RealTime::Client.new(start_method: :rtm_connect)
      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}"
        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()

  if File.exist?("#{config.path}/shortcuts/#{config.shortcuts_file}")
    file_sc = IO.readlines("#{config.path}/shortcuts/#{config.shortcuts_file}").join
    unless file_sc.to_s() == ""
      @shortcuts = eval(file_sc)
    end
  end
  if File.exist?("#{config.path}/shortcuts/shortcuts_global.rb")
    file_sc = IO.readlines("#{config.path}/shortcuts/shortcuts_global.rb").join
    unless file_sc.to_s() == ""
      @shortcuts_global = eval(file_sc)
    end
  end

  get_routines()
  get_repls()

  if config.on_master_bot and File.exist?(config.file_path.gsub(".rb", "_bots.rb"))
    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)
          @logger.info "ruby #{config.file_path} \"#{value[:channel_name]}\" \"#{value[:admins]}\" \"#{value[:rules_file]}\" #{value[:status].to_sym}"
          puts "Starting #{value[:channel_name]} Smart Bot"
          t = Thread.new do
            `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

  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"
    abort("TooManyRequestsError please re run the bot and be sure of executing first: killall ruby")
  rescue Exception => stack
    pp stack if config.testing
    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()
  @channels_id = Hash.new()
  @channels_name = Hash.new()
  get_channels_name_and_id()
  @channel_id = @channels_id[config.channel].dup
  @master_bot_id = @channels_id[config.master_channel].dup

  get_routines()
  get_repls()
  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)
        end
      end
    end
  else
    client.on :close do |_data|
      m = "Connection closing, exiting. #{Time.now}"
      @logger.info m
      @logger.info _data
    end

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

Instance Attribute Details

#channel_idObject (readonly)

Returns the value of attribute channel_id.



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

def channel_id
  @channel_id
end

#clientObject

Returns the value of attribute client.



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

def client
  @client
end

#configObject

Returns the value of attribute config.



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

def config
  @config
end

#master_bot_idObject (readonly)

Returns the value of attribute master_bot_id.



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

def master_bot_id
  @master_bot_id
end

Instance Method Details

#add_routine(dest, from, user, name, type, number_time, period, command_to_run, files, silent, channel) ⇒ 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 silent routine 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 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 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. 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: Examples: helpadmin: add routine example every 30s ruby puts 'a' helpadmin: add routine 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 routine example on Mondays at 05:00 !run customer tests helpadmin: add routine example on Tuesdays at 09:00 #SREChannel !run db cleanup helpadmin:



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

def add_routine(dest, from, user, name, type, number_time, period, command_to_run, files, silent, channel)
  save_stats(__method__)
  if files.nil? or files.size == 0 or (files.size > 0 and config.masters.include?(from))
    if config.admins.include?(from)
      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 = ''
          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'
              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
            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
            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)
            system("chmod +x #{file_path}")
          end
          channel = dest if channel.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, file_path: file_path, 
                                           command: command_to_run.to_s.strip, silent: silent,
                                           next_run: next_run.to_s, dest: channel, last_run: "", last_elapsed: "", 
                                           running: false }
          update_routines
          respond "Added routine *`#{name}`* to the channel", dest
          create_routine_thread(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:



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

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 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(">, <@")}>"
    else

      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 !config.admins.include?(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

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

def ask(question, context = nil, to = nil, dest = nil)
  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
      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
      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
end

#bot_help(user, from, dest, dchannel, specific, help_command, rules_file) ⇒ Object

help: ---------------------------------------------- help: bot help help: bot help COMMAND help: bot rules help: bot rules COMMAND help: bot help expanded help: bot rules expanded help: bot what can I do? help: it will display this help. For a more detailed help call bot help expanded or bot rules expanded. help: if COMMAND supplied just help for that command help: you can use the option 'expanded' or the alias 'extended' help: bot rules will show only the specific rules for this channel. help:



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

def bot_help(user, from, dest, dchannel, specific, help_command, rules_file)
  save_stats(__method__)
  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(">, <@")}>"
  else
    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)

    if help_command.to_s != ""
      help_message.gsub(/====+/,'-'*30).split(/^\s*-------*$/).each do |h|
        if h.match?(/[`_]#{help_command}/i)
          respond h, dest
          help_found = true
        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
      respond message, dest
    end

    if (help_command.to_s == "")
      help_message.split(/^\s*=========*$/).each do |h|
        respond("#{"=" * 35}\n#{h}", dest) unless h.match?(/\A\s*\z/)
      end
    else
      unless help_found
        if specific
          respond("I didn't find any rule starting by `#{help_command}`", dest)
        else
          respond("I didn't find any command starting by `#{help_command}`", dest)
        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 == "")
        respond "Git project: #{git_project}", dest
      else
        def git_project
          ""
        end

        def project_folder
          ""
        end
      end
    elsif help_command.to_s == ""
      respond "Slack Smart Bot Github project: https://github.com/MarioRuiz/slack-smart-bot", dest
    end
    respond message_not_expanded unless expanded
  end
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
# 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 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(">, <@")}>"
  else
    if typem == :on_extended or typem == :on_call #for the other cases above.

      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)

      if help_command.to_s != ""
        help_found = false
        help_filtered.split(/^\s*-------*$/).each do |h|
          if h.match?(/[`_]#{help_command}/i)
            respond "*#{config.channel}*:#{h}", dest
            help_found = true
          end
        end
        respond("*#{config.channel}*: I didn't find any command starting by `#{help_command}`", dest) unless help_found
      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
        respond message, dest
      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 == ""
        respond "Git project: #{git_project}", dest
      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"
        respond message_not_expanded
      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) ⇒ 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 today 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 helpmaster: You need to set stats to true to generate the stats when running the bot instance. 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:



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

def bot_stats(dest, from_user, typem, channel_id, from, to, user, st_command, exclude_masters, exclude_routines, exclude_command, monthly, all_data)
    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 (from_user.id != user and (config.masters.include?(from_user.name) or @master_admin_users_id.include?(from_user.id)) and (typem==:on_dm or dest[0]=='D'))
        on_dm_master = true #master admin user
    else
        on_dm_master = false
    end
    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" 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

            if user!=''
                 = (user)
                if users_id_name.key?(.user.id)
                    user_name = users_id_name[.user.id]
                else
                    user_name = .user.name
                end
                if users_name_id.key?(.user.name)
                    user_id = users_name_id[.user.name]
                else
                    user_id = .user.id
                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" or 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 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 == ''
                                            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
            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 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]
                        if on_dm_master
                            message << "\t#{k}: #{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}: #{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}
                        message << "\t#{channel}: #{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|
                    message << "\t#{ch}: #{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|
                        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 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
                    count_user.sort_by {|k,v| -v}.each do |user, count|
                        i+=1
                        if i <= 10
                            message << "\t#{users_id_name[user]}: #{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
                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
    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:



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

def bot_status(dest, user)
  save_stats(__method__)
  get_bots_created()
  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(">, <@")}>"
  else
    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)
      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*/i)
              resf += "\n     Example: #{line}"
              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

help: ---------------------------------------------- help: Bye Bot help: Bye Smart help: Bye NAME_OF_THE_BOT help: Bot stops listening to you help: Also apart of Bye you can use Bæ, Good Bye, Adiós, Ciao, Bless, Bless Bless, Adeu help:



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

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

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

helpmaster: ---------------------------------------------- helpmaster: create bot on CHANNEL_NAME helpmaster: create cloud 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:



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/on_master/create_bot.rb', line 10

def create_bot(dest, user, cloud, channel)
  save_stats(__method__)
  from = user.name
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!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(">, <@")}>"
  else
    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_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)
              rules_file = RULES_FOLDER + rules_file
              general_rules_file = RULES_FOLDER + 'general_rules.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"
            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")
            
            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)
            admin_users = Array.new()
            creator_info = (channel_found.creator)
            admin_users = [from, creator_info.user.name] + config.masters
            admin_users.uniq!
            @logger.info "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=false 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,
            }
            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) ⇒ 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
# File 'lib/slack/smart-bot/utils/create_routine_thread.rb', line 3

def create_routine_thread(name)
  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
        @logger.info "Routine: #{@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][: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
            }            
            save_stats(name, data: data)
            stdout, stderr, status = Open3.capture3(process_to_run)
            if !@routines[@channel_id][name][:silent] or (@routines[@channel_id][name][:silent] and 
              (!stderr.match?(/\A\s*\z/) or !stdout.match?(/\A\s*\z/)))
              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
            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 #command
            if !@routines[@channel_id][name][:silent]
              if @routines[@channel_id][name][:dest]!=@channel_id
                respond "routine from <##{@channel_id}> *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest]
              else
                respond "routine *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest]
              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 }
            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
        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?(:dayweek) and @routines[@channel_id][name][:dayweek].to_s!=''
            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
          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
              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
        sleep 30
      end
    end
  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:



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/on_bot/delete_repl.rb', line 9

def delete_repl(dest, user, session_name)
  #todo: add tests
  save_stats(__method__)
  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(">, <@")}>"
  else
    if @repls.key?(session_name)
      Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
      if config.admins.include?(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_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:



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

def delete_shortcut(dest, user, shortcut, typem, command, global)
  save_stats(__method__)
  unless typem == :on_extended
    from = user.name
    if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
      (!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(">, <@")}>"
    else
      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 !config.admins.include?(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
            (config.admins.include?(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 !config.admins.include?(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
              (config.admins.include?(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

#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 availalbe." 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

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

def event_hello()
  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
    @logger.info m
    config.nick = client.self.name
    config.nick_id = client.self.id
  end
  @salutations = [config[:nick], "<@#{config[:nick_id]}>", "bot", "smart"]

  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
    ENV['BOT_SILENT'] = 'true' if config[:silent] == 'true' and ENV['BOT_SILENT'].to_s != 'true'
    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
  @routines.each do |ch, rout|
    rout.each do |k, v|
      if !v[:running] and v[:channel_name] == config.channel
        create_routine_thread(k)
      end
    end
  end
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:



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

def exit_bot(command, from, dest, display_name)
  save_stats(__method__)
  if config.on_master_bot
    if config.admins.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}")
            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:



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/commands/on_bot/admin/extend_rules.rb', line 8

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 !config.admins.include?(from) #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_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_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:



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

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

def get_bots_created
  if File.exist?(config.file_path.gsub(".rb", "_bots.rb"))
    if !defined?(@datetime_bots_created) or @datetime_bots_created != File.mtime(config.file_path.gsub(".rb", "_bots.rb"))
      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
      @datetime_bots_created = File.mtime(config.file_path.gsub(".rb", "_bots.rb"))
      @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].gsub!(/^\./, '')
      end
    end
  end
end

#get_channels_name_and_idObject



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

def get_channels_name_and_id
  channels = get_channels()
  @channels_id = Hash.new()
  @channels_name = Hash.new()
  channels.each do |ch|
    unless ch.is_archived
      @channels_id[ch.name] = ch.id
      @channels_name[ch.id] = ch.name
    end
  end
end

#get_help(rules_file, dest, from, only_rules, expanded) ⇒ 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
# File 'lib/slack/smart-bot/utils/get_help.rb', line 2

def get_help(rules_file, dest, from, only_rules, expanded)
  order = {
    general: [:whats_new, :hi_bot, :bye_bot, :bot_help, :bot_status, :use_rules, :stop_using_rules, :bot_stats],
    on_bot: [:ruby_code, :repl, :get_repl, :run_repl, :delete_repl, :see_repls, :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, :run_routine]
  }
  if config.masters.include?(from)
    user_type = :master # master admin
  elsif config.admins.include?(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

  @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 = 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
    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
    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
    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.

    The commands you will be able to use from a channel without a bot: 
    *bot rules*, *ruby CODE*, *add shortcut NAME: COMMAND*, *delete shortcut NAME*, *see shortcuts*, *shortcut NAME*
    *And all the specific rules of the Channel*\n"
  end

  if help.key?(:general)
    unless channel_type == :direct
      txt += "===================================
      *General commands even when the Smart Bot is not listening to you:*\n"
    end
    order.general.each do |o|
      txt += help.general[o]
    end
    if channel_type == :master_bot
      txt += help.on_master.create_bot
    end
  end

  if help.key?(:on_bot)
    unless channel_type == :direct
      txt += "===================================
      *General commands only when the Smart Bot is listening to you or on demand:*\n"
    end
    order.on_bot.each do |o|
      txt += help.on_bot[o]
    end
  end
  if help.key?(:on_bot) and help.on_bot.key?(:admin)
    txt += "===================================
      *Admin commands:*\n"
    txt += "\n\n"
    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
    txt += "===================================
    *Master Admin commands:*\n"
    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
    txt += "===================================
    *Master Admin commands:*\n" unless txt.include?('*Master Admin commands*')
    help.on_master.admin_master.each do |k, v|
      txt += v if v.is_a?(String)
    end
  end

  if help.key?(:rules_file)
    @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

    unless expanded
      resf = ''
      help.rules_file.split(/^\s*\-+\s*$/).each do |rule|
        command_done = false
        explanation_done = false
        example_done = false
        if rule.match?(/These are specific commands for this/i)
          resf += rule
          resf += "-"*50
          resf += "\n"
        elsif rule.match?(/To run a command on demand and add the respond on a thread/i)
          resf += rule
          resf += "-"*50
          resf += "\n"
        else
          rule.split("\n").each do |line|
            if line.match?(/^\s*\-+\s*/i)
              resf += line
            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*/i)
              resf += "\n     Example: #{line}"
              example_done = true
            end
          end
          resf += "\n\n"
        end
      end
      unless resf.match?(/These are specific commands for this bot on this Channel/i)
        if resf.match?(/\A\s*[=\-]+$/)
          pre = ''
          post = ''
        else
          pre = ('='*50) + "\n"
          post = ('-'*50) + "\n"
        end
        resf = "#{pre}*These are specific commands for this bot on this Channel:*\n#{post}" + resf
      end
      help.rules_file = resf
    end
    txt += help.rules_file
  end

  return txt
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:



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

def get_repl(dest, user, session_name)
  #todo: add tests
  save_stats(__method__)
  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(">, <@")}>"
  else
    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 
        !config.admins.include?(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
# File 'lib/slack/smart-bot/utils/get_repls.rb', line 3

def get_repls(channel = @channel_id)
  if File.exist?("#{config.path}/repl/repls_#{channel}.rb")
    file_conf = IO.readlines("#{config.path}/repl/repls_#{channel}.rb").join
    unless file_conf.to_s() == ""
      @repls = eval(file_conf)
    end
  end
end

#get_routines(channel = @channel_id) ⇒ Object



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

def get_routines(channel = @channel_id)
  if File.exist?("#{config.path}/routines/routines_#{channel}.rb")
    file_conf = IO.readlines("#{config.path}/routines/routines_#{channel}.rb").join
    unless file_conf.to_s() == ""
      @routines = eval(file_conf)
    end
  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_user_info(user) ⇒ Object



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

def (user)
  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
end

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

help: ---------------------------------------------- help: Hi Bot help: Hi Smart help: Hello Bot Hola Bot Hallo Bot What's up Bot Hey Bot Hæ Bot help: Hello THE_NAME_OF_THE_BOT help: Bot starts listening to you help: After that if you want to avoid a single message to be treated by the smart bot, start the message by - help: Also apart of Hello you can use Hallo, Hi, Hola, What's up, Hey, Hæ help:



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

def hi_bot(user, dest, dchannel, 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[:using_channel]!=''
      respond "You are using specific rules for channel: <##{Thread.current[:using_channel]}>", dest
    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

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



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

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)
      if @bots_created[channel_id][:admins].split(",").include?(from)
        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)
        update_bots_file()
        respond "Bot on channel: #{channel}, has been killed and deleted.", dest
        send_msg_channel(channel, "Bot has been killed by #{from}")
      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

#listenObject



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

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

def listen_simulate
  @salutations = [config[:nick], "<@#{config[:nick_id]}>", "bot", "smart"]
  @pings = []
  @last_activity_check = Time.now
  get_bots_created()
    @buffer_complete = [] unless defined?(@buffer_complete)
    b = File.read("#{config.path}/buffer_complete.log")
    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:



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/on_master/admin_master/notify_message.rb', line 11

def notify_message(dest, from, where, message)
  save_stats(__method__)
  if config.on_master_bot
    if config.admins.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:



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

def pause_bot(dest, from)
  save_stats(__method__)
  if config.admins.include?(from) #admin user
    respond "This bot is paused from now on. You can start it again: start this bot", dest
    respond "zZzzzzZzzzzZZZZZZzzzzzzzz", dest
    @status = :paused
    @bots_created[@channel_id][:status] = :paused
    update_bots_file()
    unless config.on_master_bot
      send_msg_channel config.master_channel, "Changed status on #{config.channel} to :paused"
    end
  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:



10
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_routine.rb', line 10

def pause_routine(dest, from, name)
  save_stats(__method__)
  if config.admins.include?(from) #admin user
    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

#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
# 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(/^@?(#{config[:nick]}):*\s+(.+)/im) or
     command.match(/^()!!(.+)/im) or
     command.match(/^()\^(.+)/im) or
     command.match(/^()!(.+)/im) or
     command.match(/^()<@#{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 
    (@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/)
    respond config.on_maintenance_message, dest
    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
  
      case command

      when /^\s*(Hello|Hallo|Hi|Hola|What's\sup|Hey|Hæ)\s+(#{@salutations.join("|")})\s*$/i
        hi_bot(user, dest, dchannel, from, display_name)
      when /^\s*what's\s+new\s*$/i
        whats_new(user, dest, dchannel, from, display_name)
      when /^\s*(Bye|Bæ|Good\s+Bye|Adiós|Ciao|Bless|Bless\sBless|Adeu)\s+(#{@salutations.join("|")})\s*$/i
        bye_bot(dest, from, display_name)
      when /^\s*bot\s+(rules|help)\s*(.+)?$/i, /^bot,? what can I do/i
        $1.to_s.match?(/rules/i) ? specific = true : specific = false
        help_command = $2

        bot_help(user, from, dest, dchannel, specific, help_command, rules_file)
      when /^\s*use\s+(rules\s+)?(from\s+)?<#C\w+\|(.+)>\s*$/i, /^use\s+(rules\s+)?(from\s+)?([^\s]+\s*$)/i
        channel = $3
        use_rules(dest, channel, user, dchannel)
      when /^\s*stop\s+using\s+rules\s+(from\s+)<#\w+\|(.+)>/i, /^stop\s+using\s+rules\s+(from\s+)(.+)/i
        channel = $2
        stop_using_rules(dest, channel, user, dchannel)
      when /^\s*extend\s+rules\s+(to\s+)<#C\w+\|(.+)>/i, /^extend\s+rules\s+(to\s+)(.+)/i,
          /^\s*use\s+rules\s+(on\s+)<#C\w+\|(.+)>/i, /^use\s+rules\s+(on\s+)(.+)/i
        channel = $2
        extend_rules(dest, user, from, channel, typem)
      when /^\s*stop\s+using\s+rules\s+(on\s+)<#\w+\|(.+)>/i, /^stop\s+using\s+rules\s+(on\s+)(.+)/i
        channel = $2
        stop_using_rules_on(dest, user, from, channel, typem)
      when /^\s*exit\s+bot\s*$/i, /^quit\s+bot\s*$/i, /^close\s+bot\s*$/i
        exit_bot(command, from, dest, display_name)
      when /^\s*start\s+(this\s+)?bot$/i
        start_bot(dest, from)
      when /^\s*pause\s+(this\s+)?bot$/i
        pause_bot(dest, from)
      when /^\s*bot\s+status/i
        bot_status(dest, user)
      when /\Anotify\s+<#(C\w+)\|.+>\s+(.+)\s*\z/im, /\Anotify\s+(all)?\s*(.+)\s*\z/im
        where = $1
        message = $2
        notify_message(dest, from, where, message)
      when /^\s*create\s+(cloud\s+)?bot\s+on\s+<#C\w+\|(.+)>\s*/i, /^create\s+(cloud\s+)?bot\s+on\s+(.+)\s*/i
        cloud = !$1.nil?
        channel = $2
        create_bot(dest, user, cloud, channel)
      when /^\s*kill\s+bot\s+on\s+<#C\w+\|(.+)>\s*$/i, /^kill\s+bot\s+on\s+(.+)\s*$/i
        channel = $1
        kill_bot_on_channel(dest, from, channel)
      when /^\s*(add|create)\s+(silent\s+)?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*$/i,
        /^\s*(add|create)\s+(silent\s+)?routine\s+(\w+)\s+on\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)s?\s+at\s+(\d+:\d+:?\d+?)\s*()(\s<#(C\w+)\|.+>\s*)?(\s.+)?\s*$/i,
        /^\s*(add|create)\s+(silent\s+)?routine\s+(\w+)\s+(at)\s+(\d+:\d+:?\d+?)\s*()(\s<#(C\w+)\|.+>\s*)?(\s.+)?\s*$/i
        silent = $2.to_s!=''
        name = $3.downcase
        type = $4
        number_time = $5
        period = $6
        channel = $8
        command_to_run = $9
        add_routine(dest, from, user, name, type, number_time, period, command_to_run, files, silent, channel)
      when /^\s*(kill|delete|remove)\s+routine\s+(\w+)\s*$/i
        name = $2.downcase
        remove_routine(dest, from, name)
      when /^\s*(run|execute)\s+routine\s+(\w+)\s*$/i
        name = $2.downcase
        run_routine(dest, from, name)
      when /^\s*pause\s+routine\s+(\w+)\s*$/i
        name = $1.downcase
        pause_routine(dest, from, name)
      when /^\s*start\s+routine\s+(\w+)\s*$/i
        name = $1.downcase
        start_routine(dest, from, name)
      when /^\s*see\s+(all\s+)?routines\s*$/i
        all = $1.to_s != ""
        see_routines(dest, from, user, all)
      when /^\s*get\s+bot\s+logs?\s*$/i
        get_bot_logs(dest, from, typem)
      when /^\s*bot\s+stats\s*(.*)\s*$/i
        opts = $1.to_s
        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_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?('monthly')
          monthly = true
        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' #routine bot stats to be published on DM
          st_channel = dchannel
        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)
      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)
      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

      # bot rules for extended channels
      when /^bot\s+rules\s*(.+)?$/i
        help_command = $1
        bot_rules(dest, help_command, typem, rules_file, user)
      when /^\s*(add\s+)?(global\s+|generic\s+)?shortcut\s+(for\sall)?\s*([^:]+)\s*:\s*(.+)/i, 
        /^(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 /^\s*(delete|remove)\s+(global\s+|generic\s+)?shortcut\s+(.+)/i, 
        /^(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 /^\s*see\s+shortcuts/i, /^see\ssc/i
        see_shortcuts(dest, user, typem)

        #kept to be backwards compatible
      when /^\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 /^\s*ruby\s(.+)/im, /^\s*code\s(.+)/im
        code = $1
        code.gsub!("\\n", "\n")
        code.gsub!("\\r", "\r")
        @logger.info code
        ruby_code(dest, user, code, rules_file)
      when /^\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s*()()()$/i, 
        /^\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s+([\w\-]+)()()\s*$/i,
        /^\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s+([\w\-]+)\s*:\s+"([^"]+)"()\s*$/i,
        /^\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s+([\w\-]+)\s*:\s+"([^"]+)"\s+(.+)\s*$/i,
        /^\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s+([\w\-]+)()\s+(.+)\s*$/i,
        /^\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)()\s+()(.+)\s*$/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 /^\s*get\s+(repl|irb|live)\s+([\w\-]+)\s*/i
        session_name = $2
        get_repl(dest, user, session_name)      
      when /^\s*run\s+(repl|irb|live)\s+([\w\-]+)()\s*$/i,
        /^\s*run\s+(repl|irb|live)\s+([\w\-]+)\s+(.+)\s*$/i
        session_name = $2
        opts = " #{$3}"
        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
        run_repl(dest, user, session_name, env_vars.flatten, rules_file)      
      when /^\s*(delete|remove)\s+(repl|irb|live)\s+([\w\-]+)\s*$/i
        repl_name = $3
        delete_repl(dest, user, repl_name)
      when /^\s*see\s+(repls|repl|irb|irbs)\s*$/i
        see_repls(dest, user, typem)
      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) ⇒ 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
# File 'lib/slack/smart-bot/process_first.rb', line 2

def process_first(user, text, dest, dchannel, typem, files, ts, thread_ts, routine)
  nick = user.name
  rules_file = ""
  text.gsub!(/^!!/,'^') # to treat it just as ^
  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.id) and @rules_imported[user.id].key?(dchannel)
      unless @bots_created.key?(@rules_imported[user.id][dchannel])
        get_bots_created()
      end
      if @bots_created.key?(@rules_imported[user.id][dchannel])
        rules_file = @bots_created[@rules_imported[user.id][dchannel]][:rules_file]
      end
    end
  elsif dest[0] == "D" and @rules_imported.key?(user.id) and @rules_imported[user.id].key?(user.id) #direct message
    unless @bots_created.key?(@rules_imported[user.id][user.id])
      get_bots_created()
    end
    if @bots_created.key?(@rules_imported[user.id][user.id])
      rules_file = @bots_created[@rules_imported[user.id][user.id]][:rules_file]
    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

  begin
    t = Thread.new do
      begin
        Thread.current[:dest] = dest
        Thread.current[:user] = user
        Thread.current[:command] = command
        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
        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.id) &&
          @rules_imported[user.id].key?(dchannel) && @bots_created.key?(@rules_imported[user.id][dchannel])
            Thread.current[:using_channel] = @rules_imported[user.id][dchannel]
        elsif dest[0] == "D" && @rules_imported.key?(user.id) && @rules_imported[user.id].key?(user.id) and
          @bots_created.key?(@rules_imported[user.id][user.id])
            Thread.current[:using_channel] = @rules_imported[user.id][user.id]
        else
            Thread.current[:using_channel] = ''
        end

        processed = process(user, command, dest, dchannel, rules_file, typem, files, Thread.current[:thread_ts])
        @logger.info "command: #{nick}> #{command}" if processed
        on_demand = false
        if command.match(/^@?(#{config[:nick]}):*\s+(.+)/im) or
          command.match(/^()!!(.+)/im) or
          command.match(/^()\^(.+)/im) or
          command.match(/^()!(.+)/im) or
          command.match(/^()<@#{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
        unless config.on_maintenance and processed
          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}" 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.id) and @rules_imported[user.id].key?(dchannel)
                if @bots_created.key?(@rules_imported[user.id][dchannel])
                  if @bots_created[@rules_imported[user.id][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[0] = "" if command[0] == "!"
                  command.gsub!(/^@\w+:*\s*/, "")
                  if method(:rules).parameters.size == 4
                    rules(user, command, processed, dest)
                  elsif method(:rules).parameters.size == 5
                    rules(user, command, processed, dest, files)
                  else
                    rules(user, command, processed, dest, files, rules_file)
                  end
                else
                  @logger.warn "It seems like rules method is not defined"
                end
              end
            elsif @rules_imported.key?(user.id) and @rules_imported[user.id].key?(user.id)
              if @bots_created.key?(@rules_imported[user.id][user.id])
                if @bots_created[@rules_imported[user.id][user.id]][: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.id][user.id]}|#{@bots_created[@rules_imported[user.id][user.id]][:channel_name]}> is not :on", dest
                  rules_file = ""
                end
              end

              unless rules_file.empty?
                if defined?(rules)
                  command[0] = "" if command[0] == "!"
                  command.gsub!(/^@\w+:*\s*/, "")
                  if method(:rules).parameters.size == 4
                    rules(user, command, processed, dest)
                  elsif method(:rules).parameters.size == 5
                    rules(user, command, processed, dest, files)
                  else
                    rules(user, command, processed, dest, files, rules_file)
                  end
                else
                  @logger.warn "It seems like rules method is not defined"
                end
              end
            else
              @logger.info "it is a direct message with no rules file selected so no rules file executed."
              if command.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

            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
      rescue Exception => stack
        @logger.fatal stack
      end
    end
  rescue => e
    @logger.error "exception: #{e.inspect}"
  end
end

#react(emoji, ts = false) ⇒ 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
# File 'lib/slack/smart-bot/comm/react.rb', line 5

def react(emoji, ts=false)
  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 'react method no ts supplied'
  else
    begin
      client.web_client.reactions_add(channel: Thread.current[:dest], name: emoji, timestamp: ts) unless config.simulate
    rescue Exception => stack
      @logger.warn stack
    end
  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:



13
14
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 13

def remove_routine(dest, from, name)
  save_stats(__method__)
  if config.admins.include?(from) #admin user
    if !config.on_master_bot and dest[0] == "D"
      respond "It's only possible to remove 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][: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

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



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

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

      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
      if Thread.current[:ts].to_s == ''
        @ts_react = Thread.current[:thread_ts]
      else
        @ts_react = Thread.current[:ts]
      end        

      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`. 
      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 = '
          ruby -e "' + env_vars.join("\n") + '
          require \"amazing_print\"
          bindme' + serialt + ' = binding
          eval(\"require \'nice_http\'\" , bindme' + serialt + ')
          def ls(obj)
            (obj.methods - Object.methods)
          end
          
          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
                  resp_repl = eval(code_to_run_repl.gsub(/^\s*(puts|print|p|pp)\s/, \"\"), bindme' + serialt + ')
                rescue Exception => resp_repl
                  error = true
                end
                if resp_repl.to_s != \"\"
                  if code_to_run_repl.match?(/^\s*p\s+/i)
                    open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.output\", \"a+\") {|f|
                      f.puts \"\`\`\`\n#{resp_repl.inspect}\n\`\`\`\"
                    }
                  else
                    open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.output\", \"a+\") {|f|
                      f.puts \"\`\`\`\n#{resp_repl.ai}\n\`\`\`\"
                    }
                  end
                  unless error or !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
      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
              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.delete(from)
              break
          end
          sleep 0.2
          resp_repl = file_output_repl.read
          if resp_repl.to_s!=''
            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
    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/)
        respond "Sorry I cannot run this due security reasons", dest
      else
        @repl_sessions[from][:input]<<code
        case code
        when /^\s*(quit|exit|bye|bye bot)\s*$/i
          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
          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.delete(from)
        when /^\s*-/i
          #ommit
        else
          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) ⇒ 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
# File 'lib/slack/smart-bot/comm/respond.rb', line 2

def respond(msg, dest = nil)
  if dest.nil? and Thread.current.key?(:dest)
    dest = Thread.current[:dest]
  end
  dest = @channels_id[dest] if @channels_id.key?(dest) #it is a name of channel
  if !config.simulate #https://api.slack.com/docs/rate-limits
    msg.to_s.size > 500 ? wait = 0.5 : wait = 0.1
    sleep wait if Time.now <= (@last_respond+wait) 
  end
  if dest.nil?
    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])
      else
        client.message(channel: @channel_id, text: msg, as_user: true)
      end
    end
    if config[:testing] and config.on_master_bot
      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]}|#{msg}~~~"
    }
    else  
      if Thread.current[:on_thread]
        client.message(channel: dest, text: msg, as_user: true, thread_ts: Thread.current[:thread_ts])
      else
        client.message(channel: dest, text: msg, as_user: true)
      end
    end
    if config[:testing] and config.on_master_bot
      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
    send_msg_user(dest, msg)
  elsif dest[0] == "@"
    begin
       = (dest)
      send_msg_user(.user.id, msg)
    rescue Exception => stack
      @logger.warn("user #{dest} not found.")
      @logger.warn stack
      if Thread.current.key?(:dest)
        respond("User #{dest} not found.")
      end
    end
  else
    @logger.warn("method respond not treated correctly: msg:#{msg} dest:#{dest}")
  end
  @last_respond = Time.now
end

#respond_direct(msg) ⇒ Object



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

def respond_direct(msg)
  dest = Thread.current[:user].id
  respond(msg, dest)
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:



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

def ruby_code(dest, user, code, rules_file)
  save_stats(__method__)
  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(">, <@")}>"
  else
    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
        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, rules_file) ⇒ Object

help: ---------------------------------------------- help: run repl SESSION_NAME help: run repl SESSION_NAME ENV_VAR=VALUE ENV_VAR=VALUE 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: 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:



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

def run_repl(dest, user, session_name, env_vars, rules_file)
  #todo: add tests
  from = user.name
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!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(">, <@")}>"
  else
    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}")
    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 
        !config.admins.include?(user.name)
        respond "The REPL with session name: #{session_name} is private", 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[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
        Dir.mkdir("#{project_folder}/tmp") unless Dir.exist?("#{project_folder}/tmp")
        Dir.mkdir("#{project_folder}/tmp/repl") unless Dir.exist?("#{project_folder}/tmp/repl")
        File.write("#{project_folder}/tmp/repl/#{session_name}.rb", content, mode: "w+")
        process_to_run = "ruby  ./tmp/repl/#{session_name}.rb"
        process_to_run = ("cd #{project_folder} && #{process_to_run}") if defined?(project_folder)
        respond "Running REPL #{session_name}"
        stdout, stderr, status = Open3.capture3(process_to_run)
        if stderr == ""
          if stdout == ""
            respond "*#{session_name}*: Nothing returned."
          else
            if stdout.to_s.lines.count < 60 and stdout.to_s.size < 3500
              respond "*#{session_name}*: #{stdout}"
            else
              send_file(dest, "", 'response.rb', "", 'text/plain', "ruby", content: stdout)
            end
          end
        else
          if (stdout.to_s+stderr.to_s).lines.count < 60
            respond "*#{session_name}*: #{stdout} #{stderr}"
          else
            send_file(dest, "", 'response.rb', "", 'text/plain', "ruby", content: (stdout.to_s+stderr.to_s))
          end

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



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/on_bot/admin/run_routine.rb', line 13

def run_routine(dest, from, name)
  save_stats(__method__)
  if config.admins.include?(from) #admin user
    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)
      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
        respond "routine *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest]
        started = Time.now
        treat_message({ channel: @routines[@channel_id][name][:dest],
                       user: @routines[@channel_id][name][:creator_id],
                       text: @routines[@channel_id][name][:command],
                       files: nil })
      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: {}) ⇒ 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
# File 'lib/slack/smart-bot/utils/save_stats.rb', line 3

def save_stats(method, data: {})
    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']
            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

          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
          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]
          end
        rescue Exception => exception
          @logger.fatal "There was a problem on the stats"
          @logger.fatal exception
        end
    end
end

#see_repls(dest, user, typem) ⇒ Object

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



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

def see_repls(dest, user, typem)
  #todo: add tests
  save_stats(__method__)
  from = user.name
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!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(">, <@")}>"
  else
    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 (config.admins.include?(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_routines(dest, from, user, all) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: see routines helpadmin: see all routines 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: You can use this command only if you are an admin user helpadmin:



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

def see_routines(dest, from, user, all)
  save_stats(__method__)
  if config.admins.include?(from) #admin user
    if all
      routines = {}
      if config.on_master_bot
        Dir["#{config.path}/routines/routines_*.rb"].each do |rout|
          file_conf = IO.readlines(rout).join
          unless file_conf.to_s() == ""
            routines.merge!(eval(file_conf))
          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.dup
      end
    else
      if @rules_imported.key?(user.id) and @rules_imported[user.id].key?(user.id) and dest[0] == "D"
        file_conf = IO.readlines("#{config.path}/routines/routines_#{@rules_imported[user.id][user.id]}.rb").join
        routines = eval(file_conf)
      else
        routines = @routines.dup
      end
    end

    if routines.get_values(:channel_name).size == 0
      respond "There are no routines added.", dest
    else
      routines.each do |ch, rout_ch|
        respond "Routines on channel *#{rout_ch.get_values(:channel_name).values.flatten.uniq[0]}*", dest
        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]
          respond msg.join("\n"), dest
        end
      end
    end
  else
    respond "Only admin users can use this command", dest
  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:



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

def see_shortcuts(dest, user, typem)
  save_stats(__method__)
  from = user.name
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!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(">, <@")}>"
  else
    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].dup
        @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].dup
        @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

#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
# 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]
    file = 'myfile' if file.to_s == '' and content!=''
    if to[0] == "U" or to[0] == "W" #user
      im = client.web_client.im_open(user: to)
      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
  end
end

#send_msg_channel(to, msg) ⇒ 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
# File 'lib/slack/smart-bot/comm/send_msg_channel.rb', line 5

def send_msg_channel(to, msg)
  unless msg == ""
    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])
      else
        client.message(channel: channel_id, text: msg, as_user: true)
      end
    end
    if config[:testing] and config.on_master_bot
      open("#{config.path}/buffer.log", "a") { |f|
        f.puts "|#{channel_id}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
      }
    end
  end
end

#send_msg_user(id_user, msg) ⇒ 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
# File 'lib/slack/smart-bot/comm/send_msg_user.rb', line 4

def send_msg_user(id_user, msg)
  unless msg == ""
    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 Thread.current[:on_thread]
          client.message(channel: id_user, as_user: true, text: msg, thread_ts: Thread.current[:thread_ts])
        else
          client.message(channel: id_user, as_user: true, text: msg)
        end
      end
      if config[:testing] and config.on_master_bot
        open("#{config.path}/buffer.log", "a") { |f|
          f.puts "|#{id_user}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
        }
      end
    else
      im = client.web_client.im_open(user: id_user)
      if config[:simulate]
        open("#{config.path}/buffer_complete.log", "a") { |f|
          f.puts "|#{im["channel"]["id"]}|#{config[:nick_id]}|#{config[:nick]}|#{msg}~~~"
        }
      else  
        if Thread.current[:on_thread]
          client.message(channel: im["channel"]["id"], as_user: true, text: msg, thread_ts: Thread.current[:thread_ts])
        else
          client.message(channel: im["channel"]["id"], as_user: true, text: msg)
        end
      end
      if config[:testing] and config.on_master_bot
        open("#{config.path}/buffer.log", "a") { |f|
          f.puts "|#{im["channel"]["id"]}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
        }
      end
    end
  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:



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/admin_master/set_maintenance.rb', line 12

def set_maintenance(from, status, message)
  save_stats(__method__)
  if config.on_master_bot
    if config.admins.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."
      else
        config.on_maintenance = false
        respond "From now on I won't be on maintenance. Everything is back to normal!"
      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

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



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

def start_bot(dest, from)
  save_stats(__method__)
  if config.admins.include?(from) #admin user
    respond "This bot is running and listening from now on. You can pause again: pause this bot", dest
    @status = :on
    @bots_created[@channel_id][:status] = :on
    update_bots_file()
    unless config.on_master_bot
      send_msg_channel config.master_channel, "Changed status on #{config.channel} to :on"
    end
  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:



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_bot/admin/start_routine.rb', line 12

def start_routine(dest, from, name)
  save_stats(__method__)
  if config.admins.include?(from) #admin user
    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][: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: it will stop using the rules from the specified channel. help:



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/commands/general/stop_using_rules.rb', line 6

def stop_using_rules(dest, channel, user, dchannel)
  save_stats(__method__)
  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.id) and @rules_imported[user.id].key?(dchannel)
      if @rules_imported[user.id][dchannel] != channel_id
        respond "You are not using those rules.", dest
      else
        @rules_imported[user.id].delete(dchannel)
        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.id) and @rules_imported[user.id].key?(user.id)
      if @rules_imported[user.id][user.id] != channel_id
        respond "You are not using those rules.", dest
      else
        @rules_imported[user.id].delete(user.id)
        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:



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/commands/on_bot/admin/stop_using_rules_on.rb', line 8

def stop_using_rules_on(dest, user, from, channel, typem)
  save_stats(__method__)
  unless typem == :on_extended
    if !config.admins.include?(from) #not admin
      respond "Only admins can extend or stop using the rules. Admins on this channel: #{config.admins}", dest
    else
      get_bots_created()
      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

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

def treat_message(data, remove_blocks = true)

  begin
    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
  data.routine = false unless data.key?(:routine)

  if config[:testing] and config.on_master_bot
    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
  typem = :dont_treat
  if !dest.nil? and !data.text.nil? and !data.text.to_s.match?(/\A\s*\z/)
    #if data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?<#(\w+)\|([^>]+)>\s*:?\s*(.*)/im)
    if data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?((<#\w+\|[^>]+>\s*)+)\s*:?\s*(.*)/im)
      channels_rules = $2 #multiple channels @smart-bot on #channel1 #channel2 echo AAA
      data_text = $4
      channel_rules_name = ''
      channel_rules = ''
      # to be treated only on the bots of the requested channels
      channels_rules.scan(/<#(\w+)\|([^>]+)>/).each do |tcid, tcname|
        if @channel_id == tcid
          data.text = data_text
          typem = :on_call
          channel_rules = tcid
          channel_rules_name = tcname
          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?(data.user) && @rules_imported[data.user].key?(data.user) and
        @bots_created.key?(@rules_imported[data.user][data.user])
        if @channel_id == @rules_imported[data.user][data.user]
          #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
      if data.channel[0] == "G" and config.on_master_bot and typem != :on_extended #private group
        typem = :on_pg
      end
    end
  end
  unless typem == :dont_treat
    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
      #todo: when changed @questions user_id then move user_info inside the ifs to avoid calling it when not necessary
       = (data.user)

      #user_info.user.id = data.user #todo: remove this line when slack issue with Wxxxx Uxxxx fixed
      data.user = .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(.user.name, qdest).empty?
        if data.text.match?(/^\s*(Bye|Bæ|Good\sBye|Adiós|Ciao|Bless|Bless\sBless|Adeu)\s(#{@salutations.join("|")})\s*$/i)
          answer_delete(.user.name, qdest)
          command = data.text
        else
          command = answer(.user.name, qdest)
          @answer[.user.name][qdest] = data.text
          @questions[.user.name] = data.text # to be backwards compatible #todo remove it when 2.0
        end
      elsif @repl_sessions.key?(.user.name) and data.channel==@repl_sessions[.user.name][:dest] and 
        ((@repl_sessions[.user.name][:on_thread] and data.thread_ts == @repl_sessions[.user.name][:thread_ts]) or
        (!@repl_sessions[.user.name][:on_thread] and data.thread_ts.to_s == '' ))
        
        if data.text.match(/^\s*```(.*)```\s*$/im)
            @repl_sessions[.user.name][:command] = $1
        else   
          @repl_sessions[.user.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(.user, command, dest, channel_rules, typem, data.files, data.ts, data.thread_ts, data.routine)
        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(.user, command, dest, @channel_id, typem, data.files, data.ts, data.thread_ts, data.routine)
      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(.user, command, dest, @channel_id, typem, data.files, data.ts, data.thread_ts, data.routine)
      elsif (dest[0] == "D" or @channel_id == data.channel or data.user == config[:nick_id]) and
            command.size > 0 and command[0] != "-"
        process_first(.user, command, dest, data.channel, typem, data.files, data.ts, data.thread_ts, data.routine)
        # if @botname on #channel_rules: do something
      end
    rescue Exception => stack
      @logger.fatal stack
    end
  else
    if !config.on_master_bot and !dest.nil? and (data.channel == @master_bot_id or dest[0] == "D") and
       data.text.match?(/^\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 /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
            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
            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.rb")
          file_sc = IO.readlines("#{config.path}/shortcuts/shortcuts_global.rb").join
          unless file_sc.to_s() == ""
            @shortcuts_global = eval(file_sc)
          end
        end
      when /global shortcut deleted/
        sleep 2
        if File.exist?("#{config.path}/shortcuts/shortcuts_global.rb")
          file_sc = IO.readlines("#{config.path}/shortcuts/shortcuts_global.rb").join
          unless file_sc.to_s() == ""
            @shortcuts_global = eval(file_sc)
          end
        end
      end
    end
  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
# File 'lib/slack/smart-bot/comm/unreact.rb', line 5

def unreact(emoji, ts=false)
  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
end

#update_bots_fileObject



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

def update_bots_file
  file = File.open(config.file_path.gsub(".rb", "_bots.rb"), "w")
  bots_created = @bots_created.dup
  bots_created.each { |k, v| 
    v[:thread] = "" 
  }
  file.write bots_created.inspect
  file.close
end

#update_repls(channel = @channel_id) ⇒ Object



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

def update_repls(channel = @channel_id)
  file = File.open("#{config.path}/repl/repls_#{channel}.rb", "w")
  file.write (@repls.inspect)
  file.close
end

#update_routines(channel = @channel_id) ⇒ Object



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

def update_routines(channel = @channel_id)
  routines = {}
  file = File.open("#{config.path}/routines/routines_#{channel}.rb", "w")
  @routines.each do |k,v|
    routines[k]={}
    v.each do |kk,vv|
      routines[k][kk] = vv.dup
      routines[k][kk][:thread]=""
    end
  end
  file.write (routines.inspect)
  file.close
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
# File 'lib/slack/smart-bot/utils/update_shortcuts_file.rb', line 2

def update_shortcuts_file
  file = File.open("#{config.path}/shortcuts/#{config.shortcuts_file}", "w")
  file.write @shortcuts.inspect
  file.close

  if config.on_master_bot
    file = File.open("#{config.path}/shortcuts/shortcuts_global.rb", "w")
    file.write @shortcuts_global.inspect
    file.close
  end
end

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

help: ---------------------------------------------- help: use rules from CHANNEL help: use rules CHANNEL help: use CHANNEL help: it will use the rules from the specified channel. help: you need to be part of that channel to be able to use the rules. help:



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

def use_rules(dest, channel, user, dchannel)
  save_stats(__method__)
  get_bots_created()
  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(">, <@")}>"
  else
    #todo: add pagination for case more than 1000 channels on the workspace
    channels = get_channels()

    channel_found = channels.detect { |c| c.name == channel }
    members = get_channel_members(@channels_id[channel]) unless channel_found.nil?

    if channel_found.nil?
      respond "The channel you are trying to use doesn't exist", 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.id] = {} unless @rules_imported.key?(user.id)
        if dest[0] == "C" or dest[0] == "G" #todo: take in consideration bots that are not master
          @rules_imported[user.id][dchannel] = channel_found.id
        else
          @rules_imported[user.id][user.id] = channel_found.id
        end
        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:



7
8
9
10
11
12
13
14
15
16
17
# File 'lib/slack/smart-bot/commands/general/whats_new.rb', line 7

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\/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