mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-29 05:02:04 +03:00
fix: recover telegram sends from stale thread ids
This commit is contained in:
@@ -28,6 +28,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Subagents: stabilize announce timing, preserve compaction metrics across retries, clamp overflow-prone long timeouts, and cap impossible context usage token totals. (#11551) Thanks @tyler6204.
|
- Subagents: stabilize announce timing, preserve compaction metrics across retries, clamp overflow-prone long timeouts, and cap impossible context usage token totals. (#11551) Thanks @tyler6204.
|
||||||
- Agents: recover from context overflow caused by oversized tool results (pre-emptive capping + fallback truncation). (#11579) Thanks @tyler6204.
|
- Agents: recover from context overflow caused by oversized tool results (pre-emptive capping + fallback truncation). (#11579) Thanks @tyler6204.
|
||||||
- Telegram: render markdown spoilers with `<tg-spoiler>` HTML tags. (#11543) Thanks @ezhikkk.
|
- Telegram: render markdown spoilers with `<tg-spoiler>` HTML tags. (#11543) Thanks @ezhikkk.
|
||||||
|
- Telegram: recover proactive sends when stale topic thread IDs are used by retrying without `message_thread_id`, and clear explicit no-thread route updates instead of inheriting stale thread state. (#11620)
|
||||||
- Gateway/CLI: when `gateway.bind=lan`, use a LAN IP for probe URLs and Control UI links. (#11448) Thanks @AnonO6.
|
- Gateway/CLI: when `gateway.bind=lan`, use a LAN IP for probe URLs and Control UI links. (#11448) Thanks @AnonO6.
|
||||||
- Memory: set Voyage embeddings `input_type` for improved retrieval. (#10818) Thanks @mcinteerj.
|
- Memory: set Voyage embeddings `input_type` for improved retrieval. (#10818) Thanks @mcinteerj.
|
||||||
- Memory/QMD: run boot refresh in background by default, add configurable QMD maintenance timeouts, and retry QMD after fallback failures. (#9690, #9705)
|
- Memory/QMD: run boot refresh in background by default, add configurable QMD maintenance timeouts, and retry QMD after fallback failures. (#9690, #9705)
|
||||||
|
|||||||
@@ -176,6 +176,51 @@ describe("sessions", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("updateLastRoute clears threadId when deliveryContext explicitly omits it", async () => {
|
||||||
|
const mainSessionKey = "agent:main:main";
|
||||||
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
|
||||||
|
const storePath = path.join(dir, "sessions.json");
|
||||||
|
await fs.writeFile(
|
||||||
|
storePath,
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
[mainSessionKey]: {
|
||||||
|
sessionId: "sess-1",
|
||||||
|
updatedAt: 123,
|
||||||
|
deliveryContext: {
|
||||||
|
channel: "telegram",
|
||||||
|
to: "222",
|
||||||
|
threadId: "42",
|
||||||
|
},
|
||||||
|
lastChannel: "telegram",
|
||||||
|
lastTo: "222",
|
||||||
|
lastThreadId: "42",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
"utf-8",
|
||||||
|
);
|
||||||
|
|
||||||
|
await updateLastRoute({
|
||||||
|
storePath,
|
||||||
|
sessionKey: mainSessionKey,
|
||||||
|
deliveryContext: {
|
||||||
|
channel: "telegram",
|
||||||
|
to: "222",
|
||||||
|
threadId: undefined,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = loadSessionStore(storePath);
|
||||||
|
expect(store[mainSessionKey]?.deliveryContext).toEqual({
|
||||||
|
channel: "telegram",
|
||||||
|
to: "222",
|
||||||
|
});
|
||||||
|
expect(store[mainSessionKey]?.lastThreadId).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
it("updateLastRoute records origin + group metadata when ctx is provided", async () => {
|
it("updateLastRoute records origin + group metadata when ctx is provided", async () => {
|
||||||
const sessionKey = "agent:main:whatsapp:group:123@g.us";
|
const sessionKey = "agent:main:whatsapp:group:123@g.us";
|
||||||
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
|
const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-sessions-"));
|
||||||
|
|||||||
@@ -86,6 +86,15 @@ function normalizeSessionEntryDelivery(entry: SessionEntry): SessionEntry {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function removeThreadFromDeliveryContext(context?: DeliveryContext): DeliveryContext | undefined {
|
||||||
|
if (!context || context.threadId == null) {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
const next: DeliveryContext = { ...context };
|
||||||
|
delete next.threadId;
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeSessionStore(store: Record<string, SessionEntry>): void {
|
function normalizeSessionStore(store: Record<string, SessionEntry>): void {
|
||||||
for (const [key, entry] of Object.entries(store)) {
|
for (const [key, entry] of Object.entries(store)) {
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
@@ -430,7 +439,15 @@ export async function updateLastRoute(params: {
|
|||||||
threadId,
|
threadId,
|
||||||
});
|
});
|
||||||
const mergedInput = mergeDeliveryContext(explicitContext, inlineContext);
|
const mergedInput = mergeDeliveryContext(explicitContext, inlineContext);
|
||||||
const merged = mergeDeliveryContext(mergedInput, deliveryContextFromSession(existing));
|
const explicitDeliveryContext = params.deliveryContext;
|
||||||
|
const clearThreadFromFallback =
|
||||||
|
explicitDeliveryContext != null &&
|
||||||
|
Object.prototype.hasOwnProperty.call(explicitDeliveryContext, "threadId") &&
|
||||||
|
explicitDeliveryContext.threadId == null;
|
||||||
|
const fallbackContext = clearThreadFromFallback
|
||||||
|
? removeThreadFromDeliveryContext(deliveryContextFromSession(existing))
|
||||||
|
: deliveryContextFromSession(existing);
|
||||||
|
const merged = mergeDeliveryContext(mergedInput, fallbackContext);
|
||||||
const normalized = normalizeSessionDeliveryFields({
|
const normalized = normalizeSessionDeliveryFields({
|
||||||
deliveryContext: {
|
deliveryContext: {
|
||||||
channel: merged?.channel,
|
channel: merged?.channel,
|
||||||
|
|||||||
@@ -478,6 +478,36 @@ describe("sendMessageTelegram", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("retries without message_thread_id when Telegram reports missing thread", async () => {
|
||||||
|
const chatId = "123";
|
||||||
|
const threadErr = new Error("400: Bad Request: message thread not found");
|
||||||
|
const sendMessage = vi
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValueOnce(threadErr)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
message_id: 58,
|
||||||
|
chat: { id: chatId },
|
||||||
|
});
|
||||||
|
const api = { sendMessage } as unknown as {
|
||||||
|
sendMessage: typeof sendMessage;
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await sendMessageTelegram(chatId, "hello forum", {
|
||||||
|
token: "tok",
|
||||||
|
api,
|
||||||
|
messageThreadId: 271,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sendMessage).toHaveBeenNthCalledWith(1, chatId, "hello forum", {
|
||||||
|
parse_mode: "HTML",
|
||||||
|
message_thread_id: 271,
|
||||||
|
});
|
||||||
|
expect(sendMessage).toHaveBeenNthCalledWith(2, chatId, "hello forum", {
|
||||||
|
parse_mode: "HTML",
|
||||||
|
});
|
||||||
|
expect(res.messageId).toBe("58");
|
||||||
|
});
|
||||||
|
|
||||||
it("sets disable_notification when silent is true", async () => {
|
it("sets disable_notification when silent is true", async () => {
|
||||||
const chatId = "123";
|
const chatId = "123";
|
||||||
const sendMessage = vi.fn().mockResolvedValue({
|
const sendMessage = vi.fn().mockResolvedValue({
|
||||||
@@ -566,6 +596,45 @@ describe("sendMessageTelegram", () => {
|
|||||||
reply_to_message_id: 500,
|
reply_to_message_id: 500,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("retries media sends without message_thread_id when thread is missing", async () => {
|
||||||
|
const chatId = "123";
|
||||||
|
const threadErr = new Error("400: Bad Request: message thread not found");
|
||||||
|
const sendPhoto = vi
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValueOnce(threadErr)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
message_id: 59,
|
||||||
|
chat: { id: chatId },
|
||||||
|
});
|
||||||
|
const api = { sendPhoto } as unknown as {
|
||||||
|
sendPhoto: typeof sendPhoto;
|
||||||
|
};
|
||||||
|
|
||||||
|
loadWebMedia.mockResolvedValueOnce({
|
||||||
|
buffer: Buffer.from("fake-image"),
|
||||||
|
contentType: "image/jpeg",
|
||||||
|
fileName: "photo.jpg",
|
||||||
|
});
|
||||||
|
|
||||||
|
const res = await sendMessageTelegram(chatId, "photo", {
|
||||||
|
token: "tok",
|
||||||
|
api,
|
||||||
|
mediaUrl: "https://example.com/photo.jpg",
|
||||||
|
messageThreadId: 271,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sendPhoto).toHaveBeenNthCalledWith(1, chatId, expect.anything(), {
|
||||||
|
caption: "photo",
|
||||||
|
parse_mode: "HTML",
|
||||||
|
message_thread_id: 271,
|
||||||
|
});
|
||||||
|
expect(sendPhoto).toHaveBeenNthCalledWith(2, chatId, expect.anything(), {
|
||||||
|
caption: "photo",
|
||||||
|
parse_mode: "HTML",
|
||||||
|
});
|
||||||
|
expect(res.messageId).toBe("59");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("sendStickerTelegram", () => {
|
describe("sendStickerTelegram", () => {
|
||||||
@@ -626,6 +695,33 @@ describe("sendStickerTelegram", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("retries sticker sends without message_thread_id when thread is missing", async () => {
|
||||||
|
const chatId = "123";
|
||||||
|
const threadErr = new Error("400: Bad Request: message thread not found");
|
||||||
|
const sendSticker = vi
|
||||||
|
.fn()
|
||||||
|
.mockRejectedValueOnce(threadErr)
|
||||||
|
.mockResolvedValueOnce({
|
||||||
|
message_id: 109,
|
||||||
|
chat: { id: chatId },
|
||||||
|
});
|
||||||
|
const api = { sendSticker } as unknown as {
|
||||||
|
sendSticker: typeof sendSticker;
|
||||||
|
};
|
||||||
|
|
||||||
|
const res = await sendStickerTelegram(chatId, "fileId123", {
|
||||||
|
token: "tok",
|
||||||
|
api,
|
||||||
|
messageThreadId: 271,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sendSticker).toHaveBeenNthCalledWith(1, chatId, "fileId123", {
|
||||||
|
message_thread_id: 271,
|
||||||
|
});
|
||||||
|
expect(sendSticker).toHaveBeenNthCalledWith(2, chatId, "fileId123", undefined);
|
||||||
|
expect(res.messageId).toBe("109");
|
||||||
|
});
|
||||||
|
|
||||||
it("includes reply_to_message_id for threaded replies", async () => {
|
it("includes reply_to_message_id for threaded replies", async () => {
|
||||||
const chatId = "123";
|
const chatId = "123";
|
||||||
const fileId = "CAACAgIAAxkBAAI...sticker_file_id";
|
const fileId = "CAACAgIAAxkBAAI...sticker_file_id";
|
||||||
|
|||||||
+183
-66
@@ -69,6 +69,7 @@ type TelegramReactionOpts = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PARSE_ERR_RE = /can't parse entities|parse entities|find end of the entity/i;
|
const PARSE_ERR_RE = /can't parse entities|parse entities|find end of the entity/i;
|
||||||
|
const THREAD_NOT_FOUND_RE = /400:\s*Bad Request:\s*message thread not found/i;
|
||||||
const diagLogger = createSubsystemLogger("telegram/diagnostic");
|
const diagLogger = createSubsystemLogger("telegram/diagnostic");
|
||||||
|
|
||||||
function createTelegramHttpLogger(cfg: ReturnType<typeof loadConfig>) {
|
function createTelegramHttpLogger(cfg: ReturnType<typeof loadConfig>) {
|
||||||
@@ -173,6 +174,25 @@ function normalizeMessageId(raw: string | number): number {
|
|||||||
throw new Error("Message id is required for Telegram actions");
|
throw new Error("Message id is required for Telegram actions");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTelegramThreadNotFoundError(err: unknown): boolean {
|
||||||
|
return THREAD_NOT_FOUND_RE.test(formatErrorMessage(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasMessageThreadIdParam(params?: Record<string, unknown>): boolean {
|
||||||
|
return Boolean(params && Object.hasOwn(params, "message_thread_id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeMessageThreadIdParam(
|
||||||
|
params?: Record<string, unknown>,
|
||||||
|
): Record<string, unknown> | undefined {
|
||||||
|
if (!params || !hasMessageThreadIdParam(params)) {
|
||||||
|
return params;
|
||||||
|
}
|
||||||
|
const next = { ...params };
|
||||||
|
delete next.message_thread_id;
|
||||||
|
return Object.keys(next).length > 0 ? next : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function buildInlineKeyboard(
|
export function buildInlineKeyboard(
|
||||||
buttons?: TelegramSendOpts["buttons"],
|
buttons?: TelegramSendOpts["buttons"],
|
||||||
): InlineKeyboardMarkup | undefined {
|
): InlineKeyboardMarkup | undefined {
|
||||||
@@ -265,6 +285,30 @@ export async function sendMessageTelegram(
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendWithThreadFallback = async <T>(
|
||||||
|
params: Record<string, unknown> | undefined,
|
||||||
|
label: string,
|
||||||
|
attempt: (
|
||||||
|
effectiveParams: Record<string, unknown> | undefined,
|
||||||
|
effectiveLabel: string,
|
||||||
|
) => Promise<T>,
|
||||||
|
): Promise<T> => {
|
||||||
|
try {
|
||||||
|
return await attempt(params, label);
|
||||||
|
} catch (err) {
|
||||||
|
if (!hasMessageThreadIdParam(params) || !isTelegramThreadNotFoundError(err)) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
if (opts.verbose) {
|
||||||
|
console.warn(
|
||||||
|
`telegram ${label} failed with message_thread_id, retrying without thread: ${formatErrorMessage(err)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const retriedParams = removeMessageThreadIdParam(params);
|
||||||
|
return await attempt(retriedParams, `${label}-threadless`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const textMode = opts.textMode ?? "markdown";
|
const textMode = opts.textMode ?? "markdown";
|
||||||
const tableMode = resolveMarkdownTableMode({
|
const tableMode = resolveMarkdownTableMode({
|
||||||
cfg,
|
cfg,
|
||||||
@@ -282,43 +326,48 @@ export async function sendMessageTelegram(
|
|||||||
params?: Record<string, unknown>,
|
params?: Record<string, unknown>,
|
||||||
fallbackText?: string,
|
fallbackText?: string,
|
||||||
) => {
|
) => {
|
||||||
const htmlText = renderHtmlText(rawText);
|
return await sendWithThreadFallback(params, "message", async (effectiveParams, label) => {
|
||||||
const baseParams = params ? { ...params } : {};
|
const htmlText = renderHtmlText(rawText);
|
||||||
if (linkPreviewOptions) {
|
const baseParams = effectiveParams ? { ...effectiveParams } : {};
|
||||||
baseParams.link_preview_options = linkPreviewOptions;
|
if (linkPreviewOptions) {
|
||||||
}
|
baseParams.link_preview_options = linkPreviewOptions;
|
||||||
const hasBaseParams = Object.keys(baseParams).length > 0;
|
|
||||||
const sendParams = {
|
|
||||||
parse_mode: "HTML" as const,
|
|
||||||
...baseParams,
|
|
||||||
...(opts.silent === true ? { disable_notification: true } : {}),
|
|
||||||
};
|
|
||||||
const res = await requestWithDiag(
|
|
||||||
() => api.sendMessage(chatId, htmlText, sendParams),
|
|
||||||
"message",
|
|
||||||
).catch(async (err) => {
|
|
||||||
// Telegram rejects malformed HTML (e.g., unsupported tags or entities).
|
|
||||||
// When that happens, fall back to plain text so the message still delivers.
|
|
||||||
const errText = formatErrorMessage(err);
|
|
||||||
if (PARSE_ERR_RE.test(errText)) {
|
|
||||||
if (opts.verbose) {
|
|
||||||
console.warn(`telegram HTML parse failed, retrying as plain text: ${errText}`);
|
|
||||||
}
|
|
||||||
const fallback = fallbackText ?? rawText;
|
|
||||||
const plainParams = hasBaseParams ? baseParams : undefined;
|
|
||||||
return await requestWithDiag(
|
|
||||||
() =>
|
|
||||||
plainParams
|
|
||||||
? api.sendMessage(chatId, fallback, plainParams)
|
|
||||||
: api.sendMessage(chatId, fallback),
|
|
||||||
"message-plain",
|
|
||||||
).catch((err2) => {
|
|
||||||
throw wrapChatNotFound(err2);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
throw wrapChatNotFound(err);
|
const hasBaseParams = Object.keys(baseParams).length > 0;
|
||||||
|
const sendParams = {
|
||||||
|
parse_mode: "HTML" as const,
|
||||||
|
...baseParams,
|
||||||
|
...(opts.silent === true ? { disable_notification: true } : {}),
|
||||||
|
};
|
||||||
|
const res = await requestWithDiag(
|
||||||
|
() =>
|
||||||
|
api.sendMessage(chatId, htmlText, sendParams as Parameters<typeof api.sendMessage>[2]),
|
||||||
|
label,
|
||||||
|
).catch(async (err) => {
|
||||||
|
// Telegram rejects malformed HTML (e.g., unsupported tags or entities).
|
||||||
|
// When that happens, fall back to plain text so the message still delivers.
|
||||||
|
const errText = formatErrorMessage(err);
|
||||||
|
if (PARSE_ERR_RE.test(errText)) {
|
||||||
|
if (opts.verbose) {
|
||||||
|
console.warn(`telegram HTML parse failed, retrying as plain text: ${errText}`);
|
||||||
|
}
|
||||||
|
const fallback = fallbackText ?? rawText;
|
||||||
|
const plainParams = hasBaseParams
|
||||||
|
? (baseParams as Parameters<typeof api.sendMessage>[2])
|
||||||
|
: undefined;
|
||||||
|
return await requestWithDiag(
|
||||||
|
() =>
|
||||||
|
plainParams
|
||||||
|
? api.sendMessage(chatId, fallback, plainParams)
|
||||||
|
: api.sendMessage(chatId, fallback),
|
||||||
|
`${label}-plain`,
|
||||||
|
).catch((err2) => {
|
||||||
|
throw wrapChatNotFound(err2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw wrapChatNotFound(err);
|
||||||
|
});
|
||||||
|
return res;
|
||||||
});
|
});
|
||||||
return res;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (mediaUrl) {
|
if (mediaUrl) {
|
||||||
@@ -355,23 +404,39 @@ export async function sendMessageTelegram(
|
|||||||
| Awaited<ReturnType<typeof api.sendAnimation>>
|
| Awaited<ReturnType<typeof api.sendAnimation>>
|
||||||
| Awaited<ReturnType<typeof api.sendDocument>>;
|
| Awaited<ReturnType<typeof api.sendDocument>>;
|
||||||
if (isGif) {
|
if (isGif) {
|
||||||
result = await requestWithDiag(
|
result = await sendWithThreadFallback(
|
||||||
() => api.sendAnimation(chatId, file, mediaParams),
|
mediaParams,
|
||||||
"animation",
|
"animation",
|
||||||
).catch((err) => {
|
async (effectiveParams, label) =>
|
||||||
throw wrapChatNotFound(err);
|
requestWithDiag(
|
||||||
});
|
() =>
|
||||||
|
api.sendAnimation(
|
||||||
|
chatId,
|
||||||
|
file,
|
||||||
|
effectiveParams as Parameters<typeof api.sendAnimation>[2],
|
||||||
|
),
|
||||||
|
label,
|
||||||
|
).catch((err) => {
|
||||||
|
throw wrapChatNotFound(err);
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else if (kind === "image") {
|
} else if (kind === "image") {
|
||||||
result = await requestWithDiag(() => api.sendPhoto(chatId, file, mediaParams), "photo").catch(
|
result = await sendWithThreadFallback(mediaParams, "photo", async (effectiveParams, label) =>
|
||||||
(err) => {
|
requestWithDiag(
|
||||||
|
() => api.sendPhoto(chatId, file, effectiveParams as Parameters<typeof api.sendPhoto>[2]),
|
||||||
|
label,
|
||||||
|
).catch((err) => {
|
||||||
throw wrapChatNotFound(err);
|
throw wrapChatNotFound(err);
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
} else if (kind === "video") {
|
} else if (kind === "video") {
|
||||||
result = await requestWithDiag(() => api.sendVideo(chatId, file, mediaParams), "video").catch(
|
result = await sendWithThreadFallback(mediaParams, "video", async (effectiveParams, label) =>
|
||||||
(err) => {
|
requestWithDiag(
|
||||||
|
() => api.sendVideo(chatId, file, effectiveParams as Parameters<typeof api.sendVideo>[2]),
|
||||||
|
label,
|
||||||
|
).catch((err) => {
|
||||||
throw wrapChatNotFound(err);
|
throw wrapChatNotFound(err);
|
||||||
},
|
}),
|
||||||
);
|
);
|
||||||
} else if (kind === "audio") {
|
} else if (kind === "audio") {
|
||||||
const { useVoice } = resolveTelegramVoiceSend({
|
const { useVoice } = resolveTelegramVoiceSend({
|
||||||
@@ -381,27 +446,49 @@ export async function sendMessageTelegram(
|
|||||||
logFallback: logVerbose,
|
logFallback: logVerbose,
|
||||||
});
|
});
|
||||||
if (useVoice) {
|
if (useVoice) {
|
||||||
result = await requestWithDiag(
|
result = await sendWithThreadFallback(
|
||||||
() => api.sendVoice(chatId, file, mediaParams),
|
mediaParams,
|
||||||
"voice",
|
"voice",
|
||||||
).catch((err) => {
|
async (effectiveParams, label) =>
|
||||||
throw wrapChatNotFound(err);
|
requestWithDiag(
|
||||||
});
|
() =>
|
||||||
|
api.sendVoice(chatId, file, effectiveParams as Parameters<typeof api.sendVoice>[2]),
|
||||||
|
label,
|
||||||
|
).catch((err) => {
|
||||||
|
throw wrapChatNotFound(err);
|
||||||
|
}),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
result = await requestWithDiag(
|
result = await sendWithThreadFallback(
|
||||||
() => api.sendAudio(chatId, file, mediaParams),
|
mediaParams,
|
||||||
"audio",
|
"audio",
|
||||||
).catch((err) => {
|
async (effectiveParams, label) =>
|
||||||
throw wrapChatNotFound(err);
|
requestWithDiag(
|
||||||
});
|
() =>
|
||||||
|
api.sendAudio(chatId, file, effectiveParams as Parameters<typeof api.sendAudio>[2]),
|
||||||
|
label,
|
||||||
|
).catch((err) => {
|
||||||
|
throw wrapChatNotFound(err);
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result = await requestWithDiag(
|
result = await sendWithThreadFallback(
|
||||||
() => api.sendDocument(chatId, file, mediaParams),
|
mediaParams,
|
||||||
"document",
|
"document",
|
||||||
).catch((err) => {
|
async (effectiveParams, label) =>
|
||||||
throw wrapChatNotFound(err);
|
requestWithDiag(
|
||||||
});
|
() =>
|
||||||
|
api.sendDocument(
|
||||||
|
chatId,
|
||||||
|
file,
|
||||||
|
effectiveParams as Parameters<typeof api.sendDocument>[2],
|
||||||
|
),
|
||||||
|
label,
|
||||||
|
).catch((err) => {
|
||||||
|
throw wrapChatNotFound(err);
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const mediaMessageId = String(result?.message_id ?? "unknown");
|
const mediaMessageId = String(result?.message_id ?? "unknown");
|
||||||
const resolvedChatId = String(result?.chat?.id ?? chatId);
|
const resolvedChatId = String(result?.chat?.id ?? chatId);
|
||||||
@@ -730,14 +817,44 @@ export async function sendStickerTelegram(
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const sendWithThreadFallback = async <T>(
|
||||||
|
params: Record<string, number> | undefined,
|
||||||
|
label: string,
|
||||||
|
attempt: (
|
||||||
|
effectiveParams: Record<string, number> | undefined,
|
||||||
|
effectiveLabel: string,
|
||||||
|
) => Promise<T>,
|
||||||
|
): Promise<T> => {
|
||||||
|
try {
|
||||||
|
return await attempt(params, label);
|
||||||
|
} catch (err) {
|
||||||
|
if (!hasMessageThreadIdParam(params) || !isTelegramThreadNotFoundError(err)) {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
if (opts.verbose) {
|
||||||
|
console.warn(
|
||||||
|
`telegram ${label} failed with message_thread_id, retrying without thread: ${formatErrorMessage(err)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const retriedParams = removeMessageThreadIdParam(params) as
|
||||||
|
| Record<string, number>
|
||||||
|
| undefined;
|
||||||
|
return await attempt(retriedParams, `${label}-threadless`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const stickerParams = hasThreadParams ? threadParams : undefined;
|
const stickerParams = hasThreadParams ? threadParams : undefined;
|
||||||
|
|
||||||
const result = await requestWithDiag(
|
const result = await sendWithThreadFallback(
|
||||||
() => api.sendSticker(chatId, fileId.trim(), stickerParams),
|
stickerParams,
|
||||||
"sticker",
|
"sticker",
|
||||||
).catch((err) => {
|
async (effectiveParams, label) =>
|
||||||
throw wrapChatNotFound(err);
|
requestWithDiag(() => api.sendSticker(chatId, fileId.trim(), effectiveParams), label).catch(
|
||||||
});
|
(err) => {
|
||||||
|
throw wrapChatNotFound(err);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
const messageId = String(result?.message_id ?? "unknown");
|
const messageId = String(result?.message_id ?? "unknown");
|
||||||
const resolvedChatId = String(result?.chat?.id ?? chatId);
|
const resolvedChatId = String(result?.chat?.id ?? chatId);
|
||||||
|
|||||||
Reference in New Issue
Block a user