warchat 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/.gitignore +5 -0
- data/Gemfile +4 -0
- data/Rakefile +2 -0
- data/lib/warchat/byte_string.rb +8 -0
- data/lib/warchat/chat/chat_response.rb +20 -0
- data/lib/warchat/chat/client.rb +98 -0
- data/lib/warchat/chat/message.rb +35 -0
- data/lib/warchat/chat/presence.rb +22 -0
- data/lib/warchat/network/binary_reader.rb +46 -0
- data/lib/warchat/network/binary_writer.rb +86 -0
- data/lib/warchat/network/connection.rb +85 -0
- data/lib/warchat/network/request.rb +60 -0
- data/lib/warchat/network/response.rb +25 -0
- data/lib/warchat/network/session.rb +85 -0
- data/lib/warchat/srp/client.rb +138 -0
- data/lib/warchat/timer.rb +45 -0
- data/lib/warchat/version.rb +3 -0
- data/lib/warchat.rb +7 -0
- data/warchat.gemspec +24 -0
- metadata +115 -0
    
        data/.gitignore
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    
    
        data/Rakefile
    ADDED
    
    
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            module Warchat  module Chat
         | 
| 2 | 
            +
                module ChatResponse
         | 
| 3 | 
            +
                  def presence?
         | 
| 4 | 
            +
                    chat_type == 'wow_presence'
         | 
| 5 | 
            +
                  end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def ack?
         | 
| 8 | 
            +
                    chat_type == 'message_ack'
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def message? 
         | 
| 12 | 
            +
                    chat_type == 'wow_message'
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def chat_type
         | 
| 16 | 
            +
                    self['chatType']
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| @@ -0,0 +1,98 @@ | |
| 1 | 
            +
            module Warchat
         | 
| 2 | 
            +
              module Chat
         | 
| 3 | 
            +
                class Warchat::Chat::Client
         | 
| 4 | 
            +
                  attr_accessor :on_message,:on_presence,:on_logout,:on_fail,:on_establish
         | 
| 5 | 
            +
                  attr_accessor :on_message_afk,:on_message_dnd,:on_message_guild_chat,:on_message_motd,:on_message_officer_chat,:on_message_whisper,:on_chat_logout
         | 
| 6 | 
            +
                  attr_accessor :character_name,:character_realm
         | 
| 7 | 
            +
                  
         | 
| 8 | 
            +
                  attr_reader :session
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize
         | 
| 11 | 
            +
                    @session = Warchat::Network::Session.new
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    [:receive,:establish,:error].each do |m| 
         | 
| 14 | 
            +
                      session.send("on_#{m}=".to_sym, method("session_#{m}".to_sym)) 
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
                  
         | 
| 18 | 
            +
                  def start username, password
         | 
| 19 | 
            +
                    self.session.start(username,password)
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def session_error response
         | 
| 23 | 
            +
                    on_fail.andand.call(response["body"]) if response.target == "/chat-login"
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def session_establish response
         | 
| 27 | 
            +
                    puts 'Session Established'
         | 
| 28 | 
            +
                    on_establish.andand.call(response)
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def session_receive response
         | 
| 32 | 
            +
                    send(response.target.gsub('/','').underscore.to_sym,response)
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
                  
         | 
| 35 | 
            +
                  def login
         | 
| 36 | 
            +
                    request = Warchat::Network::Request.new("/chat-login",:options=>{:mature_filter=>'false'},:n=>character_name,:r=>character_realm)
         | 
| 37 | 
            +
                    session.send_request(request)
         | 
| 38 | 
            +
                    @timer = Warchat::Timer.new(120,&method(:keep_alive))
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def logout
         | 
| 42 | 
            +
                    request = Warchat::Network::Request.new('/chat-logout',:chatSessionId=>@chat_session_id)
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  def chat_logout response
         | 
| 46 | 
            +
                    puts 'Logged out of chat'
         | 
| 47 | 
            +
                    @timer.andand.stop
         | 
| 48 | 
            +
                    on_chat_logout.andand.call response
         | 
| 49 | 
            +
                    session.close
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def chat_login response
         | 
| 53 | 
            +
                    puts "Logged into chat"
         | 
| 54 | 
            +
                    @chat_session_id = response["chatSessionId"]
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  def chat response
         | 
| 58 | 
            +
                    response.extend(ChatResponse)
         | 
| 59 | 
            +
                    if response.ack?
         | 
| 60 | 
            +
                      puts("chat ack")
         | 
| 61 | 
            +
                    elsif response.message?
         | 
| 62 | 
            +
                      message = Message.new(response)
         | 
| 63 | 
            +
                      on_message.andand.call(message)
         | 
| 64 | 
            +
                      send("on_message_#{message.type}".to_sym).andand.call(message)
         | 
| 65 | 
            +
                    elsif response.presence?
         | 
| 66 | 
            +
                      puts
         | 
| 67 | 
            +
                      on_presence.andand.call(Presence.new(response))
         | 
| 68 | 
            +
                    else
         | 
| 69 | 
            +
                      puts "unhandled chat type: #{response.chat_type}"
         | 
| 70 | 
            +
                    end
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  def close
         | 
| 74 | 
            +
                    request = Warchat::Network::Request.new("/chat-logout",:chatSessionId=>@chat_session_id)
         | 
| 75 | 
            +
                    session.send_request(request)
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  def keep_alive
         | 
| 79 | 
            +
                    puts "Sending 'keep-alive'"
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    request = Warchat::Network::Request("/ah-mail")
         | 
| 82 | 
            +
                    request["r"] = realm
         | 
| 83 | 
            +
                    request["cn"] = name
         | 
| 84 | 
            +
                    session.send_request(request)
         | 
| 85 | 
            +
                  end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                  def message(msg, chat_type = Message.CHAT_MSG_TYPE_GUILD_CHAT)
         | 
| 88 | 
            +
                    request = Warchat::Network::Request.new("/chat-guild",:type=>chat_type,:body=>msg,:chatSessionId=>@chat_session_id)
         | 
| 89 | 
            +
                    session.send_request(request)
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                  def whisper(msg, char_id)
         | 
| 93 | 
            +
                    request = Warchat::Network::Request.new("/chat-whisper",:to=>char_id,:body=>msg,:chatSessionId=>@chat_session_id)
         | 
| 94 | 
            +
                    session.send_request(request)
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
                end
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
            end
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            module Warchat
         | 
| 2 | 
            +
              module Chat
         | 
| 3 | 
            +
                class Message
         | 
| 4 | 
            +
                  CHAT_ID_TYPE_CHARACTER = "character"
         | 
| 5 | 
            +
                  CHAT_ID_TYPE_GUILD = "guild"
         | 
| 6 | 
            +
                  CHAT_ID_TYPE_GUILD_MEMBER = "guild_member"
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  CHAT_MSG_TYPE_AFK = "afk"
         | 
| 9 | 
            +
                  CHAT_MSG_TYPE_DND = "dnd"
         | 
| 10 | 
            +
                  CHAT_MSG_TYPE_GUILD_CHAT = "guild_chat"
         | 
| 11 | 
            +
                  CHAT_MSG_TYPE_GUILD_MOTD = "motd"
         | 
| 12 | 
            +
                  CHAT_MSG_TYPE_OFFICER_CHAT = "officer_chat"
         | 
| 13 | 
            +
                  CHAT_MSG_TYPE_WHISPER = "whisper"
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  attr_reader :type,:body,:from_type,:character_id,:from
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def initialize response
         | 
| 18 | 
            +
                    @type = response["messageType"]
         | 
| 19 | 
            +
                    @body = response['body']
         | 
| 20 | 
            +
                    @from = response["from"]
         | 
| 21 | 
            +
                    @from_type = from["chatIdType"]
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    @character_id = from["characterId"]
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def character_name
         | 
| 27 | 
            +
                    @character_id.andand.split(':')[-2]
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def realm_id
         | 
| 31 | 
            +
                    @character_id.andand.split(':').last
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
              end
         | 
| 35 | 
            +
            end
         | 
| @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            module Warchat
         | 
| 2 | 
            +
              module Chat
         | 
| 3 | 
            +
                class Presence    
         | 
| 4 | 
            +
                  attr_reader :name,:character
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  def initialize response
         | 
| 7 | 
            +
                    @type = response["presenceType"];
         | 
| 8 | 
            +
                    @character = response["character"];
         | 
| 9 | 
            +
                    @name = character["n"];
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def offline?
         | 
| 13 | 
            +
                    @type.andand.include? 'offline'
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def type 
         | 
| 17 | 
            +
                    return 'unknown' unless @type
         | 
| 18 | 
            +
                    @type.split('_')[1..-1].join '_'
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
            end
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            module Warchat
         | 
| 2 | 
            +
              module Network
         | 
| 3 | 
            +
                class BinaryReader
         | 
| 4 | 
            +
                  def initialize socket
         | 
| 5 | 
            +
                    @socket = socket
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  TYPES = [:hash,:array,:int_32,:string,:string,:boolean,:int_64]
         | 
| 9 | 
            +
                  def parse_next *args  
         | 
| 10 | 
            +
                    send TYPES[byte.unpack('C').first-1]
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                  
         | 
| 13 | 
            +
                  def substream l
         | 
| 14 | 
            +
                    @socket.read l
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  def byte
         | 
| 18 | 
            +
                    substream 1
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def string
         | 
| 22 | 
            +
                    substream(int_32)
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def array
         | 
| 26 | 
            +
                    (1..(int_32)).map &method(:parse_next)
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  def hash
         | 
| 30 | 
            +
                    Hash[*(1..(int_32)).map do
         | 
| 31 | 
            +
                      [string,parse_next] 
         | 
| 32 | 
            +
                    end.flatten(1)]
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  {16=>'n',32=>'N',64=>'L_'}.each do |size,directive| 
         | 
| 36 | 
            +
                    define_method "int_#{size}".to_sym do 
         | 
| 37 | 
            +
                      substream(size/8).unpack(directive).first
         | 
| 38 | 
            +
                    end 
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def boolean
         | 
| 42 | 
            +
                    byte == "\001"
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         | 
| @@ -0,0 +1,86 @@ | |
| 1 | 
            +
            module Warchat
         | 
| 2 | 
            +
              module Network
         | 
| 3 | 
            +
                class BinaryWriter
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  attr_reader :stream
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def initialize stream
         | 
| 8 | 
            +
                    @stream = stream
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  def write obj
         | 
| 12 | 
            +
                    m = (obj.class.ancestors.map do |c| 
         | 
| 13 | 
            +
                      "write_#{File.basename c.name.underscore}".to_sym 
         | 
| 14 | 
            +
                    end.find(&method(:respond_to?)) or :write_string)
         | 
| 15 | 
            +
                    send(m,obj)
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def write_string obj
         | 
| 19 | 
            +
                    byte 5
         | 
| 20 | 
            +
                    string obj.to_s
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def write_integer obj
         | 
| 24 | 
            +
                    if obj < 2**32
         | 
| 25 | 
            +
                      byte 3
         | 
| 26 | 
            +
                      int_32 obj
         | 
| 27 | 
            +
                    else
         | 
| 28 | 
            +
                      byte 7
         | 
| 29 | 
            +
                      int_64 obj 
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def write_hash obj
         | 
| 34 | 
            +
                    byte 1
         | 
| 35 | 
            +
                    int_32 obj.size
         | 
| 36 | 
            +
                    obj.each do |k,v|
         | 
| 37 | 
            +
                      string(k.to_s)
         | 
| 38 | 
            +
                      write(v)
         | 
| 39 | 
            +
                    end
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  def write_array obj
         | 
| 43 | 
            +
                    byte 2
         | 
| 44 | 
            +
                    int_32 obj.size
         | 
| 45 | 
            +
                    obj.each do |v|
         | 
| 46 | 
            +
                      write(v)
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  def write_byte_string obj
         | 
| 51 | 
            +
                    byte 4
         | 
| 52 | 
            +
                    int_32 obj.length
         | 
| 53 | 
            +
                    bytes obj
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  def write_bool obj
         | 
| 57 | 
            +
                    byte 6
         | 
| 58 | 
            +
                    byte(obj ? 1 : 0)
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                  alias_method :write_true_class,:write_bool
         | 
| 61 | 
            +
                  alias_method :write_false_class,:write_bool
         | 
| 62 | 
            +
             | 
| 63 | 
            +
             | 
| 64 | 
            +
                  {16=>'n',32=>'N',64=>'L_'}.each do |size,directive|
         | 
| 65 | 
            +
                    define_method "int_#{size}".to_sym do |obj| 
         | 
| 66 | 
            +
                      @stream.print [obj].pack(directive)
         | 
| 67 | 
            +
                    end 
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                  def string obj
         | 
| 71 | 
            +
                    obj = obj.to_s
         | 
| 72 | 
            +
                    int_32 obj.length
         | 
| 73 | 
            +
                    @stream.print obj
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  def byte obj
         | 
| 77 | 
            +
                    obj = (obj.is_a? Integer and [obj].pack('C') or obj.to_s[0..0])
         | 
| 78 | 
            +
                    @stream.print obj
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  def bytes obj
         | 
| 82 | 
            +
                    @stream.print obj.to_s
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
                end  
         | 
| 85 | 
            +
              end
         | 
| 86 | 
            +
            end
         | 
| @@ -0,0 +1,85 @@ | |
| 1 | 
            +
            require 'socket'
         | 
| 2 | 
            +
            require 'thread'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Warchat
         | 
| 5 | 
            +
              module Network
         | 
| 6 | 
            +
                class Connection
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  attr_accessor :host,:port,:on_send,:on_receive,:on_close
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize *args
         | 
| 11 | 
            +
                    options = args.pop if args.last.is_a? Hash
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    self.host = (args.shift or "m.us.wowarmory.com")
         | 
| 14 | 
            +
                    self.port = (args.shift or 8780)
         | 
| 15 | 
            +
                    @closed = true
         | 
| 16 | 
            +
                    @queue = []
         | 
| 17 | 
            +
                    @mutex = Mutex.new
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                  def start
         | 
| 21 | 
            +
                    close
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    @closed = false;
         | 
| 24 | 
            +
                    @socket = TCPSocket.open(host,port)
         | 
| 25 | 
            +
                    @socket.sync = false
         | 
| 26 | 
            +
                    @request_thread = Thread.new &method(:handle_requests)
         | 
| 27 | 
            +
                    @response_thread = Thread.new &method(:handle_responses)
         | 
| 28 | 
            +
                    @request_thread['name'] = 'Request Thead'
         | 
| 29 | 
            +
                    @response_thread['name'] = 'Response Thread'
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def close reason = ""
         | 
| 33 | 
            +
                    return if is_closed?
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    on_close.andand.call(reason)
         | 
| 36 | 
            +
                    
         | 
| 37 | 
            +
                    @mutex.synchronize do 
         | 
| 38 | 
            +
                      @closed = true
         | 
| 39 | 
            +
                      @socket.close
         | 
| 40 | 
            +
                    end     
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  def is_closed? 
         | 
| 44 | 
            +
                    @closed or @socket.nil?
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  def send_request request
         | 
| 48 | 
            +
                    @mutex.synchronize do
         | 
| 49 | 
            +
                      @queue << request
         | 
| 50 | 
            +
                    end
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  def handle_responses
         | 
| 54 | 
            +
                    until is_closed?
         | 
| 55 | 
            +
                      response = Response.new(@socket)
         | 
| 56 | 
            +
                      on_receive.andand.call(response) unless is_closed?
         | 
| 57 | 
            +
                      sleep 0.01
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  rescue Exception => e
         | 
| 60 | 
            +
                    puts e.message
         | 
| 61 | 
            +
                    puts e.backtrace unless e.is_a? IOError
         | 
| 62 | 
            +
                  end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  def handle_requests
         | 
| 65 | 
            +
                    until is_closed?
         | 
| 66 | 
            +
                      @mutex.synchronize do
         | 
| 67 | 
            +
                        until @queue.empty?
         | 
| 68 | 
            +
                          
         | 
| 69 | 
            +
                          request = @queue.shift
         | 
| 70 | 
            +
                          unless is_closed?
         | 
| 71 | 
            +
                            request.stream @socket
         | 
| 72 | 
            +
                            @socket.flush
         | 
| 73 | 
            +
                            on_send.andand.call(request) 
         | 
| 74 | 
            +
                          end
         | 
| 75 | 
            +
                        end
         | 
| 76 | 
            +
                      end
         | 
| 77 | 
            +
                      sleep 0.01
         | 
| 78 | 
            +
                    end
         | 
| 79 | 
            +
                  rescue Exception => e
         | 
| 80 | 
            +
                    puts e.message
         | 
| 81 | 
            +
                    puts e.backtrace
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
            end
         | 
| @@ -0,0 +1,60 @@ | |
| 1 | 
            +
            module Warchat
         | 
| 2 | 
            +
              module Network
         | 
| 3 | 
            +
                class Request < Hash
         | 
| 4 | 
            +
                  class StringSocket
         | 
| 5 | 
            +
                    def initialize
         | 
| 6 | 
            +
                      @stream = ""
         | 
| 7 | 
            +
                    end
         | 
| 8 | 
            +
                    
         | 
| 9 | 
            +
                    def print obj
         | 
| 10 | 
            +
                      @stream << obj.to_s
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
                    
         | 
| 13 | 
            +
                    def length
         | 
| 14 | 
            +
                      @stream.length
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                    
         | 
| 17 | 
            +
                    def value
         | 
| 18 | 
            +
                      @stream
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
                  attr_reader :id,:target
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  def initialize target,*args
         | 
| 24 | 
            +
                    @target = target
         | 
| 25 | 
            +
                    @@request_count ||= -1
         | 
| 26 | 
            +
                    @@request_count += 1
         | 
| 27 | 
            +
                    @id = @@request_count
         | 
| 28 | 
            +
                    merge! args.shift if args.first.is_a? Hash
         | 
| 29 | 
            +
                    super *args
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  def stream socket
         | 
| 33 | 
            +
                    writer = BinaryWriter.new socket
         | 
| 34 | 
            +
                    writer.string(target)
         | 
| 35 | 
            +
                    writer.int_32(@id)
         | 
| 36 | 
            +
                    each do |k,v|
         | 
| 37 | 
            +
                      if [Hash,Array,Warchat::ByteString].none? &v.method(:is_a?)
         | 
| 38 | 
            +
                        writer.byte 5
         | 
| 39 | 
            +
                        writer.string k
         | 
| 40 | 
            +
                        writer.string v.to_s
         | 
| 41 | 
            +
                      else
         | 
| 42 | 
            +
                        writer.byte 4
         | 
| 43 | 
            +
                        writer.string k
         | 
| 44 | 
            +
                        tmp_socket = StringSocket.new
         | 
| 45 | 
            +
                        tmp = BinaryWriter.new(tmp_socket)
         | 
| 46 | 
            +
                        tmp.write v
         | 
| 47 | 
            +
                        writer.int_32(tmp_socket.length)        
         | 
| 48 | 
            +
                        writer.bytes(tmp_socket.value)
         | 
| 49 | 
            +
                      end
         | 
| 50 | 
            +
                      writer.byte 0xFF
         | 
| 51 | 
            +
                    end
         | 
| 52 | 
            +
                    writer.byte 0xFF
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                  
         | 
| 55 | 
            +
                  def inspect
         | 
| 56 | 
            +
                    "<#{self.class.name} id:#{id.inspect} target:#{target.inspect} #{super}>"
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end  
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
            end
         | 
| @@ -0,0 +1,25 @@ | |
| 1 | 
            +
            module Warchat
         | 
| 2 | 
            +
              module Network
         | 
| 3 | 
            +
                class Response < Hash
         | 
| 4 | 
            +
                  attr_accessor :length,:status,:target,:id
         | 
| 5 | 
            +
                  def initialize socket,*args
         | 
| 6 | 
            +
                    super *args
         | 
| 7 | 
            +
                    reader = BinaryReader.new socket
         | 
| 8 | 
            +
                    self.length = reader.int_32
         | 
| 9 | 
            +
                    self.status = reader.int_16
         | 
| 10 | 
            +
                    self.target = reader.string
         | 
| 11 | 
            +
                    self.id = reader.int_32
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    merge! reader.parse_next
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def ok?
         | 
| 17 | 
            +
                    status == 200
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                  
         | 
| 20 | 
            +
                  def inspect
         | 
| 21 | 
            +
                    "<#{self.class.name} id:#{id.inspect} target:#{target.inspect} status:#{status.inspect} #{super}>"
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
            end
         | 
| @@ -0,0 +1,85 @@ | |
| 1 | 
            +
            module Warchat
         | 
| 2 | 
            +
              module Network
         | 
| 3 | 
            +
                class Session
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                  attr_accessor :on_close,:on_receive,:on_establish,:on_error
         | 
| 6 | 
            +
             | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def initialize
         | 
| 9 | 
            +
                    @connection = Warchat::Network::Connection.new
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                    @connection.on_close = method(:connection_close)
         | 
| 12 | 
            +
                    @connection.on_receive = method(:connection_receive)
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def start account_name, password
         | 
| 16 | 
            +
                    @account_name = account_name
         | 
| 17 | 
            +
                    @password = password
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    @connection.start
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    @srp = Warchat::Srp::Client.new
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                    send_request Request.new('/authenticate1',
         | 
| 24 | 
            +
                      :screenRes => "PHONE_HIGH",
         | 
| 25 | 
            +
                      :device => "iPhone",
         | 
| 26 | 
            +
                      :deviceSystemVersion => "4.2.1",
         | 
| 27 | 
            +
                      :deviceModel => "iPhone3,1",
         | 
| 28 | 
            +
                      :appV => "3.0.0",
         | 
| 29 | 
            +
                      :deviceTime => Time.now.to_i,
         | 
| 30 | 
            +
                      :deviceTimeZoneId => "America/New_York",
         | 
| 31 | 
            +
                      :clientA => @srp.a_bytes,
         | 
| 32 | 
            +
                      :appId => "Armory",
         | 
| 33 | 
            +
                      :deviceId => '50862581c5dc46072050d51886cbae3149b3473c',
         | 
| 34 | 
            +
                      :emailAddress => account_name,
         | 
| 35 | 
            +
                      :deviceTimeZone => "-14400000",
         | 
| 36 | 
            +
                      :locale => "en_US"
         | 
| 37 | 
            +
                    )
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                  
         | 
| 40 | 
            +
                  def established? 
         | 
| 41 | 
            +
                    @established
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  def close reason=''
         | 
| 45 | 
            +
                    @connection.andand.close reason
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                  
         | 
| 48 | 
            +
                  def send_request request
         | 
| 49 | 
            +
                    @connection.andand.send_request(request)
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def stage_1 response
         | 
| 53 | 
            +
                    proof = @srp.auth1_proof(response["user"], @password[0..15].upcase, response["salt"], response["B"])
         | 
| 54 | 
            +
                    puts("sending client proof: 0x#{@srp.unpack_int(proof).to_s(16)} length: #{proof.length}")
         | 
| 55 | 
            +
                    send_request(Request.new("/authenticate2",:clientProof=>proof))
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  def stage_2 response
         | 
| 59 | 
            +
                    puts("sending client proof2")
         | 
| 60 | 
            +
                    send_request(Request.new("/authenticate2",:clientProof=>Warchat::ByteString.new("\000")))
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                  
         | 
| 63 | 
            +
                  def stage_3 response
         | 
| 64 | 
            +
                    @established = true 
         | 
| 65 | 
            +
                    on_establish.andand.call(response)
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def connection_receive response
         | 
| 69 | 
            +
                    puts response.inspect
         | 
| 70 | 
            +
                    m = "stage_#{response['stage']}".to_sym
         | 
| 71 | 
            +
                    if response.ok?
         | 
| 72 | 
            +
                      respond_to? m and send(m,response) or on_receive.andand.call(response)
         | 
| 73 | 
            +
                    else
         | 
| 74 | 
            +
                      error = response["body"]
         | 
| 75 | 
            +
                      puts("error: " + error)
         | 
| 76 | 
            +
                      close(error) if respond_to? m
         | 
| 77 | 
            +
                    end      
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  def connection_close reason
         | 
| 81 | 
            +
                    on_close.andand.call(reason)
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
              end
         | 
| 85 | 
            +
            end
         | 
| @@ -0,0 +1,138 @@ | |
| 1 | 
            +
            require 'digest'
         | 
| 2 | 
            +
            require 'openssl'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Warchat
         | 
| 5 | 
            +
              module Srp
         | 
| 6 | 
            +
                class Client
         | 
| 7 | 
            +
                  G = 2
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                  MODULUS_SIZE = 128
         | 
| 10 | 
            +
                  MODULUS = 0x86a7f6deeb306ce519770fe37d556f29944132554ded0bd68205e27f3231fef5a10108238a3150c59caf7b0b6478691c13a6acf5e1b5adafd4a943d4a21a142b800e8a55f8bfbac700eb77a7235ee5a609e350ea9fc19f10d921c2fa832e4461b7125d38d254a0be873dfc27858acb3f8b9f258461e4373bc3a6c2a9634324ab
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  SALT_SIZE = 32
         | 
| 13 | 
            +
                  HASH_SIZE = 32
         | 
| 14 | 
            +
                  SESSION_KEY_SIZE = HASH_SIZE * 2
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  attr_reader :b,:b_bytes
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def modpow(a, n, m) 
         | 
| 19 | 
            +
                    r = 1
         | 
| 20 | 
            +
                    while true
         | 
| 21 | 
            +
                      r = r * a % m if n[0] == 1
         | 
| 22 | 
            +
                      n >>= 1
         | 
| 23 | 
            +
                      return r if n == 0
         | 
| 24 | 
            +
                      a = a * a % m
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def pack_int int
         | 
| 29 | 
            +
                    Warchat::ByteString.new [int.to_s(16).reverse].pack('h*')
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
                  
         | 
| 32 | 
            +
                  def unpack_int str
         | 
| 33 | 
            +
                    str.unpack('h*').first.reverse.hex
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                  
         | 
| 36 | 
            +
                  def digest_int s
         | 
| 37 | 
            +
                    unpack_int(Digest::SHA2.digest(s))
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                  
         | 
| 40 | 
            +
                  def adjust_size str,length
         | 
| 41 | 
            +
                    str[0..(length-1)].ljust(length,"\000")
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                  def random_crypt
         | 
| 45 | 
            +
                    unpack_int(OpenSSL::Random.random_bytes(MODULUS_SIZE*2)) % MODULUS
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                  
         | 
| 48 | 
            +
                  def hN_xor_hG
         | 
| 49 | 
            +
                    # xor H(N) and H(G)
         | 
| 50 | 
            +
                    return @hN_xor_hG if @hN_xor_hG
         | 
| 51 | 
            +
                    hN = Digest::SHA2.digest(pack_int(MODULUS))
         | 
| 52 | 
            +
                    hG = Digest::SHA2.digest(pack_int(G))
         | 
| 53 | 
            +
                    
         | 
| 54 | 
            +
                    @hN_xor_hG = "\000" * HASH_SIZE
         | 
| 55 | 
            +
                    
         | 
| 56 | 
            +
                    HASH_SIZE.times do |i| @hN_xor_hG[i] = (hN[i] ^ hG[i]) end
         | 
| 57 | 
            +
                      
         | 
| 58 | 
            +
                    @hN_xor_hG
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                  def a
         | 
| 62 | 
            +
                    @a ||= random_crypt
         | 
| 63 | 
            +
                  end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                  def a_bytes 
         | 
| 66 | 
            +
                    @a_bytes ||= adjust_size(pack_int(modpow(G,a,MODULUS)),MODULUS_SIZE)
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  def k
         | 
| 70 | 
            +
                    @k ||= digest_int(pack_int(MODULUS)+pack_int(G))
         | 
| 71 | 
            +
                  end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  def u 
         | 
| 74 | 
            +
                    # H(A | B)
         | 
| 75 | 
            +
                    @u ||= digest_int(a_bytes+b_bytes)
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  def x
         | 
| 79 | 
            +
                    # H(salt | H(userHash | : | sessionPassword))
         | 
| 80 | 
            +
                    @x ||= digest_int(@salt+Digest::SHA2.digest(@user+":"+@password))
         | 
| 81 | 
            +
                  end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                  def s
         | 
| 84 | 
            +
                    # (B - k * G^x) ^ (a + u * x)
         | 
| 85 | 
            +
                    return modpow(b - k * modpow(G, x, MODULUS), a + u * x, MODULUS)
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  def s_bytes
         | 
| 89 | 
            +
                    @s_bytes ||= adjust_size(pack_int(s),(MODULUS_SIZE))
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
             | 
| 93 | 
            +
                  def auth1_proof user, password, salt, b_bytes
         | 
| 94 | 
            +
                    @b = unpack_int b_bytes
         | 
| 95 | 
            +
                    @b_bytes = adjust_size(b_bytes,MODULUS_SIZE)
         | 
| 96 | 
            +
                    @salt = adjust_size(salt,SALT_SIZE)
         | 
| 97 | 
            +
                    @user = user
         | 
| 98 | 
            +
                    @password = password
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    # hash this to generate client proof, H(H(N) xor H(G) | H(userHash) | salt | A | B | K)
         | 
| 101 | 
            +
                    Warchat::ByteString.new Digest::SHA2.digest(hN_xor_hG+Digest::SHA2.digest(@user)+salt+a_bytes+@b_bytes+session_key)
         | 
| 102 | 
            +
                  end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                  def session_key
         | 
| 105 | 
            +
                    return @session_key if @session_key
         | 
| 106 | 
            +
                    @session_key = "\000" * SESSION_KEY_SIZE
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                    l = s_bytes.length
         | 
| 109 | 
            +
                    offset = (l.odd? and 1 or 0)
         | 
| 110 | 
            +
                    l -= offset
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                    l = [l/2,MODULUS_SIZE].min
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    temp = ''
         | 
| 115 | 
            +
                    l.times do |i|
         | 
| 116 | 
            +
                      temp << s_bytes[i*2+offset]
         | 
| 117 | 
            +
                    end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    hash = Digest::SHA2.digest(temp)
         | 
| 120 | 
            +
                    HASH_SIZE.times do |i|
         | 
| 121 | 
            +
                      @session_key[i*2] = hash[i]
         | 
| 122 | 
            +
                    end
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                    temp = ''
         | 
| 125 | 
            +
                    l.times do |i|
         | 
| 126 | 
            +
                      temp << s_bytes[i*2+offset+1]
         | 
| 127 | 
            +
                    end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                    hash = Digest::SHA2.digest(temp)
         | 
| 130 | 
            +
                    HASH_SIZE.times do |i|
         | 
| 131 | 
            +
                      @session_key[i*2+1] = hash[i]
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                    @session_key
         | 
| 135 | 
            +
                  end
         | 
| 136 | 
            +
                end
         | 
| 137 | 
            +
              end
         | 
| 138 | 
            +
            end
         | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            require 'monitor'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Warchat  class Timer
         | 
| 4 | 
            +
                def initialize(interval, &handler)
         | 
| 5 | 
            +
                  
         | 
| 6 | 
            +
                  raise ArgumentError, "Illegal interval" if interval < 0
         | 
| 7 | 
            +
                  @interval = interval
         | 
| 8 | 
            +
                  extend MonitorMixin
         | 
| 9 | 
            +
                  @run = true
         | 
| 10 | 
            +
                  @th = Thread.new do
         | 
| 11 | 
            +
                    while run?
         | 
| 12 | 
            +
                      do_sleep and handler.call rescue nil
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                  @th['name'] = 'Timer'
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def stop
         | 
| 19 | 
            +
                  synchronize do
         | 
| 20 | 
            +
                    @run = false
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                  sleeping? and @th.kill or @th.join 
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                private
         | 
| 26 | 
            +
                
         | 
| 27 | 
            +
                def sleeping?
         | 
| 28 | 
            +
                  synchronize do @sleeping end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
                
         | 
| 31 | 
            +
                def do_sleep
         | 
| 32 | 
            +
                  synchronize do @sleeping = true end
         | 
| 33 | 
            +
                  sleep(@interval)
         | 
| 34 | 
            +
                  synchronize do @sleeping = false end
         | 
| 35 | 
            +
                rescue
         | 
| 36 | 
            +
                  nil
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def run?
         | 
| 40 | 
            +
                  synchronize do
         | 
| 41 | 
            +
                    @run
         | 
| 42 | 
            +
                  end
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
              end
         | 
| 45 | 
            +
            end
         | 
    
        data/lib/warchat.rb
    ADDED
    
    
    
        data/warchat.gemspec
    ADDED
    
    | @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            +
            $:.push File.expand_path("../lib", __FILE__)
         | 
| 3 | 
            +
            require "warchat/version"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Gem::Specification.new do |s|
         | 
| 6 | 
            +
              s.name        = "warchat"
         | 
| 7 | 
            +
              s.version     = Warchat::VERSION
         | 
| 8 | 
            +
              s.platform    = Gem::Platform::RUBY
         | 
| 9 | 
            +
              s.authors     = ["Zachary Gavin"]
         | 
| 10 | 
            +
              s.email       = ["zgavin@gmail.com"]
         | 
| 11 | 
            +
              s.homepage    = "http://www.github.com/zgavin/warchat"
         | 
| 12 | 
            +
              s.summary     = %q{A simple interface to World of Warcraft Remote Guild Chat based off Eike Siewertsen's C# implementation}
         | 
| 13 | 
            +
              s.description = %q{A simple interface to World of Warcraft Remote Guild Chat in Ruby.  Supports whispers, guild chat, officer chat, and presence notifications.  Many thanks to Eike Siewertsen (https://github.com/fry) for his work deciphering the protocol.  }
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              s.rubyforge_project = "warchat"
         | 
| 16 | 
            +
              
         | 
| 17 | 
            +
              s.add_dependency('activesupport','>= 3.0.0')
         | 
| 18 | 
            +
              s.add_dependency('andand')
         | 
| 19 | 
            +
              
         | 
| 20 | 
            +
              s.files         = `git ls-files`.split("\n")
         | 
| 21 | 
            +
              s.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n")
         | 
| 22 | 
            +
              s.executables   = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
         | 
| 23 | 
            +
              s.require_paths = ["lib"]
         | 
| 24 | 
            +
            end
         | 
    
        metadata
    ADDED
    
    | @@ -0,0 +1,115 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification 
         | 
| 2 | 
            +
            name: warchat
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            +
              hash: 29
         | 
| 5 | 
            +
              prerelease: 
         | 
| 6 | 
            +
              segments: 
         | 
| 7 | 
            +
              - 0
         | 
| 8 | 
            +
              - 0
         | 
| 9 | 
            +
              - 1
         | 
| 10 | 
            +
              version: 0.0.1
         | 
| 11 | 
            +
            platform: ruby
         | 
| 12 | 
            +
            authors: 
         | 
| 13 | 
            +
            - Zachary Gavin
         | 
| 14 | 
            +
            autorequire: 
         | 
| 15 | 
            +
            bindir: bin
         | 
| 16 | 
            +
            cert_chain: []
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            date: 2011-05-16 00:00:00 -04:00
         | 
| 19 | 
            +
            default_executable: 
         | 
| 20 | 
            +
            dependencies: 
         | 
| 21 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 22 | 
            +
              name: activesupport
         | 
| 23 | 
            +
              prerelease: false
         | 
| 24 | 
            +
              requirement: &id001 !ruby/object:Gem::Requirement 
         | 
| 25 | 
            +
                none: false
         | 
| 26 | 
            +
                requirements: 
         | 
| 27 | 
            +
                - - ">="
         | 
| 28 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 29 | 
            +
                    hash: 7
         | 
| 30 | 
            +
                    segments: 
         | 
| 31 | 
            +
                    - 3
         | 
| 32 | 
            +
                    - 0
         | 
| 33 | 
            +
                    - 0
         | 
| 34 | 
            +
                    version: 3.0.0
         | 
| 35 | 
            +
              type: :runtime
         | 
| 36 | 
            +
              version_requirements: *id001
         | 
| 37 | 
            +
            - !ruby/object:Gem::Dependency 
         | 
| 38 | 
            +
              name: andand
         | 
| 39 | 
            +
              prerelease: false
         | 
| 40 | 
            +
              requirement: &id002 !ruby/object:Gem::Requirement 
         | 
| 41 | 
            +
                none: false
         | 
| 42 | 
            +
                requirements: 
         | 
| 43 | 
            +
                - - ">="
         | 
| 44 | 
            +
                  - !ruby/object:Gem::Version 
         | 
| 45 | 
            +
                    hash: 3
         | 
| 46 | 
            +
                    segments: 
         | 
| 47 | 
            +
                    - 0
         | 
| 48 | 
            +
                    version: "0"
         | 
| 49 | 
            +
              type: :runtime
         | 
| 50 | 
            +
              version_requirements: *id002
         | 
| 51 | 
            +
            description: "A simple interface to World of Warcraft Remote Guild Chat in Ruby.  Supports whispers, guild chat, officer chat, and presence notifications.  Many thanks to Eike Siewertsen (https://github.com/fry) for his work deciphering the protocol.  "
         | 
| 52 | 
            +
            email: 
         | 
| 53 | 
            +
            - zgavin@gmail.com
         | 
| 54 | 
            +
            executables: []
         | 
| 55 | 
            +
             | 
| 56 | 
            +
            extensions: []
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            extra_rdoc_files: []
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            files: 
         | 
| 61 | 
            +
            - .gitignore
         | 
| 62 | 
            +
            - Gemfile
         | 
| 63 | 
            +
            - Rakefile
         | 
| 64 | 
            +
            - lib/warchat.rb
         | 
| 65 | 
            +
            - lib/warchat/byte_string.rb
         | 
| 66 | 
            +
            - lib/warchat/chat/chat_response.rb
         | 
| 67 | 
            +
            - lib/warchat/chat/client.rb
         | 
| 68 | 
            +
            - lib/warchat/chat/message.rb
         | 
| 69 | 
            +
            - lib/warchat/chat/presence.rb
         | 
| 70 | 
            +
            - lib/warchat/network/binary_reader.rb
         | 
| 71 | 
            +
            - lib/warchat/network/binary_writer.rb
         | 
| 72 | 
            +
            - lib/warchat/network/connection.rb
         | 
| 73 | 
            +
            - lib/warchat/network/request.rb
         | 
| 74 | 
            +
            - lib/warchat/network/response.rb
         | 
| 75 | 
            +
            - lib/warchat/network/session.rb
         | 
| 76 | 
            +
            - lib/warchat/srp/client.rb
         | 
| 77 | 
            +
            - lib/warchat/timer.rb
         | 
| 78 | 
            +
            - lib/warchat/version.rb
         | 
| 79 | 
            +
            - warchat.gemspec
         | 
| 80 | 
            +
            has_rdoc: true
         | 
| 81 | 
            +
            homepage: http://www.github.com/zgavin/warchat
         | 
| 82 | 
            +
            licenses: []
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            post_install_message: 
         | 
| 85 | 
            +
            rdoc_options: []
         | 
| 86 | 
            +
             | 
| 87 | 
            +
            require_paths: 
         | 
| 88 | 
            +
            - lib
         | 
| 89 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement 
         | 
| 90 | 
            +
              none: false
         | 
| 91 | 
            +
              requirements: 
         | 
| 92 | 
            +
              - - ">="
         | 
| 93 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 94 | 
            +
                  hash: 3
         | 
| 95 | 
            +
                  segments: 
         | 
| 96 | 
            +
                  - 0
         | 
| 97 | 
            +
                  version: "0"
         | 
| 98 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement 
         | 
| 99 | 
            +
              none: false
         | 
| 100 | 
            +
              requirements: 
         | 
| 101 | 
            +
              - - ">="
         | 
| 102 | 
            +
                - !ruby/object:Gem::Version 
         | 
| 103 | 
            +
                  hash: 3
         | 
| 104 | 
            +
                  segments: 
         | 
| 105 | 
            +
                  - 0
         | 
| 106 | 
            +
                  version: "0"
         | 
| 107 | 
            +
            requirements: []
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            rubyforge_project: warchat
         | 
| 110 | 
            +
            rubygems_version: 1.5.0
         | 
| 111 | 
            +
            signing_key: 
         | 
| 112 | 
            +
            specification_version: 3
         | 
| 113 | 
            +
            summary: A simple interface to World of Warcraft Remote Guild Chat based off Eike Siewertsen's C# implementation
         | 
| 114 | 
            +
            test_files: []
         | 
| 115 | 
            +
             |