SockDrawer/SockBot

View on GitHub
providers/nodebb/chat.js

Summary

Maintainability
C
1 day
Test Coverage
'use strict';
 
const utils = require('../../lib/utils');
 
/**
* Create a ChatRoom class and bind it to a forum instance
*
* @param {Provider} forum A forum instance to bind to constructed ChatRoom class
* @returns {User} A ChatRoomPo class bound to the provided `forum` instance
*/
Function `bindChat` has 177 lines of code (exceeds 25 allowed). Consider refactoring.
Function `bindChat` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.
exports.bindChat = function bindChat(forum) {
 
/**
* Send a message to the chatroom
*
* @public
*
* @param {number} roomId Chatroom to speak to
* @param {string} content Message to send to the chatroom
* @returns {Promise} Resolves when message has been sent
*/
function sendChat(roomId, content) {
return retryAction(() => forum._emit('modules.chats.send', {
roomId: roomId,
message: content
}), 5);
}
 
/**
* Message Class
*
* Represents a message in a chatroom
*
* @public
*/
class Message {
/**
* Construct a Message object from payload
*
* This constructor is intended to be private use only, if you need to construct a chat message from payload
* data use `Message.parse()` instead
*
* @private
* @class
*
* @param {*} payload Payload to construct the Message object out of
*/
constructor(payload) {
payload = utils.parseJSON(payload);
const values = {
markup: payload.content,
content: utils.htmlToRaw(payload.content),
id: payload.messageId,
room: payload.roomId,
from: forum.User.parse(payload.fromUser),
sent: new Date(payload.timestamp),
self: payload.self === 1
};
utils.mapSet(this, values);
}
 
/**
* Chat message id
*
* @public
*
* @returns {number} Id of the chat message
*/
get id() {
return utils.mapGet(this, 'id');
}
 
/**
* Id of the chatroom this message belongs to
*
* @public
*
* @returns {number} Id of the ChatRoom this message belongs to
*/
get room() {
return utils.mapGet(this, 'room');
}
 
/**
* User who sent this message
*
* @public
*
* @returns {User} User who authored this chat message
*/
get from() {
return utils.mapGet(this, 'from');
}
 
/**
* Identify if this message was created by current user
*
* @public
*
* @returns {boolean} True if message was sent by current user
*/
get self() {
return utils.mapGet(this, 'self');
}
 
/**
* Text content of message
*
* @public
*
* @returns {string} Content of the message with formatting and quotes removed
*/
get content() {
return utils.mapGet(this, 'content');
}
 
/**
* DateTime the message was sent
*
* @public
*
* @returns {Date} datetime the message was sent
*/
get sent() {
return utils.mapGet(this, 'sent');
}
 
/**
* Message markup
*
* @public
*
* @returns {Promise<string>} Resolves to the HTML markup of the chat message
*/
markup() {
return Promise.resolve(utils.mapGet(this, 'markup'));
}
 
/**
* Reply to the chat message
*
* @public
*
* @param {string} content Message to reply with
* @returns {Promise} Resolves once message has been sent
*/
reply(content) {
return sendChat(this.room, content).then(() => this);
}
 
/**
* Parse a Message from a given payload
*
* @public
*
* @param {string|object} payload Data to parse as a Message
* @returns {Message} parsed Message
*/
static parse(payload) {
if (!payload) {
throw new Error('E_CHATMESSAGE_NOT_FOUND');
}
return new Message(payload);
}
}
 
/**
* ChatRoom Class
*
* Represents a chat room
*
* @public
*
*/
class ChatRoom {
 
/**
* Construct a ChatroomObject from payload
*
* This constructor is intended to be private use only, if you need to construct a chatroom from payload
* data use `ChatRoom.parse()` instead
*
* @private
* @class
*
* @param {*} payload Payload to construct the ChatRoom object out of
*/
constructor(payload) {
payload = utils.parseJSON(payload);
const values = {
id: payload.roomId,
owner: payload.owner,
name: payload.roomName,
users: payload.users || []
};
utils.mapSet(this, values);
}
 
/**
* Get the chatroom id
*
* @public
*
* @returns {number} Id of the chatroom
*
*/
get id() {
return utils.mapGet(this, 'id');
}
 
/**
* Get the chatroom name
*
* @public
*
* @returns {string} Name of the chatroom
*/
get name() {
return utils.mapGet(this, 'name');
}
 
/**
* Get the users in the chatroom
*
* @public
*
* @returns {User[]} The users that were in teh chatroom when the room was retrieved
*/
get users() {
return utils.mapGet(this, 'users').map((user) => forum.User.parse(user));
}
 
/**
* Get the number of users in the chatroom
*
* @public
*
* @returns {number} Number of users in the chatroom
*/
get participants() {
return utils.mapGet(this, 'users').length;
}
 
/**
* Get the owner of the chatroom
*
* @public
*
* @returns {User} Owning user for the chatroom
*/
get owner() {
const ownerId = utils.mapGet(this, 'owner');
const owner = utils.mapGet(this, 'users').filter((user) => user.uid === ownerId)[0];
return forum.User.parse(owner);
}
 
/**
* Retrieve the weblink for the Chatroom
*
* @public
*
* @returns {Promise<string>} Resolves to the URL web link to the chatroom
*/
url() {
return Promise.resolve(`${forum.url}/chats/${this.id}`);
}
 
/**
* Send a message to the chatroom
*
* @public
*
* @param {string} content Message to send to the chatroom
* @returns {Promise} Resolves when message has been sent
*/
send(content) {
return sendChat(this.id, content).then(() => this);
}
 
/**
* Apply an operation to a list of users
*
* @private
*
* @param {string} action Action to apply
* @param {User|User[]} users List of users to apply action to
* @returns {Promise} resolves when action has been applied to all users
*/
_applyToUsers(action, users) {
if (!Array.isArray(users)) {
users = [users];
}
return Promise.all(
users.map((user) => forum._emit(action, {
roomId: this.id,
username: user.username
})));
}
 
/**
* Add a list of users to the chatroom
*
* @public
*
* @param {User|User[]} users User or Users to add to the chatroom
* @returns {Promise} Resolves when all users have been added to the chatroom
*/
addUsers(users) {
return this._applyToUsers('modules.chats.addUserToRoom', users).then(() => this);
}
 
/**
* Remove a list of users from the chatroom
*
* @public
*
* @param {User|User[]} users User or Users to remove from the chatroom
* @returns {Promsie} Resos when users have been removed from the chatroom
*/
removeUsers(users) {
return this._applyToUsers('modules.chats.removeUserFromRoom', users).then(() => this);
}
 
/**
* Leave the chatroom
*
* This will remove current user from the chat.
*
* @public
*
* @returns {Promise} Resolves when chatroom has been left
*/
leave() {
return forum._emit('modules.chats.leave', this.id).then(() => this);
}
 
/**
* Rename the chat room
*
* @public
*
* @param {string} newName Name to set the chatroom to
* @returns {Promise} Resolves when rename is complete
*/
rename(newName) {
return forum._emit('modules.chats.renameRoom', {
roomId: this.id,
newName: newName
}).then(() => this);
}
 
/**
* Create a new chatroom, add a list of users to it and send a message.
*
* @public
*
* @param {User|User[]} users User or users to add to the chatroom
* @param {string} message Message to send to the new chat room
* @param {string} [title] Optional: Set the title of the chat message to this value
* @returns {Promise} Resolves once message has been sent
*/
static create(users, message, title) {
if (!Array.isArray(users)) {
users = [users];
}
const rootUser = users.shift();
const payload = {
touid: rootUser.id
};
return retryAction(() => forum._emit('modules.chats.newRoom', payload), 5)
.then((roomId) => ChatRoom.get(roomId))
.then((chat) => chat.addUsers(users))
.then((chat) => chat.send(message))
.then((chat) => {
if (title) {
return chat.rename(title);
}
return Promise.resolve(chat);
});
}
 
/**
* Activate chat features. newly received chat messages will be processed
*
* @public
*/
static activate() {
forum.socket.on('event:chats.receive', handleChat);
}
 
/**
* Deactivate the Chat features. This will stop new chat messages from being processed
*
* @public
*/
static deactivate() {
forum.socket.off('event:chats.receive', handleChat);
}
 
/**
* Retrieve a ChatRoom by a given ID
*
* @public
*
* @param {number} roomId Id of the chatroom to retrieve
* @returns {Promise<ChatRoom>} Resolves to the chatroom requested
*/
static get(roomId) {
return forum._emit('modules.chats.loadRoom', {
roomId: roomId
}).then((data) => ChatRoom.parse(data));
}
 
/**
* Parse a Chatroom object from payload
*
* @public
*
* @param {string|object} payload ChatRoom Payload
* @returns {ChatRoom} Parsed Chatroom
*/
static parse(payload) {
if (!payload) {
throw new Error('E_CHATROOM_NOT_FOUND');
}
return new ChatRoom(payload);
}
}
ChatRoom.Message = Message;
 
ChatRoom.retryDelay = 250;
 
/**
* Handle Chat events from websocket
*
* @private
*
* @param {*} payload websocket event payload
* @returns {Promise} resolves when processing has been completed for event
*/
function handleChat(payload) {
if (!payload.message) {
return Promise.reject(new Error('Event payload did not include chat message'));
}
const message = ChatRoom.Message.parse(payload.message);
 
forum.emit('chatMessage', message);
const ids = {
post: -1,
topic: -1,
user: message.from.id,
pm: message.room,
chat: message.id
};
return forum.Commands.get(ids, message.content, (content) => message.reply(content))
.then((command) => command.execute());
}
 
/**
* @param {function} fn Promise returning function to possibly retry
* @param {number} trials Number or times to retry
* @returns {Promise} Resolves when
*/
function retryAction(fn, trials) {
return new Promise((resolve, reject) => {
fn().then(resolve, (err) => {
if (trials > 1 && err.message === '[[error:too-many-messages]]') {
// eslint-disable-once no-use-before-define
setTimeout(() => retryAction(fn, trials - 1).then(resolve, reject), ChatRoom.retryDelay);
} else {
reject(err);
}
});
});
}
 
return ChatRoom;
};