staff-tags: fix UserRow/HeaderName lookups, bump to 1.0.5
Two of the three previously-broken module lookups now have well-evidenced
fixes (from live sweeps run during an earlier debugging session that were
never actually applied to the code):
- details.tsx: UserRow is React.memo()-wrapped, so a plain withName('UserRow')
can never match it (memo wrapper objects have no .name). Added
withMemoDefaultName (lib/filters.ts), a custom filter checking the wrapper's
inner .type.name instead, matching on the full module namespace so the
callback gets the wrapper itself - the exact reference afterJSX needs.
- name.tsx: HeaderName doesn't exist under that name anymore; ChannelHeader
is the confirmed replacement (a plain, non-memo function, found via a
name-sweep).
DisplayName (the message-author-name row) remains unresolved - never found
under any name across two live sweep sessions, likely inlined into a bigger
component now. Left as a documented, still-guarded no-op rather than guessed
at.
This commit is contained in:
parent
e999719b21
commit
0439a6a67b
4 changed files with 61 additions and 12 deletions
28
plugins/staff-tags/js/lib/filters.ts
Normal file
28
plugins/staff-tags/js/lib/filters.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import {
|
||||
createFilterGenerator,
|
||||
FilterFlag,
|
||||
FilterScopes,
|
||||
} from '@revenge-mod/modules/finders/filters'
|
||||
|
||||
/**
|
||||
* `withName` checks `exports.name`/`exports.default.name` - a plain function/class's own
|
||||
* runtime name. A `React.memo()`-wrapped component is a `{$$typeof, type, compare}` object with
|
||||
* no `.name` of its own (the real name lives on the hidden inner `.type`), so `withName` can
|
||||
* structurally never match one - confirmed live for `UserRow` via a memo-patching sweep
|
||||
* (`next-eval-checks`' `memoSweep`), and confirmed from `withName`'s own source
|
||||
* (`@revenge-mod/modules/finders/filters`): it only ever checks `exports.name` and
|
||||
* `exports.default.name`, never `exports.default.type.name`.
|
||||
*
|
||||
* This filter checks the memo wrapper's inner name instead, and matches on the full module
|
||||
* namespace (so the callback receives `{default: <memo wrapper>, ...}`, not the unwrapped
|
||||
* default) - the wrapper is what `afterJSX` needs, since that's the exact reference Discord's own
|
||||
* `createElement(UserRow, ...)` calls use as `type`, not the inner render function memo hides.
|
||||
*/
|
||||
export const withMemoDefaultName = createFilterGenerator<[name: string]>(
|
||||
([name], _id, exports) =>
|
||||
(exports as { default?: { type?: { name?: string } } })?.default?.type
|
||||
?.name === name,
|
||||
([name]) => `revenge-next-plugs.staff-tags.memoDefaultName(${name})`,
|
||||
FilterFlag.RequiresExports,
|
||||
FilterScopes.Initialized,
|
||||
)
|
||||
|
|
@ -1,10 +1,11 @@
|
|||
import { Stores } from '@revenge-mod/discord/flux'
|
||||
import { getModules } from '@revenge-mod/modules/finders'
|
||||
import { withName, withProps } from '@revenge-mod/modules/finders/filters'
|
||||
import { withProps } from '@revenge-mod/modules/finders/filters'
|
||||
import { afterJSX } from '@revenge-mod/react/jsx-runtime'
|
||||
import { findInReactFiber } from '@revenge-mod/utils/react'
|
||||
import { afterRendered } from '../lib/afterRendered'
|
||||
import { fiberFilter } from '../lib/fiber'
|
||||
import { withMemoDefaultName } from '../lib/filters'
|
||||
import getTag from '../lib/getTag'
|
||||
import { guard } from '../lib/safe'
|
||||
import GradientTag from '../ui/GradientTag'
|
||||
|
|
@ -32,6 +33,13 @@ interface TagComponentProps {
|
|||
* profile popout, etc.), so this subscribes rather than doing a single lookup - `max` is a guess
|
||||
* and may need adjusting once verified live, see eval-for-revenge.
|
||||
*
|
||||
* `UserRow` is real but `React.memo()`-wrapped (confirmed live via a memo-patching sweep) - a
|
||||
* plain `withName('UserRow')` can never match it, since the wrapper object has no `.name` of its
|
||||
* own (only the memo-hidden inner function does). `withMemoDefaultName` (`../lib/filters`) checks
|
||||
* the wrapper's inner `.type.name` instead and matches on the full module namespace, so the
|
||||
* callback below receives `{default: <memo wrapper>}` - `mod.default` is the exact reference
|
||||
* Discord's own `createElement(UserRow, ...)` calls use, which is what `afterJSX` needs.
|
||||
*
|
||||
* Uses `getModules` (reacts once Discord itself initializes each module) instead of
|
||||
* `lookupModule` (which force-initializes uninitialized modules right away) - forcing UserRow /
|
||||
* the Tag module to init during `start()`, before Discord is done booting, is what crashed app
|
||||
|
|
@ -42,8 +50,7 @@ interface TagComponentProps {
|
|||
* synchronous setup calls - an uncaught throw here crashes app startup instead of just skipping
|
||||
* one row.
|
||||
*
|
||||
* TODO(live-verify): UserRow component name/count, rendered-tree shape (`c?.type?.Types`), see
|
||||
* eval-for-revenge.
|
||||
* TODO(live-verify): rendered-tree shape (`c?.type?.Types`) still unconfirmed, see eval-for-revenge.
|
||||
*/
|
||||
export default function patchDetails(storage: JsonStorage<StaffTagsStorage>) {
|
||||
const patches: (() => void)[] = []
|
||||
|
|
@ -63,9 +70,10 @@ export default function patchDetails(storage: JsonStorage<StaffTagsStorage>) {
|
|||
}
|
||||
|
||||
const unsubUserRow = getModules(
|
||||
withName<FC<UserRowProps>>('UserRow'),
|
||||
UserRow => {
|
||||
withMemoDefaultName('UserRow'),
|
||||
mod => {
|
||||
guard(() => {
|
||||
const UserRow = (mod as { default: FC<UserRowProps> }).default
|
||||
patches.push(
|
||||
afterJSX(UserRow, el =>
|
||||
guard(() => {
|
||||
|
|
|
|||
|
|
@ -35,9 +35,21 @@ interface TagComponentProps {
|
|||
* Covers the name row shown in chat messages (next to the display name + timestamp) and channel
|
||||
* headers.
|
||||
*
|
||||
* `HeaderName` doesn't exist under that name in the current build (confirmed live - Discord
|
||||
* renamed/refactored it at some point). `ChannelHeader` is the confirmed replacement: found via a
|
||||
* broad name-sweep as a plain, non-memo-wrapped function, high confidence given its name and
|
||||
* shape - a plain `withName` lookup can find it directly, unlike `UserRow` in `details.tsx` which
|
||||
* needed a memo-aware filter.
|
||||
*
|
||||
* `DisplayName` (the message-author-name row) is still unresolved - not found under any name even
|
||||
* via a memo/forwardRef sweep and a text-content correlation sweep across two separate live
|
||||
* sessions. Most likely inlined into a bigger message-row component now rather than factored out
|
||||
* the way Classic had it. Left as a best-effort lookup below (guarded, silently no-ops) rather
|
||||
* than guessed at - fixing this needs a fresh live investigation, not a guess.
|
||||
*
|
||||
* Uses `getModules` (reacts once Discord itself initializes each module) instead of
|
||||
* `lookupModule` (which force-initializes uninitialized modules right away) - forcing
|
||||
* DisplayName/HeaderName/the Tag module to init during `start()`, before Discord is done
|
||||
* DisplayName/ChannelHeader/the Tag module to init during `start()`, before Discord is done
|
||||
* booting, is what crashed app startup the first time this shipped.
|
||||
*
|
||||
* Every callback below is wrapped in `guard()`: it runs whenever Discord itself renders/
|
||||
|
|
@ -45,8 +57,9 @@ interface TagComponentProps {
|
|||
* synchronous setup calls - an uncaught throw here crashes app startup instead of just skipping
|
||||
* a name row.
|
||||
*
|
||||
* TODO(live-verify): DisplayName/HeaderName component names and rendered-tree shapes
|
||||
* (`c?.type?.Types`, the flexDirection: "row" container), see eval-for-revenge.
|
||||
* TODO(live-verify): ChannelHeader's rendered-tree shape (`c?.type?.Types`, the
|
||||
* flexDirection: "row" container) still unconfirmed, see eval-for-revenge. DisplayName remains
|
||||
* fully unresolved.
|
||||
*/
|
||||
export default function patchName(storage: JsonStorage<StaffTagsStorage>) {
|
||||
const cleanups: (() => void)[] = []
|
||||
|
|
@ -69,11 +82,11 @@ export default function patchName(storage: JsonStorage<StaffTagsStorage>) {
|
|||
}
|
||||
|
||||
const unsubHeaderName = getModules(
|
||||
withName<FC<HeaderNameProps>>('HeaderName'),
|
||||
HeaderName => {
|
||||
withName<FC<HeaderNameProps>>('ChannelHeader'),
|
||||
ChannelHeader => {
|
||||
guard(() => {
|
||||
cleanups.push(
|
||||
afterJSX(HeaderName, el =>
|
||||
afterJSX(ChannelHeader, el =>
|
||||
guard(() => {
|
||||
const { channelId } = el.props
|
||||
const unpatch = afterRendered(el, ret =>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
"name": "Staff Tags",
|
||||
"description": "Adds customizable tags (OWNER, ADMIN, STAFF, MOD, and more) next to members based on their server permissions. Supports per-tag colors, gradients, custom text, and visibility toggles.",
|
||||
"author": "everestmcarthur",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.5",
|
||||
"dependencies": {
|
||||
"revenge.api": {
|
||||
"version": ">=1 <2"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue