############################################################################ ### FRuby Client 1.0 by Retnur ### A big thanks goes out to Kiasyn. ############################################################################ # Copyright (c) 2009, Jeffrey Heath Basurto <bigng22@gmail.com> # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ############################################################################ ### A list of all known IMC channels. $imc_channel_list = {:icode=>["Server02:icode", "#R"], :iruby=>["Server02:iRuby", "#R"], :igame=>["Server02:igame", "#G"], :ichat=>["Server01:ichat", "#P"], :inews=>["Server02:inews", "#W"], :imudnews=>["Server02:imudnews", "#Y"], :ibuild=>["Server01:ibuild", "#r"], :imusic=>["Server02:imusic", "#p"], :admin=>["Server01:admin", "#w"]} ### String extensions ### class String def encapsulate s = self s = s.reverse s.concat '"' s = s.reverse s.concat '"' return s end end ### Net::Telnet extensions ### class Net::Telnet def waitfor(options) # :yield: recvdata time_out = @options["Timeout"] waittime = @options["Waittime"] if options.kind_of?(Hash) prompt = if options.has_key?("Match") options["Match"] elsif options.has_key?("Prompt") options["Prompt"] elsif options.has_key?("String") Regexp.new( Regexp.quote(options["String"]) ) end time_out = options["Timeout"] if options.has_key?("Timeout") waittime = options["Waittime"] if options.has_key?("Waittime") else prompt = options end if time_out == false time_out = nil end line = '' buf = '' rest = '' until(not IO::select([@sock], nil, nil, waittime)) unless IO::select([@sock], nil, nil, time_out) raise TimeoutError, "timed out while waiting for more data" end begin c = @sock.readpartial(1024 * 1024) @dumplog.log_dump('<', c) if @options.has_key?("Dump_log") if @options["Telnetmode"] c = rest + c if Integer(c.rindex(/#{IAC}#{SE}/no)) < Integer(c.rindex(/#{IAC}#{SB}/no)) buf = preprocess(c[0 ... c.rindex(/#{IAC}#{SB}/no)]) rest = c[c.rindex(/#{IAC}#{SB}/no) .. -1] elsif pt = c.rindex(/#{IAC}[^#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]?\z/no) || c.rindex(/\r\z/no) buf = preprocess(c[0 ... pt]) rest = c[pt .. -1] else buf = preprocess(c) rest = '' end else # Not Telnetmode. # # We cannot use preprocess() on this data, because that # method makes some Telnetmode-specific assumptions. buf = rest + c rest = '' unless @options["Binmode"] if pt = buf.rindex(/\r\z/no) buf = buf[0 ... pt] rest = buf[pt .. -1] end buf.gsub!(/#{EOL}/no, "\n") end end @log.print(buf) if @options.has_key?("Output_log") line += buf yield buf if block_given? rescue EOFError # End of file reached if line == '' line = nil yield nil if block_given? end break end end line end end ### Look for usage below for examples. ### You should only need to instantiate this a single time. class IMCclient ### This is called when IMCclient is instantiated. def initialize(name, pw) @connection = Net::Telnet::new( "Host" => "74.207.247.83", "Port" => 5000, "Telnetmode" => false, "Prompt" => /\n/) # Start authenticating. Will autosetup if IMC server does not have configuration. @connection.puts "PW #{name} #{pw} version=2 autosetup #{pw}2" # Set the sequence, which is a number associated with each packet @sequence = Time.now.to_i @myname = name @chat_log = [] @@client_thread = Thread.new do loop do $imclock.synchronize do $imcclient.accept_data end sleep 0.2 end end end ### call this in some event. It really should called at least twice a second. ### This continually checks for incoming packets def accept_data # empty string s= "" ### read from connection and put it in s begin @connection.waitfor({"Timeout"=>false, "Waittime"=>0}) do |c| break if c == nil s << c end rescue #$imcclient = nil end ### If s is empty we return. return if s.chomp.empty? ### Search for new packets line by line and process. while ( (line = s.pop_line) != nil ) ### Does something with each line. Probably a chat message. handle_server_input line end end ### Handles raw input ### Directs towards packets. def handle_server_input(s) case s.strip #On initial client connection: #SERVER Sends: autosetup <servername> accept <networkname> (SHA256-SET) when /^autosetup (\S+) accept (\S+)$/ log :debug, "Autosetup complete. Connected to #{$1} on network #{$2}" when /^PW Server\d+ domyhomework version=2 (\S+)$/i log :debug, "IMC Authentication complete" send_isalive when /^(\S+) \d+ \S+ (\S+) (\S+)$/i handle_packet( $1, $2, $3 ) when /^(\S+) \d+ \S+ (\S+) (\S+) (.*)$/i handle_packet( $1, $2, $3, $4 ) else # meh end #You@YourMUD 1234567890 YourMUD tell Dude@SomeMUD text="Having fun?" end ### Processes all packets and directs it. def handle_packet( sender, type, target, data=nil ) if data != nil and data.is_a?( String ) then new_data = Hash.new data.strip! data = StringScanner.new(data) while !data.eos? key = data.scan(/\w+/) ### grab a key data.skip(/=/) ### skip the = if data.peek(1).eql?('"') val = data.scan(/"([^"\\]*(\\.[^"\\]*)*)"/) val.slice!(0) val.slice!(-1) else val = "" while (!data.eos? && !data.peek(1).eql?(' ')) val << data.getch end end data.skip(/\s+/) new_data[key] = val end data = new_data end # return if sender.include?("@#{@myname}") # return if data != nil and data.include? 'sender' and data['sender'].include? "@#{@myname}" #You@YourMUD 1234567890 YourMUD tell Dude@SomeMUD text="Having fun?" case type when "keepalive-request" send_isalive when "tell" $dplayer_list.each do |dplayer| if dplayer.name == target.split('@')[0] dplayer.text_to_player("#R#{sender} tells you, '" + data['text'] + "#R'#n" + ENDL) break end $imcclient.private_send("FakeCoralMud", *sender.split('@'), "#{target.split('@')[0]} was not found on CoralMUD.") end when "ice-msg-b" cn = data['channel'].split(":") cn[1].strip! $dplayer_list.each do |dPlayer| if dPlayer.channel_flags[cn[1].to_sym] != nil next end chan_data = $imc_channel_list[cn[1].to_sym] if chan_data != nil col = chan_data[1] else col = "#D" end data['text'].gsub!('#', '##') data['text'].gsub!('\"', '"') dPlayer.text_to_player "#{col}(#{cn[1]}) #{sender} says, '#{data['text']}'" + ENDL log_chat Time.now.strftime("#{col}[%I:%M%p]"), cn[1], "(#{cn[1]}) #{sender} says, '#{data['text']}'\r\n" end when "ice-msg-p" cn = data['channel'].split(":") $dplayer_list.each do |dPlayer| if dPlayer.channel_flags[cn[1].to_sym] != nil next end dPlayer.text_to_player "#p(#{cn[1]}) #{data['realfrom']} says, '#{data['text']}'#n" + ENDL end when "ice-msg-r" cn = data['channel'].split(":") $dplayer_list.each do |dPlayer| if dPlayer.channel_flags[cn[1].to_sym] != nil next end dPlayer.text_to_player "#p(#{cn[1]}) #{data['realfrom']} says, '#{data['text']}'#n" + ENDL end end end ### Sends a packet in the right format. def packet_send( sender, type, target, destination, data) data_out = "" if data.is_a?( Hash ) then hash = data.to_hash hash.each do |k, v| v.to_s.gsub!('"', '\"') if v.to_s.include? " " then v = v.encapsulate end data_out.concat "#{k}=#{v} " end end packet = "#{sender}@#{@myname} #{@sequence} #{@myname} #{type} #{target}@#{destination} #{data_out}" @sequence += 1 @connection.puts packet end def private_send(sender, target, destination, message, type='tell') packet_send(sender, type, target, destination, {:text=>message}) end ### This formats for a specific channel. ### channel_send "Retnur", Server01:ichat, "example string" def channel_send( sender, channel, message, type='ice-msg-b') packet_send( sender, type, '*', '*', {:channel => channel, :text => message, :emote => 0, :echo => 1 }) end ### Sends info to IMC server about mud. ### host is your muds address, port is its port. url is your web site. ### This isn't required. def send_isalive packet_send( "*", "is-alive", "*", "*", { :versionid => "CoralMud Client1.0", :url => "", :host => "", :port => 0} ) end ### Shut the IMCclient down. def shutdown log :debug, "IMC Connection closed." @connection.close end # keeps a chatlog of the last 50 events. Not per channel. def log_chat time, chan, string @chat_log << [time, chan, string] #@chat_log.delete @chat_log[-1] if @chat_log.length > 50 end def ret_log chan s = "_#{chan}_Logs_________________________________________________________\r\n" @chat_log.each_index do |i| if @chat_log[i][1].include? chan s << (@chat_log[i][0] + @chat_log[i][2]) end end return s end end ### Beginning of actual execution. ### You ned to change this name and password. ### create our client and a lock for it. $imcclient = IMCclient.new("FakeCoralMud", "domyhomework") $imclock = Mutex.new