2025-04-29 21:38:14 +07:00
|
|
|
import * as DiscordLibrary from '@revenge-mod/discord'
|
|
|
|
|
import * as ModulesLibrary from '@revenge-mod/modules'
|
|
|
|
|
import * as PatcherLibrary from '@revenge-mod/patcher'
|
|
|
|
|
import * as PluginsLibrary from '@revenge-mod/plugins'
|
|
|
|
|
import * as ReactLibrary from '@revenge-mod/react'
|
|
|
|
|
|
|
|
|
|
const { PluginFlags } = PluginsLibrary
|
|
|
|
|
|
|
|
|
|
export const _uapi: PluginsLibrary.UnscopedInitPluginApi | PluginsLibrary.UnscopedPluginApi = {
|
|
|
|
|
discord: DiscordLibrary,
|
|
|
|
|
modules: ModulesLibrary,
|
|
|
|
|
plugins: PluginsLibrary,
|
|
|
|
|
react: ReactLibrary,
|
|
|
|
|
patcher: PatcherLibrary,
|
|
|
|
|
// utils: UtilsLibrary,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const _plugins = new Map<PluginsLibrary.PluginManifest['id'], InternalPlugin>()
|
|
|
|
|
|
|
|
|
|
export function registerPlugin(
|
|
|
|
|
manifest: PluginsLibrary.PluginManifest,
|
|
|
|
|
lifecycles: PluginsLibrary.PluginLifecycles,
|
|
|
|
|
flags: PluginsLibrary.PluginFlags = 0,
|
|
|
|
|
) {
|
|
|
|
|
// TODO(plugins): verify plugin manifest
|
|
|
|
|
if (_plugins.has(manifest.id)) throw new Error(`Plugin with ID "${manifest.id}" already registered`)
|
|
|
|
|
|
|
|
|
|
const handleError = (e: unknown) => {
|
|
|
|
|
plugin.errors.push(e)
|
|
|
|
|
plugin.flags |= PluginFlags.Errored
|
|
|
|
|
return plugin.disable()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const plugin: InternalPlugin = {
|
|
|
|
|
cleanups: [],
|
|
|
|
|
errors: [],
|
|
|
|
|
manifest,
|
|
|
|
|
lifecycles,
|
|
|
|
|
flags,
|
|
|
|
|
prepareStart: () => {
|
|
|
|
|
// const sapi = api as PluginsLibrary.PluginApi
|
|
|
|
|
// sapi.settings ??= ...
|
|
|
|
|
},
|
2025-04-30 18:53:00 +07:00
|
|
|
async init() {
|
2025-04-29 21:38:14 +07:00
|
|
|
plugin.flags |= PluginFlags.StatusInit
|
|
|
|
|
plugin.flags |= PluginFlags.StatusActioning
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await lifecycles.init?.(api)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
handleError(e)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Not in finally block because plugin.disable() already handles
|
|
|
|
|
plugin.flags &= ~PluginFlags.StatusInit
|
|
|
|
|
plugin.flags &= ~PluginFlags.StatusActioning
|
|
|
|
|
},
|
2025-04-30 18:53:00 +07:00
|
|
|
async start() {
|
2025-04-29 21:38:14 +07:00
|
|
|
plugin.flags |= PluginFlags.StatusStart
|
|
|
|
|
plugin.flags |= PluginFlags.StatusActioning
|
|
|
|
|
|
|
|
|
|
plugin.prepareStart()
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await lifecycles.start?.(api as PluginsLibrary.PluginApi)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
handleError(e)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Not in finally block because plugin.disable() already handles
|
|
|
|
|
plugin.flags &= ~PluginFlags.StatusStart
|
|
|
|
|
plugin.flags &= ~PluginFlags.StatusActioning
|
|
|
|
|
},
|
2025-04-30 18:53:00 +07:00
|
|
|
async stop() {
|
2025-04-29 21:38:14 +07:00
|
|
|
plugin.flags &= ~PluginFlags.StatusInit
|
|
|
|
|
plugin.flags &= ~PluginFlags.StatusStart
|
|
|
|
|
plugin.flags |= PluginFlags.StatusActioning
|
|
|
|
|
|
|
|
|
|
// In case the plugin only has init() and stop()
|
|
|
|
|
plugin.prepareStart()
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
await lifecycles.stop?.(api as PluginsLibrary.PluginApi)
|
|
|
|
|
} catch (e) {
|
|
|
|
|
handleError(e)
|
|
|
|
|
} finally {
|
|
|
|
|
// Run cleanups
|
|
|
|
|
const results = await Promise.allSettled(plugin.cleanups.map(cleanup => cleanup()))
|
|
|
|
|
for (const result of results) if (result.status === 'rejected') plugin.errors.push(result.reason)
|
|
|
|
|
|
|
|
|
|
plugin.flags &= ~PluginFlags.StatusActioning
|
|
|
|
|
}
|
|
|
|
|
},
|
2025-04-30 18:53:00 +07:00
|
|
|
async enable() {
|
2025-04-29 21:38:14 +07:00
|
|
|
plugin.flags |= PluginFlags.Enabled
|
|
|
|
|
if (lifecycles.init) plugin.flags |= PluginFlags.ReloadRequired
|
|
|
|
|
},
|
2025-04-30 18:53:00 +07:00
|
|
|
async disable() {
|
2025-04-29 21:38:14 +07:00
|
|
|
plugin.flags &= ~PluginFlags.Enabled
|
|
|
|
|
// If plugin is initialized/started, we need to stop it
|
|
|
|
|
if (plugin.flags & (PluginFlags.StatusInit | PluginFlags.StatusStart)) await plugin.stop()
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const api: PluginsLibrary.InitPluginApi | PluginsLibrary.PluginApi = {
|
|
|
|
|
cleanup: (...items) => {
|
|
|
|
|
plugin.cleanups.push(...items)
|
|
|
|
|
},
|
|
|
|
|
logger: new DiscordLibrary.Logger(`Revenge > ${plugin.manifest.name}`),
|
|
|
|
|
plugin,
|
|
|
|
|
unscoped: _uapi,
|
|
|
|
|
}!
|
|
|
|
|
|
|
|
|
|
_plugins.set(manifest.id, plugin)
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-30 18:53:41 +07:00
|
|
|
export async function initPlugins() {
|
2025-04-29 21:38:14 +07:00
|
|
|
const promises: Promise<void>[] = []
|
|
|
|
|
for (const plugin of _plugins.values()) if (plugin.flags & PluginFlags.Enabled) promises.push(plugin.init())
|
|
|
|
|
// These Promises never throw, so it is fine
|
2025-04-30 18:53:41 +07:00
|
|
|
await Promise.all(promises)
|
2025-04-29 21:38:14 +07:00
|
|
|
}
|
|
|
|
|
|
2025-04-30 18:53:41 +07:00
|
|
|
export async function startPlugins() {
|
2025-04-29 21:38:14 +07:00
|
|
|
const promises: Promise<void>[] = []
|
|
|
|
|
for (const plugin of _plugins.values()) if (plugin.flags & PluginFlags.Enabled) promises.push(plugin.start())
|
|
|
|
|
// These Promises never throw, so it is fine
|
2025-04-30 18:53:41 +07:00
|
|
|
await Promise.all(promises)
|
2025-04-29 21:38:14 +07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface InternalPlugin extends PluginsLibrary.Plugin {
|
|
|
|
|
cleanups: PluginsLibrary.PluginCleanup[]
|
|
|
|
|
prepareStart(): void
|
|
|
|
|
init(): Promise<void>
|
|
|
|
|
start(): Promise<void>
|
|
|
|
|
stop(): Promise<void>
|
|
|
|
|
}
|