diff --git a/ReplaceSettings.tsx b/ReplaceSettings.tsx index c0414fc..a8e424a 100644 --- a/ReplaceSettings.tsx +++ b/ReplaceSettings.tsx @@ -1,224 +1,224 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { CheckedTextInput } from "@components/CheckedTextInput"; -import { Margins } from "@utils/margins"; -import { identity } from "@utils/misc"; -import { Card, Forms, PresenceStore, React, Select, SnowflakeUtils, Switch, TextInput, UserStore } from "@webpack/common"; - -import { Activity, ActivityType, AppIdSetting, makeEmptyAppId } from "."; - -interface SettingsProps { - appIds: AppIdSetting[]; - update: () => void; - save: () => void; -} - -function isValidSnowflake(v: string) { - const regex = /^\d{17,20}$/; - return regex.test(v) && !Number.isNaN(SnowflakeUtils.extractTimestamp(v)); -} - -export function ReplaceTutorial() { - const activities: Activity[] = PresenceStore.getActivities(UserStore.getCurrentUser().id); - return ( - <> - IDs of currently running activities - { - activities.length === 0 ? No running activities : activities.map(activity => { return activity.flags !== 48 ? {activity.name}: {activity.application_id} : null; /* hide spotify */ }) - } - Available variables - - In all fields (except stream URL), you can put in variables that'll automatically be replaced by their original content: -
-                    :name:, :details:, :state:
-                    
- :large_image:, :large_text:, :small_image:, :small_text: -
-
- More details - - Leave a field empty to leave it as is. -
- Set a field to "null" to hide it on the presence. -
- You may need to reload Discord for changes to apply. -
- - ); -} - -export function ReplaceSettings({ appIds, update, save }: SettingsProps) { - async function onChange(val: string | boolean, index: number, key: string) { - if (index === appIds.length - 1) - appIds.push(makeEmptyAppId()); - - appIds[index][key] = val; - - save(); - update(); - } - - return ( - <> - { - appIds.map((setting, i) => - - { - isValidSnowflake(setting.appId) ? - { - onChange(value, i, "enabled"); - }} - className={Margins.bottom8} - hideBorder={true} - > - Apply edits to app - : Add new application - } - Application ID - { - onChange(v, i, "appId"); - }} - validate={v => - !v || isValidSnowflake(v) || "Invalid application ID" - } - /> - { - isValidSnowflake(setting.appId) && <> - New activity type - { + onChange(value, i, "newActivityType"); + }} + className={Margins.top8} + isSelected={value => setting.newActivityType === value} + serialize={identity} + /> + { + setting.newActivityType === ActivityType.STREAMING && + <> + Stream URL (must be YouTube or Twitch) + { + onChange(v, i, "newStreamUrl"); + }} + validate={v => { + return /https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+/.test(v) || "Invalid stream URL"; + }} + /> + + + } + { + setting.newActivityType !== ActivityType.STREAMING && + <> + New name {setting.newActivityType === ActivityType.PLAYING && "(first line)"} + { + onChange(v, i, "newName"); + }} + /> + + } + New details {setting.newActivityType === ActivityType.PLAYING ? "(second line)" : "(first line)"} + { + onChange(v, i, "newDetails"); + }} + /> + New state {setting.newActivityType === ActivityType.PLAYING ? "(third line)" : "(second line)"} + { + onChange(v, i, "newState"); + }} + /> + { + !setting.disableAssets && + <> + Large image + Text {setting.newActivityType !== ActivityType.PLAYING && "(also third line)"} + { + onChange(v, i, "newLargeImageText"); + }} + /> + URL + { + onChange(v, i, "newLargeImageUrl"); + }} + /> + Small image + Text + { + onChange(v, i, "newSmallImageText"); + }} + /> + URL + { + onChange(v, i, "newSmallImageUrl"); + }} + /> + + } + { + onChange(value, i, "disableAssets"); + }} + className={Margins.top8} + hideBorder={true} + style={{ marginBottom: "0" }} + > + Hide assets (large & small images) + + { + onChange(value, i, "disableTimestamps"); + }} + className={Margins.top8} + hideBorder={true} + style={{ marginBottom: "0" }} + > + Hide timestamps + + + } + + ) + } + + ); +} diff --git a/index.tsx b/index.tsx index b95d1b5..027b647 100644 --- a/index.tsx +++ b/index.tsx @@ -1,155 +1,154 @@ -/* - * Vencord, a Discord client mod - * Copyright (c) 2024 Vendicated and contributors - * SPDX-License-Identifier: GPL-3.0-or-later - */ - -import { DataStore } from "@api/index"; -import { definePluginSettings } from "@api/Settings"; -import { Devs } from "@utils/constants"; -import { useForceUpdater } from "@utils/react"; -import definePlugin, { OptionType } from "@utils/types"; -import { React } from "@webpack/common"; - -import { ReplaceSettings, ReplaceTutorial } from "./ReplaceSettings"; -import { parse } from "path"; - -const APP_IDS_KEY = "ReplaceActivityType_appids"; -export type AppIdSetting = { - disableAssets: boolean; - disableTimestamps: boolean; - appId: string; - enabled: boolean; - newActivityType: ActivityType; - newName: string, - newDetails: string, - newState: string, - newLargeImageUrl: string, - newLargeImageText: string, - newSmallImageUrl: string, - newSmallImageText: string; - newStreamUrl: string; -}; - -export interface Activity { - state: string; - details: string; - timestamps?: { - start?: number; - end?: number; - }; - url?: string; - assets: ActivityAssets; - buttons?: Array; - name: string; - application_id: string; - metadata?: { - button_urls?: Array; - }; - type: number; - flags: number; -} - -interface ActivityAssets { - large_image: string; - large_text: string; - small_image: string; - small_text: string; -} - -export const enum ActivityType { - PLAYING = 0, - STREAMING = 1, - LISTENING = 2, - WATCHING = 3, - COMPETING = 5 -} - -export const makeEmptyAppId: () => AppIdSetting = () => ({ - appId: "", - enabled: true, - newActivityType: ActivityType.PLAYING, - newName: "", - newDetails: "", - newState: "", - newLargeImageUrl: "", - newLargeImageText: "", - newSmallImageUrl: "", - newSmallImageText: "", - newStreamUrl: "", - disableTimestamps: false, - disableAssets: false -}); - -let appIds = [makeEmptyAppId()]; - -const settings = definePluginSettings({ - replacedAppIds: { - type: OptionType.COMPONENT, - description: "", - component: () => { - const update = useForceUpdater(); - return ( - <> - DataStore.set(APP_IDS_KEY, appIds)} - /> - - ); - } - }, -}); - -export default definePlugin({ - name: "RPCEditor", - description: "Edit the type and content of any Rich Presence", - authors: [Devs.Nyako, Devs.nin0dev], - patches: [ - { - find: '"LocalActivityStore"', - replacement: { - match: /\i\((\i)\)\{.{0,50}activity.{0,10}=\i;/, - replace: "$&$self.patchActivity($1.activity);", - } - } - ], - settings, - settingsAboutComponent: () => , - - async start() { - appIds = await DataStore.get(APP_IDS_KEY) ?? [makeEmptyAppId()]; - }, - parseField(text: string, originalActivity: Activity): string { - if (text === "null") return ""; - return text - .replaceAll(":name:", originalActivity.name) - .replaceAll(":details:", originalActivity.details) - .replaceAll(":state:", originalActivity.state) - .replaceAll(":large_image:", originalActivity.assets.large_image) - .replaceAll(":large_text:", originalActivity.assets.large_text) - .replaceAll(":small_image:", originalActivity.assets.small_image) - .replaceAll(":small_text:", originalActivity.assets.small_text); - }, - patchActivity(activity: Activity) { - if (!activity) return; - appIds.forEach(app => { - if (app.enabled && app.appId === activity.application_id) { - const oldActivity = { ...activity }; - activity.type = app.newActivityType; - if (app.newName) activity.name = this.parseField(app.newName, oldActivity); - if (app.newActivityType === ActivityType.STREAMING && app.newStreamUrl) activity.url = app.newStreamUrl; - if (app.newDetails) activity.details = this.parseField(app.newDetails, oldActivity); - if (app.newState) activity.state = this.parseField(app.newState, oldActivity); - if (app.newLargeImageText) activity.assets.large_text = this.parseField(app.newLargeImageText, oldActivity); - if (app.newLargeImageUrl) activity.assets.large_image = this.parseField(app.newLargeImageUrl, oldActivity); - if (app.newSmallImageText) activity.assets.small_text = this.parseField(app.newSmallImageText, oldActivity); - if (app.newSmallImageUrl) activity.assets.small_image = this.parseField(app.newSmallImageUrl, oldActivity); - // @ts-ignore here we are intentionally nulling assets - if (app.disableAssets) activity.assets = {}; - if (app.disableTimestamps) activity.timestamps = {}; - } - }); - }, -}); +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { DataStore } from "@api/index"; +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import { useForceUpdater } from "@utils/react"; +import definePlugin, { OptionType } from "@utils/types"; +import { React } from "@webpack/common"; + +import { ReplaceSettings, ReplaceTutorial } from "./ReplaceSettings"; + +const APP_IDS_KEY = "ReplaceActivityType_appids"; +export type AppIdSetting = { + disableAssets: boolean; + disableTimestamps: boolean; + appId: string; + enabled: boolean; + newActivityType: ActivityType; + newName: string, + newDetails: string, + newState: string, + newLargeImageUrl: string, + newLargeImageText: string, + newSmallImageUrl: string, + newSmallImageText: string; + newStreamUrl: string; +}; + +export interface Activity { + state: string; + details: string; + timestamps?: { + start?: number; + end?: number; + }; + url?: string; + assets: ActivityAssets; + buttons?: Array; + name: string; + application_id: string; + metadata?: { + button_urls?: Array; + }; + type: number; + flags: number; +} + +interface ActivityAssets { + large_image: string; + large_text: string; + small_image: string; + small_text: string; +} + +export const enum ActivityType { + PLAYING = 0, + STREAMING = 1, + LISTENING = 2, + WATCHING = 3, + COMPETING = 5 +} + +export const makeEmptyAppId: () => AppIdSetting = () => ({ + appId: "", + enabled: true, + newActivityType: ActivityType.PLAYING, + newName: "", + newDetails: "", + newState: "", + newLargeImageUrl: "", + newLargeImageText: "", + newSmallImageUrl: "", + newSmallImageText: "", + newStreamUrl: "", + disableTimestamps: false, + disableAssets: false +}); + +let appIds = [makeEmptyAppId()]; + +const settings = definePluginSettings({ + replacedAppIds: { + type: OptionType.COMPONENT, + description: "", + component: () => { + const update = useForceUpdater(); + return ( + <> + DataStore.set(APP_IDS_KEY, appIds)} + /> + + ); + } + }, +}); + +export default definePlugin({ + name: "RPCEditor", + description: "Edit the type and content of any Rich Presence", + authors: [Devs.Nyako, Devs.nin0dev], + patches: [ + { + find: '"LocalActivityStore"', + replacement: { + match: /\i\((\i)\)\{.{0,50}activity.{0,10}=\i;/, + replace: "$&$self.patchActivity($1.activity);", + } + } + ], + settings, + settingsAboutComponent: () => , + + async start() { + appIds = await DataStore.get(APP_IDS_KEY) ?? [makeEmptyAppId()]; + }, + parseField(text: string, originalActivity: Activity): string { + if (text === "null") return ""; + return text + .replaceAll(":name:", originalActivity.name) + .replaceAll(":details:", originalActivity.details) + .replaceAll(":state:", originalActivity.state) + .replaceAll(":large_image:", originalActivity.assets.large_image) + .replaceAll(":large_text:", originalActivity.assets.large_text) + .replaceAll(":small_image:", originalActivity.assets.small_image) + .replaceAll(":small_text:", originalActivity.assets.small_text); + }, + patchActivity(activity: Activity) { + if (!activity) return; + appIds.forEach(app => { + if (app.enabled && app.appId === activity.application_id) { + const oldActivity = { ...activity }; + activity.type = app.newActivityType; + if (app.newName) activity.name = this.parseField(app.newName, oldActivity); + if (app.newActivityType === ActivityType.STREAMING && app.newStreamUrl) activity.url = app.newStreamUrl; + if (app.newDetails) activity.details = this.parseField(app.newDetails, oldActivity); + if (app.newState) activity.state = this.parseField(app.newState, oldActivity); + if (app.newLargeImageText) activity.assets.large_text = this.parseField(app.newLargeImageText, oldActivity); + if (app.newLargeImageUrl) activity.assets.large_image = this.parseField(app.newLargeImageUrl, oldActivity); + if (app.newSmallImageText) activity.assets.small_text = this.parseField(app.newSmallImageText, oldActivity); + if (app.newSmallImageUrl) activity.assets.small_image = this.parseField(app.newSmallImageUrl, oldActivity); + // @ts-ignore here we are intentionally nulling assets + if (app.disableAssets) activity.assets = {}; + if (app.disableTimestamps) activity.timestamps = {}; + } + }); + }, +}); diff --git a/style.css b/style.css index 8ceda78..822a5ba 100644 --- a/style.css +++ b/style.css @@ -1,14 +1,14 @@ -.vc-rpceditor-horizontal { - display: flex; - justify-content: space-evenly; - -} - -.vc-rpceditor-horizontal div { - flex: 1; -} - - -.vc-rpceditor-horizontal div:first-child { - padding-right: 5px; -} +.vc-rpceditor-horizontal { + display: flex; + justify-content: space-evenly; + +} + +.vc-rpceditor-horizontal div { + flex: 1; +} + + +.vc-rpceditor-horizontal div:first-child { + padding-right: 5px; +}