feat: complete Ancestor quote bot with production-ready Docker support

This commit is contained in:
unfunny
2026-09-13 14:14:38 -04:00
parent f2016da05a
commit fe7351a7dc
1145 changed files with 30890 additions and 97684 deletions

View File

@@ -1,28 +1,75 @@
import _objectSpread from "@babel/runtime/helpers/objectSpread2";
/*
Copyright 2015, 2016, 2019, 2023 The Matrix.org Foundation C.I.C.
"use strict";
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.
*/
/**
* This is an internal module.
*/
import unhomoglyph from "unhomoglyph";
import promiseRetry from "p-retry";
import { M_TIMESTAMP } from "./@types/location.js";
import { ReceiptType } from "./@types/read_receipts.js";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.MapWithDefault = exports.DEFAULT_ALPHABET = void 0;
exports.alphabetPad = alphabetPad;
exports.averageBetweenStrings = averageBetweenStrings;
exports.baseToString = baseToString;
exports.checkObjectHasKeys = checkObjectHasKeys;
exports.chunkPromises = chunkPromises;
exports.compare = compare;
exports.decodeParams = decodeParams;
exports.deepCompare = deepCompare;
exports.deepCopy = deepCopy;
exports.deepSortedObjectEntries = deepSortedObjectEntries;
exports.defer = defer;
exports.encodeParams = encodeParams;
exports.encodeUri = encodeUri;
exports.ensureNoTrailingSlash = ensureNoTrailingSlash;
exports.escapeRegExp = escapeRegExp;
exports.globToRegexp = globToRegexp;
exports.immediate = immediate;
exports.internaliseString = internaliseString;
exports.isFunction = isFunction;
exports.isNullOrUndefined = isNullOrUndefined;
exports.isNumber = isNumber;
exports.isSupportedReceiptType = isSupportedReceiptType;
exports.lexicographicCompare = lexicographicCompare;
exports.mapsEqual = mapsEqual;
exports.nextString = nextString;
exports.noUnsafeEventProps = noUnsafeEventProps;
exports.normalize = normalize;
exports.prevString = prevString;
exports.promiseMapSeries = promiseMapSeries;
exports.promiseTry = promiseTry;
exports.recursiveMapToObject = recursiveMapToObject;
exports.recursivelyAssign = recursivelyAssign;
exports.removeDirectionOverrideChars = removeDirectionOverrideChars;
exports.removeElement = removeElement;
exports.removeHiddenChars = removeHiddenChars;
exports.replaceParam = replaceParam;
exports.safeSet = safeSet;
exports.simpleRetryOperation = simpleRetryOperation;
exports.sleep = sleep;
exports.sortEventsByLatestContentTimestamp = sortEventsByLatestContentTimestamp;
exports.stringToBase = stringToBase;
exports.unsafeProp = unsafeProp;
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _unhomoglyph = _interopRequireDefault(require("unhomoglyph"));
var _pRetry = _interopRequireDefault(require("p-retry"));
var _location = require("./@types/location");
var _read_receipts = require("./@types/read_receipts");
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; } /*
Copyright 2015, 2016, 2019, 2023 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.
*/ /**
* This is an internal module.
*/
const interns = new Map();
/**
@@ -31,10 +78,9 @@ const interns = new Map();
* @param str - The string to internalise.
* @returns The internalised string.
*/
export function internaliseString(str) {
function internaliseString(str) {
// Unwrap strings before entering the map, if we somehow got a wrapped
// string as our input. This should only happen from tests.
// oxlint-disable-next-line unicorn/no-instanceof-builtins
if (str instanceof String) {
str = str.toString();
}
@@ -55,8 +101,8 @@ export function internaliseString(str) {
* `{"foo": "bar", "baz": "taz"}`
* @returns The encoded string e.g. foo=bar&baz=taz
*/
export function encodeParams(params, urlSearchParams) {
const searchParams = urlSearchParams ?? new URLSearchParams();
function encodeParams(params, urlSearchParams) {
const searchParams = urlSearchParams !== null && urlSearchParams !== void 0 ? urlSearchParams : new URLSearchParams();
for (const [key, val] of Object.entries(params)) {
if (val !== undefined && val !== null) {
if (Array.isArray(val)) {
@@ -73,7 +119,7 @@ export function encodeParams(params, urlSearchParams) {
/**
* Replace a stable parameter with the unstable naming for params
*/
export function replaceParam(stable, unstable, dict) {
function replaceParam(stable, unstable, dict) {
const result = _objectSpread(_objectSpread({}, dict), {}, {
[unstable]: dict[stable]
});
@@ -90,7 +136,7 @@ export function replaceParam(stable, unstable, dict) {
* This behaviour matches Node's qs.parse but is built on URLSearchParams
* for native web compatibility
*/
export function decodeParams(query) {
function decodeParams(query) {
const o = {};
const params = new URLSearchParams(query);
for (const key of params.keys()) {
@@ -108,7 +154,7 @@ export function decodeParams(query) {
* variables with. E.g. `{ "$bar": "baz" }`.
* @returns The result of replacing all template variables e.g. '/foo/baz'.
*/
export function encodeUri(pathTemplate, variables) {
function encodeUri(pathTemplate, variables) {
for (const key in variables) {
if (!variables.hasOwnProperty(key)) {
continue;
@@ -132,7 +178,7 @@ export function encodeUri(pathTemplate, variables) {
* @param reverse - True to search in reverse order.
* @returns True if an element was removed.
*/
export function removeElement(array, fn, reverse) {
function removeElement(array, fn, reverse) {
let i;
if (reverse) {
for (i = array.length - 1; i >= 0; i--) {
@@ -157,7 +203,7 @@ export function removeElement(array, fn, reverse) {
* @param value - The thing to check.
* @returns True if it is a function.
*/
export function isFunction(value) {
function isFunction(value) {
return Object.prototype.toString.call(value) === "[object Function]";
}
@@ -168,7 +214,7 @@ export function isFunction(value) {
* @throws If the object is missing keys.
*/
// note using 'keys' here would shadow the 'keys' function defined above
export function checkObjectHasKeys(obj, keys) {
function checkObjectHasKeys(obj, keys) {
for (const key of keys) {
if (!obj.hasOwnProperty(key)) {
throw new Error("Missing required key: " + key);
@@ -182,7 +228,7 @@ export function checkObjectHasKeys(obj, keys) {
* @param obj - The object to deep copy.
* @returns A copy of the object without any references to the original.
*/
export function deepCopy(obj) {
function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj));
}
@@ -194,7 +240,7 @@ export function deepCopy(obj) {
*
* @returns true if the two objects are equal
*/
export function deepCompare(x, y) {
function deepCompare(x, y) {
// Inspired by
// http://stackoverflow.com/questions/1068834/object-comparison-in-javascript#1144249
@@ -219,16 +265,12 @@ export function deepCompare(x, y) {
}
// everything else is either an unequal primitive, or an object
// XXX: this check has been temporarily tweaked due to issues in the jest test environment,
// this will be reverted as part of the migration to vitest
if (x.constructor.name !== "Object" && x.constructor.name !== "RegExp" && x.constructor.name !== "Date" && x.constructor.name !== "Array") {
if (!(x instanceof Object)) {
return false;
}
// check they are the same type of object
// XXX: this check has been temporarily tweaked due to issues in the jest test environment,
// this will be reverted as part of the migration to vitest
if (x.prototype !== y.prototype) {
if (x.constructor !== y.constructor || x.prototype !== y.prototype) {
return false;
}
@@ -274,7 +316,7 @@ export function deepCompare(x, y) {
* @param obj - The object to get entries of
* @returns The entries, sorted by key.
*/
export function deepSortedObjectEntries(obj) {
function deepSortedObjectEntries(obj) {
if (typeof obj !== "object") return obj;
// Apparently these are object types...
@@ -295,7 +337,7 @@ export function deepSortedObjectEntries(obj) {
* @param value - the value to test
* @returns whether or not value is a finite number without type-coercion
*/
export function isNumber(value) {
function isNumber(value) {
return typeof value === "number" && isFinite(value);
}
@@ -305,9 +347,9 @@ export function isNumber(value) {
* @param str - the string to remove hidden characters from
* @returns a string with the hidden characters removed
*/
export function removeHiddenChars(str) {
function removeHiddenChars(str) {
if (typeof str === "string") {
return unhomoglyph(str.normalize("NFD").replace(removeHiddenCharsRegex, ""));
return (0, _unhomoglyph.default)(str.normalize("NFD").replace(removeHiddenCharsRegex, ""));
}
return "";
}
@@ -316,13 +358,13 @@ export function removeHiddenChars(str) {
* Removes the direction override characters from a string
* @returns string with chars removed
*/
export function removeDirectionOverrideChars(str) {
function removeDirectionOverrideChars(str) {
if (typeof str === "string") {
return str.replace(/[\u202d-\u202e]/g, "");
}
return "";
}
export function normalize(str) {
function normalize(str) {
// Note: we have to match the filter with the removeHiddenChars() because the
// function strips spaces and other characters (M becomes RN for example, in lowercase).
return removeHiddenChars(str.toLowerCase())
@@ -343,7 +385,7 @@ export function normalize(str) {
// Blank/invisible characters (U2800, U2062-U2063)
// eslint-disable-next-line no-misleading-character-class
const removeHiddenCharsRegex = /[\u2000-\u200F\u202A-\u202F\u0300-\u036F\uFEFF\u061C\u2800\u2062-\u2063\s]/g;
export function escapeRegExp(string) {
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
@@ -353,11 +395,11 @@ export function escapeRegExp(string) {
* @param glob - Matrix glob-style string
* @returns regular expression
*/
export function globToRegexp(glob) {
function globToRegexp(glob) {
return escapeRegExp(glob).replace(/\\\*/g, ".*").replace(/\?/g, ".");
}
export function ensureNoTrailingSlash(url) {
if (url?.endsWith("/")) {
function ensureNoTrailingSlash(url) {
if (url !== null && url !== void 0 && url.endsWith("/")) {
return url.slice(0, -1);
} else {
return url;
@@ -367,70 +409,47 @@ export function ensureNoTrailingSlash(url) {
/**
* Returns a promise which resolves with a given value after the given number of ms
*/
export function sleep(ms, value) {
function sleep(ms, value) {
return new Promise(resolve => {
setTimeout(resolve, ms, value);
});
}
/**
* Utility to log the duration of a promise.
*
* @param logger - The logger to log to.
* @param name - The name of the operation.
* @param block - The block to execute.
*/
export async function logDuration(logger, name, block) {
const start = Date.now();
try {
return await block();
} finally {
const end = Date.now();
logger.debug(`[Perf]: ${name} took ${end - start}ms`);
}
}
/**
* Utility to log the duration of a synchronous block.
*
* @param logger - The logger to log to.
* @param name - The name of the operation.
* @param block - The block to execute.
*/
export function logDurationSync(logger, name, block) {
const start = Date.now();
try {
return block();
} finally {
const end = Date.now();
logger.debug(`[Perf]: ${name} took ${end - start}ms`);
}
}
/**
* Promise/async version of {@link setImmediate}.
*
* Implementation is based on `setTimeout` for wider compatibility.
* @deprecated Use {@link sleep} instead.
*/
export function immediate() {
return new Promise(resolve => setTimeout(resolve));
function immediate() {
return new Promise(setImmediate);
}
export function isNullOrUndefined(val) {
function isNullOrUndefined(val) {
return val === null || val === undefined;
}
export async function promiseMapSeries(promises, fn // if async we don't care about the type as we only await resolution
// Returns a Deferred
function defer() {
let resolve;
let reject;
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
return {
resolve,
reject,
promise
};
}
async function promiseMapSeries(promises, fn // if async we don't care about the type as we only await resolution
) {
for (const o of promises) {
await fn(await o);
}
}
export function promiseTry(fn) {
function promiseTry(fn) {
return Promise.resolve(fn());
}
// Creates and awaits all promises, running no more than `chunkSize` at the same time
export async function chunkPromises(fns, chunkSize) {
async function chunkPromises(fns, chunkSize) {
const results = [];
for (let i = 0; i < fns.length; i += chunkSize) {
results.push(...(await Promise.all(fns.slice(i, i + chunkSize).map(fn => fn()))));
@@ -445,18 +464,13 @@ export async function chunkPromises(fns, chunkSize) {
* should always return a new promise.
* @param promiseFn - The function to call to get a fresh promise instance. Takes an
* attempt count as an argument, for logging/debugging purposes.
* @param shouldRetry - Optional function which is called with the error the latest rejection from promiseFn,
* retrying will ba aborted if this return false.
* @returns The promise for the retried operation.
*/
export function simpleRetryOperation(promiseFn, shouldRetry) {
return promiseRetry(attempt => {
function simpleRetryOperation(promiseFn) {
return (0, _pRetry.default)(attempt => {
return promiseFn(attempt);
}, {
retries: Infinity,
shouldRetry: shouldRetry ? ({
error
}) => shouldRetry(error) : undefined,
forever: true,
factor: 2,
minTimeout: 3000,
// ms
@@ -473,7 +487,7 @@ export function simpleRetryOperation(promiseFn, shouldRetry) {
* The default alphabet used by string averaging in this SDK. This matches
* all usefully printable ASCII characters (0x20-0x7E, inclusive).
*/
export const DEFAULT_ALPHABET = (() => {
const DEFAULT_ALPHABET = (() => {
let str = "";
for (let c = 0x20; c <= 0x7e; c++) {
str += String.fromCharCode(c);
@@ -491,7 +505,8 @@ export const DEFAULT_ALPHABET = (() => {
* @param alphabet - The alphabet to use as a single string.
* @returns The padded string.
*/
export function alphabetPad(s, n, alphabet = DEFAULT_ALPHABET) {
exports.DEFAULT_ALPHABET = DEFAULT_ALPHABET;
function alphabetPad(s, n, alphabet = DEFAULT_ALPHABET) {
return s.padEnd(n, alphabet[0]);
}
@@ -503,7 +518,7 @@ export function alphabetPad(s, n, alphabet = DEFAULT_ALPHABET) {
* @param alphabet - The alphabet to use as a single string.
* @returns The baseN number encoded as a string from the alphabet.
*/
export function baseToString(n, alphabet = DEFAULT_ALPHABET) {
function baseToString(n, alphabet = DEFAULT_ALPHABET) {
// Developer note: the stringToBase() function offsets the character set by 1 so that repeated
// characters (ie: "aaaaaa" in a..z) don't come out as zero. We have to reverse this here as
// otherwise we'll be wrong in our conversion. Undoing a +1 before an exponent isn't very fun
@@ -512,7 +527,8 @@ export function baseToString(n, alphabet = DEFAULT_ALPHABET) {
const len = BigInt(alphabet.length);
if (n <= len) {
return alphabet[Number(n) - 1] ?? "";
var _alphabet;
return (_alphabet = alphabet[Number(n) - 1]) !== null && _alphabet !== void 0 ? _alphabet : "";
}
let d = n / len;
let r = Number(n % len) - 1;
@@ -535,7 +551,7 @@ export function baseToString(n, alphabet = DEFAULT_ALPHABET) {
* @param alphabet - The alphabet to use as a single string.
* @returns The baseN number.
*/
export function stringToBase(s, alphabet = DEFAULT_ALPHABET) {
function stringToBase(s, alphabet = DEFAULT_ALPHABET) {
const len = BigInt(alphabet.length);
// In our conversion to baseN we do a couple performance optimizations to avoid using
@@ -570,7 +586,7 @@ export function stringToBase(s, alphabet = DEFAULT_ALPHABET) {
* @param alphabet - The alphabet to use as a single string.
* @returns The midpoint between the strings, as a string.
*/
export function averageBetweenStrings(a, b, alphabet = DEFAULT_ALPHABET) {
function averageBetweenStrings(a, b, alphabet = DEFAULT_ALPHABET) {
const padN = Math.max(a.length, b.length);
const baseA = stringToBase(alphabetPad(a, padN, alphabet), alphabet);
const baseB = stringToBase(alphabetPad(b, padN, alphabet), alphabet);
@@ -592,7 +608,7 @@ export function averageBetweenStrings(a, b, alphabet = DEFAULT_ALPHABET) {
* @param alphabet - The alphabet to use as a single string.
* @returns The string which follows the input string.
*/
export function nextString(s, alphabet = DEFAULT_ALPHABET) {
function nextString(s, alphabet = DEFAULT_ALPHABET) {
return baseToString(stringToBase(s, alphabet) + BigInt(1), alphabet);
}
@@ -604,7 +620,7 @@ export function nextString(s, alphabet = DEFAULT_ALPHABET) {
* @param alphabet - The alphabet to use as a single string.
* @returns The string which precedes the input string.
*/
export function prevString(s, alphabet = DEFAULT_ALPHABET) {
function prevString(s, alphabet = DEFAULT_ALPHABET) {
return baseToString(stringToBase(s, alphabet) - BigInt(1), alphabet);
}
@@ -615,7 +631,7 @@ export function prevString(s, alphabet = DEFAULT_ALPHABET) {
* @returns Negative if the reference string is before the compare string;
* positive if the reference string is after; and zero if equal.
*/
export function lexicographicCompare(a, b) {
function lexicographicCompare(a, b) {
// Dev note: this exists because I'm sad that you can use math operators on strings, so I've
// hidden the operation in this function.
if (a < b) {
@@ -626,6 +642,15 @@ export function lexicographicCompare(a, b) {
return 0;
}
}
const collator = new Intl.Collator();
/**
* Performant language-sensitive string comparison
* @param a - the first string to compare
* @param b - the second string to compare
*/
function compare(a, b) {
return collator.compare(a, b);
}
/**
* This function is similar to Object.assign() but it assigns recursively and
@@ -633,7 +658,7 @@ export function lexicographicCompare(a, b) {
*
* @returns the target object
*/
export function recursivelyAssign(target, source, ignoreNullish = false) {
function recursivelyAssign(target, source, ignoreNullish = false) {
for (const [sourceKey, sourceValue] of Object.entries(source)) {
if (target[sourceKey] instanceof Object && sourceValue) {
recursivelyAssign(target[sourceKey], sourceValue);
@@ -647,25 +672,26 @@ export function recursivelyAssign(target, source, ignoreNullish = false) {
return target;
}
function getContentTimestampWithFallback(event) {
return M_TIMESTAMP.findIn(event.getContent()) ?? -1;
var _M_TIMESTAMP$findIn;
return (_M_TIMESTAMP$findIn = _location.M_TIMESTAMP.findIn(event.getContent())) !== null && _M_TIMESTAMP$findIn !== void 0 ? _M_TIMESTAMP$findIn : -1;
}
/**
* Sort events by their content m.ts property
* Latest timestamp first
*/
export function sortEventsByLatestContentTimestamp(left, right) {
function sortEventsByLatestContentTimestamp(left, right) {
return getContentTimestampWithFallback(right) - getContentTimestampWithFallback(left);
}
export function isSupportedReceiptType(receiptType) {
return [ReceiptType.Read, ReceiptType.ReadPrivate].includes(receiptType);
function isSupportedReceiptType(receiptType) {
return [_read_receipts.ReceiptType.Read, _read_receipts.ReceiptType.ReadPrivate].includes(receiptType);
}
/**
* Determines whether two maps are equal.
* @param eq - The equivalence relation to compare values by. Defaults to strict equality.
*/
export function mapsEqual(x, y, eq = (v1, v2) => v1 === v2) {
function mapsEqual(x, y, eq = (v1, v2) => v1 === v2) {
if (x.size !== y.size) return false;
for (const [k, v1] of x) {
const v2 = y.get(k);
@@ -689,26 +715,26 @@ function processMapToObjectValue(value) {
* Recursively converts Maps to plain objects.
* Also supports sub-lists of Maps.
*/
export function recursiveMapToObject(map) {
function recursiveMapToObject(map) {
const targetMap = new Map();
for (const [key, value] of map) {
targetMap.set(key, processMapToObjectValue(value));
}
return Object.fromEntries(targetMap.entries());
}
export function unsafeProp(prop) {
function unsafeProp(prop) {
return prop === "__proto__" || prop === "prototype" || prop === "constructor";
}
export function safeSet(obj, prop, value) {
function safeSet(obj, prop, value) {
if (unsafeProp(prop)) {
throw new Error("Trying to modify prototype or constructor");
}
obj[prop] = value;
}
export function noUnsafeEventProps(event) {
return !(unsafeProp(event.room_id) || unsafeProp(event.sender) || unsafeProp(event.event_id));
function noUnsafeEventProps(event) {
return !(unsafeProp(event.room_id) || unsafeProp(event.sender) || unsafeProp(event.user_id) || unsafeProp(event.event_id));
}
export class MapWithDefault extends Map {
class MapWithDefault extends Map {
constructor(createDefault) {
super();
this.createDefault = createDefault;
@@ -725,4 +751,5 @@ export class MapWithDefault extends Map {
return this.get(key);
}
}
exports.MapWithDefault = MapWithDefault;
//# sourceMappingURL=utils.js.map