A new command since to affect all users who have joined a protected room since a given date.

This commit is contained in:
David Teller 2022-02-15 10:54:12 +01:00
parent 26ae55cd24
commit 98679ca6e1
10 changed files with 656 additions and 167 deletions

View File

@ -20,9 +20,11 @@
"@types/config": "0.0.41", "@types/config": "0.0.41",
"@types/crypto-js": "^4.0.2", "@types/crypto-js": "^4.0.2",
"@types/html-to-text": "^8.0.1", "@types/html-to-text": "^8.0.1",
"@types/humanize-duration": "^3.27.1",
"@types/jsdom": "^16.2.11", "@types/jsdom": "^16.2.11",
"@types/mocha": "^9.0.0", "@types/mocha": "^9.0.0",
"@types/node": "^16.7.10", "@types/node": "^16.7.10",
"@types/shell-quote": "^1.7.1",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"eslint": "^7.32", "eslint": "^7.32",
"expect": "^27.0.6", "expect": "^27.0.6",
@ -36,11 +38,13 @@
"config": "^3.3.6", "config": "^3.3.6",
"express": "^4.17", "express": "^4.17",
"html-to-text": "^8.0.0", "html-to-text": "^8.0.0",
"humanize-duration": "^3.27.1",
"humanize-duration-ts": "^2.1.1", "humanize-duration-ts": "^2.1.1",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jsdom": "^16.6.0", "jsdom": "^16.6.0",
"matrix-bot-sdk": "^0.5.19", "matrix-bot-sdk": "^0.5.19",
"parse-duration": "^1.0.2" "parse-duration": "^1.0.2",
"shell-quote": "^1.7.3"
}, },
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"

View File

@ -602,6 +602,12 @@ export class Mjolnir {
return list; return list;
} }
/**
* Get a protection by name.
*
* @return If there is a protection with this name *and* it is enabled,
* return the protection.
*/
public getProtection(protectionName: string): Protection | null { public getProtection(protectionName: string): Protection | null {
return this.protections.get(protectionName) ?? null; return this.protections.get(protectionName) ?? null;
} }

View File

