1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528 |
- // Converse.js
- // http://conversejs.org
- //
- // Copyright (c) 2013-2018, the Converse.js developers
- // Licensed under the Mozilla Public License (MPLv2)
- (function (root, factory) {
- define([
- "./utils/form",
- "./converse-core",
- "./converse-disco",
- "backbone.overview/backbone.overview",
- "backbone.overview/backbone.orderedlistview",
- "backbone.vdomview",
- "./utils/muc",
- "./utils/emoji"
- ], factory);
- }(this, function (u, converse) {
- "use strict";
- const MUC_ROLE_WEIGHTS = {
- 'moderator': 1,
- 'participant': 2,
- 'visitor': 3,
- 'none': 2,
- };
- const { Strophe, Backbone, Promise, $iq, $build, $msg, $pres, b64_sha1, sizzle, f, moment, _ } = converse.env;
- // Add Strophe Namespaces
- Strophe.addNamespace('MUC_ADMIN', Strophe.NS.MUC + "#admin");
- Strophe.addNamespace('MUC_OWNER', Strophe.NS.MUC + "#owner");
- Strophe.addNamespace('MUC_REGISTER', "jabber:iq:register");
- Strophe.addNamespace('MUC_ROOMCONF', Strophe.NS.MUC + "#roomconfig");
- Strophe.addNamespace('MUC_USER', Strophe.NS.MUC + "#user");
- converse.MUC_NICK_CHANGED_CODE = "303";
- converse.ROOM_FEATURES = [
- 'passwordprotected', 'unsecured', 'hidden',
- 'publicroom', 'membersonly', 'open', 'persistent',
- 'temporary', 'nonanonymous', 'semianonymous',
- 'moderated', 'unmoderated', 'mam_enabled'
- ];
- converse.ROOMSTATUS = {
- CONNECTED: 0,
- CONNECTING: 1,
- NICKNAME_REQUIRED: 2,
- PASSWORD_REQUIRED: 3,
- DISCONNECTED: 4,
- ENTERED: 5
- };
- converse.plugins.add('converse-muc', {
- /* Optional dependencies are other plugins which might be
- * overridden or relied upon, and therefore need to be loaded before
- * this plugin. They are called "optional" because they might not be
- * available, in which case any overrides applicable to them will be
- * ignored.
- *
- * It's possible however to make optional dependencies non-optional.
- * If the setting "strict_plugin_dependencies" is set to true,
- * an error will be raised if the plugin is not found.
- *
- * NB: These plugins need to have already been loaded via require.js.
- */
- dependencies: ["converse-chatboxes", "converse-disco", "converse-controlbox"],
- overrides: {
- tearDown () {
- const { _converse } = this.__super__,
- groupchats = this.chatboxes.where({'type': _converse.CHATROOMS_TYPE});
- _.each(groupchats, gc => u.safeSave(gc, {'connection_status': converse.ROOMSTATUS.DISCONNECTED}));
- this.__super__.tearDown.call(this, arguments);
- },
- ChatBoxes: {
- model (attrs, options) {
- const { _converse } = this.__super__;
- if (attrs.type == _converse.CHATROOMS_TYPE) {
- return new _converse.ChatRoom(attrs, options);
- } else {
- return this.__super__.model.apply(this, arguments);
- }
- },
- }
- },
- initialize () {
- /* The initialize function gets called as soon as the plugin is
- * loaded by converse.js's plugin machinery.
- */
- const { _converse } = this,
- { __ } = _converse;
- // Configuration values for this plugin
- // ====================================
- // Refer to docs/source/configuration.rst for explanations of these
- // configuration settings.
- _converse.api.settings.update({
- allow_muc: true,
- allow_muc_invitations: true,
- auto_join_on_invite: false,
- auto_join_rooms: [],
- auto_register_muc_nickname: false,
- muc_domain: undefined,
- muc_history_max_stanzas: undefined,
- muc_instant_rooms: true,
- muc_nickname_from_jid: false
- });
- _converse.api.promises.add(['roomsAutoJoined']);
- function openRoom (jid) {
- if (!u.isValidMUCJID(jid)) {
- return _converse.log(
- `Invalid JID "${jid}" provided in URL fragment`,
- Strophe.LogLevel.WARN
- );
- }
- const promises = [_converse.api.waitUntil('roomsAutoJoined')]
- if (_converse.allow_bookmarks) {
- promises.push( _converse.api.waitUntil('bookmarksInitialized'));
- }
- Promise.all(promises).then(() => {
- _converse.api.rooms.open(jid);
- });
- }
- _converse.router.route('converse/room?jid=:jid', openRoom);
- _converse.openChatRoom = function (jid, settings, bring_to_foreground) {
- /* Opens a groupchat, making sure that certain attributes
- * are correct, for example that the "type" is set to
- * "chatroom".
- */
- settings.type = _converse.CHATROOMS_TYPE;
- settings.id = jid;
- settings.box_id = b64_sha1(jid)
- const chatbox = _converse.chatboxes.getChatBox(jid, settings, true);
- chatbox.trigger('show', true);
- return chatbox;
- }
- _converse.ChatRoom = _converse.ChatBox.extend({
- defaults () {
- return _.assign(
- _.clone(_converse.ChatBox.prototype.defaults),
- _.zipObject(converse.ROOM_FEATURES, _.map(converse.ROOM_FEATURES, _.stubFalse)),
- {
- // For group chats, we distinguish between generally unread
- // messages and those ones that specifically mention the
- // user.
- //
- // To keep things simple, we reuse `num_unread` from
- // _converse.ChatBox to indicate unread messages which
- // mention the user and `num_unread_general` to indicate
- // generally unread messages (which *includes* mentions!).
- 'num_unread_general': 0,
- 'affiliation': null,
- 'connection_status': converse.ROOMSTATUS.DISCONNECTED,
- 'name': '',
- 'nick': _converse.xmppstatus.get('nickname') || _converse.nickname,
- 'description': '',
- 'features_fetched': false,
- 'roomconfig': {},
- 'type': _converse.CHATROOMS_TYPE,
- 'message_type': 'groupchat'
- }
- );
- },
- initialize() {
- this.constructor.__super__.initialize.apply(this, arguments);
- this.on('change:connection_status', this.onConnectionStatusChanged, this);
- this.occupants = new _converse.ChatRoomOccupants();
- this.occupants.browserStorage = new Backbone.BrowserStorage.session(
- b64_sha1(`converse.occupants-${_converse.bare_jid}${this.get('jid')}`)
- );
- this.occupants.chatroom = this;
- this.registerHandlers();
- },
- async onConnectionStatusChanged () {
- if (this.get('connection_status') === converse.ROOMSTATUS.ENTERED &&
- _converse.auto_register_muc_nickname &&
- !this.get('reserved_nick')) {
- const result = await _converse.api.disco.supports(Strophe.NS.MUC_REGISTER, this.get('jid'));
- if (result.length) {
- this.registerNickname()
- }
- }
- },
- registerHandlers () {
- /* Register presence and message handlers for this chat
- * groupchat
- */
- const room_jid = this.get('jid');
- this.removeHandlers();
- this.presence_handler = _converse.connection.addHandler((stanza) => {
- _.each(_.values(this.handlers.presence), (callback) => callback(stanza));
- this.onPresence(stanza);
- return true;
- },
- null, 'presence', null, null, room_jid,
- {'ignoreNamespaceFragment': true, 'matchBareFromJid': true}
- );
- this.message_handler = _converse.connection.addHandler((stanza) => {
- _.each(_.values(this.handlers.message), (callback) => callback(stanza));
- this.onMessage(stanza);
- return true;
- }, null, 'message', 'groupchat', null, room_jid,
- {'matchBareFromJid': true}
- );
- },
- removeHandlers () {
- /* Remove the presence and message handlers that were
- * registered for this groupchat.
- */
- if (this.message_handler) {
- _converse.connection.deleteHandler(this.message_handler);
- delete this.message_handler;
- }
- if (this.presence_handler) {
- _converse.connection.deleteHandler(this.presence_handler);
- delete this.presence_handler;
- }
- return this;
- },
- addHandler (type, name, callback) {
- /* Allows 'presence' and 'message' handlers to be
- * registered. These will be executed once presence or
- * message stanzas are received, and *before* this model's
- * own handlers are executed.
- */
- if (_.isNil(this.handlers)) {
- this.handlers = {};
- }
- if (_.isNil(this.handlers[type])) {
- this.handlers[type] = {};
- }
- this.handlers[type][name] = callback;
- },
- getDisplayName () {
- return this.get('name') || this.get('jid');
- },
- join (nick, password) {
- /* Join the groupchat.
- *
- * Parameters:
- * (String) nick: The user's nickname
- * (String) password: Optional password, if required by
- * the groupchat.
- */
- nick = nick ? nick : this.get('nick');
- if (!nick) {
- throw new TypeError('join: You need to provide a valid nickname');
- }
- if (this.get('connection_status') === converse.ROOMSTATUS.ENTERED) {
- // We have restored a groupchat from session storage,
- // so we don't send out a presence stanza again.
- return this;
- }
- const stanza = $pres({
- 'from': _converse.connection.jid,
- 'to': this.getRoomJIDAndNick(nick)
- }).c("x", {'xmlns': Strophe.NS.MUC})
- .c("history", {'maxstanzas': this.get('mam_enabled') ? 0 : _converse.muc_history_max_stanzas}).up();
- if (password) {
- stanza.cnode(Strophe.xmlElement("password", [], password));
- }
- this.save('connection_status', converse.ROOMSTATUS.CONNECTING);
- _converse.connection.send(stanza);
- return this;
- },
- leave (exit_msg) {
- /* Leave the groupchat.
- *
- * Parameters:
- * (String) exit_msg: Optional message to indicate your
- * reason for leaving.
- */
- this.occupants.browserStorage._clear();
- this.occupants.reset();
- const disco_entity = _converse.disco_entities.get(this.get('jid'));
- if (disco_entity) {
- disco_entity.destroy();
- }
- if (_converse.connection.connected) {
- this.sendUnavailablePresence(exit_msg);
- }
- u.safeSave(this, {'connection_status': converse.ROOMSTATUS.DISCONNECTED});
- this.removeHandlers();
- },
- sendUnavailablePresence (exit_msg) {
- const presence = $pres({
- type: "unavailable",
- from: _converse.connection.jid,
- to: this.getRoomJIDAndNick()
- });
- if (exit_msg !== null) {
- presence.c("status", exit_msg);
- }
- _converse.connection.sendPresence(presence);
- },
- getReferenceForMention (mention, index) {
- const longest_match = u.getLongestSubstring(
- mention,
- this.occupants.map(o => o.getDisplayName())
- );
- if (!longest_match) {
- return null;
- }
- if ((mention[longest_match.length] || '').match(/[A-Za-zäëïöüâêîôûáéíóúàèìòùÄËÏÖÜÂÊÎÔÛÁÉÍÓÚÀÈÌÒÙ]/i)) {
- // avoid false positives, i.e. mentions that have
- // further alphabetical characters than our longest
- // match.
- return null;
- }
- const occupant = this.occupants.findOccupant({'nick': longest_match}) ||
- this.occupants.findOccupant({'jid': longest_match});
- if (!occupant) {
- return null;
- }
- const obj = {
- 'begin': index,
- 'end': index + longest_match.length,
- 'value': longest_match,
- 'type': 'mention'
- };
- if (occupant.get('jid')) {
- obj.uri = `xmpp:${occupant.get('jid')}`
- }
- return obj;
- },
- extractReference (text, index) {
- for (let i=index; i<text.length; i++) {
- if (text[i] !== '@') {
- continue
- } else {
- const match = text.slice(i+1),
- ref = this.getReferenceForMention(match, i);
- if (ref) {
- return [text.slice(0, i) + match, ref, i]
- }
- }
- }
- return;
- },
- parseTextForReferences (text) {
- const refs = [];
- let index = 0;
- while (index < (text || '').length) {
- const result = this.extractReference(text, index);
- if (result) {
- text = result[0]; // @ gets filtered out
- refs.push(result[1]);
- index = result[2];
- } else {
- break;
- }
- }
- return [text, refs];
- },
- getOutgoingMessageAttributes (text, spoiler_hint) {
- const is_spoiler = this.get('composing_spoiler');
- var references;
- [text, references] = this.parseTextForReferences(text);
- return {
- 'from': `${this.get('jid')}/${this.get('nick')}`,
- 'fullname': this.get('nick'),
- 'is_spoiler': is_spoiler,
- 'message': text ? u.httpToGeoUri(u.shortnameToUnicode(text), _converse) : undefined,
- 'nick': this.get('nick'),
- 'references': references,
- 'sender': 'me',
- 'spoiler_hint': is_spoiler ? spoiler_hint : undefined,
- 'type': 'groupchat'
- };
- },
- getRoomJIDAndNick (nick) {
- /* Utility method to construct the JID for the current user
- * as occupant of the groupchat.
- *
- * This is the groupchat JID, with the user's nick added at the
- * end.
- *
- * For example: groupchat@conference.example.org/nickname
- */
- if (nick) {
- this.save({'nick': nick});
- } else {
- nick = this.get('nick');
- }
- const groupchat = this.get('jid');
- const jid = Strophe.getBareJidFromJid(groupchat);
- return jid + (nick !== null ? `/${nick}` : "");
- },
- sendChatState () {
- /* Sends a message with the status of the user in this chat session
- * as taken from the 'chat_state' attribute of the chat box.
- * See XEP-0085 Chat State Notifications.
- */
- if (this.get('connection_status') !== converse.ROOMSTATUS.ENTERED) {
- return;
- }
- const chat_state = this.get('chat_state');
- if (chat_state === _converse.GONE) {
- // <gone/> is not applicable within MUC context
- return;
- }
- _converse.connection.send(
- $msg({'to':this.get('jid'), 'type': 'groupchat'})
- .c(chat_state, {'xmlns': Strophe.NS.CHATSTATES}).up()
- .c('no-store', {'xmlns': Strophe.NS.HINTS}).up()
- .c('no-permanent-store', {'xmlns': Strophe.NS.HINTS})
- );
- },
- directInvite (recipient, reason) {
- /* Send a direct invitation as per XEP-0249
- *
- * Parameters:
- * (String) recipient - JID of the person being invited
- * (String) reason - Optional reason for the invitation
- */
- if (this.get('membersonly')) {
- // When inviting to a members-only groupchat, we first add
- // the person to the member list by giving them an
- // affiliation of 'member' (if they're not affiliated
- // already), otherwise they won't be able to join.
- const map = {}; map[recipient] = 'member';
- const deltaFunc = _.partial(u.computeAffiliationsDelta, true, false);
- this.updateMemberLists(
- [{'jid': recipient, 'affiliation': 'member', 'reason': reason}],
- ['member', 'owner', 'admin'],
- deltaFunc
- );
- }
- const attrs = {
- 'xmlns': 'jabber:x:conference',
- 'jid': this.get('jid')
- };
- if (reason !== null) { attrs.reason = reason; }
- if (this.get('password')) { attrs.password = this.get('password'); }
- const invitation = $msg({
- from: _converse.connection.jid,
- to: recipient,
- id: _converse.connection.getUniqueId()
- }).c('x', attrs);
- _converse.connection.send(invitation);
- _converse.emit('roomInviteSent', {
- 'room': this,
- 'recipient': recipient,
- 'reason': reason
- });
- },
- async refreshRoomFeatures () {
- await _converse.api.disco.refreshFeatures(this.get('jid'));
- return this.getRoomFeatures();
- },
- async getRoomFeatures () {
- const features = await _converse.api.disco.getFeatures(this.get('jid')),
- fields = await _converse.api.disco.getFields(this.get('jid')),
- identity = await _converse.api.disco.getIdentity('conference', 'text', this.get('jid')),
- attrs = {
- 'features_fetched': moment().format(),
- 'name': identity && identity.get('name')
- };
- features.each(feature => {
- const fieldname = feature.get('var');
- if (!fieldname.startsWith('muc_')) {
- if (fieldname === Strophe.NS.MAM) {
- attrs.mam_enabled = true;
- }
- return;
- }
- attrs[fieldname.replace('muc_', '')] = true;
- });
- attrs.description = _.get(fields.findWhere({'var': "muc#roominfo_description"}), 'attributes.value');
- this.save(attrs);
- },
- requestMemberList (affiliation) {
- /* Send an IQ stanza to the server, asking it for the
- * member-list of this groupchat.
- *
- * See: http://xmpp.org/extensions/xep-0045.html#modifymember
- *
- * Parameters:
- * (String) affiliation: The specific member list to
- * fetch. 'admin', 'owner' or 'member'.
- *
- * Returns:
- * A promise which resolves once the list has been
- * retrieved.
- */
- affiliation = affiliation || 'member';
- const iq = $iq({to: this.get('jid'), type: "get"})
- .c("query", {xmlns: Strophe.NS.MUC_ADMIN})
- .c("item", {'affiliation': affiliation});
- return _converse.api.sendIQ(iq);
- },
- setAffiliation (affiliation, members) {
- /* Send IQ stanzas to the server to set an affiliation for
- * the provided JIDs.
- *
- * See: http://xmpp.org/extensions/xep-0045.html#modifymember
- *
- * XXX: 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
- *
- * Parameters:
- * (String) affiliation: The affiliation
- * (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:
- * A promise which resolves and fails depending on the
- * XMPP server response.
- */
- members = _.filter(members, (member) =>
- // We only want those members who have the right
- // affiliation (or none, which implies the provided one).
- _.isUndefined(member.affiliation) ||
- member.affiliation === affiliation
- );
- const promises = _.map(members, _.bind(this.sendAffiliationIQ, this, affiliation));
- return Promise.all(promises);
- },
- saveConfiguration (form) {
- /* Submit the groupchat configuration form by sending an IQ
- * stanza to the server.
- *
- * Returns a promise which resolves once the XMPP server
- * has return a response IQ.
- *
- * Parameters:
- * (HTMLElement) form: The configuration form DOM element.
- * If no form is provided, the default configuration
- * values will be used.
- */
- return new Promise((resolve, reject) => {
- const inputs = form ? sizzle(':input:not([type=button]):not([type=submit])', form) : [],
- configArray = _.map(inputs, u.webForm2xForm);
- this.sendConfiguration(configArray, resolve, reject);
- });
- },
- autoConfigureChatRoom () {
- /* Automatically configure groupchat based on this model's
- * 'roomconfig' data.
- *
- * Returns a promise which resolves once a response IQ has
- * been received.
- */
- return new Promise((resolve, reject) => {
- this.fetchRoomConfiguration().then((stanza) => {
- const configArray = [],
- fields = stanza.querySelectorAll('field'),
- config = this.get('roomconfig');
- let count = fields.length;
- _.each(fields, (field) => {
- const fieldname = field.getAttribute('var').replace('muc#roomconfig_', ''),
- type = field.getAttribute('type');
- let value;
- if (fieldname in config) {
- switch (type) {
- case 'boolean':
- value = config[fieldname] ? 1 : 0;
- break;
- case 'list-multi':
- // TODO: we don't yet handle "list-multi" types
- value = field.innerHTML;
- break;
- default:
- value = config[fieldname];
- }
- field.innerHTML = $build('value').t(value);
- }
- configArray.push(field);
- if (!--count) {
- this.sendConfiguration(configArray, resolve, reject);
- }
- });
- });
- });
- },
- fetchRoomConfiguration () {
- /* Send an IQ stanza to fetch the groupchat configuration data.
- * Returns a promise which resolves once the response IQ
- * has been received.
- */
- return new Promise((resolve, reject) => {
- _converse.connection.sendIQ(
- $iq({
- 'to': this.get('jid'),
- 'type': "get"
- }).c("query", {xmlns: Strophe.NS.MUC_OWNER}),
- resolve,
- reject
- );
- });
- },
- sendConfiguration (config, callback, errback) {
- /* Send an IQ stanza with the groupchat configuration.
- *
- * Parameters:
- * (Array) config: The groupchat configuration
- * (Function) callback: Callback upon succesful IQ response
- * The first parameter passed in is IQ containing the
- * groupchat configuration.
- * The second is the response IQ from the server.
- * (Function) errback: Callback upon error IQ response
- * The first parameter passed in is IQ containing the
- * groupchat configuration.
- * The second is the response IQ from the server.
- */
- const iq = $iq({to: this.get('jid'), type: "set"})
- .c("query", {xmlns: Strophe.NS.MUC_OWNER})
- .c("x", {xmlns: Strophe.NS.XFORM, type: "submit"});
- _.each(config || [], function (node) { iq.cnode(node).up(); });
- callback = _.isUndefined(callback) ? _.noop : _.partial(callback, iq.nodeTree);
- errback = _.isUndefined(errback) ? _.noop : _.partial(errback, iq.nodeTree);
- return _converse.connection.sendIQ(iq, callback, errback);
- },
- saveAffiliationAndRole (pres) {
- /* Parse the presence stanza for the current user's
- * affiliation.
- *
- * Parameters:
- * (XMLElement) pres: A <presence> stanza.
- */
- const item = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"] item`, pres).pop();
- const is_self = pres.querySelector("status[code='110']");
- if (is_self && !_.isNil(item)) {
- const affiliation = item.getAttribute('affiliation');
- const role = item.getAttribute('role');
- if (affiliation) {
- this.save({'affiliation': affiliation});
- }
- if (role) {
- this.save({'role': role});
- }
- }
- },
- sendAffiliationIQ (affiliation, member) {
- /* Send an IQ stanza specifying an affiliation change.
- *
- * Paremeters:
- * (String) affiliation: affiliation (could also be stored
- * on the member object).
- * (Object) member: Map containing the member's jid and
- * optionally a reason and affiliation.
- */
- return new Promise((resolve, reject) => {
- 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 (!_.isUndefined(member.reason)) {
- iq.c("reason", member.reason);
- }
- _converse.connection.sendIQ(iq, resolve, reject);
- });
- },
- setAffiliations (members) {
- /* Send IQ stanzas to the server to modify the
- * affiliations in this groupchat.
- *
- * See: http://xmpp.org/extensions/xep-0045.html#modifymember
- *
- * Parameters:
- * (Object) members: A map of jids, affiliations and optionally reasons
- * (Function) onSuccess: callback for a succesful response
- * (Function) onError: callback for an error response
- */
- const affiliations = _.uniq(_.map(members, 'affiliation'));
- return Promise.all(_.map(affiliations, _.partial(this.setAffiliation.bind(this), _, members)));
- },
- async getJidsWithAffiliations (affiliations) {
- /* Returns a map of JIDs that have the affiliations
- * as provided.
- */
- if (_.isString(affiliations)) {
- affiliations = [affiliations];
- }
- const result = await Promise.all(affiliations.map(a =>
- this.requestMemberList(a)
- .then(iq => u.parseMemberListIQ(iq))
- .catch(iq => {
- _converse.log(iq, Strophe.LogLevel.ERROR);
- })
- ));
- return [].concat.apply([], result).filter(p => p);
- },
- updateMemberLists (members, affiliations, deltaFunc) {
- /* Fetch the lists of users with the given affiliations.
- * Then compute the delta between those users and
- * the passed in members, and if it exists, send the delta
- * to the XMPP server to update the member list.
- *
- * Parameters:
- * (Object) members: Map of member jids and affiliations.
- * (String|Array) affiliation: An array of affiliations or
- * a string if only one affiliation.
- * (Function) deltaFunc: The function to compute the delta
- * between old and new member lists.
- *
- * Returns:
- * A promise which is resolved once the list has been
- * updated or once it's been established there's no need
- * to update the list.
- */
- this.getJidsWithAffiliations(affiliations)
- .then(old_members => this.setAffiliations(deltaFunc(members, old_members)))
- .then(() => this.occupants.fetchMembers())
- .catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
- },
- getDefaultNick () {
- /* The default nickname (used when muc_nickname_from_jid is true)
- * is the node part of the user's JID.
- * We put this in a separate method so that it can be
- * overridden by plugins.
- */
- const nick = _converse.xmppstatus.vcard.get('nickname');
- if (nick) {
- return nick;
- } else if (_converse.muc_nickname_from_jid) {
- return Strophe.unescapeNode(Strophe.getNodeFromJid(_converse.bare_jid));
- }
- },
- checkForReservedNick () {
- /* Use service-discovery to ask the XMPP server whether
- * this user has a reserved nickname for this groupchat.
- * If so, we'll use that, otherwise we render the nickname form.
- *
- * Parameters:
- * (Function) callback: Callback upon succesful IQ response
- * (Function) errback: Callback upon error IQ response
- */
- return _converse.api.sendIQ(
- $iq({
- 'to': this.get('jid'),
- 'from': _converse.connection.jid,
- 'type': "get"
- }).c("query", {
- 'xmlns': Strophe.NS.DISCO_INFO,
- 'node': 'x-roomuser-item'
- })
- ).then(iq => {
- const identity_el = iq.querySelector('query[node="x-roomuser-item"] identity'),
- nick = identity_el ? identity_el.getAttribute('name') : null;
- this.save({
- 'reserved_nick': nick,
- 'nick': nick
- }, {'silent': true});
- return iq;
- });
- },
- async registerNickname () {
- // See https://xmpp.org/extensions/xep-0045.html#register
- const nick = this.get('nick'),
- jid = this.get('jid');
- let iq, err_msg;
- try {
- iq = await _converse.api.sendIQ(
- $iq({
- 'to': jid,
- 'from': _converse.connection.jid,
- 'type': 'get'
- }).c('query', {'xmlns': Strophe.NS.MUC_REGISTER})
- );
- } catch (e) {
- if (sizzle('not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
- err_msg = __("You're not allowed to register yourself in this groupchat.");
- } else if (sizzle('registration-required[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
- err_msg = __("You're not allowed to register in this groupchat because it's members-only.");
- }
- _converse.log(e, Strophe.LogLevel.ERROR);
- return err_msg;
- }
- const required_fields = sizzle('field required', iq).map(f => f.parentElement);
- if (required_fields.length > 1 && required_fields[0].getAttribute('var') !== 'muc#register_roomnick') {
- return _converse.log(`Can't register the user register in the groupchat ${jid} due to the required fields`);
- }
- try {
- await _converse.api.sendIQ($iq({
- 'to': jid,
- 'from': _converse.connection.jid,
- 'type': 'set'
- }).c('query', {'xmlns': Strophe.NS.MUC_REGISTER})
- .c('x', {'xmlns': Strophe.NS.XFORM, 'type': 'submit'})
- .c('field', {'var': 'FORM_TYPE'}).c('value').t('http://jabber.org/protocol/muc#register').up().up()
- .c('field', {'var': 'muc#register_roomnick'}).c('value').t(nick)
- );
- } catch (e) {
- if (sizzle('service-unavailable[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
- err_msg = __("Can't register your nickname in this groupchat, it doesn't support registration.");
- } else if (sizzle('bad-request[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', e).length) {
- err_msg = __("Can't register your nickname in this groupchat, invalid data form supplied.");
- }
- _converse.log(err_msg);
- _converse.log(e, Strophe.LogLevel.ERROR);
- return err_msg;
- }
- },
- updateOccupantsOnPresence (pres) {
- /* Given a presence stanza, update the occupant model
- * based on its contents.
- *
- * Parameters:
- * (XMLElement) pres: The presence stanza
- */
- const data = this.parsePresence(pres);
- if (data.type === 'error' || (!data.jid && !data.nick)) {
- return true;
- }
- const occupant = this.occupants.findOccupant(data);
- if (data.type === 'unavailable' && occupant) {
- if (!_.includes(data.states, converse.MUC_NICK_CHANGED_CODE) && !occupant.isMember()) {
- // We only destroy the occupant if this is not a nickname change operation.
- // and if they're not on the member lists.
- // Before destroying we set the new data, so
- // that we can show the disconnection message.
- occupant.set(data);
- occupant.destroy();
- return;
- }
- }
- const jid = Strophe.getBareJidFromJid(data.jid);
- const attributes = _.extend(data, {
- 'jid': jid ? jid : undefined,
- 'resource': data.jid ? Strophe.getResourceFromJid(data.jid) : undefined
- });
- if (occupant) {
- occupant.save(attributes);
- } else {
- this.occupants.create(attributes);
- }
- },
- parsePresence (pres) {
- const from = pres.getAttribute("from"),
- type = pres.getAttribute("type"),
- data = {
- 'from': from,
- 'nick': Strophe.getResourceFromJid(from),
- 'type': type,
- 'states': [],
- 'show': type !== 'unavailable' ? 'online' : 'offline'
- };
- _.each(pres.childNodes, function (child) {
- switch (child.nodeName) {
- case "status":
- data.status = child.textContent || null;
- break;
- case "show":
- data.show = child.textContent || 'online';
- break;
- case "x":
- if (child.getAttribute("xmlns") === Strophe.NS.MUC_USER) {
- _.each(child.childNodes, function (item) {
- switch (item.nodeName) {
- case "item":
- data.affiliation = item.getAttribute("affiliation");
- data.role = item.getAttribute("role");
- data.jid = item.getAttribute("jid");
- data.nick = item.getAttribute("nick") || data.nick;
- break;
- case "status":
- if (item.getAttribute("code")) {
- data.states.push(item.getAttribute("code"));
- }
- }
- });
- } else if (child.getAttribute("xmlns") === Strophe.NS.VCARDUPDATE) {
- data.image_hash = _.get(child.querySelector('photo'), 'textContent');
- }
- }
- });
- return data;
- },
- isDuplicate (message, original_stanza) {
- const msgid = message.getAttribute('id'),
- jid = message.getAttribute('from');
- if (msgid) {
- return this.messages.where({'msgid': msgid, 'from': jid}).length;
- }
- return false;
- },
- fetchFeaturesIfConfigurationChanged (stanza) {
- const configuration_changed = stanza.querySelector("status[code='104']"),
- logging_enabled = stanza.querySelector("status[code='170']"),
- logging_disabled = stanza.querySelector("status[code='171']"),
- room_no_longer_anon = stanza.querySelector("status[code='172']"),
- room_now_semi_anon = stanza.querySelector("status[code='173']"),
- room_now_fully_anon = stanza.querySelector("status[code='173']");
- if (configuration_changed || logging_enabled || logging_disabled ||
- room_no_longer_anon || room_now_semi_anon || room_now_fully_anon) {
- this.refreshRoomFeatures();
- }
- },
- onMessage (stanza) {
- /* Handler for all MUC messages sent to this groupchat.
- *
- * Parameters:
- * (XMLElement) stanza: The message stanza.
- */
- this.fetchFeaturesIfConfigurationChanged(stanza);
- const original_stanza = stanza,
- forwarded = stanza.querySelector('forwarded');
- if (!_.isNull(forwarded)) {
- stanza = forwarded.querySelector('message');
- }
- if (this.isDuplicate(stanza, original_stanza)) {
- return;
- }
- const jid = stanza.getAttribute('from'),
- resource = Strophe.getResourceFromJid(jid),
- sender = resource && Strophe.unescapeNode(resource) || '';
- if (!this.handleMessageCorrection(stanza)) {
- if (sender === '') {
- return;
- }
- const subject_el = stanza.querySelector('subject');
- if (subject_el) {
- const subject = _.propertyOf(subject_el)('textContent') || '';
- u.safeSave(this, {'subject': {'author': sender, 'text': subject}});
- }
- this.createMessage(stanza, original_stanza)
- .then(msg => this.incrementUnreadMsgCounter(msg))
- .catch(_.partial(_converse.log, _, Strophe.LogLevel.FATAL));
- }
- if (sender !== this.get('nick')) {
- // We only emit an event if it's not our own message
- _converse.emit('message', {'stanza': original_stanza, 'chatbox': this});
- }
- },
- onPresence (pres) {
- /* Handles all MUC presence stanzas.
- *
- * Parameters:
- * (XMLElement) pres: The stanza
- */
- if (pres.getAttribute('type') === 'error') {
- this.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
- return;
- }
- const is_self = pres.querySelector("status[code='110']");
- if (is_self && pres.getAttribute('type') !== 'unavailable') {
- this.onOwnPresence(pres);
- }
- this.updateOccupantsOnPresence(pres);
- if (this.get('role') !== 'none' && this.get('connection_status') === converse.ROOMSTATUS.CONNECTING) {
- this.save('connection_status', converse.ROOMSTATUS.CONNECTED);
- }
- },
- onOwnPresence (pres) {
- /* Handles a received presence relating to the current
- * user.
- *
- * For locked groupchats (which are by definition "new"), the
- * groupchat will either be auto-configured or created instantly
- * (with default config) or a configuration groupchat will be
- * rendered.
- *
- * If the groupchat is not locked, then the groupchat will be
- * auto-configured only if applicable and if the current
- * user is the groupchat's owner.
- *
- * Parameters:
- * (XMLElement) pres: The stanza
- */
- this.saveAffiliationAndRole(pres);
- const locked_room = pres.querySelector("status[code='201']");
- if (locked_room) {
- if (this.get('auto_configure')) {
- this.autoConfigureChatRoom().then(() => this.refreshRoomFeatures());
- } else if (_converse.muc_instant_rooms) {
- // Accept default configuration
- this.saveConfiguration().then(() => this.getRoomFeatures());
- } else {
- this.trigger('configurationNeeded');
- return; // We haven't yet entered the groupchat, so bail here.
- }
- } else if (!this.get('features_fetched')) {
- // The features for this groupchat weren't fetched.
- // That must mean it's a new groupchat without locking
- // (in which case Prosody doesn't send a 201 status),
- // otherwise the features would have been fetched in
- // the "initialize" method already.
- if (this.get('affiliation') === 'owner' && this.get('auto_configure')) {
- this.autoConfigureChatRoom().then(() => this.refreshRoomFeatures());
- } else {
- this.getRoomFeatures();
- }
- }
- this.save('connection_status', converse.ROOMSTATUS.ENTERED);
- },
- isUserMentioned (message) {
- /* Returns a boolean to indicate whether the current user
- * was mentioned in a message.
- *
- * Parameters:
- * (String): The text message
- */
- const nick = this.get('nick');
- if (message.get('references').length) {
- const mentions = message.get('references').filter(ref => (ref.type === 'mention')).map(ref => ref.value);
- return _.includes(mentions, nick);
- } else {
- return (new RegExp(`\\b${nick}\\b`)).test(message.get('message'));
- }
- },
- incrementUnreadMsgCounter (message) {
- /* Given a newly received message, update the unread counter if
- * necessary.
- *
- * Parameters:
- * (XMLElement): The <messsage> stanza
- */
- if (!message) { return; }
- const body = message.get('message');
- if (_.isNil(body)) { return; }
- if (u.isNewMessage(message) && this.isHidden()) {
- const settings = {'num_unread_general': this.get('num_unread_general') + 1};
- if (this.isUserMentioned(message)) {
- settings.num_unread = this.get('num_unread') + 1;
- _converse.incrementMsgCounter();
- }
- this.save(settings);
- }
- },
- clearUnreadMsgCounter() {
- u.safeSave(this, {
- 'num_unread': 0,
- 'num_unread_general': 0
- });
- }
- });
- _converse.ChatRoomOccupant = Backbone.Model.extend({
- defaults: {
- 'show': 'offline'
- },
- initialize (attributes) {
- this.set(_.extend({
- 'id': _converse.connection.getUniqueId(),
- }, attributes));
- this.on('change:image_hash', this.onAvatarChanged, this);
- },
- onAvatarChanged () {
- const hash = this.get('image_hash');
- const vcards = [];
- if (this.get('jid')) {
- vcards.push(_converse.vcards.findWhere({'jid': this.get('jid')}));
- }
- vcards.push(_converse.vcards.findWhere({'jid': this.get('from')}));
- _.forEach(_.filter(vcards, undefined), (vcard) => {
- if (hash && vcard.get('image_hash') !== hash) {
- _converse.api.vcard.update(vcard);
- }
- });
- },
- getDisplayName () {
- return this.get('nick') || this.get('jid');
- },
- isMember () {
- return _.includes(['admin', 'owner', 'member'], this.get('affiliation'));
- }
- });
- _converse.ChatRoomOccupants = Backbone.Collection.extend({
- model: _converse.ChatRoomOccupant,
- comparator (occupant1, occupant2) {
- const role1 = occupant1.get('role') || 'none';
- const role2 = occupant2.get('role') || 'none';
- if (MUC_ROLE_WEIGHTS[role1] === MUC_ROLE_WEIGHTS[role2]) {
- const nick1 = occupant1.getDisplayName().toLowerCase();
- const nick2 = occupant2.getDisplayName().toLowerCase();
- return nick1 < nick2 ? -1 : (nick1 > nick2? 1 : 0);
- } else {
- return MUC_ROLE_WEIGHTS[role1] < MUC_ROLE_WEIGHTS[role2] ? -1 : 1;
- }
- },
- fetchMembers () {
- this.chatroom.getJidsWithAffiliations(['member', 'owner', 'admin'])
- .then(new_members => {
- const new_jids = new_members.map(m => m.jid).filter(m => !_.isUndefined(m)),
- new_nicks = new_members.map(m => !m.jid && m.nick || undefined).filter(m => !_.isUndefined(m)),
- removed_members = this.filter(m => {
- return f.includes(m.get('affiliation'), ['admin', 'member', 'owner']) &&
- !f.includes(m.get('nick'), new_nicks) &&
- !f.includes(m.get('jid'), new_jids);
- });
- _.each(removed_members, (occupant) => {
- if (occupant.get('jid') === _converse.bare_jid) { return; }
- if (occupant.get('show') === 'offline') {
- occupant.destroy();
- }
- });
- _.each(new_members, (attrs) => {
- let occupant;
- if (attrs.jid) {
- occupant = this.findOccupant({'jid': attrs.jid});
- } else {
- occupant = this.findOccupant({'nick': attrs.nick});
- }
- if (occupant) {
- occupant.save(attrs);
- } else {
- this.create(attrs);
- }
- });
- }).catch(_.partial(_converse.log, _, Strophe.LogLevel.ERROR));
- },
- findOccupant (data) {
- /* Try to find an existing occupant based on the passed in
- * data object.
- *
- * If we have a JID, we use that as lookup variable,
- * otherwise we use the nick. We don't always have both,
- * but should have at least one or the other.
- */
- const jid = Strophe.getBareJidFromJid(data.jid);
- if (jid !== null) {
- return this.where({'jid': jid}).pop();
- } else {
- return this.where({'nick': data.nick}).pop();
- }
- }
- });
- _converse.RoomsPanelModel = Backbone.Model.extend({
- defaults: {
- 'muc_domain': '',
- },
- });
- _converse.onDirectMUCInvitation = function (message) {
- /* A direct MUC invitation to join a groupchat has been received
- * See XEP-0249: Direct MUC invitations.
- *
- * Parameters:
- * (XMLElement) message: The message stanza containing the
- * invitation.
- */
- const x_el = sizzle('x[xmlns="jabber:x:conference"]', message).pop(),
- from = Strophe.getBareJidFromJid(message.getAttribute('from')),
- room_jid = x_el.getAttribute('jid'),
- reason = x_el.getAttribute('reason');
- let contact = _converse.roster.get(from),
- result;
- if (_converse.auto_join_on_invite) {
- result = true;
- } else {
- // Invite request might come from someone not your roster list
- contact = contact? contact.get('fullname'): Strophe.getNodeFromJid(from);
- if (!reason) {
- result = confirm(
- __("%1$s has invited you to join a groupchat: %2$s", contact, room_jid)
- );
- } else {
- result = confirm(
- __('%1$s has invited you to join a groupchat: %2$s, and left the following reason: "%3$s"',
- contact, room_jid, reason)
- );
- }
- }
- if (result === true) {
- const chatroom = _converse.openChatRoom(
- room_jid, {'password': x_el.getAttribute('password') });
- if (chatroom.get('connection_status') === converse.ROOMSTATUS.DISCONNECTED) {
- _converse.chatboxviews.get(room_jid).join();
- }
- }
- };
- if (_converse.allow_muc_invitations) {
- const registerDirectInvitationHandler = function () {
- _converse.connection.addHandler(
- (message) => {
- _converse.onDirectMUCInvitation(message);
- return true;
- }, 'jabber:x:conference', 'message');
- };
- _converse.on('connected', registerDirectInvitationHandler);
- _converse.on('reconnected', registerDirectInvitationHandler);
- }
- const getChatRoom = function (jid, attrs, create) {
- jid = jid.toLowerCase();
- attrs.type = _converse.CHATROOMS_TYPE;
- attrs.id = jid;
- attrs.box_id = b64_sha1(jid)
- return _converse.chatboxes.getChatBox(jid, attrs, create);
- };
- const createChatRoom = function (jid, attrs) {
- if (jid.startsWith('xmpp:') && jid.endsWith('?join')) {
- jid = jid.replace(/^xmpp:/, '').replace(/\?join$/, '');
- }
- return getChatRoom(jid, attrs, true);
- };
- function autoJoinRooms () {
- /* Automatically join groupchats, based on the
- * "auto_join_rooms" configuration setting, which is an array
- * of strings (groupchat JIDs) or objects (with groupchat JID and other
- * settings).
- */
- _.each(_converse.auto_join_rooms, function (groupchat) {
- if (_converse.chatboxes.where({'jid': groupchat}).length) {
- return;
- }
- if (_.isString(groupchat)) {
- _converse.api.rooms.open(groupchat);
- } else if (_.isObject(groupchat)) {
- _converse.api.rooms.open(groupchat.jid, groupchat.nick);
- } else {
- _converse.log(
- 'Invalid groupchat criteria specified for "auto_join_rooms"',
- Strophe.LogLevel.ERROR);
- }
- });
- _converse.emit('roomsAutoJoined');
- }
- function disconnectChatRooms () {
- /* When disconnecting, mark all groupchats as
- * disconnected, so that they will be properly entered again
- * when fetched from session storage.
- */
- _converse.chatboxes.each(function (model) {
- if (model.get('type') === _converse.CHATROOMS_TYPE) {
- model.save('connection_status', converse.ROOMSTATUS.DISCONNECTED);
- }
- });
- }
- function fetchRegistrationForm (room_jid, user_jid) {
- _converse.api.sendIQ(
- $iq({
- 'from': user_jid,
- 'to': room_jid,
- 'type': 'get'
- }).c('query', {'xmlns': Strophe.NS.REGISTER})
- ).then(iq => {
- }).catch(iq => {
- if (sizzle('item-not-found[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]', iq).length) {
- this.feedback.set('error', __(`Error: the groupchat ${this.model.getDisplayName()} does not exist.`));
- } else if (sizzle('not-allowed[xmlns="urn:ietf:params:xml:ns:xmpp-stanzas"]').length) {
- this.feedback.set('error', __(`Sorry, you're not allowed to register in this groupchat`));
- }
- });
- }
- /************************ BEGIN Event Handlers ************************/
- _converse.on('addClientFeatures', () => {
- if (_converse.allow_muc) {
- _converse.api.disco.own.features.add(Strophe.NS.MUC);
- }
- if (_converse.allow_muc_invitations) {
- _converse.api.disco.own.features.add('jabber:x:conference'); // Invites
- }
- });
- _converse.api.listen.on('chatBoxesFetched', autoJoinRooms);
- _converse.api.listen.on('disconnecting', disconnectChatRooms);
- _converse.api.listen.on('statusInitialized', () => {
- // XXX: For websocket connections, we disconnect from all
- // chatrooms when the page reloads. This is a workaround for
- // issue #1111 and should be removed once we support XEP-0198
- const options = {'once': true, 'passive': true};
- window.addEventListener(_converse.unloadevent, () => {
- if (_converse.connection._proto instanceof Strophe.Websocket) {
- disconnectChatRooms();
- }
- });
- });
- /************************ END Event Handlers ************************/
- /************************ BEGIN API ************************/
- // We extend the default converse.js API to add methods specific to MUC groupchats.
- _.extend(_converse.api, {
- /**
- * The "rooms" namespace groups methods relevant to chatrooms
- * (aka groupchats).
- *
- * @namespace _converse.api.rooms
- * @memberOf _converse.api
- */
- 'rooms': {
- /**
- * Creates a new MUC chatroom (aka groupchat)
- *
- * Similar to {@link _converse.api.rooms.open}, but creates
- * the chatroom in the background (i.e. doesn't cause a
- * view to open).
- *
- * @method _converse.api.rooms.create
- * @param {(string[]|string)} jid|jids The JID or array of
- * JIDs of the chatroom(s) to create
- * @param {object} [attrs] attrs The room attributes
- */
- 'create' (jids, attrs) {
- if (_.isString(attrs)) {
- attrs = {'nick': attrs};
- } else if (_.isUndefined(attrs)) {
- attrs = {};
- }
- if (_.isUndefined(attrs.maximize)) {
- attrs.maximize = false;
- }
- if (!attrs.nick && _converse.muc_nickname_from_jid) {
- attrs.nick = Strophe.getNodeFromJid(_converse.bare_jid);
- }
- if (_.isUndefined(jids)) {
- throw new TypeError('rooms.create: You need to provide at least one JID');
- } else if (_.isString(jids)) {
- return createChatRoom(jids, attrs);
- }
- return _.map(jids, _.partial(createChatRoom, _, attrs));
- },
- /**
- * Opens a MUC chatroom (aka groupchat)
- *
- * Similar to {@link _converse.api.chats.open}, but for groupchats.
- *
- * @method _converse.api.rooms.open
- * @param {string} jid The room JID or JIDs (if not specified, all
- * currently open rooms will be returned).
- * @param {string} attrs A map containing any extra room attributes.
- * @param {string} [attrs.nick] The current user's nickname for the MUC
- * @param {boolean} [attrs.auto_configure] A boolean, indicating
- * whether the room should be configured automatically or not.
- * If set to `true`, then it makes sense to pass in configuration settings.
- * @param {object} [attrs.roomconfig] A map of configuration settings to be used when the room gets
- * configured automatically. Currently it doesn't make sense to specify
- * `roomconfig` values if `auto_configure` is set to `false`.
- * For a list of configuration values that can be passed in, refer to these values
- * in the [XEP-0045 MUC specification](http://xmpp.org/extensions/xep-0045.html#registrar-formtype-owner).
- * The values should be named without the `muc#roomconfig_` prefix.
- * @param {boolean} [attrs.maximize] A boolean, indicating whether minimized rooms should also be
- * maximized, when opened. Set to `false` by default.
- * @param {boolean} [attrs.bring_to_foreground] A boolean indicating whether the room should be
- * brought to the foreground and therefore replace the currently shown chat.
- * If there is no chat currently open, then this option is ineffective.
- *
- * @example
- * this._converse.api.rooms.open('group@muc.example.com')
- *
- * @example
- * // To return an array of rooms, provide an array of room JIDs:
- * _converse.api.rooms.open(['group1@muc.example.com', 'group2@muc.example.com'])
- *
- * @example
- * // To setup a custom nickname when joining the room, provide the optional nick argument:
- * _converse.api.rooms.open('group@muc.example.com', {'nick': 'mycustomnick'})
- *
- * @example
- * // For example, opening a room with a specific default configuration:
- * _converse.api.rooms.open(
- * 'myroom@conference.example.org',
- * { 'nick': 'coolguy69',
- * 'auto_configure': true,
- * 'roomconfig': {
- * 'changesubject': false,
- * 'membersonly': true,
- * 'persistentroom': true,
- * 'publicroom': true,
- * 'roomdesc': 'Comfy room for hanging out',
- * 'whois': 'anyone'
- * }
- * },
- * true
- * );
- */
- 'open' (jids, attrs) {
- return new Promise((resolve, reject) => {
- _converse.api.waitUntil('chatBoxesFetched').then(() => {
- if (_.isUndefined(jids)) {
- const err_msg = 'rooms.open: You need to provide at least one JID';
- _converse.log(err_msg, Strophe.LogLevel.ERROR);
- reject(new TypeError(err_msg));
- } else if (_.isString(jids)) {
- resolve(_converse.api.rooms.create(jids, attrs).trigger('show'));
- } else {
- resolve(_.map(jids, (jid) => _converse.api.rooms.create(jid, attrs).trigger('show')));
- }
- });
- });
- },
- /**
- * Returns an object representing a MUC chatroom (aka groupchat)
- *
- * @method _converse.api.rooms.get
- * @param {string} [jid] The room JID (if not specified, all rooms will be returned).
- * @param {object} attrs A map containing any extra room attributes For example, if you want
- * to specify the nickname, use `{'nick': 'bloodninja'}`. Previously (before
- * version 1.0.7, the second parameter only accepted the nickname (as a string
- * value). This is currently still accepted, but then you can't pass in any
- * other room attributes. If the nickname is not specified then the node part of
- * the user's JID will be used.
- * @param {boolean} create A boolean indicating whether the room should be created
- * if not found (default: `false`)
- * @example
- * _converse.api.waitUntil('roomsAutoJoined').then(() => {
- * const create_if_not_found = true;
- * _converse.api.rooms.get(
- * 'group@muc.example.com',
- * {'nick': 'dread-pirate-roberts'},
- * create_if_not_found
- * )
- * });
- */
- 'get' (jids, attrs, create) {
- if (_.isString(attrs)) {
- attrs = {'nick': attrs};
- } else if (_.isUndefined(attrs)) {
- attrs = {};
- }
- if (_.isUndefined(jids)) {
- const result = [];
- _converse.chatboxes.each(function (chatbox) {
- if (chatbox.get('type') === _converse.CHATROOMS_TYPE) {
- result.push(chatbox);
- }
- });
- return result;
- }
- if (!attrs.nick) {
- attrs.nick = Strophe.getNodeFromJid(_converse.bare_jid);
- }
- if (_.isString(jids)) {
- return getChatRoom(jids, attrs);
- }
- return _.map(jids, _.partial(getChatRoom, _, attrs));
- }
- }
- });
- /************************ END API ************************/
- }
- });
- }));
|