feat(lib/plugins): refactor discord plugin API as API extensions

The aim is to reduce Discord-specific code in core libraries.
This commit is contained in:
PalmDevs 2025-06-02 15:50:29 +07:00
parent 6d1ac67bff
commit 3cc039ae73
No known key found for this signature in database
GPG key ID: 6FAE8F8677ACA8BA
9 changed files with 144 additions and 122 deletions

View file

@ -21,6 +21,9 @@
"./native": {
"default": "./src/native.ts"
},
"./init": {
"default": "./src/init.ts"
},
"./preinit": {
"default": "./src/preinit.ts"
},

26
lib/discord/src/init.ts Normal file
View file

@ -0,0 +1,26 @@
import { _initExts, _uapi } from '@revenge-mod/plugins/_'
import { defineLazyProperty } from '@revenge-mod/utils/objects'
import { Logger } from './common'
import type { UnscopedInitPluginApi } from '@revenge-mod/plugins/types'
import type { PluginApiDiscord } from './types/revenge'
const uapi = _uapi as UnscopedInitPluginApi
const discord = (uapi.discord = {} as PluginApiDiscord)
// TODO: Rolldown issue: Bug requires us to do { ...require('...') } instead of just require('...')
defineLazyProperty(discord, 'actions', () => ({ ...require('@revenge-mod/discord/actions') }))
defineLazyProperty(discord, 'common', () => ({ ...require('@revenge-mod/discord/common') }))
defineLazyProperty(discord, 'design', () => ({ ...require('@revenge-mod/discord/design') }))
defineLazyProperty(discord, 'native', () => ({ ...require('@revenge-mod/discord/native') }))
defineLazyProperty(discord, 'modules', () => ({
settings: {
...require('@revenge-mod/discord/modules/settings'),
renderer: require('@revenge-mod/discord/modules/settings/renderer'),
},
}))
_initExts.push((api, { manifest: { id } }) =>
defineLazyProperty(api, 'logger', () => new Logger(`Revenge > Plugins (${id})`)),
)

View file

@ -0,0 +1,32 @@
import type { DiscordModules } from '.'
export interface PluginApiDiscord {
actions: PluginApiDiscord.Actions
common: PluginApiDiscord.Common
design: PluginApiDiscord.Design
modules: PluginApiDiscord.Modules
native: PluginApiDiscord.Native
}
export namespace PluginApiDiscord {
export type Actions = typeof import('@revenge-mod/discord/actions')
export type Common = typeof import('@revenge-mod/discord/common')
export type Design = typeof import('@revenge-mod/discord/design')
export type Native = typeof import('@revenge-mod/discord/native')
export interface Modules {
settings: typeof import('@revenge-mod/discord/modules/settings') & {
renderer: typeof import('@revenge-mod/discord/modules/settings/renderer')
}
}
}
declare module '@revenge-mod/plugins/types' {
export interface UnscopedInitPluginApi {
discord: PluginApiDiscord
}
export interface InitPluginApi {
logger: InstanceType<DiscordModules.Logger>
}
}

View file

