Software / code / prosody-modules
Comparison
mod_groups_internal/mod_groups_internal.lua @ 5856:75dee6127829
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 |
| child | 6131:f80db102fdb1 |
| child | 6208:e20901443eae |
comparison
equal
deleted
inserted
replaced
| 5664:52db2da66680 | 5856:75dee6127829 |
|---|---|
| 1 local rostermanager = require"core.rostermanager"; | 1 local rostermanager = require"core.rostermanager"; |
| 2 local modulemanager = require"core.modulemanager"; | 2 local modulemanager = require"core.modulemanager"; |
| 3 local array = require "util.array"; | |
| 3 local id = require "util.id"; | 4 local id = require "util.id"; |
| 4 local jid = require "util.jid"; | 5 local jid = require "util.jid"; |
| 5 local st = require "util.stanza"; | 6 local st = require "util.stanza"; |
| 6 local jid_join = jid.join; | 7 local jid_join = jid.join; |
| 7 local host = module.host; | 8 local host = module.host; |
| 8 | 9 |
| 9 local group_info_store = module:open_store("group_info"); | 10 local group_info_store = module:open_store("group_info", "keyval+"); |
| 10 local group_members_store = module:open_store("groups"); | 11 local group_members_store = module:open_store("groups"); |
| 11 local group_memberships = module:open_store("groups", "map"); | 12 local group_memberships = module:open_store("groups", "map"); |
| 12 | 13 |
| 13 local muc_host_name = module:get_option("groups_muc_host", "groups."..host); | 14 local muc_host_name = module:get_option("groups_muc_host", "groups."..host); |
| 14 local muc_host = nil; | 15 local muc_host = nil; |
| 15 | 16 |
| 16 local is_contact_subscribed = rostermanager.is_contact_subscribed; | 17 local is_contact_subscribed = rostermanager.is_contact_subscribed; |
| 17 | 18 |
| 18 -- Make a *one-way* subscription. User will see when contact is online, | 19 -- Make a *one-way* subscription. User will see when contact is online, |
| 19 -- contact will not see when user is online. | 20 -- contact will not see when user is online. |
| 20 local function subscribe(user, user_jid, contact, contact_jid) | 21 local function subscribe(user, user_jid, contact, contact_jid, group_name) |
| 21 -- Update user's roster to say subscription request is pending... | 22 -- Update user's roster to say subscription request is pending... |
| 22 rostermanager.set_contact_pending_out(user, host, contact_jid); | 23 rostermanager.set_contact_pending_out(user, host, contact_jid); |
| 23 -- Update contact's roster to say subscription request is pending... | 24 -- Update contact's roster to say subscription request is pending... |
| 24 rostermanager.set_contact_pending_in(contact, host, user_jid); | 25 rostermanager.set_contact_pending_in(contact, host, user_jid); |
| 25 -- Update contact's roster to say subscription request approved... | 26 -- Update contact's roster to say subscription request approved... |
| 26 rostermanager.subscribed(contact, host, user_jid); | 27 rostermanager.subscribed(contact, host, user_jid); |
| 27 -- Update user's roster to say subscription request approved... | 28 -- Update user's roster to say subscription request approved... |
| 28 rostermanager.process_inbound_subscription_approval(user, host, contact_jid); | 29 rostermanager.process_inbound_subscription_approval(user, host, contact_jid); |
| 29 | 30 |
| 31 if group_name then | |
| 32 local user_roster = rostermanager.load_roster(user, host); | |
| 33 user_roster[contact_jid].groups[group_name] = true; | |
| 34 end | |
| 35 | |
| 30 -- Push updates to both rosters | 36 -- Push updates to both rosters |
| 31 rostermanager.roster_push(user, host, contact_jid); | 37 rostermanager.roster_push(user, host, contact_jid); |
| 32 rostermanager.roster_push(contact, host, user_jid); | 38 rostermanager.roster_push(contact, host, user_jid); |
| 33 end | 39 end |
| 34 | 40 |
| 37 end | 43 end |
| 38 | 44 |
| 39 local function do_single_group_subscriptions(username, group_id) | 45 local function do_single_group_subscriptions(username, group_id) |
| 40 local members = group_members_store:get(group_id); | 46 local members = group_members_store:get(group_id); |
| 41 if not members then return; end | 47 if not members then return; end |
| 48 local group_name = group_info_store:get_key(group_id, "name"); | |
| 42 local user_jid = jid_join(username, host); | 49 local user_jid = jid_join(username, host); |
| 43 for membername in pairs(members) do | 50 for membername in pairs(members) do |
| 44 if membername ~= username then | 51 if membername ~= username then |
| 45 local member_jid = jid_join(membername, host); | 52 local member_jid = jid_join(membername, host); |
| 46 if not is_contact_subscribed(username, host, member_jid) then | 53 if not is_contact_subscribed(username, host, member_jid) then |
| 47 module:log("debug", "[group %s] Subscribing %s to %s", member_jid, user_jid); | 54 module:log("debug", "[group %s] Subscribing %s to %s", member_jid, user_jid); |
| 48 subscribe(membername, member_jid, username, user_jid); | 55 subscribe(membername, member_jid, username, user_jid, group_name); |
| 49 end | 56 end |
| 50 if not is_contact_subscribed(membername, host, user_jid) then | 57 if not is_contact_subscribed(membername, host, user_jid) then |
| 51 module:log("debug", "[group %s] Subscribing %s to %s", user_jid, member_jid); | 58 module:log("debug", "[group %s] Subscribing %s to %s", user_jid, member_jid); |
| 52 subscribe(username, user_jid, membername, member_jid); | 59 subscribe(username, user_jid, membername, member_jid, group_name); |
| 53 end | 60 end |
| 54 end | 61 end |
| 55 end | 62 end |
| 56 end | 63 end |
| 57 | 64 |
| 74 module:hook("resource-bind", function(event) | 81 module:hook("resource-bind", function(event) |
| 75 module:log("debug", "Updating group subscriptions..."); | 82 module:log("debug", "Updating group subscriptions..."); |
| 76 do_all_group_subscriptions_by_user(event.session.username); | 83 do_all_group_subscriptions_by_user(event.session.username); |
| 77 end); | 84 end); |
| 78 | 85 |
| 86 local function _create_muc_room(name) | |
| 87 if not muc_host_name then | |
| 88 module:log("error", "cannot create group MUC: no MUC host configured") | |
| 89 return nil, "service-unavailable" | |
| 90 end | |
| 91 if not muc_host then | |
| 92 module:log("error", "cannot create group MUC: MUC host %s not configured properly", muc_host_name) | |
| 93 return nil, "internal-server-error" | |
| 94 end | |
| 95 | |
| 96 local muc_jid = jid.prep(id.short() .. "@" .. muc_host_name); | |
| 97 local room = muc_host.create_room(muc_jid) | |
| 98 if not room then | |
| 99 return nil, "internal-server-error" | |
| 100 end | |
| 101 | |
| 102 local ok = pcall(function () | |
| 103 room:set_public(false); | |
| 104 room:set_persistent(true); | |
| 105 room:set_members_only(true); | |
| 106 room:set_allow_member_invites(false); | |
| 107 room:set_moderated(false); | |
| 108 room:set_whois("anyone"); | |
| 109 room:set_name(name); | |
| 110 end); | |
| 111 | |
| 112 if not ok then | |
| 113 module:log("error", "Failed to configure group MUC %s", muc_jid); | |
| 114 room:destroy(); | |
| 115 return nil, "internal-server-error"; | |
| 116 end | |
| 117 | |
| 118 return muc_jid, room; | |
| 119 end | |
| 120 | |
| 79 --luacheck: ignore 131 | 121 --luacheck: ignore 131 |
| 80 function create(group_info, create_muc, group_id) | 122 function create(group_info, create_default_muc, group_id) |
| 81 if not group_info.name then | 123 if not group_info.name then |
| 82 return nil, "group-name-required"; | 124 return nil, "group-name-required"; |
| 83 end | 125 end |
| 84 if group_id then | 126 if group_id then |
| 85 if exists(group_id) then | 127 if exists(group_id) then |
| 89 group_id = id.short(); | 131 group_id = id.short(); |
| 90 end | 132 end |
| 91 | 133 |
| 92 local muc_jid = nil | 134 local muc_jid = nil |
| 93 local room = nil | 135 local room = nil |
| 94 if create_muc then | 136 if create_default_muc then |
| 95 if not muc_host_name then | 137 muc_jid, room = _create_muc_room(group_info.name); |
| 96 module:log("error", "cannot create group with MUC: no MUC host configured") | 138 if not muc_jid then |
| 97 return nil, "service-unavailable" | 139 -- MUC creation failed, fail to create group |
| 98 end | |
| 99 if not muc_host then | |
| 100 module:log("error", "cannot create group with MUC: MUC host %s not configured properly", muc_host_name) | |
| 101 return nil, "internal-server-error" | |
| 102 end | |
| 103 | |
| 104 muc_jid = jid.prep(id.short() .. "@" .. muc_host_name); | |
| 105 room = muc_host.create_room(muc_jid) | |
| 106 if not room then | |
| 107 delete(group_id) | 140 delete(group_id) |
| 108 return nil, "internal-server-error" | 141 return nil, room; |
| 109 end | 142 end |
| 110 room:set_public(false) | |
| 111 room:set_persistent(true) | |
| 112 room:set_members_only(true) | |
| 113 room:set_allow_member_invites(false) | |
| 114 room:set_moderated(false) | |
| 115 room:set_whois("anyone") | |
| 116 room:set_name(group_info.name) | |
| 117 end | 143 end |
| 118 | 144 |
| 119 local ok = group_info_store:set(group_id, { | 145 local ok = group_info_store:set(group_id, { |
| 120 name = group_info.name; | 146 name = group_info.name; |
| 121 muc_jid = muc_jid; | 147 muc_jid = muc_jid; |
| 156 end | 182 end |
| 157 return true | 183 return true |
| 158 end | 184 end |
| 159 | 185 |
| 160 function get_members(group_id) | 186 function get_members(group_id) |
| 161 return group_members_store:get(group_id); | 187 return group_members_store:get(group_id) or {}; |
| 162 end | 188 end |
| 163 | 189 |
| 164 function exists(group_id) | 190 function exists(group_id) |
| 165 return not not get_info(group_id); | 191 return not not get_info(group_id); |
| 166 end | 192 end |
| 198 return nil, "group-not-found"; | 224 return nil, "group-not-found"; |
| 199 end | 225 end |
| 200 if not group_memberships:set(group_id, username, {}) then | 226 if not group_memberships:set(group_id, username, {}) then |
| 201 return nil, "internal-server-error"; | 227 return nil, "internal-server-error"; |
| 202 end | 228 end |
| 229 | |
| 203 if group_info.muc_jid then | 230 if group_info.muc_jid then |
| 204 local room = muc_host.get_room_from_jid(group_info.muc_jid); | 231 local room = muc_host.get_room_from_jid(group_info.muc_jid); |
| 205 if room then | 232 if room then |
| 206 local user_jid = username .. "@" .. host; | 233 local user_jid = username .. "@" .. host; |
| 207 room:set_affiliation(true, user_jid, "member"); | 234 room:set_affiliation(true, user_jid, "member"); |
| 213 }):up()); | 240 }):up()); |
| 214 module:log("debug", "set user %s to be member in %s and sent invite", username, group_info.muc_jid); | 241 module:log("debug", "set user %s to be member in %s and sent invite", username, group_info.muc_jid); |
| 215 else | 242 else |
| 216 module:log("warn", "failed to update affiliation for %s in %s", username, group_info.muc_jid); | 243 module:log("warn", "failed to update affiliation for %s in %s", username, group_info.muc_jid); |
| 217 end | 244 end |
| 218 end | 245 elseif group_info.mucs then |
| 246 local user_jid = username .. "@" .. host; | |
| 247 for i = #group_info.mucs, 1, -1 do | |
| 248 local muc_jid = group_info.mucs[i]; | |
| 249 local room = muc_host.get_room_from_jid(muc_jid); | |
| 250 if not room or room._data.destroyed then | |
| 251 -- MUC no longer available, for some reason | |
| 252 -- Let's remove it from the circle metadata... | |
| 253 table.remove(group_info.mucs, i); | |
| 254 group_info_store:set_key(group_id, "mucs", group_info.mucs); | |
| 255 else | |
| 256 room:set_affiliation(true, user_jid, "member"); | |
| 257 module:send(st.message( | |
| 258 { from = muc_jid, to = user_jid } | |
| 259 ):tag("x", { | |
| 260 xmlns = "jabber:x:conference", | |
| 261 jid = muc_jid | |
| 262 }):up()); | |
| 263 module:log("debug", "set user %s to be member in %s and sent invite", username, muc_jid); | |
| 264 end | |
| 265 end | |
| 266 end | |
| 267 | |
| 219 module:fire_event( | 268 module:fire_event( |
| 220 "group-user-added", | 269 "group-user-added", |
| 221 { | 270 { |
| 222 id = group_id, | 271 id = group_id, |
| 223 user = username, | 272 user = username, |
| 245 local user_jid = username .. "@" .. host; | 294 local user_jid = username .. "@" .. host; |
| 246 room:set_affiliation(true, user_jid, nil); | 295 room:set_affiliation(true, user_jid, nil); |
| 247 else | 296 else |
| 248 module:log("warn", "failed to update affiliation for %s in %s", username, group_info.muc_jid); | 297 module:log("warn", "failed to update affiliation for %s in %s", username, group_info.muc_jid); |
| 249 end | 298 end |
| 250 end | 299 elseif group_info.mucs then |
| 300 local user_jid = username .. "@" .. host; | |
| 301 for _, muc_jid in ipairs(group_info.mucs) do | |
| 302 local room = muc_host.get_room_from_jid(muc_jid); | |
| 303 if room then | |
| 304 room:set_affiliation(true, user_jid, nil); | |
| 305 else | |
| 306 module:log("warn", "failed to update affiliation for %s in %s", username, muc_jid); | |
| 307 end | |
| 308 end | |
| 309 end | |
| 310 | |
| 251 module:fire_event( | 311 module:fire_event( |
| 252 "group-user-removed", | 312 "group-user-removed", |
| 253 { | 313 { |
| 254 id = group_id, | 314 id = group_id, |
| 255 user = username, | 315 user = username, |
| 262 | 322 |
| 263 function sync(group_id) | 323 function sync(group_id) |
| 264 do_all_group_subscriptions_by_group(group_id); | 324 do_all_group_subscriptions_by_group(group_id); |
| 265 end | 325 end |
| 266 | 326 |
| 327 function add_group_chat(group_id, name) | |
| 328 local group_info = group_info_store:get(group_id); | |
| 329 local mucs = group_info.mucs or {}; | |
| 330 | |
| 331 -- Create the MUC | |
| 332 local muc_jid, room = _create_muc_room(name); | |
| 333 if not muc_jid then return nil, room; end | |
| 334 room:save(); -- This ensures the room is committed to storage | |
| 335 | |
| 336 table.insert(mucs, muc_jid); | |
| 337 | |
| 338 if group_info.muc_jid then -- COMPAT include old muc_jid into array | |
| 339 table.insert(mucs, group_info.muc_jid); | |
| 340 end | |
| 341 local store_ok, store_err = group_info_store:set_key(group_id, "mucs", mucs); | |
| 342 if not store_ok then | |
| 343 module:log("error", "Failed to store new MUC association: %s", store_err); | |
| 344 room:destroy(); | |
| 345 return nil, "internal-server-error"; | |
| 346 end | |
| 347 | |
| 348 -- COMPAT: clear old muc_jid (it's now in mucs array) | |
| 349 if group_info.muc_jid then | |
| 350 module:log("debug", "Clearing old single-MUC JID"); | |
| 351 group_info.muc_jid = nil; | |
| 352 group_info_store:set_key(group_id, "muc_jid", nil); | |
| 353 end | |
| 354 | |
| 355 -- Make existing group members, members of the MUC | |
| 356 for username in pairs(get_members(group_id)) do | |
| 357 local user_jid = username .. "@" ..module.host; | |
| 358 room:set_affiliation(true, user_jid, "member"); | |
| 359 module:send(st.message( | |
| 360 { from = muc_jid, to = user_jid } | |
| 361 ):tag("x", { | |
| 362 xmlns = "jabber:x:conference", | |
| 363 jid = muc_jid | |
| 364 }):up()); | |
| 365 module:log("debug", "set user %s to be member in %s and sent invite", user_jid, muc_jid); | |
| 366 end | |
| 367 | |
| 368 -- Notify other modules (such as mod_groups_muc_bookmarks) | |
| 369 local muc = { | |
| 370 jid = muc_jid; | |
| 371 name = name; | |
| 372 }; | |
| 373 | |
| 374 module:fire_event("group-chat-added", { | |
| 375 group_id = group_id; | |
| 376 group_info = group_info; | |
| 377 muc = muc; | |
| 378 }); | |
| 379 | |
| 380 return muc; | |
| 381 end | |
| 382 | |
| 383 function remove_group_chat(group_id, muc_id) | |
| 384 local group_info = group_info_store:get(group_id); | |
| 385 if not group_info then | |
| 386 return nil, "group-not-found"; | |
| 387 end | |
| 388 | |
| 389 local mucs = group_info.mucs; | |
| 390 if not mucs then | |
| 391 if not group_info.muc_jid then | |
| 392 return true; | |
| 393 end | |
| 394 -- COMPAT with old single-MUC groups - upgrade to new format | |
| 395 mucs = {}; | |
| 396 end | |
| 397 if group_info.muc_jid then | |
| 398 table.insert(mucs, group_info.muc_jid); | |
| 399 end | |
| 400 | |
| 401 local removed; | |
| 402 for i, muc_jid in ipairs(mucs) do | |
| 403 if muc_id == jid.node(muc_jid) then | |
| 404 removed = table.remove(mucs, i); | |
| 405 break; | |
| 406 end | |
| 407 end | |
| 408 | |
| 409 if removed then | |
| 410 if not group_info_store:set_key(group_id, "mucs", mucs) then | |
| 411 return nil, "internal-server-error"; | |
| 412 end | |
| 413 | |
| 414 if group_info.muc_jid then | |
| 415 -- COMPAT: Now we've set the array, clean up muc_jid | |
| 416 group_info.muc_jid = nil; | |
| 417 group_info_store:set_key(group_id, "muc_jid", nil); | |
| 418 end | |
| 419 | |
| 420 module:log("debug", "Updated group MUC list"); | |
| 421 | |
| 422 local room = muc_host.get_room_from_jid(removed); | |
| 423 if room then | |
| 424 room:destroy(); | |
| 425 else | |
| 426 module:log("warn", "Removing a group chat, but associated MUC not found (%s)", removed); | |
| 427 end | |
| 428 | |
| 429 module:fire_event( | |
| 430 "group-chat-removed", | |
| 431 { | |
| 432 group_id = group_id; | |
| 433 group_info = group_info; | |
| 434 muc = { | |
| 435 id = muc_id; | |
| 436 jid = removed; | |
| 437 }; | |
| 438 } | |
| 439 ); | |
| 440 else | |
| 441 module:log("warn", "Removal of a group chat that can't be found - %s", muc_id); | |
| 442 end | |
| 443 | |
| 444 return true; | |
| 445 end | |
| 446 | |
| 447 function get_group_chats(group_id) | |
| 448 local group_info, err = group_info_store:get(group_id); | |
| 449 if not group_info then | |
| 450 module:log("debug", "Unable to load group info: %s - %s", group_id, err); | |
| 451 return nil; | |
| 452 end | |
| 453 | |
| 454 local mucs = group_info.mucs or {}; | |
| 455 | |
| 456 -- COMPAT with single-MUC groups | |
| 457 if group_info.muc_jid then | |
| 458 table.insert(mucs, group_info.muc_jid); | |
| 459 end | |
| 460 | |
| 461 return array.map(mucs, function (muc_jid) | |
| 462 local room = muc_host.get_room_from_jid(muc_jid); | |
| 463 return { | |
| 464 id = jid.node(muc_jid); | |
| 465 jid = muc_jid; | |
| 466 name = room and room:get_name() or group_info.name; | |
| 467 deleted = not room or room._data.destroyed; | |
| 468 }; | |
| 469 end); | |
| 470 end | |
| 471 | |
| 267 function emit_member_events(group_id) | 472 function emit_member_events(group_id) |
| 268 local group_info, err = get_info(group_id) | 473 local group_info, err = get_info(group_id) |
| 269 if group_info == nil then | 474 if group_info == nil then |
| 270 return false, err | 475 return false, err |
| 271 end | 476 end |
| 285 return true | 490 return true |
| 286 end | 491 end |
| 287 | 492 |
| 288 -- Returns iterator over group ids | 493 -- Returns iterator over group ids |
| 289 function groups() | 494 function groups() |
| 290 return group_info_store:users(); | 495 return group_info_store:items(); |
| 291 end | 496 end |
| 292 | 497 |
| 293 local function setup() | 498 local function setup() |
| 294 if not muc_host_name then | 499 if not muc_host_name then |
| 295 module:log("info", "MUC management disabled (groups_muc_host set to nil)"); | 500 module:log("info", "MUC management disabled (groups_muc_host set to nil)"); |