slack-smart-bot 1.9.2 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +109 -11
- data/lib/slack/smart-bot/comm/ask.rb +55 -49
- data/lib/slack/smart-bot/comm/dont_understand.rb +1 -1
- data/lib/slack/smart-bot/comm/event_hello.rb +7 -3
- data/lib/slack/smart-bot/comm/get_channel_members.rb +9 -4
- data/lib/slack/smart-bot/comm/get_channels.rb +31 -16
- data/lib/slack/smart-bot/comm/get_user_info.rb +12 -8
- data/lib/slack/smart-bot/comm/get_users.rb +24 -0
- data/lib/slack/smart-bot/comm/react.rb +19 -2
- data/lib/slack/smart-bot/comm/respond.rb +217 -72
- data/lib/slack/smart-bot/comm/respond_direct.rb +2 -3
- data/lib/slack/smart-bot/comm/respond_thread.rb +5 -0
- data/lib/slack/smart-bot/comm/send_file.rb +38 -34
- data/lib/slack/smart-bot/comm/send_msg_channel.rb +27 -22
- data/lib/slack/smart-bot/comm/send_msg_user.rb +58 -33
- data/lib/slack/smart-bot/comm/unreact.rb +22 -18
- data/lib/slack/smart-bot/comm.rb +2 -0
- data/lib/slack/smart-bot/commands/general/add_announcement.rb +32 -0
- data/lib/slack/smart-bot/commands/general/bot_help.rb +59 -32
- data/lib/slack/smart-bot/commands/general/bot_stats.rb +10 -9
- data/lib/slack/smart-bot/commands/general/bot_status.rb +2 -4
- data/lib/slack/smart-bot/commands/general/bye_bot.rb +0 -7
- data/lib/slack/smart-bot/commands/general/delete_announcement.rb +34 -0
- data/lib/slack/smart-bot/commands/general/delete_share.rb +34 -0
- data/lib/slack/smart-bot/commands/general/hi_bot.rb +16 -11
- data/lib/slack/smart-bot/commands/general/leaderboard.rb +200 -0
- data/lib/slack/smart-bot/commands/general/see_announcements.rb +113 -0
- data/lib/slack/smart-bot/commands/general/see_favorite_commands.rb +54 -0
- data/lib/slack/smart-bot/commands/general/see_shares.rb +41 -0
- data/lib/slack/smart-bot/commands/general/see_statuses.rb +78 -0
- data/lib/slack/smart-bot/commands/general/share_messages.rb +58 -0
- data/lib/slack/smart-bot/commands/general/stop_using_rules.rb +11 -6
- data/lib/slack/smart-bot/commands/general/suggest_command.rb +30 -0
- data/lib/slack/smart-bot/commands/general/use_rules.rb +7 -7
- data/lib/slack/smart-bot/commands/general_bot_commands.rb +243 -0
- data/lib/slack/smart-bot/commands/on_bot/add_shortcut.rb +2 -5
- data/lib/slack/smart-bot/commands/on_bot/admin/add_routine.rb +32 -11
- data/lib/slack/smart-bot/commands/on_bot/admin/extend_rules.rb +2 -0
- data/lib/slack/smart-bot/commands/on_bot/admin/pause_bot.rb +4 -2
- data/lib/slack/smart-bot/commands/on_bot/admin/pause_routine.rb +1 -0
- data/lib/slack/smart-bot/commands/on_bot/admin/remove_routine.rb +2 -3
- data/lib/slack/smart-bot/commands/on_bot/admin/run_routine.rb +6 -1
- data/lib/slack/smart-bot/commands/on_bot/admin/see_result_routine.rb +32 -0
- data/lib/slack/smart-bot/commands/on_bot/admin/see_routines.rb +4 -2
- data/lib/slack/smart-bot/commands/on_bot/admin/start_bot.rb +4 -2
- data/lib/slack/smart-bot/commands/on_bot/admin/start_routine.rb +1 -0
- data/lib/slack/smart-bot/commands/on_bot/admin/stop_using_rules_on.rb +2 -0
- data/lib/slack/smart-bot/commands/on_bot/admin_master/react_to.rb +32 -0
- data/lib/slack/smart-bot/commands/on_bot/admin_master/send_message.rb +24 -0
- data/lib/slack/smart-bot/commands/on_bot/delete_repl.rb +2 -4
- data/lib/slack/smart-bot/commands/on_bot/delete_shortcut.rb +2 -4
- data/lib/slack/smart-bot/commands/on_bot/get_repl.rb +2 -4
- data/lib/slack/smart-bot/commands/on_bot/repl.rb +5 -7
- data/lib/slack/smart-bot/commands/on_bot/ruby_code.rb +2 -4
- data/lib/slack/smart-bot/commands/on_bot/run_repl.rb +4 -5
- data/lib/slack/smart-bot/commands/on_bot/see_repls.rb +3 -5
- data/lib/slack/smart-bot/commands/on_bot/see_shortcuts.rb +2 -4
- data/lib/slack/smart-bot/commands/on_extended/bot_rules.rb +45 -12
- data/lib/slack/smart-bot/commands/on_master/admin/kill_bot_on_channel.rb +4 -1
- data/lib/slack/smart-bot/commands/on_master/admin_master/exit_bot.rb +2 -0
- data/lib/slack/smart-bot/commands/on_master/admin_master/notify_message.rb +1 -0
- data/lib/slack/smart-bot/commands/on_master/admin_master/publish_announcements.rb +32 -0
- data/lib/slack/smart-bot/commands/on_master/admin_master/set_general_message.rb +38 -0
- data/lib/slack/smart-bot/commands/on_master/admin_master/set_maintenance.rb +8 -0
- data/lib/slack/smart-bot/commands/on_master/create_bot.rb +27 -14
- data/lib/slack/smart-bot/commands.rb +16 -0
- data/lib/slack/smart-bot/listen.rb +1 -3
- data/lib/slack/smart-bot/process.rb +212 -74
- data/lib/slack/smart-bot/process_first.rb +118 -37
- data/lib/slack/smart-bot/treat_message.rb +313 -248
- data/lib/slack/smart-bot/utils/build_help.rb +3 -3
- data/lib/slack/smart-bot/utils/create_routine_thread.rb +81 -46
- data/lib/slack/smart-bot/utils/get_bots_created.rb +4 -1
- data/lib/slack/smart-bot/utils/get_help.rb +58 -68
- data/lib/slack/smart-bot/utils/get_shares.rb +12 -0
- data/lib/slack/smart-bot/utils/has_access.rb +12 -0
- data/lib/slack/smart-bot/utils/save_stats.rb +2 -0
- data/lib/slack/smart-bot/utils/save_status.rb +52 -0
- data/lib/slack/smart-bot/utils.rb +3 -0
- data/lib/slack-smart-bot.rb +45 -4
- data/lib/slack-smart-bot_general_commands.rb +46 -0
- data/lib/slack-smart-bot_general_rules.rb +5 -2
- data/lib/slack-smart-bot_rules.rb +43 -17
- data/whats_new.txt +32 -20
- metadata +24 -2
| @@ -1,296 +1,361 @@ | |
| 1 1 | 
             
            class SlackSmartBot
         | 
