Files
Moltbot/src/telegram/sent-message-cache.ts
Vincent Koc 4ca84acf24 fix(runtime): duplicate messages, share singleton state across bundled chunks (#43683)
* Tests: add fresh module import helper

* Process: share command queue runtime state

* Agents: share embedded run runtime state

* Reply: share followup queue runtime state

* Reply: share followup drain callback state

* Reply: share queued message dedupe state

* Reply: share inbound dedupe state

* Tests: cover shared command queue runtime state

* Tests: cover shared embedded run runtime state

* Tests: cover shared followup queue runtime state

* Tests: cover shared inbound dedupe state

* Tests: cover shared Slack thread participation state

* Slack: share sent thread participation state

* Tests: document fresh import helper

* Telegram: share draft stream runtime state

* Tests: cover shared Telegram draft stream state

* Telegram: share sent message cache state

* Tests: cover shared Telegram sent message cache

* Telegram: share thread binding runtime state

* Tests: cover shared Telegram thread binding state

* Tests: avoid duplicate shared queue reset

* refactor(runtime): centralize global singleton access

* refactor(runtime): preserve undefined global singleton values

* test(runtime): cover undefined global singleton values

---------

Co-authored-by: Nimrod Gutman <nimrod.gutman@gmail.com>
2026-03-12 14:59:27 -04:00

72 lines
1.8 KiB
TypeScript

import { resolveGlobalMap } from "../shared/global-singleton.js";
/**
* In-memory cache of sent message IDs per chat.
* Used to identify bot's own messages for reaction filtering ("own" mode).
*/
const TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
type CacheEntry = {
timestamps: Map<number, number>;
};
/**
* Keep sent-message tracking shared across bundled chunks so Telegram reaction
* filters see the same sent-message history regardless of which chunk recorded it.
*/
const TELEGRAM_SENT_MESSAGES_KEY = Symbol.for("openclaw.telegramSentMessages");
const sentMessages = resolveGlobalMap<string, CacheEntry>(TELEGRAM_SENT_MESSAGES_KEY);
function getChatKey(chatId: number | string): string {
return String(chatId);
}
function cleanupExpired(entry: CacheEntry): void {
const now = Date.now();
for (const [msgId, timestamp] of entry.timestamps) {
if (now - timestamp > TTL_MS) {
entry.timestamps.delete(msgId);
}
}
}
/**
* Record a message ID as sent by the bot.
*/
export function recordSentMessage(chatId: number | string, messageId: number): void {
const key = getChatKey(chatId);
let entry = sentMessages.get(key);
if (!entry) {
entry = { timestamps: new Map() };
sentMessages.set(key, entry);
}
entry.timestamps.set(messageId, Date.now());
// Periodic cleanup
if (entry.timestamps.size > 100) {
cleanupExpired(entry);
}
}
/**
* Check if a message was sent by the bot.
*/
export function wasSentByBot(chatId: number | string, messageId: number): boolean {
const key = getChatKey(chatId);
const entry = sentMessages.get(key);
if (!entry) {
return false;
}
// Clean up expired entries on read
cleanupExpired(entry);
return entry.timestamps.has(messageId);
}
/**
* Clear all cached entries (for testing).
*/
export function clearSentMessageCache(): void {
sentMessages.clear();
}