Files
openclaw/extensions/telegram/src/message-dispatch-dedupe.ts
Peter Steinberger 77d9ac30bb refactor: reuse shared coercion helpers (#86419)
* refactor: share talk event metric extraction

* refactor: reuse shared coercion helpers

* refactor: reuse shared primitive guards

* refactor: reuse shared record guard

* refactor: reuse shared primitive helpers

* refactor: reuse shared string guards

* refactor: reuse shared non-empty string guard

* refactor: share plugin primitive coercion helpers

* refactor: reuse plugin coercion helpers

* refactor: reuse plugin coercion helpers in more plugins

* refactor: reuse channel coercion helpers

* refactor: reuse monitor coercion helpers

* refactor: reuse provider coercion helpers

* refactor: reuse core coercion helpers

* refactor: reuse runtime coercion helpers

* refactor: reuse helper coercion in codex paths

* refactor: reuse helper coercion in runtime paths

* refactor: reuse codex app-server coercion helpers

* refactor: reuse codex record helpers

* refactor: reuse migration and qa record helpers

* refactor: reuse feishu and core helper guards

* refactor: reuse browser and policy coercion helpers

* refactor: reuse memory wiki record helper

* refactor: share boolean coercion helpers

* refactor: reuse finite number coercion

* refactor: reuse trimmed string list helpers

* refactor: reuse string list normalization

* refactor: reuse remaining string list helpers

* refactor: reuse string entry normalizer

* refactor: share sorted string helpers

* refactor: share string list normalization

* test: preserve command registry browser imports

* refactor: reuse trimmed list helpers

* refactor: reuse string dedupe helpers

* refactor: reuse local dedupe helpers

* refactor: reuse more string dedupe helpers

* refactor: reuse command string dedupe helpers

* refactor: dedupe memory path lists with helper

* refactor: expose string dedupe helpers to plugins

* refactor: reuse core string dedupe helpers

* refactor: reuse shared unique value helpers

* refactor: reuse unique helpers in agent utilities

* refactor: reuse unique helpers in config plumbing

* refactor: reuse unique helpers in extensions

* refactor: reuse unique helpers in core utilities

* refactor: reuse unique helpers in qa plugins

* refactor: reuse unique helpers in memory plugins

* refactor: reuse unique helpers in channel plugins

* refactor: reuse unique helpers in core tails

* refactor: reuse unique helper in comfy workflow

* refactor: reuse unique helpers in test utilities

* refactor: expose unique value helper to plugins

* refactor: reuse unique helpers for numeric lists

* refactor: replace index dedupe filters

* refactor: reuse string entry normalization

* refactor: reuse string normalization in plugin helpers

* refactor: reuse string normalization in extension helpers

* refactor: reuse string normalization in channel parsers

* refactor: reuse string normalization in memory search

* refactor: reuse string normalization in provider parsers

* refactor: reuse string normalization in qa helpers

* refactor: reuse string normalization in infra parsers

* refactor: reuse string normalization in messaging parsers

* refactor: reuse string normalization in core parsers

* refactor: reuse string normalization in extension parsers

* refactor: reuse string normalization in remaining parsers

* refactor: reuse string normalization in final parser spots

* refactor: reuse string normalization in qa media helpers

* refactor: reuse normalization in provider and media lists

* refactor: reuse normalization for remaining set filters

* refactor: reuse normalization in policy allowlists

* refactor: reuse normalization in session and owner lists

* refactor: centralize primitive string lists

* refactor: reuse lowercase entry helpers

* refactor: reuse sorted string helpers

* refactor: reuse unique trimmed helpers

* refactor: reuse string normalization helpers

* refactor: reuse catalog string helpers

* refactor: reuse remaining string helpers

* refactor: simplify remaining list normalization

* refactor: reuse codex auth order normalization

* chore: refresh plugin sdk api baseline

* fix: make shared string sorting deterministic

* chore: refresh plugin sdk api baseline

* fix: align host env security ordering
2026-05-25 21:20:41 +01:00

108 lines
3.3 KiB
TypeScript

import path from "node:path";
import type { Message } from "grammy/types";
import { createClaimableDedupe, type ClaimableDedupe } from "openclaw/plugin-sdk/persistent-dedupe";
import { normalizeStringEntries, uniqueStrings } from "openclaw/plugin-sdk/string-coerce-runtime";
const TELEGRAM_MESSAGE_DISPATCH_TTL_MS = 7 * 24 * 60 * 60 * 1000;
const TELEGRAM_MESSAGE_DISPATCH_MEMORY_MAX = 5000;
const TELEGRAM_MESSAGE_DISPATCH_FILE_MAX = 50_000;
export type TelegramMessageDispatchReplayGuard = ClaimableDedupe;
export type TelegramMessageDispatchClaim =
| { kind: "claimed"; key: string }
| { kind: "duplicate" }
| { kind: "invalid" };
function sanitizeFileSegment(value: string): string {
const trimmed = value.trim();
if (!trimmed) {
return "default";
}
return trimmed.replace(/[^a-zA-Z0-9_-]/g, "_");
}
export function buildTelegramMessageDispatchReplayKey(msg: Message): string | null {
const chatId = msg.chat?.id;
const messageId = msg.message_id;
if (chatId == null || typeof messageId !== "number" || messageId <= 0) {
return null;
}
return JSON.stringify(["message", String(chatId), messageId]);
}
export function createTelegramMessageDispatchReplayGuard(params: {
storePath: string;
onDiskError?: (error: unknown) => void;
}): TelegramMessageDispatchReplayGuard {
return createClaimableDedupe({
ttlMs: TELEGRAM_MESSAGE_DISPATCH_TTL_MS,
memoryMaxSize: TELEGRAM_MESSAGE_DISPATCH_MEMORY_MAX,
fileMaxEntries: TELEGRAM_MESSAGE_DISPATCH_FILE_MAX,
resolveFilePath: (namespace) =>
path.join(
path.dirname(params.storePath),
`${path.basename(params.storePath)}.telegram-message-dispatch-${sanitizeFileSegment(
namespace,
)}.json`,
),
onDiskError: params.onDiskError,
});
}
export async function claimTelegramMessageDispatchReplay(params: {
guard: TelegramMessageDispatchReplayGuard;
accountId: string;
msg: Message;
}): Promise<TelegramMessageDispatchClaim> {
const key = buildTelegramMessageDispatchReplayKey(params.msg);
if (!key) {
return { kind: "invalid" };
}
let releaseRetries = 0;
while (true) {
const claim = await params.guard.claim(key, { namespace: params.accountId });
if (claim.kind === "claimed") {
return { kind: "claimed", key };
}
if (claim.kind === "duplicate") {
return { kind: "duplicate" };
}
try {
await claim.pending;
return { kind: "duplicate" };
} catch {
releaseRetries += 1;
if (releaseRetries > 1) {
return { kind: "duplicate" };
}
}
}
}
function normalizeReplayKeys(keys?: readonly string[]): string[] {
return uniqueStrings(normalizeStringEntries(keys ?? []));
}
export async function commitTelegramMessageDispatchReplay(params: {
guard: TelegramMessageDispatchReplayGuard;
accountId: string;
keys?: readonly string[];
}): Promise<void> {
const keys = normalizeReplayKeys(params.keys);
await Promise.all(keys.map((key) => params.guard.commit(key, { namespace: params.accountId })));
}
export function releaseTelegramMessageDispatchReplay(params: {
guard: TelegramMessageDispatchReplayGuard;
accountId: string;
keys?: readonly string[];
error?: unknown;
}): void {
const keys = normalizeReplayKeys(params.keys);
for (const key of keys) {
params.guard.release(key, { namespace: params.accountId, error: params.error });
}
}