aboutsummaryrefslogtreecommitdiffstats
path: root/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'plugins')
-rw-r--r--plugins/mod_account_activity.lua152
-rw-r--r--plugins/mod_storage_sql.lua2
2 files changed, 153 insertions, 1 deletions
diff --git a/plugins/mod_account_activity.lua b/plugins/mod_account_activity.lua
new file mode 100644
index 00000000..63a7fb39
--- /dev/null
+++ b/plugins/mod_account_activity.lua
@@ -0,0 +1,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);
+ 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;
+});
diff --git a/plugins/mod_storage_sql.lua b/plugins/mod_storage_sql.lua
index fa0588b2..ff44adba 100644
--- a/plugins/mod_storage_sql.lua
+++ b/plugins/mod_storage_sql.lua
@@ -42,7 +42,7 @@ end
local function has_upsert(engine)
if engine.params.driver == "SQLite3" then
-- SQLite3 >= 3.24.0
- return (engine.sqlite_version[2] or 0) >= 24;
+ return engine.sqlite_version and (engine.sqlite_version[2] or 0) >= 24;
elseif engine.params.driver == "PostgreSQL" then
-- PostgreSQL >= 9.5
-- Versions without support have long since reached end of life.