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/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/utils/build_help.rb,
lib/slack/smart-bot/utils/save_stats.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/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/use_rules.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_bot/admin_master/bot_stats.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

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
# 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
  if ENV['BOT_SILENT'].to_s == 'true'
    config[:silent] = true
  elsif ENV['BOT_SILENT'].to_s == 'false'
    config[:silent] = false
  else
    config[:silent] = false unless config.key?(:silent)
  end
  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)

  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 == ''
    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}")

  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

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

  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
    @admin_users_id = []
    config.admins.each do |au|
       = client.web_client.users_info(user: "@#{au}")
      @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
    abort("The admin user specified on settings: #{config.admins.join(", ")}, doesn't exist on Slack. Execution aborted")
  end

  client.on :hello do
    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
    @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 version_remote != VERSION
      version_message = ". There is a new available version: #{version_remote}."
    end
    unless config[:silent]
      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

  @status = config.status_init
  @questions = 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
  end

  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

  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) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: add routine NAME every NUMBER PERIOD 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 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: 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 every 12 hours !Run customer tests helpadmin:



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

def add_routine(dest, from, user, name, type, number_time, period, command_to_run, files, silent)
  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 == "at") && !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 = ""
          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
            at = number_time
            if next_run.strftime("%H:%M:%S") < number_time
              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) # one more day
              nt = number_time.split(":")
              next_run = Time.new(next_run.year, next_run.month, next_run.day, nt[0], nt[1], nt[2])
            end
            every_in_seconds = 24 * 60 * 60
          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

          @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, file_path: file_path, 
                                           command: command_to_run.to_s.strip, silent: silent,
                                           next_run: next_run.to_s, dest: dest, 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) ⇒ 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: 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 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:



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

def add_shortcut(dest, user, typem, for_all, shortcut_name, command, command_to_run)
  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
      @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
        unless @questions.keys.include?(from)
          ask("The shortcut already exists, are you sure you want to overwrite it?", command, from, dest)
        else
          case @questions[from]
          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
            @questions.delete(from)
          when /^no/i
            respond "ok, I won't add it", dest
            @questions.delete(from)
          else
            ask "I don't understand, yes or no?", command, from, dest
          end
        end
      end
    end
  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
# 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]}|#{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]}|#{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]}|#{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]}|#{message}"
      }
    end
  elsif dest[0] == "D" #private message
    send_msg_user(dest, message)
  end
  @questions[to] = context
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 what can I do? help: it will display this help help: if COMMAND supplied just help for that command help: bot rules will show only the specific rules for this channel. 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
# File 'lib/slack/smart-bot/commands/general/bot_help.rb', line 13

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

    help_message = get_help(rules_file, dest, from, specific)

    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
  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
# 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.
      help_filtered = get_help(rules_file, dest, from, true)

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

#bot_stats(dest, from_user, typem, channel_id, from, to, user, exclude_masters, exclude_command) ⇒ Object

helpadmin: ---------------------------------------------- helpadmin: bot stats helpadmin: bot stats USER_NAME helpadmin: bot stats exclude masters helpadmin: bot stats from YYYY/MM/DD helpadmin: bot stats from YYYY/MM/DD to YYYY/MM/DD helpadmin: bot stats CHANNEL helpadmin: bot stats CHANNEL from YYYY/MM/DD helpadmin: bot stats CHANNEL from YYYY/MM/DD to YYYY/MM/DD helpadmin: bot stats USER_NAME from YYYY/MM/DD to YYYY/MM/DD helpadmin: bot stats CHANNEL USER_NAME from YYYY/MM/DD to YYYY/MM/DD helpadmin: bot stats CHANNEL exclude masters from YYYY/MM/DD to YYYY/MM/DD helpadmin: bot stats today helpadmin: bot stats exclude COMMAND_ID helpadmin: To see the bot stats 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: You need to set stats to true to generate the stats when running the bot instance. helpadmin: Examples: helpadmin: bot stats #sales helpadmin: bot stats @peter.wind helpadmin: bot stats #sales from 2019/12/15 to 2019/12/31 helpadmin: bot stats #sales today helpadmin:



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

