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,5 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.anySignal = anySignal;
exports.parseErrorResponse = parseErrorResponse;
exports.retryNetworkOperation = retryNetworkOperation;
exports.timeoutSignal = timeoutSignal;
var _contentType3 = require("content-type");
var _logger = require("../logger");
var _utils = require("../utils");
var _errors = require("./errors");
/*
Copyright 2022 - 2024 The Matrix.org Foundation C.I.C.
Copyright 2022 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.
@@ -14,20 +27,15 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { parse as parseContentType } from "content-type";
import { logger } from "../logger.js";
import { sleep } from "../utils.js";
import { ConnectionError, HTTPError, MatrixError, MatrixSafetyError, MatrixSafetyErrorCode, safeGetRetryAfterMs } from "./errors.js";
// Ponyfill for https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout
export function timeoutSignal(ms) {
function timeoutSignal(ms) {
const controller = new AbortController();
setTimeout(() => {
controller.abort();
}, ms);
return controller.signal;
}
export function anySignal(signals) {
function anySignal(signals) {
const controller = new AbortController();
function cleanup() {
for (const signal of signals) {
@@ -61,35 +69,28 @@ export function anySignal(signals) {
* @param body - raw body of the response
* @returns
*/
export function parseErrorResponse(response, body) {
const httpHeaders = isXhr(response) ? new Headers(response.getAllResponseHeaders().trim().split(/[\r\n]+/).map(header => {
const colonIdx = header.indexOf(":");
return [header.substring(0, colonIdx), header.substring(colonIdx + 1)];
})) : response.headers;
function parseErrorResponse(response, body) {
var _contentType, _contentType2;
let contentType;
try {
contentType = getResponseContentType(httpHeaders);
contentType = getResponseContentType(response);
} catch (e) {
return e;
}
if (contentType?.type === "application/json" && body) {
const errorBody = JSON.parse(body);
if (errorBody.errcode && MatrixSafetyErrorCode.matches(errorBody.errcode)) {
return new MatrixSafetyError(errorBody, response.status, isXhr(response) ? response.responseURL : response.url, undefined, httpHeaders);
}
return new MatrixError(errorBody, response.status, isXhr(response) ? response.responseURL : response.url, undefined, httpHeaders);
if (((_contentType = contentType) === null || _contentType === void 0 ? void 0 : _contentType.type) === "application/json" && body) {
return new _errors.MatrixError(JSON.parse(body), response.status, isXhr(response) ? response.responseURL : response.url);
}
if (contentType?.type === "text/plain") {
return new HTTPError(`Server returned ${response.status} error: ${body}`, response.status, httpHeaders);
if (((_contentType2 = contentType) === null || _contentType2 === void 0 ? void 0 : _contentType2.type) === "text/plain") {
return new _errors.HTTPError(`Server returned ${response.status} error: ${body}`, response.status);
}
return new HTTPError(`Server returned ${response.status} error`, response.status, httpHeaders);
return new _errors.HTTPError(`Server returned ${response.status} error`, response.status);
}
function isXhr(response) {
return "getResponseHeader" in response;
}
/**
* extract the Content-Type header from response headers, and
* extract the Content-Type header from the response object, and
* parse it to a `{type, parameters}` object.
*
* returns null if no content-type header could be found.
@@ -97,10 +98,19 @@ function isXhr(response) {
* @param response - response object
* @returns parsed content-type header, or null if not found
*/
function getResponseContentType(headers) {
const contentType = headers.get("Content-Type");
if (contentType === null) return null;
return parseContentType(contentType);
function getResponseContentType(response) {
let contentType;
if (isXhr(response)) {
contentType = response.getResponseHeader("Content-Type");
} else {
contentType = response.headers.get("Content-Type");
}
if (!contentType) return null;
try {
return (0, _contentType3.parse)(contentType);
} catch (e) {
throw new Error(`Error parsing Content-Type '${contentType}': ${e}`);
}
}
/**
@@ -110,19 +120,19 @@ function getResponseContentType(headers) {
* @returns the result of the network operation
* @throws {@link ConnectionError} If after maxAttempts the callback still throws ConnectionError
*/
export async function retryNetworkOperation(maxAttempts, callback) {
async function retryNetworkOperation(maxAttempts, callback) {
let attempts = 0;
let lastConnectionError = null;
while (attempts < maxAttempts) {
try {
if (attempts > 0) {
const timeout = 1000 * Math.pow(2, attempts);
logger.log(`network operation failed ${attempts} times, retrying in ${timeout}ms...`);
await sleep(timeout);
_logger.logger.log(`network operation failed ${attempts} times, retrying in ${timeout}ms...`);
await (0, _utils.sleep)(timeout);
}
return await callback();
} catch (err) {
if (err instanceof ConnectionError) {
if (err instanceof _errors.ConnectionError) {
attempts += 1;
lastConnectionError = err;
} else {
@@ -132,39 +142,4 @@ export async function retryNetworkOperation(maxAttempts, callback) {
}
throw lastConnectionError;
}
/**
* Calculate the backoff time for a request retry attempt.
* This produces wait times of 2, 4, 8, and 16 seconds (30s total) after which we give up. If the
* failure was due to a rate limited request, the time specified in the error is returned.
*
* Returns -1 if the error is not retryable, or if we reach the maximum number of attempts.
*
* @param err - The error thrown by the http call
* @param attempts - The number of attempts made so far, including the one that just failed.
* @param retryConnectionError - Whether to retry on {@link ConnectionError} (CORS, connection is down, etc.)
*/
export function calculateRetryBackoff(err, attempts, retryConnectionError) {
if (attempts > 4) {
return -1; // give up
}
if (err instanceof ConnectionError && !retryConnectionError) {
return -1;
}
if (err.httpStatus && Math.floor(err.httpStatus / 100) === 4 && err.httpStatus !== 429) {
// client error; no amount of retrying will save you now (except for rate limiting which is handled below)
return -1;
}
if (err.name === "AbortError") {
// this is a client timeout, that is already very high 60s/80s
// we don't want to retry, as it could do it for very long
return -1;
}
// If we are trying to send an event (or similar) that is too large in any way, then retrying won't help
if (err.name === "M_TOO_LARGE") {
return -1;
}
return safeGetRetryAfterMs(err, 1000 * Math.pow(2, attempts));
}
//# sourceMappingURL=utils.js.map