File size: 5,192 Bytes
5c2ed06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/**
 * 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();