fix(telegram): mark message_sent success only when delivery occurred

This commit is contained in:
KimGLee
2026-03-03 14:42:43 +08:00
committed by Ayaan Zaidi
parent 6d118ab815
commit 9830b7c298
2 changed files with 42 additions and 1 deletions

View File

@@ -33,6 +33,7 @@ const CAPTION_TOO_LONG_RE = /caption is too long/i;
type DeliveryProgress = {
hasReplied: boolean;
hasDelivered: boolean;
deliveredCount: number;
};
type ChunkTextFn = (markdown: string) => ReturnType<typeof markdownToTelegramChunks>;
@@ -85,6 +86,7 @@ function markReplyApplied(progress: DeliveryProgress, replyToId?: number): void
function markDelivered(progress: DeliveryProgress): void {
progress.hasDelivered = true;
progress.deliveredCount += 1;
}
async function deliverTextReply(params: {
@@ -445,6 +447,7 @@ export async function deliverReplies(params: {
const progress: DeliveryProgress = {
hasReplied: false,
hasDelivered: false,
deliveredCount: 0,
};
const hookRunner = getGlobalHookRunner();
const hasMessageSendingHooks = hookRunner?.hasHooks("message_sending") ?? false;
@@ -489,6 +492,7 @@ export async function deliverReplies(params: {
const contentForSentHook = reply.text || "";
try {
const deliveredCountBeforeReply = progress.deliveredCount;
const replyToId =
params.replyToMode === "off" ? undefined : resolveTelegramReplyId(reply.replyToId);
const mediaList = reply.mediaUrls?.length
@@ -537,11 +541,12 @@ export async function deliverReplies(params: {
}
if (hasMessageSentHooks) {
const deliveredThisReply = progress.deliveredCount > deliveredCountBeforeReply;
void hookRunner?.runMessageSent(
{
to: params.chatId,
content: contentForSentHook,
success: true,
success: deliveredThisReply,
},
{
channelId: "telegram",

View File

@@ -4,6 +4,11 @@ import type { RuntimeEnv } from "../../runtime.js";
import { deliverReplies } from "./delivery.js";
const loadWebMedia = vi.fn();
const messageHookRunner = vi.hoisted(() => ({
hasHooks: vi.fn<(name: string) => boolean>(() => false),
runMessageSending: vi.fn(),
runMessageSent: vi.fn(),
}));
const baseDeliveryParams = {
chatId: "123",
token: "tok",
@@ -22,6 +27,10 @@ vi.mock("../../web/media.js", () => ({
loadWebMedia: (...args: unknown[]) => loadWebMedia(...args),
}));
vi.mock("../../plugins/hook-runner-global.js", () => ({
getGlobalHookRunner: () => messageHookRunner,
}));
vi.mock("grammy", () => ({
InputFile: class {
constructor(
@@ -99,6 +108,10 @@ function createVoiceFailureHarness(params: {
describe("deliverReplies", () => {
beforeEach(() => {
loadWebMedia.mockClear();
messageHookRunner.hasHooks.mockReset();
messageHookRunner.hasHooks.mockReturnValue(false);
messageHookRunner.runMessageSending.mockReset();
messageHookRunner.runMessageSent.mockReset();
});
it("skips audioAsVoice-only payloads without logging an error", async () => {
@@ -113,6 +126,29 @@ describe("deliverReplies", () => {
expect(runtime.error).not.toHaveBeenCalled();
});
it("reports message_sent success=false when hooks blank out a text-only reply", async () => {
messageHookRunner.hasHooks.mockImplementation(
(name: string) => name === "message_sending" || name === "message_sent",
);
messageHookRunner.runMessageSending.mockResolvedValue({ content: "" });
const runtime = createRuntime(false);
const sendMessage = vi.fn();
const bot = createBot({ sendMessage });
await deliverWith({
replies: [{ text: "hello" }],
runtime,
bot,
});
expect(sendMessage).not.toHaveBeenCalled();
expect(messageHookRunner.runMessageSent).toHaveBeenCalledWith(
expect.objectContaining({ success: false, content: "" }),
expect.objectContaining({ channelId: "telegram", conversationId: "123" }),
);
});
it("invokes onVoiceRecording before sending a voice note", async () => {
const events: string[] = [];
const runtime = createRuntime(false);