#!/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