diff --git a/plugins/message-logger/src/index.ts b/plugins/message-logger/src/index.ts index 5fddfde..6ccfc8e 100644 --- a/plugins/message-logger/src/index.ts +++ b/plugins/message-logger/src/index.ts @@ -3,7 +3,6 @@ import { storage } from "@vendetta/plugin"; import Settings from "./ui/Settings"; import { patchFluxIntercept, revertFakedMessages } from "./patches/fluxIntercept"; import { patchRowStyling } from "./patches/rowStyling"; -import { patchMessageRecord } from "./patches/messageRecord"; const TAG = "[MessageLogger]"; const cleanups: (() => void)[] = []; @@ -35,9 +34,8 @@ export default { let patched = 0; if (patchFluxIntercept(cleanups)) patched++; if (patchRowStyling(cleanups)) patched++; - if (patchMessageRecord(cleanups)) patched++; - console.log(TAG, `onLoad done - ${patched}/3 patches applied`); + console.log(TAG, `onLoad done - ${patched}/2 patches applied`); }, onUnload() { console.log(TAG, "onUnload"); diff --git a/plugins/message-logger/src/patches/fluxIntercept.ts b/plugins/message-logger/src/patches/fluxIntercept.ts index 72dfb11..9ba4133 100644 --- a/plugins/message-logger/src/patches/fluxIntercept.ts +++ b/plugins/message-logger/src/patches/fluxIntercept.ts @@ -8,7 +8,6 @@ import { addLogEntry } from "../lib/store"; import type { FilterOptions } from "../lib/types"; const TAG = "[MessageLogger]"; -const FAKE_DELETE_FLAG = "__msgLoggerDeleted"; const CLEANUP_FLAG = "__msgLoggerCleanup"; // A busy, high-traffic channel (flooding, spam, price-ticker bots editing the same message @@ -93,8 +92,18 @@ function hasLoggableContent(message: any): boolean { ); } -function fakeUpdateFor(original: any): any { - return { type: "MESSAGE_UPDATE", message: { ...original, [FAKE_DELETE_FLAG]: true } }; +// Any action type other than MESSAGE_DELETE stops the real removal - live-verified that +// MESSAGE_EDIT_FAILED_AUTOMOD (the same technique meqativ/dumsane's NoDelete uses) is a genuine +// no-op for a message not already in a pending-edit state: dispatching it left the cached +// MessageRecord byte-for-byte identical. No need to reconstruct/spread the message into a fake +// MESSAGE_UPDATE - that also avoids waking every other MESSAGE_UPDATE listener in the app (unread +// counts, embed refresh, etc.) for something that isn't really an edit. +function suppressionEventFor(original: any): any { + return { + type: "MESSAGE_EDIT_FAILED_AUTOMOD", + messageData: { type: 0, message: { channelId: original.channel_id, messageId: original.id, content: original.content } }, + errorResponseBody: { code: 200000, message: "Deleted message kept inline by MessageLogger" }, + }; } function captureAndMaybeKeep(original: any, channelId: string, id: string): any | undefined { @@ -112,7 +121,7 @@ function captureAndMaybeKeep(original: any, channelId: string, id: string): any fakedMessages.set(id, channelId); evictOldest(fakedMessages, MAX_TRACKED_MESSAGES); - return fakeUpdateFor(original); + return suppressionEventFor(original); } function handleSingleDelete(event: any): any { @@ -148,7 +157,7 @@ function handleBulkDelete(event: any): any { const original = ChannelMessages?.get?.(channelId)?.get?.(id); if (!original) continue; try { - FluxDispatcher.dispatch(fakeUpdateFor(original)); + FluxDispatcher.dispatch(suppressionEventFor(original)); } catch (e: any) { console.error(TAG, "Deferred bulk-keep dispatch failed for", id, e?.message ?? e); } @@ -166,7 +175,6 @@ function handleBulkDelete(event: any): any { function handleUpdate(event: any): void { const updated = event?.message; if (!updated?.id) return; - if (updated[FAKE_DELETE_FLAG]) return; // our own fake-delete-as-update, not a real edit const channelId = updated.channel_id ?? updated.channelId; const original = ChannelMessages?.get?.(channelId)?.get?.(updated.id); diff --git a/plugins/message-logger/src/patches/messageRecord.ts b/plugins/message-logger/src/patches/messageRecord.ts deleted file mode 100644 index 4c73bb6..0000000 --- a/plugins/message-logger/src/patches/messageRecord.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { findByName, findByProps } from "@vendetta/metro"; -import { after, instead } from "@vendetta/patcher"; - -const TAG = "[MessageLogger]"; -const FAKE_DELETE_FLAG = "__msgLoggerDeleted"; - -// Discord wraps raw message JSON into a MessageRecord class before it reaches row generation - a -// plain custom field on the raw object doesn't reliably survive that wrap without this. -export function patchMessageRecord(cleanups: (() => void)[]): boolean { - const MessageRecordUtils = findByProps("updateMessageRecord", "createMessageRecord"); - const MessageRecord = findByName("MessageRecord", false); - - if (!MessageRecordUtils || !MessageRecord) { - console.warn(TAG, "MessageRecord module(s) not found - deleted messages may not render styled"); - return false; - } - - try { - cleanups.push( - after("createMessageRecord", MessageRecordUtils, ([message]: any[], record: any) => { - if (message?.[FAKE_DELETE_FLAG]) record[FAKE_DELETE_FLAG] = true; - }), - ); - - cleanups.push( - instead("updateMessageRecord", MessageRecordUtils, ([oldRecord, newRecord]: any[], orig: any) => { - if (newRecord?.[FAKE_DELETE_FLAG]) { - return MessageRecordUtils.createMessageRecord(newRecord, oldRecord?.reactions); - } - return orig.apply(MessageRecordUtils, [oldRecord, newRecord]); - }), - ); - - cleanups.push( - after("default", MessageRecord, ([props]: any[], record: any) => { - if (props?.[FAKE_DELETE_FLAG]) record[FAKE_DELETE_FLAG] = true; - }), - ); - } catch (e: any) { - console.warn(TAG, "Failed to patch MessageRecord:", e?.message ?? e); - return false; - } - - return true; -}