name_restrictions/init.lua

319 lines
8.3 KiB
Lua
Raw Normal View History

2014-06-23 23:05:51 +02:00
-- name_restrictions mod by ShadowNinja
-- License: WTFPL
2014-06-25 22:25:35 +02:00
----------------------------
-- Restriction exemptions --
----------------------------
-- For legitimate player names that are caught by the filters.
local exemptions = {}
2014-10-14 20:36:58 +02:00
local temp = minetest.setting_get("name_restrictions.exemptions")
2014-06-25 22:25:35 +02:00
temp = temp and temp:split() or {}
for _, allowed_name in pairs(temp) do
exemptions[allowed_name] = true
end
temp = nil
2014-10-14 20:37:34 +02:00
-- Exempt server owner
exemptions[minetest.setting_get("name")] = true
exemptions["singleplayer"] = true
2014-06-25 22:25:35 +02:00
local disallowed_names
local function load_forbidden_names()
disallowed_names = {}
local path = minetest.setting_get("name_restrictions.forbidden_names_list_path") or
minetest.get_worldpath("name_restrictions") .. "/forbidden_names.txt"
local file = io.open(path, 'r')
if file then
local count = 0
for line in file:lines() do
local low_line = line:lower()
disallowed_names[low_line] = true
count = count + 1
end
file:close()
return true, count .. " forbidden names loaded", 'verbose'
else
disallowed_names = {}
return true, path .. " doesn't exist, no forbidden names have been loaded", 'warning'
end
end
do
local ret, msg, lvl = load_forbidden_names()
if msg and lvl then
minetest.log(lvl, msg)
end
end
2014-06-23 23:05:51 +02:00
---------------------
-- Simple matching --
---------------------
local disallowed
2014-06-23 23:05:51 +02:00
local function load_disallowed()
local path = minetest.setting_get("name_restrictions.forbidden_name_patterns_list_path") or
minetest.get_worldpath("name_restrictions") .. "/forbidden_names_patterns.txt"
local file = io.open(path, 'r')
if file then
local content = file:read('*all')
local imported = minetest.deserialize(content)
local ret, msg, lvl = true, nil, nil
if imported == nil then
disallowed = disallowed or {}
ret, msg, lvl = false, "Failed to parse " .. path .. "; patterns not imported", 'error'
elseif type(imported) ~= 'table' then
disallowed = disallowed or {}
ret, msg, lvl = false, "Parsing " .. path .. " returned a " .. type(imported) ..
" where a table was expected; patterns not imported", 'error'
else
disallowed = imported
local count = 0
for _ in pairs(disallowed) do
count = count + 1
end
msg, lvl = count .. " forbidden name patterns loaded", 'verbose'
end
file:close()
return ret, msg, lvl
else
disallowed = {}
return true, path .. " doesn't exist, no forbidden name patterns have been loaded", 'warning'
end
end
do
local ret, msg, lvl = load_disallowed()
if msg and lvl then
minetest.log(lvl, msg)
end
end
minetest.register_on_prejoinplayer(function(name, ip)
2014-06-23 23:05:51 +02:00
local lname = name:lower()
for re, reason in pairs(disallowed) do
if lname:find(re) then
return reason
end
end
if disallowed_names[lname] then
return "Sorry. This name is forbidden."
end
2014-06-23 23:05:51 +02:00
end)
--------------------------------------
-- Simple matching config reloading --
--------------------------------------
minetest.register_chatcommand("forbidden_names_reload", {
params = "",
description = "Reloads forbidden_names match lists",
privs = {ban= true},
func = function(name)
local ret1, msg, lvl = load_forbidden_names()
if msg and lvl then
minetest.log(lvl, msg)
minetest.chat_send_player(name, msg)
end
local ret2
ret2, msg, lvl = load_disallowed()
if msg and lvl then
minetest.log(lvl, msg)
minetest.chat_send_player(name, msg)
end
return ret1 and ret2
end
})
2014-06-23 23:05:51 +02:00
------------------------
-- Case-insensitivity --
------------------------
minetest.register_on_prejoinplayer(function(name, ip)
local lname = name:lower()
local enumerate = minetest.get_auth_handler().enumerate_auths
for iname, data in enumerate and enumerate() or pairs(minetest.auth_table) do
2014-06-23 23:05:51 +02:00
if iname:lower() == lname and iname ~= name then
return "Sorry, someone else is already using this"
.." name. Please pick another name."
.." Another possibility is that you used the"
2014-06-23 23:05:51 +02:00
.." wrong case for your name."
end
end
end)
-- Compatability, for old servers with conflicting players
minetest.register_chatcommand("choosecase", {
description = "Choose the casing that a player name should have",
2014-06-23 23:05:51 +02:00
params = "<name>",
privs = {server = true},
2014-06-23 23:05:51 +02:00
func = function(name, params)
local lname = params:lower()
local worldpath = minetest.get_worldpath()
local enumerate = minetest.get_auth_handler().enumerate_auths
for iname, data in enumerate and enumerate() or pairs(minetest.auth_table) do
2014-06-23 23:05:51 +02:00
if iname:lower() == lname and iname ~= params then
local delete = minetest.get_auth_handler().delete_auth
if delete then
delete(iname)
else
minetest.auth_table[iname] = nil
end
2014-06-23 23:05:51 +02:00
assert(not iname:find("[/\\]"))
os.remove(worldpath .. "/players/" .. iname)
2014-06-23 23:05:51 +02:00
end
end
return true, "Done."
end,
})
------------------------
-- Anti-impersonation --
------------------------
2014-10-14 23:52:29 +02:00
-- Prevents names that are too similar to another player's name.
2014-06-23 23:05:51 +02:00
local similar_chars = {
-- Only A-Z, a-z, 1-9, dash, and underscore are allowed in playernames
2014-06-23 23:05:51 +02:00
"A4",
2014-06-25 22:25:35 +02:00
"B8",
2014-06-23 23:05:51 +02:00
"COco0",
"Ee3",
"Gg69",
"ILil1",
"S5",
"Tt7",
"Zz2",
}
-- Map of characters to a regex of similar characters
local char_map = {}
for _, str in pairs(similar_chars) do
for c in str:gmatch(".") do
if not char_map[c] then
char_map[c] = str
else
char_map[c] = char_map[c] .. str
end
end
end
for c, str in pairs(char_map) do
char_map[c] = "[" .. char_map[c] .."]"
end
-- Characters to match for, containing all characters
local all_chars = "["
for _, str in pairs(similar_chars) do
all_chars = all_chars .. str
end
all_chars = all_chars .. "]"
minetest.register_on_prejoinplayer(function(name, ip)
2014-06-25 22:25:35 +02:00
if exemptions[name] then return end
-- Generate a regular expression to match all similar names
2014-06-23 23:05:51 +02:00
local re = name:gsub(all_chars, char_map)
re = "^[_-]*" .. re .. "[_-]*$"
2014-06-25 22:25:35 +02:00
local enumerate = minetest.get_auth_handler().enumerate_auths
for authName, _ in enumerate and enumerate() or pairs(minetest.auth_table) do
2014-06-23 23:05:51 +02:00
if authName ~= name and authName:match(re) then
2014-10-14 23:52:29 +02:00
return "Your name is too similar to another player's name."
2014-06-23 23:05:51 +02:00
end
end
end)
2014-06-25 22:25:35 +02:00
-----------------
-- Name length --
-----------------
local min_name_len = tonumber(minetest.setting_get("name_restrictions.minimum_name_length")) or 2
local max_name_len = tonumber(minetest.setting_get("name_restrictions.maximum_name_length")) or 17
2014-06-25 22:25:35 +02:00
minetest.register_on_prejoinplayer(function(name, ip)
if exemptions[name] then return end
if #name < min_name_len then
return "Your player name is too short"
.. " (" .. #name .. " characters, must be " .. min_name_len .. " characters at least)."
.. " Please try a longer name."
end
if #name > max_name_len then
return "Your player name is too long"
.. " (" .. #name .. " characters, must be " .. max_name_len .. " characters at most)."
.. " Please try a shorter name."
2014-06-25 22:25:35 +02:00
end
end)
----------------------
-- Pronounceability --
----------------------
-- Original implementation (in Python) by sfan5
local function pronounceable(pronounceability, text)
2014-06-25 22:25:35 +02:00
local pronounceable = 0
local nonpronounceable = 0
local cn = 0
local lastc = ""
for c in text:lower():gmatch(".") do
if c:find("[aeiou0-9_-]") then
if not c:find("[0-9]") then
if cn > 2 then
nonpronounceable = nonpronounceable + 1
else
pronounceable = pronounceable + 1
end
end
cn = 0
else
if cn == 1 and lastc == c and lastc ~= 's' then
nonpronounceable = nonpronounceable + 1
cn = 0
end
if cn > 2 then
nonpronounceable = nonpronounceable + 1
cn = 0
end
if lastc:find("[aeiou]") then
pronounceable = pronounceable + 1
cn = 0
end
if not (c == "r" and lastc:find("[aipfom]")) and
not (lastc == "c" and c == "h") then
cn = cn + 1
end
end
lastc = c
end
if cn > 0 then
nonpronounceable = nonpronounceable + 1
end
return pronounceable * pronounceability >= nonpronounceable
2014-06-25 22:25:35 +02:00
end
-- Pronounceability factor:
-- nil = Checking disabled.
-- 0 = Everything's unpronounceable.
-- 0.5 = Strict checking.
-- 1 = Normal checking.
-- 2 = Relaxed checking.
local pronounceability = tonumber(minetest.setting_get("name_restrictions.pronounceability"))
if pronounceability then
minetest.register_on_prejoinplayer(function(name, ip)
if exemptions[name] then return end
if not pronounceable(pronounceability, name) then
return "Your player name does not seem to be pronounceable."
.." Please choose a more pronounceable name."
end
end)
end
2014-06-25 22:25:35 +02:00