fix(replies): normalize media path variants for dedupe
Co-authored-by: Ho Lim <subhoya@gmail.com>
This commit is contained in:
@@ -96,6 +96,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Telegram/Forward bursts: coalesce forwarded text+media updates through a dedicated forward lane debounce window that works with default inbound debounce config, while keeping forwarded control commands immediate. (#19476) thanks @napetrov.
|
||||
- Telegram/Streaming: preserve archived draft preview mapping after flush and clean superseded reasoning preview bubbles so multi-message preview finals no longer cross-edit or orphan stale messages under send/rotation races. (#23202) Thanks @obviyus.
|
||||
- Telegram/Replies: scope messaging-tool text/media dedupe to same-target sends only, so cross-target tool sends can no longer silently suppress Telegram final replies.
|
||||
- Telegram/Replies: normalize `file://` and local-path media variants during messaging dedupe so equivalent media paths do not produce duplicate Telegram replies.
|
||||
- Telegram/Replies: extract forwarded-origin context from unified reply targets (`reply_to_message` and `external_reply`) so forward+comment metadata is preserved across partial reply shapes. (#9720) thanks @mcaxtr.
|
||||
- Telegram/Polling: persist a safe update-offset watermark bounded by pending updates so crash/restart cannot skip queued lower `update_id` updates after out-of-order completion. (#23284) thanks @frankekn.
|
||||
- Telegram/Polling: force-restart stuck runner instances when recoverable unhandled network rejections escape the polling task path, so polling resumes instead of silently stalling. (#19721) Thanks @jg-noncelogic.
|
||||
|
||||
@@ -58,4 +58,20 @@ describe("filterMessagingToolMediaDuplicates", () => {
|
||||
});
|
||||
expect(result).toBe(payloads);
|
||||
});
|
||||
|
||||
it("dedupes equivalent file and local path variants", () => {
|
||||
const result = filterMessagingToolMediaDuplicates({
|
||||
payloads: [{ text: "hello", mediaUrl: "/tmp/photo.jpg" }],
|
||||
sentMediaUrls: ["file:///tmp/photo.jpg"],
|
||||
});
|
||||
expect(result).toEqual([{ text: "hello", mediaUrl: undefined, mediaUrls: undefined }]);
|
||||
});
|
||||
|
||||
it("dedupes encoded file:// paths against local paths", () => {
|
||||
const result = filterMessagingToolMediaDuplicates({
|
||||
payloads: [{ text: "hello", mediaUrl: "/tmp/photo one.jpg" }],
|
||||
sentMediaUrls: ["file:///tmp/photo%20one.jpg"],
|
||||
});
|
||||
expect(result).toEqual([{ text: "hello", mediaUrl: undefined, mediaUrls: undefined }]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -100,16 +100,35 @@ export function filterMessagingToolMediaDuplicates(params: {
|
||||
payloads: ReplyPayload[];
|
||||
sentMediaUrls: string[];
|
||||
}): ReplyPayload[] {
|
||||
const normalizeMediaForDedupe = (value: string): string => {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) {
|
||||
return "";
|
||||
}
|
||||
if (!trimmed.toLowerCase().startsWith("file://")) {
|
||||
return trimmed;
|
||||
}
|
||||
try {
|
||||
const parsed = new URL(trimmed);
|
||||
if (parsed.protocol === "file:") {
|
||||
return decodeURIComponent(parsed.pathname || "");
|
||||
}
|
||||
} catch {
|
||||
// Keep fallback below for non-URL-like inputs.
|
||||
}
|
||||
return trimmed.replace(/^file:\/\//i, "");
|
||||
};
|
||||
|
||||
const { payloads, sentMediaUrls } = params;
|
||||
if (sentMediaUrls.length === 0) {
|
||||
return payloads;
|
||||
}
|
||||
const sentSet = new Set(sentMediaUrls);
|
||||
const sentSet = new Set(sentMediaUrls.map(normalizeMediaForDedupe).filter(Boolean));
|
||||
return payloads.map((payload) => {
|
||||
const mediaUrl = payload.mediaUrl;
|
||||
const mediaUrls = payload.mediaUrls;
|
||||
const stripSingle = mediaUrl && sentSet.has(mediaUrl);
|
||||
const filteredUrls = mediaUrls?.filter((u) => !sentSet.has(u));
|
||||
const stripSingle = mediaUrl && sentSet.has(normalizeMediaForDedupe(mediaUrl));
|
||||
const filteredUrls = mediaUrls?.filter((u) => !sentSet.has(normalizeMediaForDedupe(u)));
|
||||
if (!stripSingle && (!mediaUrls || filteredUrls?.length === mediaUrls.length)) {
|
||||
return payload; // No change
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user