#!/usr/bin/env ruby require 'eventmachine' require 'digest/md5' require 'fiber' require 'geoip' require 'socket' require 'timeout' # .. allowed commands .. ROLES = CAPABILITIES # normal users have ROLE broadcast. Roles are defined on a per-user basis .. in a config. $version = "0.4em_cooldns_classes_for_caching" $debug = 0 $role_commands = Hash[ #noinspection RubyStringKeysInHashInspection 'everyone' => %w(PING WHO C PART), 'broadcast_admin' => %w(BC_ID BC BC_ENDCOUNT), 'broadcast' => %w(REQ_BC BC_RE), 'specbot_admin' => %w(REQ_ASSIGN REQ_UNASSIGN REQ_PING REQ_ASSIGNMENTS), 'specbot' => %w(ASSIGN_RE UNASSIGN_RE PING_RE ASSIGNMENTS_RE REQ_DNS), ] $default_role = "everyone" # which role is talking to which role? # effectively it says: this (local) command is sent to that (remote) topic .. that certain topic is read by that user with that role. $role_dialogs = Hash[ #noinspection RubyStringKeysInHashInspection 'everyone' => %w(everyone), 'broadcast_admin' => %w(broadcast), 'broadcast' => %w(broadcast_admin), 'specbot_admin' => %w(specbot), 'specbot' => %w(specbot_admin), ] $user_roles = Hash[ #noinspection RubyStringKeysInHashInspection 'paul_dev_eggdrop' => %w(everyone broadcast), 'paul_eggdrop' => %w(everyone broadcast), 'paul_dev_specbot' => %w(everyone broadcast specbot), 'paul_specbot' => %w(everyone broadcast specbot), 'qw.nu' => %w(everyone broadcast), 'qw.nu_poster' => %w(everyone broadcast), 'mihawk_dev_specbot' => %w(everyone broadcast specbot), 'mihawk_specbot' => %w(everyone broadcast specbot), 'armitage_specbot' => %w(everyone broadcast specbot), ] $qw_list = Array.new $qw_list = [ "89.149.194.72:27700", "84.200.47.113:30000", "89.149.194.72:27500", "84.200.47.113:28502", "89.149.194.72:30000", "89.149.194.72:27600", "93.186.192.16:28502", "84.200.47.113:28501", "93.186.192.16:28501", "188.40.130.10:27502", "93.186.192.16:30000", "188.40.130.10:27599", "188.40.130.10:27503", "194.109.69.75:28000", "194.109.69.75:27500", "194.109.69.75:27501", "194.109.69.76:28504", "188.165.243.56:30000", "188.40.130.10:27501", "194.109.69.76:28502", "194.109.69.76:28501", "194.109.69.76:28503", "62.24.64.11:27501", "62.24.64.11:44444", "188.40.103.81:27600", "62.24.64.11:30000", "194.109.69.75:27502", "91.121.69.201:30000", "37.59.63.97:28504", "188.40.130.10:27500", "188.165.243.56:28009", "37.59.63.97:28501", "62.24.64.11:27500", "37.59.63.97:28503", "194.109.69.76:30000", "188.165.243.56:28008", "188.165.243.56:28006", "188.165.243.56:28001", "188.165.243.56:28002", "93.186.192.16:27500", "91.121.69.201:27600", "37.59.63.97:28502", "91.121.223.163:28001", "188.165.243.56:28003", "109.74.195.224:30000", "178.79.183.178:27500", "77.74.194.189:27501", "91.121.69.201:27502", "188.165.243.56:28007", "109.74.195.224:27500", "213.5.176.135:27502", "77.74.194.189:27504", "178.79.172.251:27600", "77.74.194.189:27502", "77.74.194.189:27503", "213.5.176.135:27500", "109.74.195.224:27501", "89.104.194.146:27504", "89.104.194.146:27503", "89.104.194.146:27508", "213.5.176.136:27510", "213.5.176.135:27501", "213.5.176.135:27503", "77.74.194.189:30000", "89.149.194.72:27800", "89.104.194.146:27502", "89.104.194.146:27666", "89.104.194.146:27507", "89.104.194.146:27509", "89.104.194.146:30000", "188.165.243.56:28004", "89.104.194.146:27500", "89.104.194.146:27501", "194.109.69.76:27500", "89.104.194.146:27506", "94.236.92.49:27501", "91.121.69.201:27501", "213.239.214.134:27500", "89.104.194.146:27510", "95.131.48.86:27502", "94.236.92.49:27500", "94.236.92.49:30000", "95.131.48.86:27504", "78.137.161.109:27501", "188.165.243.56:28005", "178.217.185.104:27500", "178.217.185.104:27600", "178.217.185.104:30000", "87.102.202.23:27502", "78.137.161.109:27500", "89.104.194.146:27505", "87.102.202.23:27505", "178.217.185.104:27501", "91.121.69.201:27500", "82.141.152.3:27501", "82.141.152.3:27500", "83.179.23.16:28002", "109.74.7.60:27500", "212.62.234.153:27502", "83.179.23.16:28003", "195.54.182.34:27500", "83.179.23.16:28005", "212.62.234.153:27503", "95.143.243.24:27600", "83.179.23.16:28001", "212.62.234.153:27504", "78.137.161.109:27502", "83.179.23.16:28004", "212.62.234.153:27501", "95.143.243.24:27500", "109.228.137.161:28501", "95.143.243.24:27900", "83.226.149.218:27500", "83.226.149.218:28001", "83.226.149.218:28002", "83.226.149.218:28003", "193.1.40.166:27975", "87.237.112.11:30000", "217.30.184.104:27500", "78.108.53.19:27500", "78.108.53.19:27501", "195.54.142.7:28001", "193.1.40.166:27500", "87.237.112.11:27501", "195.54.142.7:30000", "195.54.142.7:28007", "217.119.36.79:30000", "95.131.48.86:27501", "195.54.142.7:28002", "195.54.142.7:28008", "80.101.105.103:27500", "87.237.112.11:27502", "217.119.36.79:27500", "81.170.128.75:28501", "217.119.36.79:28001", "193.1.40.167:27500", "217.119.36.79:28003", "217.119.36.79:28002", "81.170.128.75:30000", "81.170.128.75:28504", "195.54.142.7:28005", "195.54.142.7:28010", "81.170.128.75:28503", "93.81.254.63:27502", "212.42.38.88:27504", "81.170.128.75:28502", "93.81.254.63:27500", "81.170.128.75:28000", "212.42.38.88:27500", "82.203.213.117:28002", "212.42.38.88:30000", "93.81.254.63:30000", "82.203.213.117:28001", "212.42.38.88:27501", "212.42.38.88:27503", "93.81.254.63:27503", "93.81.254.63:27501", "82.203.213.117:30000", "95.131.48.86:27503", "83.252.244.76:27500", "212.42.38.88:27502", "195.54.142.7:28003", "95.31.4.132:30000", "83.252.244.76:27501", "93.81.254.63:30001", "195.54.142.7:28006", "82.203.213.117:28003", "95.143.243.24:27700", "84.234.185.215:27503", "84.234.185.215:27500", "84.234.185.215:27519", "84.234.185.215:27508", "84.234.185.215:27506", "84.234.185.215:27505", "195.54.142.7:28004", "84.234.185.215:27501", "195.54.142.7:28009", "212.109.128.148:27501", "84.234.185.215:27502", "31.209.7.104:28501", "194.79.85.66:27501", "194.79.85.66:30000", "95.84.164.245:27501", "194.79.85.66:27502", "83.222.112.157:30000", "212.109.128.148:27500", "94.100.6.66:27500", "129.241.205.153:28000", "84.234.185.215:27507", "129.241.205.153:27500", "95.84.164.245:27500", "69.31.82.226:27501", "69.31.82.226:30000", "130.240.207.177:30000", "69.31.82.226:28100", "69.31.82.226:27500", "69.31.82.226:28101", "69.31.82.226:28010", "69.31.82.226:30001", "69.31.82.226:28000", "69.31.82.226:28002", "93.186.192.16:28000", "195.222.130.83:27500", "84.200.47.113:28000", "130.85.56.131:27500", "108.174.51.73:28003", "96.8.113.36:27501", "96.8.113.36:27500", "68.100.130.114:27501", "65.31.69.75:27500", "108.174.51.73:28006", "65.31.69.75:27508", "108.174.51.73:30000", "67.81.59.41:27500", "108.174.51.73:28004", "217.18.138.23:27505", "108.174.51.73:28001", "68.100.130.114:27500", "174.49.198.60:27502", "174.49.198.60:27503", "209.239.113.236:27500", "174.49.198.60:27515", "217.119.36.79:28000", "108.174.51.73:28005", "74.91.115.244:28001", "174.49.198.60:27500", "74.91.115.244:28000", "199.101.96.48:27501", "96.8.113.36:30000", "174.101.185.59:27500", "67.228.69.114:27502", "199.101.96.48:27500", "108.174.51.73:28002", "199.192.229.74:28001", "74.86.171.201:27502", "67.228.69.114:27501", "74.86.171.201:27500", "67.228.69.114:26666", "199.192.228.71:27501", "199.192.229.74:28003", "74.86.171.201:27501", "199.192.229.74:30000", "199.192.229.74:28002", "74.91.115.244:28002", "199.192.228.71:27500", "199.101.96.48:30000", "199.101.96.48:28000", "199.192.229.74:28004", "199.192.228.71:30000", "65.31.238.37:27500", "31.209.7.104:28000", "208.131.136.169:27500", "66.212.17.78:27500", "200.177.229.11:27510", "200.177.229.11:27522", "200.177.229.11:27521", "200.177.229.11:27500", "190.96.80.67:27500", "200.177.229.11:27511", "200.177.229.11:27520", "190.96.80.67:27000", "202.37.129.186:27500", "202.37.129.186:27505", "202.37.129.186:27501", "219.88.241.81:27500", "202.172.99.2:28001", "202.172.99.2:28002", "202.172.99.2:28003", "202.172.99.2:27500", "210.50.4.11:27501", "202.172.99.2:27501", "210.50.4.11:27508", "210.50.4.11:27511", "210.50.4.11:27509", "210.50.4.11:27510", "122.99.118.2:28001", "210.50.4.11:27500", "210.50.4.11:27503", "210.50.4.11:27506", "210.50.4.11:27505", "210.50.4.11:27504", ] class GameServers attr_accessor :gameservers # now, @gameservers is accessible via GameServers.gameservers def initialize @gameservers = Hash.new @gameservers.default = { :reverse_dns => "", :hostname_dns => "", :cool_dns => "", :type => "", :serverinfos => "", :timestamp => 0, } @gameservers["blah:blah"] wat = Hash.new(@gameservers["blah:blah"]) wat wat.store(:reverse_dns, "6") @gameservers.merge(wat) p @gameservers p @gameservers.default wat = Hash.new(@gameservers["blah:blah"]) wat wat.store(:hostname_dns, "12") @gameservers.merge(wat) p @gameservers p @gameservers.default end def scanserver(iphostport, type="qw", force=false) put_log "scanserver drin" if iphostport =~ /^(\d+\.\d+\.\d+\.\d+):(\d{1,5})/ iphost = $1 ipport = $2 if type == "qw" #p current # check if it already exists #if @gameservers["#{iphost}:#{ipport}"][:timestamp] > 0 # if old general data, then freshly get general data... # if @gameservers["#{iphost}:#{ipport}"][:timestamp] + 60 * 60 * 20 < Time.now.utc.to_i || force == true # end # of old #else current = Hash.new serverinfos = qwstatus(iphost, ipport) current.store(:serverinfos, serverinfos) current.store(:reverse_dns, find_reverse_dname(iphost)) current.store(:hostname_dns, qw_find_dname_by_serverinfos(serverinfos)) current.store(:type, "qw") # set new timestamp current.store(:timestamp, Time.now.utc.to_i) @gameservers.store("#{iphost}:#{ipport}", current) # save it all. current.store(:cool_dns, find_cooldns(iphost, ipport)) @gameservers.store("#{iphost}:#{ipport}", current) # save it all. #put_log "the saved one: #{@gameservers.fetch("#{iphost}:#{ipport}").fetch(:cool_dns)}" #puts "ALL" #p @gameservers #puts "DEFAULT of the hash" #p @gameservers.default #end end # of type qw end # of check form of iphostport parameter end # of scan() def scanserverlist(gs_array, type="qw") put_log "scanserverlist drin" gs_array.each do |gserver| scanserver(gserver, type) end put_log "End of scanning." put_log "foppa: #{@gameservers.fetch("89.104.194.146:27503")} " end # of scanserverlist() def get_cool_dns(iphost, ipport) put_log "get_cool_dns drin" @gameservers.fetch("#{iphost}:#{ipport}").fetch(:cool_dns) rescue scanserver("#{iphost}:#{ipport}", "qw") @gameservers.fetch("#{iphost}:#{ipport}").fetch(:cool_dns) end private # all following methods are private # returns serverinfo hash def qwstatus(iphost, ipport) put_log "qwstatus drin" udp_payload = [0xFF, 0xFF, 0xFF, 0xFF] udp_payload.concat(string2bytearray("status 23")) udp_payload.concat([0x0a]) # linefeed at the end udp_payload = udp_payload.pack("C*") #p udp_payload Timeout::timeout(2) do begin u2 = UDPSocket.new #put_log "#{iphost} #{ipport} #{udp_payload}" u2.send(udp_payload, 0, iphost, ipport) #put_log "sent" the_return = u2.recv(500) u2.close #put_log "muh #{the_return}" if the_return =~ /\W\W\W\Wn\\(.+)$/ line = $1 #put_log "line #{line}" matches = line.scan(/(.+?)\\(.+?)(\\|$)/) the_hash = Hash.new matches.each { |k, v, _| the_hash[k] = "#{v}" } return the_hash else return false end rescue Exception => e puts e.message end # of begin end # of Timeout end def find_cooldns_full(iphost, ipport) targetname = iphost # resolve it to a dns name (reverse lookup) if targetname == iphost # if it is still default put_log "Ip not resolved .. we try hostname dns finder" targetname = qw_find_dname_in_hostnames(iphost, ipport) end # resolve it to a dns name (reverse lookup) if targetname == iphost put_log "Still no resolve .. we try reverse dns lookup" targetname = find_reverse_dname(iphost) end return targetname end def find_cooldns(iphost, ipport) put_log "find_cooldns drin" current = @gameservers["#{iphost}:#{ipport}"] # only use this for reading... my_cooldns = iphost # we still haven't found a cool dns if (my_cooldns == iphost) && (not current[:hostname_dns].to_s.empty?) put_log "Try if #{current[:hostname_dns]} resolves to #{iphost}" begin ip_of_hostnamedns = Resolv.getaddress(current[:hostname_dns]) if ip_of_hostnamedns && (ip_of_hostnamedns == iphost) # ok, we take it. put_log "Ok, we take #{current[:hostname_dns]} for #{iphost} here.." my_cooldns = current[:hostname_dns] else put_log "Found #{current[:hostname_dns]} but #{ip_of_hostnamedns} is not #{iphost}" end rescue Exception => e my_cooldns = iphost end end # we still haven't found a cool dns unless current[:reverse_dns].to_s.empty? if my_cooldns == iphost rdns = current[:reverse_dns] # if the resulting dns name... # .. is too long, use ip-address instead. # .. has too many dots, use ip-address instead. # .. has too many numbers, use ip-address instead. if rdns.length > 21 || rdns.split(".").size > 3 || rdns.scan(/\d/).size > 3 put_log "cutting down host_name: #{rdns}, because:" put_log "length: #{rdns.length}" put_log "chunks count: #{rdns.split(".").size}" put_log "num count: #{rdns.scan(/\d/).size}" else my_cooldns = rdns end # of Resolv.getname end end put_log "COOOOOOOOOOOOLDNS: #{my_cooldns}" my_cooldns end def find_reverse_dname(iphost) put_log "find_reverse_dname drin" dname = Resolv.getname(iphost) ip_of_dname = Resolv.getaddress(dname) if ip_of_dname == iphost return dname end rescue iphost end def qw_find_dname_by_udp(iphost, ipport) targetname = iphost # set a default # get hostname from a qw status packet! perhaps there's a DNS name inside, which we can use! status = qwstatus(iphost, ipport) p status if status["hostname"] =~ /([\w\.-]{3,}\.\w{2,4})/ hostnamedns = $1.downcase begin ip_of_hostnamedns = Resolv.getaddress(hostnamedns) if ip_of_hostnamedns && (ip_of_hostnamedns == iphost) # ok, we take it. put_log "Ok, we take #{hostnamedns} for #{iphost} here.." targetname = hostnamedns else put_log "Found #{hostnamedns} but #{ip_of_hostnamedns} is not #{iphost}" end rescue Exception => e targetname = iphost end end targetname end def qw_find_dname_by_serverinfos(serverinfos) hostnamedns = "" begin hostname = serverinfos.fetch("hostname") if hostname =~ /([\w\.-]{3,}\.\w{2,4})/ hostnamedns = $1.downcase end return hostnamedns rescue return "" end end end # of Class GameServers! def string2bytearray(text) return_array = Array.new text.each_byte{ |b| return_array.push(b) } return_array end def put_log(msg) puts "#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")}: #{msg}" end module CentralProtocolHandler @@connected_clients = Array.new @@broadcasts = Hash.new put_log "Server started" # default method that is being run on connection! def post_init # connection of someone starts here... @username = nil end ### getters def entered_username? !@username.nil? && !@username.empty? # then it's true end def username @username end def my_roles $user_roles[@username] end def my_cmds my_roles.collect {|v| $role_commands[v]}.flatten end # returns online users by default by searching through saved connections def ousers users = Array.new @@connected_clients.each {|c| users.push(c.username)} users end # of ousers ### checkers def allowed_cmd(inputmessage) my_cmds.each{|c| #print "A #{c} B #{inputmessage}\n" if inputmessage =~ /^#{c}/ return true end } false end def online(user) if ousers.include? user true else false end end ### actions # searches through our hash of saved connections and writes them a messages. def write_user(input, username = nil) if username.nil? || username.empty? username = @username end if @@connected_clients.find { |c| c.username == username } connection = @@connected_clients.find { |c| c.username == username } put_log("to #{username}: #{input}") sometime = "#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S %z")}" line = "#{sometime}: #{input}\n" connection.send_data line end end # of write_user # searches through roles and writes those users on their connections def write_role(role, input, *exempts) #find users with role users = Array.new $user_roles.each {|k, v| if v.include? role users.push(k) end } # find users that are online and inside Array "users" lala = Array.new users.each {|v| if ousers.include? v lala.push(v) end } # now write to them lala.each {|user| if not exempts.include? user write_user(input, user) end } end # of write_role() # what happens with what input?! def inputting(input) put_log("SYS #{@username} typed: #{input}") #write_user("SYS You typed: #{input}") if input =~ /^([A-Z_]+)/ cmd = $1 end # now we have the cmd .. or not ;) if input =~ /^[A-Z_]+ (.+)/ payload = $1 end # now we can use payload if allowed_cmd(cmd) ### typical system commands follow if cmd == "PING" write_user("PONG") elsif cmd == "C" if payload =~ /^(.+)$/ write_role($default_role, "C #{@username}: #{$1}") else write_user("SYS Format is C ") end elsif cmd == "WHO" ousers.each {|ouser| write_user("WHO_RE #{ouser} ROLES: #{$user_roles[ouser].join(", ")}")} elsif cmd == "PART" write_user("SYS Goodbye '#{@username}'.") write_role($default_role, "PARTED User '#{@username}' just left the party.", @username) return "bye" ###now for the good stuff, broadcasting elsif cmd == "REQ_BC" # help with format .. but send the raw payload if payload =~ /^(.+),(.+),(.+),'(.+)','(.+)'$/ # n f s ni txt error = 0 # $& # The string matched by the last successful pattern match in this scope, or nil if the last pattern match failed. (Mnemonic: like & in some editors.) This variable is r network = $1 freqname = $2 source = $3 nickname = $4 text = $5 if not network =~ /^(QWalt)|(QDEV)/ write_user("SYS Network name #{network} is unknown: QWalt or QDEV allowed.") error = 1 end if not freqname =~ /^(-qw-)|(-spam-)|(-dev-)/ write_user("SYS Frequency name is unknown #{freqname}") error = 1 end if source =~ /^(#.+)|(qw:\/\/)|(http:\/\/)/ else write_user("SYS Source string is not in the form ^(#.+)|(qw:\/\/)|(http:\/\/) was: #{source}") error = 1 end if not nickname =~ /^.+/ write_user("SYS Nickname string is not in the form ^.+ #{nickname}") error = 1 end if not text =~ /^.+/ write_user("SYS Message string is not in the form ^.+ #{text}") error = 1 end else # of payload has format write_user("SYS Command format is REQ_BC ,,,'',''") error = 1 end # of payload has format if error == 0 # send REQ_BC to the corresponding role. bcid = Digest::MD5.hexdigest("%d %s %s %s %s %s" % [Time.now.utc.to_i, network, freqname, source, nickname, text]) # so it only reaches the issuer of REQ_BC write_user("BC_ID #{bcid} for: #{network},#{freqname},#{source}") @@broadcasts[bcid] = @username # qw:// ip resolving if source =~ /^qw:\/\/(.+):(\d+)(.*)$/ # ip address is 15 in length iphost = $1 targetname = iphost # set a default ipport = $2 rest = $3 if iphost =~ /^\d+\.\d+\.\d+\.\d+/ targetname = $gs.get_cool_dns(iphost, ipport) # find country code cc = GeoIP.new('GeoIP.dat').country(iphost)[3].to_s.downcase rest.prepend " #{cc}" unless cc.nil? || cc.empty? || targetname =~ /\.#{cc}$/ end # if ip is x.x.x.x source = "qw://#{targetname}:#{ipport}#{rest}" end # of if source qw://x.x.x.x:portzzz # resolve finalmessage = "BC %s %s,%s,%s,'%s','%s'" % [bcid, network, freqname, source, nickname, text] write_role("broadcast", finalmessage, @username) # write to the role with the exempt of myself. end #end of REQ_BC here.. elsif cmd == "REQ_DNS" # help with format .. but send the raw payload if payload =~ /^(\d+\.\d+\.\d+\.\d+):(\d{1,5})/ iphost = $1 ipport = $2 targetname = $gs.get_cool_dns(iphost, ipport) write_user("DNS_RE #{iphost}:#{ipport} #{targetname}") # write back to asking user. else # of payload has format write_user("SYS Command format is REQ_DNS :") error = 1 end # of if payload has format #end of REQ_DNS here.. elsif cmd == "BC_RE" if payload =~ /^(.+) (.+)=(\d+),(.+)=(\d+)$/ bcid = $1 user_string = $2 user_count = $3 item_string = $4 item_count = $5 payload = "%s %s=%d,%s=%d" % [bcid, user_string, user_count, item_string, item_count] # according bcid it is possible to get the originating user.. so send only to his topic! if @@broadcasts[bcid] to_user = @@broadcasts[bcid] write_user("SYS Broadcast reply bcid: #{bcid} underway to #{to_user}.") write_user("#{cmd} #{payload}", to_user) else write_user("SYS Broadcast reply bcid: #{bcid} underway.") write_role("broadcast", "#{cmd} #{payload}", @username) end else # of bc_re format check put_log "SYS Format is BC_RE =,=" end end else # of if allowed command if input.length > 15 input = input.slice(0,14) + ".." end write_user("SYS Command '#{input}' not allowed, your commands are: #{my_cmds.join(", ")}.") end # of if allowed command end # of inputting! # default method that is being run on receiving data! def receive_data(data) # connection receives a line on the server end data = data.chomp unless data.chomp.nil? data.each_line do |line| unless line.empty? if entered_username? # oh, it's a known user # it's alive! kill ping timer, set a new one: if @ping_timer EventMachine.cancel_timer(@ping_timer) end @ping_timer = EventMachine.add_periodic_timer 180, proc { write_user("PING alive?") } # work on input inputter = inputting(line) if inputter == "bye" close_connection end else # of username known, then it seems to be the first connect # and this line is the user name coming in! username = line if online(username) put_log "SYS User '#{username}' tried to double connect. Say bye bye." send_data "SYS Hey '#{username}', only one connection allowed. Bye.\n" EventMachine.add_timer 1, proc { write_user("PING check forced", username) close_connection } return false end if $user_roles.any? {|k| k.include? username} @username = username @@connected_clients.push(self) @ping_timer = EventMachine.add_periodic_timer 90, proc { write_user("PING alive?") } # starting a pinger put_log("SYS '#{@username}' connected. Online now: #{ousers.join(", ")}") write_user("HELLO Hi user '#{@username}'! How are you? I'm '#{$version}'") write_user("ROLES #{my_roles.join(", ")}") write_user("COMMANDS #{my_cmds.join(", ")}") write_role($default_role, "JOINED User '#{@username}' just joined the party.", @username) else put_log "SYS User '#{username}' unknown to me. Saying bye." send_data "SYS User '#{username}' unknown to me. Bye.\n" # disconnect him close_connection end end # of username known end # of unless line.empty? end # of data.each_line end # of receive data # default method that is being run on disconnection! def unbind # a connection goes bye bye if @username @@connected_clients.delete(self) if @ping_timer EventMachine.cancel_timer(@ping_timer) end put_log "SYS '#{@username}' disconnected." end put_log "SYS Online users now: #{ousers.join(", ")}" end end def main # #run: Note that this will block current thread. $gs = GameServers.new EventMachine.run do # initial scan EM.defer do $gs.scanserverlist($qw_list, "qw") # initial scan end # periodic scan, 20 hours EventMachine.add_periodic_timer( 60 * 60 * 20 ) { EM.defer do $gs.scanserverlist($qw_list, "qw") # initial scan end } EventMachine.start_server("0.0.0.0", 7337, CentralProtocolHandler) end end # of main main