Diff

mod_push2/mod_push2.lua @ 6232:d72010642b31 draft

Merge update
author Trần H. Trung <xmpp:trần.h.trung@trung.fun>
date Fri, 11 Apr 2025 23:19:21 +0700
parent 6219:06621ab30be0
child 6233:1c16bb49f6f6
line wrap: on
line diff
--- a/mod_push2/mod_push2.lua	Tue Mar 18 00:31:36 2025 +0700
+++ b/mod_push2/mod_push2.lua	Fri Apr 11 23:19:21 2025 +0700
@@ -9,11 +9,14 @@
 local crypto = require "util.crypto";
 local jwt = require "util.jwt";
 
+pcall(function() module:depends("track_muc_joins") end)
+
 local xmlns_push = "urn:xmpp:push2:0";
 
 -- configuration
 local contact_uri = module:get_option_string("contact_uri", "xmpp:" .. module.host)
 local extended_hibernation_timeout = module:get_option_number("push_max_hibernation_timeout", 72*3600)  -- use same timeout like ejabberd
+local hibernate_past_first_push = module:get_option_boolean("hibernate_past_first_push", true)
 
 local host_sessions = prosody.hosts[module.host].sessions
 local push2_registrations = module:open_store("push2_registrations", "keyval")
@@ -28,7 +31,22 @@
 module:hook("account-disco-info", account_dico_info);
 
 local function parse_match(matchel)
-		local match = { match = matchel.attr.profile }
+		local match = { match = matchel.attr.profile, chats = {} }
+
+		for chatel in matchel:childtags("chat") do
+			local chat = {}
+			if chatel:get_child("mention") then
+				chat.mention = true
+			end
+			if chatel:get_child("reply") then
+				chat.reply = true
+			end
+			match.chats[chatel.attr.jid] = chat
+		end
+
+		match.grace = matchel:get_child_text("grace")
+		if match.grace then match.grace = tonumber(match.grace) end
+
 		local send = matchel:get_child("send", "urn:xmpp:push2:send:notify-only:0")
 		if send then
 			match.send = send.attr.xmlns
@@ -156,7 +174,7 @@
 end
 
 -- is this push a high priority one
-local function is_important(stanza)
+local function is_important(stanza, session)
 	local is_voip_stanza, urgent_reason = is_voip(stanza)
 	if is_voip_stanza then return true; end
 
@@ -177,8 +195,17 @@
 		-- carbon copied outgoing messages are not important
 		if carbon and stanza_direction == "out" then return false; end
 
-		-- groupchat subjects are not important here
-		if st_type == "groupchat" and stanza:get_child_text("subject") then
+		-- groupchat reflections are not important here
+		if st_type == "groupchat" and session and session.rooms_joined then
+			local muc = jid.bare(stanza.attr.from)
+			local from_nick = jid.resource(stanza.attr.from)
+			if from_nick == session.rooms_joined[muc] then
+				return false
+			end
+		end
+
+		-- edits are not imporatnt
+		if stanza:get_child("replace", "urn:xmpp:message-correct:0") then
 			return false
 		end
 
@@ -222,7 +249,7 @@
 	end
 	if string.len(envelope_bytes) > max_data_size then
 		local body = stanza:get_child_text("body")
-		if string.len(body) > 50 then
+		if body and string.len(body) > 50 then
 			stanza_clone:maptags(function(el)
 				if el.name == "body" then
 					return nil
@@ -251,8 +278,9 @@
 		end)
 		envelope_bytes = tostring(envelope)
 	end
-	if string.len(envelope_bytes) < max_data_size/2 then
-		envelope:text_tag("rpad", base64.encode(random.bytes(math.min(150, max_data_size/3 - string.len(envelope_bytes)))))
+	local padding_size = math.min(150, max_data_size/3 - string.len(envelope_bytes))
+	if padding_size > 0 then
+		envelope:text_tag("rpad", base64.encode(random.bytes(padding_size)))
 		envelope_bytes = tostring(envelope)
 	end
 
@@ -294,12 +322,12 @@
 	push_notification_payload:text_tag("jwt", signer(payload), { key = base64.encode(public_key) })
 end
 
-local function handle_notify_request(stanza, node, user_push_services, log_push_decline)
+local function handle_notify_request(stanza, node, user_push_services, session, log_push_decline)
 	local pushes = 0;
 	if not #user_push_services then return pushes end
 
 	local notify_push_services = {};
-	if is_important(stanza) then
+	if is_important(stanza, session) then
 		notify_push_services = user_push_services
 	else
 		for identifier, push_info in pairs(user_push_services) do
@@ -329,7 +357,7 @@
 		if send_push then
 			local push_notification_payload = st.stanza("notification", { xmlns = xmlns_push })
 			push_notification_payload:text_tag("client", push_info.client)
-			push_notification_payload:text_tag("priority", is_voip(stanza) and "high" or (is_important(stanza) and "normal" or "low"))
+			push_notification_payload:text_tag("priority", is_voip(stanza) and "high" or (is_important(stanza, session) and "normal" or "low"))
 			if is_voip(stanza) then
 				push_notification_payload:tag("voip"):up()
 			end
