{
export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
const settings = Settings.plugins[plugin.name];
- const isEnabled = () => settings.enabled ?? false;
+ const isEnabled = () => Vencord.Plugins.isPluginEnabled(plugin.name);
function toggleEnabled() {
const wasEnabled = isEnabled();
@@ -292,10 +292,10 @@ export default function PluginSettings() {
if (!pluginFilter(p)) continue;
- const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
+ const isRequired = p.required || p.isDependency || depMap[p.name]?.some(d => settings.plugins[d].enabled);
if (isRequired) {
- const tooltipText = p.required
+ const tooltipText = p.required || !depMap[p.name]
? "This plugin is required for Vencord to function."
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
diff --git a/src/components/VencordSettings/PatchHelperTab.tsx b/src/components/VencordSettings/PatchHelperTab.tsx
index e09a1dbf3..fd33c09df 100644
--- a/src/components/VencordSettings/PatchHelperTab.tsx
+++ b/src/components/VencordSettings/PatchHelperTab.tsx
@@ -382,6 +382,7 @@ function PatchHelper() {
Code
+
>
)}
diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx
index bb9d37894..f718ab11f 100644
--- a/src/components/VencordSettings/ThemesTab.tsx
+++ b/src/components/VencordSettings/ThemesTab.tsx
@@ -77,8 +77,16 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
Validator
This section will tell you whether your themes can successfully be loaded
- {themeLinks.map(link => (
- {
+ const { label, link } = (() => {
+ const match = /^@(light|dark) (.*)/.exec(rawLink);
+ if (!match) return { label: rawLink, link: rawLink };
+
+ const [, mode, link] = match;
+ return { label: `[${mode} mode only] ${link}`, link };
+ })();
+
+ return
- {link}
+ {label}
-
- ))}
+ ;
+ })}
>
);
@@ -296,6 +304,7 @@ function ThemesTab() {
Paste links to css files here
One link per line
+ You can prefix lines with @light or @dark to toggle them based on your Discord theme
Make sure to use direct links to files (raw or github.io)!
diff --git a/src/plugins/_api/badges/fixBadgeOverflow.css b/src/plugins/_api/badges/fixBadgeOverflow.css
deleted file mode 100644
index 348d0ff38..000000000
--- a/src/plugins/_api/badges/fixBadgeOverflow.css
+++ /dev/null
@@ -1,3 +0,0 @@
-[class*="profileBadges"] {
- flex: none;
-}
diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx
index cf00a0e29..c44d98b90 100644
--- a/src/plugins/_api/badges/index.tsx
+++ b/src/plugins/_api/badges/index.tsx
@@ -16,8 +16,6 @@
* along with this program. If not, see .
*/
-import "./fixBadgeOverflow.css";
-
import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
import DonateButton from "@components/DonateButton";
import ErrorBoundary from "@components/ErrorBoundary";
@@ -79,7 +77,7 @@ export default definePlugin({
replace: "...$1.props,$& $1.image??"
},
{
- match: /(?<=text:(\i)\.description,.{0,50})children:/,
+ match: /(?<=text:(\i)\.description,.{0,200})children:/,
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
},
// conditionally override their onClick with badge.onClick if it exists
diff --git a/src/plugins/_api/serverList.ts b/src/plugins/_api/serverList.ts
index f45bbf104..7904e78b0 100644
--- a/src/plugins/_api/serverList.ts
+++ b/src/plugins/_api/serverList.ts
@@ -34,7 +34,7 @@ export default definePlugin({
{
find: "Messages.SERVERS,children",
replacement: {
- match: /(?<=Messages\.SERVERS,children:).+?default:return null\}\}\)/,
+ match: /(?<=Messages\.SERVERS,children:)\i\.map\(\i\)/,
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
}
}
diff --git a/src/plugins/_core/noTrack.ts b/src/plugins/_core/noTrack.ts
index de1c20562..8d6a1e76d 100644
--- a/src/plugins/_core/noTrack.ts
+++ b/src/plugins/_core/noTrack.ts
@@ -48,7 +48,7 @@ export default definePlugin({
},
},
{
- find: ".METRICS,",
+ find: ".METRICS",
replacement: [
{
match: /this\._intervalId=/,
diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx
index 3cc020836..be220db1a 100644
--- a/src/plugins/_core/settings.tsx
+++ b/src/plugins/_core/settings.tsx
@@ -64,7 +64,7 @@ export default definePlugin({
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
},
{
- match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,30}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
+ match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,60}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
}
]
diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx
index de8e37c79..432896fc7 100644
--- a/src/plugins/_core/supportHelper.tsx
+++ b/src/plugins/_core/supportHelper.tsx
@@ -142,7 +142,7 @@ export default definePlugin({
required: true,
description: "Helps us provide support to you",
authors: [Devs.Ven],
- dependencies: ["CommandsAPI", "UserSettingsAPI", "MessageAccessoriesAPI"],
+ dependencies: ["UserSettingsAPI", "MessageAccessoriesAPI"],
settings,
diff --git a/src/plugins/accountPanelServerProfile/README.md b/src/plugins/accountPanelServerProfile/README.md
new file mode 100644
index 000000000..f837864b7
--- /dev/null
+++ b/src/plugins/accountPanelServerProfile/README.md
@@ -0,0 +1,7 @@
+# AccountPanelServerProfile
+
+Right click your account panel in the bottom left to view your profile in the current server
+
+
+
+
diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx
new file mode 100644
index 000000000..fe5df48ad
--- /dev/null
+++ b/src/plugins/accountPanelServerProfile/index.tsx
@@ -0,0 +1,134 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { definePluginSettings } from "@api/Settings";
+import ErrorBoundary from "@components/ErrorBoundary";
+import { Devs } from "@utils/constants";
+import { getCurrentChannel } from "@utils/discord";
+import definePlugin, { OptionType } from "@utils/types";
+import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
+import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common";
+import { User } from "discord-types/general";
+
+interface UserProfileProps {
+ popoutProps: Record;
+ currentUser: User;
+ originalPopout: () => React.ReactNode;
+}
+
+const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cannot be undefined");
+const styles = findByPropsLazy("accountProfilePopoutWrapper");
+
+let openAlternatePopout = false;
+let accountPanelRef: React.MutableRefObject | null> = { current: null };
+
+const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
+ const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
+
+ return (
+
+ {
+ openAlternatePopout = true;
+ accountPanelRef.current?.props.onMouseDown();
+ accountPanelRef.current?.props.onClick(e);
+ }}
+ />
+ settings.store.prioritizeServerProfile = !prioritizeServerProfile}
+ />
+
+ );
+}, { noop: true });
+
+const settings = definePluginSettings({
+ prioritizeServerProfile: {
+ type: OptionType.BOOLEAN,
+ description: "Prioritize Server Profile when left clicking your account panel",
+ default: false
+ }
+});
+
+export default definePlugin({
+ name: "AccountPanelServerProfile",
+ description: "Right click your account panel in the bottom left to view your profile in the current server",
+ authors: [Devs.Nuckyz, Devs.relitrix],
+ settings,
+
+ patches: [
+ {
+ find: ".Messages.ACCOUNT_SPEAKING_WHILE_MUTED",
+ group: true,
+ replacement: [
+ {
+ match: /(?<=\.SIZE_32\)}\);)/,
+ replace: "$self.useAccountPanelRef();"
+ },
+ {
+ match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/,
+ replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalPopout:()=>{${originalPopout}}})`
+ },
+ {
+ match: /\.AVATAR,children:.+?(?=renderPopout:)/,
+ replace: "$&onRequestClose:$self.onPopoutClose,"
+ },
+ {
+ match: /(?<=.avatarWrapper,)/,
+ replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
+ }
+ ]
+ }
+ ],
+
+ get accountPanelRef() {
+ return accountPanelRef;
+ },
+
+ useAccountPanelRef() {
+ useEffect(() => () => {
+ accountPanelRef.current = null;
+ }, []);
+
+ return (accountPanelRef = useRef(null));
+ },
+
+ openAccountPanelContextMenu(event: React.UIEvent) {
+ ContextMenuApi.openContextMenu(event, AccountPanelContextMenu);
+ },
+
+ onPopoutClose() {
+ openAlternatePopout = false;
+ },
+
+ UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalPopout }: UserProfileProps) => {
+ if (
+ (settings.store.prioritizeServerProfile && openAlternatePopout) ||
+ (!settings.store.prioritizeServerProfile && !openAlternatePopout)
+ ) {
+ return originalPopout();
+ }
+
+ const currentChannel = getCurrentChannel();
+ if (currentChannel?.getGuildId() == null) {
+ return originalPopout();
+ }
+
+ return (
+
+
+
+ );
+ }, { noop: true })
+});
diff --git a/src/plugins/alwaysExpandRoles/README.md b/src/plugins/alwaysExpandRoles/README.md
new file mode 100644
index 000000000..344268cbf
--- /dev/null
+++ b/src/plugins/alwaysExpandRoles/README.md
@@ -0,0 +1,3 @@
+# Always Expand Roles
+
+Always expands the role list in profile popouts
diff --git a/src/plugins/timeBarAllActivities/index.ts b/src/plugins/alwaysExpandRoles/index.ts
similarity index 62%
rename from src/plugins/timeBarAllActivities/index.ts
rename to src/plugins/alwaysExpandRoles/index.ts
index dcb809fd4..1c20b9777 100644
--- a/src/plugins/timeBarAllActivities/index.ts
+++ b/src/plugins/alwaysExpandRoles/index.ts
@@ -1,6 +1,6 @@
/*
* Vencord, a modification for Discord's desktop app
- * Copyright (c) 2022 Vendicated and contributors
+ * Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,20 +16,22 @@
* along with this program. If not, see .
*/
+import { migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
+migratePluginSettings("AlwaysExpandRoles", "ShowAllRoles");
export default definePlugin({
- name: "TimeBarAllActivities",
- description: "Adds the Spotify time bar to all activities if they have start and end timestamps",
- authors: [Devs.fawn],
+ name: "AlwaysExpandRoles",
+ description: "Always expands the role list in profile popouts",
+ authors: [Devs.surgedevs],
patches: [
{
- find: "}renderTimeBar(",
+ find: 'action:"EXPAND_ROLES"',
replacement: {
- match: /renderTimeBar\((.{1,3})\){.{0,50}?let/,
- replace: "renderTimeBar($1){let"
+ match: /(roles:\i(?=.+?(\i)\(!0\)[,;]\i\({action:"EXPAND_ROLES"}\)).+?\[\i,\2\]=\i\.useState\()!1\)/,
+ replace: (_, rest, setExpandedRoles) => `${rest}!0)`
}
}
- ],
+ ]
});
diff --git a/src/plugins/anonymiseFileNames/index.tsx b/src/plugins/anonymiseFileNames/index.tsx
index 526ccd12e..0e97bc7f8 100644
--- a/src/plugins/anonymiseFileNames/index.tsx
+++ b/src/plugins/anonymiseFileNames/index.tsx
@@ -71,7 +71,7 @@ export default definePlugin({
description: "Anonymise uploaded file names",
patches: [
{
- find: "instantBatchUpload:function",
+ find: "instantBatchUpload:",
replacement: {
match: /uploadFiles:(\i),/,
replace:
diff --git a/src/plugins/appleMusic.desktop/index.tsx b/src/plugins/appleMusic.desktop/index.tsx
index 6fa989cdd..f3148c36d 100644
--- a/src/plugins/appleMusic.desktop/index.tsx
+++ b/src/plugins/appleMusic.desktop/index.tsx
@@ -24,7 +24,7 @@ interface ActivityButton {
}
interface Activity {
- state: string;
+ state?: string;
details?: string;
timestamps?: {
start?: number;
@@ -52,8 +52,8 @@ const enum ActivityFlag {
export interface TrackData {
name: string;
- album: string;
- artist: string;
+ album?: string;
+ artist?: string;
appleMusicLink?: string;
songLink?: string;
@@ -61,8 +61,8 @@ export interface TrackData {
albumArtwork?: string;
artistArtwork?: string;
- playerPosition: number;
- duration: number;
+ playerPosition?: number;
+ duration?: number;
}
const enum AssetImageType {
@@ -120,7 +120,7 @@ const settings = definePluginSettings({
stateString: {
type: OptionType.STRING,
description: "Activity state format string",
- default: "{artist}"
+ default: "{artist} · {album}"
},
largeImageType: {
type: OptionType.SELECT,
@@ -155,8 +155,8 @@ const settings = definePluginSettings({
function customFormat(formatStr: string, data: TrackData) {
return formatStr
.replaceAll("{name}", data.name)
- .replaceAll("{album}", data.album)
- .replaceAll("{artist}", data.artist);
+ .replaceAll("{album}", data.album ?? "")
+ .replaceAll("{artist}", data.artist ?? "");
}
function getImageAsset(type: AssetImageType, data: TrackData) {
@@ -212,14 +212,16 @@ export default definePlugin({
const assets: ActivityAssets = {};
+ const isRadio = Number.isNaN(trackData.duration) && (trackData.playerPosition === 0);
+
if (settings.store.largeImageType !== AssetImageType.Disabled) {
assets.large_image = largeImageAsset;
- assets.large_text = customFormat(settings.store.largeTextString, trackData);
+ if (!isRadio) assets.large_text = customFormat(settings.store.largeTextString, trackData);
}
if (settings.store.smallImageType !== AssetImageType.Disabled) {
assets.small_image = smallImageAsset;
- assets.small_text = customFormat(settings.store.smallTextString, trackData);
+ if (!isRadio) assets.small_text = customFormat(settings.store.smallTextString, trackData);
}
const buttons: ActivityButton[] = [];
@@ -243,17 +245,17 @@ export default definePlugin({
name: customFormat(settings.store.nameString, trackData),
details: customFormat(settings.store.detailsString, trackData),
- state: customFormat(settings.store.stateString, trackData),
+ state: isRadio ? undefined : customFormat(settings.store.stateString, trackData),
- timestamps: (settings.store.enableTimestamps ? {
+ timestamps: (trackData.playerPosition && trackData.duration && settings.store.enableTimestamps) ? {
start: Date.now() - (trackData.playerPosition * 1000),
end: Date.now() - (trackData.playerPosition * 1000) + (trackData.duration * 1000),
- } : undefined),
+ } : undefined,
assets,
- buttons: buttons.length ? buttons.map(v => v.label) : undefined,
- metadata: { button_urls: buttons.map(v => v.url) || undefined, },
+ buttons: !isRadio && buttons.length ? buttons.map(v => v.label) : undefined,
+ metadata: !isRadio && buttons.length ? { button_urls: buttons.map(v => v.url) } : undefined,
type: settings.store.activityType,
flags: ActivityFlag.INSTANCE,
diff --git a/src/plugins/appleMusic.desktop/native.ts b/src/plugins/appleMusic.desktop/native.ts
index 2eb2a0757..7d69a85ae 100644
--- a/src/plugins/appleMusic.desktop/native.ts
+++ b/src/plugins/appleMusic.desktop/native.ts
@@ -11,37 +11,11 @@ import type { TrackData } from ".";
const exec = promisify(execFile);
-// function exec(file: string, args: string[] = []) {
-// return new Promise<{ code: number | null, stdout: string | null, stderr: string | null; }>((resolve, reject) => {
-// const process = spawn(file, args, { stdio: [null, "pipe", "pipe"] });
-
-// let stdout: string | null = null;
-// process.stdout.on("data", (chunk: string) => { stdout ??= ""; stdout += chunk; });
-// let stderr: string | null = null;
-// process.stderr.on("data", (chunk: string) => { stdout ??= ""; stderr += chunk; });
-
-// process.on("exit", code => { resolve({ code, stdout, stderr }); });
-// process.on("error", err => reject(err));
-// });
-// }
-
async function applescript(cmds: string[]) {
const { stdout } = await exec("osascript", cmds.map(c => ["-e", c]).flat());
return stdout;
}
-function makeSearchUrl(type: string, query: string) {
- const url = new URL("https://tools.applemediaservices.com/api/apple-media/music/US/search.json");
- url.searchParams.set("types", type);
- url.searchParams.set("limit", "1");
- url.searchParams.set("term", query);
- return url;
-}
-
-const requestOptions: RequestInit = {
- headers: { "user-agent": "Mozilla/5.0 (Windows NT 10.0; rv:125.0) Gecko/20100101 Firefox/125.0" },
-};
-
interface RemoteData {
appleMusicLink?: string,
songLink?: string,
@@ -51,6 +25,24 @@ interface RemoteData {
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null;
+const APPLE_MUSIC_BUNDLE_REGEX = /