@ -1,7 +1,7 @@
import { type StorageOptions, getStorage } from '@revenge-mod/storage'
import { allSettled, sleepReject } from '@revenge-mod/utils/promises'
import { _uapi } from './apis'
import { _uapi as uapi } from './apis'
import { PluginFlags as Flag, PluginsStorageDirectory, PluginStatus as Status } from './constants'
@ -16,6 +16,12 @@ import type {
PreInitPluginApi,
} from './types'
export const _uapi = uapi
export const _preInitExts: Array<(api: PreInitPluginApi, plugin: InternalPlugin) => void> = []
export const _initExts: Array<(api: InitPluginApi, plugin: InternalPlugin) => void> = []
export const _startExts: Array<(api: PluginApi, plugin: InternalPlugin) => void> = []
export const _plugins = new Map<PluginManifest['id'], InternalPlugin>()
export const _metas = new Map<
PluginManifest['id'],
@ -68,62 +74,56 @@ function handlePluginError(e: unknown, plugin: InternalPlugin) {
if (!(iflags & InternalPluginFlags.Essential)) return plugin.disable()
}
function preparePluginPreInit(id: PluginManifest['id']) {
const plugin = _plugins.get(id)!
const meta = _metas.get(id)!
function preparePluginPreInit(plugin: InternalPlugin) {
const meta = _metas.get(plugin.manifest.id)!
// Clear errors from previous runs
plugin.errors = []
plugin.status &= ~Flag.Errored
meta[0] = {
const api = (meta[0] = {
cleanup: (...items) => {
plugin._c.push(...items)
},
plugin,
unscoped: _uapi,
}
})
for (const ext of _preInitExts) ext(api, plugin)
meta[3] = PluginApiLevel.PreInit
}
function preparePluginInit(id: PluginManifest['id'], storageOptions?: StorageOptions<AnyObject>) {
const meta = _metas.get(id)!
function preparePluginInit(plugin: InternalPlugin, storageOptions?: StorageOptions<AnyObject>) {
const meta = _metas.get(plugin.manifest.id)!
const api = meta[0] as InitPluginApi
Object.defineProperties(api, {
logger: {
configurable: true,
get() {
// @ts-expect-error
// biome-ignore lint/performance/noDelete: <explanation>
delete api.logger
return (api.logger = new api.unscoped.discord.common.Logger(`Revenge > Plugins (${id})`))
},
},
storage: {
configurable: true,
get() {
// @ts-expect-error
// biome-ignore lint/performance/noDelete: <explanation>
delete api.storage
return (api.storage = getStorage(`${PluginsStorageDirectory}/${id}.json`, {
...storageOptions,
directory: 'documents',
}))
},
Object.defineProperty(api, 'storage', {
configurable: true,
get() {
// @ts-expect-error
// biome-ignore lint/performance/noDelete: <explanation>
delete api.storage
return (api.storage = getStorage(`${PluginsStorageDirectory}/${plugin.manifest.id}.json`, {
...storageOptions,
directory: 'documents',
}))
},
})
for (const ext of _initExts) ext(api, plugin)
meta[3] = PluginApiLevel.Init
}
function preparePluginStart(id: PluginManifest['id']) {
const meta = _metas.get(id)!
function preparePluginStart(plugin: InternalPlugin) {
const meta = _metas.get(plugin.manifest.id)!
// const api = meta[0] as PluginApi
const api = meta[0] as PluginApi
// api.settings = ...
for (const ext of _startExts) ext(api, plugin)
meta[3] = PluginApiLevel.Start
}
@ -146,17 +146,20 @@ export function enablePlugin(plugin: InternalPlugin, late: boolean) {
}
export async function preInitPlugin(plugin: InternalPlugin) {
const { manifest, lifecycles } = plugin
const {
manifest: { id },
lifecycles,
} = plugin
if (!lifecycles.preInit) return
const meta = _metas.get(manifest.id)!
const meta = _metas.get(id)!
const [, promises] = meta
preparePluginPreInit(manifest.id)
preparePluginPreInit(plugin)
if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${manifest.id}" is not enabled`)
if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${id}" is not enabled`)
if (plugin.status & (Status.PreIniting | Status.PreInited))
throw new Error(`Plugin preInit lifecycle for "${manifest.id}" is already running`)
throw new Error(`Plugin preInit lifecycle for "${id}" is already running`)
plugin.status |= Status.PreIniting
@ -174,18 +177,21 @@ export async function preInitPlugin(plugin: InternalPlugin) {
}
export async function initPlugin(plugin: InternalPlugin) {
const { manifest, lifecycles } = plugin
const {
manifest: { id },
lifecycles,
} = plugin
if (!lifecycles.init) return
const meta = _metas.get(manifest.id)!
const meta = _metas.get(id)!
const [, promises, , apiLevel] = meta
if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${manifest.id}" is not enabled`)
if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${id}" is not enabled`)
if (plugin.status & (Status.Initing | Status.Inited))
throw new Error(`Plugin init lifecycle for "${manifest.id}" is already running`)
throw new Error(`Plugin init lifecycle for "${id}" is already running`)
if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(manifest.id)
if (apiLevel < PluginApiLevel.Init) preparePluginInit(manifest.id)
if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(plugin)
if (apiLevel < PluginApiLevel.Init) preparePluginInit(plugin)
try {
const prom = lifecycles.init(meta[0] as InitPluginApi)
@ -201,21 +207,24 @@ export async function initPlugin(plugin: InternalPlugin) {
}
export async function startPlugin(plugin: InternalPlugin) {
const { manifest, lifecycles } = plugin
const {
manifest: { id },
lifecycles,
} = plugin
if (!lifecycles.start) return
const meta = _metas.get(manifest.id)!
const meta = _metas.get(id)!
const [, promises, , apiLevel] = meta
if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${manifest.id}" is not enabled`)
if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${id}" is not enabled`)
if (plugin.status & (Status.Starting | Status.Started))
throw new Error(`Plugin start lifecycle for "${manifest.id}" is already running`)
throw new Error(`Plugin start lifecycle for "${id}" is already running`)
plugin.status |= Status.Starting
if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(manifest.id)
if (apiLevel < PluginApiLevel.Init) preparePluginInit(manifest.id)
if (apiLevel < PluginApiLevel.Start) preparePluginStart(manifest.id)
if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(plugin)
if (apiLevel < PluginApiLevel.Init) preparePluginInit(plugin)
if (apiLevel < PluginApiLevel.Start) preparePluginStart(plugin)
try {
const prom = lifecycles.start(meta[0] as PluginApi)
@ -233,15 +242,17 @@ export async function startPlugin(plugin: InternalPlugin) {
const MaxWaitTime = 5000
export async function stopPlugin(plugin: InternalPlugin) {
const { manifest, lifecycles } = plugin
const meta = _metas.get(manifest.id)!
const {
manifest: { id },
lifecycles,
} = plugin
const meta = _metas.get(id)!
const [, promises, iflags, apiLevel] = meta
if (iflags & InternalPluginFlags.Essential)
throw new Error(`Plugin "${manifest.id}" is essential and cannot be stopped`)
if (iflags & InternalPluginFlags.Essential) throw new Error(`Plugin "${id}" is essential and cannot be stopped`)
if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${manifest.id}" is not enabled`)
if (plugin.status & Status.Stopping) throw new Error(`Plugin "${manifest.id}" is stopping`)
if (!(plugin.flags & Flag.Enabled)) throw new Error(`Plugin "${id}" is not enabled`)
if (plugin.status & Status.Stopping) throw new Error(`Plugin "${id}" is stopping`)
// If the plugin is running its lifecycles, we need to wait for it to finish, then we'll stop it
// We want to wait at max 5 seconds for the lifecycles to finish
@ -256,11 +267,11 @@ export async function stopPlugin(plugin: InternalPlugin) {
})
.catch(e => handlePluginError(e, plugin))
else if (!(plugin.status & (Status.PreInited | Status.Inited | Status.Started)))
throw new Error(`Plugin "${manifest.id}" is not running`)
throw new Error(`Plugin "${id}" is not running`)
if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(manifest.id)
if (apiLevel < PluginApiLevel.Init) preparePluginInit(manifest.id, plugin._s)
if (apiLevel < PluginApiLevel.Start) preparePluginStart(manifest.id)
if (apiLevel < PluginApiLevel.PreInit) preparePluginPreInit(plugin)
if (apiLevel < PluginApiLevel.Init) preparePluginInit(plugin, plugin._s)
if (apiLevel < PluginApiLevel.Start) preparePluginStart(plugin)
plugin.status |= Status.Stopping

View file

@ -1,41 +0,0 @@
export interface PluginApiDiscord {
actions: PluginApiDiscordActions
common: PluginApiDiscordCommon
design: PluginApiDiscordDesign
modules: PluginApiDiscordModules
native: PluginApiDiscordNative
}
export type PluginApiDiscordActions = typeof import('@revenge-mod/discord/actions')
export type PluginApiDiscordCommon = typeof import('@revenge-mod/discord/common')
export type PluginApiDiscordDesign = typeof import('@revenge-mod/discord/design')
export type PluginApiDiscordNative = typeof import('@revenge-mod/discord/native')
export interface PluginApiDiscordModules {
settings: typeof import('@revenge-mod/discord/modules/settings') & {
renderer: typeof import('@revenge-mod/discord/modules/settings/renderer')
}
}
export const discord: PluginApiDiscord = {
get actions() {
return (this.actions = require('@revenge-mod/discord/actions'))
},
get common() {
return (this.common = require('@revenge-mod/discord/common'))
},
get design() {
return (this.design = require('@revenge-mod/discord/design'))
},
native: require('@revenge-mod/discord/native'),
modules: {
get settings() {
const api = {
...require('@revenge-mod/discord/modules/settings'),
renderer: require('@revenge-mod/discord/modules/settings/renderer'),
}
return (this.settings = api)
},
},
}

View file

@ -1,3 +1,5 @@
import { defineLazyProperty } from '@revenge-mod/utils/objects'
export interface PluginApiExternals {
Browserify: typeof import('@revenge-mod/externals/browserify')
ReactNativeClipboard: typeof import('@revenge-mod/externals/react-native-clipboard')
@ -5,17 +7,12 @@ export interface PluginApiExternals {
Shopify: typeof import('@revenge-mod/externals/shopify')
}
export const externals: PluginApiExternals = {
get Browserify() {
return (this.Browserify = require('@revenge-mod/externals/browserify'))
},
get ReactNativeClipboard() {
return (this.ReactNativeClipboard = require('@revenge-mod/externals/react-native-clipboard'))
},
get ReactNavigation() {
return (this.ReactNavigation = require('@revenge-mod/externals/react-navigation'))
},
get Shopify() {
return (this.Shopify = require('@revenge-mod/externals/shopify'))
},
}
export const externals = {} as PluginApiExternals
// TODO: Rolldown issue: Bug requires us to do { ...require('...') } instead of just require('...')
defineLazyProperty(externals, 'Browserify', () => ({ ...require('@revenge-mod/externals/browserify') }))
defineLazyProperty(externals, 'ReactNativeClipboard', () => ({
...require('@revenge-mod/externals/react-native-clipboard'),
}))
defineLazyProperty(externals, 'ReactNavigation', () => ({ ...require('@revenge-mod/externals/react-navigation') }))
defineLazyProperty(externals, 'Shopify', () => ({ ...require('@revenge-mod/externals/shopify') }))

View file

@ -3,7 +3,6 @@ import { onRunApplication } from '@revenge-mod/react/native'
import { _uapi } from './apis'
import * as AssetsLibrary from '@revenge-mod/assets'
import { discord } from './apis/discord'
import { externals } from './apis/externals'
import { react } from './apis/react'
@ -15,7 +14,6 @@ import type { UnscopedInitPluginApi } from './types'
// Setup init plugin APIs
const uapi = _uapi as UnscopedInitPluginApi
uapi.assets = AssetsLibrary
uapi.discord = discord
uapi.externals = externals
uapi.react = react

View file

@ -1,11 +1,8 @@
import type { PluginFlags, PluginStatus } from './constants'
import type { DiscordModules } from '@revenge-mod/discord/types'
import type { Storage, StorageOptions } from '@revenge-mod/storage'
import type { AnyObject } from '@revenge-mod/utils/types'
import type { PluginApiDiscord } from './apis/discord'
import type { PluginApiExternals } from './apis/externals'
import type { PluginApiModules } from './apis/modules'
import type { PluginApiPlugins } from './apis/plugins'
@ -28,7 +25,6 @@ export interface UnscopedPreInitPluginApi {
*/
export interface UnscopedInitPluginApi extends UnscopedPreInitPluginApi {
assets: typeof import('@revenge-mod/assets')
discord: PluginApiDiscord
externals: PluginApiExternals
react: PluginApiReact
}
@ -60,7 +56,6 @@ export interface PreInitPluginApi {
*/
export interface InitPluginApi<S extends AnyObject = AnyObject> extends PreInitPluginApi {
unscoped: UnscopedInitPluginApi
logger: InstanceType<DiscordModules.Logger>
/**
* The plugin storage.
*

View file

@ -28,6 +28,7 @@ onModuleFirstRequired(IndexModuleId, function onIndexRequired() {
onModuleInitialized(IndexModuleId, function onIndexInitialized() {
runCatching(() => {
// Initialize init libraries
require('@revenge-mod/discord/init')
// Run all init plugins
require('~/plugins/init')