Add a chat plugin to manage username prefixes (#8338)

This commit is contained in:
Annika 2021-05-26 14:39:13 -07:00 committed by GitHub
parent d50c77fb4d
commit 83df279b1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 175 additions and 0 deletions

View File

@ -16,5 +16,6 @@ server/chat-plugins/rock-paper-scissors.ts @mia-pi-git
server/chat-plugins/scavenger*.ts @xfix @sparkychildcharlie
server/chat-plugins/the-studio.ts @KrisXV
server/chat-plugins/trivia.ts @AnnikaCodes
server/chat-plugins/username-prefixes.ts @AnnikaCodes
server/modlog.ts @monsanto
test/random-battles/* @AnnikaCodes

View File

@ -0,0 +1,127 @@
/**
* Code to manage username prefixes that force battles to be public or disable modchat.
* @author Annika
*/
import {FS} from '../../lib';
const PREFIXES_FILE = 'config/chat-plugins/username-prefixes.json';
export class PrefixManager {
constructor() {
// after a restart/newly using the plugin, load prefixes from config.js
if (!Chat.oldPlugins['username-prefixes']) this.refreshConfig(true);
}
save() {
FS(PREFIXES_FILE).writeUpdate(() => JSON.stringify(Config.forcedprefixes || {}));
}
refreshConfig(configJustLoaded = false) {
if (!Config.forcedprefixes) Config.forcedprefixes = {};
if (configJustLoaded) {
// if we just loaded the config file, ensure that all prefixes are IDs
if (Config.forcedprefixes.privacy) Config.forcedprefixes.privacy = Config.forcedprefixes.privacy.map(toID);
if (Config.forcedprefixes.modchat) Config.forcedprefixes.modchat = Config.forcedprefixes.modchat.map(toID);
}
let data: AnyObject;
try {
data = JSON.parse(FS(PREFIXES_FILE).readSync());
} catch (e) {
if (e.code !== 'ENOENT') throw e;
return;
}
for (const [type, prefixes] of Object.values(data)) {
if (!Config.forcedprefixes[type]) Config.forcedprefixes[type] = [];
for (const prefix of prefixes) {
if (Config.forcedprefixes[type].includes(prefix)) continue;
Config.forcedprefixes[type].push(prefix);
}
}
}
addPrefix(prefix: ID, type: 'privacy' | 'modchat') {
if (!Config.forcedprefixes[type]) Config.forcedprefixes[type] = [];
if (Config.forcedprefixes[type].includes(prefix)) {
throw new Chat.ErrorMessage(`Username prefix '${prefix}' is already configured to force ${type}.`);
}
Config.forcedprefixes[type].push(prefix);
this.save();
}
removePrefix(prefix: ID, type: 'privacy' | 'modchat') {
if (!Config.forcedprefixes[type]?.includes(prefix)) {
throw new Chat.ErrorMessage(`Username prefix '${prefix}' is not configured to force ${type}!`);
}
Config.forcedprefixes[type] = Config.forcedprefixes[type].filter((curPrefix: ID) => curPrefix !== prefix);
this.save();
}
validateType(type: string) {
if (type !== 'privacy' && type !== 'modchat') {
throw new Chat.ErrorMessage(`'${type}' is not a valid type of forced prefix. Valid types are 'privacy' and 'modchat'.`);
}
return type;
}
}
export const prefixManager = new PrefixManager();
export const commands: Chat.ChatCommands = {
forcedprefix: 'usernameprefix',
forcedprefixes: 'usernameprefix',
usernameprefixes: 'usernameprefix',
usernameprefix: {
help: '',
''() {
this.parse(`/help forcedprefix`);
},
delete: 'add',
remove: 'add',
add(target, room, user, connection, cmd) {
this.checkCan('rangeban');
const isAdding = cmd.includes('add');
const [prefix, type] = target.split(',').map(toID);
if (!prefix || !type) return this.parse(`/help usernameprefix`);
if (prefix.length > 18) {
throw new Chat.ErrorMessage(`Specified prefix '${prefix}' is longer than the maximum user ID length.`);
}
if (isAdding) {
prefixManager.addPrefix(prefix, prefixManager.validateType(type));
} else {
prefixManager.removePrefix(prefix, prefixManager.validateType(type));
}
this.globalModlog(`FORCEDPREFIX ${isAdding ? 'ADD' : 'REMOVE'}`, null, `'${prefix}' ${isAdding ? 'to' : 'from'} ${type}`);
this.addGlobalModAction(`${user.name} set the username prefix ${prefix} to${isAdding ? '' : ' no longer'} disable ${type}.`);
},
view(target) {
this.checkCan('rangeban');
const types = target ? [prefixManager.validateType(toID(target))] : ['privacy', 'modchat'];
return this.sendReplyBox(types.map(type => {
const info = Config.forcedprefixes[type].length ?
`<code>${Config.forcedprefixes[type].join('</code>, <code>')}</code>` : `none`;
return `Username prefixes that disable <strong>${type}</strong>: ${info}.`;
}).join(`<br />`));
},
},
usernameprefixhelp() {
return this.sendReplyBox(
`<code>/usernameprefix add [prefix], [type]</code>: Sets the username prefix [prefix] to disable privacy or modchat on battles where at least one player has the prefix.<br />` +
`<code>/usernameprefix remove [prefix], [type]</code>: Removes a prefix configuration.<br />` +
`<code>/usernameprefix view [optional type]</code>: Displays the currently configured username prefixes.<br />` +
`Valid types are <code>privacy</code> (which forces battles to take place in public rooms) and <code>modchat</code> (which prevents players from setting moderated chat).<br />` +
`Requires: &`
);
},
};

View File

@ -90,6 +90,9 @@ if (Config.watchconfig) {
FS(require.resolve('../config/config')).onModify(() => {
try {
global.Config = ConfigLoader.load(true);
// ensure that battle prefixes configured via the chat plugin are not overwritten
// by battle prefixes manually specified in config.js
Chat.plugins['username-prefixes']?.prefixManager.refreshConfig(true);
Monitor.notice('Reloaded ../config/config.js');
} catch (e) {
Monitor.adminlog("Error reloading ../config/config.js: " + e.stack);

View File

@ -0,0 +1,44 @@
/**
* Tests for the username-prefixes chat plugin.
* @author Annika
*/
'use strict';
const assert = require('assert').strict;
const {PrefixManager} = require('../../../.server-dist/chat-plugins/username-prefixes');
describe('PrefixManager', () => {
beforeEach(() => {
this.prefixManager = new PrefixManager();
Config.forcedprefixes = {privacy: [], modchat: []};
});
it('Config.forcedprefixes should reflect prefix additions and removals', () => {
this.prefixManager.addPrefix('forcedpublic', 'privacy');
this.prefixManager.addPrefix('nomodchat', 'modchat');
assert(Config.forcedprefixes.privacy.includes('forcedpublic'));
assert(Config.forcedprefixes.modchat.includes('nomodchat'));
this.prefixManager.removePrefix('forcedpublic', 'privacy');
this.prefixManager.removePrefix('nomodchat', 'modchat');
assert(!Config.forcedprefixes.privacy.includes('forcedpublic'));
assert(!Config.forcedprefixes.modchat.includes('nomodchat'));
});
it('should not overwrite manually specified prefixes', () => {
Config.forcedprefixes.modchat = ['manual'];
this.prefixManager.addPrefix('nomodchat', 'modchat');
assert.deepEqual(Config.forcedprefixes.modchat, ['manual', 'nomodchat']);
});
it('should correctly validate prefix types', () => {
assert.doesNotThrow(() => this.prefixManager.validateType('privacy'));
assert.doesNotThrow(() => this.prefixManager.validateType('modchat'));
assert.throws(() => this.prefixManager.validateType('gibberish'));
assert.throws(() => this.prefixManager.validateType(''));
});
});