/* * Vencord, a Discord client mod * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import "./settings.css"; import { isPluginEnabled } from "@api/PluginManager"; import { Divider } from "@components/Divider"; import { Heading } from "@components/Heading"; import { resolveError } from "@components/settings/tabs/plugins/components/Common"; import { debounce } from "@shared/debounce"; import { classNameFactory } from "@utils/css"; import { ActivityType } from "@vencord/discord-types/enums"; import { Select, Text, TextInput, useState } from "@webpack/common"; import CustomRPCPlugin, { setRpc, settings, TimestampMode } from "."; const cl = classNameFactory("vc-customRPC-settings-"); type SettingsKey = keyof typeof settings.store; interface TextOption { settingsKey: SettingsKey; label: string; disabled?: boolean; transform?: (value: string) => T; isValid?: (value: T) => true | string; } interface SelectOption { settingsKey: SettingsKey; label: string; disabled?: boolean; options: { label: string; value: T; default?: boolean; }[]; } const makeValidator = (maxLength: number, isRequired = false) => (value: string) => { if (isRequired && !value) return "This field is required."; if (value.length > maxLength) return `Must be not longer than ${maxLength} characters.`; return true; }; const maxLength128 = makeValidator(128); function isAppIdValid(value: string) { if (!/^\d{16,21}$/.test(value)) return "Must be a valid Discord ID."; return true; } const updateRPC = debounce(() => { setRpc(true); if (isPluginEnabled(CustomRPCPlugin.name)) setRpc(); }); function isStreamLinkDisabled() { return settings.store.type !== ActivityType.STREAMING; } function isStreamLinkValid(value: string) { if (!isStreamLinkDisabled() && !/https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+/.test(value)) return "Streaming link must be a valid URL."; if (value && value.length > 512) return "Streaming link must be not longer than 512 characters."; return true; } function parseNumber(value: string) { return value ? parseInt(value, 10) : 0; } function isNumberValid(value: number) { if (isNaN(value)) return "Must be a number."; if (value < 0) return "Must be a positive number."; return true; } function isUrlValid(value: string) { if (value && !/^https?:\/\/.+/.test(value)) return "Must be a valid URL."; return true; } function isImageKeyValid(value: string) { if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//.test(value)) return "Don't use a Discord link. Use an Imgur image link instead."; if (/https?:\/\/(?!i\.)?imgur\.com\//.test(value)) return "Imgur link must be a direct link to the image (e.g. https://i.imgur.com/...). Right click the image and click 'Copy image address'"; if (/https?:\/\/(?!media\.)?tenor\.com\//.test(value)) return "Tenor link must be a direct link to the image (e.g. https://media.tenor.com/...). Right click the GIF and click 'Copy image address'"; return true; } function PairSetting(props: { data: [TextOption, TextOption]; }) { const [left, right] = props.data; return (
); } function SingleSetting({ settingsKey, label, disabled, isValid, transform }: TextOption) { const [state, setState] = useState(settings.store[settingsKey] ?? ""); const [error, setError] = useState(null); function handleChange(newValue: any) { if (transform) newValue = transform(newValue); const valid = isValid?.(newValue) ?? true; setState(newValue); setError(resolveError(valid)); if (valid === true) { settings.store[settingsKey] = newValue; updateRPC(); } } return (
{label} {error && {error}}
); } function SelectSetting({ settingsKey, label, options, disabled }: SelectOption) { return (
{label}