-- LUALOCALS < --------------------------------------------------------- local io, ipairs, math, minetest, string, tonumber = io, ipairs, math, minetest, string, tonumber local io_close, io_open, math_random, string_format, string_gsub, string_match, string_sub = io.close, io.open, math.random, string.format, string.gsub, string.match, string.sub -- LUALOCALS > --------------------------------------------------------- local modname = minetest.get_current_modname() local modstore = minetest.get_mod_storage() local phashkey = minetest.settings:get("szutil_motd_hashkey") or "" local function phash(pname) if #phashkey < 1 then return "0000" end return string_sub(minetest.sha1(phashkey .. pname .. phashkey .. pname), 1, 4) end -- Permission to ignore all involuntary MOTD interaction. -- The command will still work, but it skips the warnings and -- mandatory display. local exempt = modname .. "_exempt" minetest.register_privilege(exempt, { description = "Player will not be alerted about MOTD", give_to_admin = false, give_to_singleplayer = false }) -- Function to read current MOTD local readmotd do local motdpath = minetest.get_worldpath() .. "/" .. modname .. ".txt" readmotd = function() local f = io_open(motdpath, "rb") if not f then return end local motd = f:read("*all") io_close(f) if not string_match(motd, "%S") then return end return motd end end -- Periodically scan for MOTD changes, and notify all online -- players if there are any. do local motdinterval = tonumber(minetest.settings:get(modname .. "_interval")) if motdinterval then minetest.log("info", "polling motd for changes every " .. motdinterval .. "s") local alertmotd = readmotd() local function alertcheck() minetest.after(motdinterval, alertcheck) local motd = readmotd() if motd == alertmotd then return end for _, p in ipairs(minetest.get_connected_players()) do if not minetest.check_player_privs(p, exempt) then minetest.chat_send_player(p:get_player_name(), "Updated MOTD. Please use /motd to review.") end end alertmotd = motd end minetest.after(motdinterval, alertcheck) end end -- Function to send the actual MOTD content to the player, in either -- automatic mode (on login) or "forced" mode (on player request). local function sendmotd(name, force) -- Never auto-display to exempt players. if (not force) and minetest.check_player_privs(name, exempt) then return end -- Load the MOTD fresh on each request, so changes can be -- made while the server is running, and take effect immediately. local motd = readmotd() if not motd then return end -- Compute a hash of the MOTD content, and figure out -- if a player has already seen this version. local hash = minetest.sha1(motd) local seen = (modstore:get_string(name) or "") == hash -- If player has seen this version and did not specifically -- request redisplay, just send a chat message reminding them that -- it's available if they want. if seen and not force then minetest.chat_send_player(name, "No MOTD changes since your last view. Use /motd command to " .. "review it any time.") return end -- Send MOTD as a nicely-formatted formspec popup. local fsw = tonumber(minetest.settings:get(modname .. "_width")) or 8.5 local fsh = tonumber(minetest.settings:get(modname .. "_height")) or 6 minetest.show_formspec(name, modname, "size[" .. fsw .. "," .. fsh .. ",true]" .. "textarea[0.3,0;" .. fsw .. "," .. fsh .. ";;;" .. minetest.formspec_escape(string_gsub(motd, "", phash(name))) .. "]button_exit[0," .. (fsh - 0.75) .. ";" .. fsw .. ",1;ok;" .. (minetest.settings:get(modname .. "_button") or "Continue") .. "]") -- If the player had already seen the MOTD (i.e. this is a -- forced request) then we don't need to update the database or -- send them a reminder. if seen then return end -- Update the seen database in-memory and on-disk -- so we don't send another copy of the same content to the -- same player automatically. modstore:set_string(name, hash) -- Remind the player where they can get the MOTD if they -- want it, and explain why it may or may not appear again -- automatically on future logins. minetest.chat_send_player(name, "Updated MOTD. It will not display again " .. "automatically, unless there are changes. Use /motd command to " .. "review it at any time.") end -- Display an "automatic" MOTD popup on new player joins, i.e. for completely -- new players, or those who have not seen the latest version yet. minetest.register_on_joinplayer(function(player) sendmotd(player:get_player_name()) end) -- Force popup display for players who request it via the /motd command. minetest.register_chatcommand("motd", {func = function(name) sendmotd(name, true) end}) -- Periodically scan the database for deleted players and -- garbage-collect agreement hashes from them. do local batch = {} local rescan = 0 minetest.register_globalstep(function(dtime) if #batch > 0 then local pname = batch[#batch] batch[#batch] = nil if not minetest.player_exists(pname) then minetest.log("info", string_format( "%s gc %s", modname, pname)) modstore:set_string(pname, "") end return end if rescan > 0 then rescan = rescan - dtime return end rescan = 3600 * (math_random() + 0.5) batch = modstore:get_keys() end) end