(args: {
chatId: string | number;
sinceUpdateId: number;
seenCount: number;
intervalMs: number;
timeoutMs: number;
onSample: (sample: {
elapsedMs: number;
text: string | undefined;
message: TelegramMessage | undefined;
}) => Promise<void> | void;
})
| 300 | * Returns the highest `update_id` consumed (`reachedUpdateId`). |
| 301 | */ |
| 302 | export async function watchForNextReply(args: { |
| 303 | chatId: string | number; |
| 304 | sinceUpdateId: number; |
| 305 | seenCount: number; |
| 306 | intervalMs: number; |
| 307 | timeoutMs: number; |
| 308 | onSample: (sample: { |
| 309 | elapsedMs: number; |
| 310 | text: string | undefined; |
| 311 | message: TelegramMessage | undefined; |
| 312 | }) => Promise<void> | void; |
| 313 | }): Promise<{ |
| 314 | finalText: string | undefined; |
| 315 | finalMessage: TelegramMessage | undefined; |
| 316 | reachedUpdateId: number; |
| 317 | }> { |
| 318 | const start = Date.now(); |
| 319 | let offset = args.sinceUpdateId + 1; |
| 320 | // Map from message_id → latest known TelegramMessage (tracks edits). |
| 321 | const botMessageMap = new Map<number, TelegramMessage>(); |
| 322 | let stable = 0; |
| 323 | let lastLen = -1; |
| 324 | let reachedUpdateId = args.sinceUpdateId; |
| 325 | |
| 326 | while (Date.now() - start < args.timeoutMs) { |
| 327 | const updates = await getUpdates(offset); |
| 328 | for (const u of updates) { |
| 329 | if (u.update_id >= offset) offset = u.update_id + 1; |
| 330 | if (u.update_id > reachedUpdateId) reachedUpdateId = u.update_id; |
| 331 | // Accept both new messages and edits. |
| 332 | const msg = u.message ?? u.edited_message; |
| 333 | if (!msg) continue; |
| 334 | if (String(msg.chat.id) !== String(args.chatId)) continue; |
| 335 | if (msg.from?.is_bot) { |
| 336 | botMessageMap.set(msg.message_id, msg); |
| 337 | } |
| 338 | } |
| 339 | // Collect distinct bot message_ids in insertion order (Map preserves it). |
| 340 | const distinctMessages = Array.from(botMessageMap.values()).sort( |
| 341 | (a, b) => a.message_id - b.message_id, |
| 342 | ); |
| 343 | // Target is the (seenCount+1)-th distinct message, i.e. the first NEW one. |
| 344 | const target = |
| 345 | distinctMessages.length > args.seenCount |
| 346 | ? distinctMessages[args.seenCount] |
| 347 | : undefined; |
| 348 | const text = target?.text; |
| 349 | await args.onSample({ |
| 350 | elapsedMs: Date.now() - start, |
| 351 | text, |
| 352 | message: target, |
| 353 | }); |
| 354 | const len = text?.length ?? 0; |
| 355 | if (target && len === lastLen && len > 0) { |
| 356 | stable++; |
| 357 | if (stable >= 3) break; |
| 358 | } else { |
| 359 | stable = 0; |
nothing calls this directly
no test coverage detected