1
0
mirror of https://github.com/hack-chat/main.git synced 2024-03-22 13:20:33 +08:00

four new modules

This commit is contained in:
marzavec 2023-12-21 23:14:03 -08:00
parent b3c885f44c
commit fe6685468c
89 changed files with 3654 additions and 1429 deletions

21
LICENSE
View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 marzavec
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -48,7 +48,7 @@ export async function run({
for (let i = 0, l = targetMod.length; i < l; i += 1) { for (let i = 0, l = targetMod.length; i < l; i += 1) {
// downgrade privileges // downgrade privileges
targetMod[i].uType = 'user'; /* @legacy */ targetMod[i].uType = 'user';
targetMod[i].level = levels.default; targetMod[i].level = levels.default;
// inform ex-mod // inform ex-mod

View File

@ -6,19 +6,27 @@
* @module chat * @module chat
*/ */
import { parseText } from '../utility/_Text.js'; import {
parseText,
} from '../utility/_Text.js';
import { import {
isAdmin, isAdmin,
isModerator, isModerator,
} from '../utility/_UAC.js'; } from '../utility/_UAC.js';
/**
* Maximum length of the customId property
* @type {number}
*/
export const MAX_MESSAGE_ID_LENGTH = 6; export const MAX_MESSAGE_ID_LENGTH = 6;
/** /**
* The time in milliseconds before a message is considered stale, and thus no longer allowed * The time in milliseconds before a message is considered stale, and thus no longer allowed
* to be edited. * to be edited.
* @type {number} * @type {number}
*/ */
const ACTIVE_TIMEOUT = 5 * 60 * 1000; const ACTIVE_TIMEOUT = 5 * 60 * 1000;
/** /**
* The time in milliseconds that a check for stale messages should be performed. * The time in milliseconds that a check for stale messages should be performed.
* @type {number} * @type {number}
@ -38,11 +46,11 @@ export const ACTIVE_MESSAGES = [];
*/ */
export function cleanActiveMessages() { export function cleanActiveMessages() {
const now = Date.now(); const now = Date.now();
for (let i = 0; i < ACTIVE_MESSAGES.length; i++) { for (let i = 0; i < ACTIVE_MESSAGES.length; i += 1) {
const message = ACTIVE_MESSAGES[i]; const message = ACTIVE_MESSAGES[i];
if (now - message.sent > ACTIVE_TIMEOUT || message.toDelete) { if (now - message.sent > ACTIVE_TIMEOUT || message.toDelete) {
ACTIVE_MESSAGES.splice(i, 1); ACTIVE_MESSAGES.splice(i, 1);
i--; i -= 1;
} }
} }
} }
@ -93,7 +101,7 @@ export async function run({
}, socket); }, socket);
} }
const customId = payload.customId; const { customId } = payload;
if (typeof (customId) === 'string' && customId.length > MAX_MESSAGE_ID_LENGTH) { if (typeof (customId) === 'string' && customId.length > MAX_MESSAGE_ID_LENGTH) {
// There's a limit on the custom id length. // There's a limit on the custom id length.
@ -127,6 +135,7 @@ export async function run({
} }
addActiveMessage(outgoingPayload.customId, socket.userid); addActiveMessage(outgoingPayload.customId, socket.userid);
// broadcast to channel peers // broadcast to channel peers
server.broadcast(outgoingPayload, { channel: socket.channel }); server.broadcast(outgoingPayload, { channel: socket.channel });

View File

@ -49,7 +49,7 @@ export async function run({
} }
// `join` is the legacy entry point, check if it needs to be upgraded // `join` is the legacy entry point, check if it needs to be upgraded
if (typeof socket.hcProtocol === 'undefined') { if (typeof socket.hcProtocol === 'undefined' || socket.hcProtocol === 1) {
payload = upgradeLegacyJoin(server, socket, payload); payload = upgradeLegacyJoin(server, socket, payload);
} }

View File

@ -37,7 +37,7 @@ export async function run({ core, server, socket }) {
// gather connection and channel count // gather connection and channel count
const ips = {}; const ips = {};
const channels = {}; const channels = {};
// @todo use public channels from core.appConfig.data // @todo use public channel flag
const publicChanCounts = { const publicChanCounts = {
lounge: 0, lounge: 0,
meta: 0, meta: 0,

View File

@ -2,9 +2,9 @@
/** /**
* @author Marzavec ( https://github.com/marzavec ) * @author Marzavec ( https://github.com/marzavec )
* @summary Restore session * @summary Create or restore session
* @version 1.0.0 * @version 1.0.0
* @description Restore previous state by session * @description Restore previous state by session or create new session
* @module session * @module session
*/ */
@ -24,9 +24,9 @@ import {
const SessionLocation = './session.key'; const SessionLocation = './session.key';
/** /**
* Get a fresh session string for target socket * Get a new json web token for the provided socket
* @param {Object} socket * @param {*} socket
* @param {Object} core * @param {*} core
* @returns {object} * @returns {object}
*/ */
export function getSession(socket, core) { export function getSession(socket, core) {
@ -39,7 +39,7 @@ export function getSession(socket, core) {
nick: socket.nick, nick: socket.nick,
trip: socket.trip, trip: socket.trip,
userid: socket.userid, userid: socket.userid,
uType: socket.uType, /* @legacy */ uType: socket.uType,
muzzled: socket.muzzled || false, muzzled: socket.muzzled || false,
banned: socket.banned || false, banned: socket.banned || false,
}, core.sessionKey, { }, core.sessionKey, {
@ -49,8 +49,8 @@ export function getSession(socket, core) {
/** /**
* Reply to target socket with session failure notice * Reply to target socket with session failure notice
* @param {Object} server * @param {*} server
* @param {Object} socket * @param {*} socket
* @returns {boolean} * @returns {boolean}
*/ */
function notifyFailure(server, socket) { function notifyFailure(server, socket) {
@ -136,13 +136,21 @@ export async function run({
socket.nick = session.nick; socket.nick = session.nick;
socket.trip = session.trip; socket.trip = session.trip;
socket.userid = session.userid; socket.userid = session.userid;
socket.uType = session.uType; /* @legacy */ socket.uType = session.uType;
socket.muzzled = session.muzzled; socket.muzzled = session.muzzled;
socket.banned = session.banned; socket.banned = session.banned;
socket.hash = server.getSocketHash(socket); socket.hash = server.getSocketHash(socket);
socket.hcProtocol = 2; socket.hcProtocol = 2;
// dispatch info
server.reply({
cmd: 'session',
restored: true,
token: getSession(socket, core),
channels: socket.channels,
}, socket);
for (let i = 0, j = session.channels.length; i < j; i += 1) { for (let i = 0, j = session.channels.length; i < j; i += 1) {
restoreJoin({ restoreJoin({
core, core,
@ -152,14 +160,6 @@ export async function run({
}, true); }, true);
} }
// dispatch updated session
server.reply({
cmd: 'session',
restored: true,
token: getSession(socket, core),
channels: socket.channels,
}, socket);
return true; return true;
} }

View File

@ -1,10 +1,35 @@
import { parseText } from "../utility/_Text.js"; /**
import { isAdmin, isModerator } from "../utility/_UAC.js"; * @author MinusGix ( https://github.com/MinusGix )
import { ACTIVE_MESSAGES, MAX_MESSAGE_ID_LENGTH } from "./chat.js"; * @summary Change target message
* @version v1.0.0
* @description Will alter a previously sent message using that message's customId
* @module updateMessage
*/
export async function run({ core, server, socket, payload }) { import {
parseText,
} from '../utility/_Text.js';
import {
isAdmin,
isModerator,
} from '../utility/_UAC.js';
import {
ACTIVE_MESSAGES,
MAX_MESSAGE_ID_LENGTH,
} from './chat.js';
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @public
* @return {void}
*/
export async function run({
server, socket, payload,
}) {
// undefined | "overwrite" | "append" | "prepend" | "complete" // undefined | "overwrite" | "append" | "prepend" | "complete"
let mode = payload.mode; const { customId } = payload;
let { mode, text } = payload;
if (!mode) { if (!mode) {
mode = 'overwrite'; mode = 'overwrite';
@ -14,14 +39,10 @@ export async function run({ core, server, socket, payload }) {
return server.police.frisk(socket.address, 13); return server.police.frisk(socket.address, 13);
} }
const customId = payload.customId; if (!customId || typeof customId !== 'string' || customId.length > MAX_MESSAGE_ID_LENGTH) {
if (!customId || typeof customId !== "string" || customId.length > MAX_MESSAGE_ID_LENGTH) {
return server.police.frisk(socket.address, 13); return server.police.frisk(socket.address, 13);
} }
let text = payload.text;
if (typeof (text) !== 'string') { if (typeof (text) !== 'string') {
return server.police.frisk(socket.address, 13); return server.police.frisk(socket.address, 13);
} }
@ -43,7 +64,7 @@ export async function run({ core, server, socket, payload }) {
// Or flashing between huge and small. Etc. // Or flashing between huge and small. Etc.
let message; let message;
for (let i = 0; i < ACTIVE_MESSAGES.length; i++) { for (let i = 0; i < ACTIVE_MESSAGES.length; i += 1) {
const msg = ACTIVE_MESSAGES[i]; const msg = ACTIVE_MESSAGES[i];
if (msg.userid === socket.userid && msg.customId === customId) { if (msg.userid === socket.userid && msg.customId === customId) {
@ -80,6 +101,12 @@ export async function run({ core, server, socket, payload }) {
return true; return true;
} }
/**
* The following payload properties are required to invoke this module:
* "text", "customId"
* @public
* @typedef {Array} addmod/requiredData
*/
export const requiredData = ['text', 'customId']; export const requiredData = ['text', 'customId'];
/** /**

View File

@ -0,0 +1,86 @@
/**
* @author Marzavec ( https://github.com/marzavec )
* @summary Disables the captcha
* @version 1.0.0
* @description Disables the captcha on the channel specified in the channel property,
* default is current channel
* @module disablecaptcha
*/
import {
isModerator,
} from '../utility/_UAC.js';
/**
* Automatically executes once after server is ready
* @param {Object} core - Reference to core enviroment object
* @public
* @return {void}
*/
export async function init(core) {
if (typeof core.captchas === 'undefined') {
core.captchas = {};
}
}
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @public
* @return {void}
*/
export async function run({
core, server, socket, payload,
}) {
// increase rate limit chance and ignore if not admin or mod
if (!isModerator(socket.level)) {
return server.police.frisk(socket.address, 10);
}
let targetChannel;
if (typeof payload.channel !== 'string') {
if (typeof socket.channel !== 'string') { // @todo Multichannel
return false; // silently fail
}
targetChannel = socket.channel;
} else {
targetChannel = payload.channel;
}
if (!core.captchas[targetChannel]) {
return server.reply({
cmd: 'info',
text: 'Captcha is not enabled.',
channel: socket.channel, // @todo Multichannel
}, socket);
}
core.captchas[targetChannel] = false;
server.broadcast({
cmd: 'info',
text: `Captcha disabled on: ${targetChannel}`,
channel: false, // @todo Multichannel, false for global info
}, { channel: targetChannel, level: isModerator });
return true;
}
/**
* Module meta information
* @public
* @typedef {Object} disablecaptcha/info
* @property {string} name - Module command name
* @property {string} category - Module category name
* @property {string} description - Information about module
* @property {string} usage - Information about module usage
*/
export const info = {
name: 'disablecaptcha',
category: 'moderators',
description: 'Disables the captcha on the channel specified in the channel property, default is current channel',
usage: `
API: { cmd: 'disablecaptcha', channel: '<optional channel, defaults to your current channel' }`,
};

View File

@ -361,6 +361,7 @@ export function whisperCheck({
* @property {string} name - Module command name * @property {string} name - Module command name
* @property {string} category - Module category name * @property {string} category - Module category name
* @property {string} description - Information about module * @property {string} description - Information about module
* @property {Array} aliases - An array of alternative cmd names
* @property {string} usage - Information about module usage * @property {string} usage - Information about module usage
*/ */
export const info = { export const info = {

View File

@ -0,0 +1,279 @@
/* eslint no-param-reassign: 0 */
/**
* @author Marzavec ( https://github.com/marzavec )
* @summary Enables the captcha
* @version 1.0.0
* @description Enables the captcha on the channel specified in the channel property,
* default is current channel
* @module enablecaptcha
*/
import captcha from 'ascii-captcha';
import {
isTrustedUser,
isModerator,
verifyNickname,
getUserPerms,
} from '../utility/_UAC.js';
import {
canJoinChannel,
} from '../utility/_Channels.js';
import {
upgradeLegacyJoin,
legacyLevelToLabel,
} from '../utility/_LegacyFunctions.js';
import {
Errors,
} from '../utility/_Constants.js';
/**
* Automatically executes once after server is ready
* @param {Object} core - Reference to core enviroment object
* @public
* @return {void}
*/
export async function init(core) {
if (typeof core.captchas === 'undefined') {
core.captchas = {};
}
}
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @public
* @return {void}
*/
export async function run({
core, server, socket, payload,
}) {
// increase rate limit chance and ignore if not admin or mod
if (!isModerator(socket.level)) {
return server.police.frisk(socket.address, 10);
}
let targetChannel;
if (typeof payload.channel !== 'string') {
if (typeof socket.channel !== 'string') { // @todo Multichannel
return false; // silently fail
}
targetChannel = socket.channel;
} else {
targetChannel = payload.channel;
}
if (core.captchas[targetChannel]) {
return server.reply({
cmd: 'info',
text: 'Captcha is already enabled.',
channel: socket.channel, // @todo Multichannel
}, socket);
}
core.captchas[targetChannel] = true;
server.broadcast({
cmd: 'info',
text: `Captcha enabled on: ${targetChannel}`,
channel: socket.channel, // @todo Multichannel, false for global info
}, { channel: socket.channel, level: isModerator });
return true;
}
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server enviroment object
* @public
* @return {void}
*/
export function initHooks(server) {
server.registerHook('in', 'chat', this.chatCheck.bind(this), 5);
server.registerHook('in', 'join', this.joinCheck.bind(this), 5);
}
/**
* Executes every time an incoming chat command is invoked;
* hook incoming chat commands, check if they are answering a captcha
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @public
* @return {{Object|boolean|string}} Object = same/altered payload,
* false = suppress action,
* string = error
*/
export function chatCheck({
core, server, socket, payload,
}) {
// always verifiy user input
if (typeof payload.text !== 'string') {
return false;
}
if (typeof socket.captcha !== 'undefined') {
if (socket.captcha.awaiting === true) {
if (payload.text === socket.captcha.solution) {
if (typeof socket.captcha.whitelist === 'undefined') {
socket.captcha.whitelist = [];
}
socket.captcha.whitelist.push(socket.captcha.origChannel);
socket.captcha.awaiting = false;
if (socket.hcProtocol === 1) {
core.commands.handleCommand(server, socket, {
cmd: 'join',
nick: `${socket.captcha.origNick}#${socket.captcha.origPass}`,
channel: socket.captcha.origChannel,
});
} else {
core.commands.handleCommand(server, socket, {
cmd: 'join',
nick: socket.captcha.origNick,
pass: socket.captcha.origPass,
channel: socket.captcha.origChannel,
});
}
return false;
}
server.police.frisk(socket.address, 7);
socket.terminate();
return false;
}
}
return payload;
}
/**
* Executes every time an incoming join command is invoked;
* hook incoming join commands, check if they are joining a captcha protected channel
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @public
* @return {{Object|boolean|string}} Object = same/altered payload,
* false = suppress action,
* string = error
*/
export function joinCheck({
core, server, socket, payload,
}) {
// check if channel has captcha enabled
if (core.captchas[payload.channel] !== true) {
return payload;
}
// `join` is the legacy entry point, check if it needs to be upgraded
const origPayload = { ...payload };
if (typeof socket.hcProtocol === 'undefined') {
payload = upgradeLegacyJoin(server, socket, payload);
}
// store payload values
const { channel, nick, pass } = payload;
// check if a client is able to join target channel
const mayJoin = canJoinChannel(channel, socket);
if (mayJoin !== true) {
return server.reply({
cmd: 'warn',
text: 'You may not join that channel.',
id: mayJoin,
channel: false, // @todo Multichannel, false for global event
}, socket);
}
// calling socket already in a channel
// @todo multichannel update, will remove
if (typeof socket.channel !== 'undefined') {
return server.reply({
cmd: 'warn', // @todo Remove this
text: 'Joining more than one channel is not currently supported',
id: Errors.Join.ALREADY_JOINED,
channel: false, // @todo Multichannel, false for global event
}, socket);
}
// end todo
// validates the user input for `nick`
if (verifyNickname(nick, socket) !== true) {
return server.reply({
cmd: 'warn',
text: 'Nickname must consist of up to 24 letters, numbers, and underscores',
id: Errors.Join.INVALID_NICK,
channel: false, // @todo Multichannel, false for global event
}, socket);
}
// get trip and level
const { trip, level } = getUserPerms(pass, core.saltKey, core.appConfig.data, channel);
// store the user values
const userInfo = {
nick,
trip,
uType: legacyLevelToLabel(level),
hash: socket.hash,
level,
userid: socket.userid,
isBot: socket.isBot,
color: socket.color,
channel,
};
if (userInfo.uType === 'user') {
if (userInfo.trip == null || isTrustedUser(level) === false) {
if (typeof socket.captcha === 'undefined') {
socket.captcha = {
awaiting: true,
origChannel: payload.channel,
origNick: payload.nick,
origPass: pass,
solution: captcha.generateRandomText(6),
};
server.reply({
cmd: 'warn',
text: 'Enter the following to join (case-sensitive):',
channel: payload.channel, // @todo Multichannel
}, socket);
server.reply({
cmd: 'captcha',
text: captcha.word2Transformedstr(socket.captcha.solution),
channel: payload.channel, // @todo Multichannel
}, socket);
return false;
}
socket.terminate();
return false;
}
}
return origPayload;
}
/**
* Module meta information
* @public
* @typedef {Object} enablecaptcha/info
* @property {string} name - Module command name
* @property {string} category - Module category name
* @property {string} description - Information about module
* @property {string} usage - Information about module usage
*/
export const info = {
name: 'enablecaptcha',
category: 'moderators',
description: 'Enables a captcha in the current channel you are in',
usage: `
API: { cmd: 'enablecaptcha', channel: '<optional channel, defaults to your current channel>' }`,
};

332
commands/mod/lockroom.js Normal file
View File

@ -0,0 +1,332 @@
/* eslint no-param-reassign: 0 */
/**
* @author Marzavec ( https://github.com/marzavec )
* @summary Locks the channel
* @version 1.0.0
* @description Locks a channel preventing default levels from joining
* @module lockroom
*/
import {
levels,
isTrustedUser,
isModerator,
verifyNickname,
getUserPerms,
} from '../utility/_UAC.js';
import {
upgradeLegacyJoin,
legacyLevelToLabel,
} from '../utility/_LegacyFunctions.js';
import {
Errors,
} from '../utility/_Constants.js';
import {
canJoinChannel,
} from '../utility/_Channels.js';
const danteQuotes = [
'Do not be afraid; our fate cannot be taken from us; it is a gift.',
'In the middle of the journey of our life I found myself within a dark woods where the straight way was lost.',
'There is no greater sorrow then to recall our times of joy in wretchedness.',
'They yearn for what they fear for.',
'Through me you go into a city of weeping; through me you go into eternal pain; through me you go amongst the lost people',
'From there we came outside and saw the stars',
'But the stars that marked our starting fall away. We must go deeper into greater pain, for it is not permitted that we stay.',
'Hope not ever to see Heaven. I have come to lead you to the other shore; into eternal darkness; into fire and into ice.',
'As little flowers, which the chill of night has bent and huddled, when the white sun strikes, grow straight and open fully on their stems, so did I, too, with my exhausted force.',
'At grief so deep the tongue must wag in vain; the language of our sense and memory lacks the vocabulary of such pain.',
'Thence we came forth to rebehold the stars.',
'He is, most of all, l\'amor che move il sole e l\'altre stelle.',
'The poets leave hell and again behold the stars.',
'One ought to be afraid of nothing other then things possessed of power to do us harm, but things innoucuous need not be feared.',
'As phantoms frighten beasts when shadows fall.',
'We were men once, though we\'ve become trees',
'Here pity only lives when it is dead',
'Lasciate ogne speranza, voi ch\'intrate.',
'There is no greater sorrow than thinking back upon a happy time in misery',
'My thoughts were full of other things When I wandered off the path.',
];
/**
* Automatically executes once after server is ready
* @param {Object} core - Reference to core enviroment object
* @public
* @return {void}
*/
export async function init(core) {
if (typeof core.locked === 'undefined') {
core.locked = {};
}
}
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @public
* @return {void}
*/
export async function run({
core, server, socket, payload,
}) {
// increase rate limit chance and ignore if not admin or mod
if (!isModerator(socket.level)) {
return server.police.frisk(socket.address, 10);
}
let targetChannel;
if (typeof payload.channel !== 'string') {
if (typeof socket.channel !== 'string') { // @todo Multichannel
return false; // silently fail
}
targetChannel = socket.channel;
} else {
targetChannel = payload.channel;
}
if (core.locked[targetChannel]) {
return server.reply({
cmd: 'info',
text: 'Channel is already locked.',
channel: socket.channel, // @todo Multichannel
}, socket);
}
// apply lock flag to channel list
core.locked[targetChannel] = true;
// inform mods
server.broadcast({
cmd: 'info',
text: `Channel: ?${targetChannel} lock enabled by [${socket.trip}]${socket.nick}`,
channel: false, // @todo Multichannel, false for global info
}, { level: isModerator });
return true;
}
/**
* Automatically executes once after server is ready to register this modules hooks
* @param {Object} server - Reference to server enviroment object
* @public
* @return {void}
*/
export function initHooks(server) {
server.registerHook('in', 'changenick', this.changeNickCheck.bind(this), 1);
server.registerHook('in', 'whisper', this.whisperCheck.bind(this), 1);
server.registerHook('in', 'chat', this.chatCheck.bind(this), 1);
server.registerHook('in', 'invite', this.inviteCheck.bind(this), 1);
server.registerHook('in', 'join', this.joinCheck.bind(this), 1);
}
/**
* Executes every time an incoming changenick command is invoked;
* hook incoming changenick commands, reject them if the channel is 'purgatory'
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @public
* @return {{Object|boolean|string}} Object = same/altered payload,
* false = suppress action,
* string = error
*/
export function changeNickCheck({
socket, payload,
}) {
if (socket.channel === 'purgatory') { // @todo Multichannel update
return false;
}
return payload;
}
/**
* Executes every time an incoming whisper command is invoked;
* hook incoming whisper commands, reject them if the channel is 'purgatory'
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @public
* @return {{Object|boolean|string}} Object = same/altered payload,
* false = suppress action,
* string = error
*/
export function whisperCheck({
socket, payload,
}) {
if (socket.channel === 'purgatory') { // @todo Multichannel update
return false;
}
return payload;
}
/**
* Executes every time an incoming chat command is invoked;
* hook incoming chat commands, reject them if the channel is 'purgatory'
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @public
* @return {{Object|boolean|string}} Object = same/altered payload,
* false = suppress action,
* string = error
*/
export function chatCheck({
socket, payload,
}) {
if (socket.channel === 'purgatory') {
if (socket.level >= levels.moderator) {
return payload;
}
return false;
}
return payload;
}
/**
* Executes every time an incoming invite command is invoked;
* hook incoming invite commands, reject them if the channel is 'purgatory'
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @public
* @return {{Object|boolean|string}} Object = same/altered payload,
* false = suppress action,
* string = error
*/
export function inviteCheck({
socket, payload,
}) {
if (socket.channel === 'purgatory') {
return false;
}
return payload;
}
/**
* Executes every time an incoming join command is invoked;
* hook incoming join commands, shunt them to purgatory if needed
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @public
* @return {{Object|boolean|string}} Object = same/altered payload,
* false = suppress action,
* string = error
*/
export function joinCheck({
core, server, socket, payload,
}) {
// check if target channel is locked
if (typeof core.locked[payload.channel] === 'undefined' || core.locked[payload.channel] !== true) {
if (payload.channel !== 'purgatory') {
return payload;
}
}
// `join` is the legacy entry point, check if it needs to be upgraded
if (typeof socket.hcProtocol === 'undefined') {
payload = upgradeLegacyJoin(server, socket, payload);
}
// store payload values
const { channel, nick, pass } = payload;
// check if a client is able to join target channel
const mayJoin = canJoinChannel(channel, socket);
if (mayJoin !== true) {
return server.reply({
cmd: 'warn',
text: 'You may not join that channel.',
id: mayJoin,
channel: false, // @todo Multichannel, false for global event
}, socket);
}
// calling socket already in a channel
// @todo multichannel update, will remove
if (typeof socket.channel !== 'undefined') {
return server.reply({
cmd: 'warn', // @todo Remove this
text: 'Joining more than one channel is not currently supported',
id: Errors.Join.ALREADY_JOINED,
channel: false, // @todo Multichannel, false for global event
}, socket);
}
// end todo
// validates the user input for `nick`
if (verifyNickname(nick, socket) !== true) {
return server.reply({
cmd: 'warn',
text: 'Nickname must consist of up to 24 letters, numbers, and underscores',
id: Errors.Join.INVALID_NICK,
channel: false, // @todo Multichannel, false for global event
}, socket);
}
// get trip and level
const { trip, level } = getUserPerms(pass, core.saltKey, core.appConfig.data, channel);
// store the user values
const userInfo = {
nick,
trip,
uType: legacyLevelToLabel(level),
hash: socket.hash,
level,
userid: socket.userid,
isBot: socket.isBot,
color: socket.color,
channel,
};
// check if trip is allowed
if (userInfo.uType === 'user') {
if (userInfo.trip == null || isTrustedUser(level) === false) {
const origNick = userInfo.nick;
const origChannel = payload.channel;
// not allowed, shunt to purgatory
payload.channel = 'purgatory';
// lost souls have no names
if (origChannel === 'purgatory') {
// someone is pulling a Dante
payload.nick = `Dante_${Math.random().toString(36).substr(2, 8)}`;
} else {
payload.nick = `${Math.random().toString(36).substr(2, 8)}${Math.random().toString(36).substr(2, 8)}`;
}
setTimeout(() => {
server.reply({
cmd: 'info',
text: danteQuotes[Math.floor(Math.random() * danteQuotes.length)],
channel: 'purgatory', // @todo Multichannel
}, socket);
}, 100);
server.broadcast({
cmd: 'info',
text: `${payload.nick} is: ${origNick}\ntrip: ${userInfo.trip || 'none'}\ntried to join: ?${origChannel}\nhash: ${userInfo.hash}`,
channel: 'purgatory', // @todo Multichannel, false for global info
}, { channel: 'purgatory', level: isModerator });
}
}
return payload;
}
/**
* Module meta information
* @public
* @typedef {Object} kick/info
* @property {string} name - Module command name
* @property {string} category - Module category name
* @property {string} description - Information about module
* @property {string} usage - Information about module usage
*/
export const info = {
name: 'lockroom',
category: 'moderators',
description: 'Locks a channel preventing default levels from joining',
usage: `
API: { cmd: 'lockroom', channel: '<optional channel, defaults to your current channel>' }`,
};

View File

@ -94,6 +94,7 @@ export async function run({
* @property {string} name - Module command name * @property {string} name - Module command name
* @property {string} category - Module category name * @property {string} category - Module category name
* @property {string} description - Information about module * @property {string} description - Information about module
* @property {Array} aliases - An array of alternative cmd names
* @property {string} usage - Information about module usage * @property {string} usage - Information about module usage
*/ */
export const info = { export const info = {
@ -102,5 +103,5 @@ export const info = {
description: 'Pardon a dumb user to be able to speak again', description: 'Pardon a dumb user to be able to speak again',
aliases: ['unmuzzle', 'unmute'], aliases: ['unmuzzle', 'unmute'],
usage: ` usage: `
API: { cmd: 'speak', ip/hash: '<target ip or hash' }`, API: { cmd: 'speak', ip/hash: '<target ip or hash>' }`,
}; };

View File

@ -0,0 +1,89 @@
/* eslint no-console: 0 */
/**
* @author Marzavec ( https://github.com/marzavec )
* @summary Unlock target channel
* @version 1.0.0
* @description Unlocks a channel allowing anyone to join
* @module unlockroom
*/
import {
isModerator,
} from '../utility/_UAC.js';
/**
* Automatically executes once after server is ready
* @param {Object} core - Reference to core enviroment object
* @public
* @return {void}
*/
export async function init(core) {
if (typeof core.locked === 'undefined') {
core.locked = {};
}
}
/**
* Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket & payload
* @public
* @return {void}
*/
export async function run({
core, server, socket, payload,
}) {
// increase rate limit chance and ignore if not admin or mod
if (!isModerator(socket.level)) {
return server.police.frisk(socket.address, 10);
}
let targetChannel;
if (typeof payload.channel !== 'string') {
if (typeof socket.channel !== 'string') { // @todo Multichannel
return false; // silently fail
}
targetChannel = socket.channel;
} else {
targetChannel = payload.channel;
}
if (!core.locked[targetChannel]) {
return server.reply({
cmd: 'info',
text: 'Channel is not locked.',
channel: socket.channel, // @todo Multichannel
}, socket);
}
core.locked[targetChannel] = false;
server.broadcast({
cmd: 'info',
text: `Channel: ?${targetChannel} unlocked by [${socket.trip}]${socket.nick}`,
channel: targetChannel, // @todo Multichannel, false for global info
}, { channel: targetChannel, level: isModerator });
console.log(`Channel: ?${targetChannel} unlocked by [${socket.trip}]${socket.nick} in ${socket.channel}`);
return true;
}
/**
* Module meta information
* @public
* @typedef {Object} unlockroom/info
* @property {string} name - Module command name
* @property {string} category - Module category name
* @property {string} description - Information about module
* @property {string} usage - Information about module usage
*/
export const info = {
name: 'unlockroom',
category: 'moderators',
description: 'Unlock the current channel you are in or target channel as specified',
usage: `
API: { cmd: 'unlockroom', channel: '<optional target channel>' }`,
};

View File

@ -1,3 +1,13 @@
/* eslint import/prefer-default-export: 0 */
/**
* @author MinusGix ( https://github.com/MinusGix )
* @summary General string helper functions
* @version v1.0.0
* @description A library of several commonly used string functions
* @module Text
*/
/** /**
* Check and trim string provided by remote client * Check and trim string provided by remote client
* @public * @public

View File

@ -15,8 +15,6 @@ const {
createHash, createHash,
} = await import('crypto'); } = await import('crypto');
const TripLength = 10;
/** /**
* Object defining labels for default permission ranges * Object defining labels for default permission ranges
* @typedef {Object} levels * @typedef {Object} levels
@ -110,7 +108,7 @@ export function getUserDetails(socket) {
return { return {
nick: socket.nick, nick: socket.nick,
trip: socket.trip || '', trip: socket.trip || '',
uType: socket.uType, /* @legacy */ uType: socket.uType,
hash: socket.hash, hash: socket.hash,
level: socket.level, level: socket.level,
userid: socket.userid, userid: socket.userid,
@ -152,7 +150,7 @@ export function getUserPerms(pass, salt, config, channel) {
const sha = createHash('sha256'); const sha = createHash('sha256');
sha.update(pass + salt); sha.update(pass + salt);
const trip = sha.digest('base64').substr(0, TripLength); const trip = sha.digest('base64').substr(0, 6);
// check if user is global admin // check if user is global admin
if (trip === config.adminTrip) { if (trip === config.adminTrip) {

View File

@ -56,7 +56,7 @@ export async function run({
} }
// add new trip to config // add new trip to config
core.appConfig.data.globalMods.push({ trip: payload.trip }); core.config.mods.push({ trip: payload.trip });
// find targets current connections // find targets current connections
const newMod = server.findSockets({ trip: payload.trip }); const newMod = server.findSockets({ trip: payload.trip });
@ -150,7 +150,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -119,7 +119,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -54,7 +54,8 @@ export async function run({
} }
// do command reload and store results // do command reload and store results
let loadResult = await core.commands.reloadCommands(); let loadResult = core.dynamicImports.reloadDirCache();
loadResult += core.commands.loadCommands();
// clear and rebuild all module hooks // clear and rebuild all module hooks
server.loadHooks(); server.loadHooks();
@ -113,7 +114,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -57,9 +57,7 @@ export async function run({
// remove trip from config // remove trip from config
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
core.appConfig.data.globalMods = core.appConfig.data.globalMods.filter( core.config.mods = core.config.mods.filter((mod) => mod.trip !== payload.trip);
(mod) => mod.trip !== payload.trip,
);
// find targets current connections // find targets current connections
const targetMod = server.findSockets({ trip: payload.trip }); const targetMod = server.findSockets({ trip: payload.trip });
@ -76,7 +74,7 @@ export async function run({
for (let i = 0, l = targetMod.length; i &lt; l; i += 1) { for (let i = 0, l = targetMod.length; i &lt; l; i += 1) {
// downgrade privileges // downgrade privileges
targetMod[i].uType = 'user'; /* @legacy */ targetMod[i].uType = 'user';
targetMod[i].level = levels.default; targetMod[i].level = levels.default;
// inform ex-mod // inform ex-mod
@ -155,7 +153,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -52,9 +52,7 @@ export async function run({ core, server, socket }) {
} }
// attempt save, notify of failure // attempt save, notify of failure
try { if (!core.configManager.save()) {
await core.appConfig.write();
} catch (err) {
return server.reply({ return server.reply({
cmd: 'warn', // @todo Add numeric error code as `id` cmd: 'warn', // @todo Add numeric error code as `id`
text: 'Failed to save config, check logs.', text: 'Failed to save config, check logs.',
@ -104,7 +102,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -100,7 +100,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -200,7 +200,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -48,7 +48,7 @@ import {
* @return {void} * @return {void}
*/ */
export async function run({ export async function run({
server, socket, payload, core, server, socket, payload,
}) { }) {
const { channel } = socket; const { channel } = socket;
@ -77,6 +77,18 @@ export async function run({
}, socket); }, socket);
} }
// prevent admin impersonation
// @todo prevent mod impersonation
if (newNick.toLowerCase() === core.config.adminName.toLowerCase()) {
server.police.frisk(socket.address, 4);
return server.reply({
cmd: 'warn', // @todo Add numeric error code as `id`
text: 'You are not the admin, liar!',
channel, // @todo Multichannel
}, socket);
}
if (newNick == previousNick) { if (newNick == previousNick) {
return server.reply({ return server.reply({
cmd: 'warn', // @todo Add numeric error code as `id` cmd: 'warn', // @todo Add numeric error code as `id`
@ -255,7 +267,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -237,7 +237,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -204,7 +204,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -176,7 +176,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -165,7 +165,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -27,7 +27,6 @@
<section> <section>
<article> <article>
<pre class="prettyprint source linenums"><code>/* eslint no-param-reassign: 0 */ <pre class="prettyprint source linenums"><code>/* eslint no-param-reassign: 0 */
/* eslint import/no-cycle: [0, { ignoreExternal: true }] */
/** /**
* @author Marzavec ( https://github.com/marzavec ) * @author Marzavec ( https://github.com/marzavec )
@ -37,9 +36,6 @@
* @module join * @module join
*/ */
import {
getSession,
} from './session.js';
import { import {
canJoinChannel, canJoinChannel,
socketInChannel, socketInChannel,
@ -118,7 +114,7 @@ export async function run({
} }
// get trip and level // get trip and level
const { trip, level } = getUserPerms(pass, core.saltKey, core.appConfig.data, channel); const { trip, level } = getUserPerms(pass, core.config, channel);
// store the user values // store the user values
const userInfo = { const userInfo = {
@ -133,6 +129,13 @@ export async function run({
channel, channel,
}; };
// prevent admin impersonation
if (nick.toLowerCase() === core.config.adminName.toLowerCase()) {
if (userInfo.trip !== 'Admin') {
userInfo.nick = `Fake${userInfo.nick}`;
}
}
// check if the nickname already exists in the channel // check if the nickname already exists in the channel
const userExists = server.findSockets({ const userExists = server.findSockets({
channel, channel,
@ -177,7 +180,6 @@ export async function run({
socket.channel = channel; /* @legacy */ socket.channel = channel; /* @legacy */
// @todo multi-channel patch // @todo multi-channel patch
// socket.channels.push(channel); // socket.channels.push(channel);
socket.channels = [channel];
nicks.push(userInfo.nick); /* @legacy */ nicks.push(userInfo.nick); /* @legacy */
users.push({ ...{ isme: true, isBot: socket.isBot }, ...userInfo }); users.push({ ...{ isme: true, isBot: socket.isBot }, ...userInfo });
@ -190,14 +192,6 @@ export async function run({
channel, // @todo Multichannel (?) channel, // @todo Multichannel (?)
}, socket); }, socket);
// update client with new session info
server.reply({
cmd: 'session',
restored: false,
token: getSession(socket, core),
channels: socket.channels,
}, socket);
// stats are fun // stats are fun
core.stats.increment('users-joined'); core.stats.increment('users-joined');
@ -276,9 +270,6 @@ export function restoreJoin({
channel, // @todo Multichannel (?) channel, // @todo Multichannel (?)
}, socket); }, socket);
socket.channel = channel; /* @legacy */
socket.channels.push(channel);
return true; return true;
} }
@ -314,7 +305,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -206,7 +206,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -74,7 +74,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -26,7 +26,7 @@
<section> <section>
<article> <article>
<pre class="prettyprint source linenums"><code>/* eslint import/no-cycle: [0, { ignoreExternal: true }] */ <pre class="prettyprint source linenums"><code>/* eslint no-param-reassign: 0 */
/** /**
* @author Marzavec ( https://github.com/marzavec ) * @author Marzavec ( https://github.com/marzavec )
@ -49,7 +49,7 @@ import {
restoreJoin, restoreJoin,
} from './join.js'; } from './join.js';
const SessionLocation = './session.key'; const CertLocation = './cert.key';
/** /**
* *
@ -67,10 +67,10 @@ export function getSession(socket, core) {
nick: socket.nick, nick: socket.nick,
trip: socket.trip, trip: socket.trip,
userid: socket.userid, userid: socket.userid,
uType: socket.uType, /* @legacy */ uType: socket.uType,
muzzled: socket.muzzled || false, muzzled: socket.muzzled,
banned: socket.banned || false, banned: socket.banned,
}, core.sessionKey, { }, core.cert, {
expiresIn: '7 days', expiresIn: '7 days',
}); });
} }
@ -106,7 +106,7 @@ export async function run({
let session = false; let session = false;
try { try {
session = jsonwebtoken.verify(payload.token, core.sessionKey); session = jsonwebtoken.verify(payload.token, core.cert);
} catch (err) { } catch (err) {
return notifyFailure(server, socket); return notifyFailure(server, socket);
} }
@ -120,7 +120,7 @@ export async function run({
return notifyFailure(server, socket); return notifyFailure(server, socket);
} }
if (typeof session.color !== 'string' &amp;&amp; typeof session.color !== 'boolean') { if (typeof session.color !== 'string') {
return notifyFailure(server, socket); return notifyFailure(server, socket);
} }
@ -157,14 +157,15 @@ export async function run({
} }
// populate socket info with validated session // populate socket info with validated session
socket.channels = []; socket.channel = session.channel;
socket.channels = session.channels;
socket.color = session.color; socket.color = session.color;
socket.isBot = session.isBot; socket.isBot = session.isBot;
socket.level = session.level; socket.level = session.level;
socket.nick = session.nick; socket.nick = session.nick;
socket.trip = session.trip; socket.trip = session.trip;
socket.userid = session.userid; socket.userid = session.userid;
socket.uType = session.uType; /* @legacy */ socket.uType = session.uType;
socket.muzzled = session.muzzled; socket.muzzled = session.muzzled;
socket.banned = session.banned; socket.banned = session.banned;
@ -179,12 +180,12 @@ export async function run({
channels: socket.channels, channels: socket.channels,
}, socket); }, socket);
for (let i = 0, j = session.channels.length; i &lt; j; i += 1) { for (let i = 0, j = socket.channels.length; i &lt; j; i += 1) {
restoreJoin({ restoreJoin({
core, core,
server, server,
socket, socket,
channel: session.channels[i], channel: socket.channels[i],
}, true); }, true);
} }
@ -199,8 +200,8 @@ export async function run({
*/ */
export function init(core) { export function init(core) {
// load the encryption key if required // load the encryption key if required
if (typeof core.sessionKey === 'undefined') { if (typeof core.cert === 'undefined') {
core.sessionKey = fs.readFileSync(SessionLocation); core.cert = fs.readFileSync(CertLocation);
} }
} }
@ -235,7 +236,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -101,7 +101,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -273,7 +273,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -96,7 +96,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -34,10 +34,6 @@
* @module disconnect * @module disconnect
*/ */
import {
socketInChannel,
} from '../utility/_Channels.js';
/** /**
* Executes when invoked by a remote client * Executes when invoked by a remote client
* @param {Object} env - Enviroment object with references to core, server, socket &amp; payload * @param {Object} env - Enviroment object with references to core, server, socket &amp; payload
@ -53,15 +49,11 @@ export async function run({ server, socket, payload }) {
// send leave notice to client peers // send leave notice to client peers
// @todo Multichannel update // @todo Multichannel update
if (socket.channel) { if (socket.channel) {
const isDuplicate = socketInChannel(server, socket.channel, socket);
if (isDuplicate === false) {
server.broadcast({ server.broadcast({
cmd: 'onlineRemove', cmd: 'onlineRemove',
nick: socket.nick, nick: socket.nick,
}, { channel: socket.channel }); }, { channel: socket.channel });
} }
}
// commit close just in case // commit close just in case
socket.terminate(); socket.terminate();
@ -108,7 +100,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -93,7 +93,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -157,7 +157,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -389,6 +389,7 @@ export function whisperCheck({
* @property {string} name - Module command name * @property {string} name - Module command name
* @property {string} category - Module category name * @property {string} category - Module category name
* @property {string} description - Information about module * @property {string} description - Information about module
* @property {Array} aliases - An array of alternative cmd names
* @property {string} usage - Information about module usage * @property {string} usage - Information about module usage
*/ */
export const info = { export const info = {
@ -415,7 +416,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -241,7 +241,7 @@ Text: /forcecolor &lt;target nick> &lt;color as hex>`,
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -194,7 +194,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -122,6 +122,7 @@ export async function run({
* @property {string} name - Module command name * @property {string} name - Module command name
* @property {string} category - Module category name * @property {string} category - Module category name
* @property {string} description - Information about module * @property {string} description - Information about module
* @property {Array} aliases - An array of alternative cmd names
* @property {string} usage - Information about module usage * @property {string} usage - Information about module usage
*/ */
export const info = { export const info = {
@ -148,7 +149,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -133,7 +133,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -106,7 +106,7 @@ export const info = {
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -595,7 +595,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -522,7 +522,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -893,7 +893,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -255,7 +255,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="core_changenick.js.html">core/changenick.js</a>, <a href="core_changenick.js.html#line138">line 138</a> <a href="core_changenick.js.html">core/changenick.js</a>, <a href="core_changenick.js.html#line150">line 150</a>
</li></ul></dd> </li></ul></dd>
@ -410,7 +410,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="core_changenick.js.html">core/changenick.js</a>, <a href="core_changenick.js.html#line150">line 150</a> <a href="core_changenick.js.html">core/changenick.js</a>, <a href="core_changenick.js.html#line162">line 162</a>
</li></ul></dd> </li></ul></dd>
@ -784,7 +784,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="core_changenick.js.html">core/changenick.js</a>, <a href="core_changenick.js.html#line197">line 197</a> <a href="core_changenick.js.html">core/changenick.js</a>, <a href="core_changenick.js.html#line209">line 209</a>
</li></ul></dd> </li></ul></dd>
@ -857,7 +857,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="core_changenick.js.html">core/changenick.js</a>, <a href="core_changenick.js.html#line189">line 189</a> <a href="core_changenick.js.html">core/changenick.js</a>, <a href="core_changenick.js.html#line201">line 201</a>
</li></ul></dd> </li></ul></dd>
@ -893,7 +893,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -1038,7 +1038,7 @@ assumes a failed chat command invocation and will reject with notice
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -255,7 +255,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="internal_disconnect.js.html">internal/disconnect.js</a>, <a href="internal_disconnect.js.html#line19">line 19</a> <a href="internal_disconnect.js.html">internal/disconnect.js</a>, <a href="internal_disconnect.js.html#line15">line 15</a>
</li></ul></dd> </li></ul></dd>
@ -486,7 +486,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="internal_disconnect.js.html">internal/disconnect.js</a>, <a href="internal_disconnect.js.html#line52">line 52</a> <a href="internal_disconnect.js.html">internal/disconnect.js</a>, <a href="internal_disconnect.js.html#line44">line 44</a>
</li></ul></dd> </li></ul></dd>
@ -559,7 +559,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="internal_disconnect.js.html">internal/disconnect.js</a>, <a href="internal_disconnect.js.html#line44">line 44</a> <a href="internal_disconnect.js.html">internal/disconnect.js</a>, <a href="internal_disconnect.js.html#line36">line 36</a>
</li></ul></dd> </li></ul></dd>
@ -595,7 +595,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -1264,7 +1264,7 @@ shadow-prevent all whispers from muzzled users
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -894,7 +894,7 @@ hooks chat commands checking for /me
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -894,7 +894,7 @@ hooks chat commands checking for /forcecolor
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -821,7 +821,7 @@ hooks chat commands checking for /help
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -522,7 +522,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -97,7 +97,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="core_join.js.html">core/join.js</a>, <a href="core_join.js.html#line4">line 4</a> <a href="core_join.js.html">core/join.js</a>, <a href="core_join.js.html#line3">line 3</a>
</li></ul></dd> </li></ul></dd>
@ -255,7 +255,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="core_join.js.html">core/join.js</a>, <a href="core_join.js.html#line38">line 38</a> <a href="core_join.js.html">core/join.js</a>, <a href="core_join.js.html#line34">line 34</a>
</li></ul></dd> </li></ul></dd>
@ -486,7 +486,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="core_join.js.html">core/join.js</a>, <a href="core_join.js.html#line257">line 257</a> <a href="core_join.js.html">core/join.js</a>, <a href="core_join.js.html#line248">line 248</a>
</li></ul></dd> </li></ul></dd>
@ -522,7 +522,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -522,7 +522,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -522,7 +522,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -821,7 +821,7 @@ hooks chat commands checking for /stats
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -473,7 +473,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -486,7 +486,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="admin_reload.js.html">admin/reload.js</a>, <a href="admin_reload.js.html#line56">line 56</a> <a href="admin_reload.js.html">admin/reload.js</a>, <a href="admin_reload.js.html#line57">line 57</a>
</li></ul></dd> </li></ul></dd>
@ -522,7 +522,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -486,7 +486,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="admin_removemod.js.html">admin/removemod.js</a>, <a href="admin_removemod.js.html#line98">line 98</a> <a href="admin_removemod.js.html">admin/removemod.js</a>, <a href="admin_removemod.js.html#line96">line 96</a>
</li></ul></dd> </li></ul></dd>
@ -559,7 +559,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="admin_removemod.js.html">admin/removemod.js</a>, <a href="admin_removemod.js.html#line90">line 90</a> <a href="admin_removemod.js.html">admin/removemod.js</a>, <a href="admin_removemod.js.html#line88">line 88</a>
</li></ul></dd> </li></ul></dd>
@ -595,7 +595,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -486,7 +486,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="admin_saveconfig.js.html">admin/saveconfig.js</a>, <a href="admin_saveconfig.js.html#line47">line 47</a> <a href="admin_saveconfig.js.html">admin/saveconfig.js</a>, <a href="admin_saveconfig.js.html#line45">line 45</a>
</li></ul></dd> </li></ul></dd>
@ -522,7 +522,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -429,7 +429,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="core_session.js.html">core/session.js</a>, <a href="core_session.js.html#line172">line 172</a> <a href="core_session.js.html">core/session.js</a>, <a href="core_session.js.html#line173">line 173</a>
</li></ul></dd> </li></ul></dd>
@ -989,7 +989,7 @@
<dt class="tag-source">Source:</dt> <dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li> <dd class="tag-source"><ul class="dummy"><li>
<a href="core_session.js.html">core/session.js</a>, <a href="core_session.js.html#line179">line 179</a> <a href="core_session.js.html">core/session.js</a>, <a href="core_session.js.html#line180">line 180</a>
</li></ul></dd> </li></ul></dd>
@ -1025,7 +1025,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -595,7 +595,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -596,7 +596,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -677,7 +677,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -522,7 +522,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -522,7 +522,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -522,7 +522,7 @@
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -902,7 +902,7 @@ hooks chat commands checking for /whisper
<br class="clear"> <br class="clear">
<footer> <footer>
Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.6</a> on Wed Jun 22 2022 10:04:25 GMT-0500 (Central Daylight Time) Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 3.6.10</a> on Thu May 05 2022 00:29:35 GMT-0700 (Pacific Daylight Time)
</footer> </footer>
<script> prettyPrint(); </script> <script> prettyPrint(); </script>

View File

@ -1,4 +1,4 @@
/*global document */ /* global document */
(() => { (() => {
const source = document.getElementsByClassName('prettyprint source linenums'); const source = document.getElementsByClassName('prettyprint source linenums');
let i = 0; let i = 0;

View File

@ -1,2 +1,2 @@
PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com", PR.registerLangHandler(PR.createSimpleLexer([['pln', /^[\t\n\f\r ]+/, null, ' \t\r\n ']], [['str', /^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/, null], ['str', /^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/, null], ['lang-css-str', /^url\(([^"')]*)\)/i], ['kwd', /^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i, null], ['lang-css-kw', /^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i], ['com', /^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//], ['com',
/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]); /^(?:<\!--|--\>)/], ['lit', /^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i], ['lit', /^#[\da-f]{3,6}/i], ['pln', /^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i], ['pun', /^[^\s\w"']+/]]), ['css']); PR.registerLangHandler(PR.createSimpleLexer([], [['kwd', /^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]), ['css-kw']); PR.registerLangHandler(PR.createSimpleLexer([], [['str', /^[^"')]+/]]), ['css-str']);

View File

@ -1,28 +1,125 @@
var q=null;window.PR_SHOULD_USE_CONTINUATION=!0; const q = null; window.PR_SHOULD_USE_CONTINUATION = !0;
(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a= (function () {
[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c< function L(a) {
f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&& function m(a) { let f = a.charCodeAt(0); if (f !== 92) return f; const b = a.charAt(1); return (f = r[b]) ? f : b >= '0' && b <= '7' ? parseInt(a.substring(1), 8) : b === 'u' || b === 'x' ? parseInt(a.substring(2), 16) : a.charCodeAt(1); } function e(a) { if (a < 32) return (a < 16 ? '\\x0' : '\\x') + a.toString(16); a = String.fromCharCode(a); if (a === '\\' || a === '-' || a === '[' || a === ']')a = `\\${a}`; return a; } function h(a) {
(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r= for (var f = a.substring(1, a.length - 1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g), a = [], b = [], o = f[0] === '^', c = o ? 1 : 0, i = f.length; c < i; ++c) { var j = f[c]; if (/\\[bdsw]/i.test(j))a.push(j); else { var j = m(j); var d; c + 2 < i && f[c + 1] === '-' ? (d = m(f[c + 2]), c += 2) : d = j; b.push([j, d]); d < 65 || j > 122 || (d < 65 || j > 90 || b.push([Math.max(65, j) | 32, Math.min(d, 90) | 32]), d < 97 || j > 122 || b.push([Math.max(97, j) & -33, Math.min(d, 122) & -33])); } }b.sort((a, f) => a[0] - f[0] || f[1] - a[1]); f = []; j = [NaN, NaN]; for (c = 0; c < b.length; ++c)i = b[c], i[0] <= j[1] + 1 ? j[1] = Math.max(j[1], i[1]) : f.push(j = i); b = ['[']; o && b.push('^'); b.push.apply(b, a); for (c = 0; c
{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length, < f.length; ++c)i = f[c], b.push(e(i[0])), i[1] > i[0] && (i[1] + 1 > i[0] && b.push('-'), b.push(e(i[1]))); b.push(']'); return b.join('');
t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b=== } function y(a) {
"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m), for (var f = a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g), b = f.length, d = [], c = 0, i = 0; c < b; ++c) { var j = f[c]; j === '(' ? ++i : j.charAt(0) === '\\' && (j = +j.substring(1)) && j <= i && (d[j] = -1); } for (c = 1; c < d.length; ++c)d[c] === -1 && (d[c] = ++t); for (i = c = 0; c < b; ++c) {
l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/, j = f[c], j === '(' ? (++i, d[i] === void 0 && (f[c] = '(?:')) : j.charAt(0) === '\\'
q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/, && (j = +j.substring(1)) && j <= i && (f[c] = `\\${d[i]}`);
q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g, } for (i = c = 0; c < b; ++c)f[c] === '^' && f[c + 1] !== '^' && (f[c] = ''); if (a.ignoreCase && s) for (c = 0; c < b; ++c)j = f[c], a = j.charAt(0), j.length >= 2 && a === '[' ? f[c] = h(j) : a !== '\\' && (f[c] = j.replace(/[A-Za-z]/g, (a) => { a = a.charCodeAt(0); return `[${String.fromCharCode(a & -33, a | 32)}]`; })); return f.join('');
"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a), } for (var t = 0, s = !1, l = !1, p = 0, d = a.length; p < d; ++p) { var g = a[p]; if (g.ignoreCase)l = !0; else if (/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi, ''))) { s = !0; l = !1; break; } } for (var r = {
a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e} b: 8, t: 9, n: 10, v: 11, f: 12, r: 13,
for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value", }, n = [], p = 0, d = a.length; p < d; ++p) { g = a[p]; if (g.global || g.multiline) throw Error(`${g}`); n.push(`(?:${y(g)})`); } return RegExp(n.join('|'), l ? 'gi' : 'g');
m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m= } function M(a) {
a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue= function m(a) {
j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"], switch (a.nodeType) {
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"], case 1: if (e.test(a.className)) break; for (var g = a.firstChild; g; g = g.nextSibling)m(g); g = a.nodeName; if (g === 'BR' || g === 'LI')h[s] = '\n', t[s << 1] = y++, t[s++ << 1 | 1] = a; break; case 3: case 4: g = a.nodeValue, g.length && (g = p ? g.replace(/\r\n?/g, '\n') : g.replace(/[\t\n\r ]+/g, ' '), h[s] = g, t[s << 1] = y, y += g.length,
H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"], t[s++ << 1 | 1] = a);
J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+ }
I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]), } var e = /(?:^|\s)nocode(?:\s|$)/; var h = []; var y = 0; var t = []; var s = 0; let l; a.currentStyle ? l = a.currentStyle.whiteSpace : window.getComputedStyle && (l = document.defaultView.getComputedStyle(a, q).getPropertyValue('white-space')); var p = l && l.substring(0, 3) === 'pre'; m(a); return { a: h.join('').replace(/\n$/, ''), c: t };
["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css", } function B(a, m, e, h) { m && (a = { a: m, d: a }, e(a), h.push.apply(h, a.e)); } function x(a, m) {
/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}), function e(a) {
["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes", for (var l = a.d, p = [l, 'pln'], d = 0, g = a.a.match(y) || [], r = {}, n = 0, z = g.length; n < z; ++n) {
hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b= const f = g[n]; let b = r[f]; let o = void 0; var c; if (typeof b
!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m, === 'string')c = !1; else { var i = h[f.charAt(0)]; if (i)o = f.match(i[1]), b = i[0]; else { for (c = 0; c < t; ++c) if (i = m[c], o = f.match(i[1])) { b = i[0]; break; }o || (b = 'pln'); } if ((c = b.length >= 5 && b.substring(0, 5) === 'lang-') && !(o && typeof o[1] === 'string'))c = !1, b = 'src'; c || (r[f] = b); }i = d; d += f.length; if (c) { c = o[1]; let j = f.indexOf(c); let k = j + c.length; o[2] && (k = f.length - o[2].length, j = k - c.length); b = b.substring(5); B(l + i, f.substring(0, j), e, p); B(l + i + j, c, C(b, c), p); B(l + i + k, f.substring(k), e, p); } else p.push(l + i, b);
250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit", }a.e = p;
PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})(); } var h = {}; let y; (function () {
for (var e = a.concat(m),
l = [], p = {}, d = 0, g = e.length; d < g; ++d) { let r = e[d]; let n = r[3]; if (n) for (let k = n.length; --k >= 0;)h[n.charAt(k)] = r; r = r[1]; n = `${r}`; p.hasOwnProperty(n) || (l.push(r), p[n] = q); }l.push(/[\S\s]/); y = L(l);
}()); var t = m.length; return e;
} function u(a) {
const m = []; const e = []; a.tripleQuotedStrings ? m.push(['str', /^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/, q, "'\""]) : a.multiLineStrings ? m.push(['str', /^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
q, "'\"`"]) : m.push(['str', /^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/, q, "\"'"]); a.verbatimStrings && e.push(['str', /^@"(?:[^"]|"")*(?:"|$)/, q]); let h = a.hashComments; h && (a.cStyleComments ? (h > 1 ? m.push(['com', /^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/, q, '#']) : m.push(['com', /^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/, q, '#']), e.push(['str', /^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/, q])) : m.push(['com', /^#[^\n\r]*/,
q, '#'])); a.cStyleComments && (e.push(['com', /^\/\/[^\n\r]*/, q]), e.push(['com', /^\/\*[\S\s]*?(?:\*\/|$)/, q])); a.regexLiterals && e.push(['lang-regex', /^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]); (h = a.types) && e.push(['typ', h]); a = (`${a.keywords}`).replace(
/^ | $/g,
'',
); a.length && e.push(['kwd', RegExp(`^(?:${a.replace(/[\s,]+/g, '|')})\\b`), q]); m.push(['pln', /^\s+/, q, ' \r\n\t\xa0']); e.push(['lit', /^@[$_a-z][\w$@]*/i, q], ['typ', /^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/, q], ['pln', /^[$_a-z][\w$@]*/i, q], ['lit', /^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i, q, '0123456789'], ['pln', /^\\[\S\s]?/, q], ['pun', /^.[^\s\w"-$'./@\\`]*/, q]); return x(m, e);
} function D(a, m) {
function e(a) {
switch (a.nodeType) {
case 1: if (k.test(a.className)) break; if (a.nodeName === 'BR') {
h(a),
a.parentNode && a.parentNode.removeChild(a);
} else for (a = a.firstChild; a; a = a.nextSibling)e(a); break; case 3: case 4: if (p) { let b = a.nodeValue; const d = b.match(t); if (d) { const c = b.substring(0, d.index); a.nodeValue = c; (b = b.substring(d.index + d[0].length)) && a.parentNode.insertBefore(s.createTextNode(b), a.nextSibling); h(a); c || a.parentNode.removeChild(a); } }
}
} function h(a) {
function b(a, d) { const e = d ? a.cloneNode(!1) : a; var f = a.parentNode; if (f) { var f = b(f, 1); let g = a.nextSibling; f.appendChild(e); for (let h = g; h; h = g)g = h.nextSibling, f.appendChild(h); } return e; }
for (;!a.nextSibling;) if (a = a.parentNode, !a) return; for (var a = b(a.nextSibling, 0), e; (e = a.parentNode) && e.nodeType === 1;)a = e; d.push(a);
} var k = /(?:^|\s)nocode(?:\s|$)/; var t = /\r\n?|\n/; var s = a.ownerDocument; let l; a.currentStyle ? l = a.currentStyle.whiteSpace : window.getComputedStyle && (l = s.defaultView.getComputedStyle(a, q).getPropertyValue('white-space')); var p = l && l.substring(0, 3) === 'pre'; for (l = s.createElement('LI'); a.firstChild;)l.appendChild(a.firstChild); for (var d = [l], g = 0; g < d.length; ++g)e(d[g]); m === (m | 0) && d[0].setAttribute(
'value',
m,
); const r = s.createElement('OL'); r.className = 'linenums'; for (var n = Math.max(0, m - 1 | 0) || 0, g = 0, z = d.length; g < z; ++g)l = d[g], l.className = `L${(g + n) % 10}`, l.firstChild || l.appendChild(s.createTextNode('\xa0')), r.appendChild(l); a.appendChild(r);
} function k(a, m) { for (let e = m.length; --e >= 0;) { const h = m[e]; A.hasOwnProperty(h) ? window.console && console.warn('cannot override language handler %s', h) : A[h] = a; } } function C(a, m) { if (!a || !A.hasOwnProperty(a))a = /^\s*</.test(m) ? 'default-markup' : 'default-code'; return A[a]; } function E(a) {
var m = a.g; try {
var e = M(a.h); var h = e.a; a.a = h; a.c = e.c; a.d = 0; C(m, h)(a); const k = /\bMSIE\b/.test(navigator.userAgent); var m = /\n/g; const t = a.a; const s = t.length; var e = 0; const l = a.c; const p = l.length; var h = 0; const d = a.e; let g = d.length; var a = 0; d[g] = s; let r; let n; for (n = r = 0; n < g;)d[n] !== d[n + 2] ? (d[r++] = d[n++], d[r++] = d[n++]) : n += 2; g = r; for (n = r = 0; n < g;) { for (var z = d[n], f = d[n + 1], b = n + 2; b + 2 <= g && d[b + 1] === f;)b += 2; d[r++] = z; d[r++] = f; n = b; } for (d.length = r; h < p;) {
const o = l[h + 2] || s; const c = d[a + 2] || s; var b = Math.min(o, c); let i = l[h + 1]; var j; if (i.nodeType !== 1 && (j = t.substring(e, b))) {
k && (j = j.replace(m, '\r')); i.nodeValue = j; const u = i.ownerDocument; const v = u.createElement('SPAN'); v.className = d[a + 1]; const x = i.parentNode; x.replaceChild(v, i); v.appendChild(i); e < o && (l[h + 1] = i = u.createTextNode(t.substring(b, o)), x.insertBefore(i, v.nextSibling));
}e = b; e >= o && (h += 2); e >= c && (a += 2);
}
} catch (w) { 'console' in window && console.log(w && w.stack ? w.stack : w); }
} var v = ['break,continue,do,else,for,if,return,while']; var w = [[v, 'auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile'],
'catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof']; const F = [w, 'alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where']; const G = [w, 'abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient'];
const H = [G, 'as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var']; var w = [w, 'debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN']; const I = [v, 'and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None'];
const J = [v, 'alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END']; var v = [v, 'case,done,elif,esac,eval,fi,function,in,local,set,then,until']; const K = /^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/; const N = /\S/; const O = u({
keywords: [F, H, w, `caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END${
I}`, J, v],
hashComments: !0,
cStyleComments: !0,
multiLineStrings: !0,
regexLiterals: !0,
}); var A = {}; k(O, ['default-code']); k(
x([], [['pln', /^[^<?]+/], ['dec', /^<!\w[^>]*(?:>|$)/], ['com', /^<\!--[\S\s]*?(?:--\>|$)/], ['lang-', /^<\?([\S\s]+?)(?:\?>|$)/], ['lang-', /^<%([\S\s]+?)(?:%>|$)/], ['pun', /^(?:<[%?]|[%?]>)/], ['lang-', /^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i], ['lang-js', /^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i], ['lang-css', /^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i], ['lang-in.tag', /^(<\/?[a-z][^<>]*>)/i]]),
['default-markup', 'htm', 'html', 'mxml', 'xhtml', 'xml', 'xsl'],
); k(x([['pln', /^\s+/, q, ' \t\r\n'], ['atv', /^(?:"[^"]*"?|'[^']*'?)/, q, "\"'"]], [['tag', /^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i], ['atn', /^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i], ['lang-uq.val', /^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/], ['pun', /^[/<->]+/], ['lang-js', /^on\w+\s*=\s*"([^"]+)"/i], ['lang-js', /^on\w+\s*=\s*'([^']+)'/i], ['lang-js', /^on\w+\s*=\s*([^\s"'>]+)/i], ['lang-css', /^style\s*=\s*"([^"]+)"/i], ['lang-css', /^style\s*=\s*'([^']+)'/i], ['lang-css',
/^style\s*=\s*([^\s"'>]+)/i]]), ['in.tag']); k(x([], [['atv', /^[\S\s]+/]]), ['uq.val']); k(u({
keywords: F, hashComments: !0, cStyleComments: !0, types: K,
}), ['c', 'cc', 'cpp', 'cxx', 'cyc', 'm']); k(u({ keywords: 'null,true,false' }), ['json']); k(u({
keywords: H, hashComments: !0, cStyleComments: !0, verbatimStrings: !0, types: K,
}), ['cs']); k(u({ keywords: G, cStyleComments: !0 }), ['java']); k(u({ keywords: v, hashComments: !0, multiLineStrings: !0 }), ['bsh', 'csh', 'sh']); k(
u({
keywords: I, hashComments: !0, multiLineStrings: !0, tripleQuotedStrings: !0,
}),
['cv', 'py'],
); k(u({
keywords: 'caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END', hashComments: !0, multiLineStrings: !0, regexLiterals: !0,
}), ['perl', 'pl', 'pm']); k(u({
keywords: J, hashComments: !0, multiLineStrings: !0, regexLiterals: !0,
}), ['rb']); k(u({ keywords: w, cStyleComments: !0, regexLiterals: !0 }), ['js']); k(u({
keywords: 'all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes',
hashComments: 3,
cStyleComments: !0,
multilineStrings: !0,
tripleQuotedStrings: !0,
regexLiterals: !0,
}), ['coffee']); k(x([], [['str', /^[\S\s]+/]]), ['regex']); window.prettyPrintOne = function (a, m, e) { const h = document.createElement('PRE'); h.innerHTML = a; e && D(h, e); E({ g: m, i: e, h }); return h.innerHTML; }; window.prettyPrint = function (a) {
function m() {
for (let e = window.PR_SHOULD_USE_CONTINUATION ? l.now() + 250 : Infinity; p < h.length && l.now() < e; p++) {
const n = h[p]; var k = n.className; if (k.indexOf('prettyprint') >= 0) {
var k = k.match(g); var f; var b; if (b = !k) { b = n; for (var o = void 0, c = b.firstChild; c; c = c.nextSibling) var i = c.nodeType, o = i === 1 ? o ? b : c : i === 3 ? N.test(c.nodeValue) ? b : o : o; b = (f = o === b ? void 0 : o) && f.tagName === 'CODE'; }b && (k = f.className.match(g)); k && (k = k[1]); b = !1; for (o = n.parentNode; o; o = o.parentNode) if ((o.tagName === 'pre' || o.tagName === 'code' || o.tagName === 'xmp') && o.className && o.className.indexOf('prettyprint') >= 0) { b = !0; break; }b || ((b = (b = n.className.match(/\blinenums\b(?::(\d+))?/)) ? b[1] && b[1].length ? +b[1] : !0 : !1) && D(n, b), d = { g: k, h: n, i: b }, E(d));
}
}p < h.length ? setTimeout(
m,
250,
) : a && a();
} for (var e = [document.getElementsByTagName('pre'), document.getElementsByTagName('code'), document.getElementsByTagName('xmp')], h = [], k = 0; k < e.length; ++k) for (let t = 0, s = e[k].length; t < s; ++t)h.push(e[k][t]); var e = q; var l = Date; l.now || (l = { now() { return +new Date(); } }); var p = 0; let d; var g = /\blang(?:uage)?-([\w.]+)(?!\S)/; m();
}; window.PR = {
createSimpleLexer: x,
registerLangHandler: k,
sourceDecorator: u,
PR_ATTRIB_NAME: 'atn',
PR_ATTRIB_VALUE: 'atv',
PR_COMMENT: 'com',
PR_DECLARATION: 'dec',
PR_KEYWORD: 'kwd',
PR_LITERAL: 'lit',
PR_NOCODE: 'nocode',
PR_PLAIN: 'pln',
PR_PUNCTUATION: 'pun',
PR_SOURCE: 'src',
PR_STRING: 'str',
PR_TAG: 'tag',
PR_TYPE: 'typ',
};
}());

2273
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "hack.chat-v2", "name": "hack.chat-v2",
"version": "2.2.0", "version": "2.2.1",
"type": "module", "type": "module",
"description": "a minimal distraction free chat application", "description": "a minimal distraction free chat application",
"main": "main.mjs", "main": "main.mjs",
@ -27,15 +27,16 @@
"test": "npm run lint && c8 mocha --exit ./test/*.test.js", "test": "npm run lint && c8 mocha --exit ./test/*.test.js",
"makedocs": "jsdoc -c jsdoc.json" "makedocs": "jsdoc -c jsdoc.json"
}, },
"author": "Marzavec", "author": "marzavec",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ascii-captcha": "^0.0.3",
"enquirer": "^2.3.6", "enquirer": "^2.3.6",
"hackchat-server": "^2.2.27", "hackchat-server": "^2.2.27",
"http-server": "^14.1.0", "http-server": "^14.1.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^9.0.2",
"lowdb": "^3.0.0", "lowdb": "^3.0.0",
"pm2": "^5.2.0" "pm2": "^5.3.0"
}, },
"devDependencies": { "devDependencies": {
"c8": "^7.11.0", "c8": "^7.11.0",

View File

@ -18,8 +18,6 @@ const SessionLocation = './session.key';
const SaltLocation = './salt.key'; const SaltLocation = './salt.key';
const AppConfigLocation = './config.json'; const AppConfigLocation = './config.json';
const TripLength = 10;
// default configuration options // default configuration options
const defaultConfig = { const defaultConfig = {
adminTrip: '', adminTrip: '',
@ -107,7 +105,7 @@ const checkPermissions = async () => {
const sha = crypto.createHash('sha256'); const sha = crypto.createHash('sha256');
sha.update(password + salt); sha.update(password + salt);
config.data.adminTrip = sha.digest('base64').substr(0, TripLength); config.data.adminTrip = sha.digest('base64').substr(0, 6);
await config.write(); await config.write();
} else { } else {

View File

@ -92,6 +92,20 @@ describe('Checking changenick module', () => {
expect(resp).to.be.true; expect(resp).to.be.true;
}); });
it('should prevent admin impersonation', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'changenick',
nick: 'admin',
},
});
expect(resp).to.be.true;
});
it('should not update if there is no change', async () => { it('should not update if there is no change', async () => {
const resp = await importedModule.run({ const resp = await importedModule.run({
core: mocks.core, core: mocks.core,

View File

@ -121,6 +121,23 @@ describe('Checking chat module', () => {
expect(resp).to.be.true; expect(resp).to.be.true;
}); });
it('should reject too long of customId', async () => {
const newPayload = { ...mockPayload };
newPayload.customId = '1234567890';
const newSocket = { ...mocks.plebSocket };
newSocket.color = '000000';
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: newSocket,
payload: newPayload,
});
expect(resp).to.be.false;
});
it('should initialize hooks', async () => { it('should initialize hooks', async () => {
expect(() => importedModule.initHooks(mocks.server)).not.to.throw(); expect(() => importedModule.initHooks(mocks.server)).not.to.throw();
}); });
@ -195,4 +212,15 @@ describe('Checking chat module', () => {
expect(resp).to.be.an('object'); expect(resp).to.be.an('object');
}); });
it('should cleanup old active messages', async () => {
importedModule.ACTIVE_MESSAGES.push({
customId: '1234',
userid: 1234,
sent: 0,
toDelete: false,
});
expect(() => importedModule.cleanActiveMessages()).not.to.throw();
});
}); });

100
test/disablecaptcha.test.js Normal file
View File

@ -0,0 +1,100 @@
import { expect } from 'chai';
import mocks from './mockImports.js';
const modulePath = '../commands/mod/disablecaptcha.js';
let importedModule;
const mockPayload = {
cmd: 'disablecaptcha',
}
const mockChannelPayload = {
cmd: 'disablecaptcha',
channel: 'test',
}
describe('Checking disablecaptcha module', () => {
// module meta data
it('should be importable', async () => {
importedModule = await import(modulePath);
expect(importedModule).to.not.be.a('string');
});
it('should be named', async () => {
expect(importedModule.info.name).to.be.a('string');
});
it('should be categorized', async () => {
expect(importedModule.info.category).to.be.a('string');
});
it('should be described', async () => {
expect(importedModule.info.description).to.be.a('string');
});
it('should be documented', async () => {
expect(importedModule.info.usage).to.be.a('string');
});
it('should be invokable', async () => {
expect(importedModule.run).to.be.a('function');
});
it('should initialize', async () => {
mocks.core.captchas = undefined;
const resp = importedModule.init(mocks.core);
expect(mocks.core.captchas).to.be.an('object');
});
// module main function
it('should be invokable only by a mod', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: mockPayload,
});
expect(resp).to.be.false;
});
it('should accept a channel param', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockChannelPayload,
});
expect(resp).to.be.true;
});
it('should accept no channel param', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockPayload,
});
expect(resp).to.be.true;
});
it('should fail on missing channel data', async () => {
const origChannel = mocks.authedSocket.channel;
mocks.authedSocket.channel = undefined;
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockPayload,
});
mocks.authedSocket.channel = origChannel;
expect(resp).to.be.false;
});
});

View File

@ -191,25 +191,30 @@ describe('Checking dumb module', () => {
expect(resp).to.be.false; expect(resp).to.be.false;
}); });
it('should shadow block invite attempts', async () => { it('should shadow block chat attempts, checking for allies', async () => {
mocks.core.muzzledHashes['testHash'] = true; mocks.core.muzzledHashes['testHash'] = {
dumb: true,
allies: [1234],
};
mocks.plebSocket.hcProtocol = 1; mocks.plebSocket.hcProtocol = 1;
const newSocket = Object.assign({}, mocks.plebSocket);
newSocket.hash = 'cantcatchme';
const resp = importedModule.inviteCheck({ const resp = importedModule.chatCheck({
core: mocks.core, core: mocks.core,
server: mocks.server, server: mocks.server,
socket: mocks.plebSocket, socket: newSocket,
payload: { payload: {
cmd: 'chat', cmd: 'chat',
text: [], text: 'test',
channel: 'cake', channel: 'cake',
}, },
}); });
expect(resp).to.be.true; expect(resp).to.be.an('object');
}); });
it('should shadow block invite attempts with ratelimiting', async () => { it('should ratelimit invites', async () => {
const oldRL = mocks.server.police.frisk; const oldRL = mocks.server.police.frisk;
mocks.server.police.frisk = () => true; mocks.server.police.frisk = () => true;
@ -221,9 +226,9 @@ describe('Checking dumb module', () => {
server: mocks.server, server: mocks.server,
socket: mocks.plebSocket, socket: mocks.plebSocket,
payload: { payload: {
cmd: 'chat', cmd: 'invite',
text: [], userid: 12,
channel: 'cake', channel: 'test',
}, },
}); });
@ -232,7 +237,7 @@ describe('Checking dumb module', () => {
expect(resp).to.be.true; expect(resp).to.be.true;
}); });
it('should verify userid params on shadow block invite attempts', async () => { it('should validate v2 clients userid param', async () => {
mocks.core.muzzledHashes['testHash'] = true; mocks.core.muzzledHashes['testHash'] = true;
mocks.plebSocket.hcProtocol = 2; mocks.plebSocket.hcProtocol = 2;
@ -241,17 +246,16 @@ describe('Checking dumb module', () => {
server: mocks.server, server: mocks.server,
socket: mocks.plebSocket, socket: mocks.plebSocket,
payload: { payload: {
cmd: 'chat', cmd: 'invite',
userid: '1234', userid: 'test',
text: [], channel: 1,
channel: 'cake',
}, },
}); });
expect(resp).to.be.true; expect(resp).to.be.true;
}); });
it('should verify channel params on shadow block invite attempts', async () => { it('should validate v2 clients channel param', async () => {
mocks.core.muzzledHashes['testHash'] = true; mocks.core.muzzledHashes['testHash'] = true;
mocks.plebSocket.hcProtocol = 2; mocks.plebSocket.hcProtocol = 2;
@ -260,16 +264,123 @@ describe('Checking dumb module', () => {
server: mocks.server, server: mocks.server,
socket: mocks.plebSocket, socket: mocks.plebSocket,
payload: { payload: {
cmd: 'chat', cmd: 'invite',
userid: 1234, userid: 12,
text: [], channel: 1,
channel: false,
}, },
}); });
expect(resp).to.be.true; expect(resp).to.be.true;
}); });
it('should give warning if user is missing', async () => {
const origFindSockets = mocks.server.findSockets;
mocks.server.findSockets = () => false;
mocks.core.muzzledHashes['testHash'] = true;
mocks.plebSocket.hcProtocol = 2;
const resp = importedModule.inviteCheck({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'invite',
userid: 12,
channel: 'test',
},
});
mocks.server.findSockets = origFindSockets;
expect(resp).to.be.true;
});
it('should handle v2 output', async () => {
const origFindSockets = mocks.server.findSockets;
mocks.server.findSockets = () => [mocks.plebSocket];
mocks.core.muzzledHashes['testHash'] = true;
mocks.plebSocket.hcProtocol = 2;
const resp = importedModule.inviteCheck({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'invite',
userid: 12,
channel: 'test',
},
});
mocks.server.findSockets = origFindSockets;
expect(resp).to.be.false;
});
it('should validate v1 clients channel', async () => {
const origPlebSocket = {...mocks.plebSocket};
mocks.core.muzzledHashes['testHash'] = true;
mocks.plebSocket.hcProtocol = 1;
mocks.plebSocket.channel = undefined;
const resp = importedModule.inviteCheck({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'invite',
nick: 'test',
channel: 1,
},
});
mocks.plebSocket = origPlebSocket;
expect(resp).to.be.true;
});
it('should validate v1 clients nick param', async () => {
const origPlebSocket = {...mocks.plebSocket};
mocks.core.muzzledHashes['testHash'] = true;
mocks.plebSocket.hcProtocol = 1;
const resp = importedModule.inviteCheck({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'invite',
nick: 0,
channel: 'test',
},
});
mocks.plebSocket = origPlebSocket;
expect(resp).to.be.true;
});
it('should handle v1 output', async () => {
const origPlebSocket = {...mocks.plebSocket};
mocks.core.muzzledHashes['testHash'] = true;
mocks.plebSocket.hcProtocol = 1;
const resp = importedModule.inviteCheck({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'invite',
nick: 'test',
channel: 'test',
},
});
mocks.plebSocket = origPlebSocket;
expect(resp).to.be.false;
});
it('should accept an allies param', async () => { it('should accept an allies param', async () => {
mockPayload.allies = [1234, 5678]; mockPayload.allies = [1234, 5678];
const resp = await importedModule.run({ const resp = await importedModule.run({

222
test/enablecaptcha.test.js Normal file
View File

@ -0,0 +1,222 @@
import { expect } from 'chai';
import mocks from './mockImports.js';
const modulePath = '../commands/mod/enablecaptcha.js';
let importedModule;
const targetChannel = 'test';
const mockPayload = {
cmd: 'enablecaptcha',
}
const mockChannelPayload = {
cmd: 'enablecaptcha',
channel: targetChannel,
}
const mockBadChatPayload = {
cmd: 'chat',
text: {},
}
const mockChatPayload = {
cmd: 'chat',
text: 'asdf',
}
const mockJoinPayload = {
cmd: 'join',
nick: 'test#test',
channel: 'test',
}
describe('Checking enablecaptcha module', () => {
// module meta data
it('should be importable', async () => {
importedModule = await import(modulePath);
expect(importedModule).to.not.be.a('string');
});
it('should be named', async () => {
expect(importedModule.info.name).to.be.a('string');
});
it('should be categorized', async () => {
expect(importedModule.info.category).to.be.a('string');
});
it('should be described', async () => {
expect(importedModule.info.description).to.be.a('string');
});
it('should be documented', async () => {
expect(importedModule.info.usage).to.be.a('string');
});
it('should be invokable', async () => {
expect(importedModule.run).to.be.a('function');
});
it('should initialize', async () => {
mocks.core.captchas = undefined;
const resp = importedModule.init(mocks.core);
expect(mocks.core.captchas).to.be.an('object');
});
// module main function
it('should be invokable only by a mod', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: mockPayload,
});
expect(resp).to.be.false;
});
it('should accept a channel param', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockChannelPayload,
});
expect(resp).to.be.true;
});
it('should accept no channel param', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockPayload,
});
expect(resp).to.be.true;
});
it('should fail on missing channel data', async () => {
const origChannel = mocks.authedSocket.channel;
mocks.authedSocket.channel = undefined;
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockPayload,
});
mocks.authedSocket.channel = origChannel;
expect(resp).to.be.false;
});
it('should fail if already enabled', async () => {
const origCaptchas = mocks.core.captchas;
mocks.core.captchas = { [mocks.authedSocket.channel]: true };
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockPayload,
});
mocks.core.captchas = origCaptchas;
expect(resp).to.be.true;
});
it('should initialize hooks', async () => {
expect(() => importedModule.initHooks(mocks.server)).not.to.throw();
});
it('should reject chat if not text', async () => {
const resp = await importedModule.chatCheck({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockBadChatPayload,
});
expect(resp).to.be.false;
});
it('should return if channel is not enabled', async () => {
const resp = await importedModule.chatCheck({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockChatPayload,
});
expect(resp).to.be.an('object');
});
it('should disconnect on failed captcha', async () => {
mocks.authedSocket.captcha = {};
mocks.authedSocket.captcha.awaiting = true;
const resp = await importedModule.chatCheck({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockChatPayload,
});
expect(resp).to.be.false;
});
it('should join with correct phrase', async () => {
mocks.authedSocket.captcha = {};
mocks.authedSocket.captcha.awaiting = true;
mocks.authedSocket.captcha.solution = mockChatPayload.text;
const resp = await importedModule.chatCheck({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockChatPayload,
});
expect(resp).to.be.false;
});
it('should handle legacy clients', async () => {
mocks.authedSocket.captcha = {};
mocks.authedSocket.captcha.awaiting = true;
mocks.authedSocket.captcha.solution = mockChatPayload.text;
const origProtocol = mocks.authedSocket.hcProtocol;
mocks.authedSocket.hcProtocol = 1;
const resp = await importedModule.chatCheck({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockChatPayload,
});
mocks.authedSocket.hcProtocol = origProtocol;
expect(resp).to.be.false;
});
it('should hook join commands', async () => {
mocks.core.captchas = {};
const resp = await importedModule.joinCheck({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockJoinPayload,
});
expect(resp).to.be.an('object');
});
});

View File

@ -139,6 +139,25 @@ describe('Checking join module', () => {
expect(resp).to.be.true; expect(resp).to.be.true;
}); });
it('should prevent admin impersonation', async () => {
const newSocket = Object.assign({}, mocks.authedSocket);
newSocket.channel = undefined;
newSocket.hcProtocol = undefined;
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: newSocket,
payload: {
cmd: 'join',
nick: 'admin#test',
channel: 'cake',
},
});
expect(resp).to.be.true;
});
it('should prevent two of the same name in the same channel', async () => { it('should prevent two of the same name in the same channel', async () => {
const newSocket = Object.assign({}, mocks.authedSocket); const newSocket = Object.assign({}, mocks.authedSocket);
newSocket.channel = undefined; newSocket.channel = undefined;

413
test/lockroom.test.js Normal file
View File

@ -0,0 +1,413 @@
import { expect } from 'chai';
import mocks from './mockImports.js';
const modulePath = '../commands/mod/lockroom.js';
let importedModule;
const targetChannel = 'test';
const mockPayload = {
cmd: 'lockroom',
}
const mockChannelPayload = {
cmd: 'lockroom',
channel: targetChannel,
}
const mockBadChatPayload = {
cmd: 'chat',
text: {},
}
const mockChatPayload = {
cmd: 'chat',
text: 'asdf',
}
const mockJoinPayload = {
cmd: 'join',
nick: 'test#test',
channel: 'test',
}
const timeout = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
}
describe('Checking lockroom module', () => {
// module meta data
it('should be importable', async () => {
importedModule = await import(modulePath);
expect(importedModule).to.not.be.a('string');
});
it('should be named', async () => {
expect(importedModule.info.name).to.be.a('string');
});
it('should be categorized', async () => {
expect(importedModule.info.category).to.be.a('string');
});
it('should be described', async () => {
expect(importedModule.info.description).to.be.a('string');
});
it('should be documented', async () => {
expect(importedModule.info.usage).to.be.a('string');
});
it('should be invokable', async () => {
expect(importedModule.run).to.be.a('function');
});
it('should initialize', async () => {
mocks.core.locked = undefined;
const resp = importedModule.init(mocks.core);
expect(mocks.core.locked).to.be.an('object');
});
// module main function
it('should be invokable only by a mod', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: mockPayload,
});
expect(resp).to.be.false;
});
it('should accept a channel param', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockChannelPayload,
});
expect(resp).to.be.true;
});
it('should accept no channel param', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockPayload,
});
expect(resp).to.be.true;
});
it('should fail on missing channel data', async () => {
const origChannel = mocks.authedSocket.channel;
mocks.authedSocket.channel = undefined;
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockPayload,
});
mocks.authedSocket.channel = origChannel;
expect(resp).to.be.false;
});
it('should fail if already enabled', async () => {
mocks.core.locked = { test: true };
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockChannelPayload,
});
mocks.core.locked = {};
expect(resp).to.be.true;
});
it('should initialize hooks', async () => {
expect(() => importedModule.initHooks(mocks.server)).not.to.throw();
});
// change nick hook checks
it('should prevent name changes in purgatory channel', async () => {
const origChannel = mocks.authedSocket.channel;
mocks.authedSocket.channel = 'purgatory';
const resp = await importedModule.changeNickCheck({
socket: mocks.authedSocket,
payload: {
cmd: 'changenick',
nick: 'test',
},
});
mocks.authedSocket.channel = origChannel;
expect(resp).to.be.false;
});
it('should ignore name changes in other channels', async () => {
const resp = await importedModule.changeNickCheck({
socket: mocks.authedSocket,
payload: {
cmd: 'changenick',
nick: 'test',
},
});
expect(resp).to.be.an('object');
});
// whisper hook checks
it('should prevent whispers in purgatory channel', async () => {
const origChannel = mocks.authedSocket.channel;
mocks.authedSocket.channel = 'purgatory';
const resp = await importedModule.whisperCheck({
socket: mocks.authedSocket,
payload: {
cmd: 'whisper',
},
});
mocks.authedSocket.channel = origChannel;
expect(resp).to.be.false;
});
it('should ignore whispers in other channels', async () => {
const resp = await importedModule.whisperCheck({
socket: mocks.authedSocket,
payload: {
cmd: 'whisper',
},
});
expect(resp).to.be.an('object');
});
// chat hook checks
it('should prevent chats in purgatory channel', async () => {
const plebSocket = { ...mocks.plebSocket };
plebSocket.channel = 'purgatory';
const resp = await importedModule.chatCheck({
socket: plebSocket,
payload: {
cmd: 'chat',
text: 'test',
},
});
expect(resp).to.be.false;
});
it('should allow mods to speak though', async () => {
const origChannel = mocks.authedSocket.channel;
mocks.authedSocket.channel = 'purgatory';
const resp = await importedModule.chatCheck({
socket: mocks.authedSocket,
payload: {
cmd: 'chat',
text: 'test',
},
});
mocks.authedSocket.channel = origChannel;
expect(resp).to.be.an('object');
});
it('should ignore chats in other channels', async () => {
const resp = await importedModule.chatCheck({
socket: mocks.authedSocket,
payload: {
cmd: 'chat',
text: 'test',
},
});
expect(resp).to.be.an('object');
});
// invite hook checks
it('should prevent invites in purgatory channel', async () => {
const plebSocket = { ...mocks.plebSocket };
plebSocket.channel = 'purgatory';
const resp = await importedModule.inviteCheck({
socket: plebSocket,
payload: {
cmd: 'chat',
text: 'test',
},
});
expect(resp).to.be.false;
});
it('should ignore invites in other channels', async () => {
const resp = await importedModule.inviteCheck({
socket: mocks.authedSocket,
payload: {
cmd: 'chat',
text: 'test',
},
});
expect(resp).to.be.an('object');
});
// join hook checks
it('should ignore join if no lock record', async () => {
mocks.core.locked = {};
const resp = await importedModule.joinCheck({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'join',
channel: 'test',
nick: 'test',
pass: 'test',
},
});
expect(resp).to.be.an('object');
});
it('should ignore join if not locked or purgatory', async () => {
mocks.core.locked = { test: false };
const resp = await importedModule.joinCheck({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'join',
channel: 'test',
nick: 'test',
pass: 'test',
},
});
expect(resp).to.be.an('object');
});
it('should allow v2 into purgatory', async () => {
const plebSocket = { ...mocks.plebSocket };
plebSocket.channel = 'used';
mocks.core.locked = {};
const resp = await importedModule.joinCheck({
core: mocks.core,
server: mocks.server,
socket: plebSocket,
payload: {
cmd: 'join',
channel: 'purgatory',
nick: 'test',
},
});
expect(resp).to.be.true;
});
it('should allow v1 into purgatory', async () => {
const plebSocket = { ...mocks.plebSocket };
plebSocket.channel = undefined;
plebSocket.hcProtocol = undefined;
mocks.core.locked = {};
const resp = await importedModule.joinCheck({
core: mocks.core,
server: mocks.server,
socket: plebSocket,
payload: {
cmd: 'join',
channel: 'purgatory',
nick: 'thisnameistoolongandwillberejected',
},
});
expect(resp).to.be.true;
});
it('should wait for the timeout to run', async () => {
const mockServer = { ...mocks.server };
const plebSocket = { ...mocks.plebSocket };
plebSocket.channel = undefined;
plebSocket.hcProtocol = undefined;
mocks.core.locked = {};
mockServer.reply = () => {};
await importedModule.joinCheck({
core: mocks.core,
server: mockServer,
socket: plebSocket,
payload: {
cmd: 'join',
channel: 'purgatory',
nick: 'test',
},
});
});
it('should do channel checking', async () => {
const plebSocket = { ...mocks.plebSocket };
plebSocket.channel = undefined;
plebSocket.hcProtocol = undefined;
plebSocket.banned = true;
mocks.core.locked = {};
const resp = await importedModule.joinCheck({
core: mocks.core,
server: mocks.server,
socket: plebSocket,
payload: {
cmd: 'join',
channel: 'purgatory',
nick: 'test',
pass: 'test',
},
});
expect(resp).to.be.true;
});
it('should use dante if not needed', async () => {
const mockServer = { ...mocks.server };
const plebSocket = { ...mocks.plebSocket };
plebSocket.channel = undefined;
plebSocket.hcProtocol = undefined;
mocks.core.locked = { lockedChan: true };
mockServer.reply = () => {};
const resp = await importedModule.joinCheck({
core: mocks.core,
server: mockServer,
socket: plebSocket,
payload: {
cmd: 'join',
channel: 'lockedChan',
nick: 'test',
},
});
});
});

