qstat.tcl 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. # qstat.tcl
  2. #
  3. # This script stores game servers in a server list file and queries their
  4. # status with the utility "qstat" with the server commands below.
  5. #
  6. # Usage:
  7. # !addserver server add server to server list
  8. # !delserver server remove server from server list
  9. # !serverlist show servers in server list
  10. # !refresh query status of servers in server list
  11. #
  12. # Enable for a channel with: .chanset #channel +qstat
  13. # Disable for a channel with: .chanset #channel -qstat
  14. # tested versions, might run on earlier versions
  15. package require Tcl 8.6
  16. package require eggdrop 1.8.4
  17. namespace eval ::qstat {
  18. # channel flag for enabling/disabling
  19. setudef flag qstat
  20. # command names
  21. variable addcommand "!addserver"
  22. variable delcommand "!delserver"
  23. variable showcommand "!serverlist"
  24. variable refreshcommand "!refresh"
  25. # path to your qstat binary
  26. variable qstat "/usr/local/bin/qstat"
  27. # qstat options for querying all servers
  28. variable optionsall "-nh -u -default q2s"
  29. # qstat options for querying single server
  30. variable optionssingle "-nh -P -sort F -u -q2s"
  31. # file to store servers in and its backup file
  32. variable file "servers.lst"
  33. variable filebak "servers.lst.bak"
  34. }
  35. # read server list from server file
  36. proc ::qstat::fileGet {} {
  37. variable file
  38. # check is server list entries exist
  39. if {![file exists $file] || [file size $file] == 0} {
  40. return ""
  41. }
  42. # read servers from server file
  43. set servers ""
  44. if {[catch {open $file r} input]} {
  45. return ""
  46. }
  47. while {[gets $input line] >= 0} {
  48. lappend servers $line
  49. }
  50. close $input
  51. return $servers
  52. }
  53. # this procedure shows the saved servers:
  54. proc ::qstat::showServers {nick host hand chan arg} {
  55. # check channel flag if enabled in this channel
  56. if {![channel get $chan qstat]} {
  57. return 0
  58. }
  59. # nick must be op
  60. if {![isop $nick $chan]} {
  61. return 0
  62. }
  63. # read servers from server file
  64. set servers [fileGet]
  65. if {$servers == ""} {
  66. puthelp "PRIVMSG $nick :No servers in server list."
  67. return 0
  68. }
  69. # send each server as a separate message
  70. puthelp "PRIVMSG $nick :*** Server List ***:"
  71. set i 1
  72. foreach s $servers {
  73. puthelp "PRIVMSG $nick :($i) $s"
  74. incr i
  75. }
  76. return 1
  77. }
  78. # this procedure deletes saved servers:
  79. proc ::qstat::delServer {nick host hand chan arg} {
  80. variable file
  81. variable filebak
  82. # check channel flag if enabled in this channel
  83. if {![channel get $chan qstat]} {
  84. return 0
  85. }
  86. # nick must be op
  87. if {![isop $nick $chan]} {
  88. return 0
  89. }
  90. # read servers from server file
  91. set servers [fileGet]
  92. # check if argument contains a valid server number
  93. if {$arg == "" || $arg > [llength $servers] || $arg == 0} {
  94. puthelp "NOTICE $nick :Invalid server number."
  95. return 0
  96. }
  97. # backup server file
  98. file copy -force $file $filebak
  99. # write servers to server file, omitting deleted server
  100. if {[catch {open $file w} output]} {
  101. puthelp "NOTICE $nick :Error opening file: $output"
  102. putlog "match.tcl: ERROR! Error opening file: $output"
  103. return 0
  104. }
  105. set i 1
  106. foreach s $servers {
  107. if {$i != $arg} {
  108. puts $output $s
  109. }
  110. incr i
  111. }
  112. close $output
  113. puthelp "NOTICE $nick :Attempted to delete server number $arg."
  114. putlog "match.tcl: $nick@$chan attempted to delete server number $arg."
  115. return 1
  116. }
  117. # this procedure adds matches to the list:
  118. proc ::qstat::addServer {nick host hand chan arg} {
  119. variable file
  120. # check channel flag if enabled in this channel
  121. if {![channel get $chan qstat]} {
  122. return 0
  123. }
  124. # nick must be op
  125. if {![isop $nick $chan]} {
  126. return 0
  127. }
  128. # check if arg contains valid ip and port
  129. if { $arg == "" } {
  130. puthelp "NOTICE $nick :Can't add empty entry"
  131. return 0
  132. }
  133. # NOTE: this only checks ipv4 addresses
  134. set match [regexp {[\d]+.[\d]+.[\d]+.[\d]+:[\d]+} $arg matchl]
  135. if { $match != 1 } {
  136. return 0
  137. }
  138. # append server to server file
  139. if {[catch {open $file a} output]} {
  140. puthelp "NOTICE $nick :Error opening file: $output"
  141. putlog "match.tcl: ERROR! Error opening file: $output"
  142. return 0
  143. }
  144. puts $output "$arg"
  145. close $output
  146. puthelp "NOTICE $nick :Attempted to add server."
  147. putlog "match.tcl: $nick@$chan attempted to add a server to the list"
  148. return 1
  149. }
  150. # format a server info line
  151. proc ::qstat::formatServerLine {line servers} {
  152. # check if line is a server line and parse it
  153. set pattern {(?x)
  154. # server address + whitespace
  155. ([\d]+.[\d]+.[\d]+.[\d]+:[\d]+)[\s]+
  156. # players cur/max + whitespace
  157. ([\d]+/[\d]+)[\s]+
  158. # spectators cur/max + whitespace
  159. ([\d]+/[\d]+)[\s]+
  160. # map + whitespace
  161. ([\w]+)[\s]+
  162. # ping, retries + whitespace
  163. ([\d]+)[\s]*/[\s]*[\d]+[\s]+
  164. # server name
  165. (.+)
  166. }
  167. if {[regexp $pattern $line matchln address players spectators \
  168. map ping name] != 1} {
  169. return ""
  170. }
  171. # format the output
  172. set number [expr {[lsearch $servers $address] +1}]
  173. set fmt "%s \0030,1\00307%-21s \00315%-45s \0034%-7s \00315%-10s"
  174. return [format $fmt ($number) $address $name ($players) ($map)]
  175. }
  176. # format a player info line
  177. proc ::qstat::formatPlayerLine {line} {
  178. # check if line is a player line and parse it
  179. set pattern {(?x)
  180. # frags
  181. [\s]*(-*[\d]+)[\s]*frags
  182. # ping
  183. [\s]*([\d]+)ms
  184. # player name
  185. [\s]*(.+)
  186. }
  187. if {[regexp $pattern $line matchln playerfrags playerping \
  188. playername] != 1} {
  189. return ""
  190. }
  191. # format the output
  192. set p "\0030,1\00307$playername \00315(${playerfrags} frags, "
  193. set p "$p\00304${playerping}ms)"
  194. return $p
  195. }
  196. # query all servers in server list with qstat
  197. proc ::qstat::refreshAll {nick chan servers} {
  198. variable file
  199. variable qstat
  200. variable optionsall
  201. # run qstat and parse output
  202. if {[catch {open "|$qstat $optionsall -f $file" r} input]} {
  203. puthelp "PRIVMSG $nick :Error refreshing servers: $input"
  204. return 0
  205. }
  206. while {[gets $input line] >= 0} {
  207. # show each server line in the channel
  208. set result [formatServerLine $line $servers]
  209. if {$result != ""} {
  210. puthelp "PRIVMSG $chan :$result"
  211. }
  212. }
  213. close $input
  214. }
  215. # query a single server with qstat
  216. proc ::qstat::refreshSingle {nick chan servers server} {
  217. variable qstat
  218. variable optionssingle
  219. # run qstat and parse output
  220. set playerlist ""
  221. if {[catch {open "|$qstat $optionssingle $server" r} input]} {
  222. puthelp "NOTICE $nick :Error refreshing server: $input"
  223. return 0
  224. }
  225. while {[gets $input line] >= 0} {
  226. # show each server line in the channel
  227. set result [formatServerLine $line $servers]
  228. if {$result != ""} {
  229. puthelp "PRIVMSG $chan :$result"
  230. }
  231. # look for players and add them to player list
  232. set result [formatPlayerLine $line]
  233. if {$result != ""} {
  234. lappend playerlist $result
  235. }
  236. }
  237. close $input
  238. # show the player list in the channel
  239. if {$playerlist != ""} {
  240. set players [join $playerlist ", "]
  241. puthelp "PRIVMSG $chan :$players"
  242. }
  243. }
  244. # query servers with qstat
  245. proc ::qstat::refreshServers {nick host hand chan arg} {
  246. # check channel flag if enabled in this channel
  247. if {![channel get $chan qstat]} {
  248. return 0
  249. }
  250. # read servers from server file
  251. set servers [fileGet]
  252. if {$servers == ""} {
  253. puthelp "PRIVMSG $nick :No servers in server list."
  254. return 0
  255. }
  256. if {$arg == ""} {
  257. # no extra parameters, refresh all servers
  258. refreshAll $nick $chan $servers
  259. } else {
  260. # only refresh server specified in arg
  261. set server [lindex $servers [expr {$arg -1}]]
  262. if {$server == ""} {
  263. puthelp "PRIVMSG $nick :Server not found."
  264. return 0
  265. }
  266. refreshSingle $nick $chan $servers $server
  267. }
  268. }
  269. namespace eval ::qstat {
  270. bind pub - $showcommand ::qstat::showServers
  271. bind pub - $addcommand ::qstat::addServer
  272. bind pub - $delcommand ::qstat::delServer
  273. bind pub - $refreshcommand ::qstat::refreshServers
  274. putlog "Loaded qstat.tcl"
  275. }