@@ -340,13 +368,47 @@
 				if match.match == "urn:xmpp:push2:match:all" then
 					does_match = true
 				elseif match.match == "urn:xmpp:push2:match:important" then
-					does_match = is_important(stanza)
+					does_match = is_important(stanza, session)
 				elseif match.match == "urn:xmpp:push2:match:archived" then
 					does_match = stanza:get_child("stana-id", "urn:xmpp:sid:0")
 				elseif match.match == "urn:xmpp:push2:match:archived-with-body" then
 					does_match = stanza:get_child("stana-id", "urn:xmpp:sid:0") and has_body(stanza)
 				end
 
+				local to_user, to_host = jid.split(stanza.attr.to)
+				to_user = to_user or session.username
+				to_host = to_host or module.host
+
+				-- If another session has recent activity within configured grace period, don't send push
+				if does_match and match.grace and to_host == module.host and host_sessions[to_user] then
+					local now = os_time()
+					for _, session in pairs(host_sessions[to_user].sessions) do
+						if session.last_activity and session.push_registration_id ~= push_registration_id and (now - session.last_activity) < match.grace then
+							does_match = false
+						end
+					end
+				end
+
+				local chat = match.chats and (match.chats[stanza.attr.from] or match.chats[jid.bare(stanza.attr.from)] or match.chats[jid.host(stanza.attr.from)])
+				if does_match and chat then
+					does_match = false
+
+					local nick = (session.rooms_joined and session.rooms_joined[jid.bare(stanza.attr.from)]) or to_user
+
+					if not does_match and chat.mention then
+						local body = stanza:get_child_text("body")
+						if body and body:find(nick, 1, true) then
+							does_match = true
+						end
+					end
+					if not does_match and chat.reply then
+						local reply = stanza:get_child("reply", "urn:xmpp:reply:0")
+						if reply and (reply.attr.to == to_user.."@"..to_host or (jid.bare(reply.attr.to) == jid.bare(stanza.attr.from) and jid.resource(reply.attr.to) == nick)) then
+							does_match = true
+						end
+					end
+				end
+
 				if does_match and not sends_added[match.send] then
 					sends_added[match.send] = true
 					if match.send == "urn:xmpp:push2:send:notify-only" then
@@ -384,7 +446,7 @@
 module:hook("message/offline/handle", function(event)
 	local node, user_push_services = get_push_settings(event.stanza, event.origin);
 	module:log("debug", "Invoking handle_notify_request() for offline stanza");
-	handle_notify_request(event.stanza, node, user_push_services, true);
+	handle_notify_request(event.stanza, node, user_push_services, event.origin, true);
 end, 1);
 
 -- publish on bare groupchat
@@ -402,7 +464,7 @@
 		end
 	end
 
-	handle_notify_request(event.stanza, node, notify_push_services, true);
+	handle_notify_request(event.stanza, node, notify_push_services, event.origin, true);
 end, 1);
 
 local function process_stanza_queue(queue, session, queue_type)
@@ -415,14 +477,14 @@
 			local node, all_push_services = get_push_settings(stanza, session)
 			local user_push_services = {[session.push_registration_id] = all_push_services[session.push_registration_id]}
 			local stanza_type = "unimportant";
-			if is_important(stanza) then stanza_type = "important"; end
+			if is_important(stanza, session) then stanza_type = "important"; end
 			if not notified[stanza_type] then		-- only notify if we didn't try to push for this stanza type already
-				if handle_notify_request(stanza, node, user_push_services, false) ~= 0 then
+				if handle_notify_request(stanza, node, user_push_services, session, false) ~= 0 then
 					if session.hibernating and not session.first_hibernated_push then
 						-- if the message was important
 						-- then record the time of first push in the session for the smack module which will extend its hibernation
 						-- timeout based on the value of session.first_hibernated_push
-						if is_important(stanza) then
+						if is_important(stanza, session) and not hibernate_past_first_push then
 							session.first_hibernated_push = os_time();
 							-- check for prosody 0.12 mod_smacks
 							if session.hibernating_watchdog and session.original_smacks_callback and session.original_smacks_timeout then
@@ -567,7 +629,7 @@
 			end
 		end
 
-		handle_notify_request(stanza, to_user, notify_push_services, true);
+		handle_notify_request(stanza, to_user, notify_push_services, event.origin, true);
 	end
 end
 
@@ -577,6 +639,15 @@
 module:hook("smacks-hibernation-stanza-queued", process_smacks_stanza);
 module:hook("archive-message-added", archive_message_added);
 
+local function track_activity(event)
+	if has_body(event.stanza) or event.stanza:child_with_ns("http://jabber.org/protocol/chatstates") then
+		event.origin.last_activity = os_time()
+	end
+end
+
+module:hook("pre-message/bare", track_activity)
+module:hook("pre-message/full", track_activity)
+
 module:log("info", "Module loaded");
 function module.unload()
 	module:log("info", "Unloading module");