mirror of
https://github.com/bjc/prosody.git
synced 2025-04-01 20:27:39 +03:00
152 lines
4.8 KiB
Lua
152 lines
4.8 KiB
Lua
local jid = require "prosody.util.jid";
|
|
local time = os.time;
|
|
|
|
local store = module:open_store(nil, "keyval+");
|
|
|
|
module:hook("authentication-success", function(event)
|
|
local session = event.session;
|
|
if session.username then
|
|
store:set_key(session.username, "timestamp", time());
|
|
end
|
|
end);
|
|
|
|
module:hook("resource-unbind", function(event)
|
|
local session = event.session;
|
|
if session.username then
|
|
store:set_key(session.username, "timestamp", time());
|
|
end
|
|
end);
|
|
|
|
local user_sessions = prosody.hosts[module.host].sessions;
|
|
function get_last_active(username) --luacheck: ignore 131/get_last_active
|
|
if user_sessions[username] then
|
|
return os.time(), true; -- Currently connected
|
|
else
|
|
local last_activity = store:get(username);
|
|
if not last_activity then return nil; end
|
|
return last_activity.timestamp;
|
|
end
|
|
end
|
|
|
|
module:add_item("shell-command", {
|
|
section = "user";
|
|
section_desc = "View user activity data";
|
|
name = "activity";
|
|
desc = "View the last recorded user activity for an account";
|
|
args = { { name = "jid"; type = "string" } };
|
|
host_selector = "jid";
|
|
handler = function(self, userjid) --luacheck: ignore 212/self
|
|
local username = jid.prepped_split(userjid);
|
|
local last_timestamp, is_online = get_last_active(username);
|
|
if not last_timestamp then
|
|
return true, "No activity";
|
|
end
|
|
|
|
return true, ("%s (%s)"):format(os.date("%Y-%m-%d %H:%M:%S", last_timestamp), (is_online and "online" or "offline"));
|
|
end;
|
|
});
|
|
|
|
module:add_item("shell-command", {
|
|
section = "user";
|
|
section_desc = "View user activity data";
|
|
name = "list_inactive";
|
|
desc = "List inactive user accounts";
|
|
args = {
|
|
{ name = "host"; type = "string" };
|
|
{ name = "duration"; type = "string" };
|
|
};
|
|
host_selector = "host";
|
|
handler = function(self, host, duration) --luacheck: ignore 212/self
|
|
local um = require "prosody.core.usermanager";
|
|
local duration_sec = require "prosody.util.human.io".parse_duration(duration or "");
|
|
if not duration_sec then
|
|
return false, ("Invalid duration %q - try something like \"30d\""):format(duration);
|
|
end
|
|
|
|
local now = os.time();
|
|
local n_inactive, n_unknown = 0, 0;
|
|
|
|
for username in um.users(host) do
|
|
local last_active = store:get_key(username, "timestamp");
|
|
if not last_active then
|
|
local created_at = um.get_account_info(username, host).created;
|
|
if created_at and (now - created_at) > duration_sec then
|
|
self.session.print(username, "");
|
|
n_inactive = n_inactive + 1;
|
|
elseif not created_at then
|
|
n_unknown = n_unknown + 1;
|
|
end
|
|
elseif (now - last_active) > duration_sec then
|
|
self.session.print(username, os.date("%Y-%m-%dT%T", last_active));
|
|
n_inactive = n_inactive + 1;
|
|
end
|
|
end
|
|
|
|
if n_unknown > 0 then
|
|
return true, ("%d accounts inactive since %s (%d unknown)"):format(n_inactive, os.date("%Y-%m-%dT%T", now - duration_sec), n_unknown);
|
|
end
|
|
return true, ("%d accounts inactive since %s"):format(n_inactive, os.date("%Y-%m-%dT%T", now - duration_sec));
|
|
end;
|
|
});
|
|
|
|
module:add_item("shell-command", {
|
|
section = "migrate";
|
|
section_desc = "Perform data migrations";
|
|
name = "account_activity_lastlog2";
|
|
desc = "Migrate account activity information from mod_lastlog2";
|
|
args = { { name = "host"; type = "string" } };
|
|
host_selector = "host";
|
|
handler = function(self, host) --luacheck: ignore 212/host
|
|
local lastlog2 = module:open_store("lastlog2", "keyval+");
|
|
local n_updated, n_errors, n_skipped = 0, 0, 0;
|
|
|
|
local async = require "prosody.util.async";
|
|
|
|
local p = require "prosody.util.promise".new(function (resolve)
|
|
local async_runner = async.runner(function ()
|
|
local n = 0;
|
|
for username in lastlog2:items() do
|
|
n = n + 1;
|
|
if n % 100 == 0 then
|
|
self.session.print(("Processed %d..."):format(n));
|
|
async.sleep(0);
|
|
end
|
|
local lastlog2_data = lastlog2:get(username);
|
|
if lastlog2_data then
|
|
local current_data, err = store:get(username);
|
|
if not current_data then
|
|
if not err then
|
|
current_data = {};
|
|
else
|
|
n_errors = n_errors + 1;
|
|
end
|
|
end
|
|
if current_data then
|
|
local imported_timestamp = current_data.timestamp;
|
|
local latest;
|
|
for k, v in pairs(lastlog2_data) do
|
|
if k ~= "registered" and (not latest or v.timestamp > latest) then
|
|
latest = v.timestamp;
|
|
end
|
|
end
|
|
if latest and (not imported_timestamp or imported_timestamp < latest) then
|
|
local ok, err = store:set_key(username, "timestamp", latest);
|
|
if ok then
|
|
n_updated = n_updated + 1;
|
|
else
|
|
self.session.print(("WW: Failed to import %q: %s"):format(username, err));
|
|
n_errors = n_errors + 1;
|
|
end
|
|
else
|
|
n_skipped = n_skipped + 1;
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return resolve(("%d accounts imported, %d errors, %d skipped"):format(n_updated, n_errors, n_skipped));
|
|
end);
|
|
async_runner:run(true);
|
|
end);
|
|
return p;
|
|
end;
|
|
});
|