mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(telegram): keep rejected voice attachments out of reply history (#130399)
This commit is contained in:
committed by
GitHub
parent
771336481e
commit
549360bc42
@@ -296,12 +296,15 @@ async function deliverMediaReply(params: {
|
||||
recordMessageId: (messageId: number) => void;
|
||||
textMode?: "html";
|
||||
onPlatformSendDispatch?: () => Promise<void>;
|
||||
}): Promise<{ firstDeliveredMessageId?: number; visibleFallbackText?: string }> {
|
||||
}): Promise<{
|
||||
firstDeliveredMessageId?: number;
|
||||
visibleFallbackText?: string;
|
||||
mediaUrls: string[];
|
||||
}> {
|
||||
let firstDeliveredMessageId: number | undefined;
|
||||
let visibleFallbackText: string | undefined;
|
||||
let firstDeliveredCaption: string | undefined;
|
||||
let first = true;
|
||||
let pendingFollowUpText: string | undefined;
|
||||
const mediaUrls: string[] = [];
|
||||
const recordPromptContextMessage = async (message: Message, text?: string) => {
|
||||
const promptContextMessage = {
|
||||
messageId: message.message_id,
|
||||
@@ -312,6 +315,7 @@ async function deliverMediaReply(params: {
|
||||
};
|
||||
const deliverAcceptedMedia = async (options: {
|
||||
sender: TelegramOutboundMediaSender<Message>;
|
||||
mediaUrl: string;
|
||||
requestParams: Record<string, unknown>;
|
||||
plainCaption?: string;
|
||||
shouldLog?: (err: unknown) => boolean;
|
||||
@@ -332,6 +336,7 @@ async function deliverMediaReply(params: {
|
||||
}),
|
||||
});
|
||||
const message = delivery.result;
|
||||
mediaUrls.push(options.mediaUrl);
|
||||
// Acceptance precedes topic checks and bookkeeping; losing this id lets
|
||||
// later failures trigger a duplicate reply for an already visible message.
|
||||
params.acceptedMessageIds.push(String(message.message_id));
|
||||
@@ -358,8 +363,8 @@ async function deliverMediaReply(params: {
|
||||
deliveredCount: 0,
|
||||
...(params.progress.promptContext ? { promptContext: params.progress.promptContext } : {}),
|
||||
});
|
||||
for (const mediaUrl of params.mediaList) {
|
||||
const isFirstMedia = first;
|
||||
for (const [index, mediaUrl] of params.mediaList.entries()) {
|
||||
const isFirstMedia = index === 0;
|
||||
const media = await params.mediaLoader(
|
||||
mediaUrl,
|
||||
buildOutboundMediaLoadOptions({
|
||||
@@ -382,10 +387,6 @@ async function deliverMediaReply(params: {
|
||||
asVoice: params.reply.audioAsVoice,
|
||||
});
|
||||
const { htmlCaption, plainCaption, followUpText } = mediaPlan;
|
||||
if (followUpText) {
|
||||
pendingFollowUpText = followUpText;
|
||||
}
|
||||
first = false;
|
||||
const replyToMessageId = resolveReplyToForSend({
|
||||
replyToId: params.replyToId,
|
||||
replyToMode: params.replyToMode,
|
||||
@@ -417,6 +418,7 @@ async function deliverMediaReply(params: {
|
||||
const hasCaption = typeof requestParams.caption === "string";
|
||||
await deliverAcceptedMedia({
|
||||
sender: mediaSender,
|
||||
mediaUrl,
|
||||
requestParams,
|
||||
plainCaption: hasCaption ? plainCaption : undefined,
|
||||
shouldLog,
|
||||
@@ -521,6 +523,7 @@ async function deliverMediaReply(params: {
|
||||
send: (sender) =>
|
||||
deliverAcceptedMedia({
|
||||
sender,
|
||||
mediaUrl,
|
||||
requestParams: mediaParams,
|
||||
plainCaption,
|
||||
...(sender.label === "photo"
|
||||
@@ -530,7 +533,7 @@ async function deliverMediaReply(params: {
|
||||
});
|
||||
}
|
||||
markReplyApplied(params.progress, replyToMessageId);
|
||||
if (pendingFollowUpText && isFirstMedia) {
|
||||
if (followUpText) {
|
||||
try {
|
||||
const followUpMessageId = await deliverTextReply({
|
||||
bot: params.bot,
|
||||
@@ -538,7 +541,7 @@ async function deliverMediaReply(params: {
|
||||
runtime: params.runtime,
|
||||
thread: params.thread,
|
||||
chunkText: params.chunkText,
|
||||
text: pendingFollowUpText,
|
||||
text: followUpText,
|
||||
replyMarkup: params.replyMarkup,
|
||||
richMessages: params.richMessages,
|
||||
tableMode: params.tableMode,
|
||||
@@ -567,10 +570,9 @@ async function deliverMediaReply(params: {
|
||||
});
|
||||
}
|
||||
}
|
||||
pendingFollowUpText = undefined;
|
||||
}
|
||||
}
|
||||
return { firstDeliveredMessageId, visibleFallbackText };
|
||||
return { firstDeliveredMessageId, visibleFallbackText, mediaUrls };
|
||||
}
|
||||
|
||||
async function maybePinFirstDeliveredMessage(params: {
|
||||
@@ -881,6 +883,7 @@ export async function deliverReplies(params: {
|
||||
}),
|
||||
);
|
||||
let firstDeliveredMessageId: number | undefined;
|
||||
let deliveredMediaUrls: string[] = [];
|
||||
if (reactionEmoji && typeof targetId === "number") {
|
||||
await params.onPlatformSendDispatch?.();
|
||||
const reactionResult = await reactMessageTelegram(params.chatId, targetId, reactionEmoji, {
|
||||
@@ -953,6 +956,7 @@ export async function deliverReplies(params: {
|
||||
...(params.textMode ? { textMode: params.textMode } : {}),
|
||||
});
|
||||
firstDeliveredMessageId = mediaDelivery.firstDeliveredMessageId;
|
||||
deliveredMediaUrls = mediaDelivery.mediaUrls;
|
||||
if (mediaDelivery.visibleFallbackText !== undefined) {
|
||||
contentForSentHook = mediaDelivery.visibleFallbackText;
|
||||
}
|
||||
@@ -966,7 +970,7 @@ export async function deliverReplies(params: {
|
||||
});
|
||||
|
||||
if (progress.deliveredCount > deliveredCountBeforeReply && transcriptMirror) {
|
||||
deliveredContents.push({ text: contentForSentHook, mediaUrls: mediaList });
|
||||
deliveredContents.push({ text: contentForSentHook, mediaUrls: deliveredMediaUrls });
|
||||
}
|
||||
|
||||
emitMessageSentHooks({
|
||||
|
||||
@@ -2891,11 +2891,47 @@ describe("deliverReplies", () => {
|
||||
expect(sendVoice).toHaveBeenCalledTimes(1);
|
||||
expect(sendMessage).toHaveBeenCalledTimes(1);
|
||||
expect(firstMockCallArg(sendMessage, 1)).toContain("Hidden voice fallback");
|
||||
expect(transcriptMirror).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ text: "Hidden voice fallback" }),
|
||||
);
|
||||
expect(transcriptMirror).toHaveBeenCalledWith({
|
||||
text: "Hidden voice fallback",
|
||||
mediaUrls: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it.each(["before", "after"])(
|
||||
"mirrors accepted media %s a rejected voice without the rejected attachment",
|
||||
async (position) => {
|
||||
const voiceUrl = "https://example.com/note.ogg";
|
||||
const photoUrl = "https://example.com/photo.jpg";
|
||||
const mediaUrls = position === "before" ? [photoUrl, voiceUrl] : [voiceUrl, photoUrl];
|
||||
const sendVoice = vi.fn().mockRejectedValue(createVoiceMessagesForbiddenError());
|
||||
const sendPhoto = vi.fn().mockResolvedValue({ message_id: 7, chat: { id: "123" } });
|
||||
const sendMessage = vi.fn().mockResolvedValue({ message_id: 8, chat: { id: "123" } });
|
||||
const transcriptMirror = vi.fn();
|
||||
for (const url of mediaUrls) {
|
||||
mockMediaLoad(
|
||||
url === voiceUrl ? "note.ogg" : "photo.jpg",
|
||||
url === voiceUrl ? "audio/ogg" : "image/jpeg",
|
||||
"media",
|
||||
);
|
||||
}
|
||||
|
||||
await deliverWith({
|
||||
replies: [{ mediaUrls, audioAsVoice: true, spokenText: "Voice fallback" }],
|
||||
runtime: createRuntime(),
|
||||
bot: createBot({ sendVoice, sendPhoto, sendMessage }),
|
||||
transcriptMirror,
|
||||
});
|
||||
|
||||
expect(sendVoice).toHaveBeenCalledOnce();
|
||||
expect(sendPhoto).toHaveBeenCalledOnce();
|
||||
expect(sendMessage).toHaveBeenCalledOnce();
|
||||
expect(transcriptMirror).toHaveBeenCalledWith({
|
||||
text: "Voice fallback",
|
||||
mediaUrls: [photoUrl],
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("runs message_sending hooks over spokenText voice fallback content", async () => {
|
||||
messageHookRunner.hasHooks.mockImplementation((name: string) => name === "message_sending");
|
||||
messageHookRunner.runMessageSending.mockResolvedValue({ content: "Rewritten voice fallback" });
|
||||
|
||||
Reference in New Issue
Block a user