Last.fm: project restructure

This commit is contained in:
amsyarasyiq 2023-07-09 08:50:00 +08:00
parent 4d3eabc9f2
commit 84ef65fe04
5 changed files with 107 additions and 97 deletions

View file

@ -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();
}

View file

@ -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<Track> {
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<string> {
// 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<string[]> {
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;

View file

@ -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;

View file

@ -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<string[]> {
if (!asset) return [];
const assetIds = AssetManager.getAssetIds(appId, asset);
if (assetIds.length > 0) return assetIds;
return await AssetManager.fetchAssetIds(appId, asset);
}

View file

@ -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<Track> {
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<string> {
// 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;
}