Software / code / prosody-modules
File
mod_report_affiliations/mod_report_affiliations.lua @ 6195:886c985ece61
mod_lastlog2: Skip initializing internal API (and storage) in prosodyctl
Initializing storage in the global context under prosodyctl causes the
module.command to fail to execute because the storage module has already
been loaded.
Introduced in 7b722955c59b
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Sat, 08 Feb 2025 14:12:18 +0100 |
| parent | 6058:e905ef16efb7 |
line wrap: on
line source
local dt = require "util.datetime"; local jid = require "util.jid"; local st = require "util.stanza"; local rm = require "core.rostermanager"; local um = require "core.usermanager"; local traits = module:require("traits"); local xmlns_aff = "urn:xmpp:raa:0"; local is_host_anonymous = module:get_option_string("authentication") == "anonymous"; local trusted_servers = module:get_option_inherited_set("report_affiliations_trusted_servers", {}); local roles = { -- These affiliations are defined by XEP-0489, and we map a set of Prosody roles to each one admin = module:get_option_set("report_affiliations_admin_roles", { "prosody:admin", "prosody:operator" }); member = module:get_option_set("report_affiliations_member_roles", { "prosody:member" }); registered = module:get_option_set("report_affiliations_registered", { "prosody:user", "prosody:registered" }); guest = module:get_option_set("report_affiliations_anonymous", { "prosody:guest" }); }; -- Map of role to affiliation local role_affs = { -- [role (e.g. "prosody:guest")] = affiliation ("admin"|"member"|"registered"|"guest"); }; --Build the role->affiliation map based on the config for aff, aff_roles in pairs(roles) do for role in aff_roles do role_affs[role] = aff; end end local account_details_store = module:open_store("account_details"); local lastlog2_store = module:open_store("lastlog2"); module:add_feature(xmlns_aff); module:add_feature(xmlns_aff.."#embed-presence-sub"); module:add_feature(xmlns_aff.."#embed-presence-directed"); local function get_registered_timestamp(username) if um.get_account_info then local ts = um.get_account_info(username, module.host); if ts then return ts.created; end end local account_details = account_details_store:get(username); if account_details and account_details.registered then return account_details.registered; end local lastlog2 = lastlog2_store:get(username); if lastlog2 and lastlog2.registered then return lastlog2.registered.timestamp; end return nil; end local function get_trust_score(username) return math.floor(100 * (1 - traits.get_probability_bad(username))); end local function get_account_type(username) if is_host_anonymous then return "anonymous"; end if not um.get_user_role then return "registered"; -- COMPAT w/0.12 end local user_role = um.get_user_role(username, module.host); return role_affs[user_role] or "registered"; end function get_info_element(username) local account_type = get_account_type(username); local since, trust; if account_type == "registered" then since = get_registered_timestamp(username); trust = get_trust_score(username); end return st.stanza("info", { affiliation = account_type; since = since and dt.datetime(since - (since%86400)) or nil; trust = ("%d"):format(trust); xmlns = xmlns_aff; }); end -- Outgoing presence local function embed_in_outgoing_presence(pres_type) return function (event) local origin, stanza = event.origin, event.stanza; stanza:remove_children("info", xmlns_aff); -- Unavailable presence is pretty harmless, and blocking it may cause -- weird issues. if (pres_type == "bare" and stanza.attr.type == "unavailable") or (pres_type == "full" and stanza.attr.type ~= nil) then return; end -- Only attach info to stanzas sent to "strangers" (users that have not -- approved us to see their presence) if rm.is_user_subscribed(origin.username, origin.host, stanza.attr.to) then return; end local info = get_info_element(origin.username); if not info then return; end stanza:add_direct_child(info); end; end module:hook("pre-presence/bare", embed_in_outgoing_presence("bare")); module:hook("pre-presence/full", embed_in_outgoing_presence("full")); -- Handle direct queries local function should_permit_query(from_jid, to_username) --luacheck: ignore 212/to_username local from_node, from_host = jid.split(from_jid); if from_node then return false; end -- Who should we respond to? -- Only respond to domains -- Does user have a JID with this domain in directed presence? (doesn't work with bare JIDs) -- Does this user have a JID with domain in pending subscription requests? if trusted_servers:contains(from_host) then return true; end return false; end module:hook("iq-get/bare/urn:xmpp:raa:0:query", function (event) local origin, stanza = event.origin, event.stanza; local username = jid.node(stanza.attr.to); if not should_permit_query(stanza.attr.from, username) then origin.send(st.error_reply(stanza, "auth", "forbidden")); return true; end local info = get_info_element(username); local reply = st.reply(stanza) :add_child(info); origin.send(reply); return true; end);