From 5938714c73ab926f54cd7f4451d48aa6ce03d4c4 Mon Sep 17 00:00:00 2001 From: amsyarasyiq <82711525+amsyarasyiq@users.noreply.github.com> Date: Mon, 3 Apr 2023 22:54:15 +0800 Subject: [PATCH] RichPresence: init --- plugins/Last.fm/src/types.ts | 24 ---- plugins/RichPresence/manifest.json | 14 +++ plugins/RichPresence/src/Settings.tsx | 156 ++++++++++++++++++++++++++ plugins/RichPresence/src/index.ts | 116 +++++++++++++++++++ plugins/defs.d.ts | 26 +++++ 5 files changed, 312 insertions(+), 24 deletions(-) create mode 100644 plugins/RichPresence/manifest.json create mode 100644 plugins/RichPresence/src/Settings.tsx create mode 100644 plugins/RichPresence/src/index.ts create mode 100644 plugins/defs.d.ts diff --git a/plugins/Last.fm/src/types.ts b/plugins/Last.fm/src/types.ts index 2cabd69..72668c3 100644 --- a/plugins/Last.fm/src/types.ts +++ b/plugins/Last.fm/src/types.ts @@ -16,21 +16,6 @@ type PluginSettings = { verboseLogging: boolean; }; -// A type of an object that is returned by the Discord API after setting the activity -type Activity = { - name: string; - application_id: string; - flags: number; - type: number; - details?: string; - state?: string; - timestamps?: { - start: number; - end?: number; - }; - assets?: ActivityAssets; -}; - enum ActivityType { PLAYING = 0, STREAMING = 1, @@ -38,15 +23,6 @@ enum ActivityType { COMPETING = 5 } -// The assets of the activity -type ActivityAssets = { - large_image?: string; - large_text?: string; -} | { - small_image: string; - small_text?: string; -}; - // Last.fm track type Track = { name: string; diff --git a/plugins/RichPresence/manifest.json b/plugins/RichPresence/manifest.json new file mode 100644 index 0000000..81e0f22 --- /dev/null +++ b/plugins/RichPresence/manifest.json @@ -0,0 +1,14 @@ +{ + "name": "RichPresence", + "description": "Sets Discord Rich Presence for your profile.", + "authors": [ + { + "name": "amsyarasyiq", + "id": "492949202121261067" + } + ], + "main": "src/index.ts", + "vendetta": { + "icon": "games" + } +} diff --git a/plugins/RichPresence/src/Settings.tsx b/plugins/RichPresence/src/Settings.tsx new file mode 100644 index 0000000..910ffd8 --- /dev/null +++ b/plugins/RichPresence/src/Settings.tsx @@ -0,0 +1,156 @@ +import { React, ReactNative, stylesheet } from "@vendetta/metro/common"; +import { storage } from "@vendetta/plugin"; +import { useProxy } from "@vendetta/storage"; +import { semanticColors } from "@vendetta/ui"; +import { Forms } from "@vendetta/ui/components"; + +const { FormSection, FormInput, FormRow, FormSwitch, FormText } = Forms; +const { ScrollView } = ReactNative; + +const styles = stylesheet.createThemedStyleSheet({ + subText: { + fontSize: 14, + marginLeft: 16, + marginRight: 16, + color: semanticColors.TEXT_MUTED + }, + textLink: { + color: semanticColors.TEXT_LINK, + } +}); + +export default function Settings() { + const settings = useProxy(storage) as { [key: string]: any }; + + return ( + + settings.app_name = v} + /> + settings.application_id = v} + keyboardType="numeric" + /> + settings.type = v} + keyboardType="numeric" + /> + settings.details = v} + /> + settings.state = v} + /> + + + settings.large_image = v} + /> + settings.large_image_text = v} + /> + settings.small_image = v} + /> + settings.small_image_text = v} + /> + + {"Image assets key can be either a Discord app asset's name or a URL to an image."} + + + + settings.enable_timestamps = v} + />} + /> + settings.start_timestamp = v} + keyboardType="numeric" + /> + settings.end_timestamp = v} + keyboardType="numeric" + /> + settings.start_timestamp = String(Date.now())} + trailing={FormRow.Arrow} + /> + + Leaving start timestamp blank will use the time the Discord started. + + + + settings.button1_text = v} + /> + settings.button1_URL = v} + /> + settings.button2_text = v} + /> + settings.button2_URL = v} + /> + + ); +} diff --git a/plugins/RichPresence/src/index.ts b/plugins/RichPresence/src/index.ts new file mode 100644 index 0000000..c33783b --- /dev/null +++ b/plugins/RichPresence/src/index.ts @@ -0,0 +1,116 @@ +import { logger } from "@vendetta"; +import { findByProps } from "@vendetta/metro"; +import { FluxDispatcher } from "@vendetta/metro/common"; +import { storage } from "@vendetta/plugin"; +import Settings from "./Settings"; + +const AssetManager = findByProps("getAssetIds"); + +enum ActivityTypes { + PLAYING = 0, + STREAMING = 1, + LISTENING = 2, + WATCHING = 3, + // CUSTOM = 4, + COMPETING = 5 +} + +// :nyaboom: +function constructActivity() { + const activity: Activity = { + name: storage.app_name || "Discord", + application_id: storage.application_id || "1054951789318909972", + flags: 0, + type: ActivityTypes.PLAYING, // PLAYING + state: storage.state, + details: storage.details + }; + + // Construct timestamps + if (storage.enable_timestamps) { + const timestamps = {} as any; + + timestamps.start = Number(storage.start_timestamp) || Date.now(); + if (!isNaN(storage.end_timestamp)) timestamps.end = Number(storage.end_timestamp); + + activity.timestamps = timestamps; + } + + // Construct assets + const assets = {} as ActivityAssets; + + if (storage.large_image) { + assets.large_image = storage.large_image; + if (storage.large_image_text) assets.large_text = storage.large_image_text; + } + + if (storage.small_image) { + assets.small_image = storage.small_image; + if (storage.small_image_text) assets.small_text = storage.small_image_text; + } + + if (Object.keys(assets).length) activity.assets = assets; + + // Construct buttons + if (storage.button1_text && storage.button1_URL) { + activity.buttons = [ + { label: storage.button1_text, url: storage.button1_URL } + ]; + } + + if (storage.button2_text && storage.button2_URL) { + (activity.buttons ??= []).push({ label: storage.button2_text, url: storage.button2_URL }); + } + + return activity; +} + +async function sendRequest(activity: Activity | null): Promise { + if (typeof activity !== "object") throw new Error("Invalid activity"); + + if (activity?.assets) { + const [largeImage, smallImage] = await AssetManager.getAssetIds(activity.application_id, [activity.assets.large_image, activity.assets.small_image]); + activity.assets.large_image = largeImage; + activity.assets.small_image = smallImage; + } + + if (activity?.buttons?.length) { + activity.buttons = activity.buttons.filter(x => x.label && x.url); + Object.assign(activity, { + metadata: { button_urls: activity.buttons.map(x => x.url) }, + buttons: activity.buttons.map(x => x.label) + }); + } + + // validate activity + for (const k in activity) { + if (activity[k] === undefined || activity[k] === null) delete activity[k]; + if (typeof activity[k].length === "number" && activity[k].length === 0) delete activity[k]; + if (typeof activity[k] === "object" && Object.keys(activity[k]).length === 0) delete activity[k]; + if (typeof activity[k] === "object" && activity[k].length === 0) delete activity[k]; + } + + FluxDispatcher.dispatch({ + type: "LOCAL_ACTIVITY_UPDATE", + activity: activity + }); + + return activity; +} + +export default new class RichPresence { + onLoad() { + logger.log("Sending RPC request"); + sendRequest(constructActivity()).catch().then(x => { + logger.log("RPC request sent"); + console.log(x); + }); + } + + onUnload() { + logger.log("Stopping RPC request"); + sendRequest(null); + } + + settings = Settings; +}; diff --git a/plugins/defs.d.ts b/plugins/defs.d.ts new file mode 100644 index 0000000..e1dde06 --- /dev/null +++ b/plugins/defs.d.ts @@ -0,0 +1,26 @@ +type Activity = { + name: string; + application_id: string; + flags: number; + type: number; + details?: string; + state?: string; + timestamps?: { + start: number; + end?: number; + }; + assets?: ActivityAssets; + buttons?: ActivityButton[]; +}; + +type ActivityButton = { + label: string; + url: string; +}; + +type ActivityAssets = { + large_image?: string; + large_text?: string; + small_image?: string; + small_text?: string; +};