Aaron Suen 7175f51921 Maintain our own last-seen timestamp for players
We actually want to prune accounts based on the
last time they were used or seen online at all, not
just the last time they successfully joined the
world.  Keeping track of our own timestamp should
catch scenarios like players sucessfully authing
but failing to complete joining the world, or players
who log in and then play for a long time after
login.  This also makes it safer to use with much
shorter timeout periods and longer-running
servers.
2022-11-05 13:52:15 -04:00

144 lines
3.7 KiB
Lua

-- LUALOCALS < ---------------------------------------------------------
local ipairs, math, minetest, os, string, tonumber
= ipairs, math, minetest, os, string, tonumber
local math_random, os_time, string_format
= math.random, os.time, string.format
-- LUALOCALS > ---------------------------------------------------------
local modname = minetest.get_current_modname()
local modstore = minetest.get_mod_storage()
local scaninterval = tonumber(minetest.settings:get(modname .. "_interval")) or 3600
local maxdays = tonumber(minetest.settings:get(modname .. "_maxdays")) or 90
local keepauth = minetest.settings:get_bool(modname .. "_keepauth")
local protectnames = {
singleplayer = true,
[minetest.settings:get("name")] = true,
}
local noprune = "noprune"
minetest.register_privilege(noprune, {
description = "Do not automatically prune player for unuse",
give_to_admin = false,
give_to_singleplayer = false,
})
------------------------------------------------------------------------
local queue_current
local queue_next
local queue_pos = 1
local function processqueue()
if queue_current then
local act = queue_current[queue_pos]
act()
queue_pos = queue_pos + 1
if queue_pos > #queue_current then
queue_current = nil
queue_pos = 1
end
end
if not queue_current then
queue_current = queue_next
queue_next = nil
end
if queue_current then
minetest.after(0, processqueue)
end
end
local function enqueue(act)
queue_next = queue_next or {}
queue_next[#queue_next + 1] = act
if not queue_current then
queue_current = queue_next
queue_next = nil
minetest.after(0, processqueue)
end
end
------------------------------------------------------------------------
do
local function bump(pname)
modstore:set_string(pname, string_format("%d", os_time()))
end
minetest.register_on_authplayer(function(pname, _, success)
if success then bump(pname) end
end)
minetest.register_on_joinplayer(function(player)
bump(player:get_player_name())
end)
minetest.register_on_leaveplayer(function(player)
bump(player:get_player_name())
end)
local function scanall()
for _, player in ipairs(minetest.get_connected_players()) do
bump(player:get_player_name())
end
end
minetest.register_on_shutdown(scanall)
local function bgscan()
minetest.after(1, bgscan)
return scanall()
end
minetest.after(0, bgscan)
end
------------------------------------------------------------------------
local function check(pname)
if protectnames[pname] or minetest.get_player_by_name(pname) then return end
local handler = minetest.get_auth_handler()
local data = handler.get_auth(pname)
if not data then return end
if data.privileges[noprune] then return end
local logintime = data.last_login
local seentime = tonumber(modstore:get_string(pname)) or 0
local seen = (logintime > seentime) and logintime or seentime
local now = os_time()
local daysago = (now - seen) / 86400
if daysago <= maxdays then return end
modstore:set_string(pname, "")
minetest.log("warning", string_format(
"Deleting player %q last seen %0.1f day(s) ago",
pname, daysago))
minetest.remove_player(pname)
if not keepauth then
minetest.log("warning", string_format(
"Purging player %q auth", pname))
return minetest.remove_player_auth(pname)
end
end
local function runscan()
local handler = minetest.get_auth_handler()
local names = {}
for name in handler.iterate() do
names[#names + 1] = name
end
for i = #names, 2, -1 do
local j = math_random(1, i)
local x = names[i]
names[i] = names[j]
names[j] = x
end
for i = 1, #names do
local pname = names[i]
enqueue(function() return check(pname) end)
end
end
local function startscan()
enqueue(runscan)
minetest.after(scaninterval, startscan)
end
minetest.after(0, startscan)