rubot 0.0.1
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.
- data/README +1 -0
- data/Rakefile +12 -0
- data/lib/core.rb +8 -0
- data/lib/core/command.rb +70 -0
- data/lib/core/dispatcher.rb +84 -0
- data/lib/core/listener.rb +13 -0
- data/lib/core/runner.rb +13 -0
- data/lib/extensions.rb +4 -0
- data/lib/extensions/kernel.rb +6 -0
- data/lib/extensions/string.rb +9 -0
- data/lib/init/bundler.rb +9 -0
- data/lib/irc.rb +7 -0
- data/lib/irc/constants.rb +10 -0
- data/lib/irc/message.rb +14 -0
- data/lib/irc/server.rb +149 -0
- data/lib/rubot.rb +12 -0
- metadata +83 -0
    
        data/README
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            # todo Add documentation here
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            require 'rubygems'
         | 
| 2 | 
            +
            require 'rake'
         | 
| 3 | 
            +
            require 'echoe'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Echoe.new('rubot', '0.0.1') do |p|
         | 
| 6 | 
            +
              p.description    = "A Ruby Bot framwork for IRC"
         | 
| 7 | 
            +
              p.url            = "http://github.com/tombombadil/hello_world"
         | 
| 8 | 
            +
              p.author         = "Chris Thorn"
         | 
| 9 | 
            +
              p.email          = "thorncp @nospam@ gmail.com"
         | 
| 10 | 
            +
              p.ignore_pattern = ["tmp/*", "script/*"]
         | 
| 11 | 
            +
              p.development_dependencies = []
         | 
| 12 | 
            +
            end
         | 
    
        data/lib/core.rb
    ADDED
    
    
    
        data/lib/core/command.rb
    ADDED
    
    | @@ -0,0 +1,70 @@ | |
| 1 | 
            +
            require "shellwords"
         | 
| 2 | 
            +
            module Rubot
         | 
| 3 | 
            +
              module Core
         | 
| 4 | 
            +
                class Command
         | 
| 5 | 
            +
                  attr_reader :protected
         | 
| 6 | 
            +
              
         | 
| 7 | 
            +
                  def initialize(dispatcher)
         | 
| 8 | 
            +
                    @dispatcher = dispatcher
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
              
         | 
| 11 | 
            +
                  def run(server, message)
         | 
| 12 | 
            +
                    args = Shellwords.shellwords(message.body.gsub(/(')/n, "\\\\\'"))
         | 
| 13 | 
            +
                    options = parse(args)
         | 
| 14 | 
            +
                
         | 
| 15 | 
            +
                    if options.help
         | 
| 16 | 
            +
                      # this to_s.lines.to_a business is a hack to not display the -h, --help line
         | 
| 17 | 
            +
                      # there's got to be an easier/better way to do this. but it'll work for now
         | 
| 18 | 
            +
                      server.msg(message.destination, options.help.to_s.lines.to_a[0..-2])
         | 
| 19 | 
            +
                    else
         | 
| 20 | 
            +
                      message.body = args.join(" ")
         | 
| 21 | 
            +
                      execute(server, message, options)
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
              
         | 
| 25 | 
            +
                  def is_protected?
         | 
| 26 | 
            +
                    false
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
              
         | 
| 29 | 
            +
                  private
         | 
| 30 | 
            +
              
         | 
| 31 | 
            +
                  # commands override this
         | 
| 32 | 
            +
                  def execute(server, message, options)
         | 
| 33 | 
            +
                    server.msg(message.destincation, "unimplemented")
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
              
         | 
| 36 | 
            +
                  def self.acts_as_protected
         | 
| 37 | 
            +
                    define_method(:is_protected?) do
         | 
| 38 | 
            +
                      true
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
              
         | 
| 42 | 
            +
                  def self.aliases(*aliases)
         | 
| 43 | 
            +
                    raise ArgumentError, 'only symbols allowed' unless aliases.all? {|a| a.is_a? Symbol}
         | 
| 44 | 
            +
                    define_method(:aliases) do
         | 
| 45 | 
            +
                      aliases
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
              
         | 
| 49 | 
            +
                  def parse(args)
         | 
| 50 | 
            +
                    options = OpenStruct.new
         | 
| 51 | 
            +
                    @parser = OptionParser.new do |parser|
         | 
| 52 | 
            +
                      parser.banner = "Usage: !#{self.class.to_s.downcase} [options]"
         | 
| 53 | 
            +
                  
         | 
| 54 | 
            +
                      options(parser, options)
         | 
| 55 | 
            +
                  
         | 
| 56 | 
            +
                      parser.on_tail("-h", "--help", "Show this message") do
         | 
| 57 | 
            +
                        options.help = parser
         | 
| 58 | 
            +
                      end
         | 
| 59 | 
            +
                    end
         | 
| 60 | 
            +
              
         | 
| 61 | 
            +
                    @parser.parse!(args)
         | 
| 62 | 
            +
                    options
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
              
         | 
| 65 | 
            +
                  # override this to add more options in a command class
         | 
| 66 | 
            +
                  def options(parser, options)
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
            end
         | 
| @@ -0,0 +1,84 @@ | |
| 1 | 
            +
            require "thread"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rubot
         | 
| 4 | 
            +
              module Core
         | 
| 5 | 
            +
                class Dispatcher
         | 
| 6 | 
            +
              
         | 
| 7 | 
            +
                  attr_reader :commands, :listeners, :function_character, :config, :resource_lock
         | 
| 8 | 
            +
              
         | 
| 9 | 
            +
                  def initialize(config)
         | 
| 10 | 
            +
                    @config = config
         | 
| 11 | 
            +
                    @function_character = @config["function_character"]
         | 
| 12 | 
            +
                
         | 
| 13 | 
            +
                    @auth_list = config["auth_list"].split(',')
         | 
| 14 | 
            +
                
         | 
| 15 | 
            +
                    @resource_lock = Mutex.new
         | 
| 16 | 
            +
                    reload
         | 
| 17 | 
            +
                    # runners are only run on server connection, so there's no need them to be in reload 
         | 
| 18 | 
            +
                    load_dir "runners", @runners = {}
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
              
         | 
| 21 | 
            +
                  def connected(server)
         | 
| 22 | 
            +
                    run_runners(server)
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
              
         | 
| 25 | 
            +
                  def reload
         | 
| 26 | 
            +
                		load_dir "commands", @commands = {}
         | 
| 27 | 
            +
                	  load_dir "listeners", @listeners = {}
         | 
| 28 | 
            +
                	end
         | 
| 29 | 
            +
              
         | 
| 30 | 
            +
                  def handle_message(server, message)		  
         | 
| 31 | 
            +
                	  if message.body =~ /^#{@function_character}([a-z_]+)( .+)?$/i
         | 
| 32 | 
            +
                	    message.body = $2.nil? ? "" : $2.strip # remove the function name from the message
         | 
| 33 | 
            +
                		  command = @commands[$1.underscore.to_sym]
         | 
| 34 | 
            +
            		  
         | 
| 35 | 
            +
                		  if command.nil?
         | 
| 36 | 
            +
                		    puts "#{$1} does not yield a command"
         | 
| 37 | 
            +
                		    return
         | 
| 38 | 
            +
                	    end
         | 
| 39 | 
            +
            		  
         | 
| 40 | 
            +
                		  #if command is protected and user is not authenticated, return
         | 
| 41 | 
            +
                		  if command.is_protected? && !@auth_list.include?(message.from)
         | 
| 42 | 
            +
                		    server.msg(message.destination, "unauthorized")
         | 
| 43 | 
            +
                		    return
         | 
| 44 | 
            +
                	    end
         | 
| 45 | 
            +
            		  
         | 
| 46 | 
            +
                		  command.run(server, message)
         | 
| 47 | 
            +
                	  elsif message.from != server.nick
         | 
| 48 | 
            +
                	    @listeners.each_value do |listener|
         | 
| 49 | 
            +
                	      listener.execute(server, message)
         | 
| 50 | 
            +
                      end
         | 
| 51 | 
            +
                    end
         | 
| 52 | 
            +
                  end
         | 
| 53 | 
            +
              
         | 
| 54 | 
            +
                  def add_auth(nick)
         | 
| 55 | 
            +
                    unless @auth_list.include? nick
         | 
| 56 | 
            +
                      @auth_list.push nick
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
              
         | 
| 60 | 
            +
                  def remove_auth(nick)
         | 
| 61 | 
            +
                    @auth_list.delete nick
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
              
         | 
| 64 | 
            +
                  private
         | 
| 65 | 
            +
              
         | 
| 66 | 
            +
                  def run_runners(server)
         | 
| 67 | 
            +
                    @runners.each_value {|r| r.run(server)}
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
            	
         | 
| 70 | 
            +
                	def load_dir(dir, set = nil)
         | 
| 71 | 
            +
                	  Dir["#{dir}/*.rb"].each do |file|
         | 
| 72 | 
            +
                	    load file
         | 
| 73 | 
            +
            	    
         | 
| 74 | 
            +
                	    next unless set
         | 
| 75 | 
            +
            	    
         | 
| 76 | 
            +
                      name = File.basename(file, ".rb")
         | 
| 77 | 
            +
                      clazz = eval(name.camelize).new(self)
         | 
| 78 | 
            +
                      set[name.to_sym] = clazz
         | 
| 79 | 
            +
                      clazz.aliases.each{|a| set[a] = clazz} if clazz.respond_to? :aliases
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
                end
         | 
| 83 | 
            +
              end
         | 
| 84 | 
            +
            end
         | 
    
        data/lib/core/runner.rb
    ADDED
    
    
    
        data/lib/extensions.rb
    ADDED
    
    
    
        data/lib/init/bundler.rb
    ADDED
    
    
    
        data/lib/irc.rb
    ADDED
    
    
    
        data/lib/irc/message.rb
    ADDED
    
    
    
        data/lib/irc/server.rb
    ADDED
    
    | @@ -0,0 +1,149 @@ | |
| 1 | 
            +
            require "socket"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Rubot
         | 
| 4 | 
            +
              module Irc
         | 
| 5 | 
            +
                class Server
         | 
| 6 | 
            +
                  include Rubot::Irc::Constants
         | 
| 7 | 
            +
                  attr_reader :nick, :connected_at
         | 
| 8 | 
            +
              
         | 
| 9 | 
            +
                  def initialize(dispatcher)
         | 
| 10 | 
            +
                    dispatcher.config["server"].each_pair do |key, value|
         | 
| 11 | 
            +
                      instance_variable_set("@#{key}".to_sym, value)
         | 
| 12 | 
            +
                    end
         | 
| 13 | 
            +
                    @channels = @channels.split(",").collect(&:strip)
         | 
| 14 | 
            +
                    @dispatcher = dispatcher
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
              
         | 
| 17 | 
            +
                  def connect
         | 
| 18 | 
            +
                    return if @is_connected
         | 
| 19 | 
            +
                
         | 
| 20 | 
            +
                    @conn = TCPSocket.open(@host, @port, @vhost)
         | 
| 21 | 
            +
                    raw "USER #{@nick} #{@nick} #{@nick} :#{@real_name}"
         | 
| 22 | 
            +
                    change_nick @nick
         | 
| 23 | 
            +
                    join @channels
         | 
| 24 | 
            +
                
         | 
| 25 | 
            +
                    begin
         | 
| 26 | 
            +
                        main_loop()
         | 
| 27 | 
            +
                    rescue Interrupt
         | 
| 28 | 
            +
                    rescue Exception => detail
         | 
| 29 | 
            +
                        puts detail.message()
         | 
| 30 | 
            +
                        print detail.backtrace.join("\n")
         | 
| 31 | 
            +
                        retry
         | 
| 32 | 
            +
                    end
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
              
         | 
| 35 | 
            +
                  def quit
         | 
| 36 | 
            +
                    raw "QUIT :#{@quit_message}"
         | 
| 37 | 
            +
                    @conn.close
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
              
         | 
| 40 | 
            +
                  def change_nick(new_nick)
         | 
| 41 | 
            +
                    raw "NICK #{new_nick}"
         | 
| 42 | 
            +
                    @nick = new_nick
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
              
         | 
| 45 | 
            +
                  def join(channels)
         | 
| 46 | 
            +
                    channels = channels.split(',') if channels.is_a? String
         | 
| 47 | 
            +
                    @channels.concat(channels).uniq!
         | 
| 48 | 
            +
                    bulk_command("JOIN %s", channels)
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
              
         | 
| 51 | 
            +
                  def part(channels)
         | 
| 52 | 
            +
                    channels = channels.split(',') if channels.is_a? String
         | 
| 53 | 
            +
                    @channels.reject! { |channel| channels.include?(channel) }
         | 
| 54 | 
            +
                    bulk_command("PART %s :#{@quit_message}", channels)
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
              
         | 
| 57 | 
            +
                  def msg(destination, message)
         | 
| 58 | 
            +
                    message = message.to_s.split("\n") unless message.is_a? Array
         | 
| 59 | 
            +
                    build_message_array(message).each do |l|
         | 
| 60 | 
            +
                      raw "PRIVMSG #{destination} :#{l}"
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
              
         | 
| 64 | 
            +
                  def action(destination, message)
         | 
| 65 | 
            +
                    msg(destination, "\001ACTION #{message}\001")
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
              
         | 
| 68 | 
            +
                  def raw(message)
         | 
| 69 | 
            +
                    puts "--> #{message}"
         | 
| 70 | 
            +
                    @conn.puts "#{message}\n"
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
              
         | 
| 73 | 
            +
                  def names(channel)
         | 
| 74 | 
            +
                    raw "NAMES #{channel}"
         | 
| 75 | 
            +
                    @conn.gets.split(":")[2].split(" ")
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
              
         | 
| 78 | 
            +
                  private
         | 
| 79 | 
            +
              
         | 
| 80 | 
            +
                  def main_loop
         | 
| 81 | 
            +
                    while true
         | 
| 82 | 
            +
                      ready = select([@conn])
         | 
| 83 | 
            +
                      next unless ready
         | 
| 84 | 
            +
                  
         | 
| 85 | 
            +
                      return if @conn.eof
         | 
| 86 | 
            +
                      s = @conn.gets
         | 
| 87 | 
            +
                      handle_server_input(s)
         | 
| 88 | 
            +
                		end
         | 
| 89 | 
            +
                  end
         | 
| 90 | 
            +
              
         | 
| 91 | 
            +
                  def handle_server_input(s)
         | 
| 92 | 
            +
                	  puts s
         | 
| 93 | 
            +
                
         | 
| 94 | 
            +
                    case s.strip
         | 
| 95 | 
            +
                      when /^PING :(.+)$/i
         | 
| 96 | 
            +
                        raw "PONG :#{$1}"
         | 
| 97 | 
            +
                      when /^:([-.0-9a-z]+)\s([0-9]+)\s(.+)\s(.*)$/i
         | 
| 98 | 
            +
                        handle_meta($1, $2.to_i, $4)
         | 
| 99 | 
            +
                      when /^:(.+?)!(.+?)@(.+?)\sPRIVMSG\s(.+)\s:(.+)$/i
         | 
| 100 | 
            +
                        message = Rubot::Irc::Message.new($1, $4 == @nick ? $1 : $4, $5)
         | 
| 101 | 
            +
                        # TODO add ability to pass events other than privmsg to dispatcher. ie, nick changes, parts, joins, quits, bans, etc, etc
         | 
| 102 | 
            +
                        @dispatcher.handle_message(self, message)
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
              
         | 
| 106 | 
            +
                  # performs the same command on each element in the given collection, separated by comma
         | 
| 107 | 
            +
                  def bulk_command(formatted_string, elements)
         | 
| 108 | 
            +
                    if elements.is_a? String
         | 
| 109 | 
            +
                      elements = elements.split(',')
         | 
| 110 | 
            +
                    end
         | 
| 111 | 
            +
                
         | 
| 112 | 
            +
                    elements.each do |e|
         | 
| 113 | 
            +
                      raw sprintf(formatted_string, e.to_s.strip)
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
              
         | 
| 117 | 
            +
                  def handle_meta(server, code, message)
         | 
| 118 | 
            +
                    case code
         | 
| 119 | 
            +
                    when ERR_NICK_IN_USE
         | 
| 120 | 
            +
                      if @nick == @alt_nick
         | 
| 121 | 
            +
                        puts "all nicks used, don't know how to name myself."
         | 
| 122 | 
            +
                        quit
         | 
| 123 | 
            +
                        exit!
         | 
| 124 | 
            +
                      end
         | 
| 125 | 
            +
                      change_nick @alt_nick
         | 
| 126 | 
            +
                      join @channels
         | 
| 127 | 
            +
                    when WELLCOME
         | 
| 128 | 
            +
                      @dispatcher.connected(self)
         | 
| 129 | 
            +
                      @is_connected = true
         | 
| 130 | 
            +
                      @connected_at = Time.now
         | 
| 131 | 
            +
                    end
         | 
| 132 | 
            +
                  end
         | 
| 133 | 
            +
              
         | 
| 134 | 
            +
                  def string_to_irc_lines(str)
         | 
| 135 | 
            +
                    str.split(" ").inject([""]) do |arr, word|
         | 
| 136 | 
            +
                      arr.push("") if arr.last.size > MAX_MESSAGE_LENGTH
         | 
| 137 | 
            +
                      arr.last << "#{word} "
         | 
| 138 | 
            +
                      arr
         | 
| 139 | 
            +
                    end.map(&:strip)
         | 
| 140 | 
            +
                  end
         | 
| 141 | 
            +
              
         | 
| 142 | 
            +
                  def build_message_array(arr)
         | 
| 143 | 
            +
                    arr.each_with_index.map do |message, index|
         | 
| 144 | 
            +
                      message.size > MAX_MESSAGE_LENGTH ? string_to_irc_lines(message) : arr[index]
         | 
| 145 | 
            +
                    end.flatten
         | 
| 146 | 
            +
                  end
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
              end
         | 
| 149 | 
            +
            end
         | 
    
        data/lib/rubot.rb
    ADDED
    
    
    
        metadata
    ADDED
    
    | @@ -0,0 +1,83 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification 
         | 
| 2 | 
            +
            name: rubot
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            +
              prerelease: false
         | 
| 5 | 
            +
              segments: 
         | 
| 6 | 
            +
              - 0
         | 
| 7 | 
            +
              - 0
         | 
| 8 | 
            +
              - 1
         | 
| 9 | 
            +
              version: 0.0.1
         | 
| 10 | 
            +
            platform: ruby
         | 
| 11 | 
            +
            authors: 
         | 
| 12 | 
            +
            - Chris Thorn
         | 
| 13 | 
            +
            autorequire: 
         | 
| 14 | 
            +
            bindir: bin
         | 
| 15 | 
            +
            cert_chain: []
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            date: 2010-03-04 00:00:00 -09:00
         | 
| 18 | 
            +
            default_executable: 
         | 
| 19 | 
            +
            dependencies: []
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            description: A Ruby Bot framwork for IRC featuring reloadable commands and listeners.
         | 
| 22 | 
            +
            email: thorncp@gmail.com
         | 
| 23 | 
            +
            executables: []
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            extensions: []
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            extra_rdoc_files: 
         | 
| 28 | 
            +
            - README
         | 
| 29 | 
            +
            files: 
         | 
| 30 | 
            +
            - lib/core/command.rb
         | 
| 31 | 
            +
            - lib/core/dispatcher.rb
         | 
| 32 | 
            +
            - lib/core/listener.rb
         | 
| 33 | 
            +
            - lib/core/runner.rb
         | 
| 34 | 
            +
            - lib/core.rb
         | 
| 35 | 
            +
            - lib/extensions/kernel.rb
         | 
| 36 | 
            +
            - lib/extensions/string.rb
         | 
| 37 | 
            +
            - lib/extensions.rb
         | 
| 38 | 
            +
            - lib/init/bundler.rb
         | 
| 39 | 
            +
            - lib/irc/constants.rb
         | 
| 40 | 
            +
            - lib/irc/message.rb
         | 
| 41 | 
            +
            - lib/irc/server.rb
         | 
| 42 | 
            +
            - lib/irc.rb
         | 
| 43 | 
            +
            - lib/rubot.rb
         | 
| 44 | 
            +
            - README
         | 
| 45 | 
            +
            - Rakefile
         | 
| 46 | 
            +
            has_rdoc: true
         | 
| 47 | 
            +
            homepage: http://github.com/thorncp/rubot
         | 
| 48 | 
            +
            licenses: []
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            post_install_message: 
         | 
| 51 | 
            +
            rdoc_options: 
         | 
| 52 | 
            +
            - --line-numbers
         | 
| 53 | 
            +
            - --inline-source
         | 
| 54 | 
            +
            - --title
         | 
| 55 | 
            +
            - NiftyGenerators
         | 
| 56 | 
            +
            - --main
         | 
| 57 | 
            +
            - README.rdoc
         | 
| 58 | 
            +
            require_paths: 
         | 
| 59 | 
            +
            - lib
         | 
| 60 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement 
         | 
| 61 | 
            +
              requirements: 
         | 
| 62 | 
            +
              - - ">="
         | 
| 63 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 64 | 
            +
                  segments: 
         | 
| 65 | 
            +
                  - 0
         | 
| 66 | 
            +
                  version: "0"
         | 
| 67 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement 
         | 
| 68 | 
            +
              requirements: 
         | 
| 69 | 
            +
              - - ">="
         | 
| 70 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 71 | 
            +
                  segments: 
         | 
| 72 | 
            +
                  - 1
         | 
| 73 | 
            +
                  - 2
         | 
| 74 | 
            +
                  version: "1.2"
         | 
| 75 | 
            +
            requirements: []
         | 
| 76 | 
            +
             | 
| 77 | 
            +
            rubyforge_project: 
         | 
| 78 | 
            +
            rubygems_version: 1.3.6
         | 
| 79 | 
            +
            signing_key: 
         | 
| 80 | 
            +
            specification_version: 3
         | 
| 81 | 
            +
            summary: A Ruby Bot framwork for IRC
         | 
| 82 | 
            +
            test_files: []
         | 
| 83 | 
            +
             |