From a6156a928ee0bb2ed5079c2c38d2fb18c4a018c2 Mon Sep 17 00:00:00 2001
From: kmmiio99o <164557248+kmmiio99o@users.noreply.github.com>
Date: Sat, 13 Sep 2025 22:10:01 +0200
Subject: [PATCH] test
---
plugins/Last.fm/src/constants.ts | 39 ++-
plugins/Last.fm/src/index.tsx | 197 ++++++-------
plugins/Last.fm/src/manager.ts | 324 +++++++++++++---------
plugins/Last.fm/src/ui/pages/Settings.tsx | 314 +++++++++++----------
plugins/Last.fm/src/utils/activity.ts | 40 +--
plugins/Last.fm/src/utils/lastfm.ts | 198 +++++++++++--
6 files changed, 677 insertions(+), 435 deletions(-)
diff --git a/plugins/Last.fm/src/constants.ts b/plugins/Last.fm/src/constants.ts
index 199fc72..7fc897e 100644
--- a/plugins/Last.fm/src/constants.ts
+++ b/plugins/Last.fm/src/constants.ts
@@ -1,15 +1,32 @@
const Constants = {
- DEFAULT_APP_NAME: "Music",
- DEFAULT_TIME_INTERVAL: 5,
- /** The application ID of the Discord's developer app */
- APPLICATION_ID: "1054951789318909972",
- /** Last.fm api key */
- LFM_API_KEY: "014ffe8a614370f000d85d95ec30e1be",
- /** These are the default album covers that are used by Last.fm */
- LFM_DEFAULT_COVER_HASHES: [
- "2a96cbd8b46e442fc41c2b86b821562f",
- "c6f59c1e5e7240a4c0d427abd71f3dbb",
- ]
+ DEFAULT_APP_NAME: "Music",
+ DEFAULT_TIME_INTERVAL: 5,
+ /** The application ID of the Discord's developer app */
+ APPLICATION_ID: "1054951789318909972",
+ /** The minimum update interval in seconds to prevent rate limiting */
+ MIN_UPDATE_INTERVAL: 3,
+ /** Maximum retry attempts for API calls */
+ MAX_RETRY_ATTEMPTS: 3,
+ /** Retry delay in milliseconds */
+ RETRY_DELAY: 5000,
+ /** Base URL for Last.fm API */
+ LFM_API_BASE_URL: "https://ws.audioscrobbler.com/2.0",
+ /** These are the default album covers that are used by Last.fm */
+ LFM_DEFAULT_COVER_HASHES: [
+ "2a96cbd8b46e442fc41c2b86b821562f",
+ "c6f59c1e5e7240a4c0d427abd71f3dbb",
+ ],
+ /** Default settings */
+ DEFAULT_SETTINGS: {
+ username: "",
+ apiKey: "",
+ appName: "Music",
+ timeInterval: 5,
+ showTimestamp: true,
+ listeningTo: true,
+ ignoreSpotify: true,
+ verboseLogging: false,
+ },
} as const;
export default Constants;
diff --git a/plugins/Last.fm/src/index.tsx b/plugins/Last.fm/src/index.tsx
index 9ddf6dd..b92f2b0 100644
--- a/plugins/Last.fm/src/index.tsx
+++ b/plugins/Last.fm/src/index.tsx
@@ -1,121 +1,106 @@
import { plugin } from "@vendetta";
-import { FluxDispatcher, React } from "@vendetta/metro/common";
+import { FluxDispatcher } from "@vendetta/metro/common";
+import { getAssetIDByName } from "@vendetta/ui/assets";
+import { showToast } from "@vendetta/ui/toasts";
-import { lazy, Suspense } from "react";
-import { Activity, LFMSettings } from "../../defs";
-import { flush, initialize } from "./manager";
+import { lazy } from "react";
+import { LFMSettings } from "../../defs";
+import Constants from "./constants";
+import { initialize, stop } from "./manager";
import { UserStore } from "./modules";
-import { Forms } from "@vendetta/ui/components";
-import { ScrollView } from "react-native";
-const { FormText } = Forms;
+export const pluginState = {
+ pluginStopped: false,
+ lastActivity: undefined,
+ updateInterval: undefined,
+ lastTrackUrl: undefined,
+} as {
+ pluginStopped: boolean;
+ lastActivity?: any;
+ updateInterval?: NodeJS.Timer;
+ lastTrackUrl?: string;
+};
-// Error boundary component for settings
-function SettingsErrorBoundary({ children }: { children: React.ReactNode }) {
- const [hasError, setHasError] = React.useState(false);
-
- React.useEffect(() => {
- const errorHandler = (error: any) => {
- console.error("Settings component error:", error);
- setHasError(true);
- };
-
- return () => {
- setHasError(false);
- };
- }, []);
-
- if (hasError) {
- return (
-
-
- Failed to load Last.fm settings. Please try reloading the plugin.
-
-
- );
- }
-
- return (
-
- Loading Last.fm settings...
-
- }>
- {children}
-
- );
+// Initialize default settings
+const defaultSettings: LFMSettings = Constants.DEFAULT_SETTINGS;
+for (const key in defaultSettings) {
+ plugin.storage[key] =
+ plugin.storage[key] ?? defaultSettings[key as keyof typeof defaultSettings];
}
-// Lazy-loaded Settings component with error handling
-const LazySettings = lazy(() => {
- try {
- return import("./ui/pages/Settings").catch((error) => {
- console.error("Failed to load Settings component:", error);
- // Return a fallback component
- return {
- default: () => (
-
-
- Error: Could not load Last.fm settings component.
- {"\n"}Please check console for details.
-
-
- )
- };
- });
- } catch (error) {
- console.error("Critical error in lazy loading:", error);
- // Return fallback component for critical errors
- return Promise.resolve({
- default: () => (
-
-
- Critical error loading settings. Please restart the app.
-
-
- )
- });
- }
+export const currentSettings = new Proxy(plugin.storage, {
+ get(target, prop) {
+ return target[prop];
+ },
+ set(target, prop, value) {
+ target[prop] = value;
+ return true;
+ },
});
-// Wrapped settings component
-function WrappedSettings() {
- return (
-
-
-
- );
+const LazySettings = lazy(() => import("./ui/pages/Settings"));
+
+// Connection status tracking
+let connectionAttempts = 0;
+const MAX_CONNECTION_ATTEMPTS = 3;
+const RECONNECT_DELAY = 5000;
+
+async function tryInitialize() {
+ try {
+ await initialize();
+ connectionAttempts = 0;
+ } catch (error) {
+ console.error("[Last.fm] Initialization error:", error);
+ connectionAttempts++;
+
+ if (connectionAttempts < MAX_CONNECTION_ATTEMPTS) {
+ showToast("Retrying connection...", getAssetIDByName("ic_clock"));
+ setTimeout(tryInitialize, RECONNECT_DELAY);
+ } else {
+ showToast("Failed to connect to Last.fm", getAssetIDByName("Small"));
+ }
+ }
}
-export const pluginState = {} as {
- pluginStopped?: boolean,
- lastActivity?: Activity,
- updateInterval?: NodeJS.Timer,
- lastTrackUrl?: string,
-};
-
-plugin.storage.ignoreSpotify ??= true;
-export const currentSettings = { ...plugin.storage } as LFMSettings;
-
export default {
- settings: WrappedSettings,
- onLoad() {
- pluginState.pluginStopped = false;
+ settings: LazySettings,
+ onLoad() {
+ pluginState.pluginStopped = false;
- if (UserStore.getCurrentUser()) {
- initialize().catch(console.error);
- } else {
- const callback = () => {
- initialize().catch(console.error);
- FluxDispatcher.unsubscribe(callback);
- };
-
- FluxDispatcher.subscribe("CONNECTION_OPEN", callback);
- }
-
- },
- onUnload() {
- pluginState.pluginStopped = true;
- flush();
+ if (!currentSettings.username || !currentSettings.apiKey) {
+ showToast(
+ "Please configure Last.fm username and API key in settings",
+ getAssetIDByName("ic_warning"),
+ );
+ return;
}
+
+ if (UserStore.getCurrentUser()) {
+ tryInitialize();
+ } else {
+ const waitForUser = () => {
+ if (UserStore.getCurrentUser()) {
+ tryInitialize();
+ FluxDispatcher.unsubscribe("CONNECTION_OPEN", waitForUser);
+ }
+ };
+
+ FluxDispatcher.subscribe("CONNECTION_OPEN", waitForUser);
+ }
+ },
+ onUnload() {
+ pluginState.pluginStopped = true;
+ stop();
+ },
+ // Handle settings updates
+ onSettingsUpdate(newSettings: any) {
+ Object.assign(currentSettings, newSettings);
+ tryInitialize();
+ },
+ // Handle Discord reconnection
+ onDiscordReconnect() {
+ if (!pluginState.pluginStopped) {
+ tryInitialize();
+ }
+ },
};
diff --git a/plugins/Last.fm/src/manager.ts b/plugins/Last.fm/src/manager.ts
index 7281bd9..39ba9a1 100644
--- a/plugins/Last.fm/src/manager.ts
+++ b/plugins/Last.fm/src/manager.ts
@@ -7,157 +7,217 @@ import Constants from "./constants";
import { SelfPresenceStore } from "./modules";
import { clearActivity, fetchAsset, sendRequest } from "./utils/activity";
import { setDebugInfo } from "./utils/debug";
-import { fetchLatestScrobble } from "./utils/lastfm";
+import { lastfmClient } from "./utils/lastfm";
enum ActivityType {
- PLAYING = 0,
- STREAMING = 1,
- LISTENING = 2,
- COMPETING = 5
+ PLAYING = 0,
+ STREAMING = 1,
+ LISTENING = 2,
+ COMPETING = 5,
}
-const verboseLog = (...message: any) => currentSettings.verboseLogging && console.log(...message);
+const verboseLog = (...message: any[]) =>
+ currentSettings.verboseLogging && console.log("[Last.fm]", ...message);
-/**
- * Fetches the current scrobble and sets the activity.
- * This function is actively called by the updateInterval.
- */
-async function update() {
+class PluginManager {
+ private static instance: PluginManager;
+ private updateTimer?: NodeJS.Timer;
+ private reconnectTimer?: NodeJS.Timer;
+ private consecutiveFailures: number = 0;
+ private isReconnecting: boolean = false;
+
+ private constructor() {}
+
+ public static getInstance(): PluginManager {
+ if (!PluginManager.instance) {
+ PluginManager.instance = new PluginManager();
+ }
+ return PluginManager.instance;
+ }
+
+ /**
+ * Updates the Discord activity with the current Last.fm track
+ */
+ private async updateActivity() {
if (pluginState.pluginStopped) {
- verboseLog("--> Plugin is unloaded, aborting update()...");
- flush();
- return;
+ verboseLog("Plugin is stopped, skipping update");
+ this.stopUpdates();
+ return;
}
- verboseLog("--> Fetching last track...");
-
- if (!currentSettings.username) {
- showToast("Last.fm username is not set!", getAssetIDByName("Small"));
- flush(); // Flush as we always need reinitialization
- throw new Error("Username is not set");
- }
-
- if (currentSettings.ignoreSpotify) {
- const spotifyActivity = SelfPresenceStore.findActivity(act => act.sync_id);
- if (spotifyActivity) {
- verboseLog("--> Spotify is currently playing, aborting...");
- setDebugInfo("isSpotifyIgnored", true);
-
- clearActivity();
- return;
- } else setDebugInfo("isSpotifyIgnored", false);
- } else {
- setDebugInfo("isSpotifyIgnored", undefined);
- }
-
- const lastTrack = await fetchLatestScrobble().catch(async (err) => {
- verboseLog("--> An error occurred while fetching the last track, aborting...");
- clearActivity();
- throw err;
- });
-
- setDebugInfo("lastTrack", lastTrack);
-
- if (!lastTrack.nowPlaying) {
- verboseLog("--> Last track is not currently playing, aborting...");
- clearActivity();
- return;
- }
-
- verboseLog("--> Track fetched!");
-
- if (pluginState.lastTrackUrl === lastTrack.url) {
- verboseLog("--> Last track is the same as the previous one, aborting...");
- return;
- }
-
- const activity = {
- name: currentSettings.appName || Constants.DEFAULT_APP_NAME,
- flags: 0,
- type: currentSettings.listeningTo ? ActivityType.LISTENING : ActivityType.PLAYING,
- details: lastTrack.name,
- state: `${lastTrack.artist}`,
- status_display_type: 1,
- application_id: Constants.APPLICATION_ID,
- } as Activity;
-
- pluginState.lastTrackUrl = lastTrack.url;
-
- if (activity.name.includes("{{")) {
- for (const key in lastTrack) {
- activity.name = activity.name.replace(`{{${key}}}`, lastTrack[key]);
- }
- }
-
- // Set timestamps
- if (currentSettings.showTimestamp) {
- activity.timestamps = {
- start: Date.now()| 0
- };
- }
-
- if (lastTrack.album) {
- const asset = await fetchAsset([lastTrack.albumArt]);
-
- activity.assets = {
- large_image: asset[0],
- large_text: `${lastTrack.album}`
- };
- }
-
- verboseLog("--> Setting activity...");
- setDebugInfo("lastActivity", activity);
- verboseLog(activity);
+ verboseLog("Fetching last track...");
try {
- sendRequest(activity);
- } catch (err) {
- verboseLog("--> An error occurred while setting the activity");
+ if (!currentSettings.username || !currentSettings.apiKey) {
+ throw new Error("Username or API key not set");
+ }
+
+ if (currentSettings.ignoreSpotify) {
+ const spotifyActivity = SelfPresenceStore.findActivity(
+ (act) => act.sync_id,
+ );
+ if (spotifyActivity) {
+ verboseLog("Spotify is currently playing, clearing activity");
+ setDebugInfo("isSpotifyIgnored", true);
+ clearActivity();
+ return;
+ }
+ setDebugInfo("isSpotifyIgnored", false);
+ }
+
+ const lastTrack = await lastfmClient.fetchLatestScrobble();
+ setDebugInfo("lastTrack", lastTrack);
+
+ if (!lastTrack.nowPlaying) {
+ verboseLog("Last track is not currently playing");
clearActivity();
- throw err;
+ return;
+ }
+
+ if (pluginState.lastTrackUrl === lastTrack.url) {
+ verboseLog("Track hasn't changed");
+ return;
+ }
+
+ const activity: Activity = {
+ name: currentSettings.appName || Constants.DEFAULT_APP_NAME,
+ flags: 0,
+ type: currentSettings.listeningTo
+ ? ActivityType.LISTENING
+ : ActivityType.PLAYING,
+ details: lastTrack.name,
+ state: lastTrack.artist,
+ status_display_type: 1,
+ application_id: Constants.APPLICATION_ID,
+ };
+
+ // Handle dynamic app name
+ if (activity.name.includes("{{")) {
+ for (const key in lastTrack) {
+ activity.name = activity.name.replace(
+ `{{${key}}}`,
+ lastTrack[key as keyof typeof lastTrack] || "",
+ );
+ }
+ }
+
+ // Set timestamps if enabled
+ if (currentSettings.showTimestamp) {
+ activity.timestamps = {
+ start: Date.now() | 0,
+ };
+ }
+
+ // Handle album art
+ if (lastTrack.album) {
+ const asset = await fetchAsset([lastTrack.albumArt]);
+ if (asset[0]) {
+ activity.assets = {
+ large_image: asset[0],
+ large_text: lastTrack.album,
+ };
+ }
+ }
+
+ verboseLog("Setting activity:", activity);
+ setDebugInfo("lastActivity", activity);
+
+ await sendRequest(activity);
+ pluginState.lastTrackUrl = lastTrack.url;
+ pluginState.lastActivity = activity;
+ this.consecutiveFailures = 0;
+
+ verboseLog("Activity set successfully!");
+ } catch (error) {
+ console.error("[Last.fm] Update error:", error);
+ this.handleError(error);
}
+ }
- verboseLog("--> Successfully set activity!");
-}
+ private handleError(error: Error) {
+ this.consecutiveFailures++;
+ setDebugInfo("lastError", error);
-/** Stops and reset everything, can be started again with `initialize()` */
-export function flush(isClearActivity = false) {
- pluginState.lastActivity = null;
- pluginState.lastTrackUrl = null;
+ if (this.consecutiveFailures >= Constants.MAX_RETRY_ATTEMPTS) {
+ verboseLog(
+ `Failed ${this.consecutiveFailures} times, initiating reconnection...`,
+ );
+ this.startReconnection();
+ }
+ }
- pluginState.updateInterval && clearInterval(pluginState.updateInterval);
- !isClearActivity && clearActivity();
-}
+ private startReconnection() {
+ if (this.isReconnecting) return;
-/** Initializes the plugin */
-export async function initialize() {
+ this.isReconnecting = true;
+ this.stopUpdates();
+
+ verboseLog("Starting reconnection process");
+ this.reconnectTimer = setInterval(() => {
+ verboseLog("Attempting to reconnect...");
+ this.initialize()
+ .then(() => {
+ verboseLog("Reconnection successful");
+ this.stopReconnection();
+ })
+ .catch((error) => {
+ verboseLog("Reconnection failed:", error);
+ });
+ }, Constants.RETRY_DELAY);
+ }
+
+ private stopReconnection() {
+ if (this.reconnectTimer) {
+ clearInterval(this.reconnectTimer);
+ this.reconnectTimer = undefined;
+ }
+ this.isReconnecting = false;
+ this.consecutiveFailures = 0;
+ }
+
+ /**
+ * Stops all update and reconnection timers
+ */
+ private stopUpdates() {
+ if (this.updateTimer) {
+ clearInterval(this.updateTimer);
+ this.updateTimer = undefined;
+ }
+ }
+
+ /**
+ * Initializes the plugin and starts the update loop
+ */
+ public async initialize() {
if (pluginState.pluginStopped) {
- throw new Error("Plugin is already stopped!");
+ throw new Error("Plugin is stopped");
}
- flush();
+ this.stopUpdates();
+ await this.updateActivity();
- let tries = 0;
-
- await update().catch((err) => {
- console.error(err);
- tries++;
- });
-
- // Periodically fetches the current scrobble and sets the activity
- pluginState.updateInterval = setInterval(
- () => update()
- .then(() => {
- tries = 0;
- })
- .catch(err => {
- console.error(err);
-
- if (++tries > 3) {
- console.error("Failed to fetch/set activity 3 times, aborting...");
- flush();
- }
- }),
- (Number(currentSettings.timeInterval) || Constants.DEFAULT_TIME_INTERVAL) * 1000
+ const interval = Math.max(
+ (Number(currentSettings.timeInterval) ||
+ Constants.DEFAULT_SETTINGS.timeInterval) * 1000,
+ Constants.MIN_UPDATE_INTERVAL * 1000,
);
+
+ this.updateTimer = setInterval(() => this.updateActivity(), interval);
+ verboseLog(`Update timer started with interval: ${interval}ms`);
+ }
+
+ /**
+ * Stops the plugin and cleans up
+ */
+ public stop() {
+ pluginState.pluginStopped = true;
+ this.stopUpdates();
+ this.stopReconnection();
+ clearActivity();
+ }
}
+
+// Export singleton instance methods
+const manager = PluginManager.getInstance();
+export const initialize = () => manager.initialize();
+export const stop = () => manager.stop();
diff --git a/plugins/Last.fm/src/ui/pages/Settings.tsx b/plugins/Last.fm/src/ui/pages/Settings.tsx
index bf62a28..e67ed9b 100644
--- a/plugins/Last.fm/src/ui/pages/Settings.tsx
+++ b/plugins/Last.fm/src/ui/pages/Settings.tsx
@@ -1,162 +1,192 @@
-import { NavigationNative, React } from "@vendetta/metro/common";
-import { storage } from "@vendetta/plugin";
+import { plugin } from "@vendetta";
+import { Forms } from "@vendetta/ui/components";
import { useProxy } from "@vendetta/storage";
import { getAssetIDByName } from "@vendetta/ui/assets";
-import { Forms } from "@vendetta/ui/components";
import { showToast } from "@vendetta/ui/toasts";
+import { React } from "@vendetta/metro/common";
+import { ScrollView } from "react-native";
-import { lazy, useEffect } from "react";
-import { ScrollView, Text, TouchableOpacity } from "react-native";
-import { currentSettings } from "../..";
-import { LFMSettings } from "../../../../defs";
+import { currentSettings, pluginState } from "../..";
import Constants from "../../constants";
import { initialize } from "../../manager";
-import {
- logComponentMount,
- logComponentError,
- logNavigationError,
- incrementSettingsLoad,
-} from "../../utils/debug";
+import { lastfmClient } from "../../utils/lastfm";
-const { FormRow, FormInput, FormDivider, FormSwitchRow, FormText, FormIcon } =
+const { FormInput, FormRow, FormSwitch, FormSection, FormDivider, FormText } =
Forms;
-function UpdateButton() {
- async function onPressCallback() {
- try {
- incrementSettingsLoad();
- for (const key in storage) {
- if (storage[key] != null) {
- currentSettings[key] = storage[key];
- }
- }
+export default function Settings() {
+ useProxy(currentSettings);
+ const [loading, setLoading] = React.useState(false);
+
+ const saveSettings = (key: string, value: any) => {
+ currentSettings[key] = value;
+ plugin.storage[key] = value;
+ };
+
+ const testConnection = async () => {
+ if (!currentSettings.username || !currentSettings.apiKey) {
+ showToast(
+ "Please enter both username and API key",
+ getAssetIDByName("Small"),
+ );
+ return;
+ }
+
+ setLoading(true);
+ try {
+ await lastfmClient.fetchLatestScrobble();
+ showToast("Connection successful!", getAssetIDByName("Check"));
+
+ // Reinitialize the plugin with new settings
await initialize();
- showToast("Settings updated!", getAssetIDByName("Check"));
} catch (error) {
- logComponentError("UpdateButton", error);
- showToast("Failed to update settings", getAssetIDByName("Small"));
+ showToast(
+ `Connection failed: ${error.message}`,
+ getAssetIDByName("Small"),
+ );
+ } finally {
+ setLoading(false);
}
- }
+ };
return (
-
- UPDATE
-
- );
-}
+
+
+ saveSettings("username", value)}
+ />
+
+ saveSettings("apiKey", value)}
+ secureTextEntry={true}
+ />
+
+ Get your API key at: https://www.last.fm/api/account/create
+
+ }
+ trailing={loading ? : }
+ onPress={testConnection}
+ disabled={loading}
+ />
+
-export default React.memo(function Settings() {
- const settings = useProxy(storage) as LFMSettings;
- const navigation = NavigationNative.useNavigation();
+
+ saveSettings("appName", value)}
+ />
+
+ {
+ const interval = Number(value);
+ if (interval >= Constants.MIN_UPDATE_INTERVAL) {
+ saveSettings("timeInterval", interval);
+ } else {
+ showToast(
+ `Minimum interval is ${Constants.MIN_UPDATE_INTERVAL} seconds`,
+ getAssetIDByName("Small"),
+ );
+ }
+ }}
+ keyboardType="numeric"
+ />
+
- // Log component mount
- useEffect(() => {
- logComponentMount("Settings");
- }, []);
-
- // Safety check to ensure settings is properly initialized
- if (!settings) {
- logComponentError("Settings", "Settings proxy is null or undefined");
- return (
-
- Loading...
-
- );
- }
-
- useEffect(() => {
- try {
- navigation.setOptions({
- title: "Last.fm Configuration",
- headerRight: () => ,
- });
- } catch (error) {
- logComponentError("Settings-Navigation", error);
- }
- }, []);
-
- return (
-
- (settings.appName = value.trim())}
- title="Discord Application Name"
- placeholder={Constants.DEFAULT_APP_NAME}
- returnKeyType="done"
- />
-
- (settings.username = value.trim())}
- title="Last.fm username"
- helpText={
- !settings.username && (
-
- {"This field is required!"}
-
- )
- }
- placeholder="wumpus123"
- returnKeyType="done"
- />
-
- (settings.timeInterval = value)}
- title="Update interval (in seconds)"
- placeholder={Constants.DEFAULT_TIME_INTERVAL.toString()}
- keyboardType="numeric"
- returnKeyType="done"
- />
-
- }
- value={settings.showTimestamp}
- onValueChange={(value: boolean) => (settings.showTimestamp = value)}
- />
-
- }
- value={settings.listeningTo}
- onValueChange={(value: boolean) => (settings.listeningTo = value)}
- />
-
-
- }
- value={settings.ignoreSpotify}
- onValueChange={(value: boolean) => (settings.ignoreSpotify = value)}
- />
-
- }
- trailing={FormRow.Arrow}
- onPress={() => {
- try {
- navigation.push("VendettaCustomPage", {
- title: "Debug",
- render: lazy(() => import("./Debug")),
- });
- } catch (error) {
- logNavigationError(error);
- console.error("Failed to navigate to Debug page:", error);
- showToast("Failed to open debug page", getAssetIDByName("Small"));
+
+ }
+ trailing={
+
+ saveSettings("listeningTo", value)
+ }
+ />
}
- }}
- />
+ />
+
+ }
+ trailing={
+
+ saveSettings("showTimestamp", value)
+ }
+ />
+ }
+ />
+
+
+ }
+ trailing={
+
+ saveSettings("ignoreSpotify", value)
+ }
+ />
+ }
+ />
+
+
+ }
+ trailing={
+
+ saveSettings("verboseLogging", value)
+ }
+ />
+ }
+ />
+
+
+
+
+ Status: {pluginState.pluginStopped ? "Stopped" : "Running"}
+ {"\n"}
+ Last Update: {pluginState.lastTrackUrl ? "Success" : "No track data"}
+
+
+
+
+ Version 2.0.0
+
);
-});
+}
diff --git a/plugins/Last.fm/src/utils/activity.ts b/plugins/Last.fm/src/utils/activity.ts
index 786bf08..96ed16d 100644
--- a/plugins/Last.fm/src/utils/activity.ts
+++ b/plugins/Last.fm/src/utils/activity.ts
@@ -3,34 +3,42 @@ import { FluxDispatcher } from "@vendetta/metro/common";
import { pluginState } from "..";
import { Activity } from "../../../defs";
import Constants from "../constants";
-import { flush } from "../manager";
+import { stop } from "../manager";
import { AssetManager } from "../modules";
/** Clears the user's activity */
export function clearActivity() {
- return sendRequest(null);
+ return sendRequest(null);
}
/** Sends the activity details to Discord */
-export function sendRequest(activity: Activity) {
- if (pluginState.pluginStopped) {
- flush(true);
- activity = null;
- }
+export function sendRequest(activity: Activity | null) {
+ if (pluginState.pluginStopped) {
+ stop();
+ activity = null;
+ }
- pluginState.lastActivity = activity;
+ pluginState.lastActivity = activity;
- FluxDispatcher.dispatch({
- type: "LOCAL_ACTIVITY_UPDATE",
- activity: activity,
- pid: 2312,
- socketId: "Last.fm@Vendetta"
- });
+ FluxDispatcher.dispatch({
+ type: "LOCAL_ACTIVITY_UPDATE",
+ activity: activity,
+ pid: 2312,
+ socketId: "Last.fm@Vendetta",
+ });
}
/** Fetches a Discord application's asset */
-export async function fetchAsset(asset: string[], appId: string = Constants.APPLICATION_ID): Promise {
- if (!asset) return [];
+export async function fetchAsset(
+ asset: string[],
+ appId: string = Constants.APPLICATION_ID,
+): Promise {
+ if (!asset?.length) return [];
+ try {
return await AssetManager.fetchAssetIds(appId, asset);
+ } catch (error) {
+ console.error("[Last.fm] Failed to fetch assets:", error);
+ return [];
+ }
}
diff --git a/plugins/Last.fm/src/utils/lastfm.ts b/plugins/Last.fm/src/utils/lastfm.ts
index f46afc1..9fde87d 100644
--- a/plugins/Last.fm/src/utils/lastfm.ts
+++ b/plugins/Last.fm/src/utils/lastfm.ts
@@ -2,48 +2,190 @@ import { currentSettings } from "..";
import { Track } from "../../../defs";
import Constants from "../constants";
import { setDebugInfo } from "./debug";
+import { showToast } from "@vendetta/ui/toasts";
+import { getAssetIDByName } from "@vendetta/ui/assets";
-/** Fetches the latest user's scrobble */
-export async function fetchLatestScrobble(): Promise