revenge-bundle-next/lib/utils/src/discord.ts
PalmDevs b6d1cff9f5
feat(lib/modules)!: filter scopes + fixes and improvements
# Features

- Filters now control what types of modules they see.
- Filters can be reinstantiated via `.new()`
- Added the `Not` type in @revenge-mod/utils/types

# Fixes

- Pitfall with `lookupModules` and `withDependencies` without `uninitialize: true` fixed.
- Filter type-metadata is now stored in a single object instead of multiple parameters.
- Incorrect caching behavior for `lookupModules` where cache always reports not found if you finish the iterator.
- Also attempt to report not found when using `lookupModule`.
2025-10-28 01:01:17 +07:00

157 lines
4.5 KiB
TypeScript

import { getAssetByName } from '@revenge-mod/assets'
import { TokensModuleId } from '@revenge-mod/discord/common'
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), 2]
// Asset = relativeDep(1)
const IconComponentFilter = [
ReactModuleId,
ReactJSXRuntimeModuleId,
TokensModuleId,
null,
null,
2,
]
// 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), 2]
// ...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, 2]
} 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,
)
}