revenge-bundle-next/lib/utils/src/discord.ts
2026-08-01 14:49:20 +07:00

162 lines
4.7 KiB
TypeScript

import { getAssetByName } from '@revenge-mod/assets'
import { ImportTrackerModuleId } from '@revenge-mod/discord/common/import-tracker'
import { TokensModuleId } from '@revenge-mod/discord/common/tokens'
import { lookupModule } from '@revenge-mod/modules/finders'
import {
createFilterGenerator,
withDependencies,
} from '@revenge-mod/modules/finders/filters'
import {
ReactJSXRuntimeModuleId,
ReactModuleId,
ReactNativeModuleId,
} from '@revenge-mod/react'
import {
FilterFlag,
FilterScopes,
} from '../../modules/src/finders/filters/constants'
import type {
Filter,
FilterGenerator,
} from '@revenge-mod/modules/finders/filters'
import type { FC } from 'react'
// [React, ReactJsxRuntime, (Tokens), (BaseIconImage), (Asset), ImportTracker]
// Asset = relativeDep(1)
const IconComponentFilter = [
ReactModuleId,
ReactJSXRuntimeModuleId,
TokensModuleId,
null,
null,
ImportTrackerModuleId,
]
// Generated IconComponents that have multiple assets, there is no specific length, so the filter has to be generated on-the-fly.
// [React, ReactNative, ReactJsxRuntime, (Tokens), (BaseIconImage), (...Assets), ImportTracker]
// ...Assets = relativeDep(1..n)
const MultiIconComponentFilterBase = [
ReactModuleId,
ReactNativeModuleId,
ReactJSXRuntimeModuleId,
TokensModuleId,
null,
]
export type WithGeneratedIconComponent = FilterGenerator<
<N extends string>(
name: N,
...assets: string[]
) => Filter<{
Result: { [K in N]: FC<any> }
RequiresExports: boolean
Scopes: [
typeof FilterScopes.Uninitialized,
typeof FilterScopes.Initialized,
]
}>
>
/**
* Filter by icon component name and asset names.
*
* @param names The component name, then the asset names if the component has multiple assets. *
* @example
* ```ts
* const [CopyIconModule] = lookupModule(
* withGeneratedIconComponent('CopyIcon'),
* )
* if (CopyIconModule) {
* const { CopyIcon } = CopyIconModule
* // Use CopyIcon as a React component
* }
* ```
* @example
* ```ts
* const [CircleXIconModule] = lookupModule(
* withGeneratedIconComponent(
* 'CircleXIcon',
* 'CircleXIcon-secondary',
* 'CircleXIcon-primary',
* ),
* )
* ```
*/
export const withGeneratedIconComponent = createFilterGenerator<
Parameters<WithGeneratedIconComponent>
>(
(names, id, exports) => {
if (typeof exports === 'object') {
if (typeof exports[names[0]] === 'function')
return Object.keys(exports).length === 1
} else {
let filter = IconComponentFilter
if (names.length > 1) {
const mids = []
for (let i = 1; i < names.length; i++) {
const name = names[i]
const mid = getAssetByName(name)?.moduleId
// Module ID can never be zero, so if it's falsy, it means the asset was not found.
if (!mid) return false
mids.push(mid)
}
filter = [
...MultiIconComponentFilterBase,
...mids,
ImportTrackerModuleId,
]
} else {
const [name] = names
const mid = getAssetByName(name)?.moduleId
// Module ID can never be zero, so if it's falsy, it means the asset was not found.
if (!mid) return false
IconComponentFilter[4] = mid
}
return withDependencies(filter)(id, exports)
}
return false
},
names => `revenge.utils.discord.generatedIconComponent(${names.join(',')})`,
FilterFlag.Dynamic,
FilterScopes.Uninitialized | FilterScopes.Initialized,
) as WithGeneratedIconComponent
/**
* Looks up a generated icon component by its name and asset names.
*
* @param names The component name, then the asset names if the component has multiple assets.
* @returns The icon component, or `undefined` if it could not be found.
*/
export function lookupGeneratedIconComponent<N extends string>(
...names: [N, ...string[]]
) {
for (const name of names) {
let badFind = false
if (__DEV__) {
if (!getAssetByName(name)) {
badFind = true
warnUnregisteredAsset(name)
}
} else if (!getAssetByName(name)) return
if (__DEV__ && badFind) return
}
const [module] = lookupModule(withGeneratedIconComponent(...names))
return module?.[names[0]] as FC<any> | undefined
}
function warnUnregisteredAsset(name: string) {
nativeLoggingHook(
`\u001b[31mAsset "${name}" is not registered. Cannot get module ID to filter by.\u001b[0m`,
2,
)
}