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 { execImportCommand } from "./ImportCommand";
import { execSetDefaultListCommand } from "./SetDefaultBanListCommand"; import { execSetDefaultListCommand } from "./SetDefaultBanListCommand";
import { execDeactivateCommand } from "./DeactivateCommand"; import { execDeactivateCommand } from "./DeactivateCommand";
import { execDisableProtection, execEnableProtection, execListProtections, execConfigGetProtection, import {
execConfigSetProtection, execConfigAddProtection, execConfigRemoveProtection } from "./ProtectionsCommands"; execDisableProtection, execEnableProtection, execListProtections, execConfigGetProtection,
execConfigSetProtection, execConfigAddProtection, execConfigRemoveProtection
} from "./ProtectionsCommands";
import { execListProtectedRooms } from "./ListProtectedRoomsCommand"; import { execListProtectedRooms } from "./ListProtectedRoomsCommand";
import { execAddProtectedRoom, execRemoveProtectedRoom } from "./AddRemoveProtectedRoomsCommand"; import { execAddProtectedRoom, execRemoveProtectedRoom } from "./AddRemoveProtectedRoomsCommand";
import { execAddRoomToDirectoryCommand, execRemoveRoomFromDirectoryCommand } from "./AddRemoveRoomFromDirectoryCommand"; import { execAddRoomToDirectoryCommand, execRemoveRoomFromDirectoryCommand } from "./AddRemoveRoomFromDirectoryCommand";
@ -43,198 +45,20 @@ import { Lexer } from "./Lexer";
export const COMMAND_PREFIX = "!mjolnir"; 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) { export async function handleCommand(roomId: string, event: { content: { body: string } }, mjolnir: Mjolnir) {
const line = event['content']['body']; const line = event['content']['body'];
const parts = line.trim().split(' ').filter(p => p.trim().length > 0); const parts = line.trim().split(' ').filter(p => p.trim().length > 0);
console.debug("YORIC", "line", line); console.debug("YORIC", "line", line);
const lexer = new Lexer(line); const lexer = new Lexer(line);
lexer.token("command"); // Consume `!mjolnir`. 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); console.debug("YORIC", "cmd", cmd);
try { try {
if (cmd === '' || cmd === 'status') { if (cmd === null || cmd === 'status') {
return await execStatusCommand(roomId, event, mjolnir, parts.slice(2)); return await execStatusCommand(roomId, event, mjolnir, parts.slice(2));
} else if (cmd === 'ban' && parts.length > 2) { } else if (cmd === 'ban' && parts.length > 2) {
return await execBanCommand(roomId, event, mjolnir, parts); return await execBanCommand(roomId, event, mjolnir, parts);

View File

@ -58,10 +58,15 @@ export class Lexer extends TokenizrClass {
}); });
// Dates and durations. // Dates and durations.
this.rule("dateOrDuration", /\S+/, (ctx, match) => { try {
let date = new Date(match[0]); 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())) { if (!date || Number.isNaN(date.getDate())) {
let duration = parseDuration(match[0]); let duration = parseDuration(content);
console.debug("YORIC", "Lexer", "dateOrDuration", "duration", duration);
if (!duration || Number.isNaN(duration)) { if (!duration || Number.isNaN(duration)) {
ctx.reject(); ctx.reject();
} else { } else {
@ -71,15 +76,21 @@ export class Lexer extends TokenizrClass {
ctx.accept("date", date); ctx.accept("date", date);
} }
}); });
} catch (ex) {
console.error("YORIC", ex);
}
// Jokers. // Jokers.
this.rule("STAR", /\*/, (ctx) => { this.rule("STAR", /\*/, (ctx) => {
ctx.accept("STAR"); 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); this.input(string);
} }

View File

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