@ -38,6 +38,8 @@ import { execShutdownRoomCommand } from "./ShutdownRoomCommand";
import { execAddAliasCommand, execMoveAliasCommand, execRemoveAliasCommand, execResolveCommand } from "./AliasCommands"; import { execAddAliasCommand, execMoveAliasCommand, execRemoveAliasCommand, execResolveCommand } from "./AliasCommands";
import { execKickCommand } from "./KickCommand"; import { execKickCommand } from "./KickCommand";
import { execMakeRoomAdminCommand } from "./MakeRoomAdminCommand"; import { execMakeRoomAdminCommand } from "./MakeRoomAdminCommand";
import { parse as tokenize } from "shell-quote";
import { execSinceCommand } from "./SinceCommand";
export const COMMAND_PREFIX = "!mjolnir"; export const COMMAND_PREFIX = "!mjolnir";
@ -46,6 +48,9 @@ export async function handleCommand(roomId: string, event: { content: { body: st
const cmd = event['content']['body']; const cmd = event['content']['body'];
const parts = cmd.trim().split(' ').filter(p => p.trim().length > 0); const parts = cmd.trim().split(' ').filter(p => p.trim().length > 0);
// A shell-style parser that can parse `"a b c"` (with quotes) as a single argument.
const tokens = tokenize(cmd).slice(/* "!mjolnir" "command" */ 2);
try { try {
if (parts.length === 1 || parts[1] === 'status') { if (parts.length === 1 || parts[1] === 'status') {
return await execStatusCommand(roomId, event, mjolnir, parts.slice(2)); return await execStatusCommand(roomId, event, mjolnir, parts.slice(2));
@ -109,6 +114,8 @@ export async function handleCommand(roomId: string, event: { content: { body: st
return await execSetPowerLevelCommand(roomId, event, mjolnir, parts); return await execSetPowerLevelCommand(roomId, event, mjolnir, parts);
} else if (parts[1] === 'shutdown' && parts[2] === 'room' && parts.length > 3) { } else if (parts[1] === 'shutdown' && parts[2] === 'room' && parts.length > 3) {
return await execShutdownRoomCommand(roomId, event, mjolnir, parts); return await execShutdownRoomCommand(roomId, event, mjolnir, parts);
} else if (parts[1] === 'since') {
return await execSinceCommand(roomId, event, mjolnir, tokens);
} else if (parts[1] === 'kick' && parts.length > 2) { } else if (parts[1] === 'kick' && parts.length > 2) {
return await execKickCommand(roomId, event, mjolnir, parts); return await execKickCommand(roomId, event, mjolnir, parts);
} else if (parts[1] === 'make' && parts[2] === 'admin' && parts.length > 3) { } else if (parts[1] === 'make' && parts[2] === 'admin' && parts.length > 3) {
@ -149,6 +156,7 @@ export async function handleCommand(roomId: string, event: { content: { body: st
"!mjolnir alias add <room alias> <target room alias/ID> - Adds <room alias> to <target room>\n" + "!mjolnir alias add <room alias> <target room alias/ID> - Adds <room alias> to <target room>\n" +
"!mjolnir alias remove <room alias> - Deletes the room alias from whatever room it is attached to\n" + "!mjolnir alias remove <room alias> - Deletes the room alias from whatever room it is attached to\n" +
"!mjolnir resolve <room alias> - Resolves a room alias to a room ID\n" + "!mjolnir resolve <room alias> - Resolves a room alias to a room ID\n" +
"!mjolnir since <date>/<duration> <action> [reason] [rooms...] - Apply an action to all users who joined a room since a given date" +
"!mjolnir shutdown room <room alias/ID> [message] - Uses the bot's account to shut down a room, preventing access to the room on this server\n" + "!mjolnir shutdown room <room alias/ID> [message] - Uses the bot's account to shut down a room, preventing access to the room on this server\n" +
"!mjolnir powerlevel <user ID> <power level> [room alias/ID] - Sets the power level of the user in the specified room (or all protected rooms)\n" + "!mjolnir powerlevel <user ID> <power level> [room alias/ID] - Sets the power level of the user in the specified room (or all protected rooms)\n" +
"!mjolnir make admin <room alias> [user alias/ID] - Make the specified user or the bot itself admin of the room\n" + "!mjolnir make admin <room alias> [user alias/ID] - Make the specified user or the bot itself admin of the room\n" +

View File

@ -18,7 +18,7 @@ import { Mjolnir } from "../Mjolnir";
import { LogLevel } from "matrix-bot-sdk"; import { LogLevel } from "matrix-bot-sdk";
import config from "../config"; import config from "../config";
// !mjolnir kick <user> [room] [reason] // !mjolnir kick <user|filter> [room] [reason]
export async function execKickCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) { export async function execKickCommand(roomId: string, event: any, mjolnir: Mjolnir, parts: string[]) {
const userId = parts[2]; const userId = parts[2];

View File

@ -0,0 +1,240 @@
/*
Copyright 2020 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { Mjolnir } from "../Mjolnir";
import { LogLevel, LogService, RichReply } from "matrix-bot-sdk";
import { htmlEscape, parseDuration } from "../utils";
import { ParseEntry } from "shell-quote";
import { HumanizeDurationLanguage, HumanizeDuration } from "humanize-duration-ts";
const HUMANIZE_LAG_SERVICE: HumanizeDurationLanguage = new HumanizeDurationLanguage();
const HUMANIZER: HumanizeDuration = new HumanizeDuration(HUMANIZE_LAG_SERVICE);
enum Action {
Kick = "kick",
Ban = "ban",
Show = "show"
}
type Result<T> = {ok: T} | {error: string};
function parseToken<T>(name: string, token: ParseEntry, parser: (source: string) => Result<T>): Result<T> {
if (!token) {
return { error: `Missing ${name}`};
}
if (typeof token === "object" && "pattern" in token) {
// In future versions, we *might* be smarter patterns, but not yet.
token = token.pattern;
}
if (typeof token !== "string") {
return { error: `Invalid ${name}` };
}
const result = parser(token);
if ("error" in result) {
if (result.error) {
return { error: `Invalid ${name} ${htmlEscape(token)}: ${result.error}`};
} else {
return { error: `Invalid ${name} ${htmlEscape(token)}`};
}
}
return result;
}
function getTokenAsString(name: string, token: ParseEntry): {error: string}|{ok: string} {
if (!token) {
return { error: `Missing ${name}`};
}
if (typeof token === "object" && "pattern" in token) {
// In future versions, we *might* be smarter patterns, but not yet.
token = token.pattern;
}
if (typeof token === "string") {
return {ok: token};
}
return { error: `Invalid ${name}` };
}
// !mjolnir since <date>/<duration> <action> <number> [reason] [...rooms]
export async function execSinceCommand(destinationRoomId: string, event: any, mjolnir: Mjolnir, tokens: ParseEntry[]) {
const [dateOrDurationToken, actionToken, maxEntriesToken, ...optionalTokens] = tokens;
// Parse origin date or duration.
const minDateResult = parseToken("<date>/<duration>", dateOrDurationToken, source => {
// Attempt to parse `<date>/<duration>` as a date.
let maybeMinDate = new Date(source);
let maybeMaxAgeMS = Date.now() - maybeMinDate.getTime() as number;
if (!Number.isNaN(maybeMaxAgeMS)) {
return { ok: { minDate: maybeMinDate, maxAgeMS: maybeMaxAgeMS} };
}
//...or as a duration
maybeMaxAgeMS = parseDuration(source);
if (maybeMaxAgeMS && !Number.isNaN(maybeMaxAgeMS)) {
maybeMaxAgeMS = Math.abs(maybeMaxAgeMS);
return { ok: { minDate: new Date(Date.now() - maybeMaxAgeMS), maxAgeMS: maybeMaxAgeMS } }
}
return { error: "" };
});
if ("error" in minDateResult) {
return mjolnir.logMessage(LogLevel.WARN, "SinceCommand", minDateResult.error);
}
const { minDate, maxAgeMS } = minDateResult.ok!;
// Parse max entries.
const maxEntriesResult = parseToken("<maxEntries>", maxEntriesToken, source => {
const maybeMaxEntries = Number.parseInt(source, 10);
if (Number.isNaN(maybeMaxEntries)) {
return { error: "Not a number" };
} else {
return { ok: maybeMaxEntries };
}
});
if ("error" in maxEntriesResult) {
return mjolnir.logMessage(LogLevel.WARN, "SinceCommand", maxEntriesResult.error);
}
const maxEntries = maxEntriesResult.ok!;
// Attempt to parse `<action>` as Action.
const actionResult = parseToken("<action>", actionToken, source => {
for (let key in Action) {
const maybeAction = Action[key as keyof typeof Action];
if (key === source) {
return { ok: maybeAction }
} else if (maybeAction === source) {
return { ok: maybeAction }
}
}
return {error: `Expected one of ${JSON.stringify(Action)}`};
})
if ("error" in actionResult) {
return mjolnir.logMessage(LogLevel.WARN, "SinceCommand", actionResult.error);
}
const action: Action = actionResult.ok!;
// Now list affected rooms.
const rooms: Set<string> = new Set();
let reason: string | undefined;
for (let i = 0; i < optionalTokens.length; ++i) {
const token = optionalTokens[i];
const maybeRoomResult = getTokenAsString("[room]", token);
if ("error" in maybeRoomResult) {
return mjolnir.logMessage(LogLevel.WARN, "SinceCommand", maybeRoomResult.error);
}
const maybeRoom = maybeRoomResult.ok!;
if (maybeRoom === "*") {
for (let roomId of Object.keys(mjolnir.protectedRooms)) {
rooms.add(roomId);
}
} else if (maybeRoom.startsWith("#") || maybeRoom.startsWith("!")) {
const roomId = await mjolnir.client.resolveRoom(maybeRoom);
if (!(roomId in mjolnir.protectedRooms)) {
return mjolnir.logMessage(LogLevel.WARN, "SinceCommand", `This room is not protected: ${htmlEscape(roomId)}.`);
}
rooms.add(roomId);
} else {
if (i === 0) {
// First argument may not be a room.
reason = maybeRoom;
} else {
return mjolnir.logMessage(LogLevel.WARN, "SinceCommand", `Invalid room ${htmlEscape(maybeRoom)}.`);
}
}
}
if (rooms.size === 0) {
return mjolnir.logMessage(LogLevel.WARN, "SinceCommand", `Missing rooms. Use "*" if you wish to apply to every protected room.`);
}
const progressEventId = await mjolnir.client.unstableApis.addReactionToEvent(destinationRoomId, event['event_id'], '⏳');
for (let targetRoomId of rooms) {
let {html, text} = await (async () => {
switch (action) {
case Action.Show: {
return makeJoinStatus(mjolnir, targetRoomId, maxEntries, minDate, maxAgeMS);
}
case Action.Kick: {
const joins = mjolnir.roomJoins.getUsersInRoom(targetRoomId, minDate, maxEntries);
let results = { good: 0, bad: 0};
for (let join of joins) {
try {
await mjolnir.client.kickUser(join.userId, targetRoomId, reason);
results.good += 1;
} catch (ex) {
LogService.warn("SinceCommand", "Error while attempting to kick user", ex);
results.bad += 1;
}
}
const text_ = `Attempted to kick ${joins.length} users from room ${targetRoomId}, ${results.good} kicked, ${results.bad} failures`;
return {
html: text_,
text: text_,
}
}
case Action.Ban: {
const joins = mjolnir.roomJoins.getUsersInRoom(targetRoomId, minDate, maxEntries);
let results = { good: 0, bad: 0};
for (let join of joins) {
try {
await mjolnir.client.banUser(join.userId, targetRoomId, reason);
results.good += 1;
} catch (ex) {
LogService.warn("SinceCommand", "Error while attempting to ban user", ex);
results.bad += 1;
}
}
const text_ = `Attempted to ban ${joins.length} users from room ${targetRoomId}, ${results.good} kicked, ${results.bad} failures`;
return {
html: text_,
text: text_
}
}
}
})();
const reply = RichReply.createFor(destinationRoomId, event, text, html);
reply["msgtype"] = "m.notice";
/* no need to await */ mjolnir.client.sendMessage(destinationRoomId, reply);
}
await mjolnir.client.redactEvent(destinationRoomId, progressEventId);
mjolnir.client.unstableApis.addReactionToEvent(destinationRoomId, event['event_id'], '✅');
}
function makeJoinStatus(mjolnir: Mjolnir, targetRoomId: string, maxEntries: number, minDate: Date, maxAgeMS: number): {html: string, text: string} {
const HUMANIZER_OPTIONS = {
// Reduce "1 day" => "1day" to simplify working with CSV.
spacer: "",
// Reduce "1 day, 2 hours" => "1.XXX day" to simplify working with CSV.
largest: 1,
};
const maxAgeHumanReadable = HUMANIZER.humanize(maxAgeMS, HUMANIZER_OPTIONS);
const joins = mjolnir.roomJoins.getUsersInRoom(targetRoomId, minDate, maxEntries);
const htmlFragments = [];
const textFragments = [];
for (let join of joins) {
const durationHumanReadable = HUMANIZER.humanize(Date.now() - join.timestamp, HUMANIZER_OPTIONS);
htmlFragments.push(`<li>${htmlEscape(join.userId)}: ${durationHumanReadable}</li>`);
textFragments.push(`- ${join.userId}: ${durationHumanReadable}`);
}
return {
html: `${joins.length} recent joins (cut at ${maxAgeHumanReadable} ago / ${maxEntries} entries): <ul> ${htmlFragments.join()} </ul>`,
text: `${joins.length} recent joins (cut at ${maxAgeHumanReadable} ago / ${maxEntries} entries):\n${textFragments.join("\n")}`
}
}

View File

@ -16,17 +16,9 @@ limitations under the License.
import { Mjolnir, STATE_CHECKING_PERMISSIONS, STATE_NOT_STARTED, STATE_RUNNING, STATE_SYNCING } from "../Mjolnir"; import { Mjolnir, STATE_CHECKING_PERMISSIONS, STATE_NOT_STARTED, STATE_RUNNING, STATE_SYNCING } from "../Mjolnir";
import { RichReply } from "matrix-bot-sdk"; import { RichReply } from "matrix-bot-sdk";
import { htmlEscape } from "../utils"; import { htmlEscape, parseDuration } from "../utils";
import { default as parseDuration } from "parse-duration";
import { HumanizeDurationLanguage, HumanizeDuration } from "humanize-duration-ts"; import { HumanizeDurationLanguage, HumanizeDuration } from "humanize-duration-ts";
// Define a few aliases to simplify parsing durations.
parseDuration["days"] = parseDuration["day"];
parseDuration["weeks"] = parseDuration["week"] = parseDuration["wk"];
parseDuration["months"] = parseDuration["month"];
parseDuration["years"] = parseDuration["year"];
const HUMANIZE_LAG_SERVICE: HumanizeDurationLanguage = new HumanizeDurationLanguage(); const HUMANIZE_LAG_SERVICE: HumanizeDurationLanguage = new HumanizeDurationLanguage();
const HUMANIZER: HumanizeDuration = new HumanizeDuration(HUMANIZE_LAG_SERVICE); const HUMANIZER: HumanizeDuration = new HumanizeDuration(HUMANIZE_LAG_SERVICE);
@ -36,10 +28,10 @@ export async function execStatusCommand(roomId: string, event: any, mjolnir: Mjo
case undefined: case undefined:
case 'mjolnir': case 'mjolnir':
return showMjolnirStatus(roomId, event, mjolnir); return showMjolnirStatus(roomId, event, mjolnir);
case 'protection':
return showProtectionStatus(roomId, event, mjolnir, parts.slice(/* ["protection"] */ 1));
case 'joins': case 'joins':
return showJoinsStatus(roomId, event, mjolnir, parts.slice(/* ["joins"] */ 1)); return showJoinsStatus(roomId, event, mjolnir, parts.slice(/* ["joins"] */ 1));
case 'protection':
return showProtectionStatus(roomId, event, mjolnir, parts.slice(/* ["protection"] */ 1));
default: default:
throw new Error(`Invalid status command: ${htmlEscape(parts[0])}`); throw new Error(`Invalid status command: ${htmlEscape(parts[0])}`);
} }

View File

@ -30,6 +30,18 @@ import {
import { Mjolnir } from "./Mjolnir"; import { Mjolnir } from "./Mjolnir";
import config from "./config"; import config from "./config";
import { ClientRequest, IncomingMessage } from "http"; import { ClientRequest, IncomingMessage } from "http";
import { default as parseDuration } from "parse-duration";
// Define a few aliases to simplify parsing durations.
parseDuration["days"] = parseDuration["day"];
parseDuration["weeks"] = parseDuration["week"] = parseDuration["wk"];
parseDuration["months"] = parseDuration["month"];
parseDuration["years"] = parseDuration["year"];
// ... and reexport it
export { parseDuration };
export function htmlEscape(input: string): string { export function htmlEscape(input: string): string {
return input.replace(/["&<>]/g, (char: string) => ({ return input.replace(/["&<>]/g, (char: string) => ({

View File

@ -10,7 +10,23 @@ import * as crypto from "crypto";
* @param targetEventThunk A function that produces an event ID when called. This event ID is then used to listen for a reply. * @param targetEventThunk A function that produces an event ID when called. This event ID is then used to listen for a reply.
* @returns The replying event. * @returns The replying event.
*/ */
export async function getFirstReply(client: MatrixClient, targetRoom: string, targetEventThunk: () => Promise<string>): Promise<any> { export async function getFirstReply(client: MatrixClient, targetRoom: string, targetEventThunk: () => Promise<string>): Promise<any> {
return getNthReply(client, targetRoom, 1, targetEventThunk);
}
/**
* Returns a promise that resolves to the nth event replying to the event produced by targetEventThunk.
* @param client A MatrixClient that is already in the targetRoom. We will use it to listen for the event produced by targetEventThunk.
* This function assumes that the start() has already been called on the client.
* @param targetRoom The room to listen for the reply in.
* @param n The number of events to wait for. Must be >= 1.
* @param targetEventThunk A function that produces an event ID when called. This event ID is then used to listen for a reply.
* @returns The replying event.
*/
export async function getNthReply(client: MatrixClient, targetRoom: string, n: number, targetEventThunk: () => Promise<string>): Promise<any> {
if (Number.isNaN(n) || !Number.isInteger(n) || n <= 0) {
throw new TypeError(`Invalid number of events ${n}`);
}
let reactionEvents = []; let reactionEvents = [];
const addEvent = function (roomId, event) { const addEvent = function (roomId, event) {
if (roomId !== targetRoom) return; if (roomId !== targetRoom) return;
@ -27,7 +43,10 @@ import * as crypto from "crypto";
for (let event of reactionEvents) { for (let event of reactionEvents) {
const in_reply_to = event.content['m.relates_to']?.['m.in_reply_to']; const in_reply_to = event.content['m.relates_to']?.['m.in_reply_to'];
if (in_reply_to?.event_id === targetEventId) { if (in_reply_to?.event_id === targetEventId) {
return event; n -= 1;
if (n == 0) {
return event;
}
} }
} }
return await new Promise(resolve => { return await new Promise(resolve => {
@ -36,7 +55,10 @@ import * as crypto from "crypto";
if (event.type !== 'm.room.message') return; if (event.type !== 'm.room.message') return;
const in_reply_to = event.content['m.relates_to']?.['m.in_reply_to']; const in_reply_to = event.content['m.relates_to']?.['m.in_reply_to'];
if (in_reply_to?.event_id === targetEventId) { if (in_reply_to?.event_id === targetEventId) {
resolve(event) n -= 1;
if (n == 0) {
resolve(event);
}
} }
} }
client.on('room.event', targetCb); client.on('room.event', targetCb);
@ -50,7 +72,6 @@ import * as crypto from "crypto";
} }
/** /**
* Returns a promise that resolves to an event that is reacting to the event produced by targetEventThunk. * Returns a promise that resolves to an event that is reacting to the event produced by targetEventThunk.
* @param client A MatrixClient that is already in the targetRoom that can be started to listen for the event produced by targetEventThunk. * @param client A MatrixClient that is already in the targetRoom that can be started to listen for the event produced by targetEventThunk.

View File

@ -1,8 +1,11 @@
import { strict as assert } from "assert"; import { strict as assert } from "assert";
import { SynapseRoomProperty } from "matrix-bot-sdk"; import { SynapseRoomProperty } from "matrix-bot-sdk";
import { COMMAND_PREFIX } from "../../src/commands/CommandHandler";
import { execKickCommand } from "../../src/commands/KickCommand";
import { Mjolnir } from "../../src/Mjolnir";
import { RoomMemberManager } from "../../src/RoomMembers"; import { RoomMemberManager } from "../../src/RoomMembers";
import { newTestUser } from "./clientHelper"; import { newTestUser } from "./clientHelper";
import { getFirstReply } from "./commands/commandUtils"; import { getFirstReply, getNthReply } from "./commands/commandUtils";
describe("Test: Testing RoomMemberManager", function() { describe("Test: Testing RoomMemberManager", function() {
it("RoomMemberManager counts correctly when we call handleEvent manually", function() { it("RoomMemberManager counts correctly when we call handleEvent manually", function() {
@ -245,8 +248,8 @@ describe("Test: Testing RoomMemberManager", function() {
afterEach(async function() { afterEach(async function() {
await this.moderator?.stop(); await this.moderator?.stop();
if (this.users) { for (let array of [this.users, this.goodUsers, this.badUsers]) {
for (let client of this.users) { for (let client of array || []) {
await client.stop(); await client.stop();
} }
} }
@ -374,4 +377,200 @@ describe("Test: Testing RoomMemberManager", function() {
} }
} }
}); });
it("!mjolnir since kicks the correct users", async function() {
this.timeout(600_000);
const start = new Date(Date.now() - 10_000);
// Setup a moderator.
this.moderator = await newTestUser({ name: { contains: "moderator" } });
await this.moderator.joinRoom(this.mjolnir.managementRoomId);
// Create a few users.
this.goodUsers = [];
this.badUsers = [];
const SAMPLE_SIZE = 10;
for (let i = 0; i < SAMPLE_SIZE; ++i) {
this.goodUsers.push(await newTestUser({ name: { contains: `good_user_${i}_room_member_test` } }));
this.badUsers.push(await newTestUser({ name: { contains: `bad_user_${i}_room_member_test` } }));
}
const goodUserIds = [];
const badUserIds = [];
for (let client of this.goodUsers) {
goodUserIds.push(await client.getUserId());
}
for (let client of this.badUsers) {
badUserIds.push(await client.getUserId());
}
// Create and protect rooms.
// - room 0 remains unprotected, as witness;
// - room 1 is protected but won't be targeted directly, also as witness.
const NUMBER_OF_ROOMS = 12;
const roomIds = [];
const mjolnirUserId = await this.mjolnir.client.getUserId();
for (let i = 0; i < NUMBER_OF_ROOMS; ++i) {
const roomId = await this.moderator.createRoom({
invite: [mjolnirUserId, ...goodUserIds, ...badUserIds],
});
roomIds.push(roomId);
}
for (let i = 1; i < roomIds.length; ++i) {
// Protect all rooms except roomIds[0], as witness.
const roomId = roomIds[i];
await this.mjolnir.client.joinRoom(roomId);
await this.moderator.setUserPowerLevel(mjolnirUserId, roomId, 100);
await this.moderator.sendMessage(this.mjolnir.managementRoomId, { msgtype: 'm.text', body: `!mjolnir rooms add ${roomId}` });
}
let protectedRoomsUpdated = false;
do {
let protectedRooms = this.mjolnir.protectedRooms;
protectedRoomsUpdated = true;
for (let i = 1; i < roomIds.length; ++i) {
const roomId = roomIds[i];
if (!(roomId in protectedRooms)) {
protectedRoomsUpdated = false;
await new Promise(resolve => setTimeout(resolve, 1_000));
}
}
} while (!protectedRoomsUpdated);
// Good users join before cut date.
for (let user of this.goodUsers) {
for (let roomId of roomIds) {
await user.joinRoom(roomId);
}
}
await new Promise(resolve => setTimeout(resolve, 5_000));
const cutDate = new Date();
await new Promise(resolve => setTimeout(resolve, 5_000));
// Bad users join after cut date.
for (let user of this.badUsers) {
for (let roomId of roomIds) {
await user.joinRoom(roomId);
}
}
enum Method {
kick,
ban
}
const WITNESS_UNPROTECTED_ROOM_ID = roomIds[0];
const WITNESS_ROOM_ID = roomIds[1];
const EXPERIMENTS = [
// Kick bad users in one room, using duration syntax, no reason.
{
name: "kick with duration",
command: roomId => `!mjolnir since ${Date.now() - cutDate.getTime()}ms kick 100 ${roomId}`,
shouldAffectWitnessRoom: false,
n: 1,
method: Method.kick,
},
// Ban bad users in one room, using duration syntax, no reason.
{
name: "ban with duration",
command: roomId => `!mjolnir since ${Date.now() - cutDate.getTime()}ms ban 100 ${roomId}`,
shouldAffectWitnessRoom: false,
n: 1,
method: Method.ban,
},
// Kick bad users in one room, using date syntax, no reason.
{
name: "kick with date",
command: roomId => `!mjolnir since "${cutDate}" kick 100 ${roomId}`,
shouldAffectWitnessRoom: false,
n: 1,
method: Method.kick,
},
// Ban bad users in one room, using date syntax, no reason.
{
name: "ban with date",
command: roomId => `!mjolnir since "${cutDate}" ban 100 ${roomId}`,
shouldAffectWitnessRoom: false,
n: 1,
method: Method.ban,
},
// Kick bad users in one room, using duration syntax, with reason.
{
name: "kick with duration and reason",
command: roomId => `!mjolnir since ${Date.now() - cutDate.getTime()}ms kick 100 "bad, bad user" ${roomId}`,
shouldAffectWitnessRoom: false,
n: 1,
method: Method.kick,
},
// Ban bad users in one room, using duration syntax, with reason.
{
name: "ban with duration and reason",
command: roomId => `!mjolnir since ${Date.now() - cutDate.getTime()}ms ban 100 "bad, bad user" ${roomId}`,
shouldAffectWitnessRoom: false,
n: 1,
method: Method.ban,
},
// Kick bad users in one room, using date syntax, with reason.
{
name: "kick with date and reason",
command: roomId => `!mjolnir since "${cutDate}" kick 100 "bad, bad user" ${roomId}`,
shouldAffectWitnessRoom: false,
n: 1,
method: Method.kick,
},
// Ban bad users in one room, using date syntax, with reason.
{
name: "ban with date and reason",
command: roomId => `!mjolnir since "${cutDate}" ban 100 "bad, bad user" ${roomId}`,
shouldAffectWitnessRoom: false,
n: 1,
method: Method.ban,
},
// Kick bad users everywhere, no reason
{
name: "kick with date everywhere",
command: () => `!mjolnir since "${cutDate}" kick 100 "bad, bad user" *`,
shouldAffectWitnessRoom: true,
n: NUMBER_OF_ROOMS - 1,
method: Method.kick,
}
];
for (let i = 0; i < EXPERIMENTS.length; ++i) {
const experiment = EXPERIMENTS[i];
const roomId = roomIds[i + 2];
const joined = this.mjolnir.roomJoins.getUsersInRoom(roomId, start, 100);
assert.ok(joined.length >= 2 * SAMPLE_SIZE, `We should have seen ${2 * SAMPLE_SIZE} users, saw ${joined.length}`);
await getNthReply(this.mjolnir.client, this.mjolnir.managementRoomId, experiment.n, async () => {
const command = experiment.command(roomId);
let result = await this.moderator.sendMessage(this.mjolnir.managementRoomId, { msgtype: 'm.text', body: command });
return result;
});
const usersInRoom = await this.mjolnir.client.getJoinedRoomMembers(roomId);
const usersInUnprotectedWitnessRoom = await this.mjolnir.client.getJoinedRoomMembers(WITNESS_UNPROTECTED_ROOM_ID);
const usersInWitnessRoom = await this.mjolnir.client.getJoinedRoomMembers(WITNESS_ROOM_ID);
for (let userId of goodUserIds) {
assert.ok(usersInRoom.includes(userId), `After a ${experiment.name}, good user ${userId} should still be in affected room`);
assert.ok(usersInWitnessRoom.includes(userId), `After a ${experiment.name}, good user ${userId} should still be in witness room`);
assert.ok(usersInUnprotectedWitnessRoom.includes(userId), `After a ${experiment.name}, good user ${userId} should still be in unprotected witness room`);
}
for (let userId of badUserIds) {
assert.ok(!usersInRoom.includes(userId), `After a ${experiment.name}, bad user ${userId} should NOT be in affected room`);
assert.equal(usersInWitnessRoom.includes(userId), !experiment.shouldAffectWitnessRoom, `After a ${experiment.name}, bad user ${userId} should ${experiment.shouldAffectWitnessRoom ? "NOT" : "still"} be in witness room`);
assert.ok(usersInUnprotectedWitnessRoom.includes(userId), `After a ${experiment.name}, bad user ${userId} should still be in unprotected witness room`);
const leaveEvent = await this.mjolnir.client.getRoomStateEvent(roomId, "m.room.member", userId);
switch (experiment.method) {
case Method.kick:
assert.equal(leaveEvent.membership, "leave");
break;
case Method.ban:
assert.equal(leaveEvent.membership, "ban");
break;
}
}
}
});
}); });

297
yarn.lock
View File

@ -70,11 +70,6 @@
"@types/yargs" "^16.0.0" "@types/yargs" "^16.0.0"
chalk "^4.0.0" chalk "^4.0.0"
"@napi-rs/cli@^2.2.0":
version "2.4.4"
resolved "https://registry.yarnpkg.com/@napi-rs/cli/-/cli-2.4.4.tgz#878a38f0fba1709d89d66eba706745ce728a61a5"
integrity sha512-f+tvwCv1ka24dBqI2DgBhR7Oxl3DKHOp4onxLXwyBFt6iCADnr3YZIr1/2Iq5r3uqxFgaf01bfPsRQZPkEp0kQ==
"@selderee/plugin-htmlparser2@^0.6.0": "@selderee/plugin-htmlparser2@^0.6.0":
version "0.6.0" version "0.6.0"
resolved "https://registry.yarnpkg.com/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.6.0.tgz#27e994afd1c2cb647ceb5406a185a5574188069d" resolved "https://registry.yarnpkg.com/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.6.0.tgz#27e994afd1c2cb647ceb5406a185a5574188069d"
@ -88,14 +83,6 @@
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@turt2live/matrix-sdk-crypto-nodejs@^0.1.0-beta.10":
version "0.1.0-beta.10"
resolved "https://registry.yarnpkg.com/@turt2live/matrix-sdk-crypto-nodejs/-/matrix-sdk-crypto-nodejs-0.1.0-beta.10.tgz#9b0a8e1f48badeb37a0b0f8eb0fb6dc9bbb1949a"
integrity sha512-y5TA8fD5a7xaIwjZhQ66eT3scDsU47GkcCuQ0vjlXB0shY2cCMB4MF1nY/7c1/DniM+KvDXxrhs2VXphlPLpaA==
dependencies:
"@napi-rs/cli" "^2.2.0"
shelljs "^0.8.4"
"@types/body-parser@*": "@types/body-parser@*":
version "1.19.1" version "1.19.1"
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.1.tgz#0c0174c42a7d017b818303d4b5d969cb0b75929c" resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.1.tgz#0c0174c42a7d017b818303d4b5d969cb0b75929c"
@ -130,7 +117,7 @@
"@types/qs" "*" "@types/qs" "*"
"@types/range-parser" "*" "@types/range-parser" "*"
"@types/express@^4.17.13": "@types/express@^4.17.7":
version "4.17.13" version "4.17.13"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034"
integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==
@ -145,6 +132,11 @@
resolved "https://registry.yarnpkg.com/@types/html-to-text/-/html-to-text-8.0.1.tgz#e449513df2283b1adedc85bdc2f6b7187f32972a" resolved "https://registry.yarnpkg.com/@types/html-to-text/-/html-to-text-8.0.1.tgz#e449513df2283b1adedc85bdc2f6b7187f32972a"
integrity sha512-0B/OifmJYmk5r9z9+KJtGWOF0LEjbTN4D2QeCh+mAw81JkJwC83NvNWUZFEqRT5PpnjX7vX0ab1SMGcwCs3Lag== integrity sha512-0B/OifmJYmk5r9z9+KJtGWOF0LEjbTN4D2QeCh+mAw81JkJwC83NvNWUZFEqRT5PpnjX7vX0ab1SMGcwCs3Lag==
"@types/humanize-duration@^3.27.1":
version "3.27.1"
resolved "https://registry.yarnpkg.com/@types/humanize-duration/-/humanize-duration-3.27.1.tgz#f14740d1f585a0a8e3f46359b62fda8b0eaa31e7"
integrity sha512-K3e+NZlpCKd6Bd/EIdqjFJRFHbrq5TzPPLwREk5Iv/YoIjQrs6ljdAUCo+Lb2xFlGNOjGSE0dqsVD19cZL137w==
"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762"
@ -216,6 +208,11 @@
"@types/mime" "^1" "@types/mime" "^1"
"@types/node" "*" "@types/node" "*"
"@types/shell-quote@^1.7.1":
version "1.7.1"
resolved "https://registry.yarnpkg.com/@types/shell-quote/-/shell-quote-1.7.1.tgz#2d059091214a02c29f003f591032172b2aff77e8"
integrity sha512-SWZ2Nom1pkyXCDohRSrkSKvDh8QOG9RfAsrt5/NsPQC4UQJ55eG0qClA40I+Gkez4KTQ0uDUT8ELRXThf3J5jw==
"@types/stack-utils@^2.0.0": "@types/stack-utils@^2.0.0":
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
@ -256,6 +253,14 @@ accepts@~1.3.7:
mime-types "~2.1.24" mime-types "~2.1.24"
negotiator "0.6.2" negotiator "0.6.2"
accepts@~1.3.8:
version "1.3.8"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
dependencies:
mime-types "~2.1.34"
negotiator "0.6.3"
acorn-globals@^6.0.0: acorn-globals@^6.0.0:
version "6.0.0" version "6.0.0"
resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45"
@ -311,11 +316,6 @@ ajv@^8.0.1:
require-from-string "^2.0.2" require-from-string "^2.0.2"
uri-js "^4.2.2" uri-js "^4.2.2"
another-json@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/another-json/-/another-json-0.2.0.tgz#b5f4019c973b6dd5c6506a2d93469cb6d32aeedc"
integrity sha1-tfQBnJc7bdXGUGotk0acttMq7tw=
ansi-colors@4.1.1, ansi-colors@^4.1.1: ansi-colors@4.1.1, ansi-colors@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348"
@ -452,20 +452,20 @@ body-parser@1.19.0:
raw-body "2.4.0" raw-body "2.4.0"
type-is "~1.6.17" type-is "~1.6.17"
body-parser@1.19.1: body-parser@1.19.2:
version "1.19.1" version "1.19.2"
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.1.tgz#1499abbaa9274af3ecc9f6f10396c995943e31d4" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.2.tgz#4714ccd9c157d44797b8b5607d72c0b89952f26e"
integrity sha512-8ljfQi5eBk8EJfECMrgqNGWPEY5jWP+1IzkzkGdFFEwFQZZyaZ21UqdaHktgiMlH0xLHqIFtE/u2OYE5dOtViA== integrity sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==
dependencies: dependencies:
bytes "3.1.1" bytes "3.1.2"
content-type "~1.0.4" content-type "~1.0.4"
debug "2.6.9" debug "2.6.9"
depd "~1.1.2" depd "~1.1.2"
http-errors "1.8.1" http-errors "1.8.1"
iconv-lite "0.4.24" iconv-lite "0.4.24"
on-finished "~2.3.0" on-finished "~2.3.0"
qs "6.9.6" qs "6.9.7"
raw-body "2.4.2" raw-body "2.4.3"
type-is "~1.6.18" type-is "~1.6.18"
brace-expansion@^1.1.7: brace-expansion@^1.1.7:
@ -508,10 +508,10 @@ bytes@3.1.0:
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6"
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
bytes@3.1.1: bytes@3.1.2:
version "3.1.1" version "3.1.2"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.1.tgz#3f018291cb4cbad9accb6e6970bca9c8889e879a" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-dWe4nWO/ruEOY7HkUJ5gFt1DCFV9zPRoJr8pV0/ASQermOZjtq8jMjOprC0Kd10GLN+l7xaUPvxzJFWtxGu8Fg== integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
callsites@^3.0.0: callsites@^3.0.0:
version "3.1.0" version "3.1.0"
@ -537,7 +537,7 @@ chalk@^2.0.0, chalk@^2.3.0:
escape-string-regexp "^1.0.5" escape-string-regexp "^1.0.5"
supports-color "^5.3.0" supports-color "^5.3.0"
chalk@^4, chalk@^4.0.0, chalk@^4.1.0: chalk@^4.0.0, chalk@^4.1.0:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
@ -651,10 +651,10 @@ cookie@0.4.0:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
cookie@0.4.1: cookie@0.4.2:
version "0.4.1" version "0.4.2"
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432"
integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==
core-util-is@1.0.2: core-util-is@1.0.2:
version "1.0.2" version "1.0.2"
@ -815,6 +815,13 @@ domexception@^2.0.1:
dependencies: dependencies:
webidl-conversions "^5.0.0" webidl-conversions "^5.0.0"
domhandler@^3.0.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-3.3.0.tgz#6db7ea46e4617eb15cf875df68b2b8524ce0037a"
integrity sha512-J1C5rIANUbuYK+FuFL98650rihynUOEzRLxW+90bKZRWB6A1X1Tf82GxR1qAWLyfNPRvjqfip3Q5tdYlmAa9lA==
dependencies:
domelementtype "^2.0.1"
domhandler@^4.0.0, domhandler@^4.2.0: domhandler@^4.0.0, domhandler@^4.2.0:
version "4.2.2" version "4.2.2"
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.2.2.tgz#e825d721d19a86b8c201a35264e226c678ee755f"
@ -822,7 +829,7 @@ domhandler@^4.0.0, domhandler@^4.2.0:
dependencies: dependencies:
domelementtype "^2.2.0" domelementtype "^2.2.0"
domutils@^2.5.2: domutils@^2.0.0, domutils@^2.5.2:
version "2.8.0" version "2.8.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A== integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
@ -1080,17 +1087,17 @@ express@^4.17:
utils-merge "1.0.1" utils-merge "1.0.1"
vary "~1.1.2" vary "~1.1.2"
express@^4.17.2: express@^4.17.1:
version "4.17.2" version "4.17.3"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.2.tgz#c18369f265297319beed4e5558753cc8c1364cb3" resolved "https://registry.yarnpkg.com/express/-/express-4.17.3.tgz#f6c7302194a4fb54271b73a1fe7a06478c8f85a1"
integrity sha512-oxlxJxcQlYwqPWKVJJtvQiwHgosH/LrLSPA+H4UxpyvSS6jC5aH+5MoHFM+KABgTOt0APue4w66Ha8jCUo9QGg== integrity sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==
dependencies: dependencies:
accepts "~1.3.7" accepts "~1.3.8"
array-flatten "1.1.1" array-flatten "1.1.1"
body-parser "1.19.1" body-parser "1.19.2"
content-disposition "0.5.4" content-disposition "0.5.4"
content-type "~1.0.4" content-type "~1.0.4"
cookie "0.4.1" cookie "0.4.2"
cookie-signature "1.0.6" cookie-signature "1.0.6"
debug "2.6.9" debug "2.6.9"
depd "~1.1.2" depd "~1.1.2"
@ -1105,7 +1112,7 @@ express@^4.17.2:
parseurl "~1.3.3" parseurl "~1.3.3"
path-to-regexp "0.1.7" path-to-regexp "0.1.7"
proxy-addr "~2.0.7" proxy-addr "~2.0.7"
qs "6.9.6" qs "6.9.7"
range-parser "~1.2.1" range-parser "~1.2.1"
safe-buffer "5.2.1" safe-buffer "5.2.1"
send "0.17.2" send "0.17.2"
@ -1288,7 +1295,7 @@ glob@7.1.7:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.0" path-is-absolute "^1.0.0"
glob@^7.0.0, glob@^7.1.1, glob@^7.1.3: glob@^7.1.1, glob@^7.1.3:
version "7.2.0" version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
@ -1367,6 +1374,17 @@ html-encoding-sniffer@^2.0.1:
dependencies: dependencies:
whatwg-encoding "^1.0.5" whatwg-encoding "^1.0.5"
html-to-text@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-6.0.0.tgz#8b48adb1b781a8378f374c5bb481864a169f59f4"
integrity sha512-r0KNC5aqCAItsjlgtirW6RW25c92Ee3ybQj8z//4Sl4suE3HIPqM4deGpYCUJULLjtVPEP1+Ma+1ZeX1iMsCiA==
dependencies:
deepmerge "^4.2.2"
he "^1.2.0"
htmlparser2 "^4.1.0"
lodash "^4.17.20"
minimist "^1.2.5"
html-to-text@^8.0.0: html-to-text@^8.0.0:
version "8.0.0" version "8.0.0"
resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-8.0.0.tgz#5848681a5a38d657a7bb58cf5006d1c29fe64ce3" resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-8.0.0.tgz#5848681a5a38d657a7bb58cf5006d1c29fe64ce3"
@ -1379,23 +1397,21 @@ html-to-text@^8.0.0:
minimist "^1.2.5" minimist "^1.2.5"
selderee "^0.6.0" selderee "^0.6.0"
html-to-text@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/html-to-text/-/html-to-text-8.1.0.tgz#0c35fc452e6eccb275669adb8bcc61d93ec43ed5"
integrity sha512-Z9iYAqYK2c18GswSbnxJSeMs7lyJgwR2oIkDOyOHGBbYsPsG4HvT379jj3Lcbfko8A5ceyyMHAfkmp/BiXA9/Q==
dependencies:
"@selderee/plugin-htmlparser2" "^0.6.0"
deepmerge "^4.2.2"
he "^1.2.0"
htmlparser2 "^6.1.0"
minimist "^1.2.5"
selderee "^0.6.0"
htmlencode@^0.0.4: htmlencode@^0.0.4:
version "0.0.4" version "0.0.4"
resolved "https://registry.yarnpkg.com/htmlencode/-/htmlencode-0.0.4.tgz#f7e2d6afbe18a87a78e63ba3308e753766740e3f" resolved "https://registry.yarnpkg.com/htmlencode/-/htmlencode-0.0.4.tgz#f7e2d6afbe18a87a78e63ba3308e753766740e3f"
integrity sha1-9+LWr74YqHp45jujMI51N2Z0Dj8= integrity sha1-9+LWr74YqHp45jujMI51N2Z0Dj8=
htmlparser2@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-4.1.0.tgz#9a4ef161f2e4625ebf7dfbe6c0a2f52d18a59e78"
integrity sha512-4zDq1a1zhE4gQso/c5LP1OtrhYTncXNSpvJYtWJBtXAETPlMfi3IFNjGuQbYLuVY4ZR0QMqRVvo4Pdy9KLyP8Q==
dependencies:
domelementtype "^2.0.1"
domhandler "^3.0.0"
domutils "^2.0.0"
entities "^2.0.0"
htmlparser2@^6.0.0, htmlparser2@^6.1.0: htmlparser2@^6.0.0, htmlparser2@^6.1.0:
version "6.1.0" version "6.1.0"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
@ -1470,6 +1486,11 @@ humanize-duration-ts@^2.1.1:
resolved "https://registry.yarnpkg.com/humanize-duration-ts/-/humanize-duration-ts-2.1.1.tgz#5382b2789f851005a67229eaf031931d71f37ee9" resolved "https://registry.yarnpkg.com/humanize-duration-ts/-/humanize-duration-ts-2.1.1.tgz#5382b2789f851005a67229eaf031931d71f37ee9"
integrity sha512-TibNF2/fkypjAfHdGpWL/dmWUS0G6Qi+3mKyiB6LDCowbMy+PtzbgPTnFMNTOVAJXDau01jYrJ3tFoz5AJSqhA== integrity sha512-TibNF2/fkypjAfHdGpWL/dmWUS0G6Qi+3mKyiB6LDCowbMy+PtzbgPTnFMNTOVAJXDau01jYrJ3tFoz5AJSqhA==
humanize-duration@^3.27.1:
version "3.27.1"
resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.27.1.tgz#2cd4ea4b03bd92184aee6d90d77a8f3d7628df69"
integrity sha512-jCVkMl+EaM80rrMrAPl96SGG4NRac53UyI1o/yAzebDntEY6K6/Fj2HOjdPg8omTqIe5Y0wPBai2q5xXrIbarA==
iconv-lite@0.4.24: iconv-lite@0.4.24:
version "0.4.24" version "0.4.24"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@ -1513,11 +1534,6 @@ inherits@2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
interpret@^1.0.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e"
integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==
ipaddr.js@1.9.1: ipaddr.js@1.9.1:
version "1.9.1" version "1.9.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
@ -1537,13 +1553,6 @@ is-core-module@^2.2.0:
dependencies: dependencies:
has "^1.0.3" has "^1.0.3"
is-core-module@^2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.1.tgz#f59fdfca701d5879d0a6b100a40aa1560ce27211"
integrity sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==
dependencies:
has "^1.0.3"
is-extglob@^2.1.1: is-extglob@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@ -1758,6 +1767,11 @@ jsprim@^1.2.2:
json-schema "0.2.3" json-schema "0.2.3"
verror "1.10.0" verror "1.10.0"
klona@^2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
levn@^0.4.1: levn@^0.4.1:
version "0.4.1" version "0.4.1"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
@ -1796,7 +1810,7 @@ lodash.truncate@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM= integrity sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=
lodash@4, lodash@^4.17.19, lodash@^4.7.0: lodash@4, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.7.0:
version "4.17.21" version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -1809,7 +1823,7 @@ log-symbols@4.1.0:
chalk "^4.1.0" chalk "^4.1.0"
is-unicode-supported "^0.1.0" is-unicode-supported "^0.1.0"
lowdb@^1: lowdb@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/lowdb/-/lowdb-1.0.0.tgz#5243be6b22786ccce30e50c9a33eac36b20c8064" resolved "https://registry.yarnpkg.com/lowdb/-/lowdb-1.0.0.tgz#5243be6b22786ccce30e50c9a33eac36b20c8064"
integrity sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ== integrity sha512-2+x8esE/Wb9SQ1F9IHaYWfsC9FIecLOPrK4g17FGEayjUWH172H6nwicRovGvSE2CPZouc2MCIqCI7h9d+GftQ==
@ -1840,25 +1854,25 @@ make-error@^1.1.1:
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
"matrix-bot-sdk@file:../blurbs/matrix-bot-sdk": matrix-bot-sdk@^0.5.19:
version "99.0.2" version "0.5.19"
resolved "https://registry.yarnpkg.com/matrix-bot-sdk/-/matrix-bot-sdk-0.5.19.tgz#6ce13359ab53ea0af9dc3ebcbe288c5f6d9c02c6"
integrity sha512-RIPyvQPkOVp2yTKeDgp5rcn6z/DiKdHb6E8c69K+utai8ypRGtfDRj0PGqP+1XzqC9Wb1OFrESCUB5t0ffdC9g==
dependencies: dependencies:
"@turt2live/matrix-sdk-crypto-nodejs" "^0.1.0-beta.10" "@types/express" "^4.17.7"
"@types/express" "^4.17.13" chalk "^4.1.0"
another-json "^0.2.0" express "^4.17.1"
chalk "^4"
express "^4.17.2"
glob-to-regexp "^0.4.1" glob-to-regexp "^0.4.1"
hash.js "^1.1.7" hash.js "^1.1.7"
html-to-text "^8.1.0" html-to-text "^6.0.0"
htmlencode "^0.0.4" htmlencode "^0.0.4"
lowdb "^1" lowdb "^1.0.0"
lru-cache "^6.0.0" lru-cache "^6.0.0"
mkdirp "^1.0.4" mkdirp "^1.0.4"
morgan "^1.10.0" morgan "^1.10.0"
request "^2.88.2" request "^2.88.2"
request-promise "^4.2.6" request-promise "^4.2.6"
sanitize-html "^2.6.1" sanitize-html "^2.3.2"
media-typer@0.3.0: media-typer@0.3.0:
version "0.3.0" version "0.3.0"
@ -1888,6 +1902,11 @@ mime-db@1.49.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed"
integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA== integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==
mime-db@1.51.0:
version "1.51.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c"
integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==
mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24: mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24:
version "2.1.32" version "2.1.32"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5"
@ -1895,6 +1914,13 @@ mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24:
dependencies: dependencies:
mime-db "1.49.0" mime-db "1.49.0"
mime-types@~2.1.34:
version "2.1.34"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.34.tgz#5a712f9ec1503511a945803640fafe09d3793c24"
integrity sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==
dependencies:
mime-db "1.51.0"
mime@1.6.0: mime@1.6.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
@ -1995,15 +2021,20 @@ ms@2.1.3:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
nanocolors@^0.2.2:
version "0.2.12"
resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.2.12.tgz#4d05932e70116078673ea4cc6699a1c56cc77777"
integrity sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug==
nanoid@3.1.25: nanoid@3.1.25:
version "3.1.25" version "3.1.25"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152"
integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q== integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q==
nanoid@^3.2.0: nanoid@^3.1.25:
version "3.3.0" version "3.1.28"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.0.tgz#5906f776fd886c66c24f3653e0c46fcb1d4ad6b0" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.28.tgz#3c01bac14cb6c5680569014cc65a2f26424c6bd4"
integrity sha512-JzxqqT5u/x+/KOFSd7JP15DOo9nOoHpx6DYatqIHUW2+flybkm+mdcraotSQR5WcnZr+qhGVh8Ted0KdfSMxlg== integrity sha512-gSu9VZ2HtmoKYe/lmyPFES5nknFrHa+/DT9muUFWFMi6Jh9E1I7bkvlQ8xxf1Kos9pi9o8lBnIOkatMhKX/YUw==
natural-compare@^1.4.0: natural-compare@^1.4.0:
version "1.4.0" version "1.4.0"
@ -2025,6 +2056,11 @@ negotiator@0.6.2:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
negotiator@0.6.3:
version "0.6.3"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
normalize-path@^3.0.0, normalize-path@~3.0.0: normalize-path@^3.0.0, normalize-path@~3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
@ -2147,7 +2183,7 @@ path-key@^3.1.0:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
path-parse@^1.0.6, path-parse@^1.0.7: path-parse@^1.0.6:
version "1.0.7" version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
@ -2162,11 +2198,6 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
picocolors@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
@ -2177,14 +2208,14 @@ pify@^3.0.0:
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=
postcss@^8.3.11: postcss@^8.0.2:
version "8.4.6" version "8.3.8"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.6.tgz#c5ff3c3c457a23864f32cb45ac9b741498a09ae1" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.8.tgz#9ebe2a127396b4b4570ae9f7770e7fb83db2bac1"
integrity sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA== integrity sha512-GT5bTjjZnwDifajzczOC+r3FI3Cu+PgPvrsjhQdRqa2kTJ4968/X9CUce9xttIB0xOs5c6xf0TCWZo/y9lF6bA==
dependencies: dependencies:
nanoid "^3.2.0" nanocolors "^0.2.2"
picocolors "^1.0.0" nanoid "^3.1.25"
source-map-js "^1.0.2" source-map-js "^0.6.2"
prelude-ls@^1.2.1: prelude-ls@^1.2.1:
version "1.2.1" version "1.2.1"
@ -2239,10 +2270,10 @@ qs@6.7.0:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
qs@6.9.6: qs@6.9.7:
version "6.9.6" version "6.9.7"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.6.tgz#26ed3c8243a431b2924aca84cc90471f35d5a0ee" resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.7.tgz#4610846871485e1e048f44ae3b94033f0e675afe"
integrity sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ== integrity sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==
qs@~6.5.2: qs@~6.5.2:
version "6.5.2" version "6.5.2"
@ -2284,12 +2315,12 @@ raw-body@2.4.0:
iconv-lite "0.4.24" iconv-lite "0.4.24"
unpipe "1.0.0" unpipe "1.0.0"
raw-body@2.4.2: raw-body@2.4.3:
version "2.4.2" version "2.4.3"
resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.2.tgz#baf3e9c21eebced59dd6533ac872b71f7b61cb32" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.3.tgz#8f80305d11c2a0a545c2d9d89d7a0286fcead43c"
integrity sha512-RPMAFUJP19WIet/99ngh6Iv8fzAbqum4Li7AD6DtGaW2RpMB/11xDoalPiJMTbu6I3hkbMVkATvZrqb9EEqeeQ== integrity sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==
dependencies: dependencies:
bytes "3.1.1" bytes "3.1.2"
http-errors "1.8.1" http-errors "1.8.1"
iconv-lite "0.4.24" iconv-lite "0.4.24"
unpipe "1.0.0" unpipe "1.0.0"
@ -2306,13 +2337,6 @@ readdirp@~3.6.0:
dependencies: dependencies:
picomatch "^2.2.1" picomatch "^2.2.1"
rechoir@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
integrity sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=
dependencies:
resolve "^1.1.6"
regexpp@^3.1.0: regexpp@^3.1.0:
version "3.2.0" version "3.2.0"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2"
@ -2376,15 +2400,6 @@ resolve-from@^4.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve@^1.1.6:
version "1.22.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
dependencies:
is-core-module "^2.8.1"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
resolve@^1.3.2: resolve@^1.3.2:
version "1.20.0" version "1.20.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
@ -2420,17 +2435,18 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sanitize-html@^2.6.1: sanitize-html@^2.3.2:
version "2.7.0" version "2.5.1"
resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.7.0.tgz#e106205b468aca932e2f9baf241f24660d34e279" resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.5.1.tgz#f49998dc54c8180153940440d3a7294b09e4258a"
integrity sha512-jfQelabOn5voO7FAfnQF7v+jsA6z9zC/O4ec0z3E35XPEtHYJT/OdUziVWlKW4irCr2kXaQAyXTXDHWAibg1tA== integrity sha512-hUITPitQk+eFNLtr4dEkaaiAJndG2YE87IOpcfBSL1XdklWgwcNDJdr9Ppe8QKL/C3jFt1xH/Mbj20e0GZQOfg==
dependencies: dependencies:
deepmerge "^4.2.2" deepmerge "^4.2.2"
escape-string-regexp "^4.0.0" escape-string-regexp "^4.0.0"
htmlparser2 "^6.0.0" htmlparser2 "^6.0.0"
is-plain-object "^5.0.0" is-plain-object "^5.0.0"
klona "^2.0.3"
parse-srcset "^1.0.2" parse-srcset "^1.0.2"
postcss "^8.3.11" postcss "^8.0.2"
saxes@^5.0.1: saxes@^5.0.1:
version "5.0.1" version "5.0.1"
@ -2545,14 +2561,10 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
shelljs@^0.8.4: shell-quote@^1.7.3:
version "0.8.5" version "1.7.3"
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.3.tgz#aa40edac170445b9a431e17bb62c0b881b9c4123"
integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== integrity sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==
dependencies:
glob "^7.0.0"
interpret "^1.0.0"
rechoir "^0.6.2"
sigmund@^1.0.1: sigmund@^1.0.1:
version "1.0.1" version "1.0.1"
@ -2573,10 +2585,10 @@ slice-ansi@^4.0.0:
astral-regex "^2.0.0" astral-regex "^2.0.0"
is-fullwidth-code-point "^3.0.0" is-fullwidth-code-point "^3.0.0"
source-map-js@^1.0.2: source-map-js@^0.6.2:
version "1.0.2" version "0.6.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
source-map-support@^0.5.6: source-map-support@^0.5.6:
version "0.5.20" version "0.5.20"
@ -2682,11 +2694,6 @@ supports-color@^7.1.0:
dependencies: dependencies:
has-flag "^4.0.0" has-flag "^4.0.0"
supports-preserve-symlinks-flag@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
symbol-tree@^3.2.4: symbol-tree@^3.2.4:
version "3.2.4" version "3.2.4"
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"