Change package manager to bun and etc

- give scripts their own folder - use bun as a package manager over pnpm
- fix some errors in multi-scrobbler
This commit is contained in:
kmmiio99o 2025-11-11 00:55:22 +01:00
parent 7c810e127b
commit c46b6a165f
10 changed files with 1609 additions and 232 deletions

View file

@ -1,49 +1,39 @@
name: Build and deploy name: Build and deploy
on: on:
push: push:
branches: [main] branches: [main]
jobs: jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: write contents: write
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Prepare environment - name: Setup Bun
uses: actions/setup-node@v3 uses: oven-sh/setup-bun@v2
with: with:
node-version: "18" bun-version: "latest"
- run: npm install --global pnpm - name: Install dependencies
run: bun install --frozen-lockfile
- name: Install deps - name: Build plugin(s)
run: pnpm m i run: bun run ./build.mjs
- name: Build plugin(s) - name: Copy additional files
run: node ./build.mjs run: |
cp README.md dist/README.md
printf -- "---\npermalink: /404.html\n---\n" > dist/404.md
printf -- "> **Note:** You accessed a link that returned a 404, probably by clicking one of the plugin links. You're supposed to copy the link address and add it into Vendetta.\n\n" >> dist/404.md
cat README.md >> dist/404.md
# Foolproof feature: - name: Deploy to GitHub Pages
# - Copies over README so that the root of the deployed website shows it uses: peaceiris/actions-gh-pages@v3
# - Changes 404 page to README so that you don't get lost while clicking links with:
# If you remove this step then you should probably remove the enable_jekyll option in the next one github_token: ${{ secrets.GITHUB_TOKEN }}
- name: Copy additional files publish_dir: ./dist
run: | enable_jekyll: true
cp README.md dist/README.md
printf -- "---\npermalink: /404.html\n---\n" > dist/404.md
printf -- "> **Note:** You accessed a link that returned a 404, probably by clicking one of the plugin links. You're supposed to copy the link address and add it into Vendetta.\n\n" >> dist/404.md
cat README.md >> dist/404.md
# Documentation: https://github.com/peaceiris/actions-gh-pages
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
# Makes it so the md files in the previous step get processed by GitHub Pages
enable_jekyll: true
# This creates the CNAME file required to host GitHub Pages on a custom domain
# cname: example.com

1378
bun.lock Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,25 +1,25 @@
{ {
"scripts": { "scripts": {
"build": "node build.mjs", "build": "node scripts/build.mjs",
"watch": "node build.mjs -w", "watch": "node scripts/build.mjs -w",
"lint": "eslint plugins", "lint": "eslint plugins",
"make": "node make.mjs", "make": "node make.mjs",
"serve": "node serve.mjs" "serve": "node scripts/serve.mjs"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^28.0.6", "@rollup/plugin-commonjs": "^29.0.0",
"@rollup/plugin-node-resolve": "^16.0.2", "@rollup/plugin-node-resolve": "^16.0.3",
"@swc/core": "^1.13.5", "@swc/core": "^1.15.1",
"@types/react": "^18.0.37", "@types/react": "^19.2.2",
"@types/react-native": "^0.70.13", "@types/react-native": "^0.70.13",
"@typescript-eslint/eslint-plugin": "^8.45.0", "@typescript-eslint/eslint-plugin": "^8.46.4",
"@typescript-eslint/parser": "^8.45.0", "@typescript-eslint/parser": "^8.46.4",
"esbuild": "^0.25.10", "esbuild": "^0.27.0",
"eslint": "^9.37.0", "eslint": "^9.39.1",
"eslint-plugin-react": "^7.32.2", "eslint-plugin-react": "^7.37.5",
"rollup": "^4.24.4", "rollup": "^4.53.2",
"rollup-plugin-esbuild": "^6.1.1", "rollup-plugin-esbuild": "^6.2.1",
"typescript": "^5.7.2", "typescript": "^5.9.3",
"vendetta-types": "^2.4.21" "vendetta-types": "^2.4.21"
}, },
"dependencies": { "dependencies": {
@ -27,6 +27,7 @@
"serve": "^14.2.5" "serve": "^14.2.5"
}, },
"overrides": { "overrides": {
"@types/react": "^18.0.37" "@types/react": "^19.2.2"
} },
"packageManager": "bun@1.3.2"
} }

24
plugins/defs.d.ts vendored
View file

