From 84ef65fe0411256c4432c81d5cf05bc3862c929e Mon Sep 17 00:00:00 2001 From: amsyarasyiq <82711525+amsyarasyiq@users.noreply.github.com> Date: Sun, 9 Jul 2023 08:50:00 +0800 Subject: [PATCH] Last.fm: project restructure --- plugins/Last.fm/src/index.ts | 8 +- plugins/Last.fm/src/{utils.ts => manager.ts} | 102 +++---------------- plugins/Last.fm/src/pages/Settings.tsx | 7 +- plugins/Last.fm/src/utils/activity.ts | 40 ++++++++ plugins/Last.fm/src/utils/lastfm.ts | 47 +++++++++ 5 files changed, 107 insertions(+), 97 deletions(-) rename plugins/Last.fm/src/{utils.ts => manager.ts} (55%) create mode 100644 plugins/Last.fm/src/utils/activity.ts create mode 100644 plugins/Last.fm/src/utils/lastfm.ts diff --git a/plugins/Last.fm/src/index.ts b/plugins/Last.fm/src/index.ts index f3958ae..06805eb 100644 --- a/plugins/Last.fm/src/index.ts +++ b/plugins/Last.fm/src/index.ts @@ -2,11 +2,11 @@ import { plugin } from "@vendetta"; import { FluxDispatcher } from "@vendetta/metro/common"; import { Activity, LFMSettings } from "../../defs"; +import { flush, initialize } from "./manager"; import { UserStore } from "./modules"; import Settings from "./pages/Settings"; -import { flush, initialize } from "./utils"; -export const global = {} as { +export const pluginState = {} as { pluginStopped?: boolean, lastActivity?: Activity, updateInterval?: NodeJS.Timer, @@ -22,7 +22,7 @@ export const verboseLog = (...message: any) => currentSettings.verboseLogging && export default new class LastFM { onLoad() { console.log("Starting last.fm plugin.."); - global.pluginStopped = false; + pluginState.pluginStopped = false; if (UserStore.getCurrentUser()) { console.log("User is already logged in, initializing..."); @@ -36,7 +36,7 @@ export default new class LastFM { onUnload() { console.log("Stopping last.fm..."); - global.pluginStopped = true; + pluginState.pluginStopped = true; flush(); } diff --git a/plugins/Last.fm/src/utils.ts b/plugins/Last.fm/src/manager.ts similarity index 55% rename from plugins/Last.fm/src/utils.ts rename to plugins/Last.fm/src/manager.ts index eca3a02..adb591f 100644 --- a/plugins/Last.fm/src/utils.ts +++ b/plugins/Last.fm/src/manager.ts @@ -1,10 +1,12 @@ -import { FluxDispatcher } from "@vendetta/metro/common"; import { getAssetIDByName } from "@vendetta/ui/assets"; import { showToast } from "@vendetta/ui/toasts"; -import { currentSettings, global, verboseLog } from "."; -import { Activity, Track } from "../../defs"; + +import { currentSettings, pluginState, verboseLog } from "."; +import { Activity } from "../../defs"; import Constants from "./constants"; -import { AssetManager, SelfPresenceStore } from "./modules"; +import { SelfPresenceStore } from "./modules"; +import { clearActivity, fetchAsset, sendRequest } from "./utils/activity"; +import { fetchLatestScrobble } from "./utils/lastfm"; enum ActivityType { PLAYING = 0, @@ -13,84 +15,6 @@ enum ActivityType { COMPETING = 5 } -/** Fetches the latest user's scrobble */ -async function fetchLatestScrobble(): Promise { - const params = new URLSearchParams({ - "method": "user.getrecenttracks", - "user": currentSettings.username, - "api_key": Constants.LFM_API_KEY, - "format": "json", - "limit": "1", - "extended": "1" - }).toString(); - - const result = await fetch(`https://ws.audioscrobbler.com/2.0/?${params}`); - if (!result.ok) throw new Error(`Failed to fetch the latest scrobble: ${result.statusText}`); - - const info = await result.json(); - - const lastTrack = info?.recenttracks?.track?.[0]; - - if (!lastTrack) throw info; - - return { - name: lastTrack.name, - artist: lastTrack.artist.name, - album: lastTrack.album["#text"], - albumArt: await handleAlbumCover(lastTrack.image?.find((x: any) => x.size === "large")?.["#text"]), - url: lastTrack.url, - date: lastTrack.date?.["#text"] ?? "now", - nowPlaying: Boolean(lastTrack["@attr"]?.nowplaying), - loved: lastTrack.loved === "1", - } as Track; -} - -/** - * Currently ditches the default album covers - * @param cover The album cover given by Last.fm -*/ -async function handleAlbumCover(cover: string): Promise { - // If the cover is a default one, return null (remove the cover) - if (Constants.LFM_DEFAULT_COVER_HASHES.some(x => cover.includes(x))) { - return null; - } - return cover; -} - -/** Clears the user's activity */ -function clearActivity() { - global.lastActivity && verboseLog("--> Clearing activity..."); - return sendRequest(null); -} - -/** Sends the activity details to Discord */ -function sendRequest(activity: Activity) { - if (global.pluginStopped) { - console.log("--> Plugin is unloaded, aborting..."); - global.updateInterval && clearInterval(global.updateInterval); - activity = null; - } - - global.lastActivity = activity; - - FluxDispatcher.dispatch({ - type: "LOCAL_ACTIVITY_UPDATE", - activity: activity, - pid: 2312, - socketId: "Last.fm@Vendetta" - }); -} - -/** Fetches a Discord application's asset */ -async function fetchAsset(asset: string[], appId: string = Constants.APPLICATION_ID): Promise { - if (!asset) return []; - - const assetIds = AssetManager.getAssetIds(appId, asset); - if (assetIds.length > 0) return assetIds; - - return await AssetManager.fetchAssetIds(appId, asset); -} - /** * Fetches the current scrobble and sets the activity. * This function is actively called by the updateInterval. @@ -127,7 +51,7 @@ async function update() { verboseLog("--> Track fetched!"); - if (global.lastTrackUrl === lastTrack.url) { + if (pluginState.lastTrackUrl === lastTrack.url) { verboseLog("--> Last track is the same as the previous one, aborting..."); return; } @@ -141,7 +65,7 @@ async function update() { application_id: Constants.APPLICATION_ID, } as Activity; - global.lastTrackUrl = lastTrack.url; + pluginState.lastTrackUrl = lastTrack.url; if (activity.name.includes("{{")) { for (const key in lastTrack) { @@ -180,10 +104,10 @@ async function update() { /** Stops and reset everything, can be started again with `initialize()` */ export function flush() { console.log("--> Flushing..."); - global.lastActivity = null; - global.lastTrackUrl = null; + pluginState.lastActivity = null; + pluginState.lastTrackUrl = null; - global.updateInterval && clearInterval(global.updateInterval); + pluginState.updateInterval && clearInterval(pluginState.updateInterval); clearActivity(); } @@ -191,7 +115,7 @@ export function flush() { export async function initialize() { console.log("--> Initializing..."); - if (global.pluginStopped) { + if (pluginState.pluginStopped) { throw new Error("Plugin is already stopped!"); } @@ -205,7 +129,7 @@ export async function initialize() { }); // Periodically fetches the current scrobble and sets the activity - global.updateInterval = setInterval( + pluginState.updateInterval = setInterval( () => update() .then(() => { tries = 0; diff --git a/plugins/Last.fm/src/pages/Settings.tsx b/plugins/Last.fm/src/pages/Settings.tsx index 7e61511..d37780e 100644 --- a/plugins/Last.fm/src/pages/Settings.tsx +++ b/plugins/Last.fm/src/pages/Settings.tsx @@ -5,12 +5,11 @@ import { getAssetIDByName } from "@vendetta/ui/assets"; import { Forms } from "@vendetta/ui/components"; import { showToast } from "@vendetta/ui/toasts"; -import { currentSettings } from ".."; -import Constants from "../constants"; -import { initialize } from "../utils"; - import { FlatList, ScrollView, TouchableOpacity } from "react-native"; +import { currentSettings } from ".."; import { LFMSettings } from "../../../defs"; +import Constants from "../constants"; +import { initialize } from "../manager"; const { FormInput, FormDivider, FormSwitchRow, FormText, FormIcon, FormRow } = Forms; diff --git a/plugins/Last.fm/src/utils/activity.ts b/plugins/Last.fm/src/utils/activity.ts new file mode 100644 index 0000000..a84027b --- /dev/null +++ b/plugins/Last.fm/src/utils/activity.ts @@ -0,0 +1,40 @@ +import { FluxDispatcher } from "@vendetta/metro/common"; + +import { pluginState, verboseLog } from ".."; +import { Activity } from "../../../defs"; +import Constants from "../constants"; +import { AssetManager } from "../modules"; + +/** Clears the user's activity */ +export function clearActivity() { + pluginState.lastActivity && verboseLog("--> Clearing activity..."); + return sendRequest(null); +} + +/** Sends the activity details to Discord */ +export function sendRequest(activity: Activity) { + if (pluginState.pluginStopped) { + console.log("--> Plugin is unloaded, aborting..."); + pluginState.updateInterval && clearInterval(pluginState.updateInterval); + activity = null; + } + + pluginState.lastActivity = activity; + + 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 []; + + const assetIds = AssetManager.getAssetIds(appId, asset); + if (assetIds.length > 0) return assetIds; + + return await AssetManager.fetchAssetIds(appId, asset); +} diff --git a/plugins/Last.fm/src/utils/lastfm.ts b/plugins/Last.fm/src/utils/lastfm.ts new file mode 100644 index 0000000..265c295 --- /dev/null +++ b/plugins/Last.fm/src/utils/lastfm.ts @@ -0,0 +1,47 @@ +import { currentSettings } from ".."; +import { Track } from "../../../defs"; +import Constants from "../constants"; + +/** Fetches the latest user's scrobble */ +export async function fetchLatestScrobble(): Promise { + const params = new URLSearchParams({ + "method": "user.getrecenttracks", + "user": currentSettings.username, + "api_key": Constants.LFM_API_KEY, + "format": "json", + "limit": "1", + "extended": "1" + }).toString(); + + const result = await fetch(`https://ws.audioscrobbler.com/2.0/?${params}`); + if (!result.ok) throw new Error(`Failed to fetch the latest scrobble: ${result.statusText}`); + + const info = await result.json(); + + const lastTrack = info?.recenttracks?.track?.[0]; + + if (!lastTrack) throw info; + + return { + name: lastTrack.name, + artist: lastTrack.artist.name, + album: lastTrack.album["#text"], + albumArt: await handleAlbumCover(lastTrack.image?.find((x: any) => x.size === "large")?.["#text"]), + url: lastTrack.url, + date: lastTrack.date?.["#text"] ?? "now", + nowPlaying: Boolean(lastTrack["@attr"]?.nowplaying), + loved: lastTrack.loved === "1", + } as Track; +} + +/** + * Currently ditches the default album covers + * @param cover The album cover given by Last.fm +*/ +export async function handleAlbumCover(cover: string): Promise { + // If the cover is a default one, return null (remove the cover) + if (Constants.LFM_DEFAULT_COVER_HASHES.some(x => cover.includes(x))) { + return null; + } + return cover; +}