feat(lib)!: refactor discord, add export utils/discord

This commit is contained in:
PalmDevs 2025-05-09 21:52:36 +07:00
parent bea3439c5e
commit ca7ef6ea1a
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
20 changed files with 202 additions and 245 deletions

View file

@ -16,13 +16,7 @@
"default": "./src/common/flux.ts"
},
"./design": {
"default": "./src/design/index.ts"
},
"./design/components": {
"default": "./src/design/components/index.ts"
},
"./design/components/Icon": {
"default": "./src/design/components/Icon.ts"
"default": "./src/design.ts"
},
"./native": {
"default": "./src/native.ts"
@ -30,14 +24,14 @@
"./preinit": {
"default": "./src/preinit.ts"
},
"./modules": {
"default": "./src/modules/index.ts"
},
"./modules/settings": {
"default": "./src/modules/settings/index.ts"
},
"./modules/settings/renderer": {
"default": "./src/modules/settings/renderer.ts"
},
"./types": {
"types": "./types/index.ts"
"types": "./src/types/index.ts"
},
"./_/modules/settings": {
"default": "./src/modules/settings/_internal.ts"

View file

@ -1,12 +1,12 @@
import { lookupModule, lookupModules } from '@revenge-mod/modules/finders'
import {
byDependencies,
byProps,
every,
moduleStateAware,
preferExports,
relativeDep,
withoutProps,
} from '@revenge-mod/modules/finders/filters'
import { lookupModule, lookupModules } from '@revenge-mod/modules/finders/lookup'
import { ReactJsxRuntimeModuleId, ReactModuleId } from '@revenge-mod/react'
@ -14,12 +14,12 @@ import { proxify } from '@revenge-mod/utils/proxy'
import { DispatcherModuleId } from './common/flux'
import type { DiscordModules } from '../types'
import type { DiscordModules } from './types'
export let ActionSheetActionCreators: DiscordModules.Actions.ActionSheetActionCreators = proxify(
() => {
const module = lookupModule(
moduleStateAware(
preferExports(
byProps<DiscordModules.Actions.ActionSheetActionCreators>('hideActionSheet'),
byDependencies([
undefined,
@ -36,13 +36,7 @@ export let ActionSheetActionCreators: DiscordModules.Actions.ActionSheetActionCr
},
)
if (module) {
// This allows the Proxy instance to be garbage collected
// after the module is initialized.
ActionSheetActionCreators = module
gc()
return module
}
if (module) return (ActionSheetActionCreators = module)
},
{
hint: 'object',
@ -52,7 +46,7 @@ export let ActionSheetActionCreators: DiscordModules.Actions.ActionSheetActionCr
export let AlertActionCreators: DiscordModules.Actions.AlertActionCreators = proxify(
() => {
const module = lookupModule(
moduleStateAware(
preferExports(
byProps<DiscordModules.Actions.AlertActionCreators>('openAlert', 'dismissAlert'),
byDependencies([
ReactModuleId,
@ -66,13 +60,7 @@ export let AlertActionCreators: DiscordModules.Actions.AlertActionCreators = pro
),
)
if (module) {
// This allows the Proxy instance to be garbage collected
// after the module is initialized.
AlertActionCreators = module
gc()
return module
}
if (module) return (AlertActionCreators = module)
},
{
hint: 'object',
@ -84,7 +72,7 @@ export let ToastActionCreators: DiscordModules.Actions.ToastActionCreators = pro
// [Dispatcher, ImportTracker]
const generator = lookupModules(
moduleStateAware(
preferExports(
every(byProps<DiscordModules.Actions.ToastActionCreators>('open'), withoutProps('init')),
byDependencies([DispatcherModuleId, 2]),
),
@ -93,12 +81,5 @@ export let ToastActionCreators: DiscordModules.Actions.ToastActionCreators = pro
},
)
for (const module of generator)
if (module.open.length === 1) {
// This allows the Proxy instance to be garbage collected
// after the module is initialized.
ToastActionCreators = module
gc()
return module
}
for (const module of generator) if (module.open.length === 1) return (ToastActionCreators = module)
})!

View file

@ -1,10 +1,13 @@
import { lookupModuleId, waitForModules } from '@revenge-mod/modules/finders'
import { byDependencies, byProps, moduleStateAware, relativeDep } from '@revenge-mod/modules/finders/filters'
import { byDependencies, byProps, preferExports, relativeDep } from '@revenge-mod/modules/finders/filters'
import { lookupModuleId } from '@revenge-mod/modules/finders/lookup'
import { waitForModules } from '@revenge-mod/modules/finders/wait'
import type { DiscordModules } from '../../types'
import type { DiscordModules } from '../types'
// ../discord_common/js/packages/flux
export const DispatcherModuleId = lookupModuleId(
moduleStateAware(
preferExports(
byProps<DiscordModules.Flux.Dispatcher>('_interceptors'),
byDependencies([relativeDep(1), undefined, undefined, undefined, undefined, 2]),
),
@ -22,7 +25,7 @@ waitForModules(byProps<DiscordModules.Flux.Store>('_dispatchToken'), (_, store)
const _stores: Record<string, any> = {}
export const stores = new Proxy(_stores, {
export const Stores = new Proxy(_stores, {
ownKeys: () => Reflect.ownKeys(_stores),
get: (_, name) => stores[name as string],
get: (_, name) => Stores[name as string],
})

View file

@ -1,15 +1,15 @@
import { lookupModule, lookupModuleId } from '@revenge-mod/modules/finders'
import { byName, byProps } from '@revenge-mod/modules/finders/filters'
import { lookupModule, lookupModuleId } from '@revenge-mod/modules/finders/lookup'
import type { DiscordModules } from '../../types'
import type { DiscordModules } from '../types'
// ../discord_common/js/packages/flux
export * as flux from './flux'
export { AppStartPerformance } from '../preinit'
export * as Flux from './flux'
// ../discord_common/js/shared/Logger.tsx
export const Logger = lookupModule(byName<DiscordModules.Logger>('Logger'))!
// ../discord_common/js/packages/tokens/native.tsx
export const tokensModuleId = lookupModuleId(byProps('RawColor'))!
export const tokens = __r(tokensModuleId)
export { AppStartPerformance } from '../preinit'
// ../discord_common/js/packages/tokens/native.tsx
export const TokensModuleId = lookupModuleId(byProps('RawColor'))!
export const Tokens = __r(TokensModuleId)

View file

@ -1,17 +1,17 @@
import { lookupModule } from '@revenge-mod/modules/finders'
import { byDependencies, byProps, looseDeps, moduleStateAware, relativeDep } from '@revenge-mod/modules/finders/filters'
import { byDependencies, byProps, looseDeps, preferExports, relativeDep } from '@revenge-mod/modules/finders/filters'
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
import { proxify } from '@revenge-mod/utils/proxy'
import type { DiscordModules } from '../../../types'
import type { DiscordModules } from './types'
// design/native.tsx
let components: Components = proxify(
export let Components: Components = proxify(
() => {
// ID: 3236
// [3237, 1366, 3238, 3239, 2, ...];
const module = lookupModule(
moduleStateAware(
preferExports(
byProps<Components>('TableRow', 'Button'),
byDependencies(looseDeps([relativeDep(1), undefined, relativeDep(2), relativeDep(3), 2])),
),
@ -20,21 +20,13 @@ let components: Components = proxify(
},
)
if (module) {
// This allows the Proxy instance to be garbage collected
// after the module is initialized.
components = module
gc()
return module
}
if (module) return (Components = module)
},
{
hint: 'object',
},
)!
export default components
export interface Components {
ActionSheet: DiscordModules.Components.ActionSheet
ActionSheetRow: DiscordModules.Components.ActionSheetRow

View file

@ -1 +0,0 @@
export * as Icon from './Icon'

View file

@ -1,9 +0,0 @@
import components_ from './components'
import * as components_exports from './components/_exports'
const components = {
...components_,
...components_exports,
}
export { components }

View file

@ -1,5 +0,0 @@
export * as actions from './actions'
export * as common from './common'
export * as design from './design'
export * as modules from './modules'
export * as native from './native'

View file

@ -1 +0,0 @@
export * as settings from './settings'

View file

@ -1,4 +1,4 @@
import type { SettingsItem, SettingsModulesLoadedSubscription, SettingsSection } from './api'
import type { SettingsItem, SettingsModulesLoadedSubscription, SettingsSection } from '.'
export const _data = [
// Sections to splice in the SettingsOverviewScreen

View file

@ -1,120 +0,0 @@
import { _data, _subs } from './_internal'
import type { ComponentType, ReactNode } from 'react'
import type { DiscordModules } from '../../../types'
export type SettingsModulesLoadedSubscription = () => void
/**
* Checks if the settings modules are loaded.
*/
export function isSettingsModulesLoaded() {
return _data[2]
}
/**
* Subscribes to when settings modules are loaded.
* Plugins should ideally register their settings in the given callback to ensure fast startup time.
*
* @param subcription The subscription function to call when the settings modules are loaded.
* @returns A function to unsubscribe from the event.
* @throws Throws an error if the settings modules are already loaded. Check with {@link isSettingsModulesLoaded} first.
*/
export function onceSettingsModulesLoaded(subcription: SettingsModulesLoadedSubscription) {
if (_data[2]) throw new Error('Settings modules already loaded')
_subs.add(subcription)
return () => _subs.delete(subcription)
}
/**
* Registers a settings section with a given key.
*
* @param key The key to register the settings section with.
* @param section The settings section to register.
* @returns A function to unregister the settings section.
*/
export function registerSettingsSection(key: string, section: SettingsSection) {
_data[0][key] = section
return () => delete _data[0][key]
}
/**
* Registers a settings item with a given key.
*
* @param key The key to register the settings item with.
* @param item The settings item to register.
* @returns A function to unregister the settings item.
*/
export function registerSettingsItem(key: string, item: SettingsItem) {
_data[1][key] = item
return () => delete _data[1][key]
}
/**
* Registers multiple settings items at once.
*
* @param record The settings items to register.
* @returns A function to unregister the settings items.
*/
export function registerSettingsItems(record: Record<string, SettingsItem>) {
Object.assign(_data[1], record)
return () => {
for (const key in record) delete _data[1][key]
}
}
/**
* Adds a settings item to an existing section.
*
* @param key The section to add the settings item to.
* @param item The settings item to add.
* @returns A function to remove the settings item from the section.
*/
export function addSettingsItemToSection(key: string, item: string) {
const section = _data[0][key]
if (!section) throw new Error(`Section "${key}" does not exist`)
const newLength = section.settings.push(item)
return () => delete section.settings[newLength - 1]
}
export interface SettingsSection {
label: string
settings: string[]
index?: number
}
interface BaseSettingsItem {
title: () => string
parent: string | null
unsearchable?: boolean
variant?: DiscordModules.Components.TableRowProps['variant']
IconComponent?: () => ReactNode
usePredicate?: () => boolean
useTrailing?: () => ReactNode
useDescription?: () => string
useIsDisabled?: () => boolean
}
export interface PressableSettingsItem extends BaseSettingsItem {
type: 'pressable'
onPress?: () => void
}
export interface ToggleSettingsItem extends BaseSettingsItem {
type: 'toggle'
useValue: () => boolean
onValueChange?: (value: boolean) => void
}
export interface RouteSettingsItem extends BaseSettingsItem {
type: 'route'
screen: { route: string; getComponent(): ComponentType }
}
export interface StaticSettingsItem extends BaseSettingsItem {
type: 'static'
}
export type SettingsItem = PressableSettingsItem | ToggleSettingsItem | RouteSettingsItem | StaticSettingsItem

View file

@ -1,6 +1,82 @@
import { _data } from './_internal'
import { _data, _subs } from './_internal'
export * from './api'
export * from './common'
import type { DiscordModules } from '../../types'
// Patching in done in src/plugins/settings
export type SettingsItem = DiscordModules.Modules.Settings.SettingsItem
export type SettingsSection = DiscordModules.Modules.Settings.SettingsSection
export type SettingsModulesLoadedSubscription = () => void
/**
* Checks if the settings modules are loaded.
*/
export function isSettingsModulesLoaded() {
return _data[2]
}
/**
* Subscribes to when settings modules are loaded.
* Plugins should ideally register their settings in the given callback to ensure fast startup time.
*
* @param subcription The subscription function to call when the settings modules are loaded.
* @returns A function to unsubscribe from the event.
* @throws Throws an error if the settings modules are already loaded. Check with {@link isSettingsModulesLoaded} first.
*/
export function onceSettingsModulesLoaded(subcription: SettingsModulesLoadedSubscription) {
if (_data[2]) throw new Error('Settings modules already loaded')
_subs.add(subcription)
return () => _subs.delete(subcription)
}
/**
* Registers a settings section with a given key.
*
* @param key The key to register the settings section with.
* @param section The settings section to register.
* @returns A function to unregister the settings section.
*/
export function registerSettingsSection(key: string, section: SettingsSection) {
_data[0][key] = section
return () => delete _data[0][key]
}
/**
* Registers a settings item with a given key.
*
* @param key The key to register the settings item with.
* @param item The settings item to register.
* @returns A function to unregister the settings item.
*/
export function registerSettingsItem(key: string, item: SettingsItem) {
_data[1][key] = item
return () => delete _data[1][key]
}
/**
* Registers multiple settings items at once.
*
* @param record The settings items to register.
* @returns A function to unregister the settings items.
*/
export function registerSettingsItems(record: Record<string, SettingsItem>) {
Object.assign(_data[1], record)
return () => {
for (const key in record) delete _data[1][key]
}
}
/**
* Adds a settings item to an existing section.
*
* @param key The section to add the settings item to.
* @param item The settings item to add.
* @returns A function to remove the settings item from the section.
*/
export function addSettingsItemToSection(key: string, item: string) {
const section = _data[0][key]
if (!section) throw new Error(`Section "${key}" does not exist`)
const newLength = section.settings.push(item)
return () => delete section.settings[newLength - 1]
}

View file

@ -1,16 +1,18 @@
import { lookupModule } from '@revenge-mod/modules/finders'
import { byDependencies, byProps, looseDeps, moduleStateAware, relativeDep } from '@revenge-mod/modules/finders/filters'
import { byDependencies, byProps, looseDeps, preferExports, relativeDep } from '@revenge-mod/modules/finders/filters'
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
import { ReactJsxRuntimeModuleId, ReactModuleId, ReactNativeModuleId } from '@revenge-mod/react'
import { proxify } from '@revenge-mod/utils/proxy'
import type { ComponentType, ReactNode } from 'react'
import type { DiscordModules } from '../../types'
export type SettingListRenderer = DiscordModules.Modules.Settings.SettingListRenderer
export let SettingListRenderer: SettingListRenderer = proxify(
() => {
const module = lookupModule(
moduleStateAware(
preferExports(
byProps<SettingListRenderer>('SettingsList'),
byDependencies(
looseDeps([
@ -28,22 +30,9 @@ export let SettingListRenderer: SettingListRenderer = proxify(
},
)
if (module) {
// This allows the Proxy instance to be garbage collected
// after the module is initialized.
SettingListRenderer = module
gc()
return module
}
if (module) return (SettingListRenderer = module)
},
{
hint: 'object',
},
)!
export interface SettingListRenderer {
SettingsList: ComponentType<{
ListHeaderComponent?: ComponentType
sections: Array<{ label?: string | ReactNode; settings: string[]; subLabel?: string | ReactNode }>
}>
}

View file

@ -1,4 +1,4 @@
import type { DiscordNativeModules } from '../types/native'
import type { DiscordNativeModules } from './types/native'
/**
* Naming conventions:

View file

@ -1,11 +1,11 @@
import { lookupModule } from '@revenge-mod/modules/finders'
import { byDependencies, byProps, moduleStateAware, relativeDep } from '@revenge-mod/modules/finders/filters'
import { byDependencies, byProps, preferExports, relativeDep } from '@revenge-mod/modules/finders/filters'
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
import type { DiscordModules } from '../types'
import type { DiscordModules } from './types'
// ../discord_common/js/shared/AppStartPerformance.tsx
export const AppStartPerformance = lookupModule(
moduleStateAware(
preferExports(
byProps<DiscordModules.AppStartPerformance>('markAndLog'),
// biome-ignore lint/suspicious/noSparseArray: Nah
byDependencies([relativeDep(-1), , , 2]),

View file

@ -1,5 +1,3 @@
import type { Buffer as _Buffer } from 'buffer'
import type { ComponentProps, ComponentType, FC, ReactNode } from 'react'
import type {
ImageSourcePropType,
@ -13,6 +11,7 @@ import type {
} from 'react-native'
export * from './native'
export * from './polyfills'
export namespace DiscordModules {
export namespace Flux {
@ -251,7 +250,6 @@ export namespace DiscordModules {
end?: boolean
variant?: 'primary' | 'secondary' | 'transparent'
border?: 'faint' | 'normal' | 'strong' | 'subtle' | 'none'
// TODO
shadow?: 'none' | 'low' | 'medium' | 'high' | 'border' | 'ledge'
children: ReactNode
}
@ -488,10 +486,63 @@ export namespace DiscordModules {
export type Slider = FC<SliderProps>
}
}
/// POLYFILLS
export namespace Modules {
export namespace Settings {
export interface SettingListRenderer {
SettingsList: SettingsList
}
declare global {
var Buffer: typeof _Buffer
export interface SettingsListProps {
ListHeaderComponent?: ComponentType
sections: Array<{ label?: string | ReactNode; settings: string[]; subLabel?: string | ReactNode }>
}
export type SettingsList = FC<SettingsListProps>
export interface SettingsSection {
label: string
settings: string[]
index?: number
}
interface BaseSettingsItem {
title: () => string
parent: string | null
unsearchable?: boolean
variant?: DiscordModules.Components.TableRowProps['variant']
IconComponent?: () => ReactNode
usePredicate?: () => boolean
useTrailing?: () => ReactNode
useDescription?: () => string
useIsDisabled?: () => boolean
}
export interface PressableSettingsItem extends BaseSettingsItem {
type: 'pressable'
onPress?: () => void
}
export interface ToggleSettingsItem extends BaseSettingsItem {
type: 'toggle'
useValue: () => boolean
onValueChange?: (value: boolean) => void
}
export interface RouteSettingsItem extends BaseSettingsItem {
type: 'route'
screen: { route: string; getComponent(): ComponentType }
}
export interface StaticSettingsItem extends BaseSettingsItem {
type: 'static'
}
export type SettingsItem =
| PressableSettingsItem
| ToggleSettingsItem
| RouteSettingsItem
| StaticSettingsItem
}
}
}

View file

@ -0,0 +1,5 @@
import type { Buffer as _Buffer } from 'buffer'
declare global {
var Buffer: typeof _Buffer
}

View file

@ -6,6 +6,9 @@
"./callbacks": {
"default": "./src/callbacks.ts"
},
"./discord": {
"default": "./src/discord.ts"
},
"./errors": {
"default": "./src/errors.ts"
},

View file

@ -1,17 +1,16 @@
import { getAssetByName, getAssetModuleId } from '@revenge-mod/assets'
import { TokensModuleId } from '@revenge-mod/discord/common'
import { lookupModule } from '@revenge-mod/modules/finders'
import { byDependencies, byProps } from '@revenge-mod/modules/finders/filters'
import { lookupModule } from '@revenge-mod/modules/finders/lookup'
import { ReactJsxRuntimeModuleId, ReactModuleId } from '@revenge-mod/react'
import { tokensModuleId } from '../../common'
import type { FC } from 'react'
// [React, ReactJsxRuntime, (BaseIconImage), (Tokens), (Asset), 2]
// [React, ReactJsxRuntime, (BaseIconImage), (Tokens), (...), (Asset), 2]
// Asset = relativeDep(1)
const filter = [ReactModuleId, ReactJsxRuntimeModuleId, tokensModuleId, undefined, undefined, 2]
const IconComponentFilter = [ReactModuleId, ReactJsxRuntimeModuleId, TokensModuleId, undefined, undefined, 2]
export function lookupGeneratedIconComponent<N extends string>(name: N) {
const icon = lookupModule(byProps(name))?.[name]
@ -20,9 +19,9 @@ export function lookupGeneratedIconComponent<N extends string>(name: N) {
const mid = getAssetModuleId(getAssetByName(name)!)!
if (!mid) return
filter[4] = mid
IconComponentFilter[4] = mid
return lookupModule(byDependencies<{ [K in N]: FC }>(filter), {
return lookupModule(byDependencies<{ [K in N]: FC }>(IconComponentFilter), {
includeInitialized: false,
includeUninitialized: true,
})?.[name]