def bot_stats(dest, from_user, typem, channel_id, from, to, user, exclude_masters, exclude_command)
    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 config.masters.include?(from_user) and typem==:on_dm #master admin user
        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 = []

            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 !exclude_masters or (exclude_masters and !config.masters.include?(row[:user_name]))
                            if user=='' or (user!='' and row[:user_id] == user)
                                if exclude_command == '' or (exclude_command!='' and row[:command]!=exclude_command)
                                    if row[:bot_channel_id] == channel_id or channel_id == ''
                                        if row[:date] >= from and row[:date] <= to
                                            rows << row.to_h
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end

            total = rows.size
            if exclude_masters
                message << 'Excluding master admins'
            end
            if exclude_command != ''
                message << "Excluding command #{exclude_command}"
            end
            if user!=''
                message << "Showing only user <@#{user}>"
            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
            if total > 0

                if channel_id == ''
                    message << "*Channels*"
                    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
                if user==''
                    message << "*Users*"
                    users = rows.user_name.uniq.sort
                    users.each do |user|
                        count = rows.count {|h| h.user_name==user}
                        message << "\t#{user}: #{count} (#{(count.to_f*100/total).round(2)}%)"
                    end
                end

                message << "*Commands*"
                commands = rows.command.uniq.sort
                commands.each do |command|
                    count = rows.count {|h| h.command==command}
                    message << "\t#{command}: #{count} (#{(count.to_f*100/total).round(2)}%)"
                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
                message << "*Last activity*: #{rows[-1].date} #{rows[-1].bot_channel} #{rows[-1].type_message} #{rows[-1].user_name} #{rows[-1].command}"
            end
            
        end
    else
        message<<"Only Master admin users on a private conversation with the bot can see the 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 version_remote != 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) ⇒ Object



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

def build_help(path)
  help_message = {}
  Dir["#{path}/*"].each do |t|
    if Dir.exist?(t)
      help_message[t.scan(/\/(\w+)$/).join.to_sym] = build_help(t)
    else
      help_message[t.scan(/\/(\w+)\.rb$/).join.to_sym] = IO.readlines(t).join.scan(/#\s*help\s*\w*:(.*)/).join("\n")
    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: Also apart of Bye you can use Bæ, Good Bye, Adiós, Ciao, Bless, Bless Bless, Adeu help: Bot stops listening to you 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
# 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 = client.web_client.conversations_list(
        types: "private_channel,public_channel",
        limit: "1000",
        exclude_archived: "true",
      ).channels
      channel_found = channels.detect { |c| c.name == channel }
      members = client.web_client.conversations_members(channel: @channels_id[channel]).members 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]}", 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
            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
            end
            default_rules = (__FILE__).gsub(/slack\/smart-bot\/commands\/on_master\/create_bot\.rb$/, "slack-smart-bot_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)
            admin_users = Array.new()
            creator_info = client.web_client.users_info(user: 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
# 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)

            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/)))
              respond "routine *`#{name}`*: #{@routines[@channel_id][name][:file_path]}", @routines[@channel_id][name][:dest]
            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]
              respond "routine *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest]
            end
            started = Time.now
            data = { channel: @routines[@channel_id][name][:dest],
              user: @routines[@channel_id][name][:creator_id],
              text: @routines[@channel_id][name][:command],
              files: nil }
            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 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
          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: help: Will delete the specified REPL help: Only the creator of the REPL or an admin can delete REPLs help:



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

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

help: ---------------------------------------------- help: delete shortcut NAME help: delete sc NAME help: It will delete the shortcut with the supplied 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
44
45
46
47
# File 'lib/slack/smart-bot/commands/on_bot/delete_shortcut.rb', line 8

