converse.js
Version:
Browser based XMPP chat client
161 lines (141 loc) • 5.51 kB
JavaScript
/**
* @typedef {module:plugin-muc-parsers.MemberListItem} MemberListItem
* @typedef {import('@converse/skeletor/src/types/collection').Attributes} Attributes
* @typedef {import('@converse/skeletor/src/types/collection').CollectionOptions} CollectionOptions
* @typedef {import('@converse/skeletor/src/types/collection').Options} Options
*/
import MUCOccupant from './occupant.js';
import _converse from '../../shared/_converse.js';
import api from '../../shared/api/index.js';
import converse from '../../shared/api/public.js';
import { Collection, Model } from '@converse/skeletor';
import { Strophe } from 'strophe.js';
import { getAffiliationList } from './affiliations/utils.js';
import { occupantsComparator } from './utils.js';
import { getUniqueId } from '../../utils/index.js';
const { u } = converse.env;
/**
* A list of {@link MUCOccupant} instances, representing participants in a MUC.
* @class
* @memberOf _converse
*/
class MUCOccupants extends Collection {
constructor (attrs, options) {
super(
attrs,
Object.assign({ comparator: occupantsComparator }, options)
);
this.chatroom = null;
}
get model() {
return MUCOccupant;
}
initialize() {
this.on('change:nick', () => this.sort());
this.on('change:role', () => this.sort());
}
static getAutoFetchedAffiliationLists () {
const affs = api.settings.get('muc_fetch_members');
return Array.isArray(affs) ? affs : affs ? ['member', 'admin', 'owner'] : [];
}
/**
* @param {Model|Attributes} attrs
* @param {Options} [options]
*/
create (attrs, options) {
if (attrs.id || attrs instanceof Model) {
return super.create(attrs, options);
}
attrs.id = attrs.occupant_id || getUniqueId();
return super.create(attrs, options);
}
async fetchMembers () {
if (!['member', 'admin', 'owner'].includes(this.getOwnOccupant()?.get('affiliation'))) {
// https://xmpp.org/extensions/xep-0045.html#affil-priv
return;
}
const affiliations = MUCOccupants.getAutoFetchedAffiliationLists();
if (affiliations.length === 0) {
return;
}
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(
/**
* @param {MemberListItem[]} acc
* @param {MemberListItem[]|Error} val
* @returns {MemberListItem[]}
*/
(acc, val) => {
if (val instanceof Error) {
return acc;
}
return [...val, ...acc];
}, []
);
const known_affiliations = affiliations.filter(
a => !u.isErrorObject(aff_lists[affiliations.indexOf(a)])
);
const new_jids = /** @type {MemberListItem[]} */(new_members).map(m => m.jid).filter(m => m !== undefined);
const new_nicks = /** @type {MemberListItem[]} */(new_members).map(
(m) => (!m.jid && m.nick) || undefined).filter(m => m !== undefined);
const removed_members = this.filter(m => {
return (
known_affiliations.includes(m.get('affiliation')) &&
!new_nicks.includes(m.get('nick')) &&
!new_jids.includes(m.get('jid'))
);
});
const bare_jid = _converse.session.get('bare_jid');
removed_members.forEach(occupant => {
if (occupant.get('jid') === bare_jid) {
return;
} else if (occupant.get('show') === 'offline') {
occupant.destroy();
} else {
occupant.save('affiliation', null);
}
});
/** @type {MemberListItem[]} */(new_members).forEach(attrs => {
const occupant = this.findOccupant(attrs);
occupant ? occupant.save(attrs) : this.create(attrs);
});
/**
* Triggered once the member lists for this MUC have been fetched and processed.
* @event _converse#membersFetched
* @example _converse.api.listen.on('membersFetched', () => { ... });
*/
api.trigger('membersFetched');
}
/**
* Try to find an existing occupant based on the provided {@link OccupantData} object.
* Fetching the user by `occupant_id` is the quickest, O(1),
* since it's a dictionary lookup.
* Fetching by jid or nick is O(n), since it requires traversing an array.
* Lookup by occupant_id is done first, then jid, and then nick.
*
* @param {import('./types').OccupantData} data
*/
findOccupant (data) {
if (data.occupant_id) {
return this.get(data.occupant_id);
}
const jid = data.jid && Strophe.getBareJidFromJid(data.jid);
return jid && this.findWhere({ jid }) ||
data.nick && this.findWhere({ 'nick': data.nick });
}
/**
* Get the {@link MUCOccupant} instance which
* represents the current user.
* @method _converse.MUCOccupants#getOwnOccupant
* @returns {MUCOccupant}
*/
getOwnOccupant () {
return this.findOccupant({
jid: _converse.session.get('bare_jid'),
occupant_id: this.chatroom.get('occupant_id')
});
}
}
// @ts-ignore
export default MUCOccupants;