diff --git a/src/plugins/appleMusic.desktop/index.tsx b/src/plugins/appleMusic.desktop/index.tsx
index 327fafc30..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 {
@@ -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/betterSettings/PluginsSubmenu.tsx b/src/plugins/betterSettings/PluginsSubmenu.tsx
new file mode 100644
index 000000000..b22f82a67
--- /dev/null
+++ b/src/plugins/betterSettings/PluginsSubmenu.tsx
@@ -0,0 +1,68 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { openPluginModal } from "@components/PluginSettings/PluginModal";
+import { isObjectEmpty } from "@utils/misc";
+import { Alerts, i18n, Menu, useMemo, useState } from "@webpack/common";
+
+import Plugins from "~plugins";
+
+function onRestartNeeded() {
+ Alerts.show({
+ title: "Restart required",
+ body:
You have changed settings that require a restart.
,
+ confirmText: "Restart now",
+ cancelText: "Later!",
+ onConfirm: () => location.reload()
+ });
+}
+
+export default function PluginsSubmenu() {
+ const sortedPlugins = useMemo(() => Object.values(Plugins)
+ .sort((a, b) => a.name.localeCompare(b.name)), []);
+ const [query, setQuery] = useState("");
+
+ const search = query.toLowerCase();
+ const include = (p: typeof Plugins[keyof typeof Plugins]) => (
+ Vencord.Plugins.isPluginEnabled(p.name)
+ && p.options && !isObjectEmpty(p.options)
+ && (
+ p.name.toLowerCase().includes(search)
+ || p.description.toLowerCase().includes(search)
+ || p.tags?.some(t => t.toLowerCase().includes(search))
+ )
+ );
+
+ const plugins = sortedPlugins.filter(include);
+
+ return (
+ <>
+ (
+
+ )}
+ />
+
+ {!!plugins.length && }
+
+ {plugins.map(p => (
+ openPluginModal(p, onRestartNeeded)}
+ />
+ ))}
+ >
+ );
+}
diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx
index 380ef8604..c28bae522 100644
--- a/src/plugins/betterSettings/index.tsx
+++ b/src/plugins/betterSettings/index.tsx
@@ -15,6 +15,8 @@ import { findByProps } from "@webpack";
import { ComponentDispatch, FocusLock, i18n, Menu, useEffect, useRef } from "@webpack/common";
import type { HTMLAttributes, ReactElement } from "react";
+import PluginsSubmenu from "./PluginsSubmenu";
+
type SettingsEntry = { section: string, label: string; };
const cl = classNameFactory("");
@@ -119,13 +121,21 @@ export default definePlugin({
},
{ // Settings cog context menu
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
- replacement: {
- match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
- replace: "$1$self.wrapMenu($2)"
- }
- }
+ replacement: [
+ {
+ match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
+ replace: "$1$self.wrapMenu($2)"
+ },
+ {
+ match: /case \i\.\i\.DEVELOPER_OPTIONS:return \i;/,
+ replace: "$&case 'VencordPlugins':return $self.PluginsSubmenu();"
+ }
+ ]
+ },
],
+ PluginsSubmenu,
+
// This is the very outer layer of the entire ui, so we can't wrap this in an ErrorBoundary
// without possibly also catching unrelated errors of children.
//
diff --git a/src/plugins/consoleJanitor/index.ts b/src/plugins/consoleJanitor/index.ts
index e847c4124..f5f43c06b 100644
--- a/src/plugins/consoleJanitor/index.ts
+++ b/src/plugins/consoleJanitor/index.ts
@@ -126,7 +126,7 @@ export default definePlugin({
}
},
{
- find: '"Handling ping: "',
+ find: '"_handleLocalVideoDisabled: ',
predicate: () => settings.store.disableNoisyLoggers,
replacement: {
match: /new \i\.\i\("RTCConnection\("\.concat.+?\)\)(?=,)/,
diff --git a/src/plugins/experiments/index.tsx b/src/plugins/experiments/index.tsx
index abbd08d14..0fc6b1a57 100644
--- a/src/plugins/experiments/index.tsx
+++ b/src/plugins/experiments/index.tsx
@@ -23,12 +23,13 @@ import { ErrorCard } from "@components/ErrorCard";
import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types";
-import { findByProps } from "@webpack";
+import { find, findByProps } from "@webpack";
import { Forms, React } from "@webpack/common";
import hideBugReport from "./hideBugReport.css?managed";
const KbdStyles = findByProps("key", "combo");
+const BugReporterExperiment = find(m => m?.definition?.id === "2024-09_bug_reporter");
const settings = definePluginSettings({
toolbarDevMenu: {
@@ -78,8 +79,8 @@ export default definePlugin({
{
find: "toolbar:function",
replacement: {
- match: /\i\.isStaff\(\)/,
- replace: "true"
+ match: /hasBugReporterAccess:(\i)/,
+ replace: "_hasBugReporterAccess:$1=true"
},
predicate: () => settings.store.toolbarDevMenu
},
@@ -91,10 +92,18 @@ export default definePlugin({
match: /\i\.isDM\(\)\|\|\i\.isThread\(\)/,
replace: "false",
}
+ },
+ // enable option to always record clips even if you are not streaming
+ {
+ find: "isDecoupledGameClippingEnabled(){",
+ replacement: {
+ match: /\i\.isStaff\(\)/,
+ replace: "true"
+ }
}
],
- start: () => enableStyle(hideBugReport),
+ start: () => !BugReporterExperiment.getCurrentConfig().hasBugReporterAccess && enableStyle(hideBugReport),
stop: () => disableStyle(hideBugReport),
settingsAboutComponent: () => {
diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx
index 7e4846596..ac1d5fb5a 100644
--- a/src/plugins/reviewDB/index.tsx
+++ b/src/plugins/reviewDB/index.tsx
@@ -91,7 +91,7 @@ export default definePlugin({
}
},
{
- find: ".PANEL,isInteractionSource:",
+ find: ".PANEL,interactionType:",
replacement: {
match: /{profileType:\i\.\i\.PANEL,children:\[/,
replace: "$&$self.BiteSizeReviewsButton({user:arguments[0].user}),"
diff --git a/src/webpack/common/types/menu.d.ts b/src/webpack/common/types/menu.d.ts
index d5f92d630..e2f46d1a5 100644
--- a/src/webpack/common/types/menu.d.ts
+++ b/src/webpack/common/types/menu.d.ts
@@ -72,6 +72,11 @@ export interface Menu {
onChange(value: number): void,
renderValue?(value: number): string,
}>;
+ MenuSearchControl: RC<{
+ query: string
+ onChange(query: string): void;
+ placeholder?: string;
+ }>;
}
export interface ContextMenuApi {
diff --git a/src/webpack/wreq.d.ts b/src/webpack/wreq.d.ts
index c0d22594d..a31529c88 100644
--- a/src/webpack/wreq.d.ts
+++ b/src/webpack/wreq.d.ts
@@ -102,7 +102,7 @@ export type WebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & {
*/
a: (this: WebpackRequire, module: Module, body: AsyncModuleBody, hasAwaitAfterDependencies?: boolean) => void;
/** getDefaultExport function for compatibility with non-harmony modules */
- n: (this: WebpackRequire, module: any) => () => ModuleExports;
+ n: (this: WebpackRequire, exports: any) => () => ModuleExports;
/**
* Create a fake namespace object, useful for faking an __esModule with a default export.
*