module Potato # An IRC server. module Server extend self # A list of the current connections (IRC-dAmn clients) # @return [Hash] attr_reader :connections # Command-line options. # @return [Hash] attr_reader :opts # Write color-coded debug information if debug is enabled. def debug(*items, type) color = {:damn => 33, :irc => 35, :damn_out => 32, :irc_out => 34, :sys => 31}[type || :sys] colon = (type == :damn_out || type == :irc_out) ? ">>" : "<<" if @opts.debug items.each do |line| line.split("\n").each do |l| puts "\e[#{color}m#{colon}\e[0m #{l}" end end end end # Start the server and listen for connections. # @param [Hash] opts command-line options # @return [void] def start opts @connections = {} @opts = self.parse_opts(opts) @server = TCPServer.new(@opts.port) loop do begin action = IO.select([@server, @connections.keys, @connections.values.map{|x|x.client.server}].flatten.compact, nil, nil)[0][0] rescue IOError @connections.delete_if { |k,v| v.client.server.closed? || k.closed? } retry rescue Interrupt puts "\033[2DCaught interrupt (^C), exiting." exit end if action == @server if @connections.size <= @opts.max || @opts.max < 0 @connections[cl = @server.accept] = IRC::Client.new(cl) debug "Accepted client #{cl}" else debug "Client #{cl = @server.accept} requested connection, but user limit (#{@opts.max}) has been reached" cl.close end else if DamnSocket === action begin pkt = DAmn::Packet.new(Iconv.conv("UTF-8", "LATIN1", action.readline("\0").chomp("\0"))) rescue Errno::ECONNRESET, EOFError => e @connections.values.each do |cl| cl.notice "Lost connection to dAmn (#{e.class})." cl.socket.close end @connections = {} end debug pkt.raw, :damn client = @connections.values.find{|x|x.client.server == action}.client client.send("on_#{pkt.cmd}".to_sym, pkt) if client.respond_to?("on_#{pkt.cmd}".to_sym) else if action.eof? @connections.delete(action) debug "Client #{action} disconnected" else client = @connections[action] pkt = IRC::Packet.new(action.readline("\r\n").chomp("\r\n")) debug pkt.raw, :irc client.send("on_#{pkt.cmd.downcase}".to_sym, pkt) if client.respond_to?("on_#{pkt.cmd.downcase}".to_sym) end end end end end # Parses command-line options into @opts. # @return [Hash] def parse_opts(args) options = OpenStruct.new options.rooms = [] options.port = 6667 options.debug = false options.max = -1 o = OptionParser.new do |opts| opts.banner = "Usage: potato [options]" opts.on("-p", "--port PORT", Integer, "Specify port number (default 6667)") do |v| options.port = v end opts.on("-r", "--room ROOM", "Add a room to the autojoin list") do |v| options.rooms << v end opts.on("--debug", "Turn debug mode on or off (prints color-coded messages to stdout)") do |v| options.debug = v end opts.on("--max-users N", Integer, "Set the maximum number of users that may connect to this server (default unlimited)") do |v| options.max = v end end o.parse!(args) options end end end