init.lua 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. --[[
  2. Copyright 2017-8 Auke Kok <sofar@foo-projects.org>
  3. Copyright 2018 rubenwardy <rw@rubenwardy.com>
  4. Permission is hereby granted, free of charge, to any person obtaining
  5. a copy of this software and associated documentation files (the
  6. "Software"), to deal in the Software without restriction, including
  7. without limitation the rights to use, copy, modify, merge, publish,
  8. distribute, sublicense, and/or sell copies of the Software, and to
  9. permit persons to whom the Software is furnished to do so, subject
  10. to the following conditions:
  11. The above copyright notice and this permission notice shall be included
  12. in all copies or substantial portions of the Software.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
  14. KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  15. WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  16. NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  17. LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  18. OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  19. WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  20. ]]--
  21. filter = { registered_on_violations = {} }
  22. local words = {}
  23. local muted = {}
  24. local violations = {}
  25. local s = minetest.get_mod_storage()
  26. function filter.init()
  27. local sw = s:get_string("words")
  28. if sw and sw ~= "" then
  29. words = minetest.parse_json(sw)
  30. end
  31. if #words == 0 then
  32. filter.import_file(minetest.get_modpath("filter") .. "/words.txt")
  33. end
  34. end
  35. function filter.import_file(filepath)
  36. local file = io.open(filepath, "r")
  37. if file then
  38. for line in file:lines() do
  39. line = line:trim()
  40. if line ~= "" then
  41. words[#words + 1] = line:trim()
  42. end
  43. end
  44. return true
  45. else
  46. return false
  47. end
  48. end
  49. function filter.register_on_violation(func)
  50. table.insert(filter.registered_on_violations, func)
  51. end
  52. function filter.check_message(name, message)
  53. for _, w in ipairs(words) do
  54. if string.find(message:lower(), "%f[%a]" .. w .. "%f[%A]") then
  55. return false
  56. end
  57. end
  58. return true
  59. end
  60. function filter.mute(name, duration)
  61. do
  62. local privs = minetest.get_player_privs(name)
  63. privs.shout = nil
  64. minetest.set_player_privs(name, privs)
  65. end
  66. minetest.chat_send_player(name, "Watch your language! You have been temporarily muted")
  67. muted[name] = true
  68. minetest.after(duration * 60, function()
  69. privs = minetest.get_player_privs(name)
  70. if privs.shout == true then
  71. return
  72. end
  73. muted[name] = nil
  74. minetest.chat_send_player(name, "Chat privilege reinstated. Please do not abuse chat.")
  75. privs.shout = true
  76. minetest.set_player_privs(name, privs)
  77. end)
  78. end
  79. function filter.show_warning_formspec(name)
  80. local formspec = "size[7,3]bgcolor[#080808BB;true]" .. default.gui_bg .. default.gui_bg_img .. [[
  81. image[0,0;2,2;filter_warning.png]
  82. label[2.3,0.5;Please watch your language!]
  83. ]]
  84. if minetest.global_exists("rules") and rules.show then
  85. formspec = formspec .. [[
  86. button[0.5,2.1;3,1;rules;Show Rules]
  87. button_exit[3.5,2.1;3,1;close;Okay]
  88. ]]
  89. else
  90. formspec = formspec .. [[
  91. button_exit[2,2.1;3,1;close;Okay]
  92. ]]
  93. end
  94. minetest.show_formspec(name, "filter:warning", formspec)
  95. end
  96. function filter.on_violation(name, message)
  97. violations[name] = (violations[name] or 0) + 1
  98. local resolution
  99. for _, cb in pairs(filter.registered_on_violations) do
  100. if cb(name, message, violations) then
  101. resolution = "custom"
  102. end
  103. end
  104. if not resolution then
  105. if violations[name] == 1 and minetest.get_player_by_name(name) then
  106. resolution = "warned"
  107. filter.show_warning_formspec(name)
  108. elseif violations[name] <= 3 then
  109. resolution = "muted"
  110. filter.mute(name, 1)
  111. else
  112. resolution = "kicked"
  113. minetest.kick_player(name, "Please mind your language!")
  114. end
  115. end
  116. local logmsg = "VIOLATION (" .. resolution .. "): <" .. name .. "> ".. message
  117. minetest.log("action", logmsg)
  118. local email_to = minetest.settings:get("filter.email_to")
  119. if email_to and minetest.global_exists("email") then
  120. email.send_mail(name, email_to, logmsg)
  121. end
  122. end
  123. table.insert(minetest.registered_on_chat_messages, 1, function(name, message)
  124. if message:sub(1, 1) == "/" then
  125. return
  126. end
  127. local privs = minetest.get_player_privs(name)
  128. if not privs.shout and muted[name] then
  129. minetest.chat_send_player(name, "You are temporarily muted.")
  130. return true
  131. end
  132. if not filter.check_message(name, message) then
  133. filter.on_violation(name, message)
  134. return true
  135. end
  136. end)
  137. local function make_checker(old_func)
  138. return function(name, param)
  139. if not filter.check_message(name, param) then
  140. filter.on_violation(name, param)
  141. return false
  142. end
  143. return old_func(name, param)
  144. end
  145. end
  146. for name, def in pairs(minetest.registered_chatcommands) do
  147. if def.privs and def.privs.shout then
  148. def.func = make_checker(def.func)
  149. end
  150. end
  151. local old_register_chatcommand = minetest.register_chatcommand
  152. function minetest.register_chatcommand(name, def)
  153. if def.privs and def.privs.shout then
  154. def.func = make_checker(def.func)
  155. end
  156. return old_register_chatcommand(name, def)
  157. end
  158. local function step()
  159. for name, v in pairs(violations) do
  160. violations[name] = math.floor(v * 0.5)
  161. if violations[name] < 1 then
  162. violations[name] = nil
  163. end
  164. end
  165. minetest.after(10*60, step)
  166. end
  167. minetest.after(10*60, step)
  168. minetest.register_chatcommand("filter", {
  169. params = "filter server",
  170. description = "manage swear word filter",
  171. privs = {server = true},
  172. func = function(name, param)
  173. local cmd, val = param:match("(%w+) (.+)")
  174. if param == "list" then
  175. return true, #words .. " words: " .. table.concat(words, ", ")
  176. elseif cmd == "add" then
  177. table.insert(words, val)
  178. s:set_string("words", minetest.write_json(words))
  179. return true, "Added \"" .. val .. "\"."
  180. elseif cmd == "remove" then
  181. for i, w in ipairs(words) do
  182. if w == val then
  183. table.remove(words, i)
  184. s:set_string("words", minetest.write_json(words))
  185. return true, "Removed \"" .. val .. "\"."
  186. end
  187. end
  188. return true, "\"" .. val .. "\" not found in list."
  189. else
  190. return true, "I know " .. #words .. " words.\nUsage: /filter <add|remove|list> [<word>]"
  191. end
  192. end,
  193. })
  194. if minetest.global_exists("rules") and rules.show then
  195. minetest.register_on_player_receive_fields(function(player, formname, fields)
  196. if formname == "filter:warning" and fields.rules then
  197. rules.show(player)
  198. end
  199. end)
  200. end
  201. minetest.register_on_shutdown(function()
  202. for name, _ in pairs(muted) do
  203. local privs = minetest.get_player_privs(name)
  204. privs.shout = true
  205. minetest.set_player_privs(name, privs)
  206. end
  207. end)
  208. filter.init()