ServerDrawer: revert contentPatch.tsx drawer-swap to the confirmed-working version

This commit is contained in:
everestmcarthur 2026-08-02 09:43:05 -04:00
parent 58ddfc1f2d
commit 4568828688

View file

@ -1,10 +1,14 @@
import { React } from "@vendetta/metro/common";
import { rawFind } from "../lib/rawFind";
import ServerDrawerSheet from "../components/ServerDrawerSheet";
import { registerIntercept, registerTypeDetector } from "../lib/createElementIntercept";
import { registerIntercept } from "../lib/createElementIntercept";
const TAG = "[ServerDrawer]";
// rawFind, not @vendetta/metro's find - both patchExpanded and patchEmpty are retried by index.ts's
// fast-retry loop, and Revenge's own find() permanently caches a "not found" result per call site
// and never rescans (see rawFind.ts), which would make the retry pointless after the first
// failed attempt.
let cachedGestureContext: any = null;
function getGestureContext(): any {
if (!cachedGestureContext) {
@ -13,50 +17,47 @@ function getGestureContext(): any {
return cachedGestureContext;
}
// Same fix as hideGuildsBar.tsx: memo-wrapped components are passed to createElement/jsx as the
// outer wrapper object, which has no own .name/.displayName - only its nested .type does. Missing
// that nested check meant this never matched anything, which is why Discord's real
// QuestDockEnrolledHeader (and friends) kept running unpatched against the fake quest data.
function isNamed(name: string) {
return (type: any) => type?.name === name || type?.displayName === name ||
type?.type?.name === name || type?.type?.displayName === name;
}
export function patchExpanded(
cleanups: (() => void)[]
): boolean {
const mod = rawFind((m) => m?.type?.displayName === "QuestDockContentExpanded" || m?.type?.name === "QuestDockContentExpanded");
if (!mod?.type) {
console.log(TAG, "WARN: QuestDockContentExpanded not found (will retry)");
return false;
}
const orig = mod.type;
function Nothing() {
return null;
}
registerIntercept(orig, ServerDrawerSheet, { gestureContext: getGestureContext() });
/**
* Reset after live verification (Key Inspector's fiber capture + Eval, same methodology that found
* GuildsBar's bug) showed this file's original approach shares the exact same flaw: rawFind matching
* a component by `.type.displayName`/`.type.name` across window.modules can - and here, does - match
* a different, unrelated module than the one actually mounted. The visible symptom: Discord's real
* QuestDockEnrolledHeader (and friends) kept running unpatched, then crashed reading properties off
* the fake quest data useMobileQuestDock.ts hands out, because our replacement was silently mutating
* the wrong object the whole time.
*
* registerTypeDetector sidesteps this the same way it does for GuildsBar: it inspects the `type`
* argument passed directly to createElement/jsx at the moment something actually creates an element
* with it, which is necessarily the real, live reference - there's no module-registry ambiguity
* possible. registerIntercept then swaps that exact reference (matched by identity) for the
* replacement on every future creation.
*/
export function patchExpanded(cleanups: (() => void)[]): boolean {
registerTypeDetector(isNamed("QuestDockContentExpanded"), (real) => {
registerIntercept(real, ServerDrawerSheetWrapper);
console.log(TAG, "PATCH: found the real QuestDockContentExpanded, now rendering the drawer");
});
mod.type = function ServerDrawerPatch() {
return <ServerDrawerSheet gestureContext={getGestureContext()} />;
};
cleanups.push(() => { mod.type = orig; });
console.log(TAG, "PATCH: QuestDockContentExpanded replaced");
return true;
}
function ServerDrawerSheetWrapper() {
return <ServerDrawerSheet gestureContext={getGestureContext()} />;
}
export function patchEmpty(
name: string,
cleanups: (() => void)[]
): boolean {
const mod = rawFind((m) => m?.type?.displayName === name || m?.type?.name === name);
if (!mod?.type) {
console.log(TAG, `WARN: ${name} not found`);
return false;
}
const orig = mod.type;
const originalComponent = orig;
export function patchEmpty(name: string, cleanups: (() => void)[]): boolean {
registerTypeDetector(isNamed(name), (real) => {
registerIntercept(real, Nothing);
console.log(TAG, `PATCH: found the real ${name}, now rendering nothing`);
});
mod.type = function EmptyPatch() {
return null;
};
if (originalComponent) {
Object.defineProperties(mod.type, Object.getOwnPropertyDescriptors(originalComponent));
mod.type.displayName = name;
}
cleanups.push(() => { mod.type = orig; });
return true;
}