def delete_shortcut(dest, user, shortcut, typem, command)
  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 !config.admins.include?(from) and @shortcuts[:all].include?(shortcut) and !@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
        unless @questions.keys.include?(from)
          ask("are you sure you want to delete it?", command, from, dest)
        else
          case @questions[from]
          when /^(yes|yep)/i
            @questions.delete(from)
            respond "shortcut deleted!", dest
            respond("#{shortcut}: #{@shortcuts[from][shortcut]}", dest) if @shortcuts.key?(from) and @shortcuts[from].key?(shortcut)
            respond("#{shortcut}: #{@shortcuts[:all][shortcut]}", dest) if @shortcuts.key?(:all) and @shortcuts[:all].key?(shortcut)
            @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
            @questions.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

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

  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

#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
      unless @questions.keys.include?(from)
        ask("are you sure?", command, from, dest)
      else
        case @questions[from]
        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
          @questions.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
59
60
61
62
# 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 = client.web_client.conversations_list(
        types: "private_channel,public_channel",
        limit: "1000",
        exclude_archived: "true",
      ).channels

      channel_found = channels.detect { |c| c.name == channel }
      get_channels_name_and_id()
      members = client.web_client.conversations_members(channel: @channels_id[channel]).members 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]}", 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 `!` 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
14
15
16
17
18
19
# File 'lib/slack/smart-bot/utils/get_channels_name_and_id.rb', line 3

def get_channels_name_and_id
  #todo: add pagination for case more than 1000 channels on the workspace
  channels = client.web_client.conversations_list(
    types: "private_channel,public_channel",
    limit: "1000",
    exclude_archived: "true",
  ).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 = false) ⇒ Object



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

def get_help(rules_file, dest, from, only_rules = false)
  order = {
    general: [:hi_bot, :bye_bot, :bot_help, :bot_status, :use_rules, :stop_using_rules],
    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]
  }
  # user_type: :admin, :user, :admin_master
  if config.masters.include?(from)
    user_type = :admin_master
  elsif config.admins.include?(from)
    user_type = :admin
  else
    user_type = :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 ||= build_help("#{__dir__}/../commands")
  if only_rules
    help = {}
  else
    help = @help_messages.deep_copy
  end
  if rules_file != ""
    help[:rules_file] = IO.readlines(config.path+rules_file).join.scan(/#\s*help\s*\w*:(.*)/i).join("\n")
  end
  help = remove_hash_keys(help, :admin_master) unless user_type == :admin_master
  help = remove_hash_keys(help, :admin) unless user_type == :admin or user_type == :admin_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
    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
    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
    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"
    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
    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: help: Will get the Ruby commands sent on that SESSION_NAME. help:



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

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}.input")
      if @repls.key?(session_name) and @repls[session_name][:type] == :private 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)
          @repls[session_name][:accessed] = Time.now.to_s
          @repls[session_name][:gets] += 1
          update_repls()
        end

        content = "require 'nice_http'\n"
        if File.exist?("#{project_folder}/.smart-bot-repl")
          content += File.read("#{project_folder}/.smart-bot-repl")
          content += "\n"
        end
        content += File.read("#{config.path}/repl/#{@channel_id}/#{session_name}.input").gsub(/^(quit|exit|bye)$/i,'')
        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

#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: Also apart of Hello you can use Hallo, Hi, Hola, What's up, Hey, Hæ 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:



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



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

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

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

  restarts = 0
  started = false
  while restarts < 200 and !started
    begin
      @logger.info "Bot starting: #{config.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
# 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+)\|([^~]+)~~~/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
        command = message[2].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}, 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}, false)
            end
          end
        else
          treat_message({channel: channel, user: user, text: command})
        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 = client.web_client.users_conversations(exclude_archived: true, limit: 100, types: "im, public_channel,private_channel").channels
        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
# 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
    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
# File 'lib/slack/smart-bot/process.rb', line 2

