1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
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;
});
|