Last.fm: functional settings page
This commit is contained in:
parent
c218b5cc8a
commit
92d511c7b2
4 changed files with 124 additions and 41 deletions
11
plugins/Last.fm/src/constants.ts
Normal file
11
plugins/Last.fm/src/constants.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
export default {
|
||||||
|
/** The application ID of the plugin */
|
||||||
|
APPLICATION_ID: "1054951789318909972",
|
||||||
|
/** Last.fm api key */
|
||||||
|
LFM_API_KEY: "615322f0047e12aedbc610d9d71f7430",
|
||||||
|
/** These are the default album covers that are used by Last.fm */
|
||||||
|
DEFAULT_COVER_HASHES: [
|
||||||
|
"2a96cbd8b46e442fc41c2b86b821562f",
|
||||||
|
"c6f59c1e5e7240a4c0d427abd71f3dbb",
|
||||||
|
]
|
||||||
|
} as const;
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { logger, patcher, plugin } from "@vendetta";
|
import { patcher, plugin } from "@vendetta";
|
||||||
import { findByProps, findByStoreName } from '@vendetta/metro';
|
import { findByProps, findByStoreName } from '@vendetta/metro';
|
||||||
import { FluxDispatcher } from '@vendetta/metro/common';
|
import { FluxDispatcher } from '@vendetta/metro/common';
|
||||||
|
import Constants from "./constants";
|
||||||
import Settings from "./settings";
|
import Settings from "./settings";
|
||||||
|
|
||||||
let stopped: Boolean,
|
let stopped: Boolean,
|
||||||
|
|
@ -9,25 +10,6 @@ let stopped: Boolean,
|
||||||
lastTrackUrl: string,
|
lastTrackUrl: string,
|
||||||
activityTypeUnpatch: () => void;
|
activityTypeUnpatch: () => void;
|
||||||
|
|
||||||
const settings = plugin.storage as {
|
|
||||||
appName: string;
|
|
||||||
username: string;
|
|
||||||
showTimestamp: string;
|
|
||||||
timeInterval: number;
|
|
||||||
listeningTo: boolean;
|
|
||||||
verboseLogging: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Constants for the API calls (perhaps these should be options?)
|
|
||||||
const APPLICATION_ID = "1054951789318909972";
|
|
||||||
const LFM_API_KEY = "615322f0047e12aedbc610d9d71f7430";
|
|
||||||
|
|
||||||
// These are the default album covers that are used by Last.fm
|
|
||||||
const DEFAULT_COVER_HASHES = [
|
|
||||||
"2a96cbd8b46e442fc41c2b86b821562f",
|
|
||||||
"c6f59c1e5e7240a4c0d427abd71f3dbb",
|
|
||||||
];
|
|
||||||
|
|
||||||
// Discord activity
|
// Discord activity
|
||||||
type Activity = {
|
type Activity = {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -73,17 +55,19 @@ type Track = {
|
||||||
loved: boolean;
|
loved: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const currentSettings = { ...plugin.storage } as PluginSettings;
|
||||||
|
|
||||||
// Discord modules that we need
|
// Discord modules that we need
|
||||||
const UserStore = findByStoreName("UserStore")
|
const UserStore = findByStoreName("UserStore")
|
||||||
const SET_ACTIVITY = findByProps('SET_ACTIVITY').SET_ACTIVITY;
|
const SET_ACTIVITY = findByProps('SET_ACTIVITY').SET_ACTIVITY;
|
||||||
|
|
||||||
const verboseLog = (...message: any) => settings.verboseLogging && logger.log(...message);
|
const verboseLog = (...message: any) => currentSettings.verboseLogging && console.log(...message);
|
||||||
|
|
||||||
async function fetchCurrentScrobble(): Promise<Track> {
|
async function fetchCurrentScrobble(): Promise<Track> {
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
'method': 'user.getrecenttracks',
|
'method': 'user.getrecenttracks',
|
||||||
'user': settings.username,
|
'user': currentSettings.username,
|
||||||
'api_key': LFM_API_KEY,
|
'api_key': Constants.LFM_API_KEY,
|
||||||
'format': 'json',
|
'format': 'json',
|
||||||
'limit': '1',
|
'limit': '1',
|
||||||
'extended': '1'
|
'extended': '1'
|
||||||
|
|
@ -106,7 +90,7 @@ async function fetchCurrentScrobble(): Promise<Track> {
|
||||||
// Currently ditches the default album covers (they're ugly)
|
// Currently ditches the default album covers (they're ugly)
|
||||||
// Might as well use Youtube as fallback too (soontm)
|
// Might as well use Youtube as fallback too (soontm)
|
||||||
function filterAlbumCover(cover: string): string {
|
function filterAlbumCover(cover: string): string {
|
||||||
if (DEFAULT_COVER_HASHES.some(x => cover.includes(x))) {
|
if (Constants.DEFAULT_COVER_HASHES.some(x => cover.includes(x))) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return cover;
|
return cover;
|
||||||
|
|
@ -133,24 +117,25 @@ async function sendRequest(activity: Activity): Promise<ResultActivity> {
|
||||||
socket: {
|
socket: {
|
||||||
id: 120,
|
id: 120,
|
||||||
application: {
|
application: {
|
||||||
id: APPLICATION_ID,
|
id: Constants.APPLICATION_ID,
|
||||||
name: activity?.name || "RichPresence"
|
name: activity?.name || "RichPresence"
|
||||||
},
|
},
|
||||||
transport: "ipc"
|
transport: "ipc"
|
||||||
},
|
},
|
||||||
args: {
|
args: {
|
||||||
pid: 120,
|
pid: 120,
|
||||||
activity: activity && { ...activity }
|
activity: activity && { ...activity } || null
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetches the current scrobble and sets the activity
|
// Fetches the current scrobble and sets the activity
|
||||||
// This is the main function that is called every 5 seconds (or whatever the user has set)
|
// This is the main function that is called every 5 seconds (or whatever the user has set)
|
||||||
async function update() {
|
export async function update() {
|
||||||
verboseLog("--> Fetching last track...");
|
verboseLog("--> Fetching last track...");
|
||||||
const lastTrack = await fetchCurrentScrobble().catch((err) => {
|
const lastTrack = await fetchCurrentScrobble().catch((err) => {
|
||||||
verboseLog("--> An error occurred while fetching the last track, aborting...");
|
verboseLog("--> An error occurred while fetching the last track, aborting...");
|
||||||
|
clearActivity();
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -167,15 +152,15 @@ async function update() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const activity = {
|
const activity = {
|
||||||
name: settings.appName || "Music",
|
name: currentSettings.appName || "Music",
|
||||||
type: settings.listeningTo ? 2 : 0,
|
type: currentSettings.listeningTo ? 2 : 0,
|
||||||
details: lastTrack.name,
|
details: lastTrack.name,
|
||||||
state: `by ${lastTrack.artist}`,
|
state: `by ${lastTrack.artist}`,
|
||||||
} as Activity;
|
} as Activity;
|
||||||
|
|
||||||
lastTrackUrl = lastTrack.url;
|
lastTrackUrl = lastTrack.url;
|
||||||
|
|
||||||
if (settings.showTimestamp) {
|
if (currentSettings.showTimestamp) {
|
||||||
activity.timestamps = {
|
activity.timestamps = {
|
||||||
start: Date.now() / 1000 | 0
|
start: Date.now() / 1000 | 0
|
||||||
};
|
};
|
||||||
|
|
@ -190,6 +175,7 @@ async function update() {
|
||||||
|
|
||||||
const response = await sendRequest(activity).catch((err) => {
|
const response = await sendRequest(activity).catch((err) => {
|
||||||
verboseLog("--> An error occurred while setting the activity");
|
verboseLog("--> An error occurred while setting the activity");
|
||||||
|
clearActivity();
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -198,8 +184,17 @@ async function update() {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stops everything
|
||||||
|
export function flush(): Promise<ResultActivity> {
|
||||||
|
console.log("--> Flushing...");
|
||||||
|
lastActivity = null;
|
||||||
|
updateInterval && clearInterval(updateInterval);
|
||||||
|
|
||||||
|
return clearActivity();
|
||||||
|
}
|
||||||
|
|
||||||
// Initializes the plugin
|
// Initializes the plugin
|
||||||
async function initialize() {
|
export async function initialize() {
|
||||||
console.log("--> Initializing...");
|
console.log("--> Initializing...");
|
||||||
|
|
||||||
stopped = false;
|
stopped = false;
|
||||||
|
|
@ -209,10 +204,14 @@ async function initialize() {
|
||||||
if (updateInterval) clearInterval(updateInterval);
|
if (updateInterval) clearInterval(updateInterval);
|
||||||
if (lastActivity) await clearActivity();
|
if (lastActivity) await clearActivity();
|
||||||
|
|
||||||
update().catch(console.error);
|
|
||||||
|
|
||||||
let tries = 0;
|
let tries = 0;
|
||||||
|
|
||||||
|
update().catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
tries++;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
// Periodically fetches the current scrobble and sets the activity
|
// Periodically fetches the current scrobble and sets the activity
|
||||||
updateInterval = setInterval(
|
updateInterval = setInterval(
|
||||||
() => update()
|
() => update()
|
||||||
|
|
@ -223,9 +222,10 @@ async function initialize() {
|
||||||
if (++tries > 3) {
|
if (++tries > 3) {
|
||||||
console.error("Failed to fetch/set activity 3 times, aborting...");
|
console.error("Failed to fetch/set activity 3 times, aborting...");
|
||||||
clearInterval(updateInterval);
|
clearInterval(updateInterval);
|
||||||
|
stopped = true;
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
settings.timeInterval
|
currentSettings.timeInterval
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -277,10 +277,8 @@ export default {
|
||||||
console.log("Stopping last.fm...");
|
console.log("Stopping last.fm...");
|
||||||
|
|
||||||
stopped = true;
|
stopped = true;
|
||||||
lastActivity = null;
|
|
||||||
updateInterval && clearInterval(updateInterval);
|
|
||||||
|
|
||||||
clearActivity();
|
flush();
|
||||||
activityTypeUnpatch?.();
|
activityTypeUnpatch?.();
|
||||||
activityTypeUnpatch = null;
|
activityTypeUnpatch = null;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,69 @@
|
||||||
import { React, ReactNative } from "@vendetta/metro/common";
|
import { NavigationNative, React, ReactNative } from "@vendetta/metro/common";
|
||||||
|
import { storage } from "@vendetta/plugin";
|
||||||
|
import { useProxy } from "@vendetta/storage";
|
||||||
|
import { Forms } from "@vendetta/ui/components";
|
||||||
|
import { currentSettings, flush, initialize } from ".";
|
||||||
|
|
||||||
|
const { ScrollView, TouchableOpacity } = ReactNative;
|
||||||
|
const { FormInput, FormDivider, FormSwitchRow, FormText } = Forms;
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
|
const settings = useProxy(storage) as PluginSettings;
|
||||||
|
const navigation = NavigationNative.useNavigation();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
navigation.setOptions({
|
||||||
|
headerRight: () => (
|
||||||
|
<TouchableOpacity onPress={() => {
|
||||||
|
for (const key in storage) {
|
||||||
|
currentSettings[key] = settings[key];
|
||||||
|
}
|
||||||
|
flush().then(() => initialize()).then(() => navigation.goBack()).catch(console.error);
|
||||||
|
}}>
|
||||||
|
<FormText style={{ marginRight: 12 }}>UPDATE</FormText>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ReactNative.Text>
|
<ScrollView>
|
||||||
Hello World!
|
<FormInput
|
||||||
</ReactNative.Text>
|
value={settings.appName}
|
||||||
|
onChangeText={(value: string) => settings.appName = value.trim()}
|
||||||
|
title="Application Name"
|
||||||
|
placeholder="Music"
|
||||||
|
returnKeyType="done"
|
||||||
|
/>
|
||||||
|
<FormDivider />
|
||||||
|
<FormInput
|
||||||
|
value={settings.username}
|
||||||
|
onChangeText={(value: string) => settings.username = value.trim()}
|
||||||
|
title="Username"
|
||||||
|
placeholder="wumpus123"
|
||||||
|
returnKeyType="done"
|
||||||
|
/>
|
||||||
|
<FormDivider />
|
||||||
|
<FormSwitchRow
|
||||||
|
label="Show time elapsed"
|
||||||
|
subLabel="Show the time elapsed since the song started playing"
|
||||||
|
value={settings.showTimestamp}
|
||||||
|
onValueChange={(value: boolean) => settings.showTimestamp = value}
|
||||||
|
/>
|
||||||
|
<FormDivider />
|
||||||
|
<FormSwitchRow
|
||||||
|
label="Set status as listening"
|
||||||
|
subLabel='Set your status as "Listening to" instead of "Playing"'
|
||||||
|
value={settings.listeningTo}
|
||||||
|
onValueChange={(value: boolean) => settings.listeningTo = value}
|
||||||
|
/>
|
||||||
|
<FormDivider />
|
||||||
|
<FormSwitchRow
|
||||||
|
label="Verbose logging"
|
||||||
|
subLabel="Log more information to the console for debugging purposes"
|
||||||
|
value={settings.verboseLogging}
|
||||||
|
onValueChange={(value: boolean) => settings.verboseLogging = value}
|
||||||
|
/>
|
||||||
|
</ScrollView>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
plugins/Last.fm/src/types.ts
Normal file
14
plugins/Last.fm/src/types.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
type PluginSettings = {
|
||||||
|
/** Application's name */
|
||||||
|
appName: string;
|
||||||
|
/** User's last.fm username */
|
||||||
|
username: string;
|
||||||
|
/** Whether or not to show the timestamp */
|
||||||
|
showTimestamp: boolean;
|
||||||
|
/** The time interval between each update */
|
||||||
|
timeInterval: number;
|
||||||
|
/** Whether or not to show "Listening to" */
|
||||||
|
listeningTo: boolean;
|
||||||
|
/** Whether or not to show verbose logging */
|
||||||
|
verboseLogging: boolean;
|
||||||
|
};
|
||||||
Loading…
Add table
Reference in a new issue