Typescript Daily Spotlight, RoomFAQs, and Wi-Fi (#6373)

This commit is contained in:
Kris Johnson 2020-02-18 14:26:44 -07:00 committed by GitHub
parent d6fffde560
commit 2ea84725e2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 313 additions and 390 deletions

View File

@ -33,16 +33,22 @@ const EVASION_DETECTION_SUBSTITUTIONS: {[k: string]: string[]} = {
e: ["e", "3", "é", "ê", "E", "ⓔ", "Ⓔ", "є", "͏", "Ɇ", "ệ", "Ệ", "Ꮛ", "ε", "Σ", "ḕ", "Ḕ", "", "ɛ", "̾", "", "", "ᴇ", "ǝ", "🅔", "𝐞", "𝐄", "𝘦", "𝘌", "𝙚", "𝙀", "", "𝓮", "𝓔", "𝕖", "𝔻", "𝔢", "𝔇", "𝖊", "𝕰", "🄴", "🅴", "𝑒", "𝐸", "ҽ", "𝚎", "𝙴", "€", "е"],
f: ["f", "", "F", "ⓕ", "Ⓕ", "₣", "ḟ", "Ḟ", "Ꭶ", "ғ", "ʄ", "", "", "ɟ", "🅕", "𝐟", "𝐅", "𝘧", "𝘍", "𝙛", "𝙁", "𝒻", "𝓯", "𝓕", "𝕗", "𝔼", "𝔣", "𝔈", "𝖋", "𝕱", "🄵", "🅵", "𝐹", "ϝ", "𝚏", "𝙵", "Ϝ", "f"],
g: ["g", "q", "6", "9", "G", "ⓖ", "Ⓖ", "͏", "₲", "ġ", "Ġ", "Ꮆ", "ϑ", "Ḡ", "ɢ", "̾", "", "", "ƃ", "🅖", "𝐠", "𝐆", "𝘨", "𝘎", "𝙜", "𝙂", "", "𝓰", "𝓖", "𝕘", "𝔽", "𝔤", "𝔉", "𝖌", "𝕲", "🄶", "🅶", "𝑔", "𝒢", "ɠ", "𝚐", "𝙶", "❡", "ց"],
h: ["h", "", "H", "ⓗ", "Ⓗ", "н", "Ⱨ", "ḧ", "Ḧ", "", "ɦ", "", "", "ʜ", "ɥ", "🅗", "𝐡", "𝐇", "𝘩", "𝘏", "𝙝", "𝙃", "𝒽", "𝓱", "𝓗", "𝕙", "𝔾", "𝔥", "𝔊", "𝖍", "𝕳", "🄷", "🅷", "𝐻", "ԋ", "𝚑", "𝙷", "♄", "h"],
h: [
"h", "", "H", "ⓗ", "Ⓗ", "н", "Ⱨ", "ḧ", "Ḧ", "", "ɦ", "", "", "ʜ", "ɥ", "🅗", "𝐡", "𝐇", "𝘩", "𝘏", "𝙝", "𝙃", "𝒽", "𝓱", "𝓗", "𝕙", "𝔾", "𝔥", "𝔊", "𝖍", "𝕳", "🄷", "🅷", "𝐻", "ԋ", "𝚑", "𝙷", "♄", "h",
],
i: ["i", "!", "l", "1", "í", "I", "ⓘ", "Ⓘ", "ι", "͏", "ł", "ï", "Ï", "", "ḭ", "Ḭ", "ɨ", "̾", "", "", "ɪ", "ı", "🅘", "𝐢", "𝐈", "𝘪", "𝘐", "𝙞", "𝙄", "𝒾", "𝓲", "𝓘", "𝕚", "", "𝔦", "", "𝖎", "𝕴", "🄸", "🅸", "𝐼", "𝚒", "𝙸", "♗", "і", "¡", "|"],
j: ["j", "", "J", "ⓙ", "Ⓙ", "נ", "Ꮰ", "ϳ", "ʝ", "", "", "ᴊ", "ɾ", "🅙", "𝐣", "𝐉", "𝘫", "𝘑", "𝙟", "𝙅", "𝒿", "𝓳", "𝓙", "𝕛", "", "𝔧", "𝖏", "𝕵", "🄹", "🅹", "𝒥", "𝚓", "𝙹", "♪", "ј"],
k: ["k", "K", "ⓚ", "Ⓚ", "к", "͏", "₭", "ḳ", "Ḳ", "", "κ", "Ƙ", "ӄ", "̾", "", "", "ᴋ", "ʞ", "🅚", "𝐤", "𝐊", "𝘬", "𝘒", "𝙠", "𝙆", "𝓀", "𝓴", "𝓚", "𝕜", "𝕀", "𝔨", "", "𝖐", "𝕶", "🄺", "🅺", "𝒦", "ƙ", "𝚔", "𝙺", "ϰ", "k"],
l: ["l", "i", "1", "/", "|", "", "L", "ⓛ", "Ⓛ", "", "Ⱡ", "ŀ", "Ŀ", "Ꮭ", "Ḷ", "", "ʟ", "", "", "🅛", "𝐥", "𝐋", "𝘭", "𝘓", "𝙡", "𝙇", "𝓁", "𝓵", "𝓛", "𝕝", "𝕁", "𝔩", "", "𝖑", "𝕷", "🄻", "🅻", "𝐿", "ʅ", "𝚕", "𝙻", "↳", ""],
m: ["m", "", "M", "ⓜ", "Ⓜ", "м", "͏", "₥", "ṃ", "Ṃ", "", "ϻ", "Μ", "ṁ", "Ṁ", "ʍ", "̾", "", "", "ᴍ", "ɯ", "🅜", "𝐦", "𝐌", "𝘮", "𝘔", "𝙢", "𝙈", "𝓂", "𝓶", "𝓜", "𝕞", "𝕂", "𝔪", "𝔍", "𝖒", "𝕸", "🄼", "🅼", "𝑀", "ɱ", "𝚖", "𝙼", "♔", "ⅿ"],
m: [
"m", "", "M", "ⓜ", "Ⓜ", "м", "͏", "₥", "ṃ", "Ṃ", "", "ϻ", "Μ", "ṁ", "Ṁ", "ʍ", "̾", "", "", "ᴍ", "ɯ", "🅜", "𝐦", "𝐌", "𝘮", "𝘔", "𝙢", "𝙈", "𝓂", "𝓶", "𝓜", "𝕞", "𝕂", "𝔪", "𝔍", "𝖒", "𝕸", "🄼", "🅼", "𝑀", "ɱ", "𝚖", "𝙼", "♔", "ⅿ",
],
n: ["n", "ñ", "ᑎ", "N", "ⓝ", "Ⓝ", "и", "₦", "ń", "Ń", "Ꮑ", "π", "∏", "Ṇ", "ռ", "", "", "ɴ", "🅝", "𝐧", "𝐍", "𝘯", "𝘕", "𝙣", "𝙉", "𝓃", "𝓷", "𝓝", "𝕟", "𝕃", "𝔫", "𝔎", "𝖓", "𝕹", "🄽", "🅽", "𝒩", "ɳ", "𝚗", "𝙽", "♫", "ո"],
o: ["o", "0", "ó", "ô", "õ", "ú", "O", "ⓞ", "Ⓞ", "σ", "͏", "Ø", "ö", "Ö", "Ꭷ", "Θ", "ṏ", "Ṏ", "Ꮎ", "օ", "̾", "", "", "", "🅞", "𝐨", "𝐎", "𝘰", "𝘖", "𝙤", "𝙊", "", "𝓸", "𝓞", "𝕠", "𝕄", "𝔬", "𝔏", "𝖔", "𝕺", "🄾", "🅾", "𝑜", "𝒪", "𝚘", "𝙾", "⊙", "ο"],
p: ["p", "", "P", "ⓟ", "Ⓟ", "ρ", "₱", "ṗ", "Ṗ", "", "Ƥ", "", "ք", "", "", "ᴘ", "🅟", "𝐩", "𝐏", "𝘱", "𝘗", "𝙥", "𝙋", "𝓅", "𝓹", "𝓟", "𝕡", "", "𝔭", "𝔐", "𝖕", "𝕻", "🄿", "🅿", "𝒫", "𝚙", "𝙿", "р"],
q: ["q", "ᑫ", "Q", "ⓠ", "Ⓠ", "͏", "Ꭴ", "φ", "Ⴓ", "զ", "̾", "", "", "ϙ", "ǫ", "🅠", "𝐪", "𝐐", "𝘲", "𝘘", "𝙦", "𝙌", "𝓆", "𝓺", "𝓠", "𝕢", "", "𝔮", "𝔑", "𝖖", "𝕼", "🅀", "🆀", "𝒬", "𝚚", "𝚀", "☭", "ԛ"],
q: [
"q", "ᑫ", "Q", "ⓠ", "Ⓠ", "͏", "Ꭴ", "φ", "Ⴓ", "զ", "̾", "", "", "ϙ", "ǫ", "🅠", "𝐪", "𝐐", "𝘲", "𝘘", "𝙦", "𝙌", "𝓆", "𝓺", "𝓠", "𝕢", "", "𝔮", "𝔑", "𝖖", "𝕼", "🅀", "🆀", "𝒬", "𝚚", "𝚀", "☭", "ԛ",
],
r: ["r", "", "R", "ⓡ", "Ⓡ", "я", "Ɽ", "ŕ", "Ŕ", "", "г", "Γ", "ṙ", "Ṙ", "ʀ", "", "", "ɹ", "🅡", "𝐫", "𝐑", "𝘳", "𝘙", "𝙧", "𝙍", "𝓇", "𝓻", "𝓡", "𝕣", "𝕆", "𝔯", "𝔒", "𝖗", "𝕽", "🅁", "🆁", "𝑅", "ɾ", "𝚛", "𝚁", "☈", "r"],
s: ["s", "5", "ᔕ", "S", "ⓢ", "Ⓢ", "ѕ", "͏", "₴", "ṩ", "Ṩ", "", "Ѕ", "Ṡ", "ֆ", "̾", "", "", "", "🅢", "𝐬", "𝐒", "𝘴", "𝘚", "𝙨", "𝙎", "𝓈", "𝓼", "𝓢", "𝕤", "", "𝔰", "𝔓", "𝖘", "𝕾", "🅂", "🆂", "𝒮", "ʂ", "𝚜", "𝚂", "ѕ"],
t: ["t", "+", "T", "ⓣ", "Ⓣ", "т", "₮", "ẗ", "Ṯ", "Ꮦ", "τ", "Ƭ", "Ꮖ", "ȶ", "", "", "ᴛ", "ʇ", "🅣", "𝐭", "𝐓", "𝘵", "𝘛", "𝙩", "𝙏", "𝓉", "𝓽", "𝓣", "𝕥", "", "𝔱", "𝔔", "𝖙", "𝕿", "🅃", "🆃", "𝒯", "ƚ", "𝚝", "𝚃", "☂", "t"],
@ -50,7 +56,9 @@ const EVASION_DETECTION_SUBSTITUTIONS: {[k: string]: string[]} = {
v: ["v", "", "V", "ⓥ", "Ⓥ", "ν", "ṿ", "Ṿ", "Ꮙ", "Ʋ", "Ṽ", "ʋ", "", "", "", "ʌ", "🅥", "𝐯", "𝐕", "𝘷", "𝘝", "𝙫", "𝙑", "𝓋", "𝓿", "𝓥", "𝕧", "", "𝔳", "𝖛", "𝖁", "🅅", "🆅", "𝒱", "𝚟", "𝚅", "✓", ""],
w: ["w", "ᗯ", "W", "ⓦ", "Ⓦ", "ω", "͏", "₩", "ẅ", "Ẅ", "Ꮗ", "ш", "Ш", "ẇ", "Ẇ", "", "ա", "̾", "", "", "", "ʍ", "🅦", "𝐰", "𝐖", "𝘸", "𝘞", "𝙬", "𝙒", "𝓌", "𝔀", "𝓦", "𝕨", "", "𝔴", "𝔖", "𝖜", "𝖂", "🅆", "🆆", "𝒲", "ɯ", "𝚠", "𝚆", "ԝ"],
x: ["x", "", "X", "ⓧ", "Ⓧ", "χ", "Ӿ", "ẍ", "Ẍ", "ጀ", "ϰ", "Ж", "х", "Ӽ", "", "", "🅧", "𝐱", "𝐗", "𝘹", "𝘟", "𝙭", "𝙓", "𝓍", "𝔁", "𝓧", "𝕩", "", "𝔵", "𝔗", "𝖝", "𝖃", "🅇", "🆇", "𝒳", "𝚡", "𝚇", "⌘", "х"],
y: ["y", "Y", "ⓨ", "Ⓨ", "у", "͏", "Ɏ", "ÿ", "Ÿ", "", "ψ", "Ψ", "ẏ", "Ẏ", "", "ч", "ʏ", "̾", "", "", "ʎ", "🅨", "𝐲", "𝐘", "𝘺", "𝘠", "𝙮", "𝙔", "𝓎", "𝔂", "𝓨", "𝕪", "𝕊", "𝔶", "𝔘", "𝖞", "𝖄", "🅈", "🆈", "𝒴", "", "𝚢", "𝚈", "☿", "у"],
y: [
"y", "Y", "ⓨ", "Ⓨ", "у", "͏", "Ɏ", "ÿ", "Ÿ", "", "ψ", "Ψ", "ẏ", "Ẏ", "", "ч", "ʏ", "̾", "", "", "ʎ", "🅨", "𝐲", "𝐘", "𝘺", "𝘠", "𝙮", "𝙔", "𝓎", "𝔂", "𝓨", "𝕪", "𝕊", "𝔶", "𝔘", "𝖞", "𝖄", "🅈", "🆈", "𝒴", "", "𝚢", "𝚈", "☿", "у",
],
z: ["z", "ᘔ", "Z", "ⓩ", "Ⓩ", "Ⱬ", "ẓ", "Ẓ", "ፚ", "", "ʐ", "", "", "", "🅩", "𝐳", "𝐙", "𝘻", "𝘡", "𝙯", "𝙕", "𝓏", "𝔃", "𝓩", "𝕫", "𝕋", "𝔷", "𝔙", "𝖟", "𝖅", "🅉", "🆉", "𝒵", "ȥ", "𝚣", "𝚉", "☡", "z"],
};
@ -77,7 +85,9 @@ function saveFilters(force = false) {
FS(MONITOR_FILE).writeUpdate(() => {
let buf = 'Location\tWord\tPunishment\tReason\tTimes\r\n';
for (const key in Chat.monitors) {
buf += filterWords[key].map(word => renderEntry(Chat.monitors[key].location, word, Chat.monitors[key].punishment)).join('');
buf += filterWords[key].map(
word => renderEntry(Chat.monitors[key].location, word, Chat.monitors[key].punishment)
).join('');
}
return buf;
}, {throttle: force ? 0 : WRITE_THROTTLE_TIME});
@ -274,7 +284,14 @@ void FS(MONITOR_FILE).readIfExists().then(data => {
export const chatfilter: ChatFilter = function (message, user, room) {
// eslint-disable-next-line no-misleading-character-class
let lcMessage = message.replace(/\u039d/g, 'N').toLowerCase().replace(/[\u200b\u007F\u00AD\uDB40\uDC00\uDC21]/g, '').replace(/\u03bf/g, 'o').replace(/\u043e/g, 'o').replace(/\u0430/g, 'a').replace(/\u0435/g, 'e').replace(/\u039d/g, 'e');
let lcMessage = message
.replace(/\u039d/g, 'N').toLowerCase()
.replace(/[\u200b\u007F\u00AD\uDB40\uDC00\uDC21]/g, '')
.replace(/\u03bf/g, 'o')
.replace(/\u043e/g, 'o')
.replace(/\u0430/g, 'a')
.replace(/\u0435/g, 'e')
.replace(/\u039d/g, 'e');
lcMessage = lcMessage.replace(/__|\*\*|``|\[\[|\]\]/g, '');
const isStaffRoom = room && ((room.chatRoomData && room.roomid.endsWith('staff')) || room.roomid.startsWith('help-'));
@ -318,7 +335,14 @@ export const namefilter: NameFilter = (name, user) => {
const id = toID(name);
if (Chat.namefilterwhitelist.has(id)) return name;
if (id === toID(user.trackRename)) return '';
let lcName = name.replace(/\u039d/g, 'N').toLowerCase().replace(/[\u200b\u007F\u00AD]/g, '').replace(/\u03bf/g, 'o').replace(/\u043e/g, 'o').replace(/\u0430/g, 'a').replace(/\u0435/g, 'e').replace(/\u039d/g, 'e');
let lcName = name
.replace(/\u039d/g, 'N').toLowerCase()
.replace(/[\u200b\u007F\u00AD]/g, '')
.replace(/\u03bf/g, 'o')
.replace(/\u043e/g, 'o')
.replace(/\u0430/g, 'a')
.replace(/\u0435/g, 'e')
.replace(/\u039d/g, 'e');
// Remove false positives.
lcName = lcName.replace('herapist', '').replace('grape', '').replace('scrape', '');
@ -355,7 +379,14 @@ export const loginfilter: LoginFilter = user => {
}
};
export const nicknamefilter: NameFilter = (name, user) => {
let lcName = name.replace(/\u039d/g, 'N').toLowerCase().replace(/[\u200b\u007F\u00AD]/g, '').replace(/\u03bf/g, 'o').replace(/\u043e/g, 'o').replace(/\u0430/g, 'a').replace(/\u0435/g, 'e').replace(/\u039d/g, 'e');
let lcName = name
.replace(/\u039d/g, 'N').toLowerCase()
.replace(/[\u200b\u007F\u00AD]/g, '')
.replace(/\u03bf/g, 'o')
.replace(/\u043e/g, 'o')
.replace(/\u0430/g, 'a')
.replace(/\u0435/g, 'e')
.replace(/\u039d/g, 'e');
// Remove false positives.
lcName = lcName.replace('herapist', '').replace('grape', '').replace('scrape', '');
@ -378,7 +409,14 @@ export const nicknamefilter: NameFilter = (name, user) => {
return name;
};
export const statusfilter: StatusFilter = (status, user) => {
let lcStatus = status.replace(/\u039d/g, 'N').toLowerCase().replace(/[\u200b\u007F\u00AD]/g, '').replace(/\u03bf/g, 'o').replace(/\u043e/g, 'o').replace(/\u0430/g, 'a').replace(/\u0435/g, 'e').replace(/\u039d/g, 'e');
let lcStatus = status
.replace(/\u039d/g, 'N').toLowerCase()
.replace(/[\u200b\u007F\u00AD]/g, '')
.replace(/\u03bf/g, 'o')
.replace(/\u043e/g, 'o')
.replace(/\u0430/g, 'a')
.replace(/\u0435/g, 'e')
.replace(/\u039d/g, 'e');
// Remove false positives.
lcStatus = lcStatus.replace('herapist', '').replace('grape', '').replace('scrape', '');
// Check for blatant staff impersonation attempts. Ideally this could be completely generated from Config.grouplist
@ -473,7 +511,10 @@ export const commands: ChatCommands = {
if (Chat.monitors[list].punishment === 'EVASION') {
regex = constructEvasionRegex(word);
} else {
regex = new RegExp(Chat.monitors[list].punishment === 'SHORTENER' ? `\\b${word}` : word, filterTo ? 'ig' : 'i'); // eslint-disable-line no-unused-vars
regex = new RegExp(
Chat.monitors[list].punishment === 'SHORTENER' ? `\\b${word}` : word,
filterTo ? 'ig' : 'i'
);
}
} catch (e) {
return this.errorReply(e.message.startsWith('Invalid regular expression: ') ? e.message : `Invalid regular expression: /${word}/: ${e.message}`);

View File

@ -1,15 +1,14 @@
'use strict';
/** @type {typeof import('../../lib/fs').FS} */
const FS = require(/** @type {any} */('../../.lib-dist/fs')).FS;
import {FS} from '../../lib/fs';
import * as fs from 'fs';
const DAY = 24 * 60 * 60 * 1000;
const SPOTLIGHT_FILE = 'config/chat-plugins/spotlights.json';
/** @type {{[k: string]: {[k: string]: {image: string?, description: string}[]}}} */
let spotlights = {};
let spotlights: {[k: string]: {[k: string]: {image?: string, description: string}[]}} = {};
try {
spotlights = require(`../../${SPOTLIGHT_FILE}`);
spotlights = JSON.parse(fs.readFileSync(`../../${SPOTLIGHT_FILE}`).toString());
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND' && e.code !== 'ENOENT') throw e;
}
@ -36,11 +35,7 @@ const midnight = new Date();
midnight.setHours(24, 0, 0, 0);
let timeout = setTimeout(nextDaily, midnight.valueOf() - Date.now());
/**
* @param {string?} image
* @param {string} description
*/
async function renderSpotlight(image, description) {
async function renderSpotlight(description: string, image?: string) {
let imgHTML = '';
if (image) {
@ -51,12 +46,11 @@ async function renderSpotlight(image, description) {
return `<table style="text-align:center;margin:auto"><tr><td style="padding-right:10px;">${Chat.formatText(description, true).replace(/\n/g, `<br />`)}</td>${imgHTML}</tr></table>`;
}
exports.destroy = function () {
export const destroy = () => {
clearTimeout(timeout);
};
/** @type {PageTable} */
const pages = {
export const pages: PageTable = {
async spotlights(query, user, connection) {
this.title = 'Daily Spotlights';
this.extractRoom();
@ -64,10 +58,10 @@ const pages = {
if (!spotlights[this.room.roomid]) {
buf += `<p>This room has no daily spotlights.</p></div>`;
} else {
for (let key in spotlights[this.room.roomid]) {
for (const key in spotlights[this.room.roomid]) {
buf += `<table style="margin-bottom:30px;"><th colspan="2"><h3>${key}:</h3></th>`;
for (const [i, spotlight] of spotlights[this.room.roomid][key].entries()) {
const html = await renderSpotlight(spotlight.image, spotlight.description);
const html = await renderSpotlight(spotlight.description, spotlight.image);
buf += `<tr><td>${i ? i : 'Current'}</td><td>${html}</td></tr>`;
// @ts-ignore room is definitely a proper room here.
if (!user.can('announce', null, this.room)) break;
@ -78,10 +72,8 @@ const pages = {
return buf;
},
};
exports.pages = pages;
/** @type {ChatCommands} */
const commands = {
export const commands: ChatCommands = {
async removedaily(target, room, user) {
if (!room.chatRoomData) return this.errorReply("This command is unavailable in temporary rooms.");
let [key, rest] = target.split(',');
@ -122,27 +114,30 @@ const commands = {
if (!this.can('announce', null, room)) return false;
if (!rest.length) return this.parse('/help daily');
let image, description;
let img;
let desc;
if (rest[0].trim().startsWith('http://') || rest[0].trim().startsWith('https://')) {
[image, ...rest] = rest;
image = image.trim();
const ret = await Chat.getImageDimensions(image);
if (ret.err) return this.errorReply(`Invalid image url: ${image}`);
[img, ...rest] = rest;
img = img.trim();
const ret = await Chat.getImageDimensions(img);
if (ret.err) return this.errorReply(`Invalid image url: ${img}`);
}
description = rest.join(',');
if (Chat.stripFormatting(description).length > 500) return this.errorReply("Descriptions can be at most 500 characters long.");
const obj = {image: image || null, description: description};
desc = rest.join(',');
if (Chat.stripFormatting(desc).length > 500) {
return this.errorReply("Descriptions can be at most 500 characters long.");
}
const obj = {image: img, description: desc};
if (!spotlights[room.roomid]) spotlights[room.roomid] = {};
if (!spotlights[room.roomid][key]) spotlights[room.roomid][key] = [];
if (cmd === 'setdaily') {
spotlights[room.roomid][key].shift();
spotlights[room.roomid][key].unshift(obj);
this.modlog('SETDAILY', key, `${image ? `${image}, ` : ''}${description}`);
this.modlog('SETDAILY', key, `${img ? `${img}, ` : ''}${desc}`);
this.privateModAction(`${user.name} set the daily ${key}.`);
} else {
spotlights[room.roomid][key].push(obj);
this.modlog('QUEUEDAILY', key, `${image ? `${image}, ` : ''}${description}`);
this.modlog('QUEUEDAILY', key, `${img ? `${img}, ` : ''}${desc}`);
this.privateModAction(`${user.name} queued a daily ${key}.`);
}
@ -150,7 +145,7 @@ const commands = {
},
async daily(target, room, user) {
if (!room.chatRoomData) return this.errorReply("This command is unavailable in temporary rooms.");
let key = toID(target);
const key = toID(target);
if (!key) return this.parse('/help daily');
if (!spotlights[room.roomid] || !spotlights[room.roomid][key]) {
@ -160,7 +155,7 @@ const commands = {
if (!this.runBroadcast()) return;
const {image, description} = spotlights[room.roomid][key][0];
const html = await renderSpotlight(image, description);
const html = await renderSpotlight(description, image);
this.sendReplyBox(html);
room.update();
@ -181,8 +176,6 @@ const commands = {
],
};
exports.commands = commands;
process.nextTick(() => {
Chat.multiLinePattern.register('/(queue|set)daily ');
});

View File

@ -1,15 +1,14 @@
'use strict';
/** @type {typeof import('../../lib/fs').FS} */
const FS = require(/** @type {any} */('../../.lib-dist/fs')).FS;
import {FS} from '../../lib/fs';
import * as fs from 'fs';
const ROOMFAQ_FILE = 'config/chat-plugins/faqs.json';
const MAX_ROOMFAQ_LENGTH = 8192;
/** @type {{[k: string]: {[k: string]: string}}} */
let roomFaqs = {};
let roomFaqs: {[k: string]: {[k: string]: string}} = {};
try {
roomFaqs = require(`../../${ROOMFAQ_FILE}`);
roomFaqs = JSON.parse(fs.readFileSync(`../../${ROOMFAQ_FILE}`).toString());
} catch (e) {
if (e.code !== 'MODULE_NOT_FOUND' && e.code !== 'ENOENT') throw e;
}
@ -20,28 +19,25 @@ function saveRoomFaqs() {
}
/**
* @param {RoomID} roomid
* @param {string} key
*
* Aliases are implemented as a "regular" FAQ entry starting with a >. EX: {a: "text", b: ">a"}
* This is done to allow easy checking whether a key is associated with a value or alias as well as preserve backwards compatibility.
* This is done to allow easy checking whether a key is associated with
* a value or alias as well as preserve backwards compatibility.
*/
function getAlias(roomid, key) {
function getAlias(roomid: RoomID, key: string) {
if (!roomFaqs[roomid]) return false;
let value = roomFaqs[roomid][key];
const value = roomFaqs[roomid][key];
if (value && value[0] === '>') return value.substr(1);
return false;
}
/** @type {ChatCommands} */
const commands = {
export const commands: ChatCommands = {
addfaq(target, room, user, connection) {
if (!this.can('ban', null, room)) return false;
if (!room.chatRoomData) return this.errorReply("This command is unavailable in temporary rooms.");
if (!target) return this.parse('/help roomfaq');
target = target.trim();
let input = this.filter(target);
const input = this.filter(target);
if (target !== input) return this.errorReply("You are not allowed to use fitered words in roomfaq entries.");
let [topic, ...rest] = input.split(',');
@ -64,12 +60,16 @@ const commands = {
if (!this.canTalk()) return this.errorReply("You cannot do this while unable to talk.");
if (!this.can('ban', null, room)) return false;
if (!room.chatRoomData) return this.errorReply("This command is unavailable in temporary rooms.");
let topic = toID(target);
const topic = toID(target);
if (!topic) return this.parse('/help roomfaq');
if (!(roomFaqs[room.roomid] && roomFaqs[room.roomid][topic])) return this.errorReply("Invalid topic.");
delete roomFaqs[room.roomid][topic];
Object.keys(roomFaqs[room.roomid]).filter(val => getAlias(room.roomid, val) === topic).map(val => delete roomFaqs[room.roomid][val]);
Object.keys(roomFaqs[room.roomid]).filter(
val => getAlias(room.roomid, val) === topic
).map(
val => delete roomFaqs[room.roomid][val]
);
if (!Object.keys(roomFaqs[room.roomid]).length) delete roomFaqs[room.roomid];
saveRoomFaqs();
this.privateModAction(`(${user.name} removed the FAQ for '${topic}')`);
@ -79,7 +79,7 @@ const commands = {
if (!this.canTalk()) return this.errorReply("You cannot do this while unable to talk.");
if (!this.can('ban', null, room)) return false;
if (!room.chatRoomData) return this.errorReply("This command is unavailable in temporary rooms.");
let [alias, topic] = target.split(',').map(val => toID(val));
const [alias, topic] = target.split(',').map(val => toID(val));
if (!(alias && topic)) return this.parse('/help roomfaq');
if (alias.length > 25) return this.errorReply("FAQ topics should not exceed 25 characters.");
@ -95,8 +95,7 @@ const commands = {
rfaq: 'roomfaq',
roomfaq(target, room, user, connection, cmd) {
if (!roomFaqs[room.roomid]) return this.errorReply("This room has no FAQ topics.");
/** @type {string} */
let topic = toID(target);
let topic: string = toID(target);
if (topic === 'constructor') return false;
if (!topic) return this.sendReplyBox(`List of topics in this room: ${Object.keys(roomFaqs[room.roomid]).filter(val => !getAlias(room.roomid, val)).sort((a, b) => a.localeCompare(b)).map(rfaq => `<button class="button" name="send" value="/viewfaq ${rfaq}">${rfaq}</button>`).join(', ')}`);
if (!roomFaqs[room.roomid][topic]) return this.errorReply("Invalid topic.");
@ -124,8 +123,6 @@ const commands = {
],
};
exports.commands = commands;
process.nextTick(() => {
Chat.multiLinePattern.register('/addfaq ');
});

File diff suppressed because it is too large Load Diff

View File

@ -23,6 +23,7 @@ const RETRY_AFTER_LOGIN = null;
import {FS} from '../lib/fs';
import {WriteStream} from '../lib/streams';
import {GTSGiveaway, LotteryGiveaway, QuestionGiveaway} from './chat-plugins/wifi';
import {PM as RoomBattlePM, RoomBattle, RoomBattlePlayer, RoomBattleTimer} from "./room-battle";
import {RoomGame, RoomGamePlayer} from './room-game';
import {Roomlogs} from './roomlogs';
@ -103,6 +104,8 @@ export abstract class BasicRoom {
unoDisabled: boolean;
blackjackDisabled: boolean;
hangmanDisabled: boolean;
giveaway: QuestionGiveaway | LotteryGiveaway | null;
gtsga: GTSGiveaway | null;
toursEnabled: '%' | boolean;
tourAnnouncements: boolean;
privacySetter: Set<ID> | null;
@ -161,6 +164,8 @@ export abstract class BasicRoom {
this.unoDisabled = false;
this.blackjackDisabled = false;
this.hangmanDisabled = false;
this.giveaway = null;
this.gtsga = null;
this.toursEnabled = false;
this.tourAnnouncements = false;
this.privacySetter = null;

View File

@ -25,12 +25,8 @@
"./server/tournaments/*",
"./server/chat-commands/*.ts",
"./server/chat-plugins/*.ts",
"./server/chat-plugins/daily-spotlight.js",
"./server/chat-plugins/mafia-data.js",
"./server/chat-plugins/room-faqs.js",
"./server/chat-plugins/thecafe.js",
"./server/chat-plugins/thing-of-the-day.js",
"./server/chat-plugins/wifi.js",
"./sim/**/*",
"./tools/set-import/*.ts"
]