@ -30,21 +30,25 @@ export type ActivityAssets = {
export type ServiceType = "lastfm" | "librefm" | "listenbrainz"; export type ServiceType = "lastfm" | "librefm" | "listenbrainz";
export type LFMSettings = { export type LFMSettings = {
appName: string;
username: string;
apiKey: string;
showTimestamp: boolean;
timeInterval: number | string;
listeningTo: boolean;
ignoreSpotify: boolean;
ignoreYouTubeMusic: boolean;
verboseLogging: boolean;
service: ServiceType | undefined;
// Service-specific settings // Service-specific settings
librefmUsername?: string; librefmUsername?: string;
librefmApiKey?: string; librefmApiKey?: string;
listenbrainzUsername?: string; listenbrainzUsername?: string;
listenbrainzToken?: string; listenbrainzToken?: string;
appName: string;
username: string;
apiKey: string;
// Display settings
showTimestamp: boolean;
timeInterval: number | string;
listeningTo: boolean;
ignoreSpotify: boolean;
showLargeText: boolean;
ignoreYouTubeMusic: boolean;
// Verbose logging settings
verboseLogging: boolean;
// Main page
service: ServiceType | undefined;
}; };
export type Track = { export type Track = {

View file

@ -11,10 +11,10 @@ import Settings from "./ui/pages/Settings";
import patchSidebar from "./sidebar"; import patchSidebar from "./sidebar";
export const pluginState = { export const pluginState = {
pluginStopped: false, pluginStopped: false,
lastActivity: undefined, lastActivity: undefined,
updateInterval: undefined, updateInterval: undefined,
lastTrackUrl: undefined, lastTrackUrl: undefined,
} as { } as {
pluginStopped: boolean; pluginStopped: boolean;
lastActivity?: any; lastActivity?: any;
@ -28,20 +28,20 @@ let sidebarUnpatch: (() => void) | undefined;
// Set up default plugin settings // Set up default plugin settings
const defaultSettings: LFMSettings = Constants.DEFAULT_SETTINGS; const defaultSettings: LFMSettings = Constants.DEFAULT_SETTINGS;
for (const key of Object.keys(defaultSettings)) { for (const key of Object.keys(defaultSettings)) {
plugin.storage[key] = plugin.storage[key] =
plugin.storage[key] ?? defaultSettings[key as keyof typeof defaultSettings]; plugin.storage[key] ?? defaultSettings[key as keyof typeof defaultSettings];
} }
plugin.storage.addToSidebar ??= false; plugin.storage.addToSidebar ??= false;
export const currentSettings = new Proxy(plugin.storage, { export const currentSettings = new Proxy(plugin.storage, {
get(target, prop) { get(target, prop) {
return target[prop]; return target[prop];
}, },
set(target, prop, value) { set(target, prop, value) {
target[prop] = value; target[prop] = value;
return true; return true;
}, },
}); });
// Keep track of connection attempts // Keep track of connection attempts
@ -51,182 +51,182 @@ const RECONNECT_DELAY = 5000;
const log = (...args: any[]) => console.log("[Scrobble Plugin]", ...args); const log = (...args: any[]) => console.log("[Scrobble Plugin]", ...args);
const logError = (...args: any[]) => const logError = (...args: any[]) =>
console.error("[Scrobble Plugin] Error:", ...args); console.error("[Scrobble Plugin] Error:", ...args);
async function tryInitialize() { async function tryInitialize() {
try { try {
await initialize(); await initialize();
connectionAttempts = 0; connectionAttempts = 0;
log("Plugin initialized successfully"); log("Plugin initialized successfully");
} catch (error) { } catch (error) {
logError("Initialization failed:", error); logError("Initialization failed:", error);
connectionAttempts++; connectionAttempts++;
if (connectionAttempts < MAX_CONNECTION_ATTEMPTS) { if (connectionAttempts < MAX_CONNECTION_ATTEMPTS) {
log( log(
`Retrying connection (${connectionAttempts}/${MAX_CONNECTION_ATTEMPTS})...`, `Retrying connection (${connectionAttempts}/${MAX_CONNECTION_ATTEMPTS})...`,
); );
setTimeout(tryInitialize, RECONNECT_DELAY); setTimeout(tryInitialize, RECONNECT_DELAY);
} else { } else {
logError(`Failed to connect after ${MAX_CONNECTION_ATTEMPTS} attempts`); logError(`Failed to connect after ${MAX_CONNECTION_ATTEMPTS} attempts`);
}
} }
}
} }
async function validateAndInitialize() { async function validateAndInitialize() {
// Check if user has selected a service first // Check if user has selected a service first
if (!currentSettings.service) { if (!currentSettings.service) {
log("No service selected. Please configure a service in plugin settings."); log("No service selected. Please configure a service in plugin settings.");
return; return;
} }
const serviceName = serviceFactory.getCurrentService().getServiceName(); const serviceName = serviceFactory.getCurrentService().getServiceName();
// Make sure we have credentials for whatever service is selected // Make sure we have credentials for whatever service is selected
const service = currentSettings.service; const service = currentSettings.service;
let hasCredentials = false; let hasCredentials = false;
switch (service) { switch (service) {
case "lastfm": case "lastfm":
hasCredentials = !!(currentSettings.username && currentSettings.apiKey); hasCredentials = !!(currentSettings.username && currentSettings.apiKey);
break; break;
case "librefm": case "librefm":
hasCredentials = !!( hasCredentials = !!(
currentSettings.librefmUsername && currentSettings.librefmApiKey currentSettings.librefmUsername && currentSettings.librefmApiKey
); );
break; break;
case "listenbrainz": case "listenbrainz":
hasCredentials = !!currentSettings.listenbrainzUsername; hasCredentials = !!currentSettings.listenbrainzUsername;
break; break;
} }
if (!hasCredentials) { if (!hasCredentials) {
logError( logError(
`Missing credentials for ${serviceName}. Please configure in plugin settings.`, `Missing credentials for ${serviceName}. Please configure in plugin settings.`,
); );
return; return;
} }
log(`Starting with ${serviceName}...`); log(`Starting with ${serviceName}...`);
if (UserStore.getCurrentUser()) { if (UserStore.getCurrentUser()) {
log("Discord is already connected, initializing immediately..."); log("Discord is already connected, initializing immediately...");
await tryInitialize(); await tryInitialize();
} else { } else {
log("Waiting for Discord connection..."); log("Waiting for Discord connection...");
const waitForUser = () => { const waitForUser = () => {
if (UserStore.getCurrentUser()) { if (UserStore.getCurrentUser()) {
log("Discord connection established"); log("Discord connection established");
tryInitialize(); tryInitialize();
FluxDispatcher.unsubscribe("CONNECTION_OPEN", waitForUser); FluxDispatcher.unsubscribe("CONNECTION_OPEN", waitForUser);
} }
}; };
FluxDispatcher.subscribe("CONNECTION_OPEN", waitForUser); FluxDispatcher.subscribe("CONNECTION_OPEN", waitForUser);
} }
} }
export default { export default {
onLoad() { onLoad() {
log("Plugin loading..."); log("Plugin loading...");
pluginState.pluginStopped = false; pluginState.pluginStopped = false;
// Check if service is configured // Check if service is configured
if (!currentSettings.service) { if (!currentSettings.service) {
log("No service configured. Please select a service in plugin settings."); log("No service configured. Please select a service in plugin settings.");
} else { } else {
// Show what we're starting with // Show what we're starting with
const serviceName = serviceFactory.getCurrentService().getServiceName(); const serviceName = serviceFactory.getCurrentService().getServiceName();
log( log(
`Configuration: Service=${serviceName}, Update Interval=${currentSettings.timeInterval}s, Verbose=${currentSettings.verboseLogging}`, `Configuration: Service=${serviceName}, Update Interval=${currentSettings.timeInterval}s, Verbose=${currentSettings.verboseLogging}`,
); );
}
// Add to sidebar if user wants it
try {
sidebarUnpatch = patchSidebar();
cleanupFunctions.push(() => {
if (sidebarUnpatch) {
sidebarUnpatch();
sidebarUnpatch = undefined;
} }
});
} catch (error) {
log("Sidebar setup failed:", error);
}
// Add to sidebar if user wants it validateAndInitialize();
},
onUnload() {
log("Plugin unloading...");
pluginState.pluginStopped = true;
// Run all cleanup functions
cleanupFunctions.forEach((cleanup) => {
try {
cleanup();
} catch (error) {
// ignore cleanup errors
}
});
cleanupFunctions.length = 0;
stop();
log("Plugin unloaded");
},
// When user changes settings
async onSettingsUpdate(newSettings: any) {
const oldService = currentSettings.service;
const newService = newSettings.service;
const oldSidebar = currentSettings.addToSidebar;
const newSidebar = newSettings.addToSidebar;
// Apply the new settings
Object.assign(currentSettings, newSettings);
log("Settings updated:", Object.keys(newSettings));
// Check if sidebar setting changed
if (oldSidebar !== newSidebar) {
if (newSidebar) {
try { try {
sidebarUnpatch = patchSidebar(); sidebarUnpatch = patchSidebar();
cleanupFunctions.push(() => { log("Sidebar enabled");
if (sidebarUnpatch) {
sidebarUnpatch();
sidebarUnpatch = undefined;
}
});
} catch (error) { } catch (error) {
log("Sidebar setup failed:", error); log("Failed to enable sidebar:", error);
} }
} else {
validateAndInitialize(); if (sidebarUnpatch) {
}, sidebarUnpatch();
sidebarUnpatch = undefined;
onUnload() { log("Sidebar disabled");
log("Plugin unloading...");
pluginState.pluginStopped = true;
// Run all cleanup functions
cleanupFunctions.forEach((cleanup) => {
try {
cleanup();
} catch (error) {
// ignore cleanup errors
}
});
cleanupFunctions.length = 0;
stop();
log("Plugin unloaded");
},
// When user changes settings
async onSettingsUpdate(newSettings: any) {
const oldService = currentSettings.service;
const newService = newSettings.service;
const oldSidebar = currentSettings.addToSidebar;
const newSidebar = newSettings.addToSidebar;
// Apply the new settings
Object.assign(currentSettings, newSettings);
log("Settings updated:", Object.keys(newSettings));
// Check if sidebar setting changed
if (oldSidebar !== newSidebar) {
if (newSidebar) {
try {
sidebarUnpatch = patchSidebar();
log("Sidebar enabled");
} catch (error) {
log("Failed to enable sidebar:", error);
}
} else {
if (sidebarUnpatch) {
sidebarUnpatch();
sidebarUnpatch = undefined;
log("Sidebar disabled");
}
}
} }
}
}
// Switch services if needed // Switch services if needed
if (oldService !== newService && newService) { if (oldService !== newService && newService) {
log(`Service changed from ${oldService || "none"} to ${newService}`); log(`Service changed from ${oldService || "none"} to ${newService}`);
await switchService(newService); await switchService(newService);
} else if (!pluginState.pluginStopped && currentSettings.service) { } else if (!pluginState.pluginStopped && currentSettings.service) {
// Just restart with the updated settings (only if service is selected) // Just restart with the updated settings (only if service is selected)
log("Restarting with updated settings..."); log("Restarting with updated settings...");
await tryInitialize(); await tryInitialize();
} else if (!currentSettings.service) { } else if (!currentSettings.service) {
// Service was unselected, stop the plugin // Service was unselected, stop the plugin
log("Service unselected, stopping plugin..."); log("Service unselected, stopping plugin...");
stop(); stop();
} }
}, },
onDiscordReconnect() { onDiscordReconnect() {
if (!pluginState.pluginStopped) { if (!pluginState.pluginStopped) {
log("Discord reconnected, reinitializing plugin..."); log("Discord reconnected, reinitializing plugin...");
tryInitialize(); tryInitialize();
} }
}, },
settings: Settings, settings: Settings,
}; };

View file

@ -10,8 +10,12 @@ import Settings from "./ui/pages/Settings";
const { FormSection, FormRow } = Forms; const { FormSection, FormRow } = Forms;
const { TableRowIcon } = findByProps("TableRowIcon"); const { TableRowIcon } = findByProps("TableRowIcon");
const bunny = window.bunny; const bunny = window.bunny;
declare global {
interface Window {
bunny: any;
}
}
const tabsNavigationRef = bunny?.metro?.findByPropsLazy("getRootNavigationRef"); const tabsNavigationRef = bunny?.metro?.findByPropsLazy("getRootNavigationRef");
const settingConstants = bunny?.metro?.findByPropsLazy( const settingConstants = bunny?.metro?.findByPropsLazy(
@ -141,8 +145,8 @@ function patchTabsUI(tabs, patches) {
patches.push( patches.push(
after("default", SettingsOverviewScreen, (args, ret) => { after("default", SettingsOverviewScreen, (args, ret) => {
if (!args[0][firstRender]) { if (!(args[0] as { [key: symbol]: boolean })[firstRender]) {
args[0][firstRender] = true; (args[0] as { [key: symbol]: boolean })[firstRender] = true;
const { sections } = findInReactTree( const { sections } = findInReactTree(
ret, ret,

View file

@ -45,7 +45,7 @@ export function setDebugInfo(
key: keyof typeof debugInfo, key: keyof typeof debugInfo,
value: (typeof debugInfo)[typeof key], value: (typeof debugInfo)[typeof key],
) { ) {
debugInfo[key] = value; (debugInfo[key] as any) = value;
// log the important stuff // log the important stuff
if (key === "lastError" && value) { if (key === "lastError" && value) {