This commit is contained in:
David Teller 2022-07-19 17:35:08 +02:00
parent 7b094f3e28
commit fb54799463
3 changed files with 70 additions and 232 deletions

View File

@ -28,8 +28,10 @@ import { execRedactCommand } from "./RedactCommand";
import { execImportCommand } from "./ImportCommand";
import { execSetDefaultListCommand } from "./SetDefaultBanListCommand";
import { execDeactivateCommand } from "./DeactivateCommand";
import { execDisableProtection, execEnableProtection, execListProtections, execConfigGetProtection,
execConfigSetProtection, execConfigAddProtection, execConfigRemoveProtection } from "./ProtectionsCommands";
import {
execDisableProtection, execEnableProtection, execListProtections, execConfigGetProtection,
execConfigSetProtection, execConfigAddProtection, execConfigRemoveProtection
} from "./ProtectionsCommands";
import { execListProtectedRooms } from "./ListProtectedRoomsCommand";
import { execAddProtectedRoom, execRemoveProtectedRoom } from "./AddRemoveProtectedRoomsCommand";
import { execAddRoomToDirectoryCommand, execRemoveRoomFromDirectoryCommand } from "./AddRemoveRoomFromDirectoryCommand";
@ -43,198 +45,20 @@ 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.token("command"); // Consume `!mjolnir`.
const cmd = lexer.token("id").text;
const cmd = lexer.alternatives(
() => lexer.token("id").text,
() => null
);
console.debug("YORIC", "cmd", cmd);
try {
if (cmd === '' || cmd === 'status') {
if (cmd === null || 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

@ -58,28 +58,39 @@ export class Lexer extends TokenizrClass {
});
// Dates and durations.
this.rule("dateOrDuration", /\S+/, (ctx, match) => {
let date = new Date(match[0]);
if (!date || Number.isNaN(date.getDate())) {
let duration = parseDuration(match[0]);
if (!duration || Number.isNaN(duration)) {
ctx.reject();
try {
this.rule("dateOrDuration", /(?:"([^"]+)")|(\S+)/, (ctx, match) => {
let content = match[1] || match[2];
console.debug("YORIC", "Lexer", "dateOrDuration", content);
let date = new Date(content);
console.debug("YORIC", "Lexer", "dateOrDuration", "date", date);
if (!date || Number.isNaN(date.getDate())) {
let duration = parseDuration(content);
console.debug("YORIC", "Lexer", "dateOrDuration", "duration", duration);
if (!duration || Number.isNaN(duration)) {
ctx.reject();
} else {
ctx.accept("duration", duration);
}
} else {
ctx.accept("duration", duration);
ctx.accept("date", date);
}
} else {
ctx.accept("date", date);
}
});
});
} catch (ex) {
console.error("YORIC", ex);
}
// Jokers.
this.rule("STAR", /\*/, (ctx) => {
ctx.accept("STAR");
});
this.rule(/./, (ctx) => {
ctx.accept("ANYTHING ELSE")
// Everything left in the string.
this.rule("ETC", /.*/, (ctx) => {
ctx.accept("ETC")
});
console.debug("YORIC", "Preparing lexer", string);
this.input(string);
}

View File

@ -39,7 +39,13 @@ type Summary = { succeeded: userId[], failed: userId[] };
// !mjolnir since <date>/<duration> <action> <number> [...rooms] [...reason]
export async function execSinceCommand(destinationRoomId: string, event: any, mjolnir: Mjolnir, lexer: Lexer) {
let result = await execSinceCommandAux(destinationRoomId, event, mjolnir, lexer);
let result;
try {
result = await execSinceCommandAux(destinationRoomId, event, mjolnir, lexer);
} catch (ex) {
result = { error: ex.message };
console.error("Error executing `since` command", ex);
}
if ("error" in result) {
mjolnir.client.unstableApis.addReactionToEvent(destinationRoomId, event['event_id'], '❌');
mjolnir.logMessage(LogLevel.WARN, "SinceCommand", result.error);
@ -70,21 +76,16 @@ function formatResult(action: string, targetRoomId: string, recentJoins: Join[],
// - 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>> {
// Attempt to parse `<date/duration>` as a date or duration.
let dateOrDurationToken: Date | number;
try {
dateOrDurationToken = lexer.token("dateOrDuration").value;
} catch (ex) {
return { error: "Invalid <date/duration>" };
}
console.debug("YORIC", "dateOrDurationToken", dateOrDurationToken);
let dateOrDuration: Date |number = lexer.token("dateOrDuration").value;
console.debug("YORIC", "dateOrDuration", dateOrDuration);
let minDate;
let maxAgeMS;
if (dateOrDurationToken instanceof Date) {
minDate = dateOrDurationToken;
maxAgeMS = Date.now() - dateOrDurationToken.getTime() as number;
if (dateOrDuration instanceof Date) {
minDate = dateOrDuration;
maxAgeMS = Date.now() - dateOrDuration.getTime() as number;
} else {
minDate = new Date(Date.now() - dateOrDurationToken);
maxAgeMS = dateOrDurationToken;
minDate = new Date(Date.now() - dateOrDuration);
maxAgeMS = dateOrDuration;
}
// Attempt to parse `<action>` as Action.
@ -109,27 +110,29 @@ async function execSinceCommandAux(destinationRoomId: string, event: any, mjolni
// Now list affected rooms.
const rooms: Set</* room id */string> = new Set();
do {
try {
let token = lexer.alternatives(
() => lexer.token("STAR"),
() => lexer.token("roomAliasOrID"),
);
if (token.type == "STAR") {
for (let roomId of Object.keys(mjolnir.protectedRooms)) {
rooms.add(roomId);
}
continue;
}
if (token.type == "roomAliasOrID") {
const roomId = await mjolnir.client.resolveRoom(token.text);
if (!(roomId in mjolnir.protectedRooms)) {
return mjolnir.logMessage(LogLevel.WARN, "SinceCommand", `This room is not protected: ${htmlEscape(roomId)}.`);
}
let token = lexer.alternatives(
() => lexer.token("STAR"),
() => lexer.token("roomAliasOrID"),
);
console.debug("YORIC", "token", token);
if (!token) {
// We have reached the end of rooms.
break;
}
if (token.type === "STAR") {
for (let roomId of Object.keys(mjolnir.protectedRooms)) {
rooms.add(roomId);
continue;
}
} catch (ex) {
// If we're done with rooms, we have entered <reason>.
continue;
} else if (token.type === "roomAliasOrID") {
const roomId = await mjolnir.client.resolveRoom(token.text);
if (!(roomId in mjolnir.protectedRooms)) {
return mjolnir.logMessage(LogLevel.WARN, "SinceCommand", `This room is not protected: ${htmlEscape(roomId)}.`);
}
rooms.add(roomId);
continue;
}
if (token.type == 'EOF') {
break;
}
} while(true);
@ -143,8 +146,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.token("string"),
() => lexer.token("EVERYTHING ELSE")
).text;
() => lexer.token("ETC")
)?.text || "";
console.debug("YORIC", "reason", reason);
const progressEventId = await mjolnir.client.unstableApis.addReactionToEvent(destinationRoomId, event['event_id'], '⏳');