em_server.rb 30 KB

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