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

@@ -18,18 +18,18 @@ limitations under the License.
* This is an internal module which manages queuing, scheduling and retrying
* of requests.
*/
import { logger } from "./logger.ts";
import { type MatrixEvent } from "./models/event.ts";
import { EventType } from "./@types/event.ts";
import { removeElement } from "./utils.ts";
import { calculateRetryBackoff, type MatrixError } from "./http-api/index.ts";
import { type ISendEventResponse } from "./@types/requests.ts";
import { logger } from "./logger";
import { MatrixEvent } from "./models/event";
import { EventType } from "./@types/event";
import { defer, IDeferred, removeElement } from "./utils";
import { ConnectionError, MatrixError } from "./http-api";
import { ISendEventResponse } from "./@types/requests";
const DEBUG = false; // set true to enable console logging.
interface IQueueEntry<T> {
event: MatrixEvent;
resolvers: PromiseWithResolvers<T>;
defer: IDeferred<T>;
attempts: number;
}
@@ -40,14 +40,41 @@ interface IQueueEntry<T> {
*/
type ProcessFunction<T> = (event: MatrixEvent) => Promise<T>;
// eslint-disable-next-line camelcase
export class MatrixScheduler<T = ISendEventResponse> {
/**
* Default retry algorithm for the matrix scheduler. Retries events up to 4 times with exponential backoff.
* Retries events up to 4 times using exponential backoff. 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
* waited before being retried.
* @param attempts - Number of attempts that have been made, including the one that just failed (ie. starting at 1)
* @see retryAlgorithm
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
public static RETRY_BACKOFF_RATELIMIT(event: MatrixEvent | null, attempts: number, err: MatrixError): number {
return calculateRetryBackoff(err, attempts, false);
if (err.httpStatus === 400 || err.httpStatus === 403 || err.httpStatus === 401) {
// client error; no amount of retrying with save you now.
return -1;
}
if (err instanceof ConnectionError) {
return -1;
}
// if event that we are trying to send is too large in any way then retrying won't help
if (err.name === "M_TOO_LARGE") {
return -1;
}
if (err.name === "M_LIMIT_EXCEEDED") {
const waitTime = err.data.retry_after_ms;
if (waitTime > 0) {
return waitTime;
}
}
if (attempts > 4) {
return -1; // give up
}
return 1000 * Math.pow(2, attempts);
}
/**
@@ -55,6 +82,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
* concurrently.
* @see queueAlgorithm
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
public static QUEUE_MESSAGES(event: MatrixEvent): string | null {
// enqueue messages or events that associate with another event (redactions and relations)
if (event.getType() === EventType.RoomMessage || event.hasAssociation()) {
@@ -67,7 +95,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
// queueName: [{
// event: MatrixEvent, // event to send
// defer: PromiseWithResolvers, // defer to resolve/reject at the END of the retries
// defer: Deferred, // defer to resolve/reject at the END of the retries
// attempts: Number // number of times we've called processFn
// }, ...]
private readonly queues: Record<string, IQueueEntry<T>[]> = {};
@@ -185,15 +213,15 @@ export class MatrixScheduler<T = ISendEventResponse> {
if (!this.queues[queueName]) {
this.queues[queueName] = [];
}
const eventResolvers = Promise.withResolvers<T>();
const deferred = defer<T>();
this.queues[queueName].push({
event: event,
resolvers: eventResolvers,
defer: deferred,
attempts: 0,
});
debuglog("Queue algorithm dumped event %s into queue '%s'", event.getId(), queueName);
this.startProcessingQueues();
return eventResolvers.promise;
return deferred.promise;
}
private startProcessingQueues(): void {
@@ -236,7 +264,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
// remove this from the queue
this.removeNextEvent(queueName);
debuglog("Queue '%s' sent event %s", queueName, obj.event.getId());
obj.resolvers.resolve(res);
obj.defer.resolve(res);
// keep processing
this.processQueue(queueName);
},
@@ -276,7 +304,7 @@ export class MatrixScheduler<T = ISendEventResponse> {
logger.info("clearing queue '%s'", queueName);
let obj: IQueueEntry<T> | undefined;
while ((obj = this.removeNextEvent(queueName))) {
obj.resolvers.reject(err);
obj.defer.reject(err);
}
this.disableQueue(queueName);
}