const modulename = 'WebServer:AdvancedActions'; import v8 from 'node:v8'; import bytes from 'bytes'; import got from '@lib/got'; import consoleFactory from '@lib/console'; import { SYM_SYSTEM_AUTHOR } from '@lib/symbols'; const console = consoleFactory(modulename); //Helper functions const isUndefined = (x) => (x === undefined); /** * Endpoint for running advanced commands - basically, should not ever be used */ export default async function AdvancedActions(ctx) { //Sanity check if ( isUndefined(ctx.request.body.action) || isUndefined(ctx.request.body.parameter) ) { console.warn('Invalid request!'); return ctx.send({ type: 'danger', message: 'Invalid request :(' }); } const action = ctx.request.body.action; const parameter = ctx.request.body.parameter; //Check permissions if (!ctx.admin.testPermission('all_permissions', modulename)) { return ctx.send({ type: 'danger', message: 'You don\'t have permission to execute this action.', }); } //Action: Change Verbosity if (action == 'change_verbosity') { console.setVerbose(parameter == 'true'); //temp disabled because the verbosity convar is not being set by this method return ctx.send({ refresh: true }); } else if (action == 'perform_magic') { const message = JSON.stringify(txCore.fxPlayerlist.getPlayerList(), null, 2); return ctx.send({ type: 'success', message }); } else if (action == 'show_db') { const dbo = txCore.database.getDboRef(); console.dir(dbo); return ctx.send({ type: 'success', message: JSON.stringify(dbo, null, 2) }); } else if (action == 'show_log') { return ctx.send({ type: 'success', message: JSON.stringify(txCore.logger.server.getRecentBuffer(), null, 2) }); } else if (action == 'memory') { let memory; try { const usage = process.memoryUsage(); Object.keys(usage).forEach((prop) => { usage[prop] = bytes(usage[prop]); }); memory = JSON.stringify(usage, null, 2); } catch (error) { memory = 'error'; } return ctx.send({ type: 'success', message: memory }); } else if (action == 'freeze') { console.warn('Freezing process for 50 seconds.'); Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 50 * 1000); } else if (action == 'updateMutableConvars') { txCore.fxRunner.updateMutableConvars(); return ctx.send({ refresh: true }); } else if (action == 'reauthLast10Players') { // force refresh the admin status of the last 10 players to join const lastPlayers = txCore.fxPlayerlist.getPlayerList().map((p) => p.netid).slice(-10); txCore.fxRunner.sendEvent('adminsUpdated', lastPlayers); return ctx.send({ type: 'success', message: `refreshed: ${JSON.stringify(lastPlayers)}` }); } else if (action == 'getLoggerErrors') { const outData = { admin: txCore.logger.admin.lrLastError, fxserver: txCore.logger.fxserver.lrLastError, server: txCore.logger.server.lrLastError, }; return ctx.send({ type: 'success', message: JSON.stringify(outData, null, 2) }); } else if (action == 'testSrcAddress') { const url = 'https://api.myip.com'; const respDefault = await got(url).json(); const respReset = await got(url, { localAddress: undefined }).json(); const outData = { url, respDefault, respReset, }; return ctx.send({ type: 'success', message: JSON.stringify(outData, null, 2) }); } else if (action == 'getProcessEnv') { return ctx.send({ type: 'success', message: JSON.stringify(process.env, null, 2) }); } else if (action == 'snap') { setTimeout(() => { // if (Citizen && Citizen.snap) Citizen.snap(); const snapFile = v8.writeHeapSnapshot(); console.warn(`Heap snapshot written to: ${snapFile}`); }, 50); return ctx.send({ type: 'success', message: 'terminal' }); } else if (action === 'gc') { if (typeof global.gc === 'function') { global.gc(); return ctx.send({ type: 'success', message: 'done' }); } else { return ctx.send({ type: 'danger', message: 'GC is not exposed' }); } } else if (action === 'safeEnsureMonitor') { const setCmdResult = txCore.fxRunner.sendCommand( 'set', [ 'txAdmin-luaComToken', txCore.webServer.luaComToken, ], SYM_SYSTEM_AUTHOR ); if (!setCmdResult) { return ctx.send({ type: 'danger', message: 'Failed to reset luaComToken.' }); } const ensureCmdResult = txCore.fxRunner.sendCommand( 'ensure', ['monitor'], SYM_SYSTEM_AUTHOR ); if (ensureCmdResult) { return ctx.send({ type: 'success', message: 'done' }); } else { return ctx.send({ type: 'danger', message: 'Failed to ensure monitor.' }); } } else if (action.startsWith('playerDrop')) { const reason = action.split(' ', 2)[1]; const category = txCore.metrics.playerDrop.handlePlayerDrop(reason); return ctx.send({ type: 'success', message: category }); } else if (action.startsWith('set')) { // set general.language "pt" // set general.language "en" // set server.onesync "on" // set server.onesync "legacy" try { const [_, scopeKey, valueJson] = action.split(' ', 3); if (!scopeKey || !valueJson) throw new Error(`Invalid set command: ${action}`); const [scope, key] = scopeKey.split('.'); if (!scope || !key) throw new Error(`Invalid set command: ${action}`); const configUpdate = { [scope]: { [key]: JSON.parse(valueJson) } }; const storedKeysChanges = txCore.configStore.saveConfigs(configUpdate, ctx.admin.name); const outParts = [ 'Keys Updated: ' + JSON.stringify(storedKeysChanges ?? 'not set', null, 2), '-'.repeat(16), 'Stored:' + JSON.stringify(txCore.configStore.getStoredConfig(), null, 2), ]; return ctx.send({ type: 'success', message: outParts.join('\n') }); } catch (error) { return ctx.send({ type: 'danger', message: error.message }); } } else if (action == 'printFxRunnerChildHistory') { const message = JSON.stringify(txCore.fxRunner.history, null, 2) return ctx.send({ type: 'success', message }); } else if (action == 'xxxxxx') { return ctx.send({ type: 'success', message: '😀👍' }); } //Catch all return ctx.send({ type: 'danger', message: 'Unknown action :(' }); };