Pokemon_server / server /config-loader.ts
Jofthomas's picture
Jofthomas HF staff
Upload 4781 files
5c2ed06 verified
/**
* Config loader
* Pokemon Showdown - http://pokemonshowdown.com/
*
* @license MIT
*/
import * as defaults from '../config/config-example';
import type { GroupInfo, EffectiveGroupSymbol } from './user-groups';
import { ProcessManager, FS } from '../lib';
export type ConfigType = typeof defaults & {
groups: { [symbol: string]: GroupInfo },
groupsranking: EffectiveGroupSymbol[],
greatergroupscache: { [combo: string]: GroupSymbol },
[k: string]: any,
};
/** Map<process flag, config settings for it to turn on> */
const FLAG_PRESETS = new Map([
['--no-security', ['nothrottle', 'noguestsecurity', 'noipchecks']],
]);
const CONFIG_PATH = FS('./config/config.js').path;
export function load(invalidate = false) {
if (invalidate) delete require.cache[CONFIG_PATH];
const config = ({ ...defaults, ...require(CONFIG_PATH) }) as ConfigType;
// config.routes is nested - we need to ensure values are set for its keys as well.
config.routes = { ...defaults.routes, ...config.routes };
// Automatically stop startup if better-sqlite3 isn't installed and SQLite is enabled
if (config.usesqlite) {
try {
require('better-sqlite3');
} catch {
throw new Error(`better-sqlite3 is not installed or could not be loaded, but Config.usesqlite is enabled.`);
}
}
for (const [preset, values] of FLAG_PRESETS) {
if (process.argv.includes(preset)) {
for (const value of values) config[value] = true;
}
}
cacheGroupData(config);
return config;
}
export function cacheGroupData(config: ConfigType) {
if (config.groups) {
// Support for old config groups format.
// Should be removed soon.
reportError(
`You are using a deprecated version of user group specification in config.\n` +
`Support for this will be removed soon.\n` +
`Please ensure that you update your config.js to the new format (see config-example.js, line 457).\n`
);
} else {
config.punishgroups = Object.create(null);
config.groups = Object.create(null);
config.groupsranking = [];
config.greatergroupscache = Object.create(null);
}
const groups = config.groups;
const punishgroups = config.punishgroups;
const cachedGroups: { [k: string]: 'processing' | true } = {};
function isPermission(key: string) {
return !['symbol', 'id', 'name', 'rank', 'globalGroupInPersonalRoom'].includes(key);
}
function cacheGroup(symbol: string, groupData: GroupInfo) {
if (cachedGroups[symbol] === 'processing') {
throw new Error(`Cyclic inheritance in group config for symbol "${symbol}"`);
}
if (cachedGroups[symbol] === true) return;
for (const key in groupData) {
if (isPermission(key)) {
const jurisdiction = groupData[key as 'jurisdiction'];
if (typeof jurisdiction === 'string' && jurisdiction.includes('s')) {
reportError(`Outdated jurisdiction for permission "${key}" of group "${symbol}": 's' is no longer a supported jurisdiction; we now use 'ipself' and 'altsself'`);
delete groupData[key as 'jurisdiction'];
}
}
}
if (groupData['inherit']) {
cachedGroups[symbol] = 'processing';
const inheritGroup = groups[groupData['inherit']];
cacheGroup(groupData['inherit'], inheritGroup);
// Add lower group permissions to higher ranked groups,
// preserving permissions specifically declared for the higher group.
for (const key in inheritGroup) {
if (key in groupData) continue;
if (!isPermission(key)) continue;
(groupData as any)[key] = (inheritGroup as any)[key];
}
delete groupData['inherit'];
}
cachedGroups[symbol] = true;
}
if (config.grouplist) { // Using new groups format.
const grouplist = config.grouplist as any;
const numGroups = grouplist.length;
for (let i = 0; i < numGroups; i++) {
const groupData = grouplist[i];
// punish groups
if (groupData.punishgroup) {
punishgroups[groupData.id] = groupData;
continue;
}
groupData.rank = numGroups - i - 1;
groups[groupData.symbol] = groupData;
config.groupsranking.unshift(groupData.symbol);
}
}
for (const sym in groups) {
const groupData = groups[sym];
cacheGroup(sym, groupData);
}
// hardcode default punishgroups.
if (!punishgroups.locked) {
punishgroups.locked = {
name: 'Locked',
id: 'locked',
symbol: '\u203d',
};
}
if (!punishgroups.muted) {
punishgroups.muted = {
name: 'Muted',
id: 'muted',
symbol: '!',
};
}
}
export function checkRipgrepAvailability() {
if (Config.ripgrepmodlog === undefined) {
const cwd = FS.ROOT_PATH;
Config.ripgrepmodlog = (async () => {
try {
await ProcessManager.exec(['rg', '--version'], { cwd });
await ProcessManager.exec(['tac', '--version'], { cwd });
return true;
} catch {
return false;
}
})();
}
return Config.ripgrepmodlog;
}
function reportError(msg: string) {
// This module generally loads before Monitor, so we put this in a setImmediate to wait for it to load.
// Most child processes don't have Monitor.error, but the main process should always have them, and Config
// errors should always be the same across processes, so this is a neat way to avoid unnecessary logging.
setImmediate(() => global.Monitor?.error?.(msg));
}
export const Config = load();