Extract affiliation-related methods out of the ChatRoom model
and put them together in a utils file
This commit is contained in:
parent
13e19eb7f8
commit
383f5c1d60
@ -323,13 +323,13 @@ window.addEventListener('converse-loaded', () => {
|
|||||||
await mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
|
await mock.receiveOwnMUCPresence(_converse, muc_jid, nick);
|
||||||
|
|
||||||
await room_creation_promise;
|
await room_creation_promise;
|
||||||
const view = _converse.chatboxviews.get(muc_jid);
|
const model = _converse.chatboxes.get(muc_jid);
|
||||||
await u.waitUntil(() => (view.model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
|
await u.waitUntil(() => (model.session.get('connection_status') === converse.ROOMSTATUS.ENTERED));
|
||||||
|
|
||||||
const affs = _converse.muc_fetch_members;
|
const affs = _converse.muc_fetch_members;
|
||||||
const all_affiliations = Array.isArray(affs) ? affs : (affs ? ['member', 'admin', 'owner'] : []);
|
const all_affiliations = Array.isArray(affs) ? affs : (affs ? ['member', 'admin', 'owner'] : []);
|
||||||
await mock.returnMemberLists(_converse, muc_jid, members, all_affiliations);
|
await mock.returnMemberLists(_converse, muc_jid, members, all_affiliations);
|
||||||
return view.model.messages.fetched;
|
return model.messages.fetched;
|
||||||
};
|
};
|
||||||
|
|
||||||
mock.createContact = async function (_converse, name, ask, requesting, subscription) {
|
mock.createContact = async function (_converse, name, ask, requesting, subscription) {
|
||||||
|
153
src/headless/plugins/muc/affiliations/utils.js
Normal file
153
src/headless/plugins/muc/affiliations/utils.js
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/**
|
||||||
|
* @copyright The Converse.js contributors
|
||||||
|
* @license Mozilla Public License (MPLv2)
|
||||||
|
*/
|
||||||
|
import log from "@converse/headless/log";
|
||||||
|
import { api, converse } from '@converse/headless/core.js';
|
||||||
|
import { difference, indexOf } from 'lodash-es';
|
||||||
|
import { parseMemberListIQ } from '../parsers.js';
|
||||||
|
|
||||||
|
const { Strophe, $iq, u } = converse.env;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends an IQ stanza to the server, asking it for the relevant affiliation list .
|
||||||
|
* Returns an array of {@link MemberListItem} objects, representing occupants
|
||||||
|
* that have the given affiliation.
|
||||||
|
* See: https://xmpp.org/extensions/xep-0045.html#modifymember
|
||||||
|
* @param { ("admin"|"owner"|"member") } affiliation
|
||||||
|
* @param { String } muc_jid - The JID of the MUC for which the affiliation list should be fetched
|
||||||
|
* @returns { Promise<MemberListItem[]> }
|
||||||
|
*/
|
||||||
|
export async function getAffiliationList (affiliation, muc_jid) {
|
||||||
|
const iq = $iq({ 'to': muc_jid, 'type': 'get' })
|
||||||
|
.c('query', { xmlns: Strophe.NS.MUC_ADMIN })
|
||||||
|
.c('item', { 'affiliation': affiliation });
|
||||||
|
const result = await api.sendIQ(iq, null, false);
|
||||||
|
if (result === null) {
|
||||||
|
const err_msg = `Error: timeout while fetching ${affiliation} list for MUC ${muc_jid}`;
|
||||||
|
const err = new Error(err_msg);
|
||||||
|
log.warn(err_msg);
|
||||||
|
log.warn(result);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
if (u.isErrorStanza(result)) {
|
||||||
|
const err_msg = `Error: not allowed to fetch ${affiliation} list for MUC ${muc_jid}`;
|
||||||
|
const err = new Error(err_msg);
|
||||||
|
log.warn(err_msg);
|
||||||
|
log.warn(result);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
return parseMemberListIQ(result)
|
||||||
|
.filter(p => p)
|
||||||
|
.sort((a, b) => (a.nick < b.nick ? -1 : a.nick > b.nick ? 1 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send IQ stanzas to the server to modify affiliations for users in this groupchat.
|
||||||
|
* See: https://xmpp.org/extensions/xep-0045.html#modifymember
|
||||||
|
* @param { Object[] } users
|
||||||
|
* @param { string } users[].jid - The JID of the user whose affiliation will change
|
||||||
|
* @param { Array } users[].affiliation - The new affiliation for this user
|
||||||
|
* @param { string } [users[].reason] - An optional reason for the affiliation change
|
||||||
|
* @returns { Promise }
|
||||||
|
*/
|
||||||
|
export function setAffiliations (muc_jid, users) {
|
||||||
|
const affiliations = [...new Set(users.map(u => u.affiliation))];
|
||||||
|
return Promise.all(affiliations.map(a => setAffiliation(a, muc_jid, users)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send IQ stanzas to the server to set an affiliation for
|
||||||
|
* the provided JIDs.
|
||||||
|
* See: https://xmpp.org/extensions/xep-0045.html#modifymember
|
||||||
|
*
|
||||||
|
* Prosody doesn't accept multiple JIDs' affiliations
|
||||||
|
* being set in one IQ stanza, so as a workaround we send
|
||||||
|
* a separate stanza for each JID.
|
||||||
|
* Related ticket: https://issues.prosody.im/345
|
||||||
|
*
|
||||||
|
* @param { String } affiliation - The affiliation
|
||||||
|
* @param { String|Array<String> } jids - The JID(s) of the MUCs in which the
|
||||||
|
* affiliations need to be set.
|
||||||
|
* @param { object } members - A map of jids, affiliations and
|
||||||
|
* optionally reasons. Only those entries with the
|
||||||
|
* same affiliation as being currently set will be considered.
|
||||||
|
* @returns { Promise } A promise which resolves and fails depending on the XMPP server response.
|
||||||
|
*/
|
||||||
|
export function setAffiliation (affiliation, muc_jids, members) {
|
||||||
|
if (!Array.isArray(muc_jids)) {
|
||||||
|
muc_jids = [muc_jids];
|
||||||
|
}
|
||||||
|
members = members.filter(m => [undefined, affiliation].includes(m.affiliation));
|
||||||
|
return Promise.all(
|
||||||
|
muc_jids.reduce((acc, jid) => [...acc, ...members.map(m => sendAffiliationIQ(affiliation, jid, m))], [])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an IQ stanza specifying an affiliation change.
|
||||||
|
* @private
|
||||||
|
* @param { String } affiliation: affiliation (could also be stored on the member object).
|
||||||
|
* @param { String } muc_jid: The JID of the MUC in which the affiliation should be set.
|
||||||
|
* @param { Object } member: Map containing the member's jid and optionally a reason and affiliation.
|
||||||
|
*/
|
||||||
|
function sendAffiliationIQ (affiliation, muc_jid, member) {
|
||||||
|
const iq = $iq({ to: muc_jid, type: 'set' })
|
||||||
|
.c('query', { xmlns: Strophe.NS.MUC_ADMIN })
|
||||||
|
.c('item', {
|
||||||
|
'affiliation': member.affiliation || affiliation,
|
||||||
|
'nick': member.nick,
|
||||||
|
'jid': member.jid
|
||||||
|
});
|
||||||
|
if (member.reason !== undefined) {
|
||||||
|
iq.c('reason', member.reason);
|
||||||
|
}
|
||||||
|
return api.sendIQ(iq);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given two lists of objects with 'jid', 'affiliation' and
|
||||||
|
* 'reason' properties, return a new list containing
|
||||||
|
* those objects that are new, changed or removed
|
||||||
|
* (depending on the 'remove_absentees' boolean).
|
||||||
|
*
|
||||||
|
* The affiliations for new and changed members stay the
|
||||||
|
* same, for removed members, the affiliation is set to 'none'.
|
||||||
|
*
|
||||||
|
* The 'reason' property is not taken into account when
|
||||||
|
* comparing whether affiliations have been changed.
|
||||||
|
* @param { boolean } exclude_existing - Indicates whether JIDs from
|
||||||
|
* the new list which are also in the old list
|
||||||
|
* (regardless of affiliation) should be excluded
|
||||||
|
* from the delta. One reason to do this
|
||||||
|
* would be when you want to add a JID only if it
|
||||||
|
* doesn't have *any* existing affiliation at all.
|
||||||
|
* @param { boolean } remove_absentees - Indicates whether JIDs
|
||||||
|
* from the old list which are not in the new list
|
||||||
|
* should be considered removed and therefore be
|
||||||
|
* included in the delta with affiliation set
|
||||||
|
* to 'none'.
|
||||||
|
* @param { array } new_list - Array containing the new affiliations
|
||||||
|
* @param { array } old_list - Array containing the old affiliations
|
||||||
|
* @returns { array }
|
||||||
|
*/
|
||||||
|
export function computeAffiliationsDelta (exclude_existing, remove_absentees, new_list, old_list) {
|
||||||
|
const new_jids = new_list.map(o => o.jid);
|
||||||
|
const old_jids = old_list.map(o => o.jid);
|
||||||
|
// Get the new affiliations
|
||||||
|
let delta = difference(new_jids, old_jids).map(jid => new_list[indexOf(new_jids, jid)]);
|
||||||
|
if (!exclude_existing) {
|
||||||
|
// Get the changed affiliations
|
||||||
|
delta = delta.concat(
|
||||||
|
new_list.filter(item => {
|
||||||
|
const idx = indexOf(old_jids, item.jid);
|
||||||
|
return idx >= 0 ? item.affiliation !== old_list[idx].affiliation : false;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (remove_absentees) {
|
||||||
|
// Get the removed affiliations
|
||||||
|
delta = delta.concat(difference(old_jids, new_jids).map(jid => ({ 'jid': jid, 'affiliation': 'none' })));
|
||||||
|
}
|
||||||
|
return delta;
|
||||||
|
}
|
@ -13,7 +13,7 @@ import ChatRoomOccupant from './occupant.js';
|
|||||||
import ChatRoomOccupants from './occupants.js';
|
import ChatRoomOccupants from './occupants.js';
|
||||||
import log from '../../log';
|
import log from '../../log';
|
||||||
import muc_api from './api.js';
|
import muc_api from './api.js';
|
||||||
import muc_utils from './utils.js';
|
import { computeAffiliationsDelta } from './affiliations/utils.js';
|
||||||
import u from '../../utils/form';
|
import u from '../../utils/form';
|
||||||
import { Collection } from '@converse/skeletor/src/collection';
|
import { Collection } from '@converse/skeletor/src/collection';
|
||||||
import { _converse, api, converse } from '../../core.js';
|
import { _converse, api, converse } from '../../core.js';
|
||||||
@ -265,7 +265,8 @@ converse.plugins.add('converse-muc', {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
converse.env.muc_utils = muc_utils;
|
// This is for tests (at least until we can import modules inside tests)
|
||||||
|
converse.env.muc_utils = { computeAffiliationsDelta };
|
||||||
Object.assign(api, muc_api);
|
Object.assign(api, muc_api);
|
||||||
|
|
||||||
/* https://xmpp.org/extensions/xep-0045.html
|
/* https://xmpp.org/extensions/xep-0045.html
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import log from '../../log';
|
import log from '../../log';
|
||||||
import muc_utils from './utils.js';
|
|
||||||
import p from '../../utils/parse-helpers';
|
import p from '../../utils/parse-helpers';
|
||||||
import sizzle from 'sizzle';
|
import sizzle from 'sizzle';
|
||||||
import u from '../../utils/form';
|
import u from '../../utils/form';
|
||||||
import { Model } from '@converse/skeletor/src/model.js';
|
import { Model } from '@converse/skeletor/src/model.js';
|
||||||
import { Strophe, $build, $iq, $msg, $pres } from 'strophe.js/src/strophe';
|
import { Strophe, $build, $iq, $msg, $pres } from 'strophe.js/src/strophe';
|
||||||
import { _converse, api, converse } from '../../core.js';
|
import { _converse, api, converse } from '../../core.js';
|
||||||
|
import { computeAffiliationsDelta, setAffiliations, getAffiliationList } from './affiliations/utils.js';
|
||||||
import { debounce, intersection, invoke, isElement, pick, zipObject } from 'lodash-es';
|
import { debounce, intersection, invoke, isElement, pick, zipObject } from 'lodash-es';
|
||||||
import { isArchived } from '@converse/headless/shared/parsers';
|
import { isArchived } from '@converse/headless/shared/parsers';
|
||||||
import { parseMemberListIQ, parseMUCMessage, parseMUCPresence } from './parsers.js';
|
import { parseMUCMessage, parseMUCPresence } from './parsers.js';
|
||||||
import { sendMarker } from '@converse/headless/shared/actions';
|
import { sendMarker } from '@converse/headless/shared/actions';
|
||||||
|
|
||||||
const OWNER_COMMANDS = ['owner'];
|
const OWNER_COMMANDS = ['owner'];
|
||||||
@ -1136,29 +1136,6 @@ const ChatRoomMixin = {
|
|||||||
this.features.save(attrs);
|
this.features.save(attrs);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Send IQ stanzas to the server to set an affiliation for
|
|
||||||
* the provided JIDs.
|
|
||||||
* See: https://xmpp.org/extensions/xep-0045.html#modifymember
|
|
||||||
*
|
|
||||||
* Prosody doesn't accept multiple JIDs' affiliations
|
|
||||||
* being set in one IQ stanza, so as a workaround we send
|
|
||||||
* a separate stanza for each JID.
|
|
||||||
* Related ticket: https://issues.prosody.im/345
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @method _converse.ChatRoom#setAffiliation
|
|
||||||
* @param { string } affiliation - The affiliation
|
|
||||||
* @param { object } members - A map of jids, affiliations and
|
|
||||||
* optionally reasons. Only those entries with the
|
|
||||||
* same affiliation as being currently set will be considered.
|
|
||||||
* @returns { Promise } A promise which resolves and fails depending on the XMPP server response.
|
|
||||||
*/
|
|
||||||
setAffiliation (affiliation, members) {
|
|
||||||
members = members.filter(m => m.affiliation === undefined || m.affiliation === affiliation);
|
|
||||||
return Promise.all(members.map(m => this.sendAffiliationIQ(affiliation, m)));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a <field> element, return a copy with a <value> child if
|
* Given a <field> element, return a copy with a <value> child if
|
||||||
* we can find a value for it in this rooms config.
|
* we can find a value for it in this rooms config.
|
||||||
@ -1388,46 +1365,6 @@ const ChatRoomMixin = {
|
|||||||
return this.occupants.findWhere({ 'jid': _converse.bare_jid });
|
return this.occupants.findWhere({ 'jid': _converse.bare_jid });
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Send an IQ stanza specifying an affiliation change.
|
|
||||||
* @private
|
|
||||||
* @method _converse.ChatRoom#
|
|
||||||
* @param { String } affiliation: affiliation
|
|
||||||
* (could also be stored on the member object).
|
|
||||||
* @param { Object } member: Map containing the member's jid and
|
|
||||||
* optionally a reason and affiliation.
|
|
||||||
*/
|
|
||||||
sendAffiliationIQ (affiliation, member) {
|
|
||||||
const iq = $iq({ to: this.get('jid'), type: 'set' })
|
|
||||||
.c('query', { xmlns: Strophe.NS.MUC_ADMIN })
|
|
||||||
.c('item', {
|
|
||||||
'affiliation': member.affiliation || affiliation,
|
|
||||||
'nick': member.nick,
|
|
||||||
'jid': member.jid
|
|
||||||
});
|
|
||||||
if (member.reason !== undefined) {
|
|
||||||
iq.c('reason', member.reason);
|
|
||||||
}
|
|
||||||
return api.sendIQ(iq);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send IQ stanzas to the server to modify affiliations for users in this groupchat.
|
|
||||||
*
|
|
||||||
* See: https://xmpp.org/extensions/xep-0045.html#modifymember
|
|
||||||
* @private
|
|
||||||
* @method _converse.ChatRoom#setAffiliations
|
|
||||||
* @param { Object[] } members
|
|
||||||
* @param { string } members[].jid - The JID of the user whose affiliation will change
|
|
||||||
* @param { Array } members[].affiliation - The new affiliation for this user
|
|
||||||
* @param { string } [members[].reason] - An optional reason for the affiliation change
|
|
||||||
* @returns { Promise }
|
|
||||||
*/
|
|
||||||
setAffiliations (members) {
|
|
||||||
const affiliations = [...new Set(members.map(m => m.affiliation))];
|
|
||||||
return Promise.all(affiliations.map(a => this.setAffiliation(a, members)));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send an IQ stanza to modify an occupant's role
|
* Send an IQ stanza to modify an occupant's role
|
||||||
* @private
|
* @private
|
||||||
@ -1521,40 +1458,6 @@ const ChatRoomMixin = {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an IQ stanza to the server, asking it for the relevant affiliation list .
|
|
||||||
* Returns an array of {@link MemberListItem} objects, representing occupants
|
|
||||||
* that have the given affiliation.
|
|
||||||
* See: https://xmpp.org/extensions/xep-0045.html#modifymember
|
|
||||||
* @private
|
|
||||||
* @method _converse.ChatRoom#getAffiliationList
|
|
||||||
* @param { ("admin"|"owner"|"member") } affiliation
|
|
||||||
* @returns { Promise<MemberListItem[]> }
|
|
||||||
*/
|
|
||||||
async getAffiliationList (affiliation) {
|
|
||||||
const iq = $iq({ to: this.get('jid'), type: 'get' })
|
|
||||||
.c('query', { xmlns: Strophe.NS.MUC_ADMIN })
|
|
||||||
.c('item', { 'affiliation': affiliation });
|
|
||||||
const result = await api.sendIQ(iq, null, false);
|
|
||||||
if (result === null) {
|
|
||||||
const err_msg = `Error: timeout while fetching ${affiliation} list for MUC ${this.get('jid')}`;
|
|
||||||
const err = new Error(err_msg);
|
|
||||||
log.warn(err_msg);
|
|
||||||
log.warn(result);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
if (u.isErrorStanza(result)) {
|
|
||||||
const err_msg = `Error: not allowed to fetch ${affiliation} list for MUC ${this.get('jid')}`;
|
|
||||||
const err = new Error(err_msg);
|
|
||||||
log.warn(err_msg);
|
|
||||||
log.warn(result);
|
|
||||||
return err;
|
|
||||||
}
|
|
||||||
return parseMemberListIQ(result)
|
|
||||||
.filter(p => p)
|
|
||||||
.sort((a, b) => (a.nick < b.nick ? -1 : a.nick > b.nick ? 1 : 0));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the lists of users with the given affiliations.
|
* Fetch the lists of users with the given affiliations.
|
||||||
* Then compute the delta between those users and
|
* Then compute the delta between those users and
|
||||||
@ -1569,10 +1472,11 @@ const ChatRoomMixin = {
|
|||||||
* to update the list.
|
* to update the list.
|
||||||
*/
|
*/
|
||||||
async updateMemberLists (members) {
|
async updateMemberLists (members) {
|
||||||
|
const muc_jid = this.get('jid');
|
||||||
const all_affiliations = ['member', 'admin', 'owner'];
|
const all_affiliations = ['member', 'admin', 'owner'];
|
||||||
const aff_lists = await Promise.all(all_affiliations.map(a => this.getAffiliationList(a)));
|
const aff_lists = await Promise.all(all_affiliations.map(a => getAffiliationList(a, muc_jid)));
|
||||||
const old_members = aff_lists.reduce((acc, val) => (u.isErrorObject(val) ? acc : [...val, ...acc]), []);
|
const old_members = aff_lists.reduce((acc, val) => (u.isErrorObject(val) ? acc : [...val, ...acc]), []);
|
||||||
await this.setAffiliations(muc_utils.computeAffiliationsDelta(true, false, members, old_members));
|
await setAffiliations(muc_jid, computeAffiliationsDelta(true, false, members, old_members));
|
||||||
await this.occupants.fetchMembers();
|
await this.occupants.fetchMembers();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import u from '../../utils/form';
|
|||||||
import { Collection } from '@converse/skeletor/src/collection';
|
import { Collection } from '@converse/skeletor/src/collection';
|
||||||
import { Strophe } from 'strophe.js/src/strophe';
|
import { Strophe } from 'strophe.js/src/strophe';
|
||||||
import { _converse, api } from '../../core.js';
|
import { _converse, api } from '../../core.js';
|
||||||
|
import { getAffiliationList } from './affiliations/utils.js';
|
||||||
|
|
||||||
const MUC_ROLE_WEIGHTS = {
|
const MUC_ROLE_WEIGHTS = {
|
||||||
'moderator': 1,
|
'moderator': 1,
|
||||||
@ -43,7 +44,8 @@ const ChatRoomOccupants = Collection.extend({
|
|||||||
if (affiliations.length === 0) {
|
if (affiliations.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const aff_lists = await Promise.all(affiliations.map(a => this.chatroom.getAffiliationList(a)));
|
const muc_jid = this.chatroom.get('jid');
|
||||||
|
const aff_lists = await Promise.all(affiliations.map(a => getAffiliationList(a, muc_jid)));
|
||||||
const new_members = aff_lists.reduce((acc, val) => (u.isErrorObject(val) ? acc : [...val, ...acc]), []);
|
const new_members = aff_lists.reduce((acc, val) => (u.isErrorObject(val) ? acc : [...val, ...acc]), []);
|
||||||
const known_affiliations = affiliations.filter(
|
const known_affiliations = affiliations.filter(
|
||||||
a => !u.isErrorObject(aff_lists[affiliations.indexOf(a)])
|
a => !u.isErrorObject(aff_lists[affiliations.indexOf(a)])
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
/**
|
|
||||||
* @copyright The Converse.js contributors
|
|
||||||
* @license Mozilla Public License (MPLv2)
|
|
||||||
* @description This is the MUC utilities module.
|
|
||||||
*/
|
|
||||||
import { difference, indexOf } from "lodash-es";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The MUC utils object. Contains utility functions related to multi-user chat.
|
|
||||||
* @namespace muc_utils
|
|
||||||
*/
|
|
||||||
const muc_utils = {
|
|
||||||
/**
|
|
||||||
* Given two lists of objects with 'jid', 'affiliation' and
|
|
||||||
* 'reason' properties, return a new list containing
|
|
||||||
* those objects that are new, changed or removed
|
|
||||||
* (depending on the 'remove_absentees' boolean).
|
|
||||||
*
|
|
||||||
* The affiliations for new and changed members stay the
|
|
||||||
* same, for removed members, the affiliation is set to 'none'.
|
|
||||||
*
|
|
||||||
* The 'reason' property is not taken into account when
|
|
||||||
* comparing whether affiliations have been changed.
|
|
||||||
* @private
|
|
||||||
* @method muc_utils#computeAffiliationsDelta
|
|
||||||
* @param { boolean } exclude_existing - Indicates whether JIDs from
|
|
||||||
* the new list which are also in the old list
|
|
||||||
* (regardless of affiliation) should be excluded
|
|
||||||
* from the delta. One reason to do this
|
|
||||||
* would be when you want to add a JID only if it
|
|
||||||
* doesn't have *any* existing affiliation at all.
|
|
||||||
* @param { boolean } remove_absentees - Indicates whether JIDs
|
|
||||||
* from the old list which are not in the new list
|
|
||||||
* should be considered removed and therefore be
|
|
||||||
* included in the delta with affiliation set
|
|
||||||
* to 'none'.
|
|
||||||
* @param { array } new_list - Array containing the new affiliations
|
|
||||||
* @param { array } old_list - Array containing the old affiliations
|
|
||||||
* @returns { array }
|
|
||||||
*/
|
|
||||||
computeAffiliationsDelta (exclude_existing, remove_absentees, new_list, old_list) {
|
|
||||||
const new_jids = new_list.map(o => o.jid);
|
|
||||||
const old_jids = old_list.map(o => o.jid);
|
|
||||||
// Get the new affiliations
|
|
||||||
let delta = difference(new_jids, old_jids).map(jid => new_list[indexOf(new_jids, jid)]);
|
|
||||||
if (!exclude_existing) {
|
|
||||||
// Get the changed affiliations
|
|
||||||
delta = delta.concat(new_list.filter(item => {
|
|
||||||
const idx = indexOf(old_jids, item.jid);
|
|
||||||
return idx >= 0 ? (item.affiliation !== old_list[idx].affiliation) : false;
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (remove_absentees) { // Get the removed affiliations
|
|
||||||
delta = delta.concat(difference(old_jids, new_jids).map(jid => ({'jid': jid, 'affiliation': 'none'})));
|
|
||||||
}
|
|
||||||
return delta;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default muc_utils;
|
|
@ -1,9 +1,10 @@
|
|||||||
import BootstrapModal from "./base.js";
|
import BootstrapModal from "modals/base.js";
|
||||||
import log from "@converse/headless/log";
|
import log from "@converse/headless/log";
|
||||||
import tpl_moderator_tools_modal from "./templates/moderator-tools.js";
|
import tpl_moderator_tools_modal from "../templates/moderator-tools.js";
|
||||||
import { AFFILIATIONS, ROLES } from "@converse/headless/plugins/muc/index.js";
|
import { AFFILIATIONS, ROLES } from "@converse/headless/plugins/muc/index.js";
|
||||||
import { __ } from '../i18n';
|
import { __ } from 'i18n';
|
||||||
import { api, converse } from "@converse/headless/core";
|
import { api, converse } from "@converse/headless/core";
|
||||||
|
import { getAffiliationList, setAffiliation } from '@converse/headless/plugins/muc/affiliations/utils.js'
|
||||||
|
|
||||||
const { Strophe, sizzle } = converse.env;
|
const { Strophe, sizzle } = converse.env;
|
||||||
const u = converse.env.utils;
|
const u = converse.env.utils;
|
||||||
@ -33,7 +34,8 @@ export default BootstrapModal.extend({
|
|||||||
const chatroom = this.chatroomview.model;
|
const chatroom = this.chatroomview.model;
|
||||||
const affiliation = this.model.get('affiliation');
|
const affiliation = this.model.get('affiliation');
|
||||||
if (this.shouldFetchAffiliationsList()) {
|
if (this.shouldFetchAffiliationsList()) {
|
||||||
this.users_with_affiliation = await chatroom.getAffiliationList(affiliation);
|
const muc_jid = chatroom.get('jid');
|
||||||
|
this.users_with_affiliation = await getAffiliationList(affiliation, muc_jid);
|
||||||
} else {
|
} else {
|
||||||
this.users_with_affiliation = chatroom.getOccupantsWithAffiliation(affiliation);
|
this.users_with_affiliation = chatroom.getOccupantsWithAffiliation(affiliation);
|
||||||
}
|
}
|
||||||
@ -157,8 +159,9 @@ export default BootstrapModal.extend({
|
|||||||
'reason': data.get('reason')
|
'reason': data.get('reason')
|
||||||
}
|
}
|
||||||
const current_affiliation = this.model.get('affiliation');
|
const current_affiliation = this.model.get('affiliation');
|
||||||
|
const muc_jid = this.chatroomview.model.get('jid');
|
||||||
try {
|
try {
|
||||||
await this.chatroomview.model.setAffiliation(affiliation, [attrs]);
|
await setAffiliation(affiliation, muc_jid, [attrs]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e === null) {
|
if (e === null) {
|
||||||
this.alert(__('Timeout error while trying to set the affiliation'), 'danger');
|
this.alert(__('Timeout error while trying to set the affiliation'), 'danger');
|
@ -1,5 +1,5 @@
|
|||||||
import BaseChatView from 'shared/chat/baseview.js';
|
import BaseChatView from 'shared/chat/baseview.js';
|
||||||
import ModeratorToolsModal from 'modals/moderator-tools.js';
|
import ModeratorToolsModal from './modals/moderator-tools.js';
|
||||||
import log from '@converse/headless/log';
|
import log from '@converse/headless/log';
|
||||||
import tpl_muc from './templates/muc.js';
|
import tpl_muc from './templates/muc.js';
|
||||||
import { Model } from '@converse/skeletor/src/model.js';
|
import { Model } from '@converse/skeletor/src/model.js';
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
import spinner from "templates/spinner.js";
|
||||||
|
import { __ } from 'i18n';
|
||||||
import { html } from "lit-html";
|
import { html } from "lit-html";
|
||||||
import { __ } from '../../i18n';
|
import { modal_header_close_button } from "modals/templates/buttons.js"
|
||||||
import spinner from "../../templates/spinner.js";
|
|
||||||
import { modal_header_close_button } from "./buttons.js"
|
|
||||||
|
|
||||||
|
|
||||||
function getRoleHelpText (role) {
|
function getRoleHelpText (role) {
|
@ -3107,7 +3107,6 @@ describe("Groupchats", function () {
|
|||||||
|
|
||||||
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
|
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
|
||||||
const view = _converse.chatboxviews.get('lounge@montague.lit');
|
const view = _converse.chatboxviews.get('lounge@montague.lit');
|
||||||
spyOn(view.model, 'setAffiliation').and.callThrough();
|
|
||||||
spyOn(view.model, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
|
spyOn(view.model, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
|
||||||
|
|
||||||
let presence = $pres({
|
let presence = $pres({
|
||||||
@ -3136,7 +3135,10 @@ describe("Groupchats", function () {
|
|||||||
expect(err_msg.textContent.trim()).toBe(
|
expect(err_msg.textContent.trim()).toBe(
|
||||||
"Error: the \"owner\" command takes two arguments, the user's nickname and optionally a reason.");
|
"Error: the \"owner\" command takes two arguments, the user's nickname and optionally a reason.");
|
||||||
|
|
||||||
expect(view.model.setAffiliation).not.toHaveBeenCalled();
|
const sel = 'iq[type="set"] query[xmlns="http://jabber.org/protocol/muc#admin"]';
|
||||||
|
const stanzas = _converse.connection.IQ_stanzas.filter(s => sizzle(sel, s).length);
|
||||||
|
expect(stanzas.length).toBe(0);
|
||||||
|
|
||||||
// XXX: Calling onFormSubmitted directly, trying
|
// XXX: Calling onFormSubmitted directly, trying
|
||||||
// again via triggering Event doesn't work for some weird
|
// again via triggering Event doesn't work for some weird
|
||||||
// reason.
|
// reason.
|
||||||
@ -3146,7 +3148,7 @@ describe("Groupchats", function () {
|
|||||||
expect(Array.from(view.querySelectorAll('.chat-error')).pop().textContent.trim()).toBe(
|
expect(Array.from(view.querySelectorAll('.chat-error')).pop().textContent.trim()).toBe(
|
||||||
"Error: couldn't find a groupchat participant based on your arguments");
|
"Error: couldn't find a groupchat participant based on your arguments");
|
||||||
|
|
||||||
expect(view.model.setAffiliation).not.toHaveBeenCalled();
|
expect(_converse.connection.IQ_stanzas.filter(s => sizzle(sel, s).length).length).toBe(0);
|
||||||
|
|
||||||
// Call now with the correct of arguments.
|
// Call now with the correct of arguments.
|
||||||
// XXX: Calling onFormSubmitted directly, trying
|
// XXX: Calling onFormSubmitted directly, trying
|
||||||
@ -3156,7 +3158,6 @@ describe("Groupchats", function () {
|
|||||||
bottom_panel.onFormSubmitted(new Event('submit'));
|
bottom_panel.onFormSubmitted(new Event('submit'));
|
||||||
|
|
||||||
expect(view.model.validateRoleOrAffiliationChangeArgs.calls.count()).toBe(3);
|
expect(view.model.validateRoleOrAffiliationChangeArgs.calls.count()).toBe(3);
|
||||||
expect(view.model.setAffiliation).toHaveBeenCalled();
|
|
||||||
// Check that the member list now gets updated
|
// Check that the member list now gets updated
|
||||||
expect(Strophe.serialize(sent_IQ)).toBe(
|
expect(Strophe.serialize(sent_IQ)).toBe(
|
||||||
`<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
|
`<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
|
||||||
@ -3196,7 +3197,6 @@ describe("Groupchats", function () {
|
|||||||
|
|
||||||
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
|
await mock.openAndEnterChatRoom(_converse, 'lounge@montague.lit', 'romeo');
|
||||||
const view = _converse.chatboxviews.get('lounge@montague.lit');
|
const view = _converse.chatboxviews.get('lounge@montague.lit');
|
||||||
spyOn(view.model, 'setAffiliation').and.callThrough();
|
|
||||||
spyOn(view.model, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
|
spyOn(view.model, 'validateRoleOrAffiliationChangeArgs').and.callThrough();
|
||||||
|
|
||||||
let presence = $pres({
|
let presence = $pres({
|
||||||
@ -3224,7 +3224,10 @@ describe("Groupchats", function () {
|
|||||||
await u.waitUntil(() => view.querySelector('.message:last-child')?.textContent?.trim() ===
|
await u.waitUntil(() => view.querySelector('.message:last-child')?.textContent?.trim() ===
|
||||||
"Error: the \"ban\" command takes two arguments, the user's nickname and optionally a reason.");
|
"Error: the \"ban\" command takes two arguments, the user's nickname and optionally a reason.");
|
||||||
|
|
||||||
expect(view.model.setAffiliation).not.toHaveBeenCalled();
|
const sel = 'iq[type="set"] query[xmlns="http://jabber.org/protocol/muc#admin"]';
|
||||||
|
const stanzas = _converse.connection.IQ_stanzas.filter(s => sizzle(sel, s).length);
|
||||||
|
expect(stanzas.length).toBe(0);
|
||||||
|
|
||||||
// Call now with the correct amount of arguments.
|
// Call now with the correct amount of arguments.
|
||||||
// XXX: Calling onFormSubmitted directly, trying
|
// XXX: Calling onFormSubmitted directly, trying
|
||||||
// again via triggering Event doesn't work for some weird
|
// again via triggering Event doesn't work for some weird
|
||||||
@ -3233,7 +3236,6 @@ describe("Groupchats", function () {
|
|||||||
bottom_panel.onFormSubmitted(new Event('submit'));
|
bottom_panel.onFormSubmitted(new Event('submit'));
|
||||||
|
|
||||||
expect(view.model.validateRoleOrAffiliationChangeArgs.calls.count()).toBe(2);
|
expect(view.model.validateRoleOrAffiliationChangeArgs.calls.count()).toBe(2);
|
||||||
expect(view.model.setAffiliation).toHaveBeenCalled();
|
|
||||||
// Check that the member list now gets updated
|
// Check that the member list now gets updated
|
||||||
expect(Strophe.serialize(sent_IQ)).toBe(
|
expect(Strophe.serialize(sent_IQ)).toBe(
|
||||||
`<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
|
`<iq id="${IQ_id}" to="lounge@montague.lit" type="set" xmlns="jabber:client">`+
|
||||||
|
@ -4,6 +4,7 @@ import { __ } from 'i18n';
|
|||||||
import { _converse, api, converse } from "@converse/headless/core";
|
import { _converse, api, converse } from "@converse/headless/core";
|
||||||
import { html } from "lit-html";
|
import { html } from "lit-html";
|
||||||
import { parseMessageForCommands } from 'plugins/chatview/utils.js';
|
import { parseMessageForCommands } from 'plugins/chatview/utils.js';
|
||||||
|
import { setAffiliation } from '@converse/headless/plugins/muc/affiliations/utils.js';
|
||||||
|
|
||||||
const { Strophe, $pres, $iq, sizzle, u } = converse.env;
|
const { Strophe, $pres, $iq, sizzle, u } = converse.env;
|
||||||
|
|
||||||
@ -187,10 +188,10 @@ function setRole (muc, command, args, required_affiliations = [], required_roles
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function setAffiliation (muc, command, args, required_affiliations) {
|
function verifyAndSetAffiliation (muc, command, args, required_affiliations) {
|
||||||
const affiliation = COMMAND_TO_AFFILIATION[command];
|
const affiliation = COMMAND_TO_AFFILIATION[command];
|
||||||
if (!affiliation) {
|
if (!affiliation) {
|
||||||
throw Error(`ChatRoomView#setAffiliation called with invalid command: ${command}`);
|
throw Error(`verifyAffiliations called with invalid command: ${command}`);
|
||||||
}
|
}
|
||||||
if (!muc.verifyAffiliations(required_affiliations)) {
|
if (!muc.verifyAffiliations(required_affiliations)) {
|
||||||
return false;
|
return false;
|
||||||
@ -223,8 +224,8 @@ function setAffiliation (muc, command, args, required_affiliations) {
|
|||||||
if (occupant && api.settings.get('auto_register_muc_nickname')) {
|
if (occupant && api.settings.get('auto_register_muc_nickname')) {
|
||||||
attrs['nick'] = occupant.get('nick');
|
attrs['nick'] = occupant.get('nick');
|
||||||
}
|
}
|
||||||
muc
|
|
||||||
.setAffiliation(affiliation, [attrs])
|
setAffiliation(affiliation, muc.get('jid'), [attrs])
|
||||||
.then(() => muc.occupants.fetchMembers())
|
.then(() => muc.occupants.fetchMembers())
|
||||||
.catch(err => muc.onCommandError(err));
|
.catch(err => muc.onCommandError(err));
|
||||||
}
|
}
|
||||||
@ -249,11 +250,11 @@ export function parseMessageForMUCCommands (muc, text) {
|
|||||||
|
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'admin': {
|
case 'admin': {
|
||||||
setAffiliation(muc, command, args, ['owner']);
|
verifyAndSetAffiliation(muc, command, args, ['owner']);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'ban': {
|
case 'ban': {
|
||||||
setAffiliation(muc, command, args, ['admin', 'owner']);
|
verifyAndSetAffiliation(muc, command, args, ['admin', 'owner']);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'modtools': {
|
case 'modtools': {
|
||||||
@ -293,7 +294,7 @@ export function parseMessageForMUCCommands (muc, text) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'member': {
|
case 'member': {
|
||||||
setAffiliation(muc, command, args, ['admin', 'owner']);
|
verifyAndSetAffiliation(muc, command, args, ['admin', 'owner']);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'nick': {
|
case 'nick': {
|
||||||
@ -316,7 +317,7 @@ export function parseMessageForMUCCommands (muc, text) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'owner':
|
case 'owner':
|
||||||
setAffiliation(muc, command, args, ['owner']);
|
verifyAndSetAffiliation(muc, command, args, ['owner']);
|
||||||
break;
|
break;
|
||||||
case 'op': {
|
case 'op': {
|
||||||
setRole(muc, command, args, ['admin', 'owner']);
|
setRole(muc, command, args, ['admin', 'owner']);
|
||||||
@ -336,7 +337,7 @@ export function parseMessageForMUCCommands (muc, text) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'revoke': {
|
case 'revoke': {
|
||||||
setAffiliation(muc, command, args, ['admin', 'owner']);
|
verifyAndSetAffiliation(muc, command, args, ['admin', 'owner']);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'topic':
|
case 'topic':
|
||||||
|
Loading…
Reference in New Issue
Block a user