216 lines
5.7 KiB
Markdown
216 lines
5.7 KiB
Markdown
|
|
# Writing Plugins
|
||
|
|
|
||
|
|
BetterCurseForge features a lightweight plugin system that allows you to extend CurseForge Launcher functionality, manipulate the DOM, hook runtime methods, and persist configuration.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Plugin Directory Structure
|
||
|
|
|
||
|
|
Plugins are stored in the BetterCurseForge user directory:
|
||
|
|
|
||
|
|
```text
|
||
|
|
%APPDATA%\CurseForge\BetterCurseForge\plugins\
|
||
|
|
```
|
||
|
|
|
||
|
|
Each plugin lives in its own dedicated subfolder:
|
||
|
|
|
||
|
|
```text
|
||
|
|
plugins/
|
||
|
|
└── compact-mode/
|
||
|
|
├── manifest.json # Required: Plugin metadata
|
||
|
|
├── index.js # Required: Entry script
|
||
|
|
└── styles.css # Optional: Companion stylesheet
|
||
|
|
```
|
||
|
|
|
||
|
|
You can open the plugins folder directly from the BetterCurseForge desktop GUI by clicking **Open Plugins Folder**.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Plugin Manifest (`manifest.json`)
|
||
|
|
|
||
|
|
The `manifest.json` file declares your plugin's metadata:
|
||
|
|
|
||
|
|
```json
|
||
|
|
{
|
||
|
|
"id": "compact-mode",
|
||
|
|
"name": "Compact Mode",
|
||
|
|
"version": "1.0.0",
|
||
|
|
"description": "Reduces padding across mod lists, sidebar items, and modpack details for high-density browsing.",
|
||
|
|
"author": "ModderDev",
|
||
|
|
"main": "index.js",
|
||
|
|
"styles": "styles.css"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Manifest Fields
|
||
|
|
|
||
|
|
| Field | Type | Required | Description |
|
||
|
|
| :--- | :--- | :--- | :--- |
|
||
|
|
| `name` | `string` | **Yes** | Display name shown in the management UI. |
|
||
|
|
| `version` | `string` | **Yes** | Semantic version string (e.g. `1.0.0`). |
|
||
|
|
| `description` | `string` | No | Brief summary of what the plugin accomplishes. |
|
||
|
|
| `author` | `string` | No | Author name or handle. |
|
||
|
|
| `main` | `string` | No | Entry JavaScript file. Defaults to `index.js`. |
|
||
|
|
| `styles` | `string` | No | Companion stylesheet injected automatically on enable. Defaults to `styles.css`. |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Plugin Lifecycle & Execution
|
||
|
|
|
||
|
|
Plugin scripts are executed when enabled, receiving the `BetterCurseForge` global API and a `plugin` context object:
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
// index.js
|
||
|
|
// BetterCurseForge passes (BetterCurseForge, plugin)
|
||
|
|
const { injectCSS, patcher, settings, on, off } = BetterCurseForge;
|
||
|
|
|
||
|
|
console.log('[MyPlugin] Plugin initialized!');
|
||
|
|
|
||
|
|
// Attach an onDisable cleanup handler:
|
||
|
|
plugin.onDisable = () => {
|
||
|
|
console.log('[MyPlugin] Plugin disabled! Cleaning up...');
|
||
|
|
// Remove any custom DOM elements, event listeners, or intervals here
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
::: tip AUTOMATIC STYLE INJECTION & REMOVAL
|
||
|
|
If your plugin folder contains a `styles.css` (or file declared in `"styles"` in `manifest.json`), BetterCurseForge automatically injects it into `<head>` when your plugin is enabled and removes it when disabled! You do not need to manually inject or clean up companion stylesheets.
|
||
|
|
:::
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## BetterCurseForge Plugin APIs
|
||
|
|
|
||
|
|
### 1. `BetterCurseForge.injectCSS(css)`
|
||
|
|
Injects custom CSS dynamically into `<head>`. Returns a cleanup function that unmounts the style tag:
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
const removeStyles = BetterCurseForge.injectCSS(`
|
||
|
|
.addon-row { padding: 4px 8px !important; }
|
||
|
|
`);
|
||
|
|
|
||
|
|
// Later in cleanup:
|
||
|
|
plugin.onDisable = () => {
|
||
|
|
removeStyles();
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 2. `BetterCurseForge.settings`
|
||
|
|
Stores and retrieves persistent settings saved to disk (`settings.json`):
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
// Retrieve a stored setting (or null if not set)
|
||
|
|
const isCustomFont = await BetterCurseForge.settings.get('compactMode.customFont');
|
||
|
|
|
||
|
|
// Store a setting
|
||
|
|
await BetterCurseForge.settings.set('compactMode.density', 'ultra');
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 3. `BetterCurseForge.patcher`
|
||
|
|
Safe method monkey-patching for objects and functions:
|
||
|
|
|
||
|
|
#### `patcher.before(object, methodName, callback)`
|
||
|
|
Runs your callback before the target method executes. Receives `(args)`:
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
const unpatch = BetterCurseForge.patcher.before(console, 'error', (args) => {
|
||
|
|
console.warn('[Intercepted Console Error]', ...args);
|
||
|
|
});
|
||
|
|
|
||
|
|
plugin.onDisable = () => unpatch();
|
||
|
|
```
|
||
|
|
|
||
|
|
#### `patcher.after(object, methodName, callback)`
|
||
|
|
Runs after the target method completes. Receives `(args, result)`. You can return a modified value:
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
const unpatch = BetterCurseForge.patcher.after(targetObj, 'getItemTitle', (args, result) => {
|
||
|
|
return `⚡ ${result}`;
|
||
|
|
});
|
||
|
|
|
||
|
|
plugin.onDisable = () => unpatch();
|
||
|
|
```
|
||
|
|
|
||
|
|
#### `patcher.instead(object, methodName, callback)`
|
||
|
|
Completely supersedes the target method. Receives `(args, originalMethod)`:
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
const unpatch = BetterCurseForge.patcher.instead(targetObj, 'fetchData', (args, originalFn) => {
|
||
|
|
console.log('Intercepting fetchData call...');
|
||
|
|
return originalFn(...args);
|
||
|
|
});
|
||
|
|
|
||
|
|
plugin.onDisable = () => unpatch();
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### 4. `BetterCurseForge.on(event, callback)` / `.off()` / `.emit()`
|
||
|
|
Application-wide event emitter:
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
const onReady = () => console.log('CurseForge UI is fully loaded!');
|
||
|
|
|
||
|
|
BetterCurseForge.on('ready', onReady);
|
||
|
|
|
||
|
|
plugin.onDisable = () => {
|
||
|
|
BetterCurseForge.off('ready', onReady);
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Complete Example: Compact Mode Plugin
|
||
|
|
|
||
|
|
This plugin adds a high-density layout mode to CurseForge.
|
||
|
|
|
||
|
|
::: code-group
|
||
|
|
|
||
|
|
```json [manifest.json]
|
||
|
|
{
|
||
|
|
"id": "compact-mode",
|
||
|
|
"name": "Compact Mode",
|
||
|
|
"version": "1.0.0",
|
||
|
|
"description": "Reduces margins and padding for high-density modpack browsing.",
|
||
|
|
"author": "BetterCurseForge Team",
|
||
|
|
"main": "index.js",
|
||
|
|
"styles": "styles.css"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
```javascript [index.js]
|
||
|
|
// Add compact layout class to the document root
|
||
|
|
document.documentElement.classList.add('bcf-compact-layout');
|
||
|
|
|
||
|
|
console.log('[Compact Mode] Activated');
|
||
|
|
|
||
|
|
// Clean up when toggled off
|
||
|
|
plugin.onDisable = () => {
|
||
|
|
document.documentElement.classList.remove('bcf-compact-layout');
|
||
|
|
console.log('[Compact Mode] Deactivated');
|
||
|
|
};
|
||
|
|
```
|
||
|
|
|
||
|
|
```css [styles.css]
|
||
|
|
/* Automatically injected when plugin is enabled */
|
||
|
|
.bcf-compact-layout .tile-view-item {
|
||
|
|
width: 150px !important;
|
||
|
|
height: 240px !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
.bcf-compact-layout .action-button-container {
|
||
|
|
height: 48px !important;
|
||
|
|
margin: 6px 0 !important;
|
||
|
|
}
|
||
|
|
|
||
|
|
.bcf-compact-layout .details-title {
|
||
|
|
font-size: 13px !important;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
:::
|