gui.lua 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. xban.gui = xban.gui or {}
  2. xban.gui.states = xban.gui.states or {}
  3. -- Localize for performance.
  4. local vector_round = vector.round
  5. local FORMNAME = "xban2:main"
  6. local MAXLISTSIZE = 1000
  7. local strfind, format = string.find, string.format
  8. local ESC = minetest.formspec_escape
  9. -- Get records of all registered players.
  10. local function make_list(filter)
  11. filter = filter and filter:split(",") or {}
  12. local list, dropped = { }, false
  13. -- Trim whitespace from filters.
  14. for k, v in ipairs(filter) do
  15. filter[k] = v:trim()
  16. end
  17. -- If a filter is chosen, only return filtered items.
  18. if #filter > 0 then
  19. for _, data in ipairs(xban.db) do
  20. for name, _ in pairs(data.names) do
  21. if not name:find("[%.%:]") then -- No IP addresses.
  22. for _, fname in ipairs(filter) do
  23. -- Plaintext search.
  24. if fname ~= "" then -- Don't search empty filters.
  25. -- Match real name or alias.
  26. if strfind(name, fname, 1, true) or strfind(rename.gpn(name), fname, 1, true) then
  27. if #list > MAXLISTSIZE then
  28. dropped = true
  29. goto done
  30. end
  31. -- Do not include administrators in the list.
  32. if not minetest.check_player_privs(name, {server=true}) then
  33. list[#list+1] = name -- Insert real name.
  34. end
  35. end
  36. end
  37. end
  38. end
  39. end
  40. end
  41. else
  42. for _, data in ipairs(xban.db) do
  43. for name, _ in pairs(data.names) do
  44. if not name:find("[%.%:]") then -- No IP addresses.
  45. if #list > MAXLISTSIZE then
  46. dropped = true
  47. goto done
  48. end
  49. -- Do not include administrators in the list.
  50. if not minetest.check_player_privs(name, {server=true}) then
  51. list[#list+1] = name
  52. end
  53. end
  54. end
  55. end
  56. end
  57. ::done::
  58. -- If filter has more than one entry, remove duplicates in the list.
  59. if #filter > 1 then
  60. local klist = {}
  61. for k, v in ipairs(list) do
  62. klist[v] = true
  63. end
  64. list = {}
  65. for k, v in pairs(klist) do
  66. list[#list+1] = k
  67. end
  68. end
  69. table.sort(list)
  70. return list, dropped
  71. end
  72. local states = xban.gui.states
  73. local function get_state(name)
  74. local state = states[name]
  75. if not state then
  76. state = { index=1, filter="" }
  77. states[name] = state
  78. state.list, state.dropped = make_list()
  79. end
  80. return state
  81. end
  82. local function get_record_simple(name)
  83. local e = xban.find_entry(name)
  84. if not e then
  85. return nil, {("No entry found for <%s>."):format(rename.gpn(name))}, false
  86. elseif (not e.record) or (#e.record == 0) then
  87. return e, {("Player <%s> has no ban records."):format(rename.gpn(name))}, false
  88. end
  89. local strings = {}
  90. -- Assemble ban record strings.
  91. for _, rec in ipairs(e.record) do
  92. local msg = (os.date("%Y-%m-%d %H:%M:%S", rec.time).." | "
  93. ..(rec.reason or "No reason given."))
  94. table.insert(strings, msg)
  95. end
  96. return e, strings, true
  97. end
  98. local function make_fs(pname)
  99. local state = get_state(pname)
  100. local list, filter = state.list, state.filter
  101. local pli, ei = state.player_index or 1, state.entry_index or 0
  102. if pli > #list then
  103. pli = #list
  104. end
  105. local fs = {
  106. "size[16,12]",
  107. default.gui_bg,
  108. default.gui_bg_img,
  109. default.gui_slots,
  110. "label[0,0.02;Filter]",
  111. "field[1.5,0.33;12.8,1;filter;;"..ESC(filter).."]",
  112. "button[14,0;2,1;search;Search]",
  113. "field_close_on_enter[filter;false]",
  114. }
  115. local fsn = #fs
  116. -- Translate internal player names to display names.
  117. local nlist = {}
  118. for k, v in ipairs(list) do
  119. local dn = rename.gpn(v)
  120. local rn = rename.grn(v)
  121. local ts = ac.get_total_suspicion(rn)
  122. if dn ~= rn then
  123. if ts > 0 then
  124. nlist[k] = ESC(dn .. " [" .. rn .. "] (" .. ts .. ")")
  125. else
  126. nlist[k] = ESC(dn .. " [" .. rn .. "]")
  127. end
  128. else
  129. if ts > 0 then
  130. nlist[k] = ESC(rn .. " (" .. ts .. ")")
  131. else
  132. nlist[k] = ESC(rn)
  133. end
  134. end
  135. end
  136. fsn=fsn+1 fs[fsn] = format("textlist[0,1.8;4,8;player;%s;%d;0]",
  137. table.concat(nlist, ","), pli)
  138. local record_name = list[pli]
  139. if record_name then
  140. local e, strings, gotten = get_record_simple(record_name)
  141. for i, r in ipairs(strings) do
  142. strings[i] = ESC(r)
  143. end
  144. -- Element field name changes based on whether we got a real set of ban records.
  145. fsn=fsn+1 fs[fsn] = format(
  146. "textlist[4.2,1.8;11.6,6;" .. (gotten and "entry" or "err") .. ";%s;%d;0]",
  147. table.concat(strings, ","), ei)
  148. local rec = e.record[ei]
  149. if #e.record > 0 then
  150. -- Ensure a valid record is selected.
  151. if not rec then
  152. rec = e.record[1]
  153. state.entry_index = 1
  154. ei = 1
  155. end
  156. fsn=fsn+1 fs[fsn] = format("label[0,10.3;%s]",
  157. ESC("Source: "..(rec.source or "<none>")
  158. .."\nDate: "..os.date("%c", rec.time)
  159. .."\n"..(rec.expires and os.date("Expires: %c", rec.expires) or "")
  160. .."\n"..(e.banned and "Status: Banned!" or "Player is not banned.")),
  161. pli) -- End format.
  162. else
  163. -- No ban records?
  164. fsn=fsn+1 fs[fsn] = format("label[0,10.3;%s]",
  165. ESC("Player <" .. rename.gpn(record_name) .. "> has no ban records.")
  166. ) -- End format.
  167. end
  168. -- Obtain all alternate names/IPs for this record.
  169. local names = {}
  170. local ips = {}
  171. for k, v in pairs(e.names) do
  172. if not k:find("[%.%:]") then
  173. names[#names+1] = rename.gpn(k)
  174. else
  175. ips[#ips+1] = k -- Is an IP address.
  176. end
  177. end
  178. local infomsg = {}
  179. -- Only the server operator should be able to see this info.
  180. -- Keep the admin's alt(s) top secret :)
  181. if minetest.check_player_privs(pname, {server=true}) then
  182. infomsg[#infomsg+1] = "Other names (" .. #names .. "): {"..table.concat(names, ", ").."}"
  183. if #ips <= 5 then
  184. infomsg[#infomsg+1] = "IPs used (" .. #ips .. "): ["..table.concat(ips, " | ").."]"
  185. else
  186. infomsg[#infomsg+1] = "IPs used (" .. #ips .. "): DYNAMIC"
  187. end
  188. end
  189. -- last_pos and last_seen are per name, not per record-entry.
  190. if type(e.last_pos) == "table" and e.last_pos[record_name] then
  191. infomsg[#infomsg+1] = "User was last seen at " ..
  192. rc.pos_to_namestr(vector_round(e.last_pos[record_name])) .. "."
  193. -- We can also add a button to allow the formspec user to jump to this
  194. -- location.
  195. if minetest.check_player_privs(pname, {teleport=true}) then
  196. fsn=fsn+1 fs[fsn] = "button[13,10.3;3,1;jump;Jump To Last Pos]"
  197. end
  198. end
  199. if type(e.last_seen) == "table" and e.last_seen[record_name] then
  200. infomsg[#infomsg+1] = "Last login: " ..
  201. os.date("!%Y/%m/%d, %H:%M:%S UTC", e.last_seen[record_name]) .. "."
  202. end
  203. if sheriff.is_cheater(record_name) then
  204. infomsg[#infomsg+1] = "Player is a registered cheater/hacker."
  205. elseif sheriff.is_suspected_cheater(record_name) then
  206. infomsg[#infomsg+1] = "Player is a suspected cheater!"
  207. end
  208. for k, v in ipairs(infomsg) do
  209. infomsg[k] = ESC(v)
  210. end
  211. fsn=fsn+1 fs[fsn] = "textlist[4.2,8.0;11.6,1.8;info;"..table.concat(infomsg, ",")..";0]"
  212. else
  213. local e = "No entry matches the query."
  214. fsn=fsn+1 fs[fsn] = "textlist[4.2,1.8;11.6,6;err;"..ESC(e)..";0]"
  215. fsn=fsn+1 fs[fsn] = "textlist[4.2,8.0;11.6,1.8;info;;0]"
  216. fsn=fsn+1 fs[fsn] = "label[0,10.3;"..ESC(e).."]"
  217. end
  218. return table.concat(fs)
  219. end
  220. function xban.gui.on_receive_fields(player, formname, fields)
  221. if formname ~= FORMNAME then return end
  222. local pname = player:get_player_name()
  223. if not minetest.check_player_privs(pname, { ban=true }) then
  224. minetest.log("warning", "[xban2] Received fields from unauthorized user: " .. pname)
  225. return true
  226. end
  227. local state = get_state(pname)
  228. if fields.player then
  229. local t = minetest.explode_textlist_event(fields.player)
  230. if (t.type == "CHG") or (t.type == "DCL") then
  231. state.player_index = t.index
  232. minetest.show_formspec(pname, FORMNAME, make_fs(pname))
  233. end
  234. return true
  235. end
  236. if fields.entry then
  237. local t = minetest.explode_textlist_event(fields.entry)
  238. if (t.type == "CHG") or (t.type == "DCL") then
  239. state.entry_index = t.index
  240. minetest.show_formspec(pname, FORMNAME, make_fs(pname))
  241. end
  242. return true
  243. end
  244. if fields.key_enter_field == "filter" or fields.search then
  245. local filter = fields.filter or ""
  246. state.filter = filter
  247. state.list = make_list(filter)
  248. minetest.show_formspec(pname, FORMNAME, make_fs(pname))
  249. end
  250. if fields.jump and minetest.check_player_privs(pname, {teleport=true}) then
  251. local list = state.list
  252. local pli = state.player_index or 1
  253. if pli > #list then
  254. pli = #list
  255. end
  256. local record_name = list[pli]
  257. if record_name then
  258. local e, strings, gotten = get_record_simple(record_name)
  259. if type(e.last_pos) == "table" and e.last_pos[record_name] then
  260. local pos = vector_round(table.copy(e.last_pos[record_name]))
  261. minetest.chat_send_player(pname,
  262. "# Server: Teleporting to <" .. rename.gpn(record_name) ..
  263. ">'s last known exit position at " .. rc.pos_to_namestr(pos) .. ".")
  264. rc.notify_realm_update(pname, pos)
  265. player:set_pos(pos)
  266. end
  267. end
  268. end
  269. return true
  270. end
  271. function xban.gui.chatcommand(name, params)
  272. minetest.show_formspec(name, FORMNAME, make_fs(name))
  273. end
  274. if not xban.gui.registered then
  275. minetest.register_on_player_receive_fields(function(...)
  276. return xban.gui.on_receive_fields(...)
  277. end)
  278. minetest.register_chatcommand("xban_gui", {
  279. description = "Show XBan GUI.",
  280. params = "",
  281. privs = { ban=true, },
  282. func = function(...)
  283. return xban.gui.chatcommand(...)
  284. end,
  285. })
  286. xban.gui.registered = true
  287. end