From 503bdd3e794501d8acc2bd9e0fd23c0007f2be50 Mon Sep 17 00:00:00 2001 From: amsyarasyiq <82711525+amsyarasyiq@users.noreply.github.com> Date: Wed, 15 Feb 2023 18:15:10 +0800 Subject: [PATCH] Last.fm: ditch `SET_ACTIVITY` --- plugins/Last.fm/src/index.ts | 7 +-- plugins/Last.fm/src/types.ts | 32 ++++------- plugins/Last.fm/src/utils.ts | 107 ++++++++++++++++------------------- 3 files changed, 59 insertions(+), 87 deletions(-) diff --git a/plugins/Last.fm/src/index.ts b/plugins/Last.fm/src/index.ts index 794530b..b579eb3 100644 --- a/plugins/Last.fm/src/index.ts +++ b/plugins/Last.fm/src/index.ts @@ -3,14 +3,13 @@ import { findByProps, findByStoreName } from "@vendetta/metro"; import { FluxDispatcher } from "@vendetta/metro/common"; import Settings from "./Settings"; -import { flush, initialize, patchActivityType } from "./utils"; +import { flush, initialize } from "./utils"; export const global = {} as { pluginStopped?: boolean, lastActivity?: Activity, updateInterval?: NodeJS.Timer, lastTrackUrl?: string, - activityTypeUnpatch?: () => boolean }; export const currentSettings = { ...plugin.storage } as PluginSettings; @@ -25,7 +24,6 @@ export const verboseLog = (...message: any) => currentSettings.verboseLogging && export default { onLoad: () => { console.log("Starting last.fm plugin.."); - global.activityTypeUnpatch = patchActivityType(); global.pluginStopped = false; if (UserStore.getCurrentUser()) { @@ -39,12 +37,9 @@ export default { }, onUnload: () => { console.log("Stopping last.fm..."); - global.pluginStopped = true; flush(); - global.activityTypeUnpatch?.(); - global.activityTypeUnpatch = null; }, settings: Settings }; diff --git a/plugins/Last.fm/src/types.ts b/plugins/Last.fm/src/types.ts index 44c43e4..168e720 100644 --- a/plugins/Last.fm/src/types.ts +++ b/plugins/Last.fm/src/types.ts @@ -14,31 +14,19 @@ type PluginSettings = { verboseLogging: boolean; }; -// Discord activity, used for SET_ACTIVITY +// A type of an object that is returned by the Discord API after setting the activity type Activity = { name: string; - type: number; - details: string; - state: string; - timestamps?: { - start: number; - end?: number; - }; - assets?: ActivityAssets; -} - -// A type of an object that is returned by the Discord API after setting the activity -type ResultActivity = { - name: string; - type: number; - details: string; - state: string; - timestamps?: { - start: number; - end?: number; - }; - assets?: ActivityAssets; application_id: string; + flags: number; + type: number; + details?: string; + state?: string; + timestamps?: { + start: number; + end?: number; + }; + assets?: ActivityAssets; }; // The assets of the activity diff --git a/plugins/Last.fm/src/utils.ts b/plugins/Last.fm/src/utils.ts index 7f52d30..a296f5c 100644 --- a/plugins/Last.fm/src/utils.ts +++ b/plugins/Last.fm/src/utils.ts @@ -1,10 +1,12 @@ -import { patcher } from "@vendetta"; +import { findByProps } from "@vendetta/metro"; import { FluxDispatcher } from "@vendetta/metro/common"; import { getAssetIDByName } from "@vendetta/ui/assets"; import { showToast } from "@vendetta/ui/toasts"; -import { currentSettings, global, SET_ACTIVITY, verboseLog } from "."; +import { currentSettings, global, verboseLog } from "."; import Constants from "./constants"; +const AssetManager = findByProps("getAssetIds"); + /** Fetches the latest user's scrobble */ async function fetchLatestScrobble(): Promise { const params = new URLSearchParams({ @@ -49,8 +51,14 @@ async function handleAlbumCover(cover: string): Promise { return cover; } +/** Clears the user's activity */ +function clearActivity() { + global.lastActivity && verboseLog("--> Clearing activity..."); + return sendRequest(null); +} + /** Sends the activity details to Discord */ -async function sendRequest(activity: Activity): Promise { +function sendRequest(activity: Activity) { if (global.pluginStopped) { console.log("--> Plugin is unloaded, aborting..."); global.updateInterval && clearInterval(global.updateInterval); @@ -59,54 +67,48 @@ async function sendRequest(activity: Activity): Promise { global.lastActivity = activity; - return await SET_ACTIVITY.handler({ - isSocketConnected: () => true, - socket: { - id: 120, - application: { - id: Constants.APPLICATION_ID, - name: activity?.name || "RichPresence" - }, - transport: "ipc" - }, - args: { - pid: 120, - activity: activity && { ...activity } || null - } + FluxDispatcher.dispatch({ + type: "LOCAL_ACTIVITY_UPDATE", + activity: activity }); } -/** Clears the user's activity */ -function clearActivity(): Promise { - global.lastActivity && verboseLog("--> Clearing activity..."); - return sendRequest(null); +/** Fetches a Discord application's asset */ +async function fetchAsset(asset: T, appId: string = Constants.APPLICATION_ID): Promise { + if (typeof asset === "string") { + const assetId = await AssetManager.getAssetIds(appId, [asset]); + return assetId[0] as T; + } + + return await AssetManager.getAssetIds(appId, asset); } /** * Fetches the current scrobble and sets the activity. * This function is actively called by the updateInterval. */ -export async function update() { +async function update() { verboseLog("--> Fetching last track..."); if (!currentSettings.username) { showToast("Last.fm username is not set!", getAssetIDByName("Small")); - await flush(); // Flush as we always need reinitialization + flush(); // Flush as we always need reinitialization throw new Error("Username is not set"); } const lastTrack = await fetchLatestScrobble().catch(async (err) => { verboseLog("--> An error occurred while fetching the last track, aborting..."); - await clearActivity().catch(); + clearActivity(); throw err; }); if (!lastTrack.nowPlaying) { verboseLog("--> Last track is not currently playing, aborting..."); - return await clearActivity(); + clearActivity(); + return; } - verboseLog("--> Track fetched! Setting activity..."); + verboseLog("--> Track fetched!"); if (global.lastTrackUrl === lastTrack.url) { verboseLog("--> Last track is the same as the previous one, aborting..."); @@ -115,9 +117,11 @@ export async function update() { const activity = { name: currentSettings.appName || Constants.DEFAULT_APP_NAME, + flags: 0, type: currentSettings.listeningTo ? 2 : 0, details: lastTrack.name, state: `by ${lastTrack.artist}`, + application_id: Constants.APPLICATION_ID, } as Activity; global.lastTrackUrl = lastTrack.url; @@ -131,31 +135,33 @@ export async function update() { if (lastTrack.album) { activity.assets = { - large_image: lastTrack.albumArt, + large_image: await fetchAsset(lastTrack.albumArt), large_text: `on ${lastTrack.album}` }; } - const response = await sendRequest(activity).catch(async (err) => { + verboseLog("--> Setting activity..."); + verboseLog(activity); + + try { + sendRequest(activity); + } catch (err) { verboseLog("--> An error occurred while setting the activity"); - await clearActivity().catch(); + clearActivity(); throw err; - }); + } verboseLog("--> Successfully set activity!"); - verboseLog(response); - return response; } -/** Stops everything */ -export function flush(): Promise { +/** Stops and reset everything, can be started again with `initialize()` */ +export function flush() { console.log("--> Flushing..."); global.lastActivity = null; global.lastTrackUrl = null; global.updateInterval && clearInterval(global.updateInterval); - - return clearActivity(); + clearActivity(); } /** Initializes the plugin */ @@ -166,11 +172,11 @@ export async function initialize() { throw new Error("Plugin is already stopped!"); } - await flush(); + flush(); let tries = 0; - update().catch((err) => { + await update().catch((err) => { console.error(err); tries++; }); @@ -178,34 +184,17 @@ export async function initialize() { // Periodically fetches the current scrobble and sets the activity global.updateInterval = setInterval( () => update() - .then(() => tries = 0) + .then(() => { + tries = 0; + }) .catch(err => { console.error(err); if (++tries > 3) { console.error("Failed to fetch/set activity 3 times, aborting..."); - clearInterval(global.updateInterval); + flush(); } }), (currentSettings.timeInterval || Constants.DEFAULT_TIME_INTERVAL) * 1000 ); } - -// Discord doesn't allow us to set the activity type to "Listening to" so we have to patch it -export function patchActivityType(): () => boolean { - if (global.activityTypeUnpatch) { - console.warn("This is weird... activity type override is already defined?"); - global.activityTypeUnpatch(); - } - - console.log("Patching activity type..."); - - const nodes = Object.values(FluxDispatcher._actionHandlers._dependencyGraph.nodes); - const { actionHandler } = nodes.find((x: any) => x.name === "LocalActivityStore") as any; - - return patcher.before("LOCAL_ACTIVITY_UPDATE", actionHandler, ([{ activity }]) => { - if (activity && activity.name === global.lastActivity.name) { - activity.type = global.lastActivity.type; - } - }); -}