RichPresence: init
This commit is contained in:
parent
93d6d9b33d
commit
5938714c73
5 changed files with 312 additions and 24 deletions
|
|
@ -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;
|
||||
|
|
|
|||
14
plugins/RichPresence/manifest.json
Normal file
14
plugins/RichPresence/manifest.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
156
plugins/RichPresence/src/Settings.tsx
Normal file
156
plugins/RichPresence/src/Settings.tsx
Normal file
|
|
@ -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 (<ScrollView>
|
||||
<FormSection title="Basic">
|
||||
<FormInput
|
||||
title="Application Name"
|
||||
value={settings.app_name}
|
||||
placeholder="Vendetta"
|
||||
onChange={v => settings.app_name = v}
|
||||
/>
|
||||
<FormInput
|
||||
title="Application ID"
|
||||
value={settings.application_id}
|
||||
placeholder="1054951789318909972"
|
||||
onChange={v => settings.application_id = v}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
<FormInput
|
||||
title="Type"
|
||||
value={settings.type}
|
||||
placeholder="0"
|
||||
onChange={v => settings.type = v}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
<FormInput
|
||||
title="Details"
|
||||
value={settings.details}
|
||||
placeholder="Competitive"
|
||||
onChange={v => settings.details = v}
|
||||
/>
|
||||
<FormInput
|
||||
title="State"
|
||||
value={settings.state}
|
||||
placeholder="Playing Solo"
|
||||
onChange={v => settings.state = v}
|
||||
/>
|
||||
</FormSection>
|
||||
<FormSection title="Images">
|
||||
<FormInput
|
||||
title="Large Image Asset Key or URL"
|
||||
value={settings.large_image}
|
||||
placeholder="large_image_here"
|
||||
onChange={v => settings.large_image = v}
|
||||
/>
|
||||
<FormInput
|
||||
title="Large Image Text"
|
||||
value={settings.large_image_text}
|
||||
placeholder="Playing on Joe's lobby"
|
||||
disabled={!settings.large_image}
|
||||
onChange={v => settings.large_image_text = v}
|
||||
/>
|
||||
<FormInput
|
||||
title="Small Image Asset Key or URL"
|
||||
value={settings.small_image}
|
||||
placeholder="small_image_here"
|
||||
onChange={v => settings.small_image = v}
|
||||
/>
|
||||
<FormInput
|
||||
title="Small Image Text"
|
||||
value={settings.small_image_text}
|
||||
placeholder="Solo"
|
||||
disabled={!settings.small_image}
|
||||
onChange={v => settings.small_image_text = v}
|
||||
/>
|
||||
<FormText style={styles.subText}>
|
||||
{"Image assets key can be either a Discord app asset's name or a URL to an image."}
|
||||
</FormText>
|
||||
</FormSection>
|
||||
<FormSection title="Timestamps">
|
||||
<FormRow
|
||||
label="Enable timestamps"
|
||||
subLabel="Set whether to show timestamps or not"
|
||||
trailing={<FormSwitch
|
||||
value={settings.enable_timestamps}
|
||||
onValueChange={v => settings.enable_timestamps = v}
|
||||
/>}
|
||||
/>
|
||||
<FormInput
|
||||
title="Start Timestamp (seconds)"
|
||||
value={settings.start_timestamp}
|
||||
placeholder="1234567890"
|
||||
disabled={!settings.enable_timestamps}
|
||||
onChange={v => settings.start_timestamp = v}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
<FormInput
|
||||
title="End Timestamp (seconds)"
|
||||
value={settings.end_timestamp}
|
||||
placeholder="1234567890"
|
||||
disabled={!settings.enable_timestamps}
|
||||
onChange={v => settings.end_timestamp = v}
|
||||
keyboardType="numeric"
|
||||
/>
|
||||
<FormRow
|
||||
label="Use current time as start timestamp"
|
||||
subLabel="This will override the start timestamp you set above"
|
||||
disabled={!settings.enable_timestamps}
|
||||
onPress={() => settings.start_timestamp = String(Date.now())}
|
||||
trailing={FormRow.Arrow}
|
||||
/>
|
||||
<FormText style={styles.subText}>
|
||||
Leaving start timestamp blank will use the time the Discord started.
|
||||
</FormText>
|
||||
</FormSection>
|
||||
<FormSection title="Buttons">
|
||||
<FormInput
|
||||
title="First Button Text"
|
||||
value={settings.button1_text}
|
||||
placeholder="random link #1"
|
||||
onChange={v => settings.button1_text = v}
|
||||
/>
|
||||
<FormInput
|
||||
title="First Button URL"
|
||||
value={settings.button1_URL}
|
||||
placeholder="https://discord.com/vanityurl/dotcom/steakpants/flour/flower/index11.html"
|
||||
disabled={!settings.button1_text}
|
||||
onChange={v => settings.button1_URL = v}
|
||||
/>
|
||||
<FormInput
|
||||
title="Second Button Text"
|
||||
value={settings.button2_text}
|
||||
placeholder="random link #2"
|
||||
onChange={v => settings.button2_text = v}
|
||||
/>
|
||||
<FormInput
|
||||
title="Second Button URL"
|
||||
value={settings.button2_URL}
|
||||
placeholder="https://youtu.be/w0AOGeqOnFY"
|
||||
disabled={!settings.button2_text}
|
||||
onChange={v => settings.button2_URL = v}
|
||||
/>
|
||||
</FormSection>
|
||||
</ScrollView>);
|
||||
}
|
||||
116
plugins/RichPresence/src/index.ts
Normal file
116
plugins/RichPresence/src/index.ts
Normal file
|
|
@ -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<Activity> {
|
||||
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;
|
||||
};
|
||||
26
plugins/defs.d.ts
vendored
Normal file
26
plugins/defs.d.ts
vendored
Normal file
|
|
@ -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;
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue