module Potato module DAmn # Parser for dAmn packets. class Packet # Matches packets that should contain a body. BODIED = [ /^recv p?chat:(.*?)\n\nmsg main/, /^recv p?chat:(.*?)\n\naction main/, /^property p?chat:(.*?)\np=(topic|title|privclasses)/, /^recv p?chat:(.*?)\n\nkicked/, /^recv p?chat:(.*?)\n\nadmin show/, /^recv p?chat:(.*?)\n\nadmin privclass/, /^kicked/ ] # All existing tablumps. TABLUMPS = (%w[&b &/b &i &/i &u &/u &s &/s &sup &/sup &sub &/sub &code &/code &br &ul &/ul &ol &/ol &li &/li &bcode &/bcode &/a &/acro &/abbr &p &/p].map{|lump| lump + "\t" } + [/&emote\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t/, /&a\t([^\t]+)\t([^\t]*)\t/, /&link\t([^\t]+)\t&\t/, /&link\t([^\t]+)\t([^\t]+)\t&\t/, /&dev\t[^\t]\t([^\t]+)\t/, /&avatar\t([^\t]+)\t[0-9]+\t/, /&thumb\t([0-9]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t([^\t]+)\t/, /&img\t([^\t]+)\t([^\t]*)\t([^\t]*)\t/, /&iframe\t([^\t]+)\t([0-9%]*)\t([0-9%]*)\t&\/iframe\t/, /&acro\t([^\t]+)\t/, /&abbr\t([^\t]+)\t/, /^.+?<abbr title="(.+?)"><\/abbr>:/]). zip(["\x02", "\x0F", "\x16", "\x0F", "\x1F", "\x0F", "<s>", "</s>", "<sup>", "</sup>", "<sub>", "</sub>", "<code>", "</code>", "<br>", "<ul>", "</ul>", "<ol>", "</ol>", "<li>", "</li>", "", "", "</a>", "</acronym>", "", "<p>", "</p>", '\1', '<a href="\1" title="\2">', '\1', '\1 (\2)', ':dev\1:', ':icon\1:', ':thumb\1:', '<img src="\1" alt="\2" title="\3" />', '<iframe src="\1" width="\2" height="\3" />', '<acronym title="\1">', '', '\1:', '']) # @param [String] pkt the string received from the server to parse # @raise [PacketNilException] if the string is nil, meaning that the server connection has been closed def initialize pkt TABLUMPS.each{|find, replace| pkt.gsub!(find, replace)} @response = {:cmd => nil, :args => {}, :param => nil, :subpkts => [], :raw => pkt, :body => ""} chunks = pkt.split(/\n\n/) details = chunks.shift.split(/\n/) @response[:cmd], @response[:param] = *details.shift.split(" ") details.each{|dt| key, value = *dt.split(/\W/, 2) next if key.empty? @response[:args][key.to_sym] = CGI.unescapeHTML(value) } body = BODIED.map{|x|!!(pkt =~ x)}.include?(true) range = if body then chunks[0...-1] else chunks end range.each{|c| @response[:subpkts] << self.class.new(c) } if body && chunks[-1] if pkt =~ /\n\n$/ @response[:subpkts] << self.class.new(chunks[-1]) else @response[:body] = CGI.unescapeHTML(chunks[-1].chomp("\0").decode_entities) end end end # @return [String] the unformatted chat name def room if @response[:param] =~ /p?chat:/ then @response[:param].sub(/p?chat:/, "") else nil end end # @return [Boolean] whether the current packet has an error or not def ok? @response[:args][:e].nil? || @response[:args][:e] == "ok" end # @return [String] the current packet error def error @response[:args][:e] end # @yield [argument] iterates through each argument def each &blk @response[:args].each(&blk) end # @param [String] key the key to retrieve from the arguments # @return [String] the packet argument def [](key) @response[:args][key.to_sym] end # @return [Packet] the first subpacket def subpkt @response[:subpkts][0] end # Lookup @response entries, struct-style. def method_missing(m, *args, &blk) @response[m] end # Generates a human-readable representation of this packet # @return [String] def inspect str = "#<Potato::DAmn::Packet '#{@response[:cmd]} #{@response[:param]}'" str << ", args={#{@response[:args].map{|k,v| "#{k}: #{v}" }.join(", ")}}" if @response[:args].size > 0 str << ", body='#{@response[:body].rstrip}'" if @response[:body] str << ", subpackets=#{@response[:subpkts]}" if @response[:subpkts].size > 0 str << ">" str end end end end