386 lines
17 KiB
TypeScript
386 lines
17 KiB
TypeScript
import core from './core';
|
|
import {Character, Conversation, userStatuses} from './interfaces';
|
|
import l from './localize';
|
|
import ChannelConversation = Conversation.ChannelConversation;
|
|
import PrivateConversation = Conversation.PrivateConversation;
|
|
|
|
export const enum ParamType {
|
|
String, Number, Character, Enum
|
|
}
|
|
|
|
const defaultDelimiters: {[key: number]: string | undefined} = {[ParamType.Character]: ',', [ParamType.String]: ''};
|
|
|
|
export function isAction(this: void, text: string): boolean {
|
|
return /^\/me\b/i.test(text);
|
|
}
|
|
|
|
export function isWarn(this: void, text: string): boolean {
|
|
return /^\/warn\b/i.test(text);
|
|
}
|
|
|
|
export function isCommand(this: void, text: string): boolean {
|
|
return text.charAt(0) === '/' && !isAction(text) && !isWarn(text);
|
|
}
|
|
|
|
export function parse(this: void | never, input: string, context: CommandContext): ((this: Conversation) => void) | string {
|
|
const commandEnd = input.indexOf(' ');
|
|
const name = input.substring(1, commandEnd !== -1 ? commandEnd : undefined).toLowerCase();
|
|
const command = commands[name];
|
|
if(command === undefined) return l('commands.unknown');
|
|
const args = `${commandEnd !== -1 ? input.substr(commandEnd + 1) : ''}`;
|
|
if(command.context !== undefined && (command.context & context) === 0) return l('commands.badContext');
|
|
|
|
let index = 0;
|
|
const values: (string | number)[] = [];
|
|
|
|
if(command.params !== undefined)
|
|
for(let i = 0; i < command.params.length; ++i) {
|
|
while(args[index] === ' ') ++index;
|
|
const param = command.params[i];
|
|
if(index === -1) {
|
|
if(param.optional !== undefined) continue;
|
|
return l('commands.tooFewParams');
|
|
}
|
|
let delimiter = param.delimiter !== undefined ? param.delimiter : defaultDelimiters[param.type];
|
|
if(delimiter === undefined) delimiter = ' ';
|
|
const endIndex = delimiter.length > 0 ? args.indexOf(delimiter, index) : args.length;
|
|
const value = args.substring(index, endIndex !== -1 ? endIndex : undefined);
|
|
if(value.length === 0) {
|
|
if(param.optional !== undefined) continue;
|
|
return l('commands.tooFewParams');
|
|
}
|
|
values[i] = value;
|
|
switch(param.type) {
|
|
case ParamType.String:
|
|
if(i === command.params.length - 1) values[i] = args.substr(index);
|
|
break;
|
|
case ParamType.Enum:
|
|
if((param.options !== undefined ? param.options : []).indexOf(value) === -1)
|
|
return l('commands.invalidParam', l(`commands.${name}.param${i}`));
|
|
break;
|
|
case ParamType.Number:
|
|
const num = parseInt(value, 10);
|
|
if(isNaN(num))
|
|
return l('commands.invalidParam', l(`commands.${name}.param${i}`));
|
|
values[i] = num;
|
|
break;
|
|
case ParamType.Character:
|
|
if(value.charAt(0) === '"' && value.charAt(value.length - 1) === '"') {
|
|
values[i] = value.substring(1, value.length - 1);
|
|
break;
|
|
}
|
|
const char = core.characters.get(value);
|
|
if(char.status === 'offline') return l('commands.invalidCharacter');
|
|
}
|
|
index = endIndex === -1 ? args.length : endIndex + 1;
|
|
}
|
|
return function(this: Conversation): void {
|
|
command.exec(this, ...values);
|
|
};
|
|
}
|
|
|
|
export const enum CommandContext {
|
|
Console = 1 << 0,
|
|
Channel = 1 << 1,
|
|
Private = 1 << 2
|
|
}
|
|
|
|
export enum Permission {
|
|
RoomOp = -1,
|
|
RoomOwner = -2,
|
|
ChannelMod = 4,
|
|
ChatOp = 2,
|
|
Admin = 1
|
|
}
|
|
|
|
export interface Command {
|
|
readonly context?: CommandContext, //default implicit Console | Channel | Private
|
|
readonly permission?: Permission
|
|
readonly documented?: false, //default true
|
|
readonly params?: {
|
|
readonly type: ParamType
|
|
readonly options?: ReadonlyArray<string>, //default undefined
|
|
readonly optional?: true, //default false
|
|
readonly delimiter?: string, //default ' ' (',' for type: Character)
|
|
validator?(data: string | number): boolean //default undefined
|
|
}[]
|
|
exec(context: Conversation, ...params: (string | number | undefined)[]): void
|
|
}
|
|
|
|
const commands: {readonly [key: string]: Command | undefined} = {
|
|
me: {
|
|
exec: () => 'stub',
|
|
context: CommandContext.Channel | CommandContext.Private,
|
|
params: [{type: ParamType.String}]
|
|
},
|
|
reward: {
|
|
exec: (_, character: string) => core.connection.send('RWD', {character}),
|
|
permission: Permission.Admin,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
greports: {
|
|
permission: Permission.ChannelMod,
|
|
exec: () => core.connection.send('PCR')
|
|
},
|
|
join: {
|
|
exec: (_, channel: string) => core.connection.send('JCH', {channel}),
|
|
params: [{type: ParamType.String}]
|
|
},
|
|
close: {
|
|
exec: (conv: PrivateConversation | ChannelConversation) => conv.close(),
|
|
context: CommandContext.Private | CommandContext.Channel
|
|
},
|
|
priv: {
|
|
exec: (_, character: string) => core.conversations.getPrivate(core.characters.get(character)).show(),
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
uptime: {
|
|
exec: () => core.connection.send('UPT')
|
|
},
|
|
clear: {
|
|
exec: (conv: Conversation) => conv.clear()
|
|
},
|
|
status: {
|
|
exec: (_, status: Character.Status, statusmsg: string = '') => core.connection.send('STA', {status, statusmsg}),
|
|
params: [{type: ParamType.Enum, options: userStatuses}, {type: ParamType.String, optional: true}]
|
|
},
|
|
roll: {
|
|
exec: (conv: ChannelConversation | PrivateConversation, dice: string) => {
|
|
if(Conversation.isChannel(conv)) core.connection.send('RLL', {channel: conv.channel.id, dice});
|
|
else core.connection.send('RLL', {recipient: conv.character.name, dice});
|
|
},
|
|
context: CommandContext.Channel | CommandContext.Private,
|
|
params: [{type: ParamType.String}]
|
|
},
|
|
bottle: {
|
|
exec: (conv: ChannelConversation | PrivateConversation) => {
|
|
if(Conversation.isChannel(conv)) core.connection.send('RLL', {channel: conv.channel.id, dice: 'bottle'});
|
|
else core.connection.send('RLL', {recipient: conv.character.name, dice: 'bottle'});
|
|
},
|
|
context: CommandContext.Channel | CommandContext.Private
|
|
},
|
|
warn: {
|
|
exec: () => 'stub',
|
|
permission: Permission.RoomOp,
|
|
context: CommandContext.Channel,
|
|
params: [{type: ParamType.String}]
|
|
},
|
|
kick: {
|
|
exec: (conv: ChannelConversation, character: string) =>
|
|
core.connection.send('CKU', {channel: conv.channel.id, character}),
|
|
permission: Permission.RoomOp,
|
|
context: CommandContext.Channel,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
ban: {
|
|
exec: (conv: ChannelConversation, character: string) =>
|
|
core.connection.send('CBU', {channel: conv.channel.id, character}),
|
|
permission: Permission.RoomOp,
|
|
context: CommandContext.Channel,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
unban: {
|
|
exec: (conv: ChannelConversation, character: string) =>
|
|
core.connection.send('CUB', {channel: conv.channel.id, character}),
|
|
permission: Permission.RoomOp,
|
|
context: CommandContext.Channel,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
banlist: {
|
|
exec: (conv: ChannelConversation) => core.connection.send('CBL', {channel: conv.channel.id}),
|
|
permission: Permission.RoomOp,
|
|
context: CommandContext.Channel
|
|
},
|
|
timeout: {
|
|
exec: (conv: ChannelConversation, character: string, length: number) =>
|
|
core.connection.send('CTU', {channel: conv.channel.id, character, length}),
|
|
permission: Permission.RoomOp,
|
|
context: CommandContext.Channel,
|
|
params: [{type: ParamType.Character, delimiter: ','}, {type: ParamType.Number, validator: (x) => x >= 1}]
|
|
},
|
|
gkick: {
|
|
exec: (_, character: string) => core.connection.send('KIK', {character}),
|
|
permission: Permission.ChatOp,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
gban: {
|
|
exec: (_, character: string) => core.connection.send('ACB', {character}),
|
|
permission: Permission.ChatOp,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
gunban: {
|
|
exec: (_, character: string) => core.connection.send('UNB', {character}),
|
|
permission: Permission.ChatOp,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
gtimeout: {
|
|
exec: (_, character: string, time: number, reason: string) =>
|
|
core.connection.send('TMO', {character, time, reason}),
|
|
permission: Permission.ChatOp,
|
|
params: [{type: ParamType.Character, delimiter: ','}, {type: ParamType.Number, validator: (x) => x >= 1}, {type: ParamType.String}]
|
|
},
|
|
setowner: {
|
|
exec: (conv: ChannelConversation, character: string) =>
|
|
core.connection.send('CSO', {channel: conv.channel.id, character}),
|
|
permission: Permission.RoomOwner,
|
|
context: CommandContext.Channel,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
ignore: {
|
|
exec: (_, character: string) => core.connection.send('IGN', {action: 'add', character}),
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
unignore: {
|
|
exec: (_, character: string) => core.connection.send('IGN', {action: 'delete', character}),
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
ignorelist: {
|
|
exec: (conv: Conversation) => conv.infoText = l('chat.ignoreList', core.characters.ignoreList.join(', '))
|
|
},
|
|
makeroom: {
|
|
exec: (_, channel: string) => core.connection.send('CCR', {channel}),
|
|
params: [{type: ParamType.String}]
|
|
},
|
|
gop: {
|
|
exec: (_, character: string) => core.connection.send('AOP', {character}),
|
|
permission: Permission.Admin,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
gdeop: {
|
|
exec: (_, character: string) => core.connection.send('DOP', {character}),
|
|
permission: Permission.Admin,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
op: {
|
|
exec: (conv: ChannelConversation, character: string) =>
|
|
core.connection.send('COA', {channel: conv.channel.id, character}),
|
|
permission: Permission.RoomOwner,
|
|
context: CommandContext.Channel,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
deop: {
|
|
exec: (conv: ChannelConversation, character: string) =>
|
|
core.connection.send('COR', {channel: conv.channel.id, character}),
|
|
permission: Permission.RoomOwner,
|
|
context: CommandContext.Channel,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
scop: {
|
|
exec: (_, character: string) => core.connection.send('SCP', {action: 'add', character}),
|
|
permission: Permission.Admin,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
scdeop: {
|
|
exec: (_, character: string) => core.connection.send('SCP', {action: 'remove', character}),
|
|
permission: Permission.Admin,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
oplist: {
|
|
exec: (conv: ChannelConversation) => core.connection.send('COL', {channel: conv.channel.id}),
|
|
context: CommandContext.Channel
|
|
},
|
|
invite: {
|
|
exec: (conv: ChannelConversation, character: string) =>
|
|
core.connection.send('CIU', {channel: conv.channel.id, character}),
|
|
permission: Permission.RoomOp,
|
|
context: CommandContext.Channel,
|
|
params: [{type: ParamType.Character}]
|
|
},
|
|
closeroom: {
|
|
exec: (conv: ChannelConversation) => core.connection.send('RST', {channel: conv.channel.id, status: 'private'}),
|
|
permission: Permission.RoomOwner,
|
|
context: CommandContext.Channel
|
|
},
|
|
openroom: {
|
|
exec: (conv: ChannelConversation) => core.connection.send('RST', {channel: conv.channel.id, status: 'public'}),
|
|
permission: Permission.RoomOwner,
|
|
context: CommandContext.Channel
|
|
},
|
|
setmode: {
|
|
exec: (conv: ChannelConversation, mode: 'ads' | 'chat' | 'both') =>
|
|
core.connection.send('RMO', {channel: conv.channel.id, mode}),
|
|
permission: Permission.RoomOwner,
|
|
context: CommandContext.Channel,
|
|
params: [{type: ParamType.Enum, options: ['ads', 'chat', 'both']}]
|
|
},
|
|
setdescription: {
|
|
exec: (conv: ChannelConversation, description: string) =>
|
|
core.connection.send('CDS', {channel: conv.channel.id, description}),
|
|
permission: Permission.RoomOp,
|
|
context: CommandContext.Channel,
|
|
params: [{type: ParamType.String}]
|
|
},
|
|
code: {
|
|
exec: (conv: ChannelConversation) => {
|
|
const active = <HTMLElement>document.activeElement;
|
|
const elm = document.createElement('textarea');
|
|
elm.value = `[session=${conv.channel.name}]${conv.channel.id}[/session]`;
|
|
document.body.appendChild(elm);
|
|
elm.select();
|
|
document.execCommand('copy');
|
|
document.body.removeChild(elm);
|
|
active.focus();
|
|
conv.infoText = l('commands.code.success');
|
|
},
|
|
permission: Permission.RoomOwner,
|
|
context: CommandContext.Channel
|
|
},
|
|
killchannel: {
|
|
exec: (conv: ChannelConversation) => core.connection.send('KIC', {channel: conv.channel.id}),
|
|
permission: Permission.RoomOwner,
|
|
context: CommandContext.Channel
|
|
},
|
|
createchannel: {
|
|
exec: (_, channel: string) => core.connection.send('CRC', {channel}),
|
|
permission: Permission.ChatOp,
|
|
params: [{type: ParamType.String}]
|
|
},
|
|
broadcast: {
|
|
exec: (_, message: string) => core.connection.send('BRO', {message}),
|
|
permission: Permission.Admin,
|
|
params: [{type: ParamType.String}]
|
|
},
|
|
reloadconfig: {
|
|
exec: (_, save?: 'save') => core.connection.send('RLD', save !== undefined ? {save} : undefined),
|
|
permission: Permission.Admin,
|
|
params: [{type: ParamType.Enum, options: ['save'], optional: true}]
|
|
},
|
|
xyzzy: {
|
|
exec: (_, command: string, arg: string) => core.connection.send('ZZZ', {command, arg}),
|
|
permission: Permission.Admin,
|
|
params: [{type: ParamType.String, delimiter: ' '}, {type: ParamType.String}]
|
|
},
|
|
elf: {
|
|
exec: (conv: Conversation) => conv.infoText = elf[Math.floor(Math.random() * elf.length)],
|
|
documented: false
|
|
}
|
|
};
|
|
|
|
const elf = [ //Ceran is to be thanked for most of these.
|
|
`Now no one can say there's "not enough Elf." It's a well-kept secret, but elves love headpets. You should try it sometime.`,
|
|
`Your task for the day: provide a soft cushion in a sunny area for maximum elf comfort, earn +2 Bliss pts.`,
|
|
`Your task for the day: pet an elf at least (3) times, receive Good Karma.`,
|
|
`Your task for the day: make dinner for an elf, receive +3 Luck pts.`,
|
|
`The reason that elves' ears are so long is so that they can better hear your sins. Now that's an Elf Fact!`,
|
|
`A "straight" elf is basically an oxymoron.`,
|
|
`Don't forget to water your elf today!`,
|
|
`Please don't let your elf eat out of the trash can.`,
|
|
`Elves are not allowed to have soda after 12pm, but they will anyway.`,
|
|
`Pet an elf on the ears (4) times. Any more and the elf will bite. Now that's an Elf Fact!`,
|
|
`There are two kinds of people in the world. People who like elves. And people with questionable taste.`,
|
|
`Love yourself, pet an elf!!!`,
|
|
`Your task for the day: leave out snacks for your local elves, they'll help dispel vermin!`,
|
|
`An elf in your home will discourage the presence of predators! Or summon them. I forget which.`,
|
|
`If you crack open an elf, there's just a smaller elf within. It's just smaller and smaller elves all the way down.`,
|
|
`In case of an emergency, an elf's ass can be used as a flotation device. Now that's an Elf Fact!`,
|
|
`Running low on inventory space? An elf can be used to slot additional cylindrical artifacts! Now that's an Elf Fact!`,
|
|
`The average elf can consume half their body weight in cum. Now that's an Elf Fact!`,
|
|
`Your task for the day: subjugate yourself to your elven overlords in order to achieve righteous bliss.`,
|
|
`Your task for the day: Rub an elf's tummy. Be wary of them bear-trapping your arm as the forces of darkness consume their soul.`,
|
|
`Listen, that elfroot you found in my sock drawer is PURELY medicinal!`,
|
|
`What are elves? We just don't know.`,
|
|
`As long as you pet an elf's head, they will be content. What will happen if you stop? No one's ever come back to tell the tale.`,
|
|
`Elves are very helpful creatures. Just ask them to carry something for you, and they will. Especially eggs.`
|
|
];
|
|
|
|
export default commands; |