This repository has been archived on 2025-03-10. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
wetsock/lib/WetSock/Plugins/Rooms/RoomPlugin.js
2024-04-07 20:08:20 +01:00

307 lines
12 KiB
JavaScript

import BasePlugin from '../BasePlugin';
/**
* Plugin for handling room-related operations.
*/
export default class RoomPlugin extends BasePlugin {
#rooms = new Map();
#clients = new Map();
#config = {
allowMultipleRooms: true,
alwaysMainChannel: false,
notifyMuteUnmute: false,
notifyKick: false,
inactivityTimeout: 60_000,
};
/**
* Initialize the plugin.
* @param {Object} wetsock - The WetSock server instance.
* @param {Object} config - The configuration options for the plugin.
*/
initialize(wetsock, config = {}) {
super.initialize(wetsock);
this.#config = { ...this.#config, ...config };
wetsock.on('message', this.onMessage.bind(this));
wetsock.on('close', this.onClose.bind(this));
setInterval(this.#purge.bind(this), this.#config.inactivityTimeout);
}
/**
* Handle incoming messages from clients.
* @param {Object} client - The client object.
* @param {Object} message - The message object.
* @returns {boolean} - True if the message was handled, false otherwise.
*/
onMessage(client, message) {
if (typeof message === 'object' && message.type && this[message.type]) {
return this[message.type](client, message);
}
return false;
}
/**
* Handle a client disconnecting.
* @param {Object} client - The client object.
*/
onClose(client) {
const clientRooms = this.#clients.get(client.id);
if (clientRooms) {
for (const roomId of clientRooms) {
const room = this.#rooms.get(roomId);
if (room) {
room.members.delete(client.id);
this._getServer().publish(roomId, `${client.id} has left the room`);
}
}
this.#clients.delete(client.id);
}
}
actions () {
return Object.getOwnPropertyNames(
Object.getPrototypeOf(this)
).filter((prop) => {
return ![
"constructor",
"initialize",
"onMessage",
"onClose",
"actions"
].includes(prop) && typeof this[prop] === 'function'
});
}
/**
* Handle a client joining a room.
* @param {Object} client - The client object.
* @param {Object} message - The message object.
* @param {string} message.roomId - The ID of the room to join.
*/
join(client, { roomId }) {
const room = this.#rooms.get(roomId);
if (room) {
const memberData = room.members.get(client.id);
if (memberData && memberData.kicked) {
client.send({ type: 'error', message: 'You have been kicked from this room and cannot rejoin' });
return;
}
if (!this.#config.allowMultipleRooms && this.#clients.has(client.id)) {
client.send({ type: 'error', message: 'You can only be in one room at a time' });
return;
}
client.ws.subscribe(roomId);
room.add(client.id, {
muted: memberData ? memberData.muted : false,
lastActivity: Date.now()
});
this.#clients.set(client.id, (this.#clients.get(client.id) || new Set()).add(roomId));
this._getServer().publish(roomId, `${client.id} has joined the room`);
} else {
client.send({ type: 'error', message: `Room ${roomId} does not exist` });
}
}
/**
* Handle a client leaving a room.
* @param {Object} client - The client object.
* @param {Object} message - The message object.
* @param {string} message.roomId - The ID of the room to leave.
*/
leave(client, { roomId }) {
const room = this.#rooms.get(roomId);
if (room) {
client.ws.unsubscribe(roomId);
room.members.delete(client.id);
const clientRooms = this.#clients.get(client.id);
if (clientRooms) {
clientRooms.delete(roomId);
if (clientRooms.size === 0 && !this.#config.alwaysMainChannel) {
this.#clients.delete(client.id);
}
}
this._getServer().publish(roomId, `${client.id} has left the room`);
} else {
client.send({ type: 'error', message: `Room ${roomId} does not exist` });
}
}
/**
* Handle creating a new room.
* @param {Object} client - The client object.
* @param {Object} message - The message object.
* @param {string} message.roomId - The ID of the room to create.
*/
create(client, { roomId }) {
if (this.#rooms.has(roomId)) {
client.send({ type: 'error', message: `Room ${roomId} already exists` });
return;
}
const room = new Room(roomId);
this.#rooms.set(roomId, room);
client.ws.subscribe(roomId);
room.add(client.id, { muted: false, lastActivity: Date.now() });
this.#clients.set(client.id, (this.#clients.get(client.id) || new Set()).add(roomId));
// Initialize the permissions for the room
this._getServer().emit('permissions:init', this.constructor.name, roomId, (permissions) => {
room.permissions = permissions;
permissions.role(client.id, 'OWNER');
});
client.send({ type: 'success', message: `Room ${roomId} created successfully` });
}
/**
* Handle sending a message to a room.
* @param {Object} client - The client object.
* @param {Object} message - The message object.
* @param {string} message.roomId - The ID of the room to send the message to.
* @param {string} message.content - The content of the message.
* @returns {boolean} - True if the message was sent successfully, false otherwise.
*/
send(client, { roomId, content }) {
const room = this.#rooms.get(roomId);
if (room && room.get(client.id)) {
if (!room.permissions.can(client.id, 'SEND_MESSAGE')) {
client.send({ type: 'error', message: 'You do not have permission to send messages in this room' });
return false;
}
const memberData = room.members.get(client.id);
if (memberData.muted) {
client.send({ type: 'error', message: 'You are muted and cannot send messages in this room' });
return false;
}
room.update(client.id);
this._getServer().publish(roomId, `${client.id}: ${content}`);
this.emit('rooms:message', client, message.roomId, message.content);
this.emit(`rooms:message:${message.roomId}`, client, message.content);
return true;
} else {
client.send({ type: 'error', message: 'You are not allowed to send messages in this room' });
return false;
}
}
/**
* Handle kicking a member from a room.
* @param {Object} client - The client object.
* @param {Object} message - The message object.
* @param {string} message.roomId - The ID of the room.
* @param {string} message.memberId - The ID of the member to kick.
*/
kick(client, { roomId, memberId }) {
const room = this.#rooms.get(roomId);
if (room && room.get(memberId)) {
if (!room.permissions.can(client.id, 'KICK_MEMBER')) {
client.send({ type: 'error', message: 'You do not have permission to kick members from this room' });
return;
}
const member = this._getServer().getValidClient(memberId);
if (member) {
member.ws.unsubscribe(roomId);
room.members.set(memberId, { id: memberId, muted: true, kicked: Date.now() });
const clientRooms = this.#clients.get(memberId);
if (clientRooms) {
clientRooms.delete(roomId);
if (clientRooms.size === 0 && !this.#config.alwaysMainChannel) {
this.#clients.delete(memberId);
}
}
if (this.#config.notifyKick) {
this._getServer().publish(roomId, `${memberId} has been kicked from the room`);
}
member.send({ type: 'kicked', roomId });
}
client.send({ type: 'success', message: `Member ${memberId} has been kicked from room ${roomId}` });
} else {
client.send({ type: 'error', message: `Cannot kick member ${memberId} from room ${roomId}` });
}
}
/**
* Handle muting a member in a room.
* @param {Object} client - The client object.
* @param {Object} message - The message object.
* @param {string} message.roomId - The ID of the room.
* @param {string} message.memberId - The ID of the member to mute.
*/
mute(client, { roomId, memberId }) {
const room = this.#rooms.get(roomId);
if (room && room.get(memberId)) {
if (!room.permissions.can(client.id, 'MUTE_MEMBER')) {
client.send({ type: 'error', message: 'You do not have permission to mute members in this room' });
return;
}
room.members.get(memberId).muted = true;
const member = this._getServer().getValidClient(memberId);
if (member && this.#config.notifyMuteUnmute) {
member.send({ type: 'muted', roomId });
}
client.send({ type: 'success', message: `Member ${memberId} has been muted in room ${roomId}` });
} else {
client.send({ type: 'error', message: `Cannot mute member ${memberId} in room ${roomId}` });
}
}
/**
* Handle unmuting a member in a room.
* @param {Object} client - The client object.
* @param {Object} message - The message object.
* @param {string} message.roomId - The ID of the room.
* @param {string} message.memberId - The ID of the member to unmute.
*/
unmute(client, { roomId, memberId }) {
const room = this.#rooms.get(roomId);
if (room && room.get(memberId)) {
if (!room.permissions.can(client.id, 'UNMUTE_MEMBER')) {
client.send({ type: 'error', message: 'You do not have permission to unmute members in this room' });
return;
}
room.members.get(memberId).muted = false;
const member = this._getServer().getValidClient(memberId);
if (member && this.#config.notifyMuteUnmute) {
member.send({ type: 'unmuted', roomId });
}
client.send({ type: 'success', message: `Member ${memberId} has been unmuted in room ${roomId}` });
} else {
client.send({ type: 'error', message: `Cannot unmute member ${memberId} in room ${roomId}` });
}
}
/**
* Check for inactive members in each room and kick them.
*/
#purge() {
for (const [roomId, room] of this.#rooms.entries()) {
const now = Date.now();
for (const [memberId, memberData] of room.members.entries()) {
if (now - memberData.lastActivity > this.#config.inactivityTimeout) {
this.kick(null, { roomId, memberId });
}
}
}
}
/**
* Broadcast a message to a specific room or all rooms.
* @param {string} roomId - The ID of the room to broadcast to. If not provided, the message will be broadcast to all rooms.
* @param {string} message - The message to broadcast.
*/
#broadcast(message, roomId) {
if (this.#rooms.has(roomId)) {
this._getServer().publish(roomId, message);
return;
}
if(roomId == 'all') {
for (const room of this.#rooms.values()) {
this._getServer().publish(room.id, message);
}
}
}
}