def process(user, command, dest, dchannel, rules_file, typem, files, ts)
  from = user.name
  
  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
  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

  #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*(Bye|Bæ|Good\sBye|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 using rules (from\s+)<#\w+\|(.+)>/i, /^stop using rules (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 using rules (on\s+)<#\w+\|(.+)>/i, /^stop using rules (on\s+)(.+)/i
      channel = $2
      stop_using_rules_on(dest, user, from, channel, typem)
    when /^\s*exit\sbot\s*$/i, /^quit\sbot\s*$/i, /^close\sbot\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\sstatus/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\sbot\son\s<#C\w+\|(.+)>\s*$/i, /^kill\sbot\son\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.+)?\s*$/i,
         /^\s*(add|create)\s+(silent\s+)?routine\s+(\w+)\s+(at)\s+(\d+:\d+:?\d+?)\s*()(\s.+)?\s*$/i
      silent = $2.to_s!=''
      name = $3.downcase
      type = $4
      number_time = $5
      period = $6
      command_to_run = $7
      add_routine(dest, from, user, name, type, number_time, period, command_to_run, files, silent)
    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(' ')
      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
      exclude_masters = opts.match?(/exclude\s+masters?/i)
      if all_opts.include?('today')
        st_from = st_to = "#{Time.now.strftime("%Y-%m-%d")}"
      end
      exclude_command = opts.scan(/exclude\s+([^\s]+)/i).join
      exclude_command = '' if exclude_command == 'masters'
      bot_stats(dest, from, typem, st_channel, st_from, st_to, st_user, exclude_masters, exclude_command)
    else
      processed = false
    end
  else
    processed = false
  end

  # only when :on and (listening or on demand or direct message)
  if @status == :on and
     (@questions.key?(from) 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)?shortcut\s(for\sall)?\s*([^:]+)\s*:\s*(.+)/i, /^(add\s)sc\s(for\sall)?\s*([^:]+)\s*:\s*(.+)/i
      for_all = $2
      shortcut_name = $3.to_s.downcase
      command_to_run = $4
      add_shortcut(dest, user, typem, for_all, shortcut_name, command, command_to_run)
    when /^\s*(delete|remove)\s+shortcut\s+(.+)/i, /^(delete|remove)\s+sc\s+(.+)/i
      shortcut = $2.to_s.downcase
      delete_shortcut(dest, user, shortcut, typem, command)
    when /^\s*see\sshortcuts/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+)?(repl|irb|live)\s*()()()$/i, 
      /^\s*(private\s+)?(repl|irb|live)\s+([\w\-]+)()()\s*$/i,
      /^\s*(private\s+)?(repl|irb|live)\s+([\w\-]+)\s*:\s*"(.+)"()\s*$/i,
      /^\s*(private\s+)?(repl|irb|live)\s+([\w\-]+)\s*:\s*"(.+)"\s+(.+)\s*$/i,
      /^\s*(private\s+)?(repl|irb|live)\s+([\w\-]+)()\s+(.+)\s*$/i,
      /^\s*(private\s+)?(repl|irb|live)()\s+()(.+)\s*$/i
      if $1.to_s!=''
        type = :private
      else
        type = :public
      end
      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.downcase
      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

  return processed
end

#process_first(user, text, dest, dchannel, typem, files, ts, thread_ts) ⇒ Object



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

def process_first(user, text, dest, dchannel, typem, files, ts, thread_ts)
  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])
      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])
      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))
    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
    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
        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
        if @status == :on and
           (@questions.key?(nick) 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
      rescue Exception => stack
        @logger.fatal stack
      end
    end
  rescue => e
    @logger.error "exception: #{e.inspect}"
  end
end

#react(emoji, parent = false) ⇒ Object

list of available emojis: https://www.webfx.com/tools/emoji-cheat-sheet/ react(:thumbsup)



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

def react(emoji, parent=false)
  if parent
    ts = Thread.current[:thread_ts]
  else
    ts = Thread.current[:ts]
  end
  begin
    client.web_client.reactions_add(channel: Thread.current[:dest], name: emoji, timestamp: ts)
  rescue Exception => stack
    @logger.warn stack
  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: 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: 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: 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:



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/commands/on_bot/repl.rb', line 32

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
  
      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`. 
      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+")
      
      process_to_run = '
          ruby -e "' + env_vars.join("\n") + '
          require \"awesome_print\"
          bindme' + serialt + ' = binding
          eval(\"require \'nice_http\'\" , bindme' + serialt + ')

          file_input_repl = File.open(\"' + Dir.pwd + '/repl/' + @channel_id + '/' + session_name + '.input\", \"r\")
          if File.exist?(\"./.smart-bot-repl\")
            begin
              eval(File.read(\"./.smart-bot-repl\"), bindme' + serialt + ')
            rescue Exception => resp_repl
            end
          end
          while true do 
            sleep 0.2 
            code_to_run_repl = file_input_repl.read
            if code_to_run_repl.to_s!=''
              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+(.+)/)
                  code_to_run_repl = \"#{code_to_run_repl.scan(/^\s*ls\s+(.+)/).join}.methods - Object.methods\"
                end
                begin
                  code_to_run_repl.gsub!(/^\s*(puts|print|p|pp)\s/, \"\")
                  resp_repl = eval(code_to_run_repl.to_s, bindme' + serialt + ')
                rescue Exception => resp_repl
                end
                if resp_repl.to_s != \"\"
                  open(\"' + Dir.pwd + '/repl/' + @channel_id + '/' + session_name + '.output\", \"a+\") {|f|
                    f.puts \"\`\`\`#{resp_repl.awesome_inspect}\`\`\`\"
                  }
                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")

      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
              @repl_sessions.delete(from)
              break
          end
          sleep 0.2
          resp_repl = file_output_repl.read
          if resp_repl.to_s!=''
            respond resp_repl, dest
          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 sinde 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/)
        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
          @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
# 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 dest.nil?
    if config[:simulate]
      open("#{config.path}/buffer_complete.log", "a") { |f|
        f.puts "|#{@channel_id}|#{config[:nick_id]}|#{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]}|#{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]}|#{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]}|#{msg}"
      }
    end
  elsif dest[0] == "D" or dest[0] == "U" # Direct message
    send_msg_user(dest, msg)
  elsif dest[0] == "@"
    begin
       = client.web_client.users_info(user: 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
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
# 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/)
      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 > 100

      begin
        code.gsub!(/^\W*$/, "") #to remove special chars from slack when copy/pasting
        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
        stdout, stderr, status = Open3.capture3(ruby)
        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
      rescue Exception => exc
        respond exc, dest
      end
    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: 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:



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

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}.input")
      if @repls.key?(session_name) and @repls[session_name][:type] == :private 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")
          content += File.read("#{project_folder}/.smart-bot-repl")
          content += "\n"
        end
        content += File.read("#{config.path}/repl/#{@channel_id}/#{session_name}.input").gsub(/^(quit|exit|bye)$/i,'')
        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  #{project_folder}/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
            respond "*#{session_name}*: #{stdout}"
          end
        else
          respond "*#{session_name}*: #{stdout} #{stderr}"
        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) ⇒ Object



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

def save_stats(method)
    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
          dest = Thread.current[:dest]
          typem = Thread.current[:typem]
          user = Thread.current[:user]
          files = Thread.current[:files?]
          if method.to_s == 'ruby_code' and files
            command_txt = 'ruby'
          else
            command_txt = Thread.current[:command]
          end

          CSV.open("#{config.stats_path}.#{Time.now.strftime("%Y-%m")}.log", "a+") do |csv|
            csv << [Time.now, config.channel, @channel_id, @channels_name[dest], dest, typem, user.name, user.id, command_txt, method, 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 (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} / 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
# 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 = []
          ch != v[:dest] ? directm = " (*DM to #{v[:creator]}*)" : directm = ""
          msg << "*`#{k}`*#{directm}"
          msg << "\tCreator: #{v[:creator]}"
          msg << "\tStatus: #{v[:status]}"
          msg << "\tEvery: #{v[:every]}" unless v[:every] == ""
          msg << "\tAt: #{v[:at]}" unless v[:at] == ""
          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
# 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
        msg = "*Available shortcuts for all:*\n"
        @shortcuts[:all].each { |name, value|
          msg += "    _#{name}: #{value}_\n"
        }
        respond msg, dest
      end

      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
          msg = "*Available shortcuts for #{from}:*\n"
          new_hash.each { |name, value|
            msg += "    _#{name}: #{value}_\n"
          }
          respond msg, dest
        end
      end
      respond "No shortcuts found", dest if msg == ""
    end
  end
end

#send_file(to, msg, file, title, format, type = "text") ⇒ 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")



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_file.rb', line 6

def send_file(to, msg, file, title, format, type = "text")
  unless config[:simulate]
    if to[0] == "U" #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

    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
    )
  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]}|#{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]}|#{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]}|#{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]}|#{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]}|#{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]}|#{msg}"
        }
      end
    end
  end