View File

@ -1,6 +1,7 @@
const mocks = { const mocks = {
core: { core: {
sessionKey: 'test', sessionKey: 'test',
appConfig: { appConfig: {
data: { data: {
globalMods: [], globalMods: [],
@ -15,18 +16,23 @@ const mocks = {
}, },
write: async () => '', write: async () => '',
}, },
muzzledHashes: [], muzzledHashes: [],
stats: { stats: {
increment: () => 1, increment: () => 1,
decrement: () => 1, decrement: () => 1,
get: () => 1, get: () => 1,
set: () => 1, set: () => 1,
}, },
dynamicImports: { dynamicImports: {
reloadDirCache: () => '', reloadDirCache: () => '',
}, },
commands: { commands: {
reloadCommands: () => '', reloadCommands: () => '',
handleCommand: () => '',
commands: [], commands: [],
categoriesList: ['test'], categoriesList: ['test'],
all: () => [{ all: () => [{
@ -56,10 +62,12 @@ const mocks = {
} }
}, },
}, },
configManager: { configManager: {
save: () => true, save: () => true,
}, },
}, },
server : { server : {
police: { police: {
addresses: [], addresses: [],
@ -85,6 +93,7 @@ const mocks = {
}], }],
getSocketHash: () => 'test', getSocketHash: () => 'test',
}, },
plebSocket: { plebSocket: {
level: 100, level: 100,
address: '127.0.0.1', address: '127.0.0.1',
@ -97,6 +106,7 @@ const mocks = {
uType: 'user', uType: 'user',
userid: 1234, userid: 1234,
}, },
authedSocket: { authedSocket: {
level: 9999999, level: 9999999,
address: '127.0.0.1', address: '127.0.0.1',

View File

@ -68,4 +68,60 @@ describe('Checking speak module', () => {
expect(resp).to.be.true; expect(resp).to.be.true;
}); });
it('should accept payload.ip as a string', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: {
cmd: 'speak',
ip: '127.0.0.1',
},
});
expect(resp).to.be.true;
});
it('should accept payload.hash as a string', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: {
cmd: 'speak',
hash: 'pretendthisisahash',
},
});
expect(resp).to.be.true;
});
it('should unmuzzle all if payload.ip is *', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: {
cmd: 'speak',
ip: '*',
},
});
expect(resp).to.be.true;
});
it('should unmuzzle all if payload.hash is *', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: {
cmd: 'speak',
hash: '*',
},
});
expect(resp).to.be.true;
});
}); });

