class SlackSmartBot

Constants

VERSION

Attributes

channel_id[R]
client[RW]
config[RW]
master_bot_id[R]

Public Class Methods

new(config) click to toggle source
# File lib/slack-smart-bot.rb, line 33
def initialize(config)
  if config.key?(:path) and config[:path] != ''
    config.path.chop! if config.path[-1]=="/"
  else
    config[:path] = '.'
  end
  config[:silent] = false unless config.key?(:silent)
  config[:testing] = false unless config.key?(:testing)
  config[:simulate] = false unless config.key?(:simulate)
  config[:stats] = false unless config.key?(:stats)
  config[:allow_access] = Hash.new unless config.key?(:allow_access)
  config[:on_maintenance] = false unless config.key?(:on_maintenance)
  config[:on_maintenance_message] = "Sorry I'm on maintenance so I cannot attend your request." unless config.key?(:on_maintenance_message)

  if config.path.to_s!='' and config.file.to_s==''
    config.file = File.basename($0)
  end
  if config.key?(:file) and config.file!=''
    config.file_path = "#{config.path}/#{config.file}"
  else
    config.file_path = $0
    config.file = File.basename(config.file_path)
    config.path = File.dirname(config.file_path)
  end
  if config.stats
    Dir.mkdir("#{config.path}/stats") unless Dir.exist?("#{config.path}/stats")
    config.stats_path = "#{config.path}/stats/#{config.file.gsub(".rb", ".stats")}"
  end
  Dir.mkdir("#{config.path}/logs") unless Dir.exist?("#{config.path}/logs")
  Dir.mkdir("#{config.path}/shortcuts") unless Dir.exist?("#{config.path}/shortcuts")
  Dir.mkdir("#{config.path}/routines") unless Dir.exist?("#{config.path}/routines")
  File.delete("#{config.path}/config_tmp.status") if File.exist?("#{config.path}/config_tmp.status")

  config.masters = MASTER_USERS if config.masters.to_s=='' and defined?(MASTER_USERS)
  config.master_channel = MASTER_CHANNEL if config.master_channel.to_s=='' and defined?(MASTER_CHANNEL)

  if ARGV.size == 0 or (config.file.to_s!='' and config.file.to_s!=File.basename($0))
    config.rules_file = "#{config.file.gsub(".rb", "_rules.rb")}" unless config.rules_file.to_s!=''
    unless File.exist?(config.path + '/' + config.rules_file)
      default_rules = (__FILE__).gsub(/\.rb$/, "_rules.rb")
      FileUtils.copy_file(default_rules, config.path + '/' + config.rules_file)
    end
    config.admins = config.masters unless config.admins.to_s!=''
    config.channel = config.master_channel unless config.channel.to_s!=''
    config.status_init = :on unless config.status_init.to_s!=''
  else
    config.rules_file = ARGV[2]
    config.admins = ARGV[1].split(",")
    config.channel = ARGV[0]
    config.status_init = ARGV[3].to_sym
  end
  config.rules_file[0]='' if config.rules_file[0]=='.'
  config.rules_file='/'+config.rules_file if config.rules_file[0]!='/'

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

  if (!config.key?(:token) or config.token.to_s == '') and !config.simulate
    abort "You need to supply a valid token key on the settings. key: :token"
  elsif !config.key?(:masters) or !config.masters.is_a?(Array) or config.masters.size == 0
    abort "You need to supply a masters array on the settings containing the user names of the master admins. key: :masters"
  elsif !config.key?(:master_channel) or config.master_channel.to_s == ''
    abort "You need to supply a master_channel on the settings. key: :master_channel"
  elsif !config.key?(:channel) or config.channel.to_s == ''
    abort "You need to supply a bot channel name on the settings. key: :channel"
  end



  logfile = File.basename(config.rules_file.gsub("_rules_", "_logs_"), ".rb") + ".log"
  config.log_file = logfile
  @logger = Logger.new("#{config.path}/logs/#{logfile}")
  @last_respond = Time.now
  
  config_log = config.dup
  config_log.delete(:token)
  @logger.info "Initializing bot: #{config_log.inspect}"

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

  self.config = config

  unless config.simulate and config.key?(:client)
    Slack.configure do |conf|
      conf.token = config[:token]
    end
  end
  restarts = 0
  created = false
  while restarts < 200 and !created
    begin
      @logger.info "Connecting #{config_log.inspect}"
      if config.simulate and config.key?(:client)
        self.client = config.client
      else
        self.client = Slack::RealTime::Client.new(start_method: :rtm_connect)
      end
      created = true
    rescue Exception => e
      restarts += 1
      if restarts < 200
        @logger.fatal "*" * 50
        @logger.fatal "Rescued on creation: #{e.inspect}"
        @logger.info "Waiting 60 seconds to retry. restarts: #{restarts}"
        puts "#{Time.now}: Not able to create client. Waiting 60 seconds to retry: #{config_log.inspect}"
        sleep 60
      else
        exit!
      end
    end
  end

  @listening = Hash.new()

  @bots_created = Hash.new()
  @shortcuts = Hash.new()
  @shortcuts[:all] = Hash.new()
  @shortcuts_global = Hash.new()
  @shortcuts_global[:all] = Hash.new()
  @rules_imported = Hash.new()
  @routines = Hash.new()
  @repls = Hash.new()

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

  get_routines()
  get_repls()

  if config.on_master_bot and File.exist?(config.file_path.gsub(".rb", "_bots.rb"))
    get_bots_created()
    if @bots_created.kind_of?(Hash) and config.start_bots
      @bots_created.each { |key, value|
        if !value.key?(:cloud) or (value.key?(:cloud) and value[:cloud] == false)
          @logger.info "ruby #{config.file_path} \"#{value[:channel_name]}\" \"#{value[:admins]}\" \"#{value[:rules_file]}\" #{value[:status].to_sym}"
          puts "Starting #{value[:channel_name]} Smart Bot"
          t = Thread.new do
            `ruby #{config.file_path} \"#{value[:channel_name]}\" \"#{value[:admins]}\" \"#{value[:rules_file]}\" #{value[:status].to_sym}`
          end
          value[:thread] = t
          sleep value[:admins].size
        end
      }
    end
  end

  get_rules_imported()

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

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

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

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

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

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

Public Instance Methods

add_routine(dest, from, user, name, type, number_time, period, command_to_run, files, silent, channel) click to toggle source

helpadmin: ———————————————- helpadmin: `add routine NAME every NUMBER PERIOD COMMAND` helpadmin: `add routine NAME every NUMBER PERIOD #CHANNEL COMMAND` helpadmin: `add routine NAME every NUMBER PERIOD` helpadmin: `add silent routine NAME every NUMBER PERIOD` helpadmin: `create routine NAME every NUMBER PERIOD` helpadmin: `add routine NAME at TIME COMMAND` helpadmin: `add routine NAME at TIME #CHANNEL COMMAND` helpadmin: `add routine NAME on DAYWEEK at TIME COMMAND` helpadmin: `add routine NAME on DAYWEEK at TIME #CHANNEL COMMAND` helpadmin: `add routine NAME at TIME` helpadmin: `add silent routine NAME at TIME` helpadmin: `create routine NAME at TIME` helpadmin: It will execute the command/rule supplied. Only for Admin and Master Admins. helpadmin: If no COMMAND supplied, then it will be necessary to attach a file with the code to be run and add this command as message to the file. ONLY for MASTER ADMINS. helpadmin: In case silent provided then when executed will be only displayed if the routine returns a message helpadmin: NAME: one word to identify the routine helpadmin: NUMBER: Integer helpadmin: PERIOD: days, d, hours, h, minutes, mins, min, m, seconds, secs, sec, s helpadmin: TIME: time at format HH:MM:SS helpadmin: DAYWEEK: Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday. And their plurals. helpadmin: #CHANNEL: the destination channel where the results will be published. If not supplied then the bot channel by default or a DM if the command is run from a DM. helpadmin: COMMAND: any valid smart bot command or rule helpadmin: Examples: helpadmin: _add routine example every 30s ruby puts 'a'_ helpadmin: _add routine example every 3 days ruby puts 'a'_ helpadmin: _add routine example at 17:05 ruby puts 'a'_ helpadmin: _create silent routine Example every 12 hours !Run customer tests_ helpadmin: _add routine example on Mondays at 05:00 !run customer tests_ helpadmin: _add routine example on Tuesdays at 09:00 #SREChannel !run db cleanup_ helpadmin:

# File lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb, line 33
def add_routine(dest, from, user, name, type, number_time, period, command_to_run, files, silent, channel)
  save_stats(__method__)
  if files.nil? or files.size == 0 or (files.size > 0 and config.masters.include?(from))
    if config.admins.include?(from)
      if @routines.key?(@channel_id) && @routines[@channel_id].key?(name)
        respond "I'm sorry but there is already a routine with that name.\nCall `see routines` to see added routines", dest
      else
        number_time += ":00" if number_time.split(":").size == 2
        if (type != "every") && !number_time.match?(/^[01][0-9]:[0-5][0-9]:[0-5][0-9]$/) &&
           !number_time.match?(/^2[0-3]:[0-5][0-9]:[0-5][0-9]$/)
          respond "Wrong time specified: *#{number_time}*"
        else
          file_path = ""
          every = ""
          at = ""
          dayweek = ''
          next_run = Time.now
          case period.downcase
          when "days", "d"
            every = "#{number_time} days"
            every_in_seconds = number_time.to_i * 24 * 60 * 60
          when "hours", "h"
            every = "#{number_time} hours"
            every_in_seconds = number_time.to_i * 60 * 60
          when "minutes", "mins", "min", "m"
            every = "#{number_time} minutes"
            every_in_seconds = number_time.to_i * 60
          when "seconds", "secs", "sec", "s"
            every = "#{number_time} seconds"
            every_in_seconds = number_time.to_i
          else # time
            if type != 'at'
              dayweek = type.downcase
              days = ['sunday','monday','tuesday','wednesday','thursday','friday','saturday']
              incr = days.index(dayweek) - Time.now.wday
              if incr < 0 
                incr = (7+incr)*24*60*60
              else
                incr = incr * 24 * 60 * 60
              end
              days = incr/(24*60*60)
              every_in_seconds = 7 * 24 * 60 * 60 # one week
            else
              days = 0
              every_in_seconds = 24 * 60 * 60 # one day
            end

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

          if !files.nil? && (files.size == 1)
            @logger.info files[0].inspect if config.testing
            file_path = "#{config.path}/routines/#{@channel_id}/#{name}#{files[0].name.scan(/[^\.]+(\.\w+$)/).join}"
            if files[0].filetype == "ruby" and files[0].name.scan(/[^\.]+(\.\w+$)/).join == ''
              file_path += ".rb"
            end
            http = NiceHttp.new(host: "https://files.slack.com", headers: { "Authorization" => "Bearer #{config[:token]}" }, log_headers: :partial)
            http.get(files[0].url_private_download, save_data: file_path)
            system("chmod +x #{file_path}")
          end
          get_channels_name_and_id() unless @channels_name.keys.include?(channel) or @channels_id.keys.include?(channel)
          channel_id = nil
          if @channels_name.key?(channel) #it is an id
            channel_id = channel
            channel = @channels_name[channel_id]
          elsif @channels_id.key?(channel) #it is a channel name
            channel_id = @channels_id[channel]
          end
  
          channel_id = dest if channel_id.to_s == ''
          @routines[@channel_id] = {} unless @routines.key?(@channel_id)
          @routines[@channel_id][name] = { channel_name: config.channel, creator: from, creator_id: user.id, status: :on,
                                           every: every, every_in_seconds: every_in_seconds, at: at, dayweek: dayweek, file_path: file_path, 
                                           command: command_to_run.to_s.strip, silent: silent,
                                           next_run: next_run.to_s, dest: channel_id, last_run: "", last_elapsed: "", 
                                           running: false }
          update_routines
          respond "Added routine *`#{name}`* to the channel", dest
          create_routine_thread(name)
        end
      end
    else
      respond "Only admin users can use this command", dest
    end
  else
    respond "Only master admin users can add files to routines", dest
  end
end
add_shortcut(dest, user, typem, for_all, shortcut_name, command, command_to_run, global) click to toggle source

help: ———————————————- help: `add shortcut NAME: COMMAND` help: `add sc NAME: COMMAND` help: `add shortcut for all NAME: COMMAND` help: `add sc for all NAME: COMMAND` help: `shortcut NAME: COMMAND` help: `shortcut for all NAME: COMMAND` help: `add global sc for all NAME: COMMAND` help: It will add a shortcut that will execute the command we supply. help: In case we supply 'for all' then the shorcut will be available for everybody help: If 'global' or 'generic' supplied and in Master channel then the shortcut will be available in all Bot channels. help: If you want to use a shortcut as a inline shortcut inside a command you can do it by adding a $ fex: _!run tests $cust1_ help: Example: help: _add shortcut for all Spanish account: code require 'iso/iban'; 10.times {puts ISO::IBAN.random('ES')}_ help: Then to call this shortcut: help: _sc spanish account_ help: _shortcut Spanish Account_ help: _Spanish Account_ help:

