Last.fm: project restructure
This commit is contained in:
parent
4d3eabc9f2
commit
84ef65fe04
5 changed files with 107 additions and 97 deletions
|
|
@ -2,11 +2,11 @@ import { plugin } from "@vendetta";
|
||||||
import { FluxDispatcher } from "@vendetta/metro/common";
|
import { FluxDispatcher } from "@vendetta/metro/common";
|
||||||
|
|
||||||
import { Activity, LFMSettings } from "../../defs";
|
import { Activity, LFMSettings } from "../../defs";
|
||||||
|
import { flush, initialize } from "./manager";
|
||||||
import { UserStore } from "./modules";
|
import { UserStore } from "./modules";
|
||||||
import Settings from "./pages/Settings";
|
import Settings from "./pages/Settings";
|
||||||
import { flush, initialize } from "./utils";
|
|
||||||
|
|
||||||
export const global = {} as {
|
export const pluginState = {} as {
|
||||||
pluginStopped?: boolean,
|
pluginStopped?: boolean,
|
||||||
lastActivity?: Activity,
|
lastActivity?: Activity,
|
||||||
updateInterval?: NodeJS.Timer,
|
updateInterval?: NodeJS.Timer,
|
||||||
|
|
@ -22,7 +22,7 @@ export const verboseLog = (...message: any) => currentSettings.verboseLogging &&
|
||||||
export default new class LastFM {
|
export default new class LastFM {
|
||||||
onLoad() {
|
onLoad() {
|
||||||
console.log("Starting last.fm plugin..");
|
console.log("Starting last.fm plugin..");
|
||||||
global.pluginStopped = false;
|
pluginState.pluginStopped = false;
|
||||||
|
|
||||||
if (UserStore.getCurrentUser()) {
|
if (UserStore.getCurrentUser()) {
|
||||||
console.log("User is already logged in, initializing...");
|
console.log("User is already logged in, initializing...");
|
||||||
|
|
@ -36,7 +36,7 @@ export default new class LastFM {
|
||||||
|
|
||||||
onUnload() {
|
onUnload() {
|
||||||
console.log("Stopping last.fm...");
|
console.log("Stopping last.fm...");
|
||||||
global.pluginStopped = true;
|
pluginState.pluginStopped = true;
|
||||||
|
|
||||||
flush();
|
flush();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
import { FluxDispatcher } from "@vendetta/metro/common";
|
|
||||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||||
import { showToast } from "@vendetta/ui/toasts";
|
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 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 {
|
enum ActivityType {
|
||||||
PLAYING = 0,
|
PLAYING = 0,
|
||||||
|
|
@ -13,84 +15,6 @@ enum ActivityType {
|
||||||
COMPETING = 5
|
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.
|
* Fetches the current scrobble and sets the activity.
|
||||||
* This function is actively called by the updateInterval.
|
* This function is actively called by the updateInterval.
|
||||||
|
|
@ -127,7 +51,7 @@ async function update() {
|
||||||
|
|
||||||
verboseLog("--> Track fetched!");
|
verboseLog("--> Track fetched!");
|
||||||
|
|
||||||
if (global.lastTrackUrl === lastTrack.url) {
|
if (pluginState.lastTrackUrl === lastTrack.url) {
|
||||||
verboseLog("--> Last track is the same as the previous one, aborting...");
|
verboseLog("--> Last track is the same as the previous one, aborting...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -141,7 +65,7 @@ async function update() {
|
||||||
application_id: Constants.APPLICATION_ID,
|
application_id: Constants.APPLICATION_ID,
|
||||||
} as Activity;
|
} as Activity;
|
||||||
|
|
||||||
global.lastTrackUrl = lastTrack.url;
|
pluginState.lastTrackUrl = lastTrack.url;
|
||||||
|
|
||||||
if (activity.name.includes("{{")) {
|
if (activity.name.includes("{{")) {
|
||||||
for (const key in lastTrack) {
|
for (const key in lastTrack) {
|
||||||
|
|
@ -180,10 +104,10 @@ async function update() {
|
||||||
/** Stops and reset everything, can be started again with `initialize()` */
|
/** Stops and reset everything, can be started again with `initialize()` */
|
||||||
export function flush() {
|
export function flush() {
|
||||||
console.log("--> Flushing...");
|
console.log("--> Flushing...");
|
||||||
global.lastActivity = null;
|
pluginState.lastActivity = null;
|
||||||
global.lastTrackUrl = null;
|
pluginState.lastTrackUrl = null;
|
||||||
|
|
||||||
global.updateInterval && clearInterval(global.updateInterval);
|
pluginState.updateInterval && clearInterval(pluginState.updateInterval);
|
||||||
clearActivity();
|
clearActivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -191,7 +115,7 @@ export function flush() {
|
||||||
export async function initialize() {
|
export async function initialize() {
|
||||||
console.log("--> Initializing...");
|
console.log("--> Initializing...");
|
||||||
|
|
||||||
if (global.pluginStopped) {
|
if (pluginState.pluginStopped) {
|
||||||
throw new Error("Plugin is already stopped!");
|
throw new Error("Plugin is already stopped!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -205,7 +129,7 @@ export async function initialize() {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Periodically fetches the current scrobble and sets the activity
|
// Periodically fetches the current scrobble and sets the activity
|
||||||
global.updateInterval = setInterval(
|
pluginState.updateInterval = setInterval(
|
||||||
() => update()
|
() => update()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
tries = 0;
|
tries = 0;
|
||||||
|
|
@ -5,12 +5,11 @@ import { getAssetIDByName } from "@vendetta/ui/assets";
|
||||||
import { Forms } from "@vendetta/ui/components";
|
import { Forms } from "@vendetta/ui/components";
|
||||||
import { showToast } from "@vendetta/ui/toasts";
|
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 { FlatList, ScrollView, TouchableOpacity } from "react-native";
|
||||||
|
import { currentSettings } from "..";
|
||||||
import { LFMSettings } from "../../../defs";
|
import { LFMSettings } from "../../../defs";
|
||||||
|
import Constants from "../constants";
|
||||||
|
import { initialize } from "../manager";
|
||||||
|
|
||||||
const { FormInput, FormDivider, FormSwitchRow, FormText, FormIcon, FormRow } = Forms;
|
const { FormInput, FormDivider, FormSwitchRow, FormText, FormIcon, FormRow } = Forms;
|
||||||
|
|
||||||
|
|
|
||||||
40
plugins/Last.fm/src/utils/activity.ts
Normal file
40
plugins/Last.fm/src/utils/activity.ts
Normal 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);
|
||||||
|
}
|
||||||
47
plugins/Last.fm/src/utils/lastfm.ts
Normal file
47
plugins/Last.fm/src/utils/lastfm.ts
Normal 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;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue