em_server.rb 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120
  1. #!/usr/bin/env ruby
  2. require 'eventmachine'
  3. require 'digest/md5'
  4. require 'fiber'
  5. require 'geoip'
  6. require 'socket'
  7. require 'timeout'
  8. require './em_server_accounts.rb'
  9. require './em_server_initqwserverlist.rb'
  10. class GameServers
  11. attr_accessor :gameservers # now, @gameservers is accessible via GameServers.gameservers
  12. def initialize
  13. @gameservers = Hash.new
  14. @gameservers.default = {
  15. :reverse_dns => "",
  16. :hostname_dns => "",
  17. :cool_dns => "",
  18. :type => "",
  19. :serverinfos => "",
  20. :timestamp => 0,
  21. }
  22. @gameservers["blah:blah"]
  23. wat = Hash.new(@gameservers["blah:blah"])
  24. wat
  25. wat.store(:reverse_dns, "6")
  26. @gameservers.merge(wat)
  27. p @gameservers
  28. p @gameservers.default
  29. wat = Hash.new(@gameservers["blah:blah"])
  30. wat
  31. wat.store(:hostname_dns, "12")
  32. @gameservers.merge(wat)
  33. p @gameservers
  34. p @gameservers.default
  35. end
  36. def scanserver(iphostport, type="qw", force=false)
  37. put_log "scanserver drin"
  38. if iphostport =~ /^(\d+\.\d+\.\d+\.\d+):(\d{1,5})/
  39. iphost = $1
  40. ipport = $2
  41. if type == "qw"
  42. #p current
  43. # check if it already exists
  44. #if @gameservers["#{iphost}:#{ipport}"][:timestamp] > 0
  45. # if old general data, then freshly get general data...
  46. # if @gameservers["#{iphost}:#{ipport}"][:timestamp] + 60 * 60 * 20 < Time.now.utc.to_i || force == true
  47. # end # of old
  48. #else
  49. current = Hash.new
  50. serverinfos = qwstatus(iphost, ipport)
  51. current.store(:serverinfos, serverinfos)
  52. current.store(:reverse_dns, find_reverse_dname(iphost))
  53. current.store(:hostname_dns, qw_find_dname_by_serverinfos(serverinfos))
  54. current.store(:type, "qw")
  55. # set new timestamp
  56. current.store(:timestamp, Time.now.utc.to_i)
  57. @gameservers.store("#{iphost}:#{ipport}", current) # save it all.
  58. current.store(:cool_dns, find_cooldns(iphost, ipport))
  59. @gameservers.store("#{iphost}:#{ipport}", current) # save it all.
  60. #put_log "the saved one: #{@gameservers.fetch("#{iphost}:#{ipport}").fetch(:cool_dns)}"
  61. #puts "ALL"
  62. #p @gameservers
  63. #puts "DEFAULT of the hash"
  64. #p @gameservers.default
  65. #end
  66. end # of type qw
  67. end # of check form of iphostport parameter
  68. end # of scan()
  69. def scanserverlist(gs_array, type="qw")
  70. put_log "scanserverlist drin"
  71. gs_array.each do
  72. |gserver|
  73. scanserver(gserver, type)
  74. end
  75. put_log "End of scanning."
  76. #put_log "foppa: #{@gameservers.fetch("89.104.194.146:27501")} "
  77. end # of scanserverlist()
  78. def get_cool_dns(iphost, ipport)
  79. put_log "get_cool_dns drin"
  80. cool_dns = @gameservers.fetch("#{iphost}:#{ipport}").fetch(:cool_dns)
  81. put_log "cool_dns for #{iphost}:#{ipport} is: #{cool_dns}"
  82. return cool_dns
  83. rescue
  84. scanserver("#{iphost}:#{ipport}", "qw")
  85. cool_dns = @gameservers.fetch("#{iphost}:#{ipport}").fetch(:cool_dns)
  86. put_log "cool_dns for #{iphost}:#{ipport} is: #{cool_dns}"
  87. return cool_dns
  88. end
  89. private # all following methods are private
  90. # returns serverinfo hash
  91. def qwstatus(iphost, ipport)
  92. put_log "qwstatus drin"
  93. udp_payload = [0xFF, 0xFF, 0xFF, 0xFF]
  94. udp_payload.concat(string2bytearray("status 23"))
  95. udp_payload.concat([0x0a]) # linefeed at the end
  96. udp_payload = udp_payload.pack("C*")
  97. #p udp_payload
  98. Timeout::timeout(2) do
  99. begin
  100. u2 = UDPSocket.new
  101. #put_log "#{iphost} #{ipport} #{udp_payload}"
  102. u2.send(udp_payload, 0, iphost, ipport)
  103. #put_log "sent"
  104. the_return = u2.recv(500)
  105. u2.close
  106. #put_log "muh #{the_return}"
  107. if the_return =~ /\W\W\W\Wn\\(.+)$/
  108. line = $1
  109. #put_log "line #{line}"
  110. matches = line.scan(/(.+?)\\(.+?)(\\|$)/)
  111. the_hash = Hash.new
  112. matches.each {
  113. |k, v, _|
  114. the_hash[k] = "#{v}"
  115. }
  116. return the_hash
  117. else
  118. return false
  119. end
  120. rescue Exception => e
  121. puts e.message
  122. end # of begin
  123. end # of Timeout
  124. end
  125. def find_cooldns_full(iphost, ipport)
  126. targetname = iphost
  127. # resolve it to a dns name (reverse lookup)
  128. if targetname == iphost # if it is still default
  129. put_log "Ip not resolved .. we try hostname dns finder"
  130. targetname = qw_find_dname_in_hostnames(iphost, ipport)
  131. end
  132. # resolve it to a dns name (reverse lookup)
  133. if targetname == iphost
  134. put_log "Still no resolve .. we try reverse dns lookup"
  135. targetname = find_reverse_dname(iphost)
  136. end
  137. return targetname
  138. end
  139. def find_cooldns(iphost, ipport)
  140. put_log "find_cooldns drin"
  141. current = @gameservers["#{iphost}:#{ipport}"] # only use this for reading...
  142. my_cooldns = iphost
  143. # we still haven't found a cool dns
  144. if (my_cooldns == iphost) && (not current[:hostname_dns].to_s.empty?)
  145. put_log "Try if #{current[:hostname_dns]} resolves to #{iphost}"
  146. begin
  147. ip_of_hostnamedns = Resolv.getaddress(current[:hostname_dns])
  148. if ip_of_hostnamedns && (ip_of_hostnamedns == iphost)
  149. # ok, we take it.
  150. put_log "Ok, we take #{current[:hostname_dns]} for #{iphost} here.."
  151. my_cooldns = current[:hostname_dns]
  152. else
  153. put_log "Found #{current[:hostname_dns]} but #{ip_of_hostnamedns} is not #{iphost}"
  154. end
  155. rescue Exception => e
  156. my_cooldns = iphost
  157. end
  158. end
  159. # we still haven't found a cool dns
  160. unless current[:reverse_dns].to_s.empty?
  161. if my_cooldns == iphost
  162. rdns = current[:reverse_dns]
  163. # if the resulting dns name...
  164. # .. is too long, use ip-address instead.
  165. # .. has too many dots, use ip-address instead.
  166. # .. has too many numbers, use ip-address instead.
  167. if rdns.length > 21 || rdns.split(".").size > 3 || rdns.scan(/\d/).size > 3
  168. put_log "cutting down host_name: #{rdns}, because:"
  169. put_log "length: #{rdns.length}"
  170. put_log "chunks count: #{rdns.split(".").size}"
  171. put_log "num count: #{rdns.scan(/\d/).size}"
  172. else
  173. my_cooldns = rdns
  174. end # of Resolv.getname
  175. end
  176. end
  177. put_log "COOOOOOOOOOOOLDNS: #{my_cooldns}"
  178. my_cooldns
  179. end
  180. def find_reverse_dname(iphost)
  181. put_log "find_reverse_dname drin"
  182. dname = Resolv.getname(iphost)
  183. ip_of_dname = Resolv.getaddress(dname)
  184. if ip_of_dname == iphost
  185. return dname
  186. end
  187. rescue
  188. iphost
  189. end
  190. def qw_find_dname_by_udp(iphost, ipport)
  191. targetname = iphost # set a default
  192. # get hostname from a qw status packet! perhaps there's a DNS name inside, which we can use!
  193. status = qwstatus(iphost, ipport)
  194. p status
  195. if status["hostname"] =~ /([\w\.-]{3,}\.\w{2,4})/
  196. hostnamedns = $1.downcase
  197. begin
  198. ip_of_hostnamedns = Resolv.getaddress(hostnamedns)
  199. if ip_of_hostnamedns && (ip_of_hostnamedns == iphost)
  200. # ok, we take it.
  201. put_log "Ok, we take #{hostnamedns} for #{iphost} here.."
  202. targetname = hostnamedns
  203. else
  204. put_log "Found #{hostnamedns} but #{ip_of_hostnamedns} is not #{iphost}"
  205. end
  206. rescue Exception => e
  207. targetname = iphost
  208. end
  209. end
  210. targetname
  211. end
  212. def qw_find_dname_by_serverinfos(serverinfos)
  213. hostnamedns = ""
  214. begin
  215. hostname = serverinfos.fetch("hostname")
  216. if hostname =~ /([\w\.-]{3,}\.\w{2,4})/
  217. hostnamedns = $1.downcase
  218. end
  219. return hostnamedns
  220. rescue
  221. return ""
  222. end
  223. end
  224. end # of Class GameServers!
  225. def string2bytearray(text)
  226. return_array = Array.new
  227. text.each_byte{
  228. |b|
  229. return_array.push(b)
  230. }
  231. return_array
  232. end
  233. def put_log(msg)
  234. puts "#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S")}: #{msg}"
  235. end
  236. module CentralProtocolHandler
  237. @@connected_clients = Array.new
  238. @@broadcasts = Hash.new
  239. put_log "Server started"
  240. # default method that is being run on connection!
  241. def post_init # connection of someone starts here...
  242. @username = nil
  243. @my_servers = Hash.new
  244. @my_servers.default = {
  245. "active" => 0,
  246. "s-p" => "false",
  247. "ping" => 8888,
  248. }
  249. end
  250. ### setters
  251. def set_my_servers(server, key, value)
  252. current = Hash.new
  253. current.store(key, value)
  254. @my_servers[server] = @my_servers[server].merge(current)
  255. end
  256. def specbot_set_sp(username, server, value)
  257. @@connected_clients.each {|c|
  258. if c.username == username
  259. c.set_my_servers(server, "s-p", value)
  260. end
  261. }
  262. end
  263. ### getters
  264. def entered_username?
  265. !@username.nil? && !@username.empty? # then it's true
  266. end
  267. def username
  268. @username
  269. end
  270. def my_active_servers(active = true)
  271. unless @my_servers.nil? || @my_servers.empty?
  272. if active
  273. rethash = Hash.new
  274. @my_servers.each_pair{|k, v|
  275. if v["active"] == 1
  276. rethash[k] = @my_servers[k]
  277. end
  278. }
  279. rethash
  280. else
  281. @my_servers
  282. end
  283. end
  284. end
  285. def specbot_servers(flat = true)
  286. the_servers = Hash.new
  287. @@connected_clients.each {|c|
  288. if flat
  289. unless c.my_active_servers.nil? || c.my_active_servers.empty?
  290. the_servers = the_servers.merge(c.my_active_servers)
  291. end
  292. else
  293. unless c.my_active_servers.nil? || c.my_active_servers.empty?
  294. the_servers[c.username] = c.my_active_servers
  295. end
  296. end
  297. }
  298. the_servers
  299. end
  300. def specbot_ping(server)
  301. ms = my_active_servers(false)
  302. unless ms.nil?
  303. si = ms[server]
  304. p1 = si.fetch("ping").to_s.to_i
  305. return p1
  306. else
  307. return 8888
  308. end
  309. end
  310. def specbot_active?(server)
  311. ms = my_active_servers(false)
  312. unless ms.nil?
  313. si = ms[server]
  314. a1 = si.fetch("active")
  315. else
  316. a1 = 0
  317. end
  318. if a1 == 1
  319. return 1
  320. else
  321. return 0
  322. end
  323. end
  324. def specbot_sp?(server)
  325. ms = my_active_servers(false)
  326. unless ms.nil?
  327. si = ms[server]
  328. a1 = si.fetch("s-p")
  329. else
  330. a1 = "false"
  331. end
  332. if a1 == "true"
  333. return true
  334. else
  335. return false
  336. end
  337. end
  338. def my_maxservers
  339. if @my_maxservers.nil? || @my_maxservers.empty?
  340. nil
  341. else
  342. @my_maxservers
  343. end
  344. end
  345. def my_roles
  346. $user_roles[@username]
  347. end
  348. def my_cmds
  349. my_roles.collect {|v| $role_commands[v]}.flatten
  350. end
  351. # returns online users by default by searching through saved connections
  352. def ousers
  353. users = Array.new
  354. @@connected_clients.each {|c| users.push(c.username)}
  355. users
  356. end # of ousers
  357. # returns online users by searching through saved connections that have the specified role
  358. def ousers_by_role(role)
  359. users = Array.new
  360. @@connected_clients.each {|c|
  361. if c.my_roles.include?(role)
  362. users.push(c.username)
  363. end
  364. }
  365. users
  366. end
  367. # returns connections by searching through saved connections that have the specified role
  368. def connections_by_role(role)
  369. conns = Array.new
  370. @@connected_clients.each {|c|
  371. if c.my_roles.include?(role)
  372. conns.push(c)
  373. end
  374. }
  375. conns
  376. end
  377. ### checkers
  378. def allowed_cmd(inputmessage)
  379. my_cmds.each{|c|
  380. #print "A #{c} B #{inputmessage}\n"
  381. if inputmessage =~ /^#{c}/
  382. return true
  383. end
  384. }
  385. false
  386. end
  387. def online(user)
  388. if ousers.include? user
  389. true
  390. else
  391. false
  392. end
  393. end
  394. ### actions
  395. def specbot_dupecheck()
  396. put_log "specbot_dupecheck drin"
  397. conns = connections_by_role("specbot")
  398. # if this connection has a same key than another connection
  399. # then fight .. which connection should have that server unassigned. REQ_PING
  400. # the one with the better ping will win and the one with the worse ping will get a new free server slot ;)
  401. # we have at least 2 bots... > 1
  402. conns.repeated_combination(2){|c|
  403. c1 = c[0]
  404. c2 = c[1]
  405. unless c1 == c2
  406. unless c1.my_active_servers.nil? || c2.my_active_servers.nil?
  407. dupes = c1.my_active_servers.keys & c2.my_active_servers.keys
  408. if dupes.size > 0
  409. # damn, there's dupes.
  410. put_log "the dupes are: #{dupes.join(", ")} on #{c1.username} and #{c2.username}"
  411. dupes.each{|d|
  412. write_user("REQ_PING #{d}", c1.username)
  413. write_user("REQ_PING #{d}", c2.username)
  414. EventMachine.add_timer 2, proc {
  415. p1 = c1.specbot_ping(d)
  416. p2 = c2.specbot_ping(d)
  417. a1 = c1.specbot_active?(d)
  418. a2 = c2.specbot_active?(d)
  419. if p1 <= p2
  420. unless a2 == 0
  421. write_user("REQ_UNASSIGN #{d}", c2.username)
  422. end
  423. else
  424. unless a1 == 0
  425. write_user("REQ_UNASSIGN #{d}", c1.username)
  426. end
  427. end
  428. }
  429. }
  430. end
  431. end
  432. end
  433. }
  434. end # of specbot_dupecheck
  435. # searches through our hash of saved connections and writes them a messages.
  436. def write_user(input, username = nil)
  437. if username.nil? || username.empty?
  438. username = @username
  439. end
  440. if @@connected_clients.find { |c| c.username == username }
  441. connection = @@connected_clients.find { |c| c.username == username }
  442. put_log("to #{username}: #{input}")
  443. sometime = "#{Time.now.utc.strftime("%Y-%m-%d %H:%M:%S %z")}"
  444. line = "#{sometime}: #{input}\n"
  445. connection.send_data line
  446. end
  447. end # of write_user
  448. # searches through roles and writes those users on their connections
  449. def write_role(role, input, *exempts)
  450. #find users with role
  451. users = Array.new
  452. $user_roles.each {|k, v|
  453. if v.include? role
  454. users.push(k)
  455. end
  456. }
  457. # find users that are online and inside Array "users"
  458. lala = Array.new
  459. users.each {|v|
  460. if ousers.include? v
  461. lala.push(v)
  462. end
  463. }
  464. # now write to them
  465. lala.each {|user|
  466. if not exempts.include? user
  467. write_user(input, user)
  468. end
  469. }
  470. end # of write_role()
  471. # what happens with what input?!
  472. def inputting(input)
  473. put_log("SYS #{@username} typed: #{input}")
  474. #write_user("SYS You typed: #{input}")
  475. if input =~ /^([A-Z_]+)/
  476. cmd = $1
  477. end
  478. # now we have the cmd .. or not ;)
  479. if input =~ /^[A-Z_]+ (.+)/
  480. payload = $1
  481. end
  482. # now we can use payload
  483. if allowed_cmd(cmd)
  484. ### typical system commands follow
  485. if cmd == "PING"
  486. write_user("PONG")
  487. elsif cmd == "C"
  488. if payload =~ /^(.+)$/
  489. write_role($default_role, "C #{@username}: #{$1}")
  490. else
  491. write_user("SYS Format is C <chattext>")
  492. end
  493. elsif cmd == "WHO"
  494. ousers.each {|ouser| write_user("WHO_RE #{ouser} ROLES: #{$user_roles[ouser].join(", ")}")}
  495. elsif cmd == "PART"
  496. write_user("SYS Goodbye '#{@username}'.")
  497. write_role($default_role, "PARTED User '#{@username}' leaves the party.", @username)
  498. return "bye"
  499. ### now for the good stuff, broadcasting role
  500. elsif cmd == "REQ_BC"
  501. # help with format .. but send the raw payload
  502. if payload =~ /^(.+),(.+),(.+),'(.+)','(.+)'$/
  503. # n f s ni txt
  504. error = 0
  505. # $&
  506. # 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
  507. network = $1
  508. freqname = $2
  509. source = $3
  510. nickname = $4
  511. text = $5
  512. if not network =~ /^(QWalt)|(QDEV)/
  513. write_user("SYS Network name #{network} is unknown: QWalt or QDEV allowed.")
  514. error = 1
  515. end
  516. if not freqname =~ /^(-qw-)|(-spam-)|(-dev-)/
  517. write_user("SYS Frequency name is unknown #{freqname}")
  518. error = 1
  519. end
  520. if source =~ /^(#.+)|(qw:\/\/)|(http:\/\/)/
  521. else
  522. write_user("SYS Source string is not in the form ^(#.+)|(qw:\/\/)|(http:\/\/) was: #{source}")
  523. error = 1
  524. end
  525. if not nickname =~ /^.+/
  526. write_user("SYS Nickname string is not in the form ^.+ #{nickname}")
  527. error = 1
  528. end
  529. if not text =~ /^.+/
  530. write_user("SYS Message string is not in the form ^.+ #{text}")
  531. error = 1
  532. end
  533. else # of payload has format
  534. write_user("SYS Command format is REQ_BC <network>,<frequency>,<source/channel>,'<nickname>','<message>'")
  535. error = 1
  536. end # of payload has format
  537. if error == 0
  538. # send REQ_BC to the corresponding role.
  539. bcid = Digest::MD5.hexdigest("%d %s %s %s %s %s" % [Time.now.utc.to_i, network, freqname, source, nickname, text])
  540. # so it only reaches the issuer of REQ_BC
  541. write_user("BC_ID #{bcid} for: #{network},#{freqname},#{source}")
  542. @@broadcasts[bcid] = @username
  543. # qw:// ip resolving
  544. if source =~ /^qw:\/\/(.+):(\d+)(.*)$/
  545. # ip address is 15 in length
  546. iphost = $1
  547. targetname = iphost # set a default
  548. ipport = $2
  549. rest = $3
  550. if iphost =~ /^\d+\.\d+\.\d+\.\d+/
  551. targetname = $gs.get_cool_dns(iphost, ipport)
  552. # find country code
  553. cc = GeoIP.new('GeoIP.dat').country(iphost)[3].to_s.downcase
  554. rest.prepend " #{cc}" unless cc.nil? || cc.empty? || targetname =~ /\.#{cc}$/
  555. end # if ip is x.x.x.x
  556. source = "qw://#{targetname}:#{ipport}#{rest}"
  557. end # of if source qw://x.x.x.x:portzzz
  558. # resolve
  559. finalmessage = "BC %s %s,%s,%s,'%s','%s'" % [bcid, network, freqname, source, nickname, text]
  560. write_role("broadcast", finalmessage, @username) # write to the role with the exempt of myself.
  561. end
  562. #end of REQ_BC here..
  563. elsif cmd == "BC_RE"
  564. if payload =~ /^(.+) (.+)=(\d+),(.+)=(\d+)$/
  565. bcid = $1
  566. user_string = $2
  567. user_count = $3
  568. item_string = $4
  569. item_count = $5
  570. payload = "%s %s=%d,%s=%d" % [bcid, user_string, user_count, item_string, item_count]
  571. # according bcid it is possible to get the originating user.. so send only to his topic!
  572. if @@broadcasts[bcid]
  573. to_user = @@broadcasts[bcid]
  574. write_user("SYS Broadcast reply bcid: #{bcid} underway to #{to_user}.")
  575. write_user("#{cmd} #{payload}", to_user)
  576. else
  577. write_user("SYS Broadcast reply bcid: #{bcid} underway.")
  578. write_role("broadcast", "#{cmd} #{payload}", @username)
  579. end
  580. else # of bc_re format check
  581. put_log "SYS Format is BC_RE <bcid> <userstring>=<usercount>,<itemstring>=<itemcount>"
  582. end
  583. ### the specbot-admin ROLE does ...
  584. elsif cmd == "REQ_ASSIGN"
  585. if payload =~ /^([a-zA-Z_\.]+) (\d+\.\d+\.\d+\.\d+:\d{1,5})[,]?(true|false)?/
  586. specbot = $1
  587. hostport = $2
  588. sp = $3
  589. if sp == "true"
  590. specbot_set_sp(specbot, hostport, sp)
  591. write_user("REQ_ASSIGN #{hostport},#{sp}", specbot)
  592. else
  593. write_user("REQ_ASSIGN #{hostport}", specbot)
  594. end
  595. else # of format check
  596. write_user("SYS Format is REQ_ASSIGN <specbot> <ip:port>[,<s-p;true or false>]")
  597. end
  598. elsif cmd == "REQ_UNASSIGN"
  599. if payload =~ /^([a-zA-Z_\.]+) (\d+\.\d+\.\d+\.\d+:\d{1,5})/
  600. specbot = $1
  601. hostport = $2
  602. write_user("REQ_UNASSIGN #{hostport}", specbot)
  603. else # of format check
  604. write_user("SYS Format is REQ_UNASSIGN <specbot> <ip:port>")
  605. end
  606. elsif cmd == "REQ_PING"
  607. if payload =~ /^([a-zA-Z_\.]+) (\d+\.\d+\.\d+\.\d+:\d{1,5})/
  608. specbot = $1
  609. hostport = $2
  610. write_user("REQ_PING #{hostport}", specbot)
  611. else # of format check
  612. write_user("SYS Format is REQ_PING <specbot> <ip:port>")
  613. end
  614. elsif cmd == "REQ_ASSIGNMENTS"
  615. if payload =~ /^([a-zA-Z_\.]+)/
  616. specbot = $1
  617. write_user("REQ_ASSIGNMENTS give me your assignments", specbot)
  618. else # of format check
  619. write_user("SYS Format is REQ_ASSIGNMENTS <specbot>")
  620. end
  621. elsif cmd == "REQ_MAXSERVERS"
  622. if payload =~ /^([a-zA-Z_\.]+)/
  623. specbot = $1
  624. write_user("REQ_MAXSERVERS how many do you do?", specbot)
  625. else # of format check
  626. write_user("SYS Format is REQ_MAXSERVERS <specbot>")
  627. end
  628. elsif cmd == "DUPECHECK"
  629. write_user("SYS specbot server monitoring dupecheck started")
  630. # start the central function for dupechecking:
  631. specbot_dupecheck()
  632. elsif cmd == "ELECTION"
  633. write_user("SYS specbot server ping election on global server list started")
  634. # start the central function for server election:
  635. all_servers = specbot_servers()
  636. put_log("#{all_servers}")
  637. write_user("SYS active unique server count: #{all_servers.size}")
  638. conns = connections_by_role("specbot")
  639. #für jeden server, jeden ping der einzelnen bots vergleichen..
  640. #falls ping info noch nicht vorhanden (>5000), dann einfordern
  641. all_servers.each_key{|k|
  642. put_log("SYS bots race for #{k} now..")
  643. conns.each{|c|
  644. ping = c.specbot_ping(k)
  645. if ping > 5000
  646. write_user("REQ_PING #{k}", c.username)
  647. end
  648. }
  649. EventMachine.add_timer 5, proc {
  650. lastbest = 5000
  651. winner = ""
  652. conns.each{|c|
  653. ping = c.specbot_ping(k)
  654. put_log("SYS #{ping} of #{c.username} to #{k}")
  655. if ping < lastbest
  656. lastbest = ping
  657. winner = c
  658. put_log("SYS - current best bot for #{k} is #{c.username}")
  659. end
  660. }
  661. put_log("SYS --> best bot for #{k} is #{winner.username}")
  662. conns.each{|c|
  663. a = c.specbot_active?(k)
  664. unless c == winner
  665. unless a == 0
  666. write_user("REQ_UNASSIGN #{k}", c.username)
  667. end
  668. end
  669. if c == winner
  670. unless a == 1
  671. as = all_servers[k]
  672. sp = as.fetch("s-p")
  673. if sp == "true" || sp == "1"
  674. write_user("REQ_ASSIGN #{k},true", c.username)
  675. else
  676. write_user("REQ_ASSIGN #{k}", c.username)
  677. end
  678. end
  679. end
  680. }
  681. } # end of timer
  682. } # end of all active servers loop
  683. ### the specbot ROLE does ...
  684. elsif cmd == "ASSIGNMENTS_RE"
  685. if payload =~ /^(\d+\.\d+\.\d+\.\d+:\d{1,5}),(.*),(.*)/
  686. hostport = $1
  687. sp = $2
  688. ping = $3.chomp
  689. p hostport
  690. p sp
  691. p ping
  692. current = Hash.new()
  693. current.store("active", 1)
  694. current.store("s-p", sp)
  695. current.store("ping", ping)
  696. # save the hash current to the hash @my_servers
  697. @my_servers[hostport] = @my_servers[hostport].merge(current)
  698. p @my_servers
  699. # save new hash to a config file or sth. fixme
  700. end
  701. #end of ASSIGNMENTS_RE here..
  702. elsif cmd == "MAXSERVERS_RE"
  703. if payload =~ /^(\d+)/
  704. @my_maxservers = $1
  705. end
  706. #end of MAXSERVERS_RE here..
  707. elsif cmd == "PING_RE"
  708. if payload =~ /^(\d+\.\d+\.\d+\.\d+:\d{1,5}),(.*)/
  709. hostport = $1
  710. ping = $2
  711. current = Hash.new()
  712. current.store("ping", ping)
  713. # save the hash current to the hash @my_servers
  714. @my_servers[hostport] = @my_servers[hostport].merge(current)
  715. p @my_servers
  716. # save new hash to a config file or sth. fixme
  717. end
  718. #end of PING_RE here..
  719. elsif cmd == "ASSIGN_RE"
  720. #assign_re should only be issued when a req_assign was issued to the specbot before
  721. if payload =~ /^(\d+\.\d+\.\d+\.\d+:\d{1,5}) ([A-Z]+)[\s]?(.*)/
  722. hostport = $1
  723. good = $2
  724. reason = $3
  725. if good == "OK"
  726. current = Hash.new()
  727. current.store("active", 1)
  728. @my_servers[hostport] = @my_servers[hostport].merge(current) # this way it preserves previously saved ping values
  729. write_user("REQ_PING #{hostport}")
  730. put_log "SYS #{username} assigned to #{hostport} and asked ping for it."
  731. write_role("specbot_admin", "SYS #{username} assigned to #{hostport} and asked ping for it.")
  732. else
  733. put_log "SYS #{username} failed to assign #{hostport}, reason: '#{reason}'"
  734. write_role("specbot_admin", "SYS #{username} failed to assign #{hostport}, reason: '#{reason}'")
  735. end
  736. end
  737. #end of ASSIGN_RE here..
  738. elsif cmd == "UNASSIGN_RE"
  739. if payload =~ /^(\d+\.\d+\.\d+\.\d+:\d{1,5}) ([A-Z]+)[\s]?(.*)/
  740. hostport = $1
  741. good = $2
  742. reason = $3
  743. if good == "OK"
  744. current = Hash.new()
  745. current.store("active", 0)
  746. @my_servers[hostport] = @my_servers[hostport].merge(current) # this way it preserves previously saved ping values
  747. put_log "SYS #{username} unassigned #{hostport}"
  748. write_role("specbot_admin", "SYS #{username} unassigned #{hostport}")
  749. else
  750. put_log "SYS #{username} failed to unassign #{hostport}, reason: '#{reason}'"
  751. write_role("specbot_admin", "SYS #{username} failed to unassign #{hostport}, reason: '#{reason}'")
  752. end
  753. end
  754. #end of UNASSIGN_RE here..
  755. elsif cmd == "REQ_DNS"
  756. # help with format .. but send the raw payload
  757. if payload =~ /^(\d+\.\d+\.\d+\.\d+):(\d{1,5})/
  758. iphost = $1
  759. ipport = $2
  760. targetname = $gs.get_cool_dns(iphost, ipport)
  761. write_user("DNS_RE #{iphost}:#{ipport} #{targetname}") # write back to asking user.
  762. else # of payload has format
  763. write_user("SYS Command format is REQ_DNS <serverip>:<serverport>")
  764. error = 1
  765. end # of if payload has format
  766. #end of REQ_DNS here..
  767. end
  768. else # of if allowed command
  769. if input.length > 15
  770. input = input.slice(0,14) + ".."
  771. end
  772. write_user("SYS Command '#{input}' not allowed, your commands are: #{my_cmds.join(", ")}.")
  773. end # of if allowed command
  774. end # of inputting!
  775. # default method that is being run on receiving data!
  776. def receive_data(data) # connection receives a line on the server end
  777. data = data.chomp unless data.chomp.nil?
  778. data.each_line do |line|
  779. unless line.empty?
  780. if entered_username? # oh, it's a known user
  781. # it's alive! kill ping timer, set a new one:
  782. if @ping_timer
  783. EventMachine.cancel_timer(@ping_timer)
  784. end
  785. @ping_timer = EventMachine.add_periodic_timer 180, proc { write_user("PING alive?") }
  786. # work on input
  787. inputter = inputting(line)
  788. if inputter == "bye"
  789. close_connection
  790. end
  791. else # of username known, then it seems to be the first connect
  792. # and this line is the user name coming in!
  793. username = line
  794. if online(username)
  795. put_log "SYS User '#{username}' tried to double connect. Say bye bye."
  796. send_data "SYS Hey '#{username}', only one connection allowed. Bye.\n"
  797. EventMachine.add_timer 1, proc {
  798. write_user("PING check forced", username)
  799. close_connection
  800. }
  801. return false
  802. end
  803. if $user_roles.any? {|k| k.include? username}
  804. @username = username
  805. @@connected_clients.push(self)
  806. @ping_timer = EventMachine.add_periodic_timer 90, proc { write_user("PING alive?") }
  807. # starting a pinger
  808. put_log("SYS '#{@username}' connected. Online now: #{ousers.join(", ")}")
  809. write_user("HELLO Hi user '#{@username}'! How are you? I'm '#{$version}'")
  810. write_user("ROLES #{my_roles.join(", ")}")
  811. write_user("COMMANDS #{my_cmds.join(", ")}")
  812. write_role($default_role, "JOINED User '#{@username}' just joined the party.", @username)
  813. # if that guy is a specbot, ask it for his current list
  814. if my_roles.include?("specbot")
  815. EventMachine.add_timer 2, proc {
  816. write_user("REQ_MAXSERVERS how many can you do?")
  817. write_user("REQ_ASSIGNMENTS gimme all your servers")
  818. }
  819. # if more than 1 specbots are connected, then we want to activate the dupe-check.
  820. if connections_by_role("specbot").size > 1
  821. # in 6 seconds we start it. by then, we should have the full serverlists of all bots
  822. EventMachine.add_timer 6, proc {
  823. put_log("this guy: #{username} triggers the dupecheck now")
  824. specbot_dupecheck
  825. }
  826. end
  827. end
  828. else
  829. put_log "SYS User '#{username}' unknown to me. Saying bye."
  830. send_data "SYS User '#{username}' unknown to me. Bye.\n"
  831. # disconnect him
  832. close_connection
  833. end
  834. end # of username known
  835. end # of unless line.empty?
  836. end # of data.each_line
  837. end # of receive data
  838. # default method that is being run on disconnection!
  839. def unbind # a connection goes bye bye
  840. if @username
  841. @@connected_clients.delete(self)
  842. if @ping_timer
  843. EventMachine.cancel_timer(@ping_timer)
  844. end
  845. put_log "SYS '#{@username}' disconnected."
  846. write_role($default_role, "SYS User '#{@username}' disconnected.", @username)
  847. end
  848. put_log "SYS Online users now: #{ousers.join(", ")}"
  849. end
  850. end
  851. def main
  852. # #run: Note that this will block current thread.
  853. $gs = GameServers.new
  854. EventMachine.run do
  855. # initial scan
  856. EM.defer do
  857. $gs.scanserverlist($qw_list, "qw") # initial scan
  858. end
  859. # periodic scan, 20 hours
  860. EventMachine.add_periodic_timer( 60 * 60 * 20 ) {
  861. EM.defer do
  862. $gs.scanserverlist($qw_list, "qw") # initial scan
  863. end
  864. }
  865. EventMachine.start_server("0.0.0.0", 7337, CentralProtocolHandler)
  866. end
  867. end # of main
  868. main