end

#start_bot(dest, from) ⇒ Object

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



9
10
11
12
13
14
15
16
17
18
19
20
21
# 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
    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
# 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
      #only works when no @user or #channel mentioned
      if remove_blocks and !data.blocks.nil? and data.blocks.size > 0
        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'
                  if e.elements.size > 0 and (e.elements.type.uniq - ['link', 'text']) == []
                    data.text = ''
                    e.elements.each do |el|
                      if el.type == 'text'
                        data.text += el.text
                      else
                        data.text += el.url
                      end
                    end
                  end
                  break
                end
              end
            end
            break
          end
        end
      end
      data.text = CGI.unescapeHTML(data.text)
      data.text.gsub!("\u00A0", " ") #to change &nbsp; (asc char 160) into blank space
    end
  rescue
    @logger.warn "Impossible to unescape or clean format for data.text:#{data.text}"
  end
  if config[:testing] and config.on_master_bot
    open("#{config.path}/buffer.log", "a") { |f|
      f.puts "|#{data.channel}|#{data.user}|#{data.text}"
    }
  end
  if 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?(/^\s*$/)
    if data.text.match(/^<@#{config[:nick_id]}>\s(on\s)?<#(\w+)\|([^>]+)>\s*:?\s*(.*)/im)
      channel_rules = $2
      channel_rules_name = $3
      # to be treated only on the bot of the requested channel
      if @channel_id == channel_rules
        data.text = $4
        typem = :on_call
      end
    elsif dest == @master_bot_id
      if config.on_master_bot #only to be treated on master bot channel
        typem = :on_master
      end
    elsif @bots_created.key?(dest)
      if @channel_id == dest #only to be treated by the bot on the channel
        typem = :on_bot
      end
    elsif dest[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 dest[0] == "C" or dest[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[dest]) 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[dest])
            typem = :on_extended
            break
          end
        end
      end
      if dest[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
       = client.web_client.users_info(user: data.user)
      if @questions.key?(.user.name)
        if data.text.match?(/^\s*(Bye|Bæ|Good\sBye|Adiós|Ciao|Bless|Bless\sBless|Adeu)\s(#{@salutations.join("|")})\s*$/i)
          @questions.delete(.user.name)
          command = data.text
        else
          command = @questions[.user.name]
          @questions[.user.name] = data.text
        end
      elsif @repl_sessions.key?(.user.name) and dest==@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 == '' ))
        @repl_sessions[.user.name][:command] = data.text
        command = 'repl'
      else
        command = data.text
      end

      #when added special characters on the message
      if 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 = client.web_client.conversations_list(
          types: "private_channel,public_channel",
          limit: "1000",
          exclude_archived: "true",
        ).channels
        channel_found = channels.detect { |c| c.name == channel_rules_name }
        members = client.web_client.conversations_members(channel: @channels_id[channel_rules_name]).members 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.", dest
        elsif @status != :on
          respond "The bot in that channel is not :on", dest
        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)
        else
          respond "You need to join the channel <##{channel_found.id}> to be able to use the rules.", dest
        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)
      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)
      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)
        # 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 (dest == @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 /^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()
      end
    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
# 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
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
50
51
52
53
# 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 = client.web_client.conversations_list(
      types: "private_channel,public_channel",
      limit: "1000",
      exclude_archived: "true",
    ).channels

    channel_found = channels.detect { |c| c.name == channel }
    members = client.web_client.conversations_members(channel: @channels_id[channel]).members 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