Last.fm: Major refactoring

This commit is contained in:
amsyarasyiq 2023-07-09 08:17:28 +08:00
parent 7d6ce4055d
commit 646bc0b40b
8 changed files with 203 additions and 130 deletions

1
.gitignore vendored
View file

@ -6,3 +6,4 @@ yarn.lock
package-lock.json
Themer
RealThemer
ChatMesser

View file

@ -68,6 +68,7 @@ async function buildPlugin(plugin) {
format: "iife",
compact: true,
exports: "named",
inlineDynamicImports: true,
},
onwarn: (warning) => {
![

View file

@ -1,107 +0,0 @@
import { NavigationNative, React } from "@vendetta/metro/common";
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
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 { useEffect } from "react";
import { ScrollView, Text, TouchableOpacity } from "react-native";
const { FormInput, FormDivider, FormSwitchRow, FormText, FormIcon } = Forms;
function UpdateButton() {
async function onPressCallback() {
for (const key in storage) {
if (storage[key] === false || storage[key]) {
currentSettings[key] = storage[key];
}
}
console.log("Applying settings...");
await initialize();
showToast("Settings updated!", getAssetIDByName("Check"));
}
return <TouchableOpacity onPress={onPressCallback}>
<FormText style={{ marginRight: 12 }}>UPDATE</FormText>
</TouchableOpacity>;
}
export default function Settings() {
const settings = useProxy(storage) as LFMSettings;
const navigation = NavigationNative.useNavigation();
useEffect(() => {
navigation.setOptions({
title: "Last.fm Configuration",
headerRight: UpdateButton
});
}, []);
return (
<ScrollView>
<FormInput
value={settings.appName || undefined}
onChangeText={(value: string) => settings.appName = value.trim()}
title="Discord Application Name"
placeholder={Constants.DEFAULT_APP_NAME}
returnKeyType="done"
/>
<FormDivider />
<FormInput required
value={settings.username || undefined}
onChangeText={(value: string) => settings.username = value.trim()}
title="Last.fm username"
helpText={!settings.username && <Text style={{ color: "#FF0000" }}>{"This field is required!"}</Text>}
placeholder="wumpus123"
returnKeyType="done"
/>
<FormDivider />
<FormInput
value={settings.timeInterval}
onChangeText={(value: string) => settings.timeInterval = value}
title="Update interval (in seconds)"
placeholder={Constants.DEFAULT_TIME_INTERVAL.toString()}
keyboardType="numeric"
returnKeyType="done"
/>
<FormDivider />
<FormSwitchRow
label="Show time elapsed"
subLabel="Show the time elapsed since the song started playing"
leading={<FormIcon source={getAssetIDByName("clock")} />}
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"'
leading={<FormIcon source={getAssetIDByName("ic_headset_neutral")} />}
value={settings.listeningTo}
onValueChange={(value: boolean) => settings.listeningTo = value}
/>
<FormDivider />
<FormSwitchRow
label="Hide when Spotify is running"
subLabel="Hide the status when a Spotify activity is detected"
leading={<FormIcon source={getAssetIDByName("img_account_sync_spotify_light_and_dark")} />}
value={settings.ignoreSpotify}
onValueChange={(value: boolean) => settings.ignoreSpotify = value}
/>
<FormDivider />
<FormSwitchRow
label="Verbose logging"
subLabel="Log more information to the console for debugging purposes"
leading={<FormIcon source={getAssetIDByName("pencil")} />}
value={settings.verboseLogging}
onValueChange={(value: boolean) => settings.verboseLogging = value}
/>
</ScrollView>
);
}

View file

@ -1,8 +1,9 @@
import { plugin } from "@vendetta";
import { findByProps, findByStoreName } from "@vendetta/metro";
import { FluxDispatcher } from "@vendetta/metro/common";
import Settings from "./Settings";
import { Activity, LFMSettings } from "../../defs";
import { UserStore } from "./modules";
import Settings from "./pages/Settings";
import { flush, initialize } from "./utils";
export const global = {} as {
@ -15,10 +16,6 @@ export const global = {} as {
plugin.storage.ignoreSpotify ??= true;
export const currentSettings = { ...plugin.storage } as LFMSettings;
// Discord modules that we need
export const UserStore = findByStoreName("UserStore");
export const { SET_ACTIVITY } = findByProps("SET_ACTIVITY");
export const verboseLog = (...message: any) => currentSettings.verboseLogging && console.log(...message);
// Plugin entry point

View file

@ -0,0 +1,18 @@
import { findByProps, findByStoreName } from "@vendetta/metro";
// We don't really *need* this module, BUT this module has to be initialized before we can dispatch LOCAL_ACTIVITY_UPDATE
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { SET_ACTIVITY } = findByProps("SET_ACTIVITY");
export const AssetManager = findByProps("getAssetIds") as {
getAssetIds: (appId: string, ids: string[]) => string[];
fetchAssetIds: (appId: string, ids: string[]) => Promise<string[]>;
[key: PropertyKey]: any;
};
export const SelfPresenceStore = findByStoreName("SelfPresenceStore") as {
getActivities: () => any[];
findActivity: (filter: (activity) => boolean) => any;
};
export const UserStore = findByStoreName("UserStore");

View file

@ -0,0 +1,12 @@
import { Codeblock } from "@vendetta/ui/components";
import { ScrollView } from "react-native";
export default function Debug() {
return (
<ScrollView>
<Codeblock selectable>
{JSON.stringify({ blah: "bleh" }, null, 4)}
</Codeblock>
</ScrollView>
);
}

View file

@ -0,0 +1,158 @@
import { NavigationNative, React } from "@vendetta/metro/common";
import { storage } from "@vendetta/plugin";
import { useProxy } from "@vendetta/storage";
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 { LFMSettings } from "../../../defs";
const { FormInput, FormDivider, FormSwitchRow, FormText, FormIcon, FormRow } = Forms;
function UpdateButton() {
async function onPressCallback() {
for (const key in storage) {
if (storage[key] === false || storage[key]) {
currentSettings[key] = storage[key];
}
}
console.log("Applying settings...");
await initialize();
showToast("Settings updated!", getAssetIDByName("Check"));
}
return <TouchableOpacity onPress={onPressCallback}>
<FormText style={{ marginRight: 12 }}>UPDATE</FormText>
</TouchableOpacity>;
}
type FormRowProps = {
label: string;
subLabel: string;
leading: React.ReactNode;
trailing: React.ReactNode;
onPress: () => void;
};
type RowProps = Partial<FormRowProps & ({
type: "switch";
value: boolean;
onValueChange: (value: boolean) => void;
} | {
label: never;
subLabel: never;
type: "input";
title: string;
value: string;
required?: boolean;
onChangeText: (value: string) => void;
placeholder: string;
keyboardType?: "default" | "numeric";
returnKeyType?: "done";
})>;
function Rows({ rows }: { rows: RowProps[] }) {
const Row = ({ type, ...props }: RowProps) => {
switch (type) {
case "switch": return <FormSwitchRow {...props} />;
case "input": return <FormInput {...props} />;
default: return <FormRow {...props} />;
}
};
return <FlatList
data={rows}
renderItem={({ item }) => <Row {...item} />}
keyExtractor={(item) => item.label}
ItemSeparatorComponent={FormDivider}
/>;
}
export default function Settings() {
const settings = useProxy(storage) as LFMSettings;
const navigation = NavigationNative.useNavigation();
navigation.addListener("focus", () => {
navigation.setOptions({
headerRight: UpdateButton
});
});
return (
<ScrollView>
<Rows rows={[
{
type: "input",
title: "Discord Application Name",
value: settings.appName || void 0,
onChangeText: (value: string) => settings.appName = value.trim(),
placeholder: Constants.DEFAULT_APP_NAME,
returnKeyType: "done"
},
{
type: "input",
title: "Last.fm username",
value: settings.username || void 0,
onChangeText: (value: string) => settings.username = value.trim(),
placeholder: "wumpus123",
returnKeyType: "done",
required: true
},
{
type: "input",
title: "Update interval (in seconds)",
value: String(settings.timeInterval),
onChangeText: (value: string) => settings.timeInterval = value,
placeholder: Constants.DEFAULT_TIME_INTERVAL.toString(),
keyboardType: "numeric",
returnKeyType: "done"
},
{
type: "switch",
label: "Show time elapsed",
subLabel: "Show the time elapsed since the song started playing",
leading: <FormIcon source={getAssetIDByName("clock")} />,
value: settings.showTimestamp,
onValueChange: (value: boolean) => settings.showTimestamp = value
},
{
type: "switch",
label: "Set status as listening",
subLabel: "Set your status as \"Listening to\" instead of \"Playing\"",
leading: <FormIcon source={getAssetIDByName("ic_headset_neutral")} />,
value: settings.listeningTo,
onValueChange: (value: boolean) => settings.listeningTo = value
},
{
type: "switch",
label: "Hide when Spotify is running",
subLabel: "Hide the status when a Spotify activity is detected",
leading: <FormIcon source={getAssetIDByName("img_account_sync_spotify_light_and_dark")} />,
value: settings.ignoreSpotify,
onValueChange: (value: boolean) => settings.ignoreSpotify = value
},
{
label: "Debug",
subLabel: "View debug information",
leading: <FormIcon source={getAssetIDByName("debug")} />,
trailing: FormRow.Arrow,
onPress: async () => {
const imported = await import("./Debug");
navigation.push("VendettaCustomPage", {
title: "Debug",
render: imported.default
});
}
}
]} />
</ScrollView>
);
}

View file

@ -1,16 +1,10 @@
import { findByProps, findByStoreName } from "@vendetta/metro";
import { FluxDispatcher } from "@vendetta/metro/common";
import { getAssetIDByName } from "@vendetta/ui/assets";
import { showToast } from "@vendetta/ui/toasts";
import { UserStore, currentSettings, global, verboseLog } from ".";
import { currentSettings, global, verboseLog } from ".";
import { Activity, Track } from "../../defs";
import Constants from "./constants";
const AssetManager = findByProps("getAssetIds") as {
getAssetIds: (appId: string, ids: string[]) => string[];
fetchAssetIds: (appId: string, ids: string[]) => Promise<string[]>;
[key: PropertyKey]: any;
};
const PresenceStore = findByStoreName("PresenceStore");
import { AssetManager, SelfPresenceStore } from "./modules";
enum ActivityType {
PLAYING = 0,
@ -43,7 +37,7 @@ async function fetchLatestScrobble(): Promise<Track> {
name: lastTrack.name,
artist: lastTrack.artist.name,
album: lastTrack.album["#text"],
albumArt: await handleAlbumCover(lastTrack.image[3]["#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),
@ -111,12 +105,11 @@ async function update() {
}
if (currentSettings.ignoreSpotify) {
for (const activity of PresenceStore.getActivities(UserStore.getCurrentUser().id)) {
if (activity?.type === ActivityType.LISTENING && activity.application_id !== Constants.APPLICATION_ID) {
verboseLog("--> Spotify is currently playing, aborting...");
clearActivity();
return;
}
const spotifyActivity = SelfPresenceStore.findActivity(act => act.sync_id);
if (spotifyActivity) {
verboseLog("--> Spotify is currently playing, aborting...");
clearActivity();
return;
}
}
@ -142,7 +135,7 @@ async function update() {
const activity = {
name: currentSettings.appName || Constants.DEFAULT_APP_NAME,
flags: 0,
type: currentSettings.listeningTo ? 2 : 0,
type: currentSettings.listeningTo ? ActivityType.LISTENING : ActivityType.PLAYING,
details: lastTrack.name,
state: `by ${lastTrack.artist}`,
application_id: Constants.APPLICATION_ID,