converse.js
Version:
Browser based XMPP chat client
223 lines (197 loc) • 7.33 kB
JavaScript
import { Model } from "@converse/skeletor";
import log from "@converse/log";
import api from "../../shared/api/index.js";
import _converse from "../../shared/_converse.js";
import converse from "../../shared/api/public.js";
import ColorAwareModel from "../../shared/color.js";
import ModelWithMessages from "../../shared/model-with-messages.js";
import ModelWithVCard from "../../shared/model-with-vcard";
import { AFFILIATIONS, ROLES } from "./constants.js";
import MUCMessages from "./messages.js";
import u from "../../utils/index.js";
import { shouldCreateGroupchatMessage } from "./utils";
import { sendChatState } from "../../shared/actions";
const { Strophe, stx } = converse.env;
/**
* Represents a participant in a MUC
*/
class MUCOccupant extends ModelWithVCard(ModelWithMessages(ColorAwareModel(Model))) {
/**
* @typedef {import('../../shared/types').MessageAttributes} MessageAttributes
* @typedef {import('../../shared/errors').StanzaParseError} StanzaParseError
*/
async initialize() {
this.lazy_load_vcard = true;
super.initialize();
await this.fetchMessages();
this.on("change:nick", () => this.setColor());
this.on("change:jid", () => this.setColor());
this.on("change:chat_state", () => sendChatState(this.get("jid"), this.get("chat_state")));
}
defaults() {
return {
hats: [],
presence: 'offline',
show: undefined,
states: [],
hidden: true,
num_unread: 0,
message_type: "chat",
};
}
save(key, val, options) {
let attrs;
if (key == null) {
// eslint-disable-line no-eq-null
return super.save(key, val, options);
} else if (typeof key === "object") {
attrs = key;
options = val;
} else {
(attrs = {})[key] = val;
}
if (attrs.occupant_id) {
attrs.id = attrs.occupant_id;
}
return super.save(attrs, options);
}
getMessagesCacheKey() {
const id = this.get('occupant_id') || this.get('jid') || this.get('nick');
return `converse.muc-private-msgs-${id}-${_converse.session.get('bare_jid')}`;
}
getMessagesCollection() {
return new MUCMessages();
}
/**
* Handler for all MUC private messages sent to this occupant.
* This method houldn't be called directly, instead {@link MUC#queueMessage} should be called.
* @param {MessageAttributes|StanzaParseError} attrs_or_error
*/
async onMessage(attrs_or_error) {
if (u.isErrorObject(attrs_or_error)) {
const { stanza, message } = /** @type {StanzaParseError} */ (attrs_or_error);
if (stanza) log.error(stanza);
return log.error(message);
}
const attrs = /** @type {MessageAttributes} */ (attrs_or_error);
if (attrs.type === "error" && !(await this.shouldShowErrorMessage(attrs))) {
return;
}
const message = this.getDuplicateMessage(attrs);
if (message) {
this.updateMessage(message, attrs);
return;
} else if (await this.handleRetraction(attrs)) {
return;
}
this.setEditable(attrs, attrs.time);
if (shouldCreateGroupchatMessage(attrs)) {
const msg = (await this.handleCorrection(attrs)) || (await this.createMessage(attrs));
this.handleUnreadMessage(msg);
}
}
/**
* @returns {string}
*/
getDisplayName() {
return this.get("nick") || this.get("jid") || "";
}
/**
* Return roles which may be assigned to this occupant
* @returns {typeof ROLES} - An array of assignable roles
*/
getAssignableRoles() {
let disabled = api.settings.get("modtools_disable_assign");
if (!Array.isArray(disabled)) {
disabled = disabled ? ROLES : [];
}
if (this.get("role") === "moderator") {
return ROLES.filter((r) => !disabled.includes(r));
} else {
return [];
}
}
/**
* Return affiliations which may be assigned by this occupant
* @returns {typeof AFFILIATIONS} An array of assignable affiliations
*/
getAssignableAffiliations() {
let disabled = api.settings.get("modtools_disable_assign");
if (!Array.isArray(disabled)) {
disabled = disabled ? AFFILIATIONS : [];
}
if (this.get("affiliation") === "owner") {
return AFFILIATIONS.filter((a) => !disabled.includes(a));
} else if (this.get("affiliation") === "admin") {
return AFFILIATIONS.filter((a) => !["owner", "admin", ...disabled].includes(a));
} else {
return [];
}
}
isMember() {
return ["admin", "owner", "member"].includes(this.get("affiliation"));
}
isModerator() {
return ["admin", "owner"].includes(this.get("affiliation")) || this.get("role") === "moderator";
}
isSelf() {
return this.get("states").includes("110");
}
/**
* @param {MessageAttributes} [attrs]
* @return {Promise<MessageAttributes>}
*/
async getOutgoingMessageAttributes(attrs) {
const origin_id = u.getUniqueId();
const text = attrs?.body;
const body = text ? u.shortnamesToUnicode(text) : undefined;
const muc = this.collection.chatroom;
const own_occupant = muc.getOwnOccupant();
attrs = Object.assign(
{},
attrs,
{
body,
from: own_occupant.get("from"),
fullname: _converse.state.profile.get("fullname"),
id: origin_id,
jid: this.get("jid"),
message: body,
msgid: origin_id,
nick: own_occupant.get("nickname"),
origin_id,
sender: "me",
time: new Date().toISOString(),
to: this.get("from") ?? `${muc.get("jid")}/${this.get("nick")}`,
type: "chat",
},
await u.getMediaURLsMetadata(text)
);
/**
* *Hook* which allows plugins to update the attributes of an outgoing message.
* These attributes get set on the {@link Message} and persisted.
* @event _converse#getOutgoingMessageAttributes
* @param {MUCOccupant} chat
* The chat from which this message will be sent.
* @param {MessageAttributes} attrs
* The message attributes, from which the stanza will be created.
*/
attrs = await api.hook("getOutgoingMessageAttributes", this, attrs);
return attrs;
}
/**
* @param {import('../chat/message').default} message - The message object
*/
async createMessageStanza(message) {
const stanza = await super.createMessageStanza(message);
stanza.cnode(stx`<x xmlns="${Strophe.NS.MUC}#user"/>`).root();
return stanza;
}
/**
* @param {import('../../shared/message').default} message
*/
isChatMessage(message) {
return message.get('type') === this.get('message_type');
}
}
export default MUCOccupant;