19
test/text.test.js Normal file
View File

@ -0,0 +1,19 @@
import { expect } from 'chai';
import mocks from './mockImports.js';
const modulePath = '../commands/utility/_Text.js';
let importedModule;
describe('Checking _Text module', () => {
// module meta data
it('should be importable', async () => {
importedModule = await import(modulePath);
expect(importedModule).to.not.be.a('string');
});
it('should return null if not text', () => {
const resp = importedModule.parseText([]);
expect(resp).to.be.null;
});
});

View File

@ -42,7 +42,7 @@ describe('Checking UAC module', () => {
it('should return admin level labels', async () => { it('should return admin level labels', async () => {
const newConfig = Object.assign({}, mocks.core.appConfig.data); const newConfig = Object.assign({}, mocks.core.appConfig.data);
newConfig.adminTrip = 'Tt8H7clbL9'; newConfig.adminTrip = 'Tt8H7c';
const resp = importedModule.getUserPerms('test', 'salt', newConfig, 'cake'); const resp = importedModule.getUserPerms('test', 'salt', newConfig, 'cake');
expect(resp).to.be.an('object'); expect(resp).to.be.an('object');
}); });
@ -50,7 +50,7 @@ describe('Checking UAC module', () => {
it('should return mod level labels', async () => { it('should return mod level labels', async () => {
const newConfig = Object.assign({}, mocks.core.appConfig.data); const newConfig = Object.assign({}, mocks.core.appConfig.data);
newConfig.globalMods = [{ newConfig.globalMods = [{
trip: 'Tt8H7clbL9', trip: 'Tt8H7c',
}]; }];
const resp = importedModule.getUserPerms('test', 'salt', newConfig, 'cake'); const resp = importedModule.getUserPerms('test', 'salt', newConfig, 'cake');
expect(resp).to.be.an('object'); expect(resp).to.be.an('object');

113
test/unlockroom.test.js Normal file
View File

@ -0,0 +1,113 @@
import { expect } from 'chai';
import mocks from './mockImports.js';
const modulePath = '../commands/mod/unlockroom.js';
let importedModule;
const mockPayload = {
cmd: 'unlockroom',
}
const mockChannelPayload = {
cmd: 'unlockroom',
channel: 'test',
}
describe('Checking unlockroom module', () => {
// module meta data
it('should be importable', async () => {
importedModule = await import(modulePath);
expect(importedModule).to.not.be.a('string');
});
it('should be named', async () => {
expect(importedModule.info.name).to.be.a('string');
});
it('should be categorized', async () => {
expect(importedModule.info.category).to.be.a('string');
});
it('should be described', async () => {
expect(importedModule.info.description).to.be.a('string');
});
it('should be documented', async () => {
expect(importedModule.info.usage).to.be.a('string');
});
it('should be invokable', async () => {
expect(importedModule.run).to.be.a('function');
});
it('should initialize', async () => {
mocks.core.locked = undefined;
const resp = importedModule.init(mocks.core);
expect(mocks.core.locked).to.be.an('object');
});
// module main function
it('should be invokable only by a mod', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: mockPayload,
});
expect(resp).to.be.false;
});
it('should accept a channel param', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockChannelPayload,
});
expect(resp).to.be.true;
});
it('should accept no channel param', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockPayload,
});
expect(resp).to.be.true;
});
it('should fail on missing channel data', async () => {
const origChannel = mocks.authedSocket.channel;
mocks.authedSocket.channel = undefined;
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockPayload,
});
mocks.authedSocket.channel = origChannel;
expect(resp).to.be.false;
});
it('should unlock if locked', async () => {
mocks.core.locked = { [mocks.authedSocket.channel]: true };
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.authedSocket,
payload: mockPayload,
});
expect(resp).to.be.true;
});
});

