Diff

mod_groups_internal/mod_groups_internal.lua @ 5856:75dee6127829 draft

Merge upstream
author Trần H. Trung <xmpp:trần.h.trung@trung.fun>
date Tue, 06 Feb 2024 18:32:01 +0700
parent 5848:865c77b5c6dc
line wrap: on
line diff
--- a/mod_groups_internal/mod_groups_internal.lua	Tue Aug 29 23:51:17 2023 +0700
+++ b/mod_groups_internal/mod_groups_internal.lua	Tue Feb 06 18:32:01 2024 +0700
@@ -1,12 +1,13 @@
 local rostermanager = require"core.rostermanager";
 local modulemanager = require"core.modulemanager";
+local array = require "util.array";
 local id = require "util.id";
 local jid = require "util.jid";
 local st = require "util.stanza";
 local jid_join = jid.join;
 local host = module.host;
 
-local group_info_store = module:open_store("group_info");
+local group_info_store = module:open_store("group_info", "keyval+");
 local group_members_store = module:open_store("groups");
 local group_memberships = module:open_store("groups", "map");
 
@@ -17,7 +18,7 @@
 
 -- Make a *one-way* subscription. User will see when contact is online,
 -- contact will not see when user is online.
-local function subscribe(user, user_jid, contact, contact_jid)
+local function subscribe(user, user_jid, contact, contact_jid, group_name)
 	-- Update user's roster to say subscription request is pending...
 	rostermanager.set_contact_pending_out(user, host, contact_jid);
 	-- Update contact's roster to say subscription request is pending...
@@ -27,6 +28,11 @@
 	-- Update user's roster to say subscription request approved...
 	rostermanager.process_inbound_subscription_approval(user, host, contact_jid);
 
+	if group_name then
+		local user_roster = rostermanager.load_roster(user, host);
+		user_roster[contact_jid].groups[group_name] = true;
+	end
+
 	-- Push updates to both rosters
 	rostermanager.roster_push(user, host, contact_jid);
 	rostermanager.roster_push(contact, host, user_jid);
@@ -39,17 +45,18 @@
 local function do_single_group_subscriptions(username, group_id)
 	local members = group_members_store:get(group_id);
 	if not members then return; end
+	local group_name = group_info_store:get_key(group_id, "name");
 	local user_jid = jid_join(username, host);
 	for membername in pairs(members) do
 		if membername ~= username then
 			local member_jid = jid_join(membername, host);
 			if not is_contact_subscribed(username, host, member_jid) then
 				module:log("debug", "[group %s] Subscribing %s to %s", member_jid, user_jid);
-				subscribe(membername, member_jid, username, user_jid);
+				subscribe(membername, member_jid, username, user_jid, group_name);
 			end
 			if not is_contact_subscribed(membername, host, user_jid) then
 				module:log("debug", "[group %s] Subscribing %s to %s", user_jid, member_jid);
-				subscribe(username, user_jid, membername, member_jid);
+				subscribe(username, user_jid, membername, member_jid, group_name);
 			end
 		end
 	end
@@ -76,8 +83,43 @@
 	do_all_group_subscriptions_by_user(event.session.username);
 end);
 
+local function _create_muc_room(name)
+	if not muc_host_name then
+		module:log("error", "cannot create group MUC: no MUC host configured")
+		return nil, "service-unavailable"
+	end
+	if not muc_host then
+		module:log("error", "cannot create group MUC: MUC host %s not configured properly", muc_host_name)
+		return nil, "internal-server-error"
+	end
+
+	local muc_jid = jid.prep(id.short() .. "@" .. muc_host_name);
+	local room = muc_host.create_room(muc_jid)
+	if not room then
+		return nil, "internal-server-error"
+	end
+
+	local ok = pcall(function ()
+		room:set_public(false);
+		room:set_persistent(true);
+		room:set_members_only(true);
+		room:set_allow_member_invites(false);
+		room:set_moderated(false);
+		room:set_whois("anyone");
+		room:set_name(name);
+	end);
+
+	if not ok then
+		module:log("error", "Failed to configure group MUC %s", muc_jid);
+		room:destroy();
+		return nil, "internal-server-error";
+	end
+
+	return muc_jid, room;
+end
+
 --luacheck: ignore 131
