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;
+};