This commit is contained in:
David Teller 2022-07-19 15:36:30 +02:00
parent 5395a66071
commit 7b094f3e28
3 changed files with 220 additions and 36 deletions

View File

@ -41,18 +41,200 @@ import { execMakeRoomAdminCommand } from "./MakeRoomAdminCommand";
import { execSinceCommand } from "./SinceCommand";
import { Lexer } from "./Lexer";
export const COMMAND_PREFIX = "!mjolnir";
type Command = {
cmd: string,
help: string,
code: (roomId: string, event: {content: {body: string}}, mjolnir: Mjolnir, parts: string[], lexer: Lexer) => Promise<any>,
};
const COMMANDS: Command[] = [{
cmd: '',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execStatusCommand(roomId, event, mjolnir, parts.slice(2))
},
{
cmd: 'ban',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execBanCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'unban',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execUnbanCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'rules',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execRulesMatchingCommand(roomId, event, mjolnir, parts[3]),
},
{
cmd: 'rules',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execDumpRulesCommand(roomId, event, mjolnir),
},
{
cmd: 'sync',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execSyncCommand(roomId, event, mjolnir),
},
{
cmd: 'verify',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execPermissionCheckCommand(roomId, event, mjolnir),
},
{
cmd: 'list',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execCreateListCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'watch',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execWatchCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'unwatch',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execUnwatchCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'redact',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execRedactCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'import',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execImportCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'default',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execSetDefaultListCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'deactivate',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execDeactivateCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'protections',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execListProtections(roomId, event, mjolnir, parts),
},
{
cmd: 'enable',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execEnableProtection(roomId, event, mjolnir, parts),
},
{
cmd: 'disable',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execDisableProtection(roomId, event, mjolnir, parts),
},
{
cmd: 'config',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execConfigSetProtection(roomId, event, mjolnir, parts.slice(3)),
},
{
cmd: 'config',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execConfigAddProtection(roomId, event, mjolnir, parts.slice(3)),
},
{
cmd: 'config',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execConfigRemoveProtection(roomId, event, mjolnir, parts.slice(3)),
},
{
cmd: 'config',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execConfigGetProtection(roomId, event, mjolnir, parts.slice(3)),
},
{
cmd: 'rooms',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execAddProtectedRoom(roomId, event, mjolnir, parts),
},
{
cmd: 'rooms',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execRemoveProtectedRoom(roomId, event, mjolnir, parts),
},
{
cmd: 'rooms',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execListProtectedRooms(roomId, event, mjolnir),
},
{
cmd: 'move',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execMoveAliasCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'directory',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execAddRoomToDirectoryCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'directory',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execRemoveRoomFromDirectoryCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'alias',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execAddAliasCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'alias',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execRemoveAliasCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'resolve',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execResolveCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'powerlevel',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execSetPowerLevelCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'shutdown',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execShutdownRoomCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'since',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execSinceCommand(roomId, event, mjolnir, lexer),
},
{
cmd: 'kick',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execKickCommand(roomId, event, mjolnir, parts),
},
{
cmd: 'make',
help: "FIXME",
code: (roomId, event, mjolnir, parts, lexer) => execMakeRoomAdminCommand(roomId, event, mjolnir, parts),
}];
export async function handleCommand(roomId: string, event: { content: { body: string } }, mjolnir: Mjolnir) {
const line = event['content']['body'];
const parts = line.trim().split(' ').filter(p => p.trim().length > 0);
console.debug("YORIC", "line", line);
const lexer = new Lexer(line);
lexer.consume("command"); // Consume `!mjolnir`.
let cmd = parts.length === 1 ? null : lexer.consume("id").text;
lexer.token("command"); // Consume `!mjolnir`.
const cmd = lexer.token("id").text;
console.debug("YORIC", "cmd", cmd);
try {
if (parts.length === 1 || cmd === 'status') {
if (cmd === '' || cmd === 'status') {
return await execStatusCommand(roomId, event, mjolnir, parts.slice(2));
} else if (cmd === 'ban' && parts.length > 2) {
return await execBanCommand(roomId, event, mjolnir, parts);

View File

@ -18,47 +18,47 @@ export class Lexer extends TokenizrClass {
ctx.ignore()
})
// Command rules, e.g. `!mjolnir`
this.rule("command", /![a-zA-Z_]+/, (ctx) => {
ctx.accept("command");
});
// Identifier rules, used e.g. for subcommands `get`, `set` ...
this.rule(/[a-zA-Z_]+/, (ctx) => {
this.rule("id", /[a-zA-Z_]+/, (ctx) => {
ctx.accept("id");
});
// User IDs
this.rule(/@[a-zA-Z0-9_.=\-/]+:.+/, (ctx) => {
// Users
this.rule("userID", /@[a-zA-Z0-9_.=\-/]+:.+/, (ctx) => {
ctx.accept("userID");
});
this.rule(/@[a-zA-Z0-9_.=\-?*/]+:.+/, (ctx) => {
this.rule("globUserID", /@[a-zA-Z0-9_.=\-?*/]+:.+/, (ctx) => {
ctx.accept("globUserID");
});
// User IDs
this.rule(/![a-zA-Z0-9_.=\-/]+:.+/, (ctx) => {
// Rooms
this.rule("roomID", /![a-zA-Z0-9_.=\-/]+:.+/, (ctx) => {
ctx.accept("roomID");
});
this.rule(/#[a-zA-Z0-9_.=\-/]+:.+/, (ctx) => {
this.rule("roomAlias", /#[a-zA-Z0-9_.=\-/]+:.+/, (ctx) => {
ctx.accept("roomAlias");
});
this.rule(/[#!][a-zA-Z0-9_.=\-/]+:.+/, (ctx) => {
this.rule("roomAliasOrID", /[#!][a-zA-Z0-9_.=\-/]+:.+/, (ctx) => {
ctx.accept("roomAliasOrID");
});
// Numbers.
this.rule(/[+-]?[0-9]+/, (ctx, match) => {
this.rule("int", /[+-]?[0-9]+/, (ctx, match) => {
ctx.accept("int", parseInt(match[0]))
});
// Quoted strings.
this.rule(/"((?:\\"|[^\r\n])*)"/, (ctx, match) => {
this.rule("string", /"((?:\\"|[^\r\n])*)"/, (ctx, match) => {
ctx.accept("string", match[1].replace(/\\"/g, "\""))
});
// Arbitrary non-space content.
this.rule(/\S+/, (ctx) => {
ctx.accept("nospace");
});
// Dates and durations.
this.rule(/\S+/, (ctx, match) => {
this.rule("dateOrDuration", /\S+/, (ctx, match) => {
let date = new Date(match[0]);
if (!date || Number.isNaN(date.getDate())) {
let duration = parseDuration(match[0]);
@ -73,13 +73,20 @@ export class Lexer extends TokenizrClass {
});
// Jokers.
this.rule(/\*/, (ctx) => {
this.rule("STAR", /\*/, (ctx) => {
ctx.accept("STAR");
});
this.rule(/.*/, ctx => {
ctx.accept("EVERYTHING ELSE");
this.rule(/./, (ctx) => {
ctx.accept("ANYTHING ELSE")
});
this.input(string);
}
public token(state?: string): TokenizrModule.Token {
if (typeof state === "string") {
this.state(state);
}
return super.token();
}
}

View File

@ -69,15 +69,10 @@ function formatResult(action: string, targetRoomId: string, recentJoins: Join[],
// - attempts to execute action;
// - in case of success, returns `{ok: undefined}`, in case of error, returns `{error: string}`.
async function execSinceCommandAux(destinationRoomId: string, event: any, mjolnir: Mjolnir, lexer: Lexer): Promise<Result<undefined>> {
console.debug("YORIC", "lexer", lexer);
// Attempt to parse `<date/duration>` as a date or duration.
let dateOrDurationToken: Date | number;
try {
dateOrDurationToken = lexer.alternatives(
() => lexer.consume("date"),
() => lexer.consume("duration")
);
dateOrDurationToken = lexer.token("dateOrDuration").value;
} catch (ex) {
return { error: "Invalid <date/duration>" };
}
@ -93,7 +88,7 @@ async function execSinceCommandAux(destinationRoomId: string, event: any, mjolni
}
// Attempt to parse `<action>` as Action.
let actionToken = lexer.consume("id").text;
let actionToken = lexer.token("id").text;
let action: Action | null = null;
for (let key in Action) {
const maybeAction = Action[key as keyof typeof Action];
@ -108,7 +103,7 @@ async function execSinceCommandAux(destinationRoomId: string, event: any, mjolni
console.debug("YORIC", "action", action);
// Attempt to parse `<limit>` as a number.
const maxEntries = lexer.consume("int").value as number;
const maxEntries = lexer.token("int").value as number;
console.debug("YORIC", "maxEntries", maxEntries);
// Now list affected rooms.
@ -116,8 +111,8 @@ async function execSinceCommandAux(destinationRoomId: string, event: any, mjolni
do {
try {
let token = lexer.alternatives(
() => lexer.consume("STAR"),
() => lexer.consume("roomAliasOrID"),
() => lexer.token("STAR"),
() => lexer.token("roomAliasOrID"),
);
if (token.type == "STAR") {
for (let roomId of Object.keys(mjolnir.protectedRooms)) {
@ -147,8 +142,8 @@ async function execSinceCommandAux(destinationRoomId: string, event: any, mjolni
// Parse everything else as `<reason>`, stripping quotes if any have been added.
const reason = lexer.alternatives(
() => lexer.consume("string"),
() => lexer.consume("EVERYTHING ELSE")
() => lexer.token("string"),
() => lexer.token("EVERYTHING ELSE")
).text;
console.debug("YORIC", "reason", reason);