diff --git a/package/server/resource/addCommand/index.ts b/package/server/resource/addCommand/index.ts new file mode 100644 index 0000000..47df2f7 --- /dev/null +++ b/package/server/resource/addCommand/index.ts @@ -0,0 +1,140 @@ +import { addAce } from '../acl'; + +type OxCommandArguments = Record; + +interface OxCommandParams { + name: string; + help?: string; + paramType?: 'number' | 'playerId' | 'string'; + optional?: boolean; +} + +interface OxCommandProperties { + name?: string; + help?: string; + params?: OxCommandParams[]; + restricted?: boolean | string | string[]; +} + +const registeredCommmands: OxCommandProperties[] = []; +let shouldSendCommands = false; + +setTimeout(() => { + shouldSendCommands = true; + emitNet('chat:addSuggestions', -1, registeredCommmands); +}, 1000); + +on('playerJoining', (source: number) => { + emitNet('chat:addSuggestions', source, registeredCommmands); +}); + +function parseArguments( + source: number, + args: OxCommandArguments, + raw: string, + params?: OxCommandParams[] +): OxCommandArguments | undefined { + if (!params) { return args; } + + const result = params.every((param, index) => { + const arg = args[index]; + let value; + + switch (param.paramType) { + case 'number': + value = +arg; + break; + + case 'string': + value = !Number(arg) ? arg : false; + break; + + case 'playerId': + value = arg === 'me' ? source : +arg; + if (!value || !GetPlayerGuid(value.toString())) { + value = false; + } + break; + + default: + value = arg; + break; + } + + if (!value && (!param.optional || (param.optional && arg))) { + return Citizen.trace( + `^1command '${raw.split(' ') || raw}' received an invalid ${param.paramType} for argument ${index} (${ + param.name + }), received '${arg}'^0` + ); + } + + args[param.name] = value; + delete args[index]; + + return true; + }); + + return result ? args : undefined; +} + +export function addCommand( + commandName: string | string[], + cb: (source: number, args: object, raw: string) => any, + properties?: OxCommandProperties +) { + const restricted = properties?.restricted; + const params = properties?.params; + + if (params) { + params.forEach((param) => { + if (param.paramType) { + param.help = param.help ? `${param.help} (type: ${param.paramType})` : `(type: ${param.paramType})`; + } + }); + } + + const commands = typeof commandName !== 'object' ? [commandName] : commandName; + const numCommands = commands.length; + + const commandHandler = (source: number, args: OxCommandArguments, raw: string) => { + const parsed = parseArguments(source, args, raw, params); + if (!parsed) { return; } + + cb(source, parsed, raw); + }; + + commands.forEach((commandName, index) => { + RegisterCommand(commandName, commandHandler, restricted ? true : false); + + if (restricted) { + const ace = `command.${commandName}`; + const restrictedType = typeof restricted; + + if (restrictedType === 'string' && !IsPrincipalAceAllowed(restricted as string, ace)) { + addAce(restricted as string, ace, true); + } else if (restrictedType === 'object') { + const _restricted = restricted as string[]; + _restricted.forEach((principal) => { + if (!IsPrincipalAceAllowed(principal, ace)) { + addAce(principal as string, ace, true); + } + }); + } + } + + if (properties) { + properties.name = `/${commandName}`; + delete properties.restricted; + registeredCommmands.push(properties); + + if (index !== numCommands && numCommands !== 1) { + properties = { ...properties }; + } + + if (shouldSendCommands) { + emitNet('chat:addSuggestions', -1, properties); + } + } + }); +} diff --git a/package/server/resource/index.ts b/package/server/resource/index.ts index fbce843..8ed674a 100644 --- a/package/server/resource/index.ts +++ b/package/server/resource/index.ts @@ -2,3 +2,4 @@ export * from './acl'; export * from './locale'; export * from './callback'; export * from './version'; +export * from './addCommand'; \ No newline at end of file