123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125 |
- #!/usr/bin/env ruby
- require 'eventmachine'
- require 'digest/md5'
- require 'fiber'
- require 'geoip'
- require 'socket'
- require 'timeout'
- require './em_server_accounts.rb'
- require './em_server_initqwserverlist.rb'
- $version = "0.6em_specservers_dupe_election"
- $debug = 0
- 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:27501")} "
- end # of scanserverlist()
- def get_cool_dns(iphost, ipport)
- put_log "get_cool_dns drin"
- cool_dns = @gameservers.fetch("#{iphost}:#{ipport}").fetch(:cool_dns)
- put_log "cool_dns for #{iphost}:#{ipport} is: #{cool_dns}"
- return cool_dns
- rescue
- scanserver("#{iphost}:#{ipport}", "qw")
- cool_dns = @gameservers.fetch("#{iphost}:#{ipport}").fetch(:cool_dns)
- put_log "cool_dns for #{iphost}:#{ipport} is: #{cool_dns}"
- return 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
- begin
- Timeout::timeout(2) do
- 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
- end # of Timeout
- rescue Timeout::Error => e
- put_log "Timeout. We go on."
- rescue Exception => e
- puts e.message
- end # of begin
- 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
- @my_servers = Hash.new
- @my_servers.default = {
- "active" => 0,
- "s-p" => "false",
- "ping" => 8888,
- }
- end
- ### setters
- def set_my_servers(server, key, value)
- current = Hash.new
- current.store(key, value)
- @my_servers[server] = @my_servers[server].merge(current)
- end
- def specbot_set_sp(username, server, value)
- @@connected_clients.each {|c|
- if c.username == username
- c.set_my_servers(server, "s-p", value)
- end
- }
- end
- ### getters
- def entered_username?
- !@username.nil? && !@username.empty? # then it's true
- end
- def username
- @username
- end
- def my_active_servers(active = true)
- unless @my_servers.nil? || @my_servers.empty?
- if active
- rethash = Hash.new
- @my_servers.each_pair{|k, v|
- if v["active"] == 1
- rethash[k] = @my_servers[k]
- end
- }
- rethash
- else
- @my_servers
- end
- end
- end
- def specbot_servers(flat = true)
- the_servers = Hash.new
- @@connected_clients.each {|c|
- if flat
- unless c.my_active_servers.nil? || c.my_active_servers.empty?
- the_servers = the_servers.merge(c.my_active_servers)
- end
- else
- unless c.my_active_servers.nil? || c.my_active_servers.empty?
- the_servers[c.username] = c.my_active_servers
- end
- end
- }
- the_servers
- end
- def specbot_ping(server)
- ms = my_active_servers(false)
- unless ms.nil?
- si = ms[server]
- p1 = si.fetch("ping").to_s.to_i
- return p1
- else
- return 8888
- end
- end
- def specbot_active?(server)
- ms = my_active_servers(false)
- unless ms.nil?
- si = ms[server]
- a1 = si.fetch("active")
- else
- a1 = 0
- end
- if a1 == 1
- return 1
- else
- return 0
- end
- end
- def specbot_sp?(server)
- ms = my_active_servers(false)
- unless ms.nil?
- si = ms[server]
- a1 = si.fetch("s-p")
- else
- a1 = "false"
- end
- if a1 == "true"
- return true
- else
- return false
- end
- end
- def my_maxservers
- if @my_maxservers.nil? || @my_maxservers.empty?
- nil
- else
- @my_maxservers
- end
- 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
- # returns online users by searching through saved connections that have the specified role
- def ousers_by_role(role)
- users = Array.new
- @@connected_clients.each {|c|
- if c.my_roles.include?(role)
- users.push(c.username)
- end
- }
- users
- end
- # returns connections by searching through saved connections that have the specified role
- def connections_by_role(role)
- conns = Array.new
- @@connected_clients.each {|c|
- if c.my_roles.include?(role)
- conns.push(c)
- end
- }
- conns
- end
- ### 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
- def specbot_dupecheck()
- put_log "specbot_dupecheck drin"
- conns = connections_by_role("specbot")
- # if this connection has a same key than another connection
- # then fight .. which connection should have that server unassigned. REQ_PING
- # the one with the better ping will win and the one with the worse ping will get a new free server slot ;)
- # we have at least 2 bots... > 1
- conns.repeated_combination(2){|c|
- c1 = c[0]
- c2 = c[1]
- unless c1 == c2
- unless c1.my_active_servers.nil? || c2.my_active_servers.nil?
- dupes = c1.my_active_servers.keys & c2.my_active_servers.keys
- if dupes.size > 0
- # damn, there's dupes.
- put_log "the dupes are: #{dupes.join(", ")} on #{c1.username} and #{c2.username}"
- dupes.each{|d|
- write_user("REQ_PING #{d}", c1.username)
- write_user("REQ_PING #{d}", c2.username)
- EventMachine.add_timer 2, proc {
- p1 = c1.specbot_ping(d)
- p2 = c2.specbot_ping(d)
- a1 = c1.specbot_active?(d)
- a2 = c2.specbot_active?(d)
- if p1 <= p2
- unless a2 == 0
- write_user("REQ_UNASSIGN #{d}", c2.username)
- end
- else
- unless a1 == 0
- write_user("REQ_UNASSIGN #{d}", c1.username)
- end
- end
- }
- }
- end
- end
- end
- }
- end # of specbot_dupecheck
- # 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 <chattext>")
- 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}' leaves the party.", @username)
- return "bye"
- ### now for the good stuff, broadcasting role
- 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 <network>,<frequency>,<source/channel>,'<nickname>','<message>'")
- 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 == "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 <bcid> <userstring>=<usercount>,<itemstring>=<itemcount>"
- end
- ### the specbot-admin ROLE does ...
- elsif cmd == "REQ_ASSIGN"
- if payload =~ /^([a-zA-Z_\.]+) (\d+\.\d+\.\d+\.\d+:\d{1,5})[,]?(true|false)?/
- specbot = $1
- hostport = $2
- sp = $3
- if sp == "true"
- specbot_set_sp(specbot, hostport, sp)
- write_user("REQ_ASSIGN #{hostport},#{sp}", specbot)
- else
- write_user("REQ_ASSIGN #{hostport}", specbot)
- end
- else # of format check
- write_user("SYS Format is REQ_ASSIGN <specbot> <ip:port>[,<s-p;true or false>]")
- end
- elsif cmd == "REQ_UNASSIGN"
- if payload =~ /^([a-zA-Z_\.]+) (\d+\.\d+\.\d+\.\d+:\d{1,5})/
- specbot = $1
- hostport = $2
- write_user("REQ_UNASSIGN #{hostport}", specbot)
- else # of format check
- write_user("SYS Format is REQ_UNASSIGN <specbot> <ip:port>")
- end
- elsif cmd == "REQ_PING"
- if payload =~ /^([a-zA-Z_\.]+) (\d+\.\d+\.\d+\.\d+:\d{1,5})/
- specbot = $1
- hostport = $2
- write_user("REQ_PING #{hostport}", specbot)
- else # of format check
- write_user("SYS Format is REQ_PING <specbot> <ip:port>")
- end
- elsif cmd == "REQ_ASSIGNMENTS"
- if payload =~ /^([a-zA-Z_\.]+)/
- specbot = $1
- write_user("REQ_ASSIGNMENTS give me your assignments", specbot)
- else # of format check
- write_user("SYS Format is REQ_ASSIGNMENTS <specbot>")
- end
- elsif cmd == "REQ_MAXSERVERS"
- if payload =~ /^([a-zA-Z_\.]+)/
- specbot = $1
- write_user("REQ_MAXSERVERS how many do you do?", specbot)
- else # of format check
- write_user("SYS Format is REQ_MAXSERVERS <specbot>")
- end
- elsif cmd == "DUPECHECK"
- write_user("SYS specbot server monitoring dupecheck started")
- # start the central function for dupechecking:
- specbot_dupecheck()
- elsif cmd == "ELECTION"
- write_user("SYS specbot server ping election on global server list started")
- # start the central function for server election:
- all_servers = specbot_servers()
- put_log("#{all_servers}")
- write_user("SYS active unique server count: #{all_servers.size}")
- conns = connections_by_role("specbot")
- #für jeden server, jeden ping der einzelnen bots vergleichen..
- #falls ping info noch nicht vorhanden (>5000), dann einfordern
- all_servers.each_key{|k|
- put_log("SYS bots race for #{k} now..")
- conns.each{|c|
- ping = c.specbot_ping(k)
- if ping > 5000
- write_user("REQ_PING #{k}", c.username)
- end
- }
- EventMachine.add_timer 5, proc {
- lastbest = 5000
- winner = ""
- conns.each{|c|
- ping = c.specbot_ping(k)
- put_log("SYS #{ping} of #{c.username} to #{k}")
- if ping < lastbest
- lastbest = ping
- winner = c
- put_log("SYS - current best bot for #{k} is #{c.username}")
- end
- }
- put_log("SYS --> best bot for #{k} is #{winner.username}")
- conns.each{|c|
- a = c.specbot_active?(k)
- unless c == winner
- unless a == 0
- write_user("REQ_UNASSIGN #{k}", c.username)
- end
- end
- if c == winner
- unless a == 1
- as = all_servers[k]
- sp = as.fetch("s-p")
- if sp == "true" || sp == "1"
- write_user("REQ_ASSIGN #{k},true", c.username)
- else
- write_user("REQ_ASSIGN #{k}", c.username)
- end
- end
- end
- }
- } # end of timer
- } # end of all active servers loop
- ### the specbot ROLE does ...
- elsif cmd == "ASSIGNMENTS_RE"
- if payload =~ /^(\d+\.\d+\.\d+\.\d+:\d{1,5}),(.*),(.*)/
- hostport = $1
- sp = $2
- ping = $3.chomp
- p hostport
- p sp
- p ping
- current = Hash.new()
- current.store("active", 1)
- current.store("s-p", sp)
- current.store("ping", ping)
- # save the hash current to the hash @my_servers
- @my_servers[hostport] = @my_servers[hostport].merge(current)
- p @my_servers
- # save new hash to a config file or sth. fixme
- end
- #end of ASSIGNMENTS_RE here..
- elsif cmd == "MAXSERVERS_RE"
- if payload =~ /^(\d+)/
- @my_maxservers = $1
- end
- #end of MAXSERVERS_RE here..
- elsif cmd == "PING_RE"
- if payload =~ /^(\d+\.\d+\.\d+\.\d+:\d{1,5}),(.*)/
- hostport = $1
- ping = $2
- current = Hash.new()
- current.store("ping", ping)
- # save the hash current to the hash @my_servers
- @my_servers[hostport] = @my_servers[hostport].merge(current)
- p @my_servers
- # save new hash to a config file or sth. fixme
- end
- #end of PING_RE here..
- elsif cmd == "ASSIGN_RE"
- #assign_re should only be issued when a req_assign was issued to the specbot before
- if payload =~ /^(\d+\.\d+\.\d+\.\d+:\d{1,5}) ([A-Z]+)[\s]?(.*)/
- hostport = $1
- good = $2
- reason = $3
- if good == "OK"
- current = Hash.new()
- current.store("active", 1)
- @my_servers[hostport] = @my_servers[hostport].merge(current) # this way it preserves previously saved ping values
- write_user("REQ_PING #{hostport}")
- put_log "SYS #{username} assigned to #{hostport} and asked ping for it."
- write_role("specbot_admin", "SYS #{username} assigned to #{hostport} and asked ping for it.")
- else
- put_log "SYS #{username} failed to assign #{hostport}, reason: '#{reason}'"
- write_role("specbot_admin", "SYS #{username} failed to assign #{hostport}, reason: '#{reason}'")
- end
- end
- #end of ASSIGN_RE here..
- elsif cmd == "UNASSIGN_RE"
- if payload =~ /^(\d+\.\d+\.\d+\.\d+:\d{1,5}) ([A-Z]+)[\s]?(.*)/
- hostport = $1
- good = $2
- reason = $3
- if good == "OK"
- current = Hash.new()
- current.store("active", 0)
- @my_servers[hostport] = @my_servers[hostport].merge(current) # this way it preserves previously saved ping values
- put_log "SYS #{username} unassigned #{hostport}"
- write_role("specbot_admin", "SYS #{username} unassigned #{hostport}")
- else
- put_log "SYS #{username} failed to unassign #{hostport}, reason: '#{reason}'"
- write_role("specbot_admin", "SYS #{username} failed to unassign #{hostport}, reason: '#{reason}'")
- end
- end
- #end of UNASSIGN_RE 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 <serverip>:<serverport>")
- error = 1
- end # of if payload has format
- #end of REQ_DNS here..
- 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)
- # if that guy is a specbot, ask it for his current list
- if my_roles.include?("specbot")
- EventMachine.add_timer 2, proc {
- write_user("REQ_MAXSERVERS how many can you do?")
- write_user("REQ_ASSIGNMENTS gimme all your servers")
- }
- # if more than 1 specbots are connected, then we want to activate the dupe-check.
- if connections_by_role("specbot").size > 1
- # in 6 seconds we start it. by then, we should have the full serverlists of all bots
- EventMachine.add_timer 6, proc {
- put_log("this guy: #{username} triggers the dupecheck now")
- specbot_dupecheck
- }
- end
- end
- 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."
- write_role($default_role, "SYS User '#{@username}' disconnected.", @username)
- 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
|