diff --git a/server.js b/server.js index d33d845..45be1fe 100644 --- a/server.js +++ b/server.js @@ -163,9 +163,34 @@ function sendTelegram(text, topicId, replyMarkup) { } // Same bot, any chat: the team forum, or the public payment-proof channel // (config.telegramProofChatId — the bot just has to be an admin there). +// Last-resort duplicate guard, at the point of send. +// +// 2026-09-18: members saw the same payout line posted twice in the proof channel and the +// shared payments topic — "did this person get paid twice?" The money was always right; only +// the announcement doubled. The indexer now has a durable announce-once key per on-chain +// event, which is the real fix, but a payment feed is the last place to rely on a single +// layer: anything upstream that replays an event would put a second receipt in front of +// members and make them doubt the ledger. So the sender itself refuses to post the same text +// to the same chat twice within the window. Distinct events never collide — every line +// carries its own ids, amount and transaction hash. +const TG_RECENT = new Map(); +const TG_DEDUPE_MS = 15 * 60 * 1000; +function tgSeenRecently(chatId, topicId, text) { + const k = String(chatId) + '|' + String(topicId || '') + '|' + text; + const now = Date.now(); + if (TG_RECENT.size > 500) for (const [kk, ts] of TG_RECENT) if (now - ts > TG_DEDUPE_MS) TG_RECENT.delete(kk); + const prev = TG_RECENT.get(k); + if (prev && now - prev < TG_DEDUPE_MS) return true; + TG_RECENT.set(k, now); + return false; +} function sendTelegramTo(chatId, text, topicId, replyMarkup, parseMode) { const c = getConfig(); if (!c.telegramBotToken || !chatId) return; + if (tgSeenRecently(chatId, topicId, text)) { + console.warn('telegram duplicate suppressed:', String(text).replace(/\s+/g, ' ').slice(0, 90)); + return; + } // No link previews: payout/team-build posts carry Polygonscan links, and the // preview card tripled the height of every message in the group. const payload = { chat_id: chatId, text, disable_web_page_preview: true };