-function create(group_info, create_muc, group_id)
+function create(group_info, create_default_muc, group_id)
 	if not group_info.name then
 		return nil, "group-name-required";
 	end
@@ -91,29 +133,13 @@
 
 	local muc_jid = nil
 	local room = nil
-	if create_muc then
-		if not muc_host_name then
-			module:log("error", "cannot create group with MUC: no MUC host configured")
-			return nil, "service-unavailable"
-		end
-		if not muc_host then
-			module:log("error", "cannot create group with MUC: MUC host %s not configured properly", muc_host_name)
-			return nil, "internal-server-error"
+	if create_default_muc then
+		muc_jid, room = _create_muc_room(group_info.name);
+		if not muc_jid then
+			-- MUC creation failed, fail to create group
+			delete(group_id)
+			return nil, room;
 		end
-
-		muc_jid = jid.prep(id.short() .. "@" .. muc_host_name);
-		room = muc_host.create_room(muc_jid)
-		if not room then
-			delete(group_id)
-			return nil, "internal-server-error"
-		end
-		room:set_public(false)
-		room:set_persistent(true)
-		room:set_members_only(true)
-		room:set_allow_member_invites(false)
-		room:set_moderated(false)
-		room:set_whois("anyone")
-		room:set_name(group_info.name)
 	end
 
 	local ok = group_info_store:set(group_id, {
@@ -158,7 +184,7 @@
 end
 
 function get_members(group_id)
-	return group_members_store:get(group_id);
+	return group_members_store:get(group_id) or {};
 end
 
 function exists(group_id)
@@ -200,6 +226,7 @@
 	if not group_memberships:set(group_id, username, {}) then
 		return nil, "internal-server-error";
 	end
+
 	if group_info.muc_jid then
 		local room = muc_host.get_room_from_jid(group_info.muc_jid);
 		if room then
@@ -215,7 +242,29 @@
 		else
 			module:log("warn", "failed to update affiliation for %s in %s", username, group_info.muc_jid);
 		end
+	elseif group_info.mucs then
+		local user_jid = username .. "@" .. host;
+		for i = #group_info.mucs, 1, -1 do
+			local muc_jid = group_info.mucs[i];
+			local room = muc_host.get_room_from_jid(muc_jid);
+			if not room or room._data.destroyed then
+				-- MUC no longer available, for some reason
+				-- Let's remove it from the circle metadata...
+				table.remove(group_info.mucs, i);
+				group_info_store:set_key(group_id, "mucs", group_info.mucs);
+			else
+				room:set_affiliation(true, user_jid, "member");
+				module:send(st.message(
+					{ from = muc_jid, to = user_jid }
+				):tag("x", {
+					xmlns = "jabber:x:conference",
+					jid = muc_jid
+				}):up());
+				module:log("debug", "set user %s to be member in %s and sent invite", username, muc_jid);
+			end
+		end
 	end
+
 	module:fire_event(
 		"group-user-added",
 		{
@@ -247,7 +296,18 @@
 		else
 			module:log("warn", "failed to update affiliation for %s in %s", username, group_info.muc_jid);
 		end
+	elseif group_info.mucs then
+		local user_jid = username .. "@" .. host;
+		for _, muc_jid in ipairs(group_info.mucs) do
+			local room = muc_host.get_room_from_jid(muc_jid);
+			if room then
+				room:set_affiliation(true, user_jid, nil);
+			else
+				module:log("warn", "failed to update affiliation for %s in %s", username, muc_jid);
+			end
+		end
 	end
+
 	module:fire_event(
 		"group-user-removed",
 		{
@@ -264,6 +324,151 @@
 	do_all_group_subscriptions_by_group(group_id);
 end
 
+function add_group_chat(group_id, name)
+	local group_info = group_info_store:get(group_id);
+	local mucs = group_info.mucs or {};
+
+	-- Create the MUC
+	local muc_jid, room = _create_muc_room(name);
+	if not muc_jid then return nil, room; end
+	room:save(); -- This ensures the room is committed to storage
+
+	table.insert(mucs, muc_jid);
+
+	if group_info.muc_jid then -- COMPAT include old muc_jid into array
+		table.insert(mucs, group_info.muc_jid);
+	end
+	local store_ok, store_err = group_info_store:set_key(group_id, "mucs", mucs);
+	if not store_ok then
+		module:log("error", "Failed to store new MUC association: %s", store_err);
+		room:destroy();
+		return nil, "internal-server-error";
+	end
+
+	-- COMPAT: clear old muc_jid (it's now in mucs array)
+	if group_info.muc_jid then
+		module:log("debug", "Clearing old single-MUC JID");
+		group_info.muc_jid = nil;
+		group_info_store:set_key(group_id, "muc_jid", nil);
+	end
+
+	-- Make existing group members, members of the MUC
+	for username in pairs(get_members(group_id)) do
+		local user_jid = username .. "@" ..module.host;
+		room:set_affiliation(true, user_jid, "member");
+		module:send(st.message(
+			{ from = muc_jid, to = user_jid }
+		):tag("x", {
+			xmlns = "jabber:x:conference",
+			jid = muc_jid
+		}):up());
+		module:log("debug", "set user %s to be member in %s and sent invite", user_jid, muc_jid);
+	end
+
+	-- Notify other modules (such as mod_groups_muc_bookmarks)
+	local muc = {
+		jid = muc_jid;
+		name = name;
+	};
+
+	module:fire_event("group-chat-added", {
+		group_id = group_id;
+		group_info = group_info;
+		muc = muc;
+	});
+
+	return muc;
+end
+
+function remove_group_chat(group_id, muc_id)
+	local group_info = group_info_store:get(group_id);
+	if not group_info then
+		return nil, "group-not-found";
+	end
+
+	local mucs = group_info.mucs;
+	if not mucs then
+		if not group_info.muc_jid then
+			return true;
+		end
+		-- COMPAT with old single-MUC groups - upgrade to new format
+		mucs = {};
+	end
+	if group_info.muc_jid then
+		table.insert(mucs, group_info.muc_jid);
+	end
+
+	local removed;
+	for i, muc_jid in ipairs(mucs) do
+		if muc_id == jid.node(muc_jid) then
+			removed = table.remove(mucs, i);
+			break;
+		end
+	end
+
+	if removed then
+		if not group_info_store:set_key(group_id, "mucs", mucs) then
+			return nil, "internal-server-error";
+		end
+
+		if group_info.muc_jid then
+			-- COMPAT: Now we've set the array, clean up muc_jid
+			group_info.muc_jid = nil;
+			group_info_store:set_key(group_id, "muc_jid", nil);
+		end
+
+		module:log("debug", "Updated group MUC list");
+
+		local room = muc_host.get_room_from_jid(removed);
+		if room then
+			room:destroy();
+		else
+			module:log("warn", "Removing a group chat, but associated MUC not found (%s)", removed);
+		end
+
+		module:fire_event(
+			"group-chat-removed",
+			{
+				group_id = group_id;
+				group_info = group_info;
+				muc = {
+					id = muc_id;
+					jid = removed;
+				};
+			}
+		);
+	else
+		module:log("warn", "Removal of a group chat that can't be found - %s", muc_id);
+	end
+
+	return true;
+end
+
+function get_group_chats(group_id)
+	local group_info, err = group_info_store:get(group_id);
+	if not group_info then
+		module:log("debug", "Unable to load group info: %s - %s", group_id, err);
+		return nil;
+	end
+
+	local mucs = group_info.mucs or {};
+
+	-- COMPAT with single-MUC groups
+	if group_info.muc_jid then
+		table.insert(mucs, group_info.muc_jid);
+	end
+
+	return array.map(mucs, function (muc_jid)
+		local room = muc_host.get_room_from_jid(muc_jid);
+		return {
+			id = jid.node(muc_jid);
+			jid = muc_jid;
+			name = room and room:get_name() or group_info.name;
+			deleted = not room or room._data.destroyed;
+		};
+	end);
+end
+
 function emit_member_events(group_id)
 	local group_info, err = get_info(group_id)
 	if group_info == nil then
@@ -287,7 +492,7 @@
 
 -- Returns iterator over group ids
 function groups()
-	return group_info_store:users();
+	return group_info_store:items();
 end
 
 local function setup()