


























@@ -35,43 +35,83 @@ export function filterMessagingToolMediaDuplicates(params: {
3535payloads: ReplyPayload[];
3636sentMediaUrls: string[];
3737}): ReplyPayload[] {
38-const normalizeMediaForDedupe = (value: string): string => {
39-const trimmed = value.trim();
40-if (!trimmed) {
41-return "";
42-}
43-if (!normalizeLowercaseStringOrEmpty(trimmed).startsWith("file://")) {
44-return trimmed;
45-}
46-try {
47-const parsed = new URL(trimmed);
48-if (parsed.protocol === "file:") {
49-return decodeURIComponent(parsed.pathname || "");
50-}
51-} catch {
52-// Keep fallback below for non-URL-like inputs.
53-}
54-return trimmed.replace(/^file:\/\//i, "");
55-};
56-5738const { payloads, sentMediaUrls } = params;
5839if (sentMediaUrls.length === 0) {
5940return payloads;
6041}
61-const sentSet = new Set(sentMediaUrls.map(normalizeMediaForDedupe).filter(Boolean));
62-return payloads.map((payload) => {
42+const sentSet = new Set<string>();
43+for (const sentMediaUrl of sentMediaUrls) {
44+const normalized = normalizeMediaForDedupe(sentMediaUrl);
45+if (normalized) {
46+sentSet.add(normalized);
47+}
48+}
49+if (sentSet.size === 0) {
50+return payloads;
51+}
52+53+let nextPayloads: ReplyPayload[] | undefined;
54+for (let index = 0; index < payloads.length; index++) {
55+const payload = payloads[index]!;
6356const mediaUrl = payload.mediaUrl;
6457const mediaUrls = payload.mediaUrls;
6558const stripSingle = mediaUrl && sentSet.has(normalizeMediaForDedupe(mediaUrl));
66-const filteredUrls = mediaUrls?.filter((u) => !sentSet.has(normalizeMediaForDedupe(u)));
67-if (!stripSingle && (!mediaUrls || filteredUrls?.length === mediaUrls.length)) {
68-return payload;
59+60+let filteredUrls: string[] | undefined;
61+let strippedMediaUrls = false;
62+if (mediaUrls?.length) {
63+for (let mediaIndex = 0; mediaIndex < mediaUrls.length; mediaIndex++) {
64+const url = mediaUrls[mediaIndex]!;
65+if (sentSet.has(normalizeMediaForDedupe(url))) {
66+strippedMediaUrls = true;
67+if (!filteredUrls) {
68+filteredUrls = mediaUrls.slice(0, mediaIndex);
69+}
70+continue;
71+}
72+if (filteredUrls) {
73+filteredUrls.push(url);
74+}
75+}
76+}
77+78+if (!stripSingle && !strippedMediaUrls) {
79+if (nextPayloads) {
80+nextPayloads.push(payload);
81+}
82+continue;
6983}
70-return Object.assign({}, payload, {
84+85+const nextPayload = Object.assign({}, payload, {
7186mediaUrl: stripSingle ? undefined : mediaUrl,
7287mediaUrls: filteredUrls?.length ? filteredUrls : undefined,
7388});
74-});
89+if (!nextPayloads) {
90+nextPayloads = payloads.slice(0, index);
91+}
92+nextPayloads.push(nextPayload);
93+}
94+95+return nextPayloads ?? payloads;
96+}
97+98+function normalizeMediaForDedupe(value: string): string {
99+const trimmed = value.trim();
100+if (!trimmed) {
101+return "";
102+}
103+if (!normalizeLowercaseStringOrEmpty(trimmed).startsWith("file://")) {
104+return trimmed;
105+}
106+try {
107+const parsed = new URL(trimmed);
108+if (parsed.protocol === "file:") {
109+return decodeURIComponent(parsed.pathname || "");
110+}
111+} catch {
112+// Keep fallback below for non-URL-like inputs.
113+}
114+return trimmed.replace(/^file:\/\//i, "");
75115}
7611677117function normalizeProviderForComparison(value?: string): string | undefined {
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。