qstat.tcl 7.6 KB

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