Hello there !

I was trying to code a scriptable MUD client (following the RubyQuiz
challenge). For the moment, I implemented a scriptable telnet which
works fine with my mail server and also with the test server provided by
James Edward Gray II on the RubyQuiz website. However, when I tried it
on a real MUD server, it just stopped receiving data after a few lines
(I tested it on imperian.com:23). The connection is not lost however, as
I can send commands (simple: if you can send "3", it will exit your
session :) ).

I join my code here, all the connection stuff are in the two methods
"connect" and "idle" of the MUD class.

Thanks,

Pierre

Code :


======8<============8<===========8<===========8<============8<=========
#!/usr/bin/ruby -w

require "socket"

class MUD

  attr_reader :url, :port, :prefix, :io

  attr_reader :commands, :answers

  def initialize(url, port)
    @url = url
    @port = port
    @answers = MUDFilters.new
    @commands = MUDFilters.new
    @prefix = "!"
    @io = MUD::Io.new
    register_defaults
  end

  def connect
    @io.send ||= lambda { |line| @io.server_socket.write line }
    @io.report ||= lambda { |line| $stdout.write line }
    @socket = TCPSocket.new(@url, @port)
    @io.report["Connected to #{@url}:#{@port}\n"]
    @io.server_socket = @socket
    @io.freeze
    @url.freeze
    @prefix.freeze
    @thread = Thread.new do
      idle
    end
  end

  def disconnect
    @io.server_socket.close unless @io.server_socket.closed?
    @thread.join
    puts "Disconnected ..."
  end

  def idle
    f = File.new("log_server", "wb")
    begin
      while line = @io.server_socket.gets
        f.write line
        process_answer line
      end
    rescue
    end
    @io.server_socket.close unless @io.server_socket.closed?
    return 0
  end

  def process_command(line)
    process(@commands, line)
  end

  alias_method :send, :process_command

  def process_answer(line)
    process(@answers, line)
  end

  def add_command(regexp=//, priority=0, &block)
    register(@commands, Rule.new(regexp, priority, &block))
  end

  def remove_command(id)
    unregister(@commands, id)
  end

  def add_answer(regexp=//, priority=0, &block)
    register(@answers, Rule.new(regexp, priority, &block))
  end

  def remove_answer(id)
    unregister(@answers, id)
  end

protected

  def register_defaults
    # Echo the server commands if nothing else
    add_answer do |io,line|
      io.report[line]
    end
    # Send the commands to the server
    add_command do |io,line|
      io.send[line]
    end
    # Load a new definition file
    add_command(/^#{@prefix}load/i, 10000) do |io,line|
      if line =~ /^#{@prefix}load (.*)$/i
        config = $1
        if config and FileTest.exists? config and FileTest.readable?(config)
          io.report["Loading file #{config} ...\n"]
          load(config,true)
        end
      end
      nil
    end
  end

  def process(filters,line)
    selected = []
    filters.each do |regexp, rules|
      if line =~ regexp
        selected += rules
      end
    end
    selected.sort!
    selected.each do |rule|
      #puts "Launching rule #{rule.regexp.inspect} with priority
#{rule.priority}"
      line = rule.block[@io,line]
      if line == nil
        return
      end
    end
  end

  def register(filters, rule)
    filters[rule.regexp] << rule
    filters.ids[rule.object_id] = rule.regexp
    rule.object_id
  end

  def unregister(filters, id)
    regexp = filters.ids[id]
    filters[regexp].delete_if do |rule|
      rule.object_id == id
    end
  end

  class Rule
    attr_accessor :priority, :block
    attr_reader :regexp

    def initialize(regexp, priority, &block)
      @regexp = regexp
      @priority = priority
      @block = block
    end

    def <=>(other)
      other.priority <=> @priority
    end

  end
  class Io
    attr_accessor :send, :report
    attr_accessor :server_socket
  end

  class MUDFilters
    attr_accessor :ids

    include Enumerable

    def initialize
      @filters = Hash.new { |h,k| h[k] = [] }
      @ids = Hash.new
    end

    def [](regexp)
      @filters[regexp]
    end

    def []=(regexp, value)
      @filters[regexp] = value
    end

    def each(&block)
      @filters.each(&block)
    end

  end

end

if $0 == __FILE__
  require "getoptlong"

  opts = GetoptLong.new(
    [ "--port", "-p", GetoptLong::OPTIONAL_ARGUMENT ],
    [ "--config", "-c", GetoptLong::OPTIONAL_ARGUMENT ],
    [ "--prefix", GetoptLong::OPTIONAL_ARGUMENT],
    [ "--prompt", GetoptLong::OPTIONAL_ARGUMENT]
  )

  config = nil
  port = 23
  prefix = "!"

  opts.each do |opt, arg|
    case opt
      when "--port"
        port = arg.to_i
      when "--config"
        config = arg
      when "--prefix"
        prefix = Regexp.escape arg
    end
  end


  if ARGV.length != 1
    $stderr.write <<-EOF
    Usage: #{$0} [--port PORT] [--config FILE] [--prefix PREFIX] [--] HOST
        PORT    port of the host to connect (default: #{port.to_s})
        FILE    ruby file imported with a main function called with the
MUD object (default:none)
        HOST    host to connect
        PREFIX  prefix for local commands (default:#{prefix})
    EOF
    exit 2
  end

  host = ARGV[0]

  $mud = MUD.new(host, port)
  $mud.prefix.replace(prefix)

  if config and FileTest.exists? config and FileTest.readable? config
    load(config,true)
  end

  $mud.connect

  begin
    loop do
      line = $stdin.readline
      $mud.send line
    end
  rescue EOFError
    $mud.disconnect
  end
end
======>8============>8===========>8===========>8============>8=========