# File lib/slack/smart-bot/commands/on_bot/add_shortcut.rb, line 22
def add_shortcut(dest, user, typem, for_all, shortcut_name, command, command_to_run, global)
  save_stats(__method__)
  unless typem == :on_extended
    from = user.name
    if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
      (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
      respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
    else

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

        found_other = false
        if for_all.to_s != ""
          @shortcuts.each { |sck, scv|
            if sck != :all and sck != from and scv.key?(shortcut_name)
              found_other = true
            end
          }
        end
        if !config.admins.include?(from) and @shortcuts[:all].include?(shortcut_name) and !@shortcuts[from].include?(shortcut_name)
          respond "Only the creator of the shortcut can modify it", dest
        elsif found_other
          respond "You cannot create a shortcut for all with the same name than other user is using", dest
        elsif !@shortcuts[from].include?(shortcut_name)
          #new shortcut
          @shortcuts[from][shortcut_name] = command_to_run
          @shortcuts[:all][shortcut_name] = command_to_run if for_all.to_s != ""
          update_shortcuts_file()
          respond "shortcut added", dest
        else
          #are you sure? to avoid overwriting existing
          if answer.empty?
            ask("The shortcut already exists, are you sure you want to overwrite it?", command, from, dest)
          else
            case answer
            when /^(yes|yep)/i
              @shortcuts[from][shortcut_name] = command_to_run
              @shortcuts[:all][shortcut_name] = command_to_run if for_all.to_s != ""
              update_shortcuts_file()
              respond "shortcut added", dest
              answer_delete(from)
            when /^no/i
              respond "ok, I won't add it", dest
              answer_delete(from)
            else
              ask "I don't understand, yes or no?", command, from, dest
            end
          end
        end
      end
    end
  end
end
answer(from = Thread.current[:user].name, dest = Thread.current[:dest]) click to toggle source
# File lib/slack/smart-bot/utils/answer.rb, line 2
def answer(from = Thread.current[:user].name, dest = Thread.current[:dest])
    if @answer.key?(from)
        if Thread.current[:on_thread]
            dest = Thread.current[:thread_ts]
        end
        if @answer[from].key?(dest)
            return @answer[from][dest]
        else
            return ''
        end
    else
        return ''
    end
end
answer_delete(from = Thread.current[:user].name, dest = Thread.current[:dest]) click to toggle source
# File lib/slack/smart-bot/utils/answer_delete.rb, line 2
def answer_delete(from = Thread.current[:user].name, dest = Thread.current[:dest])
    if @answer.key?(from)
        if Thread.current[:on_thread]
            dest = Thread.current[:thread_ts]
        end
        if @answer[from].key?(dest)
            @answer[from].delete(dest)
        end
        @questions.delete(from) # to be backwards compatible #todo: remove when 2.0
    end
end
ask(question, context = nil, to = nil, dest = nil) click to toggle source

context: previous message to: user that should answer

# File lib/slack/smart-bot/comm/ask.rb, line 5
def ask(question, context = nil, to = nil, dest = nil)
  if dest.nil? and Thread.current.key?(:dest)
    dest = Thread.current[:dest]
  end
  if to.nil?
    to = Thread.current[:user].name
  end
  if context.nil?
    context = Thread.current[:command]
  end
  message = "#{to}: #{question}"
  if dest.nil?
    if config[:simulate]
      open("#{config.path}/buffer_complete.log", "a") { |f|
        f.puts "|#{@channel_id}|#{config[:nick_id]}|#{config[:nick]}|#{message}~~~"
      }
    else  
      if Thread.current[:on_thread]
        client.message(channel: @channel_id, text: message, as_user: true, thread_ts: Thread.current[:thread_ts])
      else
        client.message(channel: @channel_id, text: message, as_user: true)
      end
    end
    if config[:testing] and config.on_master_bot
      open("#{config.path}/buffer.log", "a") { |f|
        f.puts "|#{@channel_id}|#{config[:nick_id]}|#{config[:nick]}|#{message}"
      }
    end
  elsif dest[0] == "C" or dest[0] == "G" # channel
    if config[:simulate]
      open("#{config.path}/buffer_complete.log", "a") { |f|
        f.puts "|#{dest}|#{config[:nick_id]}|#{config[:nick]}|#{message}~~~"
      }
    else  
      if Thread.current[:on_thread]
        client.message(channel: dest, text: message, as_user: true, thread_ts: Thread.current[:thread_ts])
      else
        client.message(channel: dest, text: message, as_user: true)
      end
    end
    if config[:testing] and config.on_master_bot
      open("#{config.path}/buffer.log", "a") { |f|
        f.puts "|#{dest}|#{config[:nick_id]}|#{config[:nick]}|#{message}"
      }
    end
  elsif dest[0] == "D" #private message
    send_msg_user(dest, message)
  end
  if Thread.current[:on_thread]
    qdest = Thread.current[:thread_ts]
  else
    qdest = dest
  end
  @answer[to] = {} unless @answer.key?(to)
  @answer[to][qdest] = context
  @questions[to] = context # to be backwards compatible #todo remove it when 2.0
end
bot_help(user, from, dest, dchannel, specific, help_command, rules_file) click to toggle source

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

# File lib/slack/smart-bot/commands/general/bot_help.rb, line 16
def bot_help(user, from, dest, dchannel, specific, help_command, rules_file)
  save_stats(__method__)
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
    respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
  else
    help_found = false

    message = ""
    if help_command.to_s != ''
      help_command = '' if help_command.to_s.match?(/^\s*expanded\s*$/i) or help_command.to_s.match?(/^\s*extended\s*$/i)
      expanded = true
      message_not_expanded = ''
    else
      expanded = false
      message_not_expanded = "*If you want to see the expanded version of `bot help` or `bot rules`, please call `bot help expanded` or `bot rules expanded`*\n"
      message_not_expanded += "*Also to get specific expanded help for a specific command or rule call `bot help COMMAND`*\n"
    end

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

    if help_command.to_s != ""
      help_message.gsub(/====+/,'-'*30).split(/^\s*-------*$/).each do |h|
        if h.match?(/[`_]#{help_command}/i)
          respond h, dest
          help_found = true
        end
      end
    else
      if Thread.current[:using_channel]!=''
        message += "*You are using rules from another channel: <##{Thread.current[:using_channel]}>. These are the specific commands for that channel:*"
      end
      respond message, dest
    end

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

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

        def project_folder
          ""
        end
      end
    elsif help_command.to_s == ""
      respond "Slack Smart Bot Github project: https://github.com/MarioRuiz/slack-smart-bot", dest
    end
    respond message_not_expanded unless expanded
  end
end
bot_rules(dest, help_command, typem, rules_file, user) click to toggle source
# File lib/slack/smart-bot/commands/on_extended/bot_rules.rb, line 2
def bot_rules(dest, help_command, typem, rules_file, user)
  save_stats(__method__)
  from = user.name
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
    respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
  else
    if typem == :on_extended or typem == :on_call #for the other cases above.

      if help_command.to_s != ''
        help_command = '' if help_command.to_s.match?(/^\s*expanded\s*$/i) or help_command.to_s.match?(/^\s*extended\s*$/i)
        expanded = true
      else
        expanded = false
      end 

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

      if help_command.to_s != ""
        help_found = false
        help_filtered.split(/^\s*-------*$/).each do |h|
          if h.match?(/[`_]#{help_command}/i)
            respond "*#{config.channel}*:#{h}", dest
            help_found = true
          end
        end
        respond("*#{config.channel}*: I didn't find any command starting by `#{help_command}`", dest) unless help_found
      else
        message = "-\n\n\n===================================\n*Rules from channel #{config.channel}*\n"
        if typem == :on_extended
          message += "To run the commands on this extended channel, add `!`, `!!` or `^` before the command.\n"
        end
        message += help_filtered
        respond message, dest
      end

      unless rules_file.empty?
        begin
          eval(File.new(config.path+rules_file).read) if File.exist?(config.path+rules_file)
        end
      end
      if defined?(git_project) and git_project.to_s != "" and help_command.to_s == ""
        respond "Git project: #{git_project}", dest
      else
        def git_project() "" end
        def project_folder() "" end
      end
      unless expanded
        message_not_expanded = "*If you want to see the expanded version of `bot rules`, please call `bot rules expanded`*\n"
        message_not_expanded += "*Also to get specific expanded help for a specific command or rule call `bot rules COMMAND`*\n"
        respond message_not_expanded
      end
    end
  end
end
bot_stats(dest, from_user, typem, channel_id, from, to, user, st_command, exclude_masters, exclude_routines, exclude_command, monthly, all_data) click to toggle source

help: ———————————————- help: `bot stats` helpmaster: `bot stats USER_NAME` help: `bot stats exclude masters` help: `bot stats exclude routines` help: `bot stats from YYYY/MM/DD` help: `bot stats from YYYY/MM/DD to YYYY/MM/DD` help: `bot stats CHANNEL` help: `bot stats CHANNEL from YYYY/MM/DD` help: `bot stats CHANNEL from YYYY/MM/DD to YYYY/MM/DD` help: `bot stats command COMMAND` helpmaster: `bot stats USER_NAME from YYYY/MM/DD to YYYY/MM/DD` helpmaster: `bot stats CHANNEL USER_NAME from YYYY/MM/DD to YYYY/MM/DD` help: `bot stats CHANNEL exclude masters from YYYY/MM/DD to YYYY/MM/DD` help: `bot stats today` help: `bot stats exclude COMMAND_ID` help: `bot stats monthly` help: `bot stats alldata` help: To see the bot stats helpmaster: You can use this command only if you are a Master admin user and if you are in a private conversation with the bot helpmaster: You need to set stats to true to generate the stats when running the bot instance. help: If alldata option supplied then it will be attached files including all data and not only the top 10. help: Examples: help: _bot stats sales_ helpmaster: _bot stats @peter.wind_ help: _bot stats sales from 2019/12/15 to 2019/12/31_ help: _bot stats sales today_ help: _bot stats sales from 2020-01-01 monthly_ help: _bot stats exclude routines masters from 2021/01/01 monthly_ help:

# File lib/slack/smart-bot/commands/general/bot_stats.rb, line 32
def bot_stats(dest, from_user, typem, channel_id, from, to, user, st_command, exclude_masters, exclude_routines, exclude_command, monthly, all_data)
    require 'csv'
    if config.stats
        message = []
    else
        message = ["You need to set stats to true to generate the stats when running the bot instance."]
    end
    save_stats(__method__)
    if (from_user.id != user and (config.masters.include?(from_user.name) or @master_admin_users_id.include?(from_user.id)) and (typem==:on_dm or dest[0]=='D'))
        on_dm_master = true #master admin user
    else
        on_dm_master = false
    end
    if on_dm_master or (from_user.id == user) # normal user can only see own stats
        if !File.exist?("#{config.stats_path}.#{Time.now.strftime("%Y-%m")}.log")
            message<<'No stats'
        else
            from = "#{Time.now.strftime("%Y-%m")}-01" if from == ''
            to = "#{Time.now.strftime("%Y-%m-%d")}" if to == ''
            from_short = from
            to_short = to
            from_file = from[0..3] + '-' + from[5..6]
            to_file = to[0..3] + '-' + to[5..6]
            from+= " 00:00:00 +0000"
            to+= " 23:59:59 +0000"
            rows = []
            rows_month = {}
            users_month = {}
            commands_month = {}
            users_id_name = {}
            users_name_id = {}
            count_users = {}
            count_channels_dest = {}

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

            if user!=''
                user_info = get_user_info(user)
                if users_id_name.key?(user_info.user.id)
                    user_name = users_id_name[user_info.user.id]
                else
                    user_name = user_info.user.name
                end
                if users_name_id.key?(user_info.user.name)
                    user_id = users_name_id[user_info.user.name]
                else
                    user_id = user_info.user.id
                end
            end
            master_admins = config.masters.dup
            if users_id_name.size > 0
                config.masters.each do |u|
                    if users_id_name.key?(u)
                        master_admins << users_id_name[u]
                    elsif users_name_id.key?(u)
                        master_admins << users_name_id[u]
                    end
                end
            end

            Dir["#{config.stats_path}.*.log"].sort.each do |file|
                if file >= "#{config.stats_path}.#{from_file}.log" or file <= "#{config.stats_path}.#{to_file}.log"
                    CSV.foreach(file, headers: true, header_converters: :symbol, converters: :numeric) do |row|
                        row[:date] = row[:date].to_s
                        if row[:dest_channel_id].to_s[0]=='D'
                            row[:dest_channel] = 'DM'
                        elsif row[:dest_channel].to_s == ''
                            row[:dest_channel] = row[:dest_channel_id]
                        end
                        if users_name_id.size > 0
                            row[:user_name] = users_id_name[row[:user_id]]
                            row[:user_id] = users_name_id[row[:user_name]]
                        else
                            users_id_name[row[:user_id]] ||= row[:user_name]
                        end
                        if !exclude_masters or (exclude_masters and !master_admins.include?(row[:user_name]) and 
                                                !master_admins.include?(row[:user_id]) and
                                                !@master_admin_users_id.include?(row[:user_id]))
                            if !exclude_routines or (exclude_routines and !row[:user_name].match?(/^routine\//) )
                                if exclude_command == '' or (exclude_command!='' and row[:command]!=exclude_command)
                                    if st_command == '' or (st_command != '' and row[:command] == st_command)
                                        if row[:bot_channel_id] == channel_id or channel_id == ''
                                            if row[:date] >= from and row[:date] <= to
                                                count_users[row[:user_id]] ||= 0
                                                count_users[row[:user_id]] += 1
                                                if user=='' or (user!='' and row[:user_name] == user_name) or (user!='' and row[:user_id] == user_id)
                                                    rows << row.to_h
                                                    count_channels_dest[row[:dest_channel]] ||= 0
                                                    count_channels_dest[row[:dest_channel]] += 1
                                                    if monthly
                                                        rows_month[row[:date][0..6]] = 0 unless rows_month.key?(row[:date][0..6])
                                                        users_month[row[:date][0..6]] = [] unless users_month.key?(row[:date][0..6])
                                                        commands_month[row[:date][0..6]] = [] unless commands_month.key?(row[:date][0..6])
                                                        rows_month[row[:date][0..6]] += 1
                                                        users_month[row[:date][0..6]] << row[:user_id]
                                                        commands_month[row[:date][0..6]] << row[:command]
                                                    end
                                                end
                                            end
                                        end
                                    end
                                end
                            end
                        end
                    end
                end
            end
            total = rows.size
            if exclude_masters
                message << 'Excluding master admins'
            end
            if exclude_routines
                message << 'Excluding routines'
            end
            if exclude_command != ''
                message << "Excluding command #{exclude_command}"
            end
            if st_command != ''
                message << "Including only command #{st_command}"
            end
            if user!=''
                if user==from_user.id
                    message << "Bot stats for <@#{user}>"
                else
                    message << "Showing only user <@#{user}>"
                end
            end
            if channel_id == ''
                message << "*Total calls*: #{total} from #{from_short} to #{to_short}"
            else
                message << "*Total calls <##{channel_id}>*: #{total} from #{from_short} to #{to_short}"
            end
            unless count_users.size == 0 or total == 0 or user == ''
                my_place = (count_users.sort_by(&:last).reverse.to_h.keys.index(user_id)+1)
                message <<"\tYou are the *\# #{my_place}* of *#{count_users.size}* users"
            end
            if total > 0
                if monthly 
                    if on_dm_master
                        message << '*Totals by month / commands / users (%new)*'
                    else
                        message << '*Totals by month / commands*'
                    end

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

                if channel_id == ''
                    message << "*SmartBots*"
                    channels = rows.bot_channel.uniq.sort
                    channels.each do |channel|
                        count = rows.count {|h| h.bot_channel==channel}
                        message << "\t#{channel}: #{count} (#{(count.to_f*100/total).round(2)}%)"
                    end
                end
                channels_dest_attachment = []
                count_channels_dest = count_channels_dest.sort_by(&:last).reverse.to_h
                if count_channels_dest.size > 10
                    message << "*From Channel* - #{count_channels_dest.size} (Top 10)"
                else
                    message << "*From Channel* - #{count_channels_dest.size}"
                end

                count_channels_dest.keys[0..9].each do |ch|
                    message << "\t#{ch}: #{count_channels_dest[ch]} (#{(count_channels_dest[ch].to_f*100/total).round(2)}%)"
                end
                if count_channels_dest.size > 10 and all_data
                    count_channels_dest.each do |ch, value|
                        channels_dest_attachment << "\t#{ch}: #{value} (#{(value.to_f*100/total).round(2)}%)"
                    end
                end


                users_attachment = []
                if user==''
                    users = rows.user_id.uniq.sort
                    if users.size > 10
                        message << "*Users* - #{users.size} (Top 10)"
                    else
                        message << "*Users* - #{users.size}"
                    end
                    count_user = {}
                    users.each do |user|
                        count = rows.count {|h| h.user_id==user}
                        count_user[user] = count
                    end
                    i = 0
                    count_user.sort_by {|k,v| -v}.each do |user, count|
                        i+=1
                        if i <= 10
                            message << "\t#{users_id_name[user]}: #{count} (#{(count.to_f*100/total).round(2)}%)"
                        end
                        if users.size > 10 and all_data
                            users_attachment << "\t#{users_id_name[user]}: #{count} (#{(count.to_f*100/total).round(2)}%)"
                        end
                    end
                end
                commands_attachment = []

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

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

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

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

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

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:

# File lib/slack/smart-bot/commands/general/bot_status.rb, line 7
def bot_status(dest, user)
  save_stats(__method__)
  get_bots_created()
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
    respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
  else
    gems_remote = `gem list slack-smart-bot --remote`
    version_remote = gems_remote.to_s().scan(/slack-smart-bot \((\d+\.\d+\.\d+)/).join
    version_message = ""
    if Gem::Version.new(version_remote) > Gem::Version.new(VERSION)
      version_message = " There is a new available version: #{version_remote}."
    end
    require "socket"
    ip_address = Socket.ip_address_list.find { |ai| ai.ipv4? && !ai.ipv4_loopback? }.ip_address
    respond "*#{Socket.gethostname} (#{ip_address})*\n\tStatus: #{@status}.\n\tVersion: #{VERSION}.#{version_message}\n\tRules file: #{File.basename config.rules_file}\n\tExtended: #{@bots_created[@channel_id][:extended] unless config.on_master_bot}\n\tAdmins: #{config.admins}\n\tBot time: #{Time.now}", dest
    if @status == :on
      respond "I'm listening to [#{@listening.keys.join(", ")}]", dest
      if config.on_master_bot and config.admins.include?(user.name)
        sleep 5
        @bots_created.each do |k, v|
          msg = []
          msg << "`#{v[:channel_name]}` (#{k}):"
          msg << "\tcreator: #{v[:creator_name]}"
          msg << "\tadmins: #{v[:admins]}"
          msg << "\tstatus: #{v[:status]} #{" *(not responded)*" unless @pings.include?(v[:channel_name])}"
          msg << "\tcreated: #{v[:created]}"
          msg << "\trules: #{v[:rules_file]}"
          msg << "\textended: #{v[:extended]}"
          msg << "\tcloud: #{v[:cloud]}"
          if config.on_master_bot and v.key?(:cloud) and v[:cloud]
            msg << "\trunner: `ruby #{config.file} \"#{v[:channel_name]}\" \"#{v[:admins]}\" \"#{v[:rules_file]}\" on&`"
          end
          respond msg.join("\n"), dest
        end
        @pings = []
      end
    end
  end
end
build_help(path, expanded) click to toggle source
# File lib/slack/smart-bot/utils/build_help.rb, line 3
def build_help(path, expanded)
  help_message = {normal: {}, admin: {}, master: {}}
  if Dir.exist?(path)
    files = Dir["#{path}/*"]
  elsif File.exist?(path)
    files = [path]
  else
    return help_message
  end

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

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

# 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", "Bæ", "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) click to toggle source

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:

# File lib/slack/smart-bot/commands/on_master/create_bot.rb, line 10
def create_bot(dest, user, cloud, channel)
  save_stats(__method__)
  from = user.name
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
    respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
  else
    if config.on_master_bot
      get_channels_name_and_id() unless @channels_name.keys.include?(channel) or @channels_id.keys.include?(channel)
      channel_id = nil
      if @channels_name.key?(channel) #it is an id
        channel_id = channel
        channel = @channels_name[channel_id]
      elsif @channels_id.key?(channel) #it is a channel name
        channel_id = @channels_id[channel]
      end
      #todo: add pagination for case more than 1000 channels on the workspace
      channels = get_channels()
      channel_found = channels.detect { |c| c.name == channel }
      members = get_channel_members(@channels_id[channel]) unless channel_found.nil?

      if channel_id.nil?
        respond "There is no channel with that name: #{channel}, please be sure is written exactly the same", dest
      elsif channel == config.master_channel
        respond "There is already a bot in this channel: #{channel}", dest
      elsif @bots_created.keys.include?(channel_id)
        respond "There is already a bot in this channel: #{channel}, kill it before", dest
      elsif config[:nick_id] != channel_found.creator and !members.include?(config[:nick_id])
        respond "You need to add first to the channel the smart bot user: <@#{config[:nick_id]}>", dest
      else
        if channel_id != config[:channel]
          begin
            rules_file = "slack-smart-bot_rules_#{channel_id}_#{from.gsub(" ", "_")}.rb"
            if defined?(RULES_FOLDER)
              rules_file = RULES_FOLDER + rules_file
              general_rules_file = RULES_FOLDER + 'general_rules.rb'
            else
              Dir.mkdir("#{config.path}/rules") unless Dir.exist?("#{config.path}/rules")
              Dir.mkdir("#{config.path}/rules/#{channel_id}") unless Dir.exist?("#{config.path}/rules/#{channel_id}")
              rules_file = "/rules/#{channel_id}/" + rules_file
              general_rules_file = "/rules/general_rules.rb"
            end
            default_rules = (__FILE__).gsub(/slack\/smart-bot\/commands\/on_master\/create_bot\.rb$/, "slack-smart-bot_rules.rb")
            default_general_rules = (__FILE__).gsub(/slack\/smart-bot\/commands\/on_master\/create_bot\.rb$/, "slack-smart-bot_general_rules.rb")
            
            File.delete(config.path + rules_file) if File.exist?(config.path + rules_file)
            FileUtils.copy_file(default_rules, config.path + rules_file) unless File.exist?(config.path + rules_file)
            FileUtils.copy_file(default_general_rules, config.path + general_rules_file) unless File.exist?(config.path + general_rules_file)
            admin_users = Array.new()
            creator_info = get_user_info(channel_found.creator)
            admin_users = [from, creator_info.user.name] + config.masters
            admin_users.uniq!
            @logger.info "ruby #{config.file_path} \"#{channel}\" \"#{admin_users.join(",")}\" \"#{rules_file}\" on"
        
            if cloud
              respond "Copy the bot folder to your cloud location and run `ruby #{config.file} \"#{channel}\" \"#{admin_users.join(",")}\" \"#{rules_file}\" on&`", dest
            else
              t = Thread.new do
                `BOT_SILENT=false ruby #{config.file_path} \"#{channel}\" \"#{admin_users.join(",")}\" \"#{rules_file}\" on`
              end
            end
            @bots_created[channel_id] = {
              creator_name: from,
              channel_id: channel_id,
              channel_name: @channels_name[channel_id],
              status: :on,
              created: Time.now.strftime("%Y-%m-%dT%H:%M:%S.000Z")[0..18],
              rules_file: rules_file,
              admins: admin_users.join(","),
              extended: [],
              cloud: cloud,
              thread: t,
            }
            respond "The bot has been created on channel: #{channel}. Rules file: #{File.basename rules_file}. Admins: #{admin_users.join(", ")}", dest
            update_bots_file()
          rescue Exception => stack
            @logger.fatal stack
            message = "Problem creating the bot on channel #{channel}. Error: <#{stack}>."
            @logger.error message
            respond message, dest
          end
        else
          respond "There is already a bot in this channel: #{channel}, and it is the Master Channel!", dest
        end
      end
    else
      respond "Sorry I cannot create bots from this channel, please visit the master channel: <##{@master_bot_id}>", dest
    end
  end
end
create_routine_thread(name) click to toggle source
# File lib/slack/smart-bot/utils/create_routine_thread.rb, line 3
def create_routine_thread(name)
  t = Thread.new do
    while @routines.key?(@channel_id) and @routines[@channel_id].key?(name)
      @routines[@channel_id][name][:thread] = Thread.current
      started = Time.now
      if @status == :on and @routines[@channel_id][name][:status] == :on
        @logger.info "Routine: #{@routines[@channel_id][name].inspect}"
        if @routines[@channel_id][name][:file_path].match?(/\.rb$/i)
          ruby = "ruby "
        else
          ruby = ""
        end
        @routines[@channel_id][name][:silent] = false if !@routines[@channel_id][name].key?(:silent)
        if @routines[@channel_id][name][:at] == "" or
           (@routines[@channel_id][name][:at] != "" and @routines[@channel_id][name][:running] and
            @routines[@channel_id][name][:next_run] != "" and Time.now.to_s >= @routines[@channel_id][name][:next_run])
          if @routines[@channel_id][name][:file_path] != ""
            process_to_run = "#{ruby}#{Dir.pwd}#{@routines[@channel_id][name][:file_path][1..-1]}"
            process_to_run = ("cd #{project_folder} &&" + process_to_run) if defined?(project_folder)
            data = {
              dest: @routines[@channel_id][name][:dest],
              typem: 'routine_file',
              user: {id: @routines[@channel_id][name][:creator_id], name: @routines[@channel_id][name][:creator]},
              files: false,
              command: @routines[@channel_id][name][:file_path],
              routine: true
            }            
            save_stats(name, data: data)
            stdout, stderr, status = Open3.capture3(process_to_run)
            if !@routines[@channel_id][name][:silent] or (@routines[@channel_id][name][:silent] and 
              (!stderr.match?(/\A\s*\z/) or !stdout.match?(/\A\s*\z/)))
              if @routines[@channel_id][name][:dest]!=@channel_id
                respond "routine from <##{@channel_id}> *`#{name}`*: #{@routines[@channel_id][name][:file_path]}", @routines[@channel_id][name][:dest]
              else
                respond "routine *`#{name}`*: #{@routines[@channel_id][name][:file_path]}", @routines[@channel_id][name][:dest]
              end
            end
            if stderr == ""
              unless stdout.match?(/\A\s*\z/)
                respond stdout, @routines[@channel_id][name][:dest]
              end
            else
              respond "#{stdout} #{stderr}", @routines[@channel_id][name][:dest]
            end
          else #command
            if !@routines[@channel_id][name][:silent]
              if @routines[@channel_id][name][:dest]!=@channel_id
                respond "routine from <##{@channel_id}> *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest]
              else
                respond "routine *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest]
              end
            end
            started = Time.now
            data = { channel: @channel_id,
              dest: @routines[@channel_id][name][:dest],
              user: @routines[@channel_id][name][:creator_id],
              text: @routines[@channel_id][name][:command],
              files: nil,
              routine: true }
            treat_message(data)
          end
          # in case the routine was deleted while running the process
          if !@routines.key?(@channel_id) or !@routines[@channel_id].key?(name)
            Thread.exit
          end
          @routines[@channel_id][name][:last_run] = started.to_s
        end
        if @routines[@channel_id][name][:last_run] == "" and @routines[@channel_id][name][:next_run] != "" #for the first create_routine of one routine with at
          elapsed = 0
          require "time"
          every_in_seconds = Time.parse(@routines[@channel_id][name][:next_run]) - Time.now
        elsif @routines[@channel_id][name][:at] != "" #coming from start after pause for 'at'
          if @routines[@channel_id][name].key?(:dayweek) and @routines[@channel_id][name][:dayweek].to_s!=''
            day = @routines[@channel_id][name][:dayweek]
            days = ['sunday','monday','tuesday','wednesday','thursday','friday','saturday']
            incr = days.index(day) - Time.now.wday
            if incr < 0 
              incr = (7+incr)*24*60*60
            else
              incr = incr * 24 * 60 * 60
            end
            days = incr/(24*60*60)
            weekly = true
          else
            days = 0
            weekly = false
          end

          if started.strftime("%H:%M:%S") < @routines[@channel_id][name][:at] and days == 0
            nt = @routines[@channel_id][name][:at].split(":")
            next_run = Time.new(started.year, started.month, started.day, nt[0], nt[1], nt[2])
          else
            if days == 0 and started.strftime("%H:%M:%S") >= @routines[@channel_id][name][:at]
              if weekly
                  days = 7
              else
                  days = 1
              end
            end
            next_run = started + (days * 24 * 60 * 60) # one more day/week
            nt = @routines[@channel_id][name][:at].split(":")
            next_run = Time.new(next_run.year, next_run.month, next_run.day, nt[0], nt[1], nt[2])
          end
          @routines[@channel_id][name][:next_run] = next_run.to_s
          elapsed = 0
          every_in_seconds = next_run - started
        else
          every_in_seconds = @routines[@channel_id][name][:every_in_seconds]
          elapsed = Time.now - started
          @routines[@channel_id][name][:last_elapsed] = elapsed
          @routines[@channel_id][name][:next_run] = (started + every_in_seconds).to_s
        end
        @routines[@channel_id][name][:running] = true
        @routines[@channel_id][name][:sleeping] = (every_in_seconds - elapsed).ceil
        update_routines()
        sleep(@routines[@channel_id][name][:sleeping]) unless elapsed > every_in_seconds
      else
        sleep 30
      end
    end
  end
end
delete_repl(dest, user, session_name) click to toggle source

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

# File lib/slack/smart-bot/commands/on_bot/delete_repl.rb, line 9
def delete_repl(dest, user, session_name)
  #todo: add tests
  save_stats(__method__)
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
    respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
  else
    if @repls.key?(session_name)
      Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
      if config.admins.include?(user.name) or @repls[session_name].creator_name == user.name
        @repls.delete(session_name)
        update_repls()
        File.rename("#{config.path}/repl/#{@channel_id}/#{session_name}.input", "#{config.path}/repl/#{@channel_id}/#{session_name}_#{Time.now.strftime("%Y%m%d%H%M%S%N")}.deleted")
        File.delete("#{config.path}/repl/#{@channel_id}/#{session_name}.output") if File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.output")
        File.delete("#{config.path}/repl/#{@channel_id}/#{session_name}.run") if File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.run")
        respond "REPL #{session_name} deleted"
      else
        respond "Only admins or the creator of this REPL can delete it", dest
      end

    else
      respond "The REPL with session name: #{session_name} doesn't exist on this Smart Bot Channel", dest
    end
  end
end
delete_shortcut(dest, user, shortcut, typem, command, global) click to toggle source

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

# File lib/slack/smart-bot/commands/on_bot/delete_shortcut.rb, line 10
def delete_shortcut(dest, user, shortcut, typem, command, global)
  save_stats(__method__)
  unless typem == :on_extended
    from = user.name
    if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
      (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
      respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
    else
      deleted = false

      if global
        if !config.on_master_bot or typem != :on_master
          respond "It is only possible to delete global shortcuts from Master channel"
        else
          if !config.admins.include?(from) and @shortcuts_global[:all].include?(shortcut) and 
            (!@shortcuts_global.key?(from) or !@shortcuts_global[from].include?(shortcut))
            respond "Only the creator of the shortcut or an admin user can delete it"
          elsif (@shortcuts_global.key?(from) and @shortcuts_global[from].keys.include?(shortcut)) or
            (config.admins.include?(from) and @shortcuts_global[:all].include?(shortcut))
            
            respond "global shortcut deleted!", dest
            if @shortcuts_global.key?(from) and @shortcuts_global[from].key?(shortcut)
              respond("#{shortcut}: #{@shortcuts_global[from][shortcut]}", dest) 
            elsif @shortcuts_global.key?(:all) and @shortcuts_global[:all].key?(shortcut)
              respond("#{shortcut}: #{@shortcuts_global[:all][shortcut]}", dest)
            end
            @shortcuts_global[from].delete(shortcut) if @shortcuts_global.key?(from) and @shortcuts_global[from].key?(shortcut)
            @shortcuts_global[:all].delete(shortcut) if @shortcuts_global.key?(:all) and @shortcuts_global[:all].key?(shortcut)
            update_shortcuts_file()
          else
            respond 'shortcut not found'
          end
        end
      else
        if !config.admins.include?(from) and @shortcuts[:all].include?(shortcut) and 
          (!@shortcuts.key?(from) or !@shortcuts[from].include?(shortcut))
          respond "Only the creator of the shortcut or an admin user can delete it", dest
        elsif (@shortcuts.keys.include?(from) and @shortcuts[from].keys.include?(shortcut)) or
              (config.admins.include?(from) and @shortcuts[:all].include?(shortcut))
          #are you sure? to avoid deleting by mistake
          if answer.empty?
            ask("are you sure you want to delete it?", command, from, dest)
          else
            case answer
            when /^(yes|yep)/i
              answer_delete(from)
              respond "shortcut deleted!", dest
              if @shortcuts.key?(from) and @shortcuts[from].key?(shortcut)
                respond("#{shortcut}: #{@shortcuts[from][shortcut]}", dest) 
              elsif @shortcuts.key?(:all) and @shortcuts[:all].key?(shortcut)
                respond("#{shortcut}: #{@shortcuts[:all][shortcut]}", dest)
              end
              @shortcuts[from].delete(shortcut) if @shortcuts.key?(from) and @shortcuts[from].key?(shortcut)
              @shortcuts[:all].delete(shortcut) if @shortcuts.key?(:all) and @shortcuts[:all].key?(shortcut)
              update_shortcuts_file()
            when /^no/i
              answer_delete(from)
              respond "ok, I won't delete it", dest
            else
              ask("I don't understand, are you sure you want to delete it? (yes or no)", command, from, dest)
            end
          end
        else
          respond "shortcut not found", dest
        end
      end
    end
  end
end
dont_understand(rules_file = nil, command = nil, user = nil, dest = nil, answer = ["what?", "huh?", "sorry?", "what do you mean?", "I don't understand"], channel_rules: config.channel, typem: nil) click to toggle source
# File lib/slack/smart-bot/comm/dont_understand.rb, line 3
def dont_understand(rules_file = nil, command = nil, user = nil, dest = nil, answer = ["what?", "huh?", "sorry?", "what do you mean?", "I don't understand"], channel_rules: config.channel, typem: nil)
  save_stats(:dont_understand)
  command = Thread.current[:command] if command.nil?
  user = Thread.current[:user] if user.nil?
  dest = Thread.current[:dest] if dest.nil?
  rules_file = Thread.current[:rules_file] if rules_file.nil?
  typem = Thread.current[:typem] if typem.nil?
  if typem==:on_extended
    get_bots_created()
  end
  text = get_help(rules_file, dest, user.name, typem==:on_extended, true)

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

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

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

  if typem==:on_extended
    if @extended_from[@channels_name[dest]].size == 1
      respond "#{user.profile.display_name}, I don't understand.", dest
    end
    unless res_final.empty?
      respond "Similar rules on : *#{channel_rules}*\n`#{res_final[0..4].join("`\n`")}`", dest
    end
  else
    message = ''
    message = "\nTake in consideration when on external calls, not all the commands are availalbe." if typem==:on_call
    if res_final.empty?
      resp = answer.sample
      respond "#{user.profile.display_name}, #{resp}#{message}", dest
    else
      respond "#{user.profile.display_name}, I don't understand. Maybe you are trying to say:\n`#{res_final[0..4].join("`\n`")}`#{message}", dest
    end
  end
end
event_hello() click to toggle source
# File lib/slack/smart-bot/comm/event_hello.rb, line 2
def event_hello()
  unless config.simulate
    m = "Successfully connected, welcome '#{client.self.name}' to the '#{client.team.name}' team at https://#{client.team.domain}.slack.com."
    puts m
    @logger.info m
    config.nick = client.self.name
    config.nick_id = client.self.id
  end
  @salutations = [config[:nick], "<@#{config[:nick_id]}>", "bot", "smart"]

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

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:

# File lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb, line 10
def exit_bot(command, from, dest, display_name)
  save_stats(__method__)
  if config.on_master_bot
    if config.admins.include?(from) #admin user
      if answer.empty?
        ask("are you sure?", command, from, dest)
      else
        case answer
        when /yes/i, /yep/i, /sure/i
          respond "Game over!", dest
          respond "Ciao #{display_name}!", dest
          @bots_created.each { |key, value|
            value[:thread] = ""
            send_msg_channel(key, "Bot has been closed by #{from}")
            sleep 0.5
          }
          update_bots_file()
          sleep 0.5
          if config.simulate
            @status = :off
            config.simulate = false
            Thread.exit
          else
            exit!
          end
        when /no/i, /nope/i, /cancel/i
          answer_delete(from)
          respond "Thanks, I'm happy to be alive", dest
        else
          ask("I don't understand, are you sure do you want me to close? (yes or no)", command, from, dest)
        end
      end
    else
      respond "Only admin users can kill me", dest
    end
  else
    respond "To do this you need to be an admin user in the master channel: <##{@master_bot_id}>", dest
  end
end
extend_rules(dest, user, from, channel, typem) click to toggle source

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:

# File lib/slack/smart-bot/commands/on_bot/admin/extend_rules.rb, line 8
def extend_rules(dest, user, from, channel, typem)
  save_stats(__method__)
  unless typem == :on_extended
    if config.on_master_bot
      respond "You cannot use the rules from Master Channel on any other channel.", dest
    elsif !config.admins.include?(from) #not admin
      respond "Only admins can extend the rules. Admins on this channel: #{config.admins}", dest
    else
      #todo: add pagination for case more than 1000 channels on the workspace
      channels = get_channels()

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

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:

# 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_created() click to toggle source
# 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_id() click to toggle source
# File lib/slack/smart-bot/utils/get_channels_name_and_id.rb, line 3
def get_channels_name_and_id
  channels = get_channels()
  @channels_id = Hash.new()
  @channels_name = Hash.new()
  channels.each do |ch|
    unless ch.is_archived
      @channels_id[ch.name] = ch.id
      @channels_name[ch.id] = ch.name
    end
  end
end
get_help(rules_file, dest, from, only_rules, expanded) click to toggle source
# File lib/slack/smart-bot/utils/get_help.rb, line 2
def get_help(rules_file, dest, from, only_rules, expanded)
  order = {
    general: [:whats_new, :hi_bot, :bye_bot, :bot_help, :bot_status, :use_rules, :stop_using_rules, :bot_stats],
    on_bot: [:ruby_code, :repl, :get_repl, :run_repl, :delete_repl, :see_repls, :add_shortcut, :delete_shortcut, :see_shortcuts],
    on_bot_admin: [:extend_rules, :stop_using_rules_on, :start_bot, :pause_bot, :add_routine,
      :see_routines, :start_routine, :pause_routine, :remove_routine, :run_routine]
  }
  if config.masters.include?(from)
    user_type = :master # master admin
  elsif config.admins.include?(from)
    user_type = :admin
  else
    user_type = :normal #normal user
  end
  # channel_type: :bot, :master_bot, :direct, :extended, :external
  if dest[0] == "D"
    channel_type = :direct
  elsif config.on_master_bot
    channel_type = :master_bot
  elsif @channel_id != dest
    channel_type = :extended
  else
    channel_type = :bot
  end

  @help_messages_expanded ||= build_help("#{__dir__}/../commands", true)
  @help_messages_not_expanded ||= build_help("#{__dir__}/../commands", false)
  if only_rules
    help = {}
  elsif expanded
    help = @help_messages_expanded.deep_copy[user_type]
  else
    help = @help_messages_not_expanded.deep_copy[user_type]
  end

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

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

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

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

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

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

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

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

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

  return txt
end
get_repl(dest, user, session_name) click to toggle source

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

# File lib/slack/smart-bot/commands/on_bot/get_repl.rb, line 8
def get_repl(dest, user, session_name)
  #todo: add tests
  save_stats(__method__)
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
    respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
  else
    Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
    Dir.mkdir("#{config.path}/repl/#{@channel_id}") unless Dir.exist?("#{config.path}/repl/#{@channel_id}")
    if File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.run")
      if @repls.key?(session_name) and (@repls[session_name][:type] == :private or @repls[session_name][:type] == :private_clean) and 
        @repls[session_name][:creator_name]!=user.name and 
        !config.admins.include?(user.name)
        respond "The REPL with session name: #{session_name} is private", dest
      else
        content = "require 'nice_http'\n"
        if @repls.key?(session_name)
          @repls[session_name][:accessed] = Time.now.to_s
          @repls[session_name][:gets] += 1
          update_repls()
        end
        if !@repls.key?(session_name) or 
          (File.exist?("#{project_folder}/.smart-bot-repl") and @repls[session_name][:type] != :private_clean and @repls[session_name][:type] != :public_clean)
          content += File.read("#{project_folder}/.smart-bot-repl")
          content += "\n"
        end

        content += File.read("#{config.path}/repl/#{@channel_id}/#{session_name}.run").gsub(/^(quit|exit|bye)$/i,'') #todo: remove this gsub it will never contain it
        File.write("#{config.path}/repl/#{@channel_id}/#{session_name}.rb", content, mode: "w+")
        send_file(dest, "REPL #{session_name} on #{config.channel}", "#{config.path}/repl/#{@channel_id}/#{session_name}.rb", " REPL #{session_name} on #{config.channel}", 'text/plain', "ruby")
      end
    else
      respond "The REPL with session name: #{session_name} doesn't exist on this Smart Bot Channel", dest
    end
  end
end
get_repls(channel = @channel_id) click to toggle source
# 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) click to toggle source
# 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_imported() click to toggle source
# File lib/slack/smart-bot/utils/get_rules_imported.rb, line 3
def get_rules_imported
  if File.exist?("#{config.path}/rules/rules_imported.rb")
    if !defined?(@datetime_rules_imported) or @datetime_rules_imported != File.mtime("#{config.path}/rules/rules_imported.rb")        
      @datetime_rules_imported = File.mtime("#{config.path}/rules/rules_imported.rb")
      file_conf = IO.readlines("#{config.path}/rules/rules_imported.rb").join
      unless file_conf.to_s() == ""
        @rules_imported = eval(file_conf)
      end
    end
  end
end
get_user_info(user) click to toggle source
# File lib/slack/smart-bot/comm/get_user_info.rb, line 3
def get_user_info(user)
  if user.to_s.length>0
    if config.simulate and config.key?(:client)
      if user[0]=='@' #name
        client.web_client.users_info.select{|k, v| v[:user][:name] == user[1..-1]}.values[-1]
      else #id
        client.web_client.users_info[user.to_sym]
      end
    else
      client.web_client.users_info(user: user)
    end
  end
end
git_project() click to toggle source
# File lib/slack/smart-bot/commands/general/bot_help.rb, line 74
def git_project
  ""
end
hi_bot(user, dest, dchannel, from, display_name) click to toggle source

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

# 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", "Hæ"].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) click to toggle source

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:

# 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
listen() click to toggle source
# File lib/slack/smart-bot/listen.rb, line 44
def listen
  @pings = []
  @last_activity_check = Time.now
  get_bots_created()

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

  restarts = 0
  started = false
  while restarts < 200 and !started
    begin
      @logger.info "Bot starting: #{config.inspect}"
      client.start!
    rescue Slack::RealTime::Client::ClientAlreadyStartedError
      @logger.info "ClientAlreadyStarted so we continue with execution"
      started = true
    rescue Exception => e
      started = false
      restarts += 1
      if restarts < 200
        @logger.info "*" * 50
        @logger.fatal "Rescued on starting: #{e.inspect}"
        @logger.info "Waiting 60 seconds to retry. restarts: #{restarts}"
        puts "#{Time.now}: Not able to start client. Waiting 60 seconds to retry: #{config.inspect}"
        sleep 60
      else
        exit!
      end
    end
  end
end
listen_simulate() click to toggle source
# File lib/slack/smart-bot/listen.rb, line 2
def listen_simulate
  @salutations = [config[:nick], "<@#{config[:nick_id]}>", "bot", "smart"]
  @pings = []
  @last_activity_check = Time.now
  get_bots_created()
    @buffer_complete = [] unless defined?(@buffer_complete)
    b = File.read("#{config.path}/buffer_complete.log")
    result = b.scan(/^\|(\w+)\|(\w+)\|(\w+)\|([^~]+)~~~/m)
    result.delete(nil)
    new_messages = result[@buffer_complete.size..-1]
    unless new_messages.nil? or new_messages.empty?
      @buffer_complete = result
      new_messages.each do |message|
        channel = message[0].strip
        user = message[1].strip
        user_name = message[2].strip
        command = message[3].to_s.strip
        # take in consideration that on simulation we are treating all messages even those that are not populated on real cases like when the message is not populated to the specific bot connection when message is sent with the bot
        @logger.info "treat message: #{message}" if config.testing


        if command.match?(/^\s*\-!!/) or command.match?(/^\s*\-\^/)
          command.scan(/`([^`]+)`/).flatten.each do |cmd|
            if cmd.to_s!=''
              cmd = "^#{cmd}"
              treat_message({channel: channel, user: user, text: cmd, user_name: user_name}, false)
            end
          end
        elsif command.match?(/^\s*\-!/)
          command.scan(/`([^`]+)`/).flatten.each do |cmd|
            if cmd.to_s!=''
              cmd = "!#{cmd}"
              treat_message({channel: channel, user: user, text: cmd, user_name: user_name}, false)
            end
          end
        else
          treat_message({channel: channel, user: user, text: command, user_name: user_name})
        end
      end
    end
end
notify_message(dest, from, where, message) click to toggle source

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:

# File lib/slack/smart-bot/commands/on_master/admin_master/notify_message.rb, line 11
def notify_message(dest, from, where, message)
  save_stats(__method__)
  if config.on_master_bot
    if config.admins.include?(from) #admin user
      if where.nil? #not all and not channel
        @bots_created.each do |k, v|
          respond message, k
        end
        respond "Bot channels have been notified", dest
      elsif where == "all" #all
        myconv = get_channels(bot_is_in: true)
        myconv.each do |c|
          respond message, c.id unless c.name == config.master_channel
        end
        respond "Channels and users have been notified", dest
      else #channel
        respond message, where
        @bots_created[where][:extended].each do |ch|
          respond message, @channels_id[ch]
        end
        respond "Bot channel and extended channels have been notified", dest
      end
    end
  end
end
pause_bot(dest, from) click to toggle source

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:

# File lib/slack/smart-bot/commands/on_bot/admin/pause_bot.rb, line 9
def pause_bot(dest, from)
  save_stats(__method__)
  if config.admins.include?(from) #admin user
    respond "This bot is paused from now on. You can start it again: start this bot", dest
    respond "zZzzzzZzzzzZZZZZZzzzzzzzz", dest
    @status = :paused
    @bots_created[@channel_id][:status] = :paused
    update_bots_file()
    unless config.on_master_bot
      send_msg_channel config.master_channel, "Changed status on #{config.channel} to :paused"
    end
  else
    respond "Only admin users can put me on pause", dest
  end
end
pause_routine(dest, from, name) click to toggle source

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:

# 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) click to toggle source
# File lib/slack/smart-bot/process.rb, line 2
def process(user, command, dest, dchannel, rules_file, typem, files, ts)
  from = user.name
  if config.simulate
    display_name = user.profile.display_name
  else
    if user.profile.display_name.to_s.match?(/\A\s*\z/)
      user.profile.display_name = user.profile.real_name
    end
    display_name = user.profile.display_name
  end
  
  processed = true

  on_demand = false
  if command.match(/^@?(#{config[:nick]}):*\s+(.+)/im) or
     command.match(/^()!!(.+)/im) or
     command.match(/^()\^(.+)/im) or
     command.match(/^()!(.+)/im) or
     command.match(/^()<@#{config[:nick_id]}>\s+(.+)/im)
      command2 = $2
      Thread.current[:command] = command2
      if command2.match?(/^()!!(.+)/im) or
        command.match?(/^()\^(.+)/im)
        Thread.current[:on_thread] = true
      end
      command = command2
      on_demand = true
  end
  if (on_demand or typem == :on_dm or
    (@listening.key?(from) and (@listening[from].key?(dest) or @listening[from].key?(Thread.current[:thread_ts])) )) and 
    config.on_maintenance and !command.match?(/\A(set|turn)\s+maintenance\s+off\s*\z/)
    respond config.on_maintenance_message, dest
    processed = true
  end

  if !config.on_maintenance or (config.on_maintenance and command.match?(/\A(set|turn)\s+maintenance\s+off\s*\z/))
    #todo: check :on_pg in this case
    if typem == :on_master or typem == :on_bot or typem == :on_pg or typem == :on_dm
  
      case command

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

        bot_help(user, from, dest, dchannel, specific, help_command, rules_file)
      when /^\s*use\s+(rules\s+)?(from\s+)?<#C\w+\|(.+)>\s*$/i, /^use\s+(rules\s+)?(from\s+)?([^\s]+\s*$)/i
        channel = $3
        use_rules(dest, channel, user, dchannel)
      when /^\s*stop\s+using\s+rules\s+(from\s+)<#\w+\|(.+)>/i, /^stop\s+using\s+rules\s+(from\s+)(.+)/i
        channel = $2
        stop_using_rules(dest, channel, user, dchannel)
      when /^\s*extend\s+rules\s+(to\s+)<#C\w+\|(.+)>/i, /^extend\s+rules\s+(to\s+)(.+)/i,
          /^\s*use\s+rules\s+(on\s+)<#C\w+\|(.+)>/i, /^use\s+rules\s+(on\s+)(.+)/i
        channel = $2
        extend_rules(dest, user, from, channel, typem)
      when /^\s*stop\s+using\s+rules\s+(on\s+)<#\w+\|(.+)>/i, /^stop\s+using\s+rules\s+(on\s+)(.+)/i
        channel = $2
        stop_using_rules_on(dest, user, from, channel, typem)
      when /^\s*exit\s+bot\s*$/i, /^quit\s+bot\s*$/i, /^close\s+bot\s*$/i
        exit_bot(command, from, dest, display_name)
      when /^\s*start\s+(this\s+)?bot$/i
        start_bot(dest, from)
      when /^\s*pause\s+(this\s+)?bot$/i
        pause_bot(dest, from)
      when /^\s*bot\s+status/i
        bot_status(dest, user)
      when /\Anotify\s+<#(C\w+)\|.+>\s+(.+)\s*\z/im, /\Anotify\s+(all)?\s*(.+)\s*\z/im
        where = $1
        message = $2
        notify_message(dest, from, where, message)
      when /^\s*create\s+(cloud\s+)?bot\s+on\s+<#C\w+\|(.+)>\s*/i, /^create\s+(cloud\s+)?bot\s+on\s+(.+)\s*/i
        cloud = !$1.nil?
        channel = $2
        create_bot(dest, user, cloud, channel)
      when /^\s*kill\s+bot\s+on\s+<#C\w+\|(.+)>\s*$/i, /^kill\s+bot\s+on\s+(.+)\s*$/i
        channel = $1
        kill_bot_on_channel(dest, from, channel)
      when /^\s*(add|create)\s+(silent\s+)?routine\s+(\w+)\s+(every)\s+(\d+)\s*(days|hours|minutes|seconds|mins|min|secs|sec|d|h|m|s)\s*(\s#(\w+)\s*)(\s.+)?\s*$/i,
        /^\s*(add|create)\s+(silent\s+)?routine\s+(\w+)\s+(every)\s+(\d+)\s*(days|hours|minutes|seconds|mins|min|secs|sec|d|h|m|s)\s*(\s<#(C\w+)\|.+>\s*)?(\s.+)?\s*$/i,
        /^\s*(add|create)\s+(silent\s+)?routine\s+(\w+)\s+on\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)s?\s+at\s+(\d+:\d+:?\d+?)\s*()(\s#(\w+)\s*)(\s.+)?\s*$/i,
        /^\s*(add|create)\s+(silent\s+)?routine\s+(\w+)\s+on\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)s?\s+at\s+(\d+:\d+:?\d+?)\s*()(\s<#(C\w+)\|.+>\s*)?(\s.+)?\s*$/i,
        /^\s*(add|create)\s+(silent\s+)?routine\s+(\w+)\s+(at)\s+(\d+:\d+:?\d+?)\s*()(\s#(\w+)\s*)(\s.+)?\s*$/i,
        /^\s*(add|create)\s+(silent\s+)?routine\s+(\w+)\s+(at)\s+(\d+:\d+:?\d+?)\s*()(\s<#(C\w+)\|.+>\s*)?(\s.+)?\s*$/i
        silent = $2.to_s!=''
        name = $3.downcase
        type = $4
        number_time = $5
        period = $6
        channel = $8
        command_to_run = $9
        add_routine(dest, from, user, name, type, number_time, period, command_to_run, files, silent, channel)
      when /^\s*(kill|delete|remove)\s+routine\s+(\w+)\s*$/i
        name = $2.downcase
        remove_routine(dest, from, name)
      when /^\s*(run|execute)\s+routine\s+(\w+)\s*$/i
        name = $2.downcase
        run_routine(dest, from, name)
      when /^\s*pause\s+routine\s+(\w+)\s*$/i
        name = $1.downcase
        pause_routine(dest, from, name)
      when /^\s*start\s+routine\s+(\w+)\s*$/i
        name = $1.downcase
        start_routine(dest, from, name)
      when /^\s*see\s+(all\s+)?routines\s*$/i
        all = $1.to_s != ""
        see_routines(dest, from, user, all)
      when /^\s*get\s+bot\s+logs?\s*$/i
        get_bot_logs(dest, from, typem)
      when /^\s*bot\s+stats\s*(.*)\s*$/i
        opts = $1.to_s
        all_opts = opts.downcase.split(' ')
        all_data = all_opts.include?('alldata')
        st_channel = opts.scan(/<#(\w+)\|.+>/).join
        st_from = opts.scan(/from\s+(\d\d\d\d[\/\-\.]\d\d[\/\-\.]\d\d)/).join
        st_from = st_from.gsub('.','-').gsub('/','-')
        st_to = opts.scan(/to\s+(\d\d\d\d[\/\-\.]\d\d[\/\-\.]\d\d)/).join
        st_to = st_to.gsub('.','-').gsub('/','-')
        st_user = opts.scan(/<@([^>]+)>/).join
        st_command = opts.scan(/\s+command\s+(\w+)/i).join.downcase
        st_command = opts.scan(/^command\s+(\w+)/i).join.downcase if st_command == ''
        exclude_masters = all_opts.include?('exclude') && all_opts.include?('masters')
        exclude_routines = all_opts.include?('exclude') && all_opts.include?('routines')
        if exclude_masters
          opts.gsub!(/\s+masters$/,'')
          opts.gsub!(/\s+masters\s+/,'')
        end
        if exclude_routines
          opts.gsub!(/\s+routines$/,'')
          opts.gsub!(/\s+routines\s+/,'')
        end
        monthly = false
        if all_opts.include?('today')
          st_from = st_to = "#{Time.now.strftime("%Y-%m-%d")}"
        elsif all_opts.include?('monthly')
          monthly = true
        end
        exclude_command = opts.scan(/exclude\s+([^\s]+)/i).join
        unless @master_admin_users_id.include?(user.id)
          st_user = user.id
        end
        if (typem == :on_master or typem == :on_bot) and dest[0]!='D' #routine bot stats to be published on DM
          st_channel = dchannel
        end
        bot_stats(dest, user, typem, st_channel, st_from, st_to, st_user, st_command, exclude_masters, exclude_routines, exclude_command, monthly, all_data)
      when /\A(set|turn)\s+maintenance\s+(on|off)\s*()\z/im, /\A(set|turn)\s+maintenance\s+(on)\s*(.+)\s*\z/im
        status = $2.downcase
        message = $3.to_s
        set_maintenance(from, status, message)
      else
        processed = false
      end
    else
      processed = false
    end

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

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

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

        #kept to be backwards compatible
      when /^\s*id\schannel\s<#C\w+\|(.+)>\s*/i, /^id channel (.+)/
        unless typem == :on_extended
          channel_name = $1
          get_channels_name_and_id()
          if @channels_id.keys.include?(channel_name)
            respond "the id of #{channel_name} is #{@channels_id[channel_name]}", dest
          else
            respond "channel: #{channel_name} not found", dest
          end
        end
      when /^\s*ruby\s(.+)/im, /^\s*code\s(.+)/im
        code = $1
        code.gsub!("\\n", "\n")
        code.gsub!("\\r", "\r")
        @logger.info code
        ruby_code(dest, user, code, rules_file)
      when /^\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s*()()()$/i, 
        /^\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s+([\w\-]+)()()\s*$/i,
        /^\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s+([\w\-]+)\s*:\s+"([^"]+)"()\s*$/i,
        /^\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s+([\w\-]+)\s*:\s+"([^"]+)"\s+(.+)\s*$/i,
        /^\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)\s+([\w\-]+)()\s+(.+)\s*$/i,
        /^\s*(private\s+|clean\s+|clean\s+private\s+|private\s+clean\s+)?(repl|irb|live)()\s+()(.+)\s*$/i
        opts_type = $1.to_s.downcase.split(' ')
        opts_type.include?('private') ? type = :private : type = :public
        type = "#{type}_clean".to_sym if opts_type.include?('clean')
        
        session_name = $3
        description = $4
        opts = " #{$5}"
        env_vars = opts.scan(/\s+[\w\-]+="[^"]+"/i) + opts.scan(/\s+[\w\-]+='[^']+'/i)  
        opts.scan(/\s+[\w\-]+=[^'"\s]+/i).flatten.each do |ev|
          env_vars << ev.gsub('=',"='") + "'"
        end
        env_vars.each_with_index do |ev, idx|
            ev.gsub!("=","']=")
            ev.lstrip!
            env_vars[idx] = "ENV['#{ev}"
        end
        repl(dest, user, session_name, env_vars.flatten, rules_file, command, description, type)
      when /^\s*get\s+(repl|irb|live)\s+([\w\-]+)\s*/i
        session_name = $2
        get_repl(dest, user, session_name)      
      when /^\s*run\s+(repl|irb|live)\s+([\w\-]+)()\s*$/i,
        /^\s*run\s+(repl|irb|live)\s+([\w\-]+)\s+(.+)\s*$/i
        session_name = $2
        opts = " #{$3}"
        env_vars = opts.scan(/\s+[\w\-]+="[^"]+"/i) + opts.scan(/\s+[\w\-]+='[^']+'/i)  
        opts.scan(/\s+[\w\-]+=[^'"\s]+/i).flatten.each do |ev|
          env_vars << ev.gsub('=',"='") + "'"
        end
        env_vars.each_with_index do |ev, idx|
            ev.gsub!("=","']=")
            ev.lstrip!
            env_vars[idx] = "ENV['#{ev}"
        end
        run_repl(dest, user, session_name, env_vars.flatten, rules_file)      
      when /^\s*(delete|remove)\s+(repl|irb|live)\s+([\w\-]+)\s*$/i
        repl_name = $3
        delete_repl(dest, user, repl_name)
      when /^\s*see\s+(repls|repl|irb|irbs)\s*$/i
        see_repls(dest, user, typem)
      else
        processed2 = false
      end #of case

      processed = true if processed or processed2
    end
  end
  return processed
end
process_first(user, text, dest, dchannel, typem, files, ts, thread_ts, routine) click to toggle source
# File lib/slack/smart-bot/process_first.rb, line 2
def process_first(user, text, dest, dchannel, typem, files, ts, thread_ts, routine)
  nick = user.name
  rules_file = ""
  text.gsub!(/^!!/,'^') # to treat it just as ^
  if typem == :on_call
    rules_file = config.rules_file
  elsif dest[0] == "C" or dest[0] == "G" # on a channel or private channel
    rules_file = config.rules_file

    if @rules_imported.key?(user.id) and @rules_imported[user.id].key?(dchannel)
      unless @bots_created.key?(@rules_imported[user.id][dchannel])
        get_bots_created()
      end
      if @bots_created.key?(@rules_imported[user.id][dchannel])
        rules_file = @bots_created[@rules_imported[user.id][dchannel]][:rules_file]
      end
    end
  elsif dest[0] == "D" and @rules_imported.key?(user.id) and @rules_imported[user.id].key?(user.id) #direct message
    unless @bots_created.key?(@rules_imported[user.id][user.id])
      get_bots_created()
    end
    if @bots_created.key?(@rules_imported[user.id][user.id])
      rules_file = @bots_created[@rules_imported[user.id][user.id]][:rules_file]
    end
  end

  if nick == config[:nick] #if message is coming from the bot
    begin
      case text
      when /^Bot has been (closed|killed) by/i
        if config.channel == @channels_name[dchannel]
          @logger.info "#{nick}: #{text}"
          if config.simulate
            @status = :off
            config.simulate = false
            Thread.exit
          else
            exit!
          end
        end
      when /^Changed status on (.+) to :(.+)/i
        channel_name = $1
        status = $2
        if config.on_master_bot or config.channel == channel_name
          @bots_created[@channels_id[channel_name]][:status] = status.to_sym
          update_bots_file()
          if config.channel == channel_name
            @logger.info "#{nick}: #{text}"
          else #on master bot
            @logger.info "Changed status on #{channel_name} to :#{status}"
          end
        end
      when /extended the rules from (.+) to be used on (.+)\.$/i
        from_name = $1
        to_name = $2
        if config.on_master_bot and @bots_created[@channels_id[from_name]][:cloud]
          @bots_created[@channels_id[from_name]][:extended] << to_name
          @bots_created[@channels_id[from_name]][:extended].uniq!
          update_bots_file()
        end
      when /removed the access to the rules of (.+) from (.+)\.$/i
        from_name = $1
        to_name = $2
        if config.on_master_bot and @bots_created[@channels_id[from_name]][:cloud]
          @bots_created[@channels_id[from_name]][:extended].delete(to_name)
          update_bots_file()
        end
      end

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

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

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

  command = text

  begin
    t = Thread.new do
      begin
        Thread.current[:dest] = dest
        Thread.current[:user] = user
        Thread.current[:command] = command
        Thread.current[:rules_file] = rules_file
        Thread.current[:typem] = typem
        Thread.current[:files?] = !files.nil? && files.size>0
        Thread.current[:ts] = ts
        Thread.current[:thread_ts] = thread_ts
        Thread.current[:routine] = routine
        if thread_ts.to_s == ''
          Thread.current[:on_thread] = false
          Thread.current[:thread_ts] = Thread.current[:ts] # to create the thread if necessary
        else
          Thread.current[:on_thread] = true
        end
        if (dest[0] == "C") || (dest[0] == "G") and @rules_imported.key?(user.id) &&
          @rules_imported[user.id].key?(dchannel) && @bots_created.key?(@rules_imported[user.id][dchannel])
            Thread.current[:using_channel] = @rules_imported[user.id][dchannel]
        elsif dest[0] == "D" && @rules_imported.key?(user.id) && @rules_imported[user.id].key?(user.id) and
          @bots_created.key?(@rules_imported[user.id][user.id])
            Thread.current[:using_channel] = @rules_imported[user.id][user.id]
        else
            Thread.current[:using_channel] = ''
        end

        processed = process(user, command, dest, dchannel, rules_file, typem, files, Thread.current[:thread_ts])
        @logger.info "command: #{nick}> #{command}" if processed
        on_demand = false
        if command.match(/^@?(#{config[:nick]}):*\s+(.+)/im) or
          command.match(/^()!!(.+)/im) or
          command.match(/^()\^(.+)/im) or
          command.match(/^()!(.+)/im) or
          command.match(/^()<@#{config[:nick_id]}>\s+(.+)/im)
          command2 = $2
          Thread.current[:command] = command2
          if command2.match?(/^()!!(.+)/im) or
            command.match?(/^()\^(.+)/im)
            Thread.current[:on_thread] = true
          end
          command = command2
          on_demand = true
        end
        unless config.on_maintenance and processed
          if @status == :on and
            (!answer.empty? or
            (@repl_sessions.key?(nick) and dest==@repl_sessions[nick][:dest] and 
              ((@repl_sessions[nick][:on_thread] and thread_ts == @repl_sessions[nick][:thread_ts]) or
                (!@repl_sessions[nick][:on_thread] and !Thread.current[:on_thread] ))) or 
            (@listening.key?(nick) and typem != :on_extended and 
              ((@listening[nick].key?(dest) and !Thread.current[:on_thread]) or 
                (@listening[nick].key?(thread_ts) and Thread.current[:on_thread] ) )) or
              dest[0] == "D" or on_demand)
            @logger.info "command: #{nick}> #{command}" unless processed
            #todo: verify this

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

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

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

          end
        end
      rescue Exception => stack
        @logger.fatal stack
      end
    end
  rescue => e
    @logger.error "exception: #{e.inspect}"
  end
end
project_folder() click to toggle source
# File lib/slack/smart-bot/commands/general/bot_help.rb, line 78
def project_folder
  ""
end
react(emoji, ts=false) click to toggle source

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

# File lib/slack/smart-bot/comm/react.rb, line 5
def react(emoji, ts=false)
  if ts.is_a?(TrueClass) or ts.is_a?(FalseClass)
    parent = ts
    ts = nil
  else
    parent = false
  end
  if ts.nil?
    if parent or Thread.current[:ts].to_s == ''
      ts = Thread.current[:thread_ts]
    else
      ts = Thread.current[:ts]
    end
  end
  if ts.nil?
    @logger.warn 'react method no ts supplied'
  else
    begin
      client.web_client.reactions_add(channel: Thread.current[:dest], name: emoji, timestamp: ts) unless config.simulate
    rescue Exception => stack
      @logger.warn stack
    end
  end
end
remove_hash_keys(hash, key) click to toggle source
# 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) click to toggle source

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:

# 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) click to toggle source

help: ———————————————- help: `repl` help: `live` help: `irb` help: `repl SESSION_NAME` help: `private repl SESSION_NAME` help: `clean repl SESSION_NAME` help: `repl ENV_VAR=VALUE` help: `repl SESSION_NAME ENV_VAR=VALUE ENV_VAR='VALUE'` help: `repl SESSION_NAME: “DESCRIPTION”` help: `repl SESSION_NAME: “DESCRIPTION” ENV_VAR=VALUE ENV_VAR='VALUE'` help: Will run all we write as a ruby command and will keep the session values. help: SESSION_NAME only admits from a to Z, numbers, - and _ help: If no SESSION_NAME supplied it will be treated as a temporary REPL help: If 'private' specified the repl will be accessible only by you and it will be displayed only to you when `see repls` help: If 'clean' specified the repl won't pre execute the code written on the .smart-bot-repl file help: To avoid a message to be treated, start the message with '-'. help: Send quit, bye or exit to finish the session. help: Send puts, print, p or pp if you want to print out something when using `run repl` later. help: After 30 minutes of no communication with the Smart Bot the session will be dismissed. help: If you declare on your rules file a method called `project_folder` returning the path for the project folder, the code will be executed from that folder. help: By default it will be automatically loaded the gems: string_pattern, nice_hash and nice_http help: To pre-execute some ruby when starting the session add the code to .smart-bot-repl file on the project root folder defined on project_folder help: If you want to see the methods of a class or module you created use _ls TheModuleOrClass_ help: You can supply the Environmental Variables you need for the Session help: Examples: help: _repl CreateCustomer LOCATION=spain HOST='10.30.40.50:8887’_ help: _repl CreateCustomer: “It creates a random customer for testing” LOCATION=spain HOST='10.30.40.50:8887’_ help: _repl delete_logs_ help: _private repl random-ssn_ help:

# File lib/slack/smart-bot/commands/on_bot/repl.rb, line 33
def repl(dest, user, session_name, env_vars, rules_file, command, description, type)
  #todo: add more tests
  from = user.name
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
    respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
  else
    if !@repl_sessions.key?(from)
      save_stats(__method__)
      Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
      Dir.mkdir("#{config.path}/repl/#{@channel_id}") unless Dir.exist?("#{config.path}/repl/#{@channel_id}")
      
      serialt = Time.now.strftime("%Y%m%d%H%M%S%N")
      if session_name.to_s==''
        session_name = "#{from}_#{serialt}"
        temp_repl = true
      else
        temp_repl = false
        i = 0
        name = session_name
        while File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.input")
          i+=1
          session_name = "#{name}#{i}"
        end
      end
      @repl_sessions[from] = {
        name: session_name,
        dest: dest,
        started: Time.now,
        finished: Time.now,
        input: [],
        on_thread: Thread.current[:on_thread],
        thread_ts: Thread.current[:thread_ts]
      }

      unless temp_repl
        @repls[session_name] = {
            created: @repl_sessions[from][:started].to_s,
            accessed: @repl_sessions[from][:started].to_s,
            creator_name: user.name,
            creator_id: user.id,
            description: description,
            type: type,
            runs_by_creator: 0,
            runs_by_others: 0,
            gets: 0
        }
        update_repls()        
      end
      react :running
      if Thread.current[:ts].to_s == ''
        @ts_react = Thread.current[:thread_ts]
      else
        @ts_react = Thread.current[:ts]
      end        

      message = "Session name: *#{session_name}*
      From now on I will execute all you write as a Ruby command and I will keep the session open until you send `quit` or `bye` or `exit`. 
      I will respond with the result so it is not necessary you send `print`, `puts`, `p` or `pp` unless you want it as the output when calling `run repl`. 
      Use `p` to print a message raw, exacly like it is returned. 
      If you want to avoid a message to be treated by me, start the message with '-'. 
      After 30 minutes of no communication with the Smart Bot the session will be dismissed.
      If you want to see the methods of a class or module you created use _ls TheModuleOrClass_
      You can supply the Environmental Variables you need for the Session
      Example:
        _repl CreateCustomer LOCATION=spain HOST='https://10.30.40.50:8887'_
      "
      respond message, dest
      
      File.write("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", "", mode: "a+")
      File.write("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.output", "", mode: "a+")
      File.write("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.run", "", mode: "a+")
      
      if type != :private_clean and type != :public_clean
        pre_execute = '
          if File.exist?(\"./.smart-bot-repl\")
            begin
              eval(File.read(\"./.smart-bot-repl\"), bindme' + serialt + ')
            rescue Exception => resp_repl
            end
          end
        '
      else
        pre_execute = ''
      end

      process_to_run = '
          ruby -e "' + env_vars.join("\n") + '
          require \"amazing_print\"
          bindme' + serialt + ' = binding
          eval(\"require \'nice_http\'\" , bindme' + serialt + ')
          def ls(obj)
            (obj.methods - Object.methods)
          end
          
          file_input_repl = File.open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.input\", \"r\")
          ' + pre_execute + '
          while true do 
            sleep 0.2 
            code_to_run_repl = file_input_repl.read
            if code_to_run_repl.to_s!=\"\"
              add_to_run_repl = true
              if code_to_run_repl.to_s.match?(/^quit$/i) or 
                code_to_run_repl.to_s.match?(/^exit$/i) or 
                code_to_run_repl.to_s.match?(/^bye bot$/i) or
                code_to_run_repl.to_s.match?(/^bye$/i)
                exit
              else
                if code_to_run_repl.match?(/^\s*ls\s+(.+)/)
                  add_to_run_repl = false
                end
                error = false
                begin
                  resp_repl = eval(code_to_run_repl.gsub(/^\s*(puts|print|p|pp)\s/, \"\"), bindme' + serialt + ')
                rescue Exception => resp_repl
                  error = true
                end
                if resp_repl.to_s != \"\"
                  if code_to_run_repl.match?(/^\s*p\s+/i)
                    open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.output\", \"a+\") {|f|
                      f.puts \"\`\`\`\n#{resp_repl.inspect}\n\`\`\`\"
                    }
                  else
                    open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.output\", \"a+\") {|f|
                      f.puts \"\`\`\`\n#{resp_repl.ai}\n\`\`\`\"
                    }
                  end
                  unless error or !add_to_run_repl
                    open(\"' + File.expand_path(config.path) + '/repl/' + @channel_id + '/' + session_name + '.run\", \"a+\") {|f|
                      f.puts code_to_run_repl
                    }
                  end
                end
              end
            end
          end"
      '
      unless rules_file.empty? # to get the project_folder
        begin
          eval(File.new(config.path+rules_file).read) if File.exist?(config.path+rules_file)
        end
      end
      started = Time.now
      process_to_run = ("cd #{project_folder} &&" + process_to_run) if defined?(project_folder)

      stdin, stdout, stderr, wait_thr = Open3.popen3(process_to_run)
      timeout = 30 * 60 # 30 minutes
      
      file_output_repl = File.open("#{config.path}/repl/#{@channel_id}/#{session_name}.output", "r")
      @repl_sessions[from][:pid] = wait_thr.pid
      while (wait_thr.status == 'run' or wait_thr.status == 'sleep') and @repl_sessions.key?(from)
        begin
          if (Time.now-@repl_sessions[from][:finished]) > timeout
              open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", 'a+') {|f|
                f.puts 'quit'
              }
              respond "REPL session finished: #{@repl_sessions[from][:name]}", dest
              unreact :running, @ts_react
              pids = `pgrep -P #{@repl_sessions[from][:pid]}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
              pids.each do |pid|
                begin
                  Process.kill("KILL", pid)
                rescue
                end
              end
              @repl_sessions.delete(from)
              break
          end
          sleep 0.2
          resp_repl = file_output_repl.read
          if resp_repl.to_s!=''
            if resp_repl.to_s.lines.count < 60 and resp_repl.to_s.size < 3500
              respond resp_repl, dest
            else
              resp_repl.gsub!(/^\s*```/,'')
              resp_repl.gsub!(/```\s*$/,'')
              send_file(dest, "", 'response.rb', "", 'text/plain', "ruby", content: resp_repl)
            end
          end
        rescue Exception => excp
          @logger.fatal excp
        end
      end
    else
      @repl_sessions[from][:finished] = Time.now
      code = @repl_sessions[from][:command]
      @repl_sessions[from][:command] = ''
      code.gsub!("\\n", "\n")
      code.gsub!("\\r", "\r")
      # Disabled for the moment since it is deleting lines with '}'
      #code.gsub!(/^\W*$/, "") #to remove special chars from slack when copy/pasting.
      if code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File.") or
        code.include?("`") or code.include?("exec") or code.include?("spawn") or code.include?("IO.") or
        code.match?(/open3/i) or code.match?(/bundle/i) or code.match?(/gemfile/i) or code.include?("%x") or
        code.include?("ENV") or code.match?(/=\s*IO/) or code.include?("Dir.") or 
        code.match?(/=\s*File/) or code.match?(/=\s*Dir/) or code.match?(/<\s*File/) or code.match?(/<\s*Dir/) or
        code.match?(/\w+:\s*File/) or code.match?(/\w+:\s*Dir/)
        respond "Sorry I cannot run this due security reasons", dest
      else
        @repl_sessions[from][:input]<<code
        case code
        when /^\s*(quit|exit|bye|bye bot)\s*$/i
          open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", 'a+') {|f|
            f.puts code
          }
          respond "REPL session finished: #{@repl_sessions[from][:name]}", dest
          unreact :running, @ts_react
          pids = `pgrep -P #{@repl_sessions[from][:pid]}`.split("\n").map(&:to_i) #todo: it needs to be adapted for Windows
          pids.each do |pid|
            begin
              Process.kill("KILL", pid)
            rescue
            end
          end
          @repl_sessions.delete(from)
        when /^\s*-/i
          #ommit
        else
          open("#{config.path}/repl/#{@channel_id}/#{@repl_sessions[from][:name]}.input", 'a+') {|f|
            f.puts code
          }
        end
      end
    end
  end
end
respond(msg, dest = nil) click to toggle source
# File lib/slack/smart-bot/comm/respond.rb, line 2
def respond(msg, dest = nil)
  if dest.nil? and Thread.current.key?(:dest)
    dest = Thread.current[:dest]
  end
  dest = @channels_id[dest] if @channels_id.key?(dest) #it is a name of channel
  if !config.simulate #https://api.slack.com/docs/rate-limits
    msg.to_s.size > 500 ? wait = 0.5 : wait = 0.1
    sleep wait if Time.now <= (@last_respond+wait)
  else
    wait = 0
  end
  msgs = msg.chars.each_slice(4000).map(&:join) # max of 4000 characters per message
  if dest.nil?
    if config[:simulate]
      open("#{config.path}/buffer_complete.log", "a") { |f|
        f.puts "|#{@channel_id}|#{config[:nick_id]}|#{config[:nick]}|#{msg}~~~"
      }
    else  
      if Thread.current[:on_thread]
        msgs.each do |msg|
          client.message(channel: @channel_id, text: msg, as_user: true, thread_ts: Thread.current[:thread_ts])
          sleep wait
        end
      else
        msgs.each do |msg|
          client.message(channel: @channel_id, text: msg, as_user: true)
          sleep wait
        end
      end
    end
    if config[:testing] and config.on_master_bot
      open("#{config.path}/buffer.log", "a") { |f|
        f.puts "|#{@channel_id}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
      }
    end
  elsif dest[0] == "C" or dest[0] == "G" # channel
    if config[:simulate]
      open("#{config.path}/buffer_complete.log", "a") { |f|
      f.puts "|#{dest}|#{config[:nick_id]}|#{config[:nick]}|#{msg}~~~"
    }
    else  
      if Thread.current[:on_thread]
        msgs.each do |msg|
          client.message(channel: dest, text: msg, as_user: true, thread_ts: Thread.current[:thread_ts])
          sleep wait
        end
      else
        msgs.each do |msg|
          client.message(channel: dest, text: msg, as_user: true)
          sleep wait
        end
      end
    end
    if config[:testing] and config.on_master_bot
      open("#{config.path}/buffer.log", "a") { |f|
        f.puts "|#{dest}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
      }
    end
  elsif dest[0] == "D" or dest[0] == "U"  or dest[0] == "W" # Direct message
    msgs.each do |msg|
      send_msg_user(dest, msg)
      sleep wait
    end
  elsif dest[0] == "@"
    begin
      user_info = get_user_info(dest)
      msgs.each do |msg|
        send_msg_user(user_info.user.id, msg)
        sleep wait
      end
    rescue Exception => stack
      @logger.warn("user #{dest} not found.")
      @logger.warn stack
      if Thread.current.key?(:dest)
        respond("User #{dest} not found.")
      end
    end
  else
    @logger.warn("method respond not treated correctly: msg:#{msg} dest:#{dest}")
  end
  @last_respond = Time.now
end
respond_direct(msg) click to toggle source
# 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) click to toggle source

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={result: res}; puts my_json.to_json_ help:

# File lib/slack/smart-bot/commands/on_bot/ruby_code.rb, line 10
def ruby_code(dest, user, code, rules_file)
  save_stats(__method__)
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
    respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
  else
    unless code.match?(/System/i) or code.match?(/Kernel/i) or code.include?("File.") or
          code.include?("`") or code.include?("exec") or code.include?("spawn") or code.include?("IO.") or
          code.match?(/open3/i) or code.match?(/bundle/i) or code.match?(/gemfile/i) or code.include?("%x") or
          code.include?("ENV") or code.match?(/=\s*IO/) or code.include?("Dir.") or code.match?(/=\s*IO/) or
          code.match?(/=\s*File/) or code.match?(/=\s*Dir/) or code.match?(/<\s*File/) or code.match?(/<\s*Dir/) or
          code.match?(/\w+:\s*File/) or code.match?(/\w+:\s*Dir/)
      react :running
      unless rules_file.empty?
        begin
          eval(File.new(config.path+rules_file).read) if File.exist?(config.path+rules_file)
        end
      end

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

      begin
        code.gsub!(/^\W*$/, "") #to remove special chars from slack when copy/pasting
        code.gsub!('$','\$') #to take $ as literal, fex: puts '$lolo' => puts '\$lolo'
        ruby = "ruby -e \"#{code.gsub('"', '\"')}\""
        if defined?(project_folder) and project_folder.to_s != "" and Dir.exist?(project_folder)
          ruby = ("cd #{project_folder} &&" + ruby)
        else
          def project_folder() "" end
        end

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

help: ———————————————- help: `run repl SESSION_NAME` help: `run repl SESSION_NAME ENV_VAR=VALUE ENV_VAR=VALUE` help: `run live SESSION_NAME` help: `run irb SESSION_NAME` help: Will run the repl session specified and return the output. help: You can supply the Environmental Variables you need for the Session help: It will return only the values that were print out on the repl with puts, print, p or pp help: Example: help: _run repl CreateCustomer LOCATION=spain HOST='10.30.40.50:8887’_ help:

# File lib/slack/smart-bot/commands/on_bot/run_repl.rb, line 13
def run_repl(dest, user, session_name, env_vars, rules_file)
  #todo: add tests
  from = user.name
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
    respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
  else
    save_stats(__method__)
    Dir.mkdir("#{config.path}/repl") unless Dir.exist?("#{config.path}/repl")
    Dir.mkdir("#{config.path}/repl/#{@channel_id}") unless Dir.exist?("#{config.path}/repl/#{@channel_id}")
    if File.exist?("#{config.path}/repl/#{@channel_id}/#{session_name}.run")
      if @repls.key?(session_name) and (@repls[session_name][:type] == :private or @repls[session_name][:type] == :private_clean) and 
        @repls[session_name][:creator_name]!=user.name and 
        !config.admins.include?(user.name)
        respond "The REPL with session name: #{session_name} is private", dest
      else
        if @repls.key?(session_name) #not temp
          @repls[session_name][:accessed] = Time.now.to_s
          if @repls[session_name].creator_name == user.name
            @repls[session_name][:runs_by_creator] += 1
          else
            @repls[session_name][:runs_by_others] += 1
          end
          update_repls()        
        end

        content = env_vars.join("\n")
        content += "\nrequire 'nice_http'\n"
        unless rules_file.empty? # to get the project_folder
          begin
            eval(File.new(config.path+rules_file).read) if File.exist?(config.path+rules_file)
          end
        end
        if File.exist?("#{project_folder}/.smart-bot-repl") and @repls[session_name][:type] != :private_clean and @repls[session_name][:type] != :public_clean
          content += File.read("#{project_folder}/.smart-bot-repl")
          content += "\n"
        end
        content += File.read("#{config.path}/repl/#{@channel_id}/#{session_name}.run").gsub(/^(quit|exit|bye)$/i,'') #todo: remove this gsub, it will never contain it
        Dir.mkdir("#{project_folder}/tmp") unless Dir.exist?("#{project_folder}/tmp")
        Dir.mkdir("#{project_folder}/tmp/repl") unless Dir.exist?("#{project_folder}/tmp/repl")
        File.write("#{project_folder}/tmp/repl/#{session_name}.rb", content, mode: "w+")
        process_to_run = "ruby  ./tmp/repl/#{session_name}.rb"
        process_to_run = ("cd #{project_folder} && #{process_to_run}") if defined?(project_folder)
        respond "Running REPL #{session_name}"
        stdout, stderr, status = Open3.capture3(process_to_run)
        if stderr == ""
          if stdout == ""
            respond "*#{session_name}*: Nothing returned."
          else
            if stdout.to_s.lines.count < 60 and stdout.to_s.size < 3500
              respond "*#{session_name}*: #{stdout}"
            else
              send_file(dest, "", 'response.rb', "", 'text/plain', "ruby", content: stdout)
            end
          end
        else
          if (stdout.to_s+stderr.to_s).lines.count < 60
            respond "*#{session_name}*: #{stdout} #{stderr}"
          else
            send_file(dest, "", 'response.rb', "", 'text/plain', "ruby", content: (stdout.to_s+stderr.to_s))
          end

        end
      end
    else
      respond "The REPL with session name: #{session_name} doesn't exist on this Smart Bot Channel", dest
    end
  end
end
run_routine(dest, from, name) click to toggle source

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:

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

        stdout, stderr, status = Open3.capture3(process_to_run)
        if stderr == ""
          unless stdout.match?(/\A\s*\z/)
            respond "routine *`#{name}`*: #{stdout}", @routines[@channel_id][name][:dest]
          end
        else
          respond "routine *`#{name}`*: #{stdout} #{stderr}", @routines[@channel_id][name][:dest]
        end
      else #command
        respond "routine *`#{name}`*: #{@routines[@channel_id][name][:command]}", @routines[@channel_id][name][:dest]
        started = Time.now
        treat_message({ channel: @routines[@channel_id][name][:dest],
                       user: @routines[@channel_id][name][:creator_id],
                       text: @routines[@channel_id][name][:command],
                       files: nil })
      end
      @routines[@channel_id][name][:last_elapsed] = (Time.now - started)
      @routines[@channel_id][name][:last_run] = started.to_s
      update_routines()
    else
      respond "There isn't a routine with that name: `#{name}`.\nCall `see routines` to see added routines", dest
    end
  else
    respond "Only admin users can run routines", dest
  end
end
save_stats(method, data: {}) click to toggle source
# File lib/slack/smart-bot/utils/save_stats.rb, line 3
def save_stats(method, data: {})
    if config.stats
        begin
          require 'csv'
          if !File.exist?("#{config.stats_path}.#{Time.now.strftime("%Y-%m")}.log")
            CSV.open("#{config.stats_path}.#{Time.now.strftime("%Y-%m")}.log", 'wb') do |csv|
                csv << ['date','bot_channel', 'bot_channel_id', 'dest_channel', 'dest_channel_id', 'type_message', 'user_name', 'user_id', 'text', 'command', 'files']
            end
          end
          if data.empty?
            data = {
              dest: Thread.current[:dest],
              typem: Thread.current[:typem],
              user: Thread.current[:user],
              files: Thread.current[:files?],
              command: Thread.current[:command],
              routine: Thread.current[:routine]
            }
          end
          if method.to_s == 'ruby_code' and data.files
            command_txt = 'ruby'
          else
            command_txt = data.command
          end

          if data.routine
            user_name = "routine/#{data.user.name}"
            user_id = "routine/#{data.user.id}"
          else
            user_name = data.user.name
            user_id = data.user.id
          end
          CSV.open("#{config.stats_path}.#{Time.now.strftime("%Y-%m")}.log", "a+") do |csv|
            csv << [Time.now, config.channel, @channel_id, @channels_name[data.dest], data.dest, data.typem, user_name, user_id, command_txt, method, data.files]
          end
        rescue Exception => exception
          @logger.fatal "There was a problem on the stats"
          @logger.fatal exception
        end
    end
end
see_repls(dest, user, typem) click to toggle source

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

# File lib/slack/smart-bot/commands/on_bot/see_repls.rb, line 7
def see_repls(dest, user, typem)
  #todo: add tests
  save_stats(__method__)
  from = user.name
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
    respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
  else
    message = ""
    @repls.sort.to_h.each do |session_name, repl|
      if (repl.creator_name == user.name or repl.type == :public or repl.type == :public_clean) or (config.admins.include?(user.name) and typem == :on_dm)
        message += "(#{repl.type}) *#{session_name}*: #{repl.description} / created: #{repl.created} / accessed: #{repl.accessed} / creator: #{repl.creator_name} / runs: #{repl.runs_by_creator+repl.runs_by_others} / gets: #{repl.gets} \n"
      end
    end
    message = "No repls created" if message == ''
    respond message
  end
end
see_routines(dest, from, user, all) click to toggle source

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:

# File lib/slack/smart-bot/commands/on_bot/admin/see_routines.rb, line 9
def see_routines(dest, from, user, all)
  save_stats(__method__)
  if config.admins.include?(from) #admin user
    if all
      routines = {}
      if config.on_master_bot
        Dir["#{config.path}/routines/routines_*.rb"].each do |rout|
          file_conf = IO.readlines(rout).join
          unless file_conf.to_s() == ""
            routines.merge!(eval(file_conf))
          end
        end
      else
        respond "To see all routines on all channels you need to run the command on the master channel.\nI'll display only the routines on this channel.", dest
        routines = @routines.dup
      end
    else
      if @rules_imported.key?(user.id) and @rules_imported[user.id].key?(user.id) and dest[0] == "D"
        file_conf = IO.readlines("#{config.path}/routines/routines_#{@rules_imported[user.id][user.id]}.rb").join
        routines = eval(file_conf)
      else
        routines = @routines.dup
      end
    end

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

help: ———————————————- help: `see shortcuts` help: `see sc` help: It will display the shortcuts stored for the user and for :all help:

# File lib/slack/smart-bot/commands/on_bot/see_shortcuts.rb, line 7
def see_shortcuts(dest, user, typem)
  save_stats(__method__)
  from = user.name
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
    respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
  else
    unless typem == :on_extended
      msg = ""
      if @shortcuts[:all].keys.size > 0 or @shortcuts_global[:all].keys.size > 0
        msg = "*Available shortcuts for all:*\n"
        
        if @shortcuts[:all].keys.size > 0
          @shortcuts[:all].each { |name, value|
            msg += "    _#{name}: #{value}_\n"
          }
        end
        if @shortcuts_global[:all].keys.size > 0
          @shortcuts_global[:all].each { |name, value|
            msg += "    _#{name} (global): #{value}_\n"
          }
        end
        respond msg, dest
      end
      msg2 = ''
      if @shortcuts.keys.include?(from) and @shortcuts[from].keys.size > 0
        new_hash = @shortcuts[from].dup
        @shortcuts[:all].keys.each { |k| new_hash.delete(k) }
        if new_hash.keys.size > 0
          msg2 = "*Available shortcuts for #{from}:*\n"
          new_hash.each { |name, value|
            msg2 += "    _#{name}: #{value}_\n"
          }
        end
      end
      if @shortcuts_global.keys.include?(from) and @shortcuts_global[from].keys.size > 0
        new_hash = @shortcuts_global[from].dup
        @shortcuts_global[:all].keys.each { |k| new_hash.delete(k) }
        if new_hash.keys.size > 0
          msg2 = "*Available shortcuts for #{from}:*\n" if msg2 == ''
          new_hash.each { |name, value|
            msg2 += "    _#{name} (global): #{value}_\n"
          }
        end
      end
      respond msg2 unless msg2 == ''
      respond "No shortcuts found" if (msg + msg2) == ""
    end
  end
end
send_file(to, msg, file, title, format, type = "text", content: '') click to toggle source

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

# File lib/slack/smart-bot/comm/send_file.rb, line 8
def send_file(to, msg, file, title, format, type = "text", content: '')
  unless config[:simulate]
    file = 'myfile' if file.to_s == '' and content!=''
    if to[0] == "U" or to[0] == "W" #user
      im = client.web_client.im_open(user: to)
      channel = im["channel"]["id"]
    else
      channel = to
    end

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

    if content.to_s == ''
      client.web_client.files_upload(
        channels: channel,
        as_user: true,
        file: Faraday::UploadIO.new(file, format),
        title: title,
        filename: file,
        filetype: type,
        initial_comment: msg,
        thread_ts: ts
      )
    else
      client.web_client.files_upload(
        channels: channel,
        as_user: true,
        content: content,
        title: title,
        filename: file,
        filetype: type,
        initial_comment: msg,
        thread_ts: ts
      )
    end
  end
end
send_msg_channel(to, msg) click to toggle source

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

# File lib/slack/smart-bot/comm/send_msg_channel.rb, line 5
def send_msg_channel(to, msg)
  unless msg == ""
    get_channels_name_and_id() unless @channels_name.key?(to) or @channels_id.key?(to)
    if @channels_name.key?(to) #it is an id
      channel_id = to
    elsif @channels_id.key?(to) #it is a channel name
      channel_id = @channels_id[to]
    else
      @logger.fatal "Channel: #{to} not found. Message: #{msg}"
    end
    if config[:simulate]
      open("#{config.path}/buffer_complete.log", "a") { |f|
        f.puts "|#{channel_id}|#{config[:nick_id]}|#{config[:nick]}|#{msg}~~~"
      }
    else  
      if Thread.current[:on_thread]
        client.message(channel: channel_id, text: msg, as_user: true, thread_ts: Thread.current[:thread_ts])
      else
        client.message(channel: channel_id, text: msg, as_user: true)
      end
    end
    if config[:testing] and config.on_master_bot
      open("#{config.path}/buffer.log", "a") { |f|
        f.puts "|#{channel_id}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
      }
    end
  end
end
send_msg_user(id_user, msg) click to toggle source

to send messages without listening for a response to users

# File lib/slack/smart-bot/comm/send_msg_user.rb, line 4
def send_msg_user(id_user, msg)
  unless msg == ""
    if id_user[0] == "D"
      if config[:simulate]
        open("#{config.path}/buffer_complete.log", "a") { |f|
          f.puts "|#{id_user}|#{config[:nick_id]}|#{config[:nick]}|#{msg}~~~"
        }
      else  
        if Thread.current[:on_thread]
          client.message(channel: id_user, as_user: true, text: msg, thread_ts: Thread.current[:thread_ts])
        else
          client.message(channel: id_user, as_user: true, text: msg)
        end
      end
      if config[:testing] and config.on_master_bot
        open("#{config.path}/buffer.log", "a") { |f|
          f.puts "|#{id_user}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
        }
      end
    else
      im = client.web_client.im_open(user: id_user)
      if config[:simulate]
        open("#{config.path}/buffer_complete.log", "a") { |f|
          f.puts "|#{im["channel"]["id"]}|#{config[:nick_id]}|#{config[:nick]}|#{msg}~~~"
        }
      else  
        if Thread.current[:on_thread]
          client.message(channel: im["channel"]["id"], as_user: true, text: msg, thread_ts: Thread.current[:thread_ts])
        else
          client.message(channel: im["channel"]["id"], as_user: true, text: msg)
        end
      end
      if config[:testing] and config.on_master_bot
        open("#{config.path}/buffer.log", "a") { |f|
          f.puts "|#{im["channel"]["id"]}|#{config[:nick_id]}|#{config[:nick]}|#{msg}"
        }
      end
    end
  end
end
set_maintenance(from, status, message) click to toggle source

helpmaster: ———————————————- helpmaster: `set maintenance on` helpmaster: `set maintenance on MESSAGE` helpmaster: `set maintenance off` helpmaster: `turn maintenance on` helpmaster: `turn maintenance on MESSAGE` helpmaster: `turn maintenance off` helpmaster: The SmartBot will be on maintenance and responding with a generic message helpmaster: Only works if you are on Master channel and you are a master admin user helpmaster:

# File lib/slack/smart-bot/commands/on_master/admin_master/set_maintenance.rb, line 12
def set_maintenance(from, status, message)
  save_stats(__method__)
  if config.on_master_bot
    if config.admins.include?(from) #admin user
      if message == ''
        config.on_maintenance_message = "Sorry I'm on maintenance so I cannot attend your request."
      else
        config.on_maintenance_message = message
      end

      if status == 'on'
        config.on_maintenance = true
        respond "From now on I'll be on maintenance status so I won't be responding accordingly."
      else
        config.on_maintenance = false
        respond "From now on I won't be on maintenance. Everything is back to normal!"
      end
      
      file = File.open("#{config.path}/config_tmp.status", "w")
      file.write config.inspect
      file.close
  
    else
      respond 'Only master admins on master channel can use this command.'
    end
  else
    respond 'Only master admins on master channel can use this command.'
  end
end
start_bot(dest, from) click to toggle source

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:

# File lib/slack/smart-bot/commands/on_bot/admin/start_bot.rb, line 9
def start_bot(dest, from)
  save_stats(__method__)
  if config.admins.include?(from) #admin user
    respond "This bot is running and listening from now on. You can pause again: pause this bot", dest
    @status = :on
    @bots_created[@channel_id][:status] = :on
    update_bots_file()
    unless config.on_master_bot
      send_msg_channel config.master_channel, "Changed status on #{config.channel} to :on"
    end
  else
    respond "Only admin users can change my status", dest
  end
end
start_routine(dest, from, name) click to toggle source

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:

# 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) click to toggle source

help: ———————————————- help: `stop using rules from CHANNEL` help: it will stop using the rules from the specified channel. help:

# 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) click to toggle source

helpadmin: ———————————————- helpadmin: `stop using rules on CHANNEL_NAME` helpadmin: it will stop using the extended rules on the specified channel. helpadmin:

# 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) click to toggle source
# File lib/slack/smart-bot/treat_message.rb, line 2
def treat_message(data, remove_blocks = true)

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

  if config[:testing] and config.on_master_bot
    open("#{config.path}/buffer.log", "a") { |f|
      f.puts "|#{data.channel}|#{data.user}|#{data.user_name}|#{data.text}"
    }
  end
  if data.key?(:dest) and data.dest.to_s!='' # for run routines and publish on different channels
    dest = data.dest
  elsif data.channel[0] == "D" or data.channel[0] == "C" or data.channel[0] == "G" #Direct message or Channel or Private Channel
    dest = data.channel
  else # not treated
    dest = nil
  end
  #todo: sometimes data.user is nil, check the problem.
  @logger.warn "!dest is nil. user: #{data.user}, channel: #{data.channel}, message: #{data.text}" if dest.nil?
  if !data.files.nil? and data.files.size == 1 and data.text.to_s == "" and data.files[0].filetype == "ruby"
    data.text = "ruby"
  end
  if !dest.nil? and config.on_master_bot and !data.text.nil? and data.text.match(/^ping from (.+)\s*$/) and data.user == config[:nick_id]
    @pings << $1
  end
  typem = :dont_treat
  if !dest.nil? and !data.text.nil? and !data.text.to_s.match?(/\A\s*\z/)
    #todo: we need to add mixed channels: @smart-bot on private1 #bot1cm <#CXDDFRDDF|bot2cu>: echo A
    if data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?((<#\w+\|[^>]+>\s*)+)\s*:?\s*(.*)/im) or 
      data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?((#[a-zA-Z0-9]+\s*)+)\s*:?\s*(.*)/im) or
      data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?(([a-zA-Z0-9]+\s*)+)\s*:\s*(.*)/im)
      channels_rules = $2 #multiple channels @smart-bot on #channel1 #channel2 echo AAA
      data_text = $4
      channel_rules_name = ''
      channel_rules = ''
      channels_arr = channels_rules.scan(/<#(\w+)\|([^>]+)>/)
      if channels_arr.size == 0
        channels_arr = []
        channels_rules.scan(/([^\s]+)/).each do |cn|
          cna = cn.join.gsub('#','')
          channels_arr << [@channels_id[cna], cna]
        end
      end
      # to be treated only on the bots of the requested channels
      channels_arr.each do |tcid, tcname|
        if @channel_id == tcid
          data.text = data_text
          typem = :on_call
          channel_rules = tcid
          channel_rules_name = tcname
          break
        end
      end
    elsif data.channel == @master_bot_id
      if config.on_master_bot #only to be treated on master bot channel
        typem = :on_master
      end
    elsif @bots_created.key?(data.channel)
      if @channel_id == data.channel #only to be treated by the bot on the channel
        typem = :on_bot
      end
    elsif data.channel[0] == "D" #Direct message
      get_rules_imported()
      if @rules_imported.key?(data.user) && @rules_imported[data.user].key?(data.user) and
        @bots_created.key?(@rules_imported[data.user][data.user])
        if @channel_id == @rules_imported[data.user][data.user]
          #only to be treated by the channel we are 'using'
          typem = :on_dm
        end
      elsif config.on_master_bot
        #only to be treated by master bot
        typem = :on_dm
      end
    elsif data.channel[0] == "C" or data.channel[0] == "G"
      #only to be treated on the channel of the bot. excluding running ruby
      if !config.on_master_bot and @bots_created.key?(@channel_id) and @bots_created[@channel_id][:extended].include?(@channels_name[data.channel]) and
         !data.text.match?(/^!?\s*(ruby|code)\s+/) and !data.text.match?(/^!?!?\s*(ruby|code)\s+/) and !data.text.match?(/^\^?\s*(ruby|code)\s+/)
        typem = :on_extended
      elsif config.on_master_bot and (data.text.match?(/^!?\s*(ruby|code)\s+/) or data.text.match?(/^!?!?\s*(ruby|code)\s+/) or data.text.match?(/^\^?\s*(ruby|code)\s+/)  )
        #or in case of running ruby, the master bot
        @bots_created.each do |k, v|
          if v.key?(:extended) and v[:extended].include?(@channels_name[data.channel])
            typem = :on_extended
            break
          end
        end
      end
      if data.channel[0] == "G" and config.on_master_bot and typem != :on_extended #private group
        typem = :on_pg
      end
    end
  end
  unless typem == :dont_treat
    if (Time.now - @last_activity_check) > 60 * 30 #every 30 minutes
      @last_activity_check = Time.now
      @listening.each do |k,v|
        v.each do |kk, vv|
          @listening[k].delete(kk) if (Time.now - vv) > 60 * 30
        end
        @listening.delete(k) if @listening[k].empty?
      end
    end
    begin
      #todo: when changed @questions user_id then move user_info inside the ifs to avoid calling it when not necessary
      user_info = get_user_info(data.user)

      #user_info.user.id = data.user #todo: remove this line when slack issue with Wxxxx Uxxxx fixed
      data.user = user_info.user.id  #todo: remove this line when slack issue with Wxxxx Uxxxx fixed
      if data.thread_ts.nil?
        qdest = dest
      else
        qdest = data.thread_ts
      end
      if !answer(user_info.user.name, qdest).empty?
        if data.text.match?(/^\s*(Bye|Bæ|Good\sBye|Adiós|Ciao|Bless|Bless\sBless|Adeu)\s(#{@salutations.join("|")})\s*$/i)
          answer_delete(user_info.user.name, qdest)
          command = data.text
        else
          command = answer(user_info.user.name, qdest)
          @answer[user_info.user.name][qdest] = data.text
          @questions[user_info.user.name] = data.text # to be backwards compatible #todo remove it when 2.0
        end
      elsif @repl_sessions.key?(user_info.user.name) and data.channel==@repl_sessions[user_info.user.name][:dest] and 
        ((@repl_sessions[user_info.user.name][:on_thread] and data.thread_ts == @repl_sessions[user_info.user.name][:thread_ts]) or
        (!@repl_sessions[user_info.user.name][:on_thread] and data.thread_ts.to_s == '' ))
        
        if data.text.match(/^\s*```(.*)```\s*$/im)
            @repl_sessions[user_info.user.name][:command] = $1
        else   
          @repl_sessions[user_info.user.name][:command] = data.text
        end
        command = 'repl'
      else
        command = data.text
      end

      #when added special characters on the message
      if command.match(/\A\s*```(.*)```\s*\z/im)
        command = $1
      elsif command.size >= 2 and
        ((command[0] == "`" and command[-1] == "`") or (command[0] == "*" and command[-1] == "*") or (command[0] == "_" and command[-1] == "_"))
        command = command[1..-2]
      end

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

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

        #todo: add pagination for case more than 1000 channels on the workspace
        channels = get_channels()
        channel_found = channels.detect { |c| c.name == channel_rules_name }
        members = get_channel_members(@channels_id[channel_rules_name]) unless channel_found.nil?
        if channel_found.nil?
          @logger.fatal "Not possible to find the channel #{channel_rules_name}"
        elsif channel_found.name == config.master_channel
          respond "You cannot use the rules from Master Channel on any other channel.", data.channel
        elsif @status != :on
          respond "The bot in that channel is not :on", data.channel
        elsif data.user == channel_found.creator or members.include?(data.user)
          process_first(user_info.user, command, dest, channel_rules, typem, data.files, data.ts, data.thread_ts, data.routine)
        else
          respond "You need to join the channel <##{channel_found.id}> to be able to use the rules.", data.channel
        end
      elsif config.on_master_bot and typem == :on_extended and
            command.size > 0 and command[0] != "-"
        # to run ruby only from the master bot for the case more than one extended
        process_first(user_info.user, command, dest, @channel_id, typem, data.files, data.ts, data.thread_ts, data.routine)
      elsif !config.on_master_bot and @bots_created[@channel_id].key?(:extended) and
            @bots_created[@channel_id][:extended].include?(@channels_name[data.channel]) and
            command.size > 0 and command[0] != "-"
        process_first(user_info.user, command, dest, @channel_id, typem, data.files, data.ts, data.thread_ts, data.routine)
      elsif (dest[0] == "D" or @channel_id == data.channel or data.user == config[:nick_id]) and
            command.size > 0 and command[0] != "-"
        process_first(user_info.user, command, dest, data.channel, typem, data.files, data.ts, data.thread_ts, data.routine)
        # if @botname on #channel_rules: do something
      end
    rescue Exception => stack
      @logger.fatal stack
    end
  else
    if !config.on_master_bot and !dest.nil? and (data.channel == @master_bot_id or dest[0] == "D") and
       data.text.match?(/^\s*bot\s+status\s*$/i) and @admin_users_id.include?(data.user)
      respond "ping from #{config.channel}", dest
    elsif !config.on_master_bot and !dest.nil? and data.user == config[:nick_id] and dest == @master_bot_id
      # to treat on other bots the status messages populated on master bot
      case data.text
      when /From now on I'll be on maintenance status/i
        sleep 2
        if File.exist?("#{config.path}/config_tmp.status")
          file_cts = IO.readlines("#{config.path}/config_tmp.status").join
          unless file_cts.to_s() == ""
            file_cts = eval(file_cts)
            if file_cts.is_a?(Hash) and file_cts.key?(:on_maintenance)
              config.on_maintenance = file_cts.on_maintenance
              config.on_maintenance_message = file_cts.on_maintenance_message
            end
          end
        end
      when /From now on I won't be on maintenance/i
        sleep 2
        if File.exist?("#{config.path}/config_tmp.status")
          file_cts = IO.readlines("#{config.path}/config_tmp.status").join
          unless file_cts.to_s() == ""
            file_cts = eval(file_cts)
            if file_cts.is_a?(Hash) and file_cts.key?(:on_maintenance)
              config.on_maintenance = file_cts.on_maintenance
              config.on_maintenance_message = file_cts.on_maintenance_message
            end
          end
        end
        
      when /^Bot has been (closed|killed) by/i
        sleep 2
        get_bots_created()
      when /^Changed status on (.+) to :(.+)/i
        sleep 2
        get_bots_created()
      when /extended the rules from (.+) to be used on (.+)\.$/i
        sleep 2
        get_bots_created()
      when /removed the access to the rules of (.+) from (.+)\.$/i
        sleep 2
        get_bots_created()
      when /global shortcut added/
        sleep 2
        if File.exist?("#{config.path}/shortcuts/shortcuts_global.rb")
          file_sc = IO.readlines("#{config.path}/shortcuts/shortcuts_global.rb").join
          unless file_sc.to_s() == ""
            @shortcuts_global = eval(file_sc)
          end
        end
      when /global shortcut deleted/
        sleep 2
        if File.exist?("#{config.path}/shortcuts/shortcuts_global.rb")
          file_sc = IO.readlines("#{config.path}/shortcuts/shortcuts_global.rb").join
          unless file_sc.to_s() == ""
            @shortcuts_global = eval(file_sc)
          end
        end
      end
    end
  end
end
unreact(emoji, ts=false) click to toggle source

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

# File lib/slack/smart-bot/comm/unreact.rb, line 5
def unreact(emoji, ts=false)
  if ts.is_a?(TrueClass) or ts.is_a?(FalseClass)
    parent = ts
    ts = nil
  else
    parent = false
  end
  if ts.nil?
    if parent or Thread.current[:ts].to_s == ''
      ts = Thread.current[:thread_ts]
    else
      ts = Thread.current[:ts]
    end
  end
  if ts.nil?
    @logger.warn 'unreact method no ts supplied'
  else
    begin
      client.web_client.reactions_remove(channel: Thread.current[:dest], name: emoji, timestamp: ts) unless config.simulate
    rescue Exception => stack
      @logger.warn stack
    end
  end
end
update_bots_file() click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# 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_imported() click to toggle source
# 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_file() click to toggle source
# File lib/slack/smart-bot/utils/update_shortcuts_file.rb, line 2
def update_shortcuts_file
  file = File.open("#{config.path}/shortcuts/#{config.shortcuts_file}", "w")
  file.write @shortcuts.inspect
  file.close

  if config.on_master_bot
    file = File.open("#{config.path}/shortcuts/shortcuts_global.rb", "w")
    file.write @shortcuts_global.inspect
    file.close
  end
end
use_rules(dest, channel, user, dchannel) click to toggle source

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:

# File lib/slack/smart-bot/commands/general/use_rules.rb, line 10
def use_rules(dest, channel, user, dchannel)
  save_stats(__method__)
  get_bots_created()
  if config[:allow_access].key?(__method__) and !config[:allow_access][__method__].include?(user.name) and !config[:allow_access][__method__].include?(user.id) and 
    (!user.key?(:enterprise_user) or ( user.key?(:enterprise_user) and !config[:allow_access][__method__].include?(user[:enterprise_user].id)))
    respond "You don't have access to use this command, please contact an Admin to be able to use it: <@#{config.admins.join(">, <@")}>"
  else
    #todo: add pagination for case more than 1000 channels on the workspace
    channels = get_channels()
    channel.gsub!('#','') # for the case the channel name is in plain text including #
    channel_found = channels.detect { |c| c.name == channel }
    get_channels_name_and_id() unless @channels_id.key?(channel)
    members = get_channel_members(@channels_id[channel]) unless channel_found.nil? or !@channels_id.key?(channel)

    if channel_found.nil? or !@channels_id.key?(channel)
      respond "The channel you are trying to use doesn't exist or cannot be found.", dest
    elsif channel_found.name == config.master_channel
      respond "You cannot use the rules from Master Channel on any other channel.", dest
    elsif !@bots_created.key?(@channels_id[channel])
      respond "There is no bot running on that channel.", dest
    elsif @bots_created.key?(@channels_id[channel]) and @bots_created[@channels_id[channel]][:status] != :on
      respond "The bot in that channel is not :on", dest
    else
      if user.id == channel_found.creator or members.include?(user.id)
        @rules_imported[user.id] = {} unless @rules_imported.key?(user.id)
        if dest[0] == "C" or dest[0] == "G" #todo: take in consideration bots that are not master
          @rules_imported[user.id][dchannel] = channel_found.id
        else
          @rules_imported[user.id][user.id] = channel_found.id
        end
        update_rules_imported()
        respond "I'm using now the rules from <##{channel_found.id}>", dest

        def git_project() "" end
        def project_folder() "" end
      else
        respond "You need to join the channel <##{channel_found.id}> to be able to use the rules.", dest
      end
    end
  end
end
whats_new(user, dest, dchannel, from, display_name) click to toggle source

help: ———————————————- help: `What's new` help: It will display the last user changes on Slack Smart Bot help:

# File lib/slack/smart-bot/commands/general/whats_new.rb, line 7
def whats_new(user, dest, dchannel, from, display_name)
  if @status == :on
    save_stats(__method__)
    whats_new_file = (__FILE__).gsub(/lib\/slack\/smart-bot\/commands\/general\/whats_new\.rb$/, "whats_new.txt")
    whats_new = File.read(whats_new_file)
    whats_new.split(/^\-\-\-\-\-\-+$/).each do |msg|
        respond msg
        sleep 0.3
    end
  end
end