class SlackSmartBot
Constants
- VERSION
Attributes
Public Class Methods
# 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
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
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
# 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
# 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
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
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
# 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
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
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
# 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
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
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
# 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
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
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
# 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
# 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
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
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
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
# 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
# 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
# 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
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
# 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
# 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
# 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
# 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
# File lib/slack/smart-bot/commands/general/bot_help.rb, line 74 def git_project "" end
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
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
# 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
# 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
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
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
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
# 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
# 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
# File lib/slack/smart-bot/commands/general/bot_help.rb, line 78 def project_folder "" end
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
# 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
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
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
# 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
# File lib/slack/smart-bot/comm/respond_direct.rb, line 2 def respond_direct(msg) dest = Thread.current[:user].id respond(msg, dest) end
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
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
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
# 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
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
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
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
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
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
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
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
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
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
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
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
# 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 (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
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
# 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
# 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
# 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
# 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
# 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
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
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