| 2 2 | 
             
              def treat_message(data, remove_blocks = true)
         | 
| 3 | 
            -
             | 
| 3 | 
            +
                @buffered = false if config[:testing]
         | 
| 4 4 | 
             
                begin
         | 
| 5 | 
            -
                   | 
| 6 | 
            -
                     | 
| 7 | 
            -
             | 
| 8 | 
            -
                       | 
| 9 | 
            -
             | 
| 10 | 
            -
                         | 
| 11 | 
            -
                          if b. | 
| 12 | 
            -
                            b.elements. | 
| 13 | 
            -
                               | 
| 14 | 
            -
                                if e. | 
| 15 | 
            -
                                   | 
| 16 | 
            -
             | 
| 17 | 
            -
                                     | 
| 18 | 
            -
                                       | 
| 19 | 
            -
             | 
| 20 | 
            -
                                       | 
| 21 | 
            -
             | 
| 22 | 
            -
                                       | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
                                       | 
| 5 | 
            +
                  begin
         | 
| 6 | 
            +
                    unless data.text.to_s.match(/\A\s*\z/)
         | 
| 7 | 
            +
                      #to remove italic, bold... from data.text since there is no method on slack api
         | 
| 8 | 
            +
                      if remove_blocks and !data.blocks.nil? and data.blocks.size > 0
         | 
| 9 | 
            +
                        data_text = ''
         | 
| 10 | 
            +
                        data.blocks.each do |b|
         | 
| 11 | 
            +
                          if b.type == 'rich_text'
         | 
| 12 | 
            +
                            if b.elements.size > 0
         | 
| 13 | 
            +
                              b.elements.each do |e|
         | 
| 14 | 
            +
                                if e.type == 'rich_text_section' or e.type == 'rich_text_preformatted'
         | 
| 15 | 
            +
                                  if e.elements.size > 0 and (e.elements.type.uniq - ['link', 'text', 'user', 'channel']) == []
         | 
| 16 | 
            +
                                    data_text += '```' if e.type == 'rich_text_preformatted'
         | 
| 17 | 
            +
                                    e.elements.each do |el|
         | 
| 18 | 
            +
                                      if el.type == 'text'
         | 
| 19 | 
            +
                                        data_text += el.text
         | 
| 20 | 
            +
                                      elsif el.type == 'user'
         | 
| 21 | 
            +
                                        data_text += "<@#{el.user_id}>"
         | 
| 22 | 
            +
                                      elsif el.type == 'channel'
         | 