233
test/updateMessage.test.js Normal file
View File

@ -0,0 +1,233 @@
import { expect } from 'chai';
import mocks from './mockImports.js';
const modulePath = '../commands/core/updateMessage.js';
let importedModule;
const mockPayload = {
cmd: 'updateMessage',
}
const mockChannelPayload = {
cmd: 'updateMessage',
channel: 'test',
}
describe('Checking unlockroom module', () => {
// module meta data
it('should be importable', async () => {
importedModule = await import(modulePath);
expect(importedModule).to.not.be.a('string');
});
it('should be named', async () => {
expect(importedModule.info.name).to.be.a('string');
});
it('should be categorized', async () => {
expect(importedModule.info.category).to.be.a('string');
});
it('should be described', async () => {
expect(importedModule.info.description).to.be.a('string');
});
it('should be documented', async () => {
expect(importedModule.info.usage).to.be.a('string');
});
it('should be invokable', async () => {
expect(importedModule.run).to.be.a('function');
});
// module main function
it('should default the mode param if missing', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'updateMessage',
customId: '1234',
text: 'test',
},
});
expect(resp).to.be.false;
});
it('should reject if mode is invalid', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'updateMessage',
customId: '1234',
text: 'test',
mode: 'poop',
},
});
expect(resp).to.be.false;
});
it('should reject if customId is missing', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'updateMessage',
text: 'test',
mode: 'overwrite',
},
});
expect(resp).to.be.false;
});
it('should reject if customId is not text', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'updateMessage',
text: 'test',
customId: {},
mode: 'overwrite',
},
});
expect(resp).to.be.false;
});
it('should reject if customId is not too long', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'updateMessage',
text: 'test',
customId: `A`.repeat(importedModule.MAX_MESSAGE_ID_LENGTH * 2),
mode: 'overwrite',
},
});
expect(resp).to.be.false;
});
it('should reject if text is not text', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'updateMessage',
text: {},
customId: `A`,
mode: 'overwrite',
},
});
expect(resp).to.be.false;
});
it('should change text to null if empty', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'updateMessage',
text: '',
customId: `A`,
mode: 'overwrite',
},
});
expect(resp).to.be.false;
});
it('should otherwise reject empty text', async () => {
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'updateMessage',
text: '',
customId: `A`,
mode: 'prepend',
},
});
expect(resp).to.be.false;
});
it('should delete active message records', async () => {
const chatModule = await import('../commands/core/chat.js');
chatModule.ACTIVE_MESSAGES.push({
customId: 'asdf',
userid: 1234,
sent: 0,
toDelete: false,
});
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: mocks.plebSocket,
payload: {
cmd: 'updateMessage',
text: 'a',
customId: 'asdf',
mode: 'complete',
},
});
expect(resp).to.be.true;
});
it('should mark if sent by mod', async () => {
const newSocket = { ...mocks.authedSocket };
newSocket.level = 999999;
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: newSocket,
payload: {
cmd: 'updateMessage',
text: 'a',
customId: 'asdf',
mode: 'append',
},
});
expect(resp).to.be.true;
});
it('should mark if sent by admin', async () => {
const newSocket = { ...mocks.authedSocket };
newSocket.level = 9999999;
const resp = await importedModule.run({
core: mocks.core,
server: mocks.server,
socket: newSocket,
payload: {
cmd: 'updateMessage',
text: 'a',
customId: 'asdf',
mode: 'append',
},
});
expect(resp).to.be.true;
});
});