2025-09-17 17:12:25 +07:00
|
|
|
import TableRowAssetIcon from '@revenge-mod/components/TableRowAssetIcon'
|
2026-07-07 23:12:40 +07:00
|
|
|
import {
|
|
|
|
|
AlertActionCreators,
|
|
|
|
|
ToastActionCreators,
|
|
|
|
|
} from '@revenge-mod/discord/actions'
|
2025-09-17 17:12:25 +07:00
|
|
|
import {
|
|
|
|
|
refreshSettingsNavigator,
|
|
|
|
|
registerSettingsItem,
|
|
|
|
|
} from '@revenge-mod/discord/modules/settings'
|
2026-07-09 23:09:29 +07:00
|
|
|
import {
|
|
|
|
|
isPluginErrored,
|
|
|
|
|
isPluginPendingReload,
|
|
|
|
|
isPluginPendingUpdate,
|
|
|
|
|
pEmitter,
|
|
|
|
|
pList,
|
|
|
|
|
} from '@revenge-mod/plugins/_'
|
2026-07-07 23:12:40 +07:00
|
|
|
import { lookupGeneratedIconComponent } from '@revenge-mod/utils/discord'
|
2025-09-17 17:12:25 +07:00
|
|
|
import { useLayoutEffect } from 'react'
|
feat!: new repo system + versioning system support + bug fixes
BREAKING CHANGES:
- New versioning system: Allowing any amount of number segments, with prerelease support.
- Any number of segments is supported: 1, 1.0, 1.0.0, 1.0.0.0, ...
- Prerelease labels are compared "digit-run", similar to Debian.
- Plugins must specify a version.
- User-facing plugins should use CalVer. API plugins should use SemVer.
Revenge APIs use SemVer while the new `discord` dependency parses the Discord version string directly.
- Dependency is now a Map, with support for version ranges and optional. Suggested URL is removed. (See new repo system)
- Plugins will now have to declare a dependency on `revenge.api` (Revenge API version) and `discord` (Discord version).
- Manifest now requires a format version (currently `1`)
New APIs:
- lib/plugins/utils: Utilities to format versioning
Features:
- New repo system: Repository design is inspired by Linux package repositories.
- Repos host manifest, versions, channels, download URL/size, and hash for multiple plugins.
- New dependency system: Plugins are versioned, and shouldn't break when updates come.
Plugins can now declare dependency on supported Discord/Revenge API versions, so they won't break with auto-updates.
Dependencies can be optional, but dependency on `revenge.api` and `discord` must be required.
Dependents are soft-disabled when dependencies don't satisfy. Once satisfied, the plugin can start again.
- All updates for running plugins REQUIRE a reload to prevent stale linking issues. Both native and JS-side.
- Update checking is handled entirely by JS. But the native-side still handles installation, indexing, and verification.
- Local indexes are updated against remote indexes. Dependency checking always uses local indexes.
- Users can add custom repositories and custom their priority order. URL is the identity.
- Plugins can now have custom data URI icons instead of being restricted to React Native app assets.
- External plugin installs require UI confirmation before installing.
- Pending Update and Pending Reload filters in Plugins page.
- Ability to browse external plugins in repositories. Official repository will be added soon.
Fixes/Refactor:
- Refactored Plugin-related settings to a separate "Plugin Settings" plugin.
- Masonry layout fixes in landscape.
- Updated plugins now show "reload to apply" toast instead.
- Removed dead code.
2026-07-25 23:35:23 +07:00
|
|
|
import { Setting } from '../settings/constants'
|
2025-09-17 17:12:25 +07:00
|
|
|
import defer * as NavigatorHeaderWithIcon from './components/NavigatorHeaderWithIcon'
|
feat!: new repo system + versioning system support + bug fixes
BREAKING CHANGES:
- New versioning system: Allowing any amount of number segments, with prerelease support.
- Any number of segments is supported: 1, 1.0, 1.0.0, 1.0.0.0, ...
- Prerelease labels are compared "digit-run", similar to Debian.
- Plugins must specify a version.
- User-facing plugins should use CalVer. API plugins should use SemVer.
Revenge APIs use SemVer while the new `discord` dependency parses the Discord version string directly.
- Dependency is now a Map, with support for version ranges and optional. Suggested URL is removed. (See new repo system)
- Plugins will now have to declare a dependency on `revenge.api` (Revenge API version) and `discord` (Discord version).
- Manifest now requires a format version (currently `1`)
New APIs:
- lib/plugins/utils: Utilities to format versioning
Features:
- New repo system: Repository design is inspired by Linux package repositories.
- Repos host manifest, versions, channels, download URL/size, and hash for multiple plugins.
- New dependency system: Plugins are versioned, and shouldn't break when updates come.
Plugins can now declare dependency on supported Discord/Revenge API versions, so they won't break with auto-updates.
Dependencies can be optional, but dependency on `revenge.api` and `discord` must be required.
Dependents are soft-disabled when dependencies don't satisfy. Once satisfied, the plugin can start again.
- All updates for running plugins REQUIRE a reload to prevent stale linking issues. Both native and JS-side.
- Update checking is handled entirely by JS. But the native-side still handles installation, indexing, and verification.
- Local indexes are updated against remote indexes. Dependency checking always uses local indexes.
- Users can add custom repositories and custom their priority order. URL is the identity.
- Plugins can now have custom data URI icons instead of being restricted to React Native app assets.
- External plugin installs require UI confirmation before installing.
- Pending Update and Pending Reload filters in Plugins page.
- Ability to browse external plugins in repositories. Official repository will be added soon.
Fixes/Refactor:
- Refactored Plugin-related settings to a separate "Plugin Settings" plugin.
- Masonry layout fixes in landscape.
- Updated plugins now show "reload to apply" toast instead.
- Removed dead code.
2026-07-25 23:35:23 +07:00
|
|
|
import PluginInstallConfirmAlert from './components/PluginInstallConfirmAlert'
|
2026-07-07 23:12:40 +07:00
|
|
|
import PluginInstallFailedAlert from './components/PluginInstallFailedAlert'
|
2025-10-23 00:55:24 +07:00
|
|
|
import PluginsFailedToStartAlert from './components/PluginsFailedToStartAlert'
|
|
|
|
|
import PluginsRequireReloadAlert from './components/PluginsRequireReloadAlert'
|
2025-09-17 17:12:25 +07:00
|
|
|
import type { StackScreenProps } from '@react-navigation/stack'
|
|
|
|
|
import type { ReactNavigationParamList } from '@revenge-mod/externals/react-navigation'
|
|
|
|
|
import type { PluginApi } from '@revenge-mod/plugins/types'
|
|
|
|
|
|
2026-06-13 01:35:33 +07:00
|
|
|
const PluginsRequireReloadAlertKey = 'plugins-require-reload'
|
|
|
|
|
const PluginsFailedToStartAlertKey = 'plugins-failed-to-start'
|
|
|
|
|
|
2025-09-17 17:12:25 +07:00
|
|
|
/// SETTINGS ROUTES
|
|
|
|
|
|
|
|
|
|
pEmitter.on('started', plugin => {
|
|
|
|
|
if (plugin.SettingsComponent) {
|
|
|
|
|
const api = plugin.api as PluginApi<any>
|
|
|
|
|
const Component = plugin.SettingsComponent!
|
|
|
|
|
|
|
|
|
|
function PluginSettings({
|
|
|
|
|
navigation,
|
|
|
|
|
}: StackScreenProps<ReactNavigationParamList>) {
|
|
|
|
|
// biome-ignore lint/correctness/useExhaustiveDependencies: We only want to set options once
|
|
|
|
|
useLayoutEffect(() => {
|
|
|
|
|
if (plugin.manifest.icon)
|
|
|
|
|
navigation.setOptions({
|
|
|
|
|
headerTitle: () => (
|
|
|
|
|
<NavigatorHeaderWithIcon.default
|
|
|
|
|
title={plugin.manifest.name}
|
|
|
|
|
icon={plugin.manifest.icon!}
|
|
|
|
|
/>
|
|
|
|
|
),
|
|
|
|
|
})
|
|
|
|
|
else
|
|
|
|
|
navigation.setOptions({
|
|
|
|
|
title: plugin.manifest.name,
|
|
|
|
|
})
|
|
|
|
|
}, [])
|
|
|
|
|
|
|
|
|
|
return <Component api={api} />
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
api.cleanup(
|
|
|
|
|
registerSettingsItem(plugin.manifest.id, {
|
|
|
|
|
parent: Setting.Revenge,
|
|
|
|
|
type: 'route',
|
|
|
|
|
IconComponent: plugin.manifest.icon
|
|
|
|
|
? () => <TableRowAssetIcon name={plugin.manifest.icon!} />
|
|
|
|
|
: undefined,
|
2026-05-13 01:08:55 +07:00
|
|
|
useTitle: () => plugin.manifest.name,
|
2025-09-17 17:12:25 +07:00
|
|
|
screen: {
|
|
|
|
|
route: plugin.manifest.id,
|
|
|
|
|
getComponent: () => PluginSettings,
|
|
|
|
|
},
|
|
|
|
|
}),
|
|
|
|
|
refreshSettingsNavigator,
|
|
|
|
|
// TODO(PalmDevs): In the future, we may allow pinning plugin settings, so we'll need this cleanup
|
|
|
|
|
// () => refreshSettingsOverviewScreen(),
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
refreshSettingsNavigator()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
/// RELOAD REQUIRED ALERT
|
|
|
|
|
|
2026-07-07 23:12:40 +07:00
|
|
|
function showPendingReloadAlertIfNeeded() {
|
2025-09-17 17:12:25 +07:00
|
|
|
const plugins = [...pList.values()].filter(
|
2026-07-07 23:12:40 +07:00
|
|
|
plugin =>
|
2026-07-09 23:09:29 +07:00
|
|
|
isPluginPendingReload(plugin) || isPluginPendingUpdate(plugin),
|
2025-09-17 17:12:25 +07:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (!plugins.length) return
|
|
|
|
|
|
2026-06-13 01:35:33 +07:00
|
|
|
AlertActionCreators.dismissAlert(PluginsRequireReloadAlertKey)
|
2025-09-17 17:12:25 +07:00
|
|
|
AlertActionCreators.openAlert(
|
2026-06-13 01:35:33 +07:00
|
|
|
PluginsRequireReloadAlertKey,
|
2025-10-23 00:55:24 +07:00
|
|
|
<PluginsRequireReloadAlert plugins={plugins} />,
|
2025-09-17 17:12:25 +07:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-23 00:55:24 +07:00
|
|
|
function showErrorAlertIfNeeded() {
|
2026-07-09 23:09:29 +07:00
|
|
|
const plugins = [...pList.values()].filter(plugin =>
|
|
|
|
|
isPluginErrored(plugin),
|
2025-10-23 00:55:24 +07:00
|
|
|
)
|
2025-09-17 17:12:25 +07:00
|
|
|
|
2025-10-23 00:55:24 +07:00
|
|
|
if (!plugins.length) return
|
|
|
|
|
|
2026-06-13 01:35:33 +07:00
|
|
|
AlertActionCreators.dismissAlert(PluginsFailedToStartAlertKey)
|
2025-10-23 00:55:24 +07:00
|
|
|
AlertActionCreators.openAlert(
|
2026-06-13 01:35:33 +07:00
|
|
|
PluginsFailedToStartAlertKey,
|
2025-10-23 00:55:24 +07:00
|
|
|
<PluginsFailedToStartAlert plugins={plugins} />,
|
2025-09-17 17:12:25 +07:00
|
|
|
)
|
|
|
|
|
}
|
2025-10-23 00:55:24 +07:00
|
|
|
|
|
|
|
|
pEmitter.on('flagUpdate', plugin => {
|
2026-07-09 23:09:29 +07:00
|
|
|
if (isPluginPendingReload(plugin) || isPluginPendingUpdate(plugin))
|
2026-07-07 23:12:40 +07:00
|
|
|
showPendingReloadAlertIfNeeded()
|
2025-10-23 00:55:24 +07:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
pEmitter.on('stopped', plugin => {
|
2026-07-09 23:09:29 +07:00
|
|
|
if (isPluginErrored(plugin)) showErrorAlertIfNeeded()
|
2025-10-23 00:55:24 +07:00
|
|
|
})
|
|
|
|
|
|
2026-07-07 23:12:40 +07:00
|
|
|
/// PLUGIN INSTALL FEEDBACK
|
|
|
|
|
|
|
|
|
|
const pluginInstallToastKeyFor = (id: string) => `REVENGE_PLUGIN_INSTALL:${id}`
|
|
|
|
|
const PluginInstallFailedAlertKey = 'plugin-install-failed'
|
feat!: new repo system + versioning system support + bug fixes
BREAKING CHANGES:
- New versioning system: Allowing any amount of number segments, with prerelease support.
- Any number of segments is supported: 1, 1.0, 1.0.0, 1.0.0.0, ...
- Prerelease labels are compared "digit-run", similar to Debian.
- Plugins must specify a version.
- User-facing plugins should use CalVer. API plugins should use SemVer.
Revenge APIs use SemVer while the new `discord` dependency parses the Discord version string directly.
- Dependency is now a Map, with support for version ranges and optional. Suggested URL is removed. (See new repo system)
- Plugins will now have to declare a dependency on `revenge.api` (Revenge API version) and `discord` (Discord version).
- Manifest now requires a format version (currently `1`)
New APIs:
- lib/plugins/utils: Utilities to format versioning
Features:
- New repo system: Repository design is inspired by Linux package repositories.
- Repos host manifest, versions, channels, download URL/size, and hash for multiple plugins.
- New dependency system: Plugins are versioned, and shouldn't break when updates come.
Plugins can now declare dependency on supported Discord/Revenge API versions, so they won't break with auto-updates.
Dependencies can be optional, but dependency on `revenge.api` and `discord` must be required.
Dependents are soft-disabled when dependencies don't satisfy. Once satisfied, the plugin can start again.
- All updates for running plugins REQUIRE a reload to prevent stale linking issues. Both native and JS-side.
- Update checking is handled entirely by JS. But the native-side still handles installation, indexing, and verification.
- Local indexes are updated against remote indexes. Dependency checking always uses local indexes.
- Users can add custom repositories and custom their priority order. URL is the identity.
- Plugins can now have custom data URI icons instead of being restricted to React Native app assets.
- External plugin installs require UI confirmation before installing.
- Pending Update and Pending Reload filters in Plugins page.
- Ability to browse external plugins in repositories. Official repository will be added soon.
Fixes/Refactor:
- Refactored Plugin-related settings to a separate "Plugin Settings" plugin.
- Masonry layout fixes in landscape.
- Updated plugins now show "reload to apply" toast instead.
- Removed dead code.
2026-07-25 23:35:23 +07:00
|
|
|
const PluginInstallConfirmAlertKey = 'plugin-install-confirm'
|
|
|
|
|
|
|
|
|
|
pEmitter.on('installReady', prompt => {
|
|
|
|
|
AlertActionCreators.dismissAlert(PluginInstallConfirmAlertKey)
|
|
|
|
|
AlertActionCreators.openAlert(
|
|
|
|
|
PluginInstallConfirmAlertKey,
|
|
|
|
|
<PluginInstallConfirmAlert prompt={prompt} />,
|
|
|
|
|
)
|
|
|
|
|
})
|
2026-07-07 23:12:40 +07:00
|
|
|
|
|
|
|
|
pEmitter.on('install', result => {
|
|
|
|
|
if (result.error !== false) {
|
|
|
|
|
AlertActionCreators.dismissAlert(PluginInstallFailedAlertKey)
|
|
|
|
|
AlertActionCreators.openAlert(
|
|
|
|
|
PluginInstallFailedAlertKey,
|
|
|
|
|
<PluginInstallFailedAlert error={result.error} />,
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
feat!: new repo system + versioning system support + bug fixes
BREAKING CHANGES:
- New versioning system: Allowing any amount of number segments, with prerelease support.
- Any number of segments is supported: 1, 1.0, 1.0.0, 1.0.0.0, ...
- Prerelease labels are compared "digit-run", similar to Debian.
- Plugins must specify a version.
- User-facing plugins should use CalVer. API plugins should use SemVer.
Revenge APIs use SemVer while the new `discord` dependency parses the Discord version string directly.
- Dependency is now a Map, with support for version ranges and optional. Suggested URL is removed. (See new repo system)
- Plugins will now have to declare a dependency on `revenge.api` (Revenge API version) and `discord` (Discord version).
- Manifest now requires a format version (currently `1`)
New APIs:
- lib/plugins/utils: Utilities to format versioning
Features:
- New repo system: Repository design is inspired by Linux package repositories.
- Repos host manifest, versions, channels, download URL/size, and hash for multiple plugins.
- New dependency system: Plugins are versioned, and shouldn't break when updates come.
Plugins can now declare dependency on supported Discord/Revenge API versions, so they won't break with auto-updates.
Dependencies can be optional, but dependency on `revenge.api` and `discord` must be required.
Dependents are soft-disabled when dependencies don't satisfy. Once satisfied, the plugin can start again.
- All updates for running plugins REQUIRE a reload to prevent stale linking issues. Both native and JS-side.
- Update checking is handled entirely by JS. But the native-side still handles installation, indexing, and verification.
- Local indexes are updated against remote indexes. Dependency checking always uses local indexes.
- Users can add custom repositories and custom their priority order. URL is the identity.
- Plugins can now have custom data URI icons instead of being restricted to React Native app assets.
- External plugin installs require UI confirmation before installing.
- Pending Update and Pending Reload filters in Plugins page.
- Ability to browse external plugins in repositories. Official repository will be added soon.
Fixes/Refactor:
- Refactored Plugin-related settings to a separate "Plugin Settings" plugin.
- Masonry layout fixes in landscape.
- Updated plugins now show "reload to apply" toast instead.
- Removed dead code.
2026-07-25 23:35:23 +07:00
|
|
|
if (result.pending) {
|
|
|
|
|
// Applied on disk only, the new version loads at next reload
|
|
|
|
|
showInstallToast(
|
|
|
|
|
`Downloaded ${pList.get(result.id)?.manifest.name || result.id} ${result.version}, reload to apply`,
|
|
|
|
|
result.id,
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-07 23:12:40 +07:00
|
|
|
showInstallToast(
|
|
|
|
|
result.updated
|
|
|
|
|
? `Updated ${result.manifest.name}`
|
|
|
|
|
: `Installed ${result.manifest.name}`,
|
|
|
|
|
result.manifest.id,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const CircleCheckIcon = lookupGeneratedIconComponent(
|
|
|
|
|
'CircleCheckIcon',
|
|
|
|
|
'CircleCheckIcon-secondary',
|
|
|
|
|
'CircleCheckIcon-primary',
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
function showInstallToast(content: string, id: string) {
|
|
|
|
|
ToastActionCreators.open({
|
|
|
|
|
key: pluginInstallToastKeyFor(id),
|
|
|
|
|
content,
|
|
|
|
|
IconComponent: CircleCheckIcon,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-23 00:55:24 +07:00
|
|
|
showErrorAlertIfNeeded()
|
2026-07-07 23:12:40 +07:00
|
|
|
showPendingReloadAlertIfNeeded()
|