| 23 | 
            +
                                        tch = data.text.scan(/(<##{el.channel_id}\|[^\>]*>)/).join
         | 
| 24 | 
            +
                                        data_text += tch
         | 
| 25 | 
            +
                                      else
         | 
| 26 | 
            +
                                        data_text += el.url
         | 
| 27 | 
            +
                                      end
         | 
| 26 28 | 
             
                                    end
         | 
| 29 | 
            +
                                    data_text += '```' if e.type == 'rich_text_preformatted'
         | 
| 27 30 | 
             
                                  end
         | 
| 28 | 
            -
                                  data_text += '```' if e.type == 'rich_text_preformatted'
         | 
| 29 31 | 
             
                                end
         | 
| 30 32 | 
             
                              end
         | 
| 31 33 | 
             
                            end
         | 
| 32 34 | 
             
                          end
         | 
| 33 35 | 
             
                        end
         | 
| 36 | 
            +
                        data.text = data_text unless data_text == ''
         | 
| 34 37 | 
             
                      end
         | 
| 35 | 
            -
                      data.text =  | 
| 38 | 
            +
                      data.text = CGI.unescapeHTML(data.text)
         | 
| 39 | 
            +
                      data.text.gsub!("\u00A0", " ") #to change   (asc char 160) into blank space
         | 
| 36 40 | 
             
                    end
         | 
| 37 | 
            -
                    data.text | 
| 38 | 
            -
                    data.text.gsub!( | 
| 41 | 
            +
                    data.text.gsub!('‘', "'")
         | 
| 42 | 
            +
                    data.text.gsub!('’', "'")
         | 
| 43 | 
            +
                    data.text.gsub!('“', '"') 
         | 
| 44 | 
            +
                    data.text.gsub!('”', '"')
         | 
| 45 | 
            +
                  rescue Exception => exc
         | 
| 46 | 
            +
                    @logger.warn "Impossible to unescape or clean format for data.text:#{data.text}"
         | 
| 47 | 
            +
                    @logger.warn exc.inspect
         | 
| 39 48 | 
             
                  end
         | 
| 40 | 
            -
                   | 
| 41 | 
            -
                  data. | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
                   | 
| 46 | 
            -
                   | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            +
                  
         | 
| 50 | 
            +
                  unless data.key?(:routine)
         | 
| 51 | 
            +
                    data.routine = false 
         | 
| 52 | 
            +
                    data.routine_name = ''
         | 
| 53 | 
            +
                    data.routine_type = ''
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                  if config[:testing] and config.on_master_bot and !@buffered
         | 
| 56 | 
            +
                    @buffered = true
         | 
| 57 | 
            +
                    open("#{config.path}/buffer.log", "a") { |f|
         | 
| 58 | 
            +
                      f.puts "|#{data.channel}|#{data.user}|#{data.user_name}|#{data.text}"
         | 
| 59 | 
            +
                    }
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
                  if data.key?(:dest) and data.dest.to_s!='' # for run routines and publish on different channels
         | 
| 62 | 
            +
                    dest = data.dest
         | 
| 63 | 
            +
                  elsif data.channel[0] == "D" or data.channel[0] == "C" or data.channel[0] == "G" #Direct message or Channel or Private Channel
         | 
| 64 | 
            +
                    dest = data.channel
         | 
| 65 | 
            +
                  else # not treated
         | 
| 66 | 
            +
                    dest = nil
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
                  #todo: sometimes data.user is nil, check the problem.
         | 
| 69 | 
            +
                  @logger.warn "!dest is nil. user: #{data.user}, channel: #{data.channel}, message: #{data.text}" if dest.nil?
         | 
| 70 | 
            +
                  if !data.files.nil? and data.files.size == 1 and data.text.to_s == "" and data.files[0].filetype == "ruby"
         | 
| 71 | 
            +
                    data.text = "ruby"
         | 
| 72 | 
            +
                  end
         | 
| 73 | 
            +
                  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]
         | 
| 74 | 
            +
                    @pings << $1
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
                  typem = :dont_treat
         | 
| 77 | 
            +
                  if data.nil? or data.user.nil? or data.user.to_s==''
         | 
| 78 | 
            +
                    user_info = nil
         | 
| 79 | 
            +
                    @users = get_users() if @users.empty?
         | 
| 80 | 
            +
                  else
         | 
| 81 | 
            +
                    #todo: when changed @questions user_id then move user_info inside the ifs to avoid calling it when not necessary
         | 
| 82 | 
            +
                    user_info = @users.select{|u| u.id == data.user or (u.key?(:enterprise_user) and u.enterprise_user.id == data.user)}[-1]
         | 
| 83 | 
            +
                    if user_info.nil? or user_info.empty?
         | 
| 84 | 
            +
                      @users = get_users() 
         | 
| 85 | 
            +
                      user_info = @users.select{|u| u.id == data.user or (u.key?(:enterprise_user) and u.enterprise_user.id == data.user)}[-1]
         | 
| 86 | 
            +
                    end
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
                  if !dest.nil? and !data.text.nil? and !data.text.to_s.match?(/\A\s*\z/)
         | 
| 89 | 
            +
                    if data.channel[0] == "D" and !data.text.to_s.match?(/^\s*<@#{config[:nick_id]}>\s+/) and 
         | 
| 90 | 
            +
                      (data.text.to_s.match?(/^\s*(on)?\s*<#\w+\|[^>]*>/i) or data.text.to_s.match?(/^\s*(on)?\s*#\w+/i))
         | 
| 91 | 
            +
                      data.text = "<@#{config[:nick_id]}> " + data.text.to_s
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
                    #todo: we need to add mixed channels: @smart-bot on private1 #bot1cm <#CXDDFRDDF|bot2cu>: echo A
         | 
| 94 | 
            +
                    if data.text.match(/\A\^\^+/) # to open a thread it will be only when starting by single ^
         | 
| 95 | 
            +
                      typem = :dont_treat
         | 
| 96 | 
            +
                    elsif data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?((<#\w+\|[^>]*>\s*)+)\s*:?\s*(.*)/im) or 
         | 
| 97 | 
            +
                      data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?((#[a-zA-Z0-9\-\_]+\s*)+)\s*:?\s*(.*)/im) or
         | 
| 98 | 
            +
                      data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?(([a-zA-Z0-9\-\_]+\s*)+)\s*:\s*(.*)/im)
         | 
| 99 | 
            +
                      channels_rules = $2 #multiple channels @smart-bot on #channel1 #channel2 echo AAA
         | 
| 100 | 
            +
                      data_text = $4
         | 
| 101 | 
            +
                      channel_rules_name = ''
         | 
| 102 | 
            +
                      channel_rules = ''
         | 
| 103 | 
            +
                      channels_arr = channels_rules.scan(/<#(\w+)\|([^>]*)>/)
         | 
| 104 | 
            +
                      if channels_arr.size == 0
         | 
| 105 | 
            +
                        channels_arr = []
         | 
| 106 | 
            +
                        channels_rules.scan(/([^\s]+)/).each do |cn|
         | 
| 107 | 
            +
                          cna = cn.join.gsub('#','')
         | 
| 108 | 
            +
                          if @channels_name.key?(cna)
         | 
| 109 | 
            +
                            channels_arr << [cna, @channels_name[cna]]
         | 
| 110 | 
            +
                          else
         | 
| 111 | 
            +
                            channels_arr << [@channels_id[cna], cna]
         | 
| 112 | 
            +
                          end
         | 
| 113 | 
            +
                        end
         | 
| 114 | 
            +
                      else
         | 
| 115 | 
            +
                        channels_arr.each do |row|
         | 
| 116 | 
            +
                          row[0] = @channels_id[row[1]] if row[0] == ''
         | 
| 117 | 
            +
                          row[1] = @channels_name[row[0]] if row[1] == ''              
         | 
| 118 | 
            +
                        end
         | 
| 119 | 
            +
                      end
         | 
| 120 | 
            +
                      
         | 
| 121 | 
            +
                      # to be treated only on the bots of the requested channels
         | 
| 122 | 
            +
                      channels_arr.each do |tcid, tcname|
         | 
| 123 | 
            +
                        if @channel_id == tcid
         | 
| 124 | 
            +
                          data.text = data_text
         | 
| 125 | 
            +
                          typem = :on_call
         | 
| 126 | 
            +
                          channel_rules = tcid
         | 
| 127 | 
            +
                          channel_rules_name = tcname
         | 
| 128 | 
            +
                          break
         | 
| 129 | 
            +
                        elsif @bots_created.key?(@channel_id) and @bots_created[@channel_id][:extended].include?(tcname)
         | 
| 130 | 
            +
                          data.text = data_text
         | 
| 131 | 
            +
                          typem = :on_call
         | 
| 132 | 
            +
                          channel_rules = @channel_id
         | 
| 133 | 
            +
                          channel_rules_name = @channels_name[@channel_id]
         | 
| 134 | 
            +
                          break
         | 
| 135 | 
            +
                        end
         | 
| 136 | 
            +
                      end
         | 
| 49 137 |  | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
                  }
         | 
| 54 | 
            -
                end
         | 
| 55 | 
            -
                if data.key?(:dest) and data.dest.to_s!='' # for run routines and publish on different channels
         | 
| 56 | 
            -
                  dest = data.dest
         | 
| 57 | 
            -
                elsif data.channel[0] == "D" or data.channel[0] == "C" or data.channel[0] == "G" #Direct message or Channel or Private Channel
         | 
| 58 | 
            -
                  dest = data.channel
         | 
| 59 | 
            -
                else # not treated
         | 
| 60 | 
            -
                  dest = nil
         | 
| 61 | 
            -
                end
         | 
| 62 | 
            -
                #todo: sometimes data.user is nil, check the problem.
         | 
| 63 | 
            -
                @logger.warn "!dest is nil. user: #{data.user}, channel: #{data.channel}, message: #{data.text}" if dest.nil?
         | 
| 64 | 
            -
                if !data.files.nil? and data.files.size == 1 and data.text.to_s == "" and data.files[0].filetype == "ruby"
         | 
| 65 | 
            -
                  data.text = "ruby"
         | 
| 66 | 
            -
                end
         | 
| 67 | 
            -
                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]
         | 
| 68 | 
            -
                  @pings << $1
         | 
| 69 | 
            -
                end
         | 
| 70 | 
            -
                typem = :dont_treat
         | 
| 71 | 
            -
                if !dest.nil? and !data.text.nil? and !data.text.to_s.match?(/\A\s*\z/)
         | 
| 72 | 
            -
                  #todo: we need to add mixed channels: @smart-bot on private1 #bot1cm <#CXDDFRDDF|bot2cu>: echo A
         | 
| 73 | 
            -
                  if data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?((<#\w+\|[^>]+>\s*)+)\s*:?\s*(.*)/im) or 
         | 
| 74 | 
            -
                    data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?((#[a-zA-Z0-9]+\s*)+)\s*:?\s*(.*)/im) or
         | 
| 75 | 
            -
                    data.text.match(/^\s*<@#{config[:nick_id]}>\s+(on\s+)?(([a-zA-Z0-9]+\s*)+)\s*:\s*(.*)/im)
         | 
| 76 | 
            -
                    channels_rules = $2 #multiple channels @smart-bot on #channel1 #channel2 echo AAA
         | 
| 77 | 
            -
                    data_text = $4
         | 
| 78 | 
            -
                    channel_rules_name = ''
         | 
| 79 | 
            -
                    channel_rules = ''
         | 
| 80 | 
            -
                    channels_arr = channels_rules.scan(/<#(\w+)\|([^>]+)>/)
         | 
| 81 | 
            -
                    if channels_arr.size == 0
         | 
| 82 | 
            -
                      channels_arr = []
         | 
| 83 | 
            -
                      channels_rules.scan(/([^\s]+)/).each do |cn|
         | 
| 84 | 
            -
                        cna = cn.join.gsub('#','')
         | 
| 85 | 
            -
                        channels_arr << [@channels_id[cna], cna]
         | 
| 138 | 
            +
                    elsif data.channel == @master_bot_id
         | 
| 139 | 
            +
                      if config.on_master_bot #only to be treated on master bot channel
         | 
| 140 | 
            +
                        typem = :on_master
         | 
| 86 141 | 
             
                      end
         | 
| 87 | 
            -
                     | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
                      if @channel_id == tcid
         | 
| 91 | 
            -
                        data.text = data_text
         | 
| 92 | 
            -
                        typem = :on_call
         | 
| 93 | 
            -
                        channel_rules = tcid
         | 
| 94 | 
            -
                        channel_rules_name = tcname
         | 
| 95 | 
            -
                        break
         | 
| 142 | 
            +
                    elsif @bots_created.key?(data.channel)
         | 
| 143 | 
            +
                      if @channel_id == data.channel #only to be treated by the bot on the channel
         | 
| 144 | 
            +
                        typem = :on_bot
         | 
| 96 145 | 
             
                      end
         | 
| 97 | 
            -
                     | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
                    get_rules_imported()
         | 
| 108 | 
            -
                    if @rules_imported.key?(data.user) && @rules_imported[data.user].key?(data.user) and
         | 
| 109 | 
            -
                      @bots_created.key?(@rules_imported[data.user][data.user])
         | 
| 110 | 
            -
                      if @channel_id == @rules_imported[data.user][data.user]
         | 
| 111 | 
            -
                        #only to be treated by the channel we are 'using'
         | 
| 146 | 
            +
                    elsif data.channel[0] == "D" #Direct message
         | 
| 147 | 
            +
                      get_rules_imported()
         | 
| 148 | 
            +
                      if @rules_imported.key?(user_info.name) && @rules_imported[user_info.name].key?(user_info.name) and
         | 
| 149 | 
            +
                        @bots_created.key?(@rules_imported[user_info.name][user_info.name])
         | 
| 150 | 
            +
                        if @channel_id == @rules_imported[user_info.name][user_info.name]
         | 
| 151 | 
            +
                          #only to be treated by the channel we are 'using'
         | 
| 152 | 
            +
                          typem = :on_dm
         | 
| 153 | 
            +
                        end
         | 
| 154 | 
            +
                      elsif config.on_master_bot
         | 
| 155 | 
            +
                        #only to be treated by master bot
         | 
| 112 156 | 
             
                        typem = :on_dm
         | 
| 113 157 | 
             
                      end
         | 
| 114 | 
            -
                    elsif  | 
| 115 | 
            -
                      #only to be treated  | 
| 116 | 
            -
                       | 
| 117 | 
            -
             | 
| 118 | 
            -
             | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 123 | 
            -
             | 
| 124 | 
            -
             | 
| 158 | 
            +
                    elsif data.channel[0] == "C" or data.channel[0] == "G"
         | 
| 159 | 
            +
                      #only to be treated on the channel of the bot. excluding running ruby
         | 
| 160 | 
            +
                      if !config.on_master_bot and @bots_created.key?(@channel_id) and @bots_created[@channel_id][:extended].include?(@channels_name[data.channel]) and
         | 
| 161 | 
            +
                        !data.text.match?(/^!?\s*(ruby|code)\s+/) and !data.text.match?(/^!?!?\s*(ruby|code)\s+/) and !data.text.match?(/^\^?\s*(ruby|code)\s+/)
         | 
| 162 | 
            +
                        typem = :on_extended
         | 
| 163 | 
            +
                      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+/)  )
         | 
| 164 | 
            +
                        #or in case of running ruby, the master bot
         | 
| 165 | 
            +
                        @bots_created.each do |k, v|
         | 
| 166 | 
            +
                          if v.key?(:extended) and v[:extended].include?(@channels_name[data.channel])
         | 
| 167 | 
            +
                            typem = :on_extended
         | 
| 168 | 
            +
                            break
         | 
| 169 | 
            +
                          end
         | 
| 170 | 
            +
                        end
         | 
| 171 | 
            +
                      end
         | 
| 172 | 
            +
                      extended = false
         | 
| 125 173 | 
             
                      @bots_created.each do |k, v|
         | 
| 126 174 | 
             
                        if v.key?(:extended) and v[:extended].include?(@channels_name[data.channel])
         | 
| 127 | 
            -
                           | 
| 175 | 
            +
                          extended = true
         | 
| 128 176 | 
             
                          break
         | 
| 129 177 | 
             
                        end
         | 
| 130 178 | 
             
                      end
         | 
| 131 | 
            -
             | 
| 132 | 
            -
             | 
| 133 | 
            -
                       | 
| 134 | 
            -
             | 
| 135 | 
            -
                  end
         | 
| 136 | 
            -
                end
         | 
| 137 | 
            -
                unless typem == :dont_treat
         | 
| 138 | 
            -
                  if (Time.now - @last_activity_check) > 60 * 30 #every 30 minutes
         | 
| 139 | 
            -
                    @last_activity_check = Time.now
         | 
| 140 | 
            -
                    @listening.each do |k,v|
         | 
| 141 | 
            -
                      v.each do |kk, vv|
         | 
| 142 | 
            -
                        @listening[k].delete(kk) if (Time.now - vv) > 60 * 30
         | 
| 179 | 
            +
                      if data.channel[0] == "G" and config.on_master_bot and !extended #private group
         | 
| 180 | 
            +
                        typem = :on_pg
         | 
| 181 | 
            +
                      elsif data.channel[0] == 'C' and config.on_master_bot and !extended #public group
         | 
| 182 | 
            +
                        typem = :on_pub
         | 
| 143 183 | 
             
                      end
         | 
| 144 | 
            -
                      @listening.delete(k) if @listening[k].empty?
         | 
| 145 184 | 
             
                    end
         | 
| 146 185 | 
             
                  end
         | 
| 147 | 
            -
                   | 
| 148 | 
            -
             | 
| 149 | 
            -
                     | 
| 150 | 
            -
             | 
| 151 | 
            -
             | 
| 152 | 
            -
             | 
| 153 | 
            -
             | 
| 154 | 
            -
             | 
| 155 | 
            -
             | 
| 156 | 
            -
                       | 
| 186 | 
            +
                  load "#{config.path}/rules/general_commands.rb" if File.exists?("#{config.path}/rules/general_commands.rb") and @datetime_general_commands != File.mtime("#{config.path}/rules/general_commands.rb")
         | 
| 187 | 
            +
                  unless typem == :dont_treat or user_info.nil?
         | 
| 188 | 
            +
                    if (Time.now - @last_activity_check) > 60 * 30 #every 30 minutes
         | 
| 189 | 
            +
                      @last_activity_check = Time.now
         | 
| 190 | 
            +
                      @listening.each do |k,v|
         | 
| 191 | 
            +
                        v.each do |kk, vv|
         | 
| 192 | 
            +
                          @listening[k].delete(kk) if (Time.now - vv) > 60 * 30
         | 
| 193 | 
            +
                        end
         | 
| 194 | 
            +
                        @listening.delete(k) if @listening[k].empty?
         | 
| 195 | 
            +
                      end
         | 
| 157 196 | 
             
                    end
         | 
| 158 | 
            -
                     | 
| 159 | 
            -
                       | 
| 160 | 
            -
             | 
| 161 | 
            -
             | 
| 197 | 
            +
                    begin
         | 
| 198 | 
            +
                      #user_info.id = data.user #todo: remove this line when slack issue with Wxxxx Uxxxx fixed
         | 
| 199 | 
            +
                      data.user = user_info.id  #todo: remove this line when slack issue with Wxxxx Uxxxx fixed
         | 
| 200 | 
            +
                      if data.thread_ts.nil?
         | 
| 201 | 
            +
                        qdest = dest
         | 
| 162 202 | 
             
                      else
         | 
| 163 | 
            -
                         | 
| 164 | 
            -
                        @answer[user_info.user.name][qdest] = data.text
         | 
| 165 | 
            -
                        @questions[user_info.user.name] = data.text # to be backwards compatible #todo remove it when 2.0
         | 
| 203 | 
            +
                        qdest = data.thread_ts
         | 
| 166 204 | 
             
                      end
         | 
| 167 | 
            -
             | 
| 168 | 
            -
             | 
| 169 | 
            -
             | 
| 170 | 
            -
             | 
| 171 | 
            -
             | 
| 172 | 
            -
                           | 
| 173 | 
            -
             | 
| 174 | 
            -
             | 
| 205 | 
            +
                      if !answer(user_info.name, qdest).empty?
         | 
| 206 | 
            +
                        if data.text.match?(/\A\s*(Bye|Bæ|Good\sBye|Adiós|Ciao|Bless|Bless\sBless|Adeu)\s(#{@salutations.join("|")})\s*$/i)
         | 
| 207 | 
            +
                          answer_delete(user_info.name, qdest)
         | 
| 208 | 
            +
                          command = data.text
         | 
| 209 | 
            +
                        else
         | 
| 210 | 
            +
                          command = answer(user_info.name, qdest)
         | 
| 211 | 
            +
                          @answer[user_info.name][qdest] = data.text
         | 
| 212 | 
            +
                          @questions[user_info.name] = data.text # to be backwards compatible #todo remove it when 2.0
         | 
| 213 | 
            +
                        end
         | 
| 214 | 
            +
                      elsif @repl_sessions.key?(user_info.name) and data.channel==@repl_sessions[user_info.name][:dest] and 
         | 
| 215 | 
            +
                        ((@repl_sessions[user_info.name][:on_thread] and data.thread_ts == @repl_sessions[user_info.name][:thread_ts]) or
         | 
| 216 | 
            +
                        (!@repl_sessions[user_info.name][:on_thread] and data.thread_ts.to_s == '' ))
         | 
| 217 | 
            +
                        
         | 
| 218 | 
            +
                        if data.text.match(/^\s*```(.*)```\s*$/im)
         | 
| 219 | 
            +
                            @repl_sessions[user_info.name][:command] = $1
         | 
| 220 | 
            +
                        else   
         | 
| 221 | 
            +
                          @repl_sessions[user_info.name][:command] = data.text
         | 
| 222 | 
            +
                        end
         | 
| 223 | 
            +
                        command = 'repl'
         | 
| 224 | 
            +
                      else
         | 
| 225 | 
            +
                        command = data.text
         | 
| 175 226 | 
             
                      end
         | 
| 176 | 
            -
                      command = 'repl'
         | 
| 177 | 
            -
                    else
         | 
| 178 | 
            -
                      command = data.text
         | 
| 179 | 
            -
                    end
         | 
| 180 227 |  | 
| 181 | 
            -
             | 
| 182 | 
            -
             | 
| 183 | 
            -
             | 
| 184 | 
            -
             | 
| 185 | 
            -
             | 
| 186 | 
            -
             | 
| 187 | 
            -
             | 
| 228 | 
            +
                      #when added special characters on the message
         | 
| 229 | 
            +
                      if command.match(/\A\s*```(.*)```\s*\z/im)
         | 
| 230 | 
            +
                        command = $1
         | 
| 231 | 
            +
                      elsif command.size >= 2 and
         | 
| 232 | 
            +
                        ((command[0] == "`" and command[-1] == "`") or (command[0] == "*" and command[-1] == "*") or (command[0] == "_" and command[-1] == "_"))
         | 
| 233 | 
            +
                        command = command[1..-2]
         | 
| 234 | 
            +
                      end
         | 
| 188 235 |  | 
| 189 | 
            -
             | 
| 190 | 
            -
             | 
| 191 | 
            -
             | 
| 192 | 
            -
             | 
| 193 | 
            -
             | 
| 194 | 
            -
             | 
| 195 | 
            -
             | 
| 196 | 
            -
             | 
| 236 | 
            +
                      #ruby file attached
         | 
| 237 | 
            +
                      if !data.files.nil? and data.files.size == 1 and
         | 
| 238 | 
            +
                        (command.match?(/^(ruby|code)\s*$/) or (command.match?(/^\s*$/) and data.files[0].filetype == "ruby") or
         | 
| 239 | 
            +
                          (typem == :on_call and data.files[0].filetype == "ruby"))
         | 
| 240 | 
            +
                        res = Faraday.new("https://files.slack.com", headers: { "Authorization" => "Bearer #{config[:token]}" }).get(data.files[0].url_private)
         | 
| 241 | 
            +
                        command += " ruby" if command != "ruby"
         | 
| 242 | 
            +
                        command = "#{command} #{res.body.to_s.force_encoding("UTF-8")}"
         | 
| 243 | 
            +
                      end
         | 
| 197 244 |  | 
| 198 | 
            -
             | 
| 199 | 
            -
             | 
| 245 | 
            +
                      if typem == :on_call
         | 
| 246 | 
            +
                        command = "!" + command unless command[0] == "!" or command.match?(/^\s*$/) or command[0] == "^"
         | 
| 200 247 |  | 
| 201 | 
            -
             | 
| 202 | 
            -
             | 
| 203 | 
            -
             | 
| 204 | 
            -
             | 
| 205 | 
            -
             | 
| 206 | 
            -
             | 
| 207 | 
            -
             | 
| 208 | 
            -
             | 
| 209 | 
            -
             | 
| 210 | 
            -
             | 
| 211 | 
            -
             | 
| 212 | 
            -
             | 
| 213 | 
            -
             | 
| 214 | 
            -
             | 
| 248 | 
            +
                        #todo: add pagination for case more than 1000 channels on the workspace
         | 
| 249 | 
            +
                        channels = get_channels()
         | 
| 250 | 
            +
                        channel_found = channels.detect { |c| c.name == channel_rules_name }
         | 
| 251 | 
            +
                        members = get_channel_members(@channels_id[channel_rules_name]) unless channel_found.nil?
         | 
| 252 | 
            +
                        if channel_found.nil?
         | 
| 253 | 
            +
                          @logger.fatal "Not possible to find the channel #{channel_rules_name}"
         | 
| 254 | 
            +
                        elsif channel_found.name == config.master_channel
         | 
| 255 | 
            +
                          respond "You cannot use the rules from Master Channel on any other channel.", data.channel
         | 
| 256 | 
            +
                        elsif @status != :on
         | 
| 257 | 
            +
                          respond "The bot in that channel is not :on", data.channel
         | 
| 258 | 
            +
                        elsif data.user == channel_found.creator or members.include?(data.user)
         | 
| 259 | 
            +
                          process_first(user_info, command, dest, channel_rules, typem, data.files, data.ts, data.thread_ts, data.routine, data.routine_name, data.routine_type)
         | 
| 260 | 
            +
                        else
         | 
| 261 | 
            +
                          respond "You need to join the channel <##{channel_found.id}> to be able to use the rules.", data.channel
         | 
| 262 | 
            +
                        end
         | 
| 263 | 
            +
                      elsif config.on_master_bot and typem == :on_extended and
         | 
| 264 | 
            +
                            command.size > 0 and command[0] != "-"
         | 
| 265 | 
            +
                        # to run ruby only from the master bot for the case more than one extended
         | 
| 266 | 
            +
                        process_first(user_info, command, dest, @channel_id, typem, data.files, data.ts, data.thread_ts, data.routine, data.routine_name, data.routine_type)
         | 
| 267 | 
            +
                      elsif !config.on_master_bot and @bots_created[@channel_id].key?(:extended) and
         | 
| 268 | 
            +
                            @bots_created[@channel_id][:extended].include?(@channels_name[data.channel]) and
         | 
| 269 | 
            +
                            command.size > 0 and command[0] != "-"
         | 
| 270 | 
            +
                        process_first(user_info, command, dest, @channel_id, typem, data.files, data.ts, data.thread_ts, data.routine, data.routine_name, data.routine_type)
         | 
| 271 | 
            +
                      elsif (dest[0] == "D" or @channel_id == data.channel or data.user == config[:nick_id]) and
         | 
| 272 | 
            +
                            command.size > 0 and command[0] != "-"
         | 
| 273 | 
            +
                        process_first(user_info, command, dest, data.channel, typem, data.files, data.ts, data.thread_ts, data.routine, data.routine_name, data.routine_type)
         | 
| 274 | 
            +
                        # if @botname on #channel_rules: do something
         | 
| 275 | 
            +
                      elsif typem == :on_pub or typem == :on_pg
         | 
| 276 | 
            +
                        process_first(user_info, command, dest, channel_rules, typem, data.files, data.ts, data.thread_ts, data.routine, data.routine_name, data.routine_type)
         | 
| 215 277 | 
             
                      end
         | 
| 216 | 
            -
                     | 
| 217 | 
            -
             | 
| 218 | 
            -
                      # to run ruby only from the master bot for the case more than one extended
         | 
| 219 | 
            -
                      process_first(user_info.user, command, dest, @channel_id, typem, data.files, data.ts, data.thread_ts, data.routine)
         | 
| 220 | 
            -
                    elsif !config.on_master_bot and @bots_created[@channel_id].key?(:extended) and
         | 
| 221 | 
            -
                          @bots_created[@channel_id][:extended].include?(@channels_name[data.channel]) and
         | 
| 222 | 
            -
                          command.size > 0 and command[0] != "-"
         | 
| 223 | 
            -
                      process_first(user_info.user, command, dest, @channel_id, typem, data.files, data.ts, data.thread_ts, data.routine)
         | 
| 224 | 
            -
                    elsif (dest[0] == "D" or @channel_id == data.channel or data.user == config[:nick_id]) and
         | 
| 225 | 
            -
                          command.size > 0 and command[0] != "-"
         | 
| 226 | 
            -
                      process_first(user_info.user, command, dest, data.channel, typem, data.files, data.ts, data.thread_ts, data.routine)
         | 
| 227 | 
            -
                      # if @botname on #channel_rules: do something
         | 
| 278 | 
            +
                    rescue Exception => stack
         | 
| 279 | 
            +
                      @logger.fatal stack
         | 
| 228 280 | 
             
                    end
         | 
| 229 | 
            -
             | 
| 230 | 
            -
             | 
| 231 | 
            -
             | 
| 232 | 
            -
             | 
| 233 | 
            -
             | 
| 234 | 
            -
             | 
| 235 | 
            -
                     | 
| 236 | 
            -
             | 
| 237 | 
            -
             | 
| 238 | 
            -
             | 
| 239 | 
            -
             | 
| 240 | 
            -
             | 
| 241 | 
            -
             | 
| 242 | 
            -
             | 
| 243 | 
            -
             | 
| 244 | 
            -
             | 
| 245 | 
            -
             | 
| 246 | 
            -
                             | 
| 247 | 
            -
                            config.on_maintenance_message = file_cts.on_maintenance_message
         | 
| 281 | 
            +
             | 
| 282 | 
            +
                  else
         | 
| 283 | 
            +
                    @logger.warn "Pay attention there is no user on users with id #{data.user}" if user_info.nil?
         | 
| 284 | 
            +
                    if !config.on_master_bot and !dest.nil? and (data.channel == @master_bot_id or dest[0] == "D") and
         | 
| 285 | 
            +
                      data.text.match?(/^\s*(!|!!|\^)?\s*bot\s+status\s*$/i) and @admin_users_id.include?(data.user)
         | 
| 286 | 
            +
                      respond "ping from #{config.channel}", dest
         | 
| 287 | 
            +
                    elsif !config.on_master_bot and !dest.nil? and data.user == config[:nick_id] and dest == @master_bot_id
         | 
| 288 | 
            +
                      # to treat on other bots the status messages populated on master bot
         | 
| 289 | 
            +
                      case data.text
         | 
| 290 | 
            +
                      when /General message has been set\./i, /General message won't be displayed anymore./i
         | 
| 291 | 
            +
                        sleep 2
         | 
| 292 | 
            +
                        if File.exist?("#{config.path}/config_tmp.status")
         | 
| 293 | 
            +
                          file_cts = IO.readlines("#{config.path}/config_tmp.status").join
         | 
| 294 | 
            +
                          unless file_cts.to_s() == ""
         | 
| 295 | 
            +
                            file_cts = eval(file_cts)
         | 
| 296 | 
            +
                            if file_cts.is_a?(Hash) and file_cts.key?(:general_message)
         | 
| 297 | 
            +
                              config.general_message = file_cts.general_message
         | 
| 298 | 
            +
                            end
         | 
| 248 299 | 
             
                          end
         | 
| 249 300 | 
             
                        end
         | 
| 250 | 
            -
                       | 
| 251 | 
            -
             | 
| 252 | 
            -
             | 
| 253 | 
            -
             | 
| 254 | 
            -
             | 
| 255 | 
            -
             | 
| 256 | 
            -
             | 
| 257 | 
            -
             | 
| 258 | 
            -
             | 
| 259 | 
            -
                             | 
| 301 | 
            +
                      when /From now on I'll be on maintenance status/i
         | 
| 302 | 
            +
                        sleep 2
         | 
| 303 | 
            +
                        if File.exist?("#{config.path}/config_tmp.status")
         | 
| 304 | 
            +
                          file_cts = IO.readlines("#{config.path}/config_tmp.status").join
         | 
| 305 | 
            +
                          unless file_cts.to_s() == ""
         | 
| 306 | 
            +
                            file_cts = eval(file_cts)
         | 
| 307 | 
            +
                            if file_cts.is_a?(Hash) and file_cts.key?(:on_maintenance)
         | 
| 308 | 
            +
                              config.on_maintenance = file_cts.on_maintenance
         | 
| 309 | 
            +
                              config.on_maintenance_message = file_cts.on_maintenance_message
         | 
| 310 | 
            +
                            end
         | 
| 260 311 | 
             
                          end
         | 
| 261 312 | 
             
                        end
         | 
| 262 | 
            -
                       | 
| 263 | 
            -
             | 
| 264 | 
            -
             | 
| 265 | 
            -
             | 
| 266 | 
            -
             | 
| 267 | 
            -
             | 
| 268 | 
            -
             | 
| 269 | 
            -
             | 
| 270 | 
            -
             | 
| 271 | 
            -
             | 
| 272 | 
            -
             | 
| 273 | 
            -
                    when /removed the access to the rules of (.+) from (.+)\.$/i
         | 
| 274 | 
            -
                      sleep 2
         | 
| 275 | 
            -
                      get_bots_created()
         | 
| 276 | 
            -
                    when /global shortcut added/
         | 
| 277 | 
            -
                      sleep 2
         | 
| 278 | 
            -
                      if File.exist?("#{config.path}/shortcuts/shortcuts_global.rb")
         | 
| 279 | 
            -
                        file_sc = IO.readlines("#{config.path}/shortcuts/shortcuts_global.rb").join
         | 
| 280 | 
            -
                        unless file_sc.to_s() == ""
         | 
| 281 | 
            -
                          @shortcuts_global = eval(file_sc)
         | 
| 313 | 
            +
                      when /From now on I won't be on maintenance/i
         | 
| 314 | 
            +
                        sleep 2
         | 
| 315 | 
            +
                        if File.exist?("#{config.path}/config_tmp.status")
         | 
| 316 | 
            +
                          file_cts = IO.readlines("#{config.path}/config_tmp.status").join
         | 
| 317 | 
            +
                          unless file_cts.to_s() == ""
         | 
| 318 | 
            +
                            file_cts = eval(file_cts)
         | 
| 319 | 
            +
                            if file_cts.is_a?(Hash) and file_cts.key?(:on_maintenance)
         | 
| 320 | 
            +
                              config.on_maintenance = file_cts.on_maintenance
         | 
| 321 | 
            +
                              config.on_maintenance_message = file_cts.on_maintenance_message
         | 
| 322 | 
            +
                            end
         | 
| 323 | 
            +
                          end
         | 
| 282 324 | 
             
                        end
         | 
| 283 | 
            -
             | 
| 284 | 
            -
             | 
| 285 | 
            -
             | 
| 286 | 
            -
             | 
| 287 | 
            -
             | 
| 288 | 
            -
                         | 
| 289 | 
            -
             | 
| 325 | 
            +
                        
         | 
| 326 | 
            +
                      when /^Bot has been (closed|killed) by/i
         | 
| 327 | 
            +
                        sleep 2
         | 
| 328 | 
            +
                        get_bots_created()
         | 
| 329 | 
            +
                      when /^Changed status on (.+) to :(.+)/i
         | 
| 330 | 
            +
                        sleep 2
         | 
| 331 | 
            +
                        get_bots_created()
         | 
| 332 | 
            +
                      when /extended the rules from (.+) to be used on (.+)\.$/i
         | 
| 333 | 
            +
                        sleep 2
         | 
| 334 | 
            +
                        get_bots_created()
         | 
| 335 | 
            +
                      when /removed the access to the rules of (.+) from (.+)\.$/i
         | 
| 336 | 
            +
                        sleep 2
         | 
| 337 | 
            +
                        get_bots_created()
         | 
| 338 | 
            +
                      when /global shortcut added/
         | 
| 339 | 
            +
                        sleep 2
         | 
| 340 | 
            +
                        if File.exist?("#{config.path}/shortcuts/shortcuts_global.rb")
         | 
| 341 | 
            +
                          file_sc = IO.readlines("#{config.path}/shortcuts/shortcuts_global.rb").join
         | 
| 342 | 
            +
                          unless file_sc.to_s() == ""
         | 
| 343 | 
            +
                            @shortcuts_global = eval(file_sc)
         | 
| 344 | 
            +
                          end
         | 
| 345 | 
            +
                        end
         | 
| 346 | 
            +
                      when /global shortcut deleted/
         | 
| 347 | 
            +
                        sleep 2
         | 
| 348 | 
            +
                        if File.exist?("#{config.path}/shortcuts/shortcuts_global.rb")
         | 
| 349 | 
            +
                          file_sc = IO.readlines("#{config.path}/shortcuts/shortcuts_global.rb").join
         | 
| 350 | 
            +
                          unless file_sc.to_s() == ""
         | 
| 351 | 
            +
                            @shortcuts_global = eval(file_sc)
         | 
| 352 | 
            +
                          end
         | 
| 290 353 | 
             
                        end
         | 
| 291 354 | 
             
                      end
         | 
| 292 355 | 
             
                    end
         | 
| 293 356 | 
             
                  end
         | 
| 357 | 
            +
                rescue Exception => stack
         | 
| 358 | 
            +
                  @logger.fatal stack
         | 
| 294 359 | 
             
                end
         | 
| 295 360 | 
             
              end
         | 
| 296 361 | 
             
            end
         |