#!/usr/local/bin/ruby ############################################################################ ### FRuby Client 1.0 by Retnur ### This code is released under GNU General Public License (GPL) version 2. ### 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. ############################################################################ require "net/telnet.rb" require 'thread' require "eventmachine" # For standalone colors. # Ripped out of Rocketmud $ansi_table = [ [ ?d, "30", false ], [ ?D, "30", true ], [ ?r, "31", false ], [ ?R, "31", true ], [ ?g, "32", false ], [ ?G, "32", true ], [ ?y, "33", false ], [ ?Y, "33", true ], [ ?b, "34", false ], [ ?B, "34", true ], [ ?p, "35", false ], [ ?P, "35", true ], [ ?c, "36", false ], [ ?C, "36", true ], [ ?w, "37", false ], [ ?W, "37", true ] ] ### Ripped out of RocketMUD # takes a string and returns a string with color codes added. def render_color txt output = "" underline = bold = false last = -1 i = 0 while i < txt.size if txt[i] == ?# i += 1 # toggle underline on/off with #u if txt[i] == ?u i += 1 if underline underline = false output << "\e[0" output << ';1' if bold if last != -1 output << ';' output << $ansi_table[last][1] end output << 'm' else underline = true output << "\e[4m" end # parse ## to # elsif txt[i] == ?# i += 1 output << '#' # #n should clear all tags elsif txt[i] == ?n i += 1 if last != -1 || underline || bold underline = false bold = false output << "\e[0m" end last = -1 # check for valid color tag and parse else validTag = false $ansi_table.size.times do |j| if txt[i] == $ansi_table[j][0] validTag = true # we only add the color sequence if it's needed if last != j cSequence = false # escape sequence output << "\e[" # remember if a color change is needed cSequence = true if last == -1 || last / 2 != j / 2 # handle font boldness if bold && $ansi_table[j][2] == false output << '0' bold = false output << ";4" if underline # changing from bold wipes the old color output << ';' cSequence = true elsif !bold && $ansi_table[j][2] == true output << '1' bold = true output << ';' if cSequence end # add color sequence if needed output << $ansi_table[j][1] if cSequence output << 'm' end # remember the last color last = j end end # it wasn't a valid color tag if !validTag output << '#' else i += 1 end end else output << txt[i].chr i += 1 end end # while # and terminate it with the standard color if last != -1 || underline || bold output << "\e[0m" end return output end ### String extensions ### class String # Return the first line in a string def pop_line if (pos = index("\n")) != nil then return slice!(0..pos).chomp end nil end 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 read_from_telnet # :yield: recvdata time_out = @options["Timeout"] waittime = @options["Waittime"] if time_out == false time_out = nil end line = '' buf = '' rest = '' until(1 == 1 and 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") buf = c buf.gsub!(/#{EOL}/no, "\n") unless @options["Binmode"] rest = '' @log.print(buf) if @options.has_key?("Output_log") line += buf yield buf if block_given? rescue # 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" => "server01.mudbytes.net", "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 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 @connection.read_from_telnet do |c| break if c == nil s << c 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) # <sender>@<origin> <sequence> <route> <packet-type> <target>@<destination> <data...> case s.strip #On initial client connection: #SERVER Sends: autosetup <servername> accept <networkname> (SHA256-SET) when /^autosetup (\S+) accept (\S+)$/ puts "Autosetup complete. Connected to #{$1} on network #{$2}\n" when /^PW Server\d+ homework2 version=2 (\S+)$/i puts "IMC Authentication complete\n" 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 puts "Not found!" end 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.scan( /(\S+)=([^"\s]+)/) do |k,v| new_data[k] = v end data.scan( /(\S+)="([^"\\]*(\\.[^"\\]*)*)"/) do |k,v| new_data[k] = v end data = new_data end return if sender.include?("@#{@myname}") return if data != nil and data.include? 'sender' and data['sender'].include? "@#{@myname}" case type when "keepalive-request" send_isalive when "ice-msg-b" puts render_color("#R[#Y#{data['channel']}#R] #C#{sender}: #c#{data['text']}") 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| 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 ### This formats for a specific channel. ### channel_send "Retnur", Server01:ichat, "example string" def channel_send( sender, channel, message ) packet_send( sender, 'ice-msg-b', '*', '*', {: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 => "FRuby Client1.0", :url => "", :host => "", :port => 0} ) end ### Shut the IMCclient down. def shutdown puts "Connection closed." @connection.close end end ### Beginning of actual execution. ### You ned to change this name and password. ### create our client and a lock for it. client = IMCclient.new("Rstandalone", "mypass") imclock = Mutex.new ### Run EventMachine in a new thread. ### This lets it autonomously receive data ### even when the application stops waiting on user input. t = Thread.new() do EventMachine::run do EM::add_periodic_timer(0.01) do ### takes control of the lock for our IMC client. imclock.synchronize do client.accept_data end end end end sleep 1 ### Get your name. puts "Type quit to exit." print "What is your name? " name = gets name = name.chomp ### This loop for all processing of input. ### accept input forever loop do print ">>>\n" ### Gets a full line of input, and stops this thread while we wait. s = gets s = s.strip next if s.empty? ### Type quit to end the stand alone client. break if s =~ /quit|shutdown/i ### Syncs our thread just in case we do something that isn't totally thread safe there. imclock.synchronize do client.channel_send("#{name.capitalize}", "Server01:ichat", s) puts render_color("#R[#YServer01:ichat#R] #C#{name.capitalize}:#c #{s}#n") end end client.shutdown