Compare commits

...

37 commits

Author SHA1 Message Date
93d1bb5c9f fixed merge 2025-04-03 09:31:58 +02:00
Vendicated
93f98cee2c
use correct card background colour on new ui 2025-04-02 05:49:15 +02:00
OutCraft
dcb31ca849
Fix inconsistency in VcNarrator default options (#3246) 2025-04-02 03:24:24 +00:00
khcrysalis
dcd4531327
Settings: make donor message less misleading for people without a badge (#3264) 2025-04-02 05:04:38 +02:00
Vendicated
188fd48659
pnpm patch: pass through cli args 2025-04-02 04:55:24 +02:00
khcrysalis
1126dc6e66
Settings: update addon cards for new ui (#3311) 2025-04-02 02:35:08 +00:00
nin0dev
f075fed236
SpotifyControls: make panel look more in line with visual refresh (#3312) 2025-04-02 04:30:10 +02:00
sadan4
cef806a243
PlainFolderIcon: Fix on new UI (#3317) 2025-04-02 02:22:47 +00:00
Vendicated
95bd8c831c
NSFWGateBypass: bypass new UK/Australia gate
Co-Authored-By: dotle31 <abacubabacus@gmail.com>
2025-04-02 03:41:55 +02:00
sadan4
8ca91354ac
fix MessageDecorationsApi (#3337) 2025-04-02 03:09:45 +02:00
sadan4
b980320d0a
ThemeAttributes: Fix null handling (#3308) 2025-04-02 02:43:17 +02:00
Vendicated
d563b66842
fix: do not limit text settings to 999 chars
Fixes https://github.com/Vendicated/Vencord/issues/3322
2025-03-27 02:18:20 +01:00
Vendicated
d62be1b94a
SupportHelper: Enable in more channels 2025-03-27 02:18:19 +01:00
8085325814 Merge branch 'main' of https://github.com/Vendicated/Vencord 2025-03-25 17:46:05 +01:00
Nuckyz
b3bff83dd5
Bump to 1.11.7 2025-03-21 09:34:41 -03:00
sadan4
7eec2e5d5b
CommandsAPI: Fix issue with overlapping IDS (#3271) 2025-03-21 12:33:37 +00:00
sadan4
d178dcc480
IrcColors: Dont color wumpus in Role Settings (#3272) 2025-03-21 12:26:49 +00:00
sadan4
16910c95ad
VolumeBooster: Fix error when going back to the default device (#3273) 2025-03-21 12:24:03 +00:00
sadan4
94ee0c518a
InvisibleChat: Fix crashing when message contains a link (#3303) 2025-03-21 12:22:58 +00:00
sadan4
c4fc9ac8e0
Fix plugins for Discord update (#3298)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2025-03-21 12:18:12 +00:00
sadan4
48868f01fe
MessageLatency: Fix off by one error on some deltas (#3297) 2025-03-18 03:49:52 +01:00
Sukka
dcb7a593e5
Replace unpkg with jsDelivr (#3291) 2025-03-15 18:32:14 +01:00
Nuckyz
6f5fd5d0b6
Fix BadgesAPI and IrcColors 2025-03-14 23:33:24 -03:00
92406bd718 Merge branch 'main' of https://github.com/Vendicated/Vencord 2025-03-14 15:35:59 +01:00
sadan4
4391fcc21b
AccountPanelServerProfile: Fix not working (#3286) 2025-03-12 21:17:18 -03:00
sadan4
c5300713b2
EmoteCloner: Fix crashing (#3275) 2025-03-07 02:28:05 +01:00
sadan4
5eb4435463
Fix plugins using ApplicationAssetUtils (like CustomRPC) (#3270) 2025-03-06 18:23:05 -03:00
sadan4
c32680100e
Fix plugins using OAuth2AuthorizeModal (#3266) 2025-03-04 21:17:33 +00:00
sadan4
678da62723
Fix BadgesAPI not working (#3267)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2025-03-04 18:15:42 -03:00
Nuckyz
721aca6e1a
ShikiCodeblocks: Fix codeblocks line breaking 2025-03-01 21:12:53 -03:00
Nuckyz
0f384419d5
DynamicImageModalAPI: Include new image modal too 2025-02-28 19:54:05 -03:00
sadan4
11715da9e0
Fix ShowMeYourName not working (#3259) 2025-02-28 04:26:52 -03:00
Nuckyz
3de661585c
Bump to 1.11.6 2025-02-27 21:38:51 -03:00
sadan4
e88af36be9
ConsoleJanitor: Support allowing log levels (#3255)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
2025-02-27 21:32:09 -03:00
Nuckyz
8f65d3cae9
ImageZoom: Fix for new media viewer 2025-02-27 21:05:43 -03:00
Nuckyz
aab09ac249
Fix ChatInputButtonAPI 2025-02-27 18:55:55 -03:00
sadan4
ff82532a18
GameActivityToggle: Fix showing button (#3256) 2025-02-26 22:15:02 -03:00
44 changed files with 939 additions and 741 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.11.5", "version": "1.11.7",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": { "bugs": {
@ -25,8 +25,8 @@
"watchWeb": "pnpm buildWeb --watch", "watchWeb": "pnpm buildWeb --watch",
"generatePluginJson": "tsx scripts/generatePluginList.ts", "generatePluginJson": "tsx scripts/generatePluginList.ts",
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types --allowJs false", "generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types --allowJs false",
"inject": "node scripts/runInstaller.mjs", "inject": "node scripts/runInstaller.mjs -- --install",
"uninject": "node scripts/runInstaller.mjs", "uninject": "node scripts/runInstaller.mjs -- --uninstall",
"lint": "eslint", "lint": "eslint",
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins", "lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
"lint:fix": "pnpm lint --fix", "lint:fix": "pnpm lint --fix",
@ -41,22 +41,22 @@
"fflate": "^0.8.2", "fflate": "^0.8.2",
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3", "gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
"monaco-editor": "^0.52.2", "monaco-editor": "^0.52.2",
"nanoid": "^5.0.9", "nanoid": "^5.1.5",
"virtual-merge": "^1.0.1" "virtual-merge": "^1.0.1"
}, },
"devDependencies": { "devDependencies": {
"@stylistic/eslint-plugin": "^4.0.0", "@stylistic/eslint-plugin": "^4.2.0",
"@types/chrome": "^0.0.304", "@types/chrome": "^0.0.312",
"@types/diff": "^7.0.1", "@types/diff": "^7.0.2",
"@types/lodash": "^4.17.14", "@types/lodash": "^4.17.14",
"@types/node": "^22.10.5", "@types/node": "^22.13.13",
"@types/react": "^19.0.10", "@types/react": "^19.0.10",
"@types/react-dom": "^19.0.4", "@types/react-dom": "^19.0.4",
"@types/yazl": "^2.4.5", "@types/yazl": "^2.4.5",
"diff": "^7.0.0", "diff": "^7.0.0",
"discord-types": "^1.3.26", "discord-types": "^1.3.26",
"esbuild": "^0.25.0", "esbuild": "^0.25.1",
"eslint": "^9.20.1", "eslint": "9.20.1",
"eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "2.1.0", "eslint-plugin-path-alias": "2.1.0",
"eslint-plugin-react": "^7.37.3", "eslint-plugin-react": "^7.37.3",
@ -66,17 +66,17 @@
"highlight.js": "11.11.1", "highlight.js": "11.11.1",
"html-minifier-terser": "^7.2.0", "html-minifier-terser": "^7.2.0",
"moment": "^2.22.2", "moment": "^2.22.2",
"puppeteer-core": "^24.2.1", "puppeteer-core": "^24.4.0",
"standalone-electron-types": "^34.2.0", "standalone-electron-types": "^34.2.0",
"stylelint": "^16.12.0", "stylelint": "^16.17.0",
"stylelint-config-standard": "^37.0.0", "stylelint-config-standard": "^37.0.0",
"ts-patch": "^3.3.0", "ts-patch": "^3.3.0",
"ts-pattern": "^5.6.0", "ts-pattern": "^5.6.0",
"tsx": "^4.19.2", "tsx": "^4.19.3",
"type-fest": "^4.31.0", "type-fest": "^4.38.0",
"typescript": "^5.7.2", "typescript": "^5.8.2",
"typescript-eslint": "^8.19.0", "typescript-eslint": "^8.28.0",
"typescript-transform-paths": "^3.5.3", "typescript-transform-paths": "^3.5.5",
"zip-local": "^0.3.5" "zip-local": "^0.3.5"
}, },
"packageManager": "pnpm@10.4.1", "packageManager": "pnpm@10.4.1",

1172
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -118,8 +118,11 @@ const installerBin = await ensureBinary();
console.log("Now running Installer..."); console.log("Now running Installer...");
const argStart = process.argv.indexOf("--");
const args = argStart === -1 ? [] : process.argv.slice(argStart + 1);
try { try {
execFileSync(installerBin, { execFileSync(installerBin, args, {
stdio: "inherit", stdio: "inherit",
env: { env: {
...process.env, ...process.env,

View file

@ -31,6 +31,7 @@ export const commands = {} as Record<string, Command>;
// hack for plugins being evaluated before we can grab these from webpack // hack for plugins being evaluated before we can grab these from webpack
const OptPlaceholder = Symbol("OptionalMessageOption") as any as Option; const OptPlaceholder = Symbol("OptionalMessageOption") as any as Option;
const ReqPlaceholder = Symbol("RequiredMessageOption") as any as Option; const ReqPlaceholder = Symbol("RequiredMessageOption") as any as Option;
/** /**
* Optional message option named "message" you can use in commands. * Optional message option named "message" you can use in commands.
* Used in "tableflip" or "shrug" * Used in "tableflip" or "shrug"
@ -44,11 +45,16 @@ export let OptionalMessageOption: Option = OptPlaceholder;
*/ */
export let RequiredMessageOption: Option = ReqPlaceholder; export let RequiredMessageOption: Option = ReqPlaceholder;
// Discord's command list has random gaps for some reason, which can cause issues while rendering the commands
// Add this offset to every added command to keep them unique
let commandIdOffset: number;
export const _init = function (cmds: Command[]) { export const _init = function (cmds: Command[]) {
try { try {
BUILT_IN = cmds; BUILT_IN = cmds;
OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0]; OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0];
RequiredMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "me")!.options![0]; RequiredMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "me")!.options![0];
commandIdOffset = Math.abs(BUILT_IN.map(x => Number(x.id)).sort((x, y) => x - y)[0]) - BUILT_IN.length;
} catch (e) { } catch (e) {
new Logger("CommandsAPI").error("Failed to load CommandsApi", e, " - cmds is", cmds); new Logger("CommandsAPI").error("Failed to load CommandsApi", e, " - cmds is", cmds);
} }
@ -142,7 +148,7 @@ export function registerCommand<C extends Command>(command: C, plugin: string) {
command.isVencordCommand = true; command.isVencordCommand = true;
command.untranslatedName ??= command.name; command.untranslatedName ??= command.name;
command.untranslatedDescription ??= command.description; command.untranslatedDescription ??= command.description;
command.id ??= `-${BUILT_IN.length + 1}`; command.id ??= `-${BUILT_IN.length + commandIdOffset + 1}`;
command.applicationId ??= "-1"; // BUILT_IN; command.applicationId ??= "-1"; // BUILT_IN;
command.type ??= ApplicationCommandType.CHAT_INPUT; command.type ??= ApplicationCommandType.CHAT_INPUT;
command.inputType ??= ApplicationCommandInputType.BUILT_IN_TEXT; command.inputType ??= ApplicationCommandInputType.BUILT_IN_TEXT;

View file

@ -51,6 +51,7 @@ export function SettingTextComponent({ option, pluginSettings, definedSettings,
onChange={handleChange} onChange={handleChange}
placeholder={option.placeholder ?? "Enter a value"} placeholder={option.placeholder ?? "Enter a value"}
disabled={option.disabled?.call(definedSettings) ?? false} disabled={option.disabled?.call(definedSettings) ?? false}
maxLength={null}
{...option.componentProps} {...option.componentProps}
/> />
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>} {error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}

View file

@ -115,7 +115,7 @@ function VencordSettings() {
<SpecialCard <SpecialCard
title="Donations" title="Donations"
subtitle="Thank you for donating!" subtitle="Thank you for donating!"
description="All Vencord users can see your badge! You can change it at any time by messaging @vending.machine." description="You can manage your perks at any time by messaging @vending.machine."
cardImage={VENNIE_DONATOR_IMAGE} cardImage={VENNIE_DONATOR_IMAGE}
backgroundImage={DONOR_BACKGROUND_IMAGE} backgroundImage={DONOR_BACKGROUND_IMAGE}
backgroundColor="#ED87A9" backgroundColor="#ED87A9"

View file

@ -11,6 +11,11 @@
box-sizing: border-box; box-sizing: border-box;
} }
.visual-refresh .vc-addon-card {
background-color: var(--card-primary-bg);
border: 1px solid var(--border-subtle);
}
.vc-addon-card-disabled { .vc-addon-card-disabled {
opacity: 0.6; opacity: 0.6;
} }
@ -21,6 +26,11 @@
box-shadow: var(--elevation-high); box-shadow: var(--elevation-high);
} }
.visual-refresh .vc-addon-card:hover {
/* same as non-hover, here to overwrite the non-refresh hover background */
background-color: var(--card-primary-bg);
}
.vc-addon-header { .vc-addon-header {
margin-top: auto; margin-top: auto;
display: flex; display: flex;

View file

@ -36,6 +36,14 @@
outline-offset: 2px; outline-offset: 2px;
} }
.visual-refresh .vc-settings-quickActions-pill {
background: var(--button-secondary-background);
}
.visual-refresh .vc-settings-quickActions-pill:hover {
background: var(--button-secondary-background-hover);
}
.vc-settings-quickActions-img { .vc-settings-quickActions-img {
width: 24px; width: 24px;
height: 24px; height: 24px;

View file

@ -101,7 +101,7 @@ if (IS_VESKTOP || !IS_VANILLA) {
// TODO: Restrict this to only imported packages with fixed version. // TODO: Restrict this to only imported packages with fixed version.
// Perhaps auto generate with esbuild // Perhaps auto generate with esbuild
csp["script-src"] ??= []; csp["script-src"] ??= [];
csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com"); csp["script-src"].push("'unsafe-eval'", "https://cdn.jsdelivr.net", "https://cdnjs.cloudflare.com");
headers[header] = [stringifyPolicy(csp)]; headers[header] = [stringifyPolicy(csp)];
} }
}; };

View file

@ -65,15 +65,15 @@ export default definePlugin({
{ {
find: ".FULL_SIZE]:26", find: ".FULL_SIZE]:26",
replacement: { replacement: {
match: /(?<=(\i)=\(0,\i\.\i\)\(\i\);)return 0===\i.length\?/, match: /(?=;return 0===(\i)\.length\?)(?<=(\i)\.useMemo.+?)/,
replace: "$1.unshift(...$self.getBadges(arguments[0].displayProfile));$&" replace: ";$1=$2.useMemo(()=>[...$self.getBadges(arguments[0].displayProfile),...$1],[$1])"
} }
}, },
{ {
find: ".description,delay:", find: "#{intl::PROFILE_USER_BADGES}",
replacement: [ replacement: [
{ {
match: /(alt:" ","aria-hidden":!0,src:)(.{0,20}(\i)\.icon\))/, match: /(alt:" ","aria-hidden":!0,src:)(.+?)(?=,)(?<=href:(\i)\.link.+?)/,
replace: (_, rest, originalSrc, badge) => `...${badge}.props,${rest}${badge}.image??(${originalSrc})` replace: (_, rest, originalSrc, badge) => `...${badge}.props,${rest}${badge}.image??(${originalSrc})`
}, },
{ {

View file

@ -17,7 +17,7 @@ export default definePlugin({
find: '"sticker")', find: '"sticker")',
replacement: { replacement: {
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed // FIXME(Bundler change related): Remove old compatiblity once enough time has passed
match: /return\((!)?\i\.\i(?:\|\||&&)(?=\(\i\.isDM.+?(\i)\.push)/, match: /return\((!)?\i\.\i(?:\|\||&&)(?=\(.+?(\i)\.push)/,
replace: (m, not, children) => not replace: (m, not, children) => not
? `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),true)&&` ? `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),true)&&`
: `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),false)||` : `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),false)||`

View file

@ -14,11 +14,18 @@ export default definePlugin({
description: "Allows you to omit either width or height when opening an image modal", description: "Allows you to omit either width or height when opening an image modal",
patches: [ patches: [
{ {
find: "SCALE_DOWN:", find: ".contain,SCALE_DOWN:",
replacement: { replacement: {
match: /(?<="IMAGE"===\i\?)\i(?=\?)/, match: /(?<="IMAGE"===\i\?)\i(?=\?)/,
replace: "true" replace: "true"
} }
},
{
find: ".dimensionlessImage,",
replacement: {
match: /(?<="IMAGE"===\i&&\(\i=)\i(?=\?)/,
replace: "true"
}
} }
] ]
}); });

View file

@ -32,7 +32,7 @@ export default definePlugin({
{ {
find: '"Message Username"', find: '"Message Username"',
replacement: { replacement: {
match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/, match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?renderPopout:.+?(?=\])/,
replace: "$&,Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])" replace: "$&,Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
} }
} }

View file

@ -22,7 +22,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab"; import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, KNOWN_ISSUES_CHANNEL_ID, REGULAR_ROLE_ID, SUPPORT_CHANNEL_ID, VENBOT_USER_ID, VENCORD_GUILD_ID } from "@utils/constants"; import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, KNOWN_ISSUES_CHANNEL_ID, REGULAR_ROLE_ID, SUPPORT_CATEGORY_ID, SUPPORT_CHANNEL_ID, VENBOT_USER_ID, VENCORD_GUILD_ID } from "@utils/constants";
import { sendMessage } from "@utils/discord"; import { sendMessage } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
@ -32,7 +32,8 @@ import { onlyOnce } from "@utils/onlyOnce";
import { makeCodeblock } from "@utils/text"; import { makeCodeblock } from "@utils/text";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { checkForUpdates, isOutdated, update } from "@utils/updater"; import { checkForUpdates, isOutdated, update } from "@utils/updater";
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common"; import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, PermissionsBits, PermissionStore, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
import { Channel } from "discord-types/general";
import { JSX } from "react"; import { JSX } from "react";
import gitHash from "~git-hash"; import gitHash from "~git-hash";
@ -42,10 +43,8 @@ import SettingsPlugin from "./settings";
const CodeBlockRe = /```js\n(.+?)```/s; const CodeBlockRe = /```js\n(.+?)```/s;
const AllowedChannelIds = [ const AdditionalAllowedChannelIds = [
SUPPORT_CHANNEL_ID,
"1024286218801926184", // Vencord > #bot-spam "1024286218801926184", // Vencord > #bot-spam
"1033680203433660458", // Vencord > #v
]; ];
const TrustedRolesIds = [ const TrustedRolesIds = [
@ -58,6 +57,8 @@ const AsyncFunction = async function () { }.constructor;
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!; const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
const isSupportAllowedChannel = (channel: Channel) => channel.parent_id === SUPPORT_CATEGORY_ID || AdditionalAllowedChannelIds.includes(channel.id);
async function forceUpdate() { async function forceUpdate() {
const outdated = await checkForUpdates(); const outdated = await checkForUpdates();
if (outdated) { if (outdated) {
@ -155,20 +156,21 @@ export default definePlugin({
{ {
name: "vencord-debug", name: "vencord-debug",
description: "Send Vencord debug info", description: "Send Vencord debug info",
predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id), predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || isSupportAllowedChannel(ctx.channel),
execute: async () => ({ content: await generateDebugInfoMessage() }) execute: async () => ({ content: await generateDebugInfoMessage() })
}, },
{ {
name: "vencord-plugins", name: "vencord-plugins",
description: "Send Vencord plugin list", description: "Send Vencord plugin list",
predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id), predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || isSupportAllowedChannel(ctx.channel),
execute: () => ({ content: generatePluginList() }) execute: () => ({ content: generatePluginList() })
} }
], ],
flux: { flux: {
async CHANNEL_SELECT({ channelId }) { async CHANNEL_SELECT({ channelId }) {
if (channelId !== SUPPORT_CHANNEL_ID) return; const isSupportChannel = channelId === SUPPORT_CHANNEL_ID || ChannelStore.getChannel(channelId)?.parent_id === SUPPORT_CATEGORY_ID;
if (!isSupportChannel) return;
const selfId = UserStore.getCurrentUser()?.id; const selfId = UserStore.getCurrentUser()?.id;
if (!selfId || isPluginDev(selfId)) return; if (!selfId || isPluginDev(selfId)) return;
@ -239,7 +241,7 @@ export default definePlugin({
!IS_UPDATER_DISABLED !IS_UPDATER_DISABLED
&& ( && (
(props.channel.id === KNOWN_ISSUES_CHANNEL_ID) || (props.channel.id === KNOWN_ISSUES_CHANNEL_ID) ||
(props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID) (props.channel.parent_id === SUPPORT_CATEGORY_ID && props.message.author.id === VENBOT_USER_ID)
) )
&& props.message.content?.includes("update"); && props.message.content?.includes("update");
@ -265,7 +267,7 @@ export default definePlugin({
); );
} }
if (props.channel.id === SUPPORT_CHANNEL_ID) { if (props.channel.parent_id === SUPPORT_CATEGORY_ID && PermissionStore.can(PermissionsBits.SEND_MESSAGES, props.channel)) {
if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) { if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) {
buttons.push( buttons.push(
<Button <Button

View file

@ -73,8 +73,8 @@ export default definePlugin({
group: true, group: true,
replacement: [ replacement: [
{ {
match: /(?<=\.AVATAR_SIZE\);)/, match: /let{speaking:\i/,
replace: "$self.useAccountPanelRef();" replace: "$self.useAccountPanelRef();$&"
}, },
{ {
match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/, match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/,

View file

@ -44,15 +44,15 @@ export default definePlugin({
find: "#{intl::GUILD_OWNER}),children:", find: "#{intl::GUILD_OWNER}),children:",
replacement: { replacement: {
match: /(\.CUSTOM_STATUS.+?animate:)\i/, match: /(\.CUSTOM_STATUS.+?animate:)\i/,
replace: (_, rest) => `${rest}!0` replace: "$1!0"
} }
}, },
{ {
// Guild Banner // Guild Banner
find: ".animatedBannerHoverLayer,onMouseEnter:", find: ".animatedBannerHoverLayer,onMouseEnter:",
replacement: { replacement: {
match: /(?<=guildBanner:\i,animate:)\i(?=}\))/, match: /(\.headerContent.+?guildBanner:\i,animate:)\i/,
replace: "!0" replace: "$1!0"
} }
} }
] ]

View file

@ -5,8 +5,11 @@
*/ */
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { ErrorBoundary, Flex } from "@components/index";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType, StartAt } from "@utils/types"; import { Margins } from "@utils/margins";
import definePlugin, { defineDefault, OptionType, StartAt } from "@utils/types";
import { Checkbox, Forms, Text } from "@webpack/common";
const Noop = () => { }; const Noop = () => { };
const NoopLogger = { const NoopLogger = {
@ -24,6 +27,48 @@ const NoopLogger = {
const logAllow = new Set(); const logAllow = new Set();
interface AllowLevels {
error: boolean;
warn: boolean;
trace: boolean;
log: boolean;
info: boolean;
debug: boolean;
}
interface AllowLevelSettingProps {
settingKey: keyof AllowLevels;
}
function AllowLevelSetting({ settingKey }: AllowLevelSettingProps) {
const { allowLevel } = settings.use(["allowLevel"]);
const value = allowLevel[settingKey];
return (
<Checkbox
value={value}
onChange={(_, newValue) => settings.store.allowLevel[settingKey] = newValue}
size={20}
>
<Text variant="text-sm/normal">{settingKey[0].toUpperCase() + settingKey.slice(1)}</Text>
</Checkbox>
);
}
const AllowLevelSettings = ErrorBoundary.wrap(() => {
return (
<Forms.FormSection>
<Forms.FormTitle tag="h3">Filter List</Forms.FormTitle>
<Forms.FormText className={Margins.bottom8} type={Forms.FormText.Types.DESCRIPTION}>Always allow loggers of these types</Forms.FormText>
<Flex flexDirection="row">
{Object.keys(settings.store.allowLevel).map(key => (
<AllowLevelSetting key={key} settingKey={key as keyof AllowLevels} />
))}
</Flex>
</Forms.FormSection>
);
});
const settings = definePluginSettings({ const settings = definePluginSettings({
disableLoggers: { disableLoggers: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
@ -45,6 +90,18 @@ const settings = definePluginSettings({
logAllow.clear(); logAllow.clear();
newVal.split(";").map(x => x.trim()).forEach(logAllow.add.bind(logAllow)); newVal.split(";").map(x => x.trim()).forEach(logAllow.add.bind(logAllow));
} }
},
allowLevel: {
type: OptionType.COMPONENT,
component: AllowLevelSettings,
default: defineDefault<AllowLevels>({
error: true,
warn: false,
trace: false,
log: false,
info: false,
debug: false
})
} }
}); });
@ -61,8 +118,9 @@ export default definePlugin({
}, },
NoopLogger: () => NoopLogger, NoopLogger: () => NoopLogger,
shouldLog(logger: string) {
return logAllow.has(logger); shouldLog(logger: string, level: keyof AllowLevels) {
return logAllow.has(logger) || settings.store.allowLevel[level] === true;
}, },
patches: [ patches: [
@ -136,13 +194,13 @@ export default definePlugin({
replace: "" replace: ""
} }
}, },
// Patches discords generic logger function // Patches Discord generic logger function
{ {
find: "Σ:", find: "Σ:",
predicate: () => settings.store.disableLoggers, predicate: () => settings.store.disableLoggers,
replacement: { replacement: {
match: /(?<=&&)(?=console)/, match: /(?<=&&)(?=console)/,
replace: "$self.shouldLog(arguments[0])&&" replace: "$self.shouldLog(arguments[0],arguments[1])&&"
} }
}, },
{ {

View file

@ -99,7 +99,7 @@ export default definePlugin({
}, },
// Current user area, at bottom of channels/dm list // Current user area, at bottom of channels/dm list
{ {
find: "renderAvatarWithPopout(){", find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
replacement: [ replacement: [
// Use Decor avatar decoration hook // Use Decor avatar decoration hook
{ {

View file

@ -25,11 +25,14 @@ import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/moda
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByCodeLazy, findStoreLazy } from "@webpack"; import { findByCodeLazy, findStoreLazy } from "@webpack";
import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common"; import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
import { Guild } from "discord-types/general";
import { Promisable } from "type-fest"; import { Promisable } from "type-fest";
const StickersStore = findStoreLazy("StickersStore"); const StickersStore = findStoreLazy("StickersStore");
const uploadEmoji = findByCodeLazy(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START"); const uploadEmoji = findByCodeLazy(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START");
const getGuildMaxEmojiSlots = findByCodeLazy(".additionalEmojiSlots") as (guild: Guild) => number;
interface Sticker { interface Sticker {
t: "Sticker"; t: "Sticker";
description: string; description: string;
@ -125,7 +128,7 @@ function getGuildCandidates(data: Data) {
const { isAnimated } = data as Emoji; const { isAnimated } = data as Emoji;
const emojiSlots = g.getMaxEmojiSlots(); const emojiSlots = getGuildMaxEmojiSlots(g);
const { emojis } = EmojiStore.getGuilds()[g.id]; const { emojis } = EmojiStore.getGuilds()[g.id];
let count = 0; let count = 0;

View file

@ -95,7 +95,7 @@ export default definePlugin({
{ {
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}", find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
replacement: { replacement: {
match: /this\.renderNameZone\(\).+?children:\[/, match: /className:\i\.buttons,.{0,50}children:\[/,
replace: "$&$self.GameActivityToggleButton()," replace: "$&$self.GameActivityToggleButton(),"
} }
} }

View file

@ -260,14 +260,7 @@ export default definePlugin({
replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),` replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`
} }
}, },
// Discord has 2 different components for activities. Currently, the last is the one being used // Activities from the apps launcher in the bottom right of the chat bar
{
find: ".activityTitleText,variant",
replacement: {
match: /\.activityTitleText.+?children:(\i)\.name.*?}\),/,
replace: (m, props) => `${m}$self.renderToggleActivityButton(${props}),`
},
},
{ {
find: ".promotedLabelWrapperNonBanner,children", find: ".promotedLabelWrapperNonBanner,children",
replacement: { replacement: {

View file

@ -165,8 +165,35 @@ export default definePlugin({
{ {
find: ".contain,SCALE_DOWN:", find: ".contain,SCALE_DOWN:",
replacement: { replacement: {
match: /\.slide,\i\),/g, match: /imageClassName:/,
replace: `$&id:"${ELEMENT_ID}",` replace: `id:"${ELEMENT_ID}",$&`
}
},
{
find: ".dimensionlessImage,",
replacement: [
{
match: /className:\i\.media,/,
replace: `id:"${ELEMENT_ID}",$&`
},
{
// This patch needs to be above the next one as it uses the zoomed class as an anchor
match: /\.zoomed]:.+?,(?=children:)/,
replace: "$&onClick:()=>{},"
},
{
match: /className:\i\(\)\(\i\.wrapper,.+?}\),/,
replace: ""
},
]
},
// Make media viewer options not hide when zoomed in with the default Discord feature
{
find: '="FOCUS_SENSITIVE",',
replacement: {
match: /(?<=\.hidden]:)\i/,
replace: "false"
} }
}, },

View file

@ -149,6 +149,12 @@ export default definePlugin({
renderChatBarButton: ChatBarIcon, renderChatBarButton: ChatBarIcon,
colorCodeFromNumber(color: number): string {
return `#${[color >> 16, color >> 8, color]
.map(x => (x & 0xFF).toString(16))
.join("")}`;
},
// Gets the Embed of a Link // Gets the Embed of a Link
async getEmbed(url: URL): Promise<Object | {}> { async getEmbed(url: URL): Promise<Object | {}> {
const { body } = await RestAPI.post({ const { body } = await RestAPI.post({
@ -157,6 +163,8 @@ export default definePlugin({
urls: [url] urls: [url]
} }
}); });
// The endpoint returns the color as a number, but Discord expects a string
body.embeds[0].color = this.colorCodeFromNumber(body.embeds[0].color);
return await body.embeds[0]; return await body.embeds[0];
}, },
@ -166,7 +174,7 @@ export default definePlugin({
message.embeds.push({ message.embeds.push({
type: "rich", type: "rich",
rawTitle: "Decrypted Message", rawTitle: "Decrypted Message",
color: "0x45f5f5", color: "#45f5f5",
rawDescription: revealed, rawDescription: revealed,
footer: { footer: {
text: "Made with ❤️ by c0dine and Sammy!", text: "Made with ❤️ by c0dine and Sammy!",

View file

@ -73,17 +73,21 @@ export default definePlugin({
{ {
find: "#{intl::GUILD_OWNER}),children:", find: "#{intl::GUILD_OWNER}),children:",
replacement: { replacement: {
match: /(?<=\.MEMBER_LIST}\),\[\]\),)(.+?color:)null!=.{0,50}?(?=,)/, match: /(typingIndicatorRef:.+?},)(\i=.+?)color:null!=.{0,50}?(?=,)/,
replace: (_, rest) => `ircColor=$self.calculateNameColorForListContext(arguments[0]),${rest}ircColor` replace: (_, rest1, rest2) => `${rest1}ircColor=$self.calculateNameColorForListContext(arguments[0]),${rest2}color:ircColor`
}, },
predicate: () => settings.store.memberListColors predicate: () => settings.store.memberListColors
} }
], ],
calculateNameColorForMessageContext(context: any) { calculateNameColorForMessageContext(context: any) {
const id = context?.message?.author?.id; const userId: string | undefined = context?.message?.author?.id;
const colorString = context?.author?.colorString; const colorString = context?.author?.colorString;
const color = calculateNameColorForUser(id); const color = calculateNameColorForUser(userId);
// Color preview in role settings
if (context?.message?.channel_id === "1337" && userId === "313337")
return colorString;
if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) { if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) {
return colorString; return colorString;

View file

@ -63,11 +63,11 @@ export default definePlugin({
stringDelta(delta: number, showMillis: boolean) { stringDelta(delta: number, showMillis: boolean) {
const diff: Diff = { const diff: Diff = {
days: Math.round(delta / (60 * 60 * 24 * 1000)), days: Math.floor(delta / (60 * 60 * 24 * 1000)),
hours: Math.round((delta / (60 * 60 * 1000)) % 24), hours: Math.floor((delta / (60 * 60 * 1000)) % 24),
minutes: Math.round((delta / (60 * 1000)) % 60), minutes: Math.floor((delta / (60 * 1000)) % 60),
seconds: Math.round(delta / 1000 % 60), seconds: Math.floor(delta / 1000 % 60),
milliseconds: Math.round(delta % 1000) milliseconds: Math.floor(delta % 1000)
}; };
const str = (k: DiffKey) => diff[k] > 0 ? `${diff[k]} ${diff[k] > 1 ? k : k.substring(0, k.length - 1)}` : null; const str = (k: DiffKey) => diff[k] > 0 ? `${diff[k]} ${diff[k] > 1 ? k : k.substring(0, k.length - 1)}` : null;

View file

@ -57,10 +57,10 @@ export default definePlugin({
patches: [ patches: [
{ {
find: "#{intl::BLOCKED_MESSAGES_HIDE}", find: ".__invalid_blocked,",
replacement: [ replacement: [
{ {
match: /let\{[^}]*collapsedReason[^}]*\}/, match: /let{expanded:\i,[^}]*?collapsedReason[^}]*}/,
replace: "if($self.shouldHide(arguments[0]))return null;$&" replace: "if($self.shouldHide(arguments[0]))return null;$&"
} }
] ]

View file

@ -1,6 +1,6 @@
/* /*
* Vencord, a modification for Discord's desktop app * Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors * Copyright (c) 2025 Vendicated and contributors
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -26,10 +26,16 @@ export default definePlugin({
patches: [ patches: [
{ {
find: ".nsfwAllowed=null", find: ".nsfwAllowed=null",
replacement: { replacement: [
match: /(?<=\.nsfwAllowed=)null!==.+?(?=[,;])/, {
replace: "!0", match: /(?<=\.nsfwAllowed=)null!==.+?(?=[,;])/,
}, replace: "true",
}, },
{
match: /(?<=\.ageVerificationStatus=)null!==.+?(?=[,;])/,
replace: "3", // VERIFIED_ADULT
}
],
}
], ],
}); });

View file

@ -25,9 +25,19 @@ export default definePlugin({
authors: [Devs.botato], authors: [Devs.botato],
patches: [{ patches: [{
find: ".expandedFolderIconWrapper", find: ".expandedFolderIconWrapper",
replacement: [{ replacement: [
match: /\(\w\|\|\w\)&&(\(.{0,40}\(.{1,3}\.animated)/, // there are two elements, the first one is the plain folder icon
replace: "$1", // the second is the four guild preview icons
}] // always show this one (the plain icons)
{
match: /\(\i\|\|\i\)&&(\(.{0,40}\(\i\.animated)/,
replace: "$1",
},
// and never show this one (the guild preview icons)
{
match: /\(\i\|\|!\i\)&&(\(.{0,40}\(\i\.animated)/,
replace: "false&&$1",
}
]
}] }]
}); });

View file

@ -96,6 +96,6 @@
.vc-shiki-root .vc-shiki-table-cell:last-child { .vc-shiki-root .vc-shiki-table-cell:last-child {
padding-left: 8px; padding-left: 8px;
overflow-wrap: break-word; overflow-wrap: anywhere;
width: 100%; width: 100%;
} }

View file

@ -50,8 +50,8 @@ export default definePlugin({
{ {
find: '?"@":""', find: '?"@":""',
replacement: { replacement: {
match: /(?<=onContextMenu:\i,children:).*?\)}/, match: /(?<=children:)\(\i\?"@":""\)\+\i(?=,|\})/,
replace: "$self.renderUsername(arguments[0])}" replace: "$self.renderUsername(arguments[0])"
} }
}, },
], ],

View file

@ -17,6 +17,7 @@
*/ */
import "./spotifyStyles.css"; import "./spotifyStyles.css";
import "./visualRefreshSpotifyStyles.css"; // TODO: merge with spotifyStyles.css and remove when old UI is discontinued
import { Settings } from "@api/Settings"; import { Settings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
@ -307,8 +308,8 @@ function Info({ track }: { track: Track; }) {
{track.name} {track.name}
</Forms.FormText> </Forms.FormText>
{track.artists.some(a => a.name) && ( {track.artists.some(a => a.name) && (
<Forms.FormText variant="text-sm/normal" className={cl("ellipoverflow")}> <Forms.FormText variant="text-sm/normal" className={cl(["ellipoverflow", "secondary-song-info"])}>
by&nbsp; <span className={cl("song-info-prefix")}>by&nbsp;</span>
{track.artists.map((a, i) => ( {track.artists.map((a, i) => (
<React.Fragment key={a.name}> <React.Fragment key={a.name}>
<span <span
@ -325,8 +326,8 @@ function Info({ track }: { track: Track; }) {
</Forms.FormText> </Forms.FormText>
)} )}
{track.album.name && ( {track.album.name && (
<Forms.FormText variant="text-sm/normal" className={cl("ellipoverflow")}> <Forms.FormText variant="text-sm/normal" className={cl(["ellipoverflow", "secondary-song-info"])}>
on&nbsp; <span className={cl("song-info-prefix")}>on&nbsp;</span>
<span <span
id={cl("album-title")} id={cl("album-title")}
className={cl("album")} className={cl("album")}

View file

@ -32,7 +32,7 @@ function toggleHoverControls(value: boolean) {
export default definePlugin({ export default definePlugin({
name: "SpotifyControls", name: "SpotifyControls",
description: "Adds a Spotify player above the account panel", description: "Adds a Spotify player above the account panel",
authors: [Devs.Ven, Devs.afn, Devs.KraXen72, Devs.Av32000], authors: [Devs.Ven, Devs.afn, Devs.KraXen72, Devs.Av32000, Devs.nin0dev],
options: { options: {
hoverControls: { hoverControls: {
description: "Show controls on hover", description: "Show controls on hover",

View file

@ -2,7 +2,9 @@
padding: 0.375rem 0.5rem; padding: 0.375rem 0.5rem;
border-bottom: 1px solid var(--background-modifier-accent); border-bottom: 1px solid var(--background-modifier-accent);
--vc-spotify-green: #1db954; /* so custom themes can easily change it */ --vc-spotify-green: var(--spotify, #1db954); /* so custom themes can easily change it */
--vc-spotify-green-90: color-mix(in hsl, var(--vc-spotify-green), transparent 90%);
--vc-spotify-green-80: color-mix(in hsl, var(--vc-spotify-green), transparent 80%);
} }
.theme-light #vc-spotify-player { .theme-light #vc-spotify-player {

View file

@ -0,0 +1,77 @@
/* TODO: merge with spotifyStyles.css and remove when old UI is discontinued */
.visual-refresh {
#vc-spotify-player {
padding: 12px;
background: var(--bg-overlay-floating, var(--background-base-low, var(--background-secondary-alt)));
margin: 0;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
}
.vc-spotify-song-info-prefix {
display: none;
}
.vc-spotify-artist, .vc-spotify-album {
color: var(--header-primary);
}
.vc-spotify-secondary-song-info {
font-size: 12px;
}
#vc-spotify-progress-bar {
position: relative;
color: var(--text-normal);
width: 100%;
}
#vc-spotify-progress-bar > [class^="slider"] {
flex-grow: 1;
width: 100%;
padding: 0 !important;
}
#vc-spotify-progress-bar > [class^="slider"] [class^="bar"] {
height: 3px !important;
top: calc(12px - 4px / 2 + var(--bar-offset));
}
#vc-spotify-progress-bar > [class^="slider"] [class^="barFill"] {
background-color: var(--interactive-active);
}
#vc-spotify-progress-bar > [class^="slider"]:hover [class^="barFill"] {
background-color: var(--vc-spotify-green);
}
#vc-spotify-progress-bar > [class^="slider"] [class^="grabber"] {
background-color: var(--interactive-active);
width: 16px !important;
height: 16px !important;
margin-top: calc(17px/-2 + var(--bar-offset)/2);
margin-left: -0.5px;
}
.vc-spotify-progress-time {
margin-top: 8px;
font-family: var(--font-code);
}
.vc-spotify-button-row {
margin-top: 14px;
}
.vc-spotify-button {
margin: 0 2px;
border-radius: var(--radius-sm);
}
.vc-spotify-repeat-context, .vc-spotify-repeat-track, .vc-spotify-shuffle-on {
background-color: var(--vc-spotify-green-90);
}
.vc-spotify-repeat-context:hover, .vc-spotify-repeat-track:hover, .vc-spotify-shuffle-on:hover {
background-color: var(--vc-spotify-green-80);
}
}

View file

@ -54,8 +54,8 @@ export default definePlugin({
} }
], ],
getAvatarStyles(src: string) { getAvatarStyles(src: string | null) {
if (src.startsWith("data:")) return {}; if (!src || src.startsWith("data:")) return {};
return Object.fromEntries( return Object.fromEntries(
[128, 256, 512, 1024, 2048, 4096].map(size => [ [128, 256, 512, 1024, 2048, 4096].map(size => [

View file

@ -273,7 +273,7 @@ export default definePlugin({
muteMessage: { muteMessage: {
type: OptionType.STRING, type: OptionType.STRING,
description: "Mute Message (only self for now)", description: "Mute Message (only self for now)",
default: "{{USER}} Muted" default: "{{USER}} muted"
}, },
unmuteMessage: { unmuteMessage: {
type: OptionType.STRING, type: OptionType.STRING,

View file

@ -136,7 +136,7 @@ export default definePlugin({
// @ts-expect-error // @ts-expect-error
if (data.sinkId != null && data.sinkId !== data.audioContext.sinkId && "setSinkId" in AudioContext.prototype) { if (data.sinkId != null && data.sinkId !== data.audioContext.sinkId && "setSinkId" in AudioContext.prototype) {
// @ts-expect-error https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/setSinkId // @ts-expect-error https://developer.mozilla.org/en-US/docs/Web/API/AudioContext/setSinkId
data.audioContext.setSinkId(data.sinkId); data.audioContext.setSinkId(data.sinkId === "default" ? "" : data.sinkId);
} }
data.gainNode.gain.value = data._mute data.gainNode.gain.value = data._mute

View file

@ -42,13 +42,16 @@ const settings = definePluginSettings({
addBack: { addBack: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Add back the Discord context menus for images, links and the chat input bar", description: "Add back the Discord context menus for images, links and the chat input bar",
default: false,
restartNeeded: true,
// Web slate menu has proper spellcheck suggestions and image context menu is also pretty good, // Web slate menu has proper spellcheck suggestions and image context menu is also pretty good,
// so disable this by default. Vesktop just doesn't, so enable by default // so disable this by default. Vesktop just doesn't, so we force enable it there
default: IS_VESKTOP, hidden: IS_VESKTOP,
restartNeeded: true
} }
}); });
const shouldAddBackMenus = () => IS_VESKTOP || settings.store.addBack;
const MEDIA_PROXY_URL = "https://media.discordapp.net"; const MEDIA_PROXY_URL = "https://media.discordapp.net";
const CDN_URL = "cdn.discordapp.com"; const CDN_URL = "cdn.discordapp.com";
@ -81,7 +84,7 @@ export default definePlugin({
settings, settings,
start() { start() {
if (settings.store.addBack) { if (shouldAddBackMenus()) {
window.removeEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackWeb); window.removeEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackWeb);
window.addEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackNative); window.addEventListener("contextmenu", ctxMenuCallbacks.contextMenuCallbackNative);
this.changedListeners = true; this.changedListeners = true;
@ -144,7 +147,7 @@ export default definePlugin({
{ {
find: 'navId:"image-context"', find: 'navId:"image-context"',
all: true, all: true,
predicate: () => settings.store.addBack, predicate: shouldAddBackMenus,
replacement: { replacement: {
// return IS_DESKTOP ? React.createElement(Menu, ...) // return IS_DESKTOP ? React.createElement(Menu, ...)
match: /return \i\.\i(?=\?|&&)/, match: /return \i\.\i(?=\?|&&)/,
@ -155,7 +158,7 @@ export default definePlugin({
// Add back link context menu // Add back link context menu
{ {
find: '"interactionUsernameProfile"', find: '"interactionUsernameProfile"',
predicate: () => settings.store.addBack, predicate: shouldAddBackMenus,
replacement: { replacement: {
match: /if\((?="A"===\i\.tagName&&""!==\i\.textContent)/, match: /if\((?="A"===\i\.tagName&&""!==\i\.textContent)/,
replace: "if(false&&" replace: "if(false&&"
@ -165,7 +168,7 @@ export default definePlugin({
// Add back slate / text input context menu // Add back slate / text input context menu
{ {
find: 'getElementById("slate-toolbar"', find: 'getElementById("slate-toolbar"',
predicate: () => settings.store.addBack, predicate: shouldAddBackMenus,
replacement: { replacement: {
match: /(?<=handleContextMenu\(\i\)\{.{0,200}isPlatformEmbedded)\)/, match: /(?<=handleContextMenu\(\i\)\{.{0,200}isPlatformEmbedded)\)/,
replace: "||true)" replace: "||true)"
@ -173,7 +176,7 @@ export default definePlugin({
}, },
{ {
find: ".SLASH_COMMAND_SUGGESTIONS_TOGGLED,{", find: ".SLASH_COMMAND_SUGGESTIONS_TOGGLED,{",
predicate: () => settings.store.addBack, predicate: shouldAddBackMenus,
replacement: [ replacement: [
{ {
// if (!IS_DESKTOP) return null; // if (!IS_DESKTOP) return null;
@ -189,7 +192,7 @@ export default definePlugin({
}, },
{ {
find: '"add-to-dictionary"', find: '"add-to-dictionary"',
predicate: () => settings.store.addBack, predicate: shouldAddBackMenus,
replacement: { replacement: {
match: /let\{text:\i=""/, match: /let\{text:\i=""/,
replace: "return [null,null];$&" replace: "return [null,null];$&"

View file

@ -23,6 +23,7 @@ export const DONOR_ROLE_ID = "1042507929485586532";
export const CONTRIB_ROLE_ID = "1026534353167208489"; export const CONTRIB_ROLE_ID = "1026534353167208489";
export const REGULAR_ROLE_ID = "1026504932959977532"; export const REGULAR_ROLE_ID = "1026504932959977532";
export const SUPPORT_CHANNEL_ID = "1026515880080842772"; export const SUPPORT_CHANNEL_ID = "1026515880080842772";
export const SUPPORT_CATEGORY_ID = "1108135649699180705";
export const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920"; export const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920";
export interface Dev { export interface Dev {

View file

@ -69,8 +69,8 @@ export interface ApngFrameData {
// The below code is only used on the Desktop (electron) build of Vencord. // The below code is only used on the Desktop (electron) build of Vencord.
// Browser (extension) builds do not contain these remote imports. // Browser (extension) builds do not contain these remote imports.
export const shikiWorkerSrc = `https://unpkg.com/@vap/shiki-worker@0.0.8/dist/${IS_DEV ? "index.js" : "index.min.js"}`; export const shikiWorkerSrc = `https://cdn.jsdelivr.net/npm/@vap/shiki-worker@0.0.8/dist/${IS_DEV ? "index.js" : "index.min.js"}`;
export const shikiOnigasmSrc = "https://unpkg.com/@vap/shiki@0.10.3/dist/onig.wasm"; export const shikiOnigasmSrc = "https://cdn.jsdelivr.net/npm/@vap/shiki@0.10.3/dist/onig.wasm";
// @ts-expect-error // @ts-expect-error
export const getStegCloak = /* #__PURE__*/ makeLazy(() => import("https://unpkg.com/stegcloak-dist@1.0.0/index.js")); export const getStegCloak = /* #__PURE__*/ makeLazy(() => import("https://cdn.jsdelivr.net/npm/stegcloak-dist@1.0.0/index.js"));

View file

@ -26,7 +26,7 @@ import { MessageDecorationFactory } from "@api/MessageDecorations";
import { MessageClickListener, MessageEditListener, MessageSendListener } from "@api/MessageEvents"; import { MessageClickListener, MessageEditListener, MessageSendListener } from "@api/MessageEvents";
import { MessagePopoverButtonFactory } from "@api/MessagePopover"; import { MessagePopoverButtonFactory } from "@api/MessagePopover";
import { FluxEvents } from "@webpack/types"; import { FluxEvents } from "@webpack/types";
import { JSX } from "react"; import { ReactNode } from "react";
import { Promisable } from "type-fest"; import { Promisable } from "type-fest";
// exists to export default definePlugin({...}) // exists to export default definePlugin({...})
@ -202,6 +202,10 @@ export const enum ReporterTestable {
FluxEvents = 1 << 4 FluxEvents = 1 << 4
} }
export function defineDefault<T = any>(value: T) {
return value;
}
export const enum OptionType { export const enum OptionType {
STRING, STRING,
NUMBER, NUMBER,
@ -334,7 +338,8 @@ export interface IPluginOptionComponentProps {
export interface PluginSettingComponentDef { export interface PluginSettingComponentDef {
type: OptionType.COMPONENT; type: OptionType.COMPONENT;
component: (props: IPluginOptionComponentProps) => JSX.Element; component: (props: IPluginOptionComponentProps) => ReactNode | Promise<ReactNode>;
default?: any;
} }
/** Maps a `PluginSettingDef` to its value type */ /** Maps a `PluginSettingDef` to its value type */

View file

@ -38,6 +38,7 @@ export const Forms = {
export const Card = waitForComponent<t.Card>("Card", filters.componentByCode(".editable),", ".outline:")); export const Card = waitForComponent<t.Card>("Card", filters.componentByCode(".editable),", ".outline:"));
export const Button = waitForComponent<t.Button>("Button", filters.componentByCode("#{intl::A11Y_LOADING_STARTED}))),!1")); export const Button = waitForComponent<t.Button>("Button", filters.componentByCode("#{intl::A11Y_LOADING_STARTED}))),!1"));
export const Switch = waitForComponent<t.Switch>("Switch", filters.componentByCode(".labelRow,ref:", ".disabledText")); export const Switch = waitForComponent<t.Switch>("Switch", filters.componentByCode(".labelRow,ref:", ".disabledText"));
export const Checkbox = waitForComponent<t.Checkbox>("Checkbox", filters.componentByCode(".checkboxWrapperDisabled:"));
const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", { const Tooltips = mapMangledModuleLazy(".tooltipTop,bottom:", {
Tooltip: filters.componentByCode("this.renderTooltip()]"), Tooltip: filters.componentByCode("this.renderTooltip()]"),
@ -94,7 +95,7 @@ waitFor(m => {
export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", filters.componentByCode("MASKED_LINK)")); export const MaskedLink = waitForComponent<t.MaskedLink>("MaskedLink", filters.componentByCode("MASKED_LINK)"));
export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.componentByCode("#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}")); export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.componentByCode("#{intl::MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL}"));
export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]); export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);
export const OAuth2AuthorizeModal = waitForComponent("OAuth2AuthorizeModal", filters.componentByCode(".authorize),children:", ".contentBackground")); export const OAuth2AuthorizeModal = waitForComponent("OAuth2AuthorizeModal", filters.componentByCode(".authorize,children:", ".contentBackground"));
export const Animations = mapMangledModuleLazy(".assign({colorNames:", { export const Animations = mapMangledModuleLazy(".assign({colorNames:", {
Transition: filters.componentByCode('["items","children"]', ",null,"), Transition: filters.componentByCode('["items","children"]', ",null,"),

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import type { ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react"; import type { ComponentPropsWithRef, ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, JSX, KeyboardEvent, MouseEvent, PointerEvent, PropsWithChildren, ReactNode, Ref } from "react";
export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code";
@ -197,6 +197,36 @@ export type Switch = ComponentType<PropsWithChildren<{
tooltipNote?: ReactNode; tooltipNote?: ReactNode;
}>>; }>>;
export type CheckboxAligns = {
CENTER: "center";
TOP: "top";
};
export type CheckboxTypes = {
DEFAULT: "default";
INVERTED: "inverted";
GHOST: "ghost";
ROW: "row";
};
export type Checkbox = ComponentType<PropsWithChildren<{
value: boolean;
onChange(event: PointerEvent, value: boolean): void;
align?: "center" | "top";
disabled?: boolean;
displayOnly?: boolean;
readOnly?: boolean;
reverse?: boolean;
shape?: string;
size?: number;
type?: "default" | "inverted" | "ghost" | "row";
}>> & {
Shapes: Record<"BOX" | "ROUND" | "SMALL_BOX", string>;
Aligns: CheckboxAligns;
Types: CheckboxTypes;
};
export type Timestamp = ComponentType<PropsWithChildren<{ export type Timestamp = ComponentType<PropsWithChildren<{
timestamp: Date; timestamp: Date;
isEdited?: boolean; isEdited?: boolean;
@ -215,7 +245,8 @@ export type TextInput = ComponentType<PropsWithChildren<{
onChange?(value: string, name?: string): void; onChange?(value: string, name?: string): void;
placeholder?: string; placeholder?: string;
editable?: boolean; editable?: boolean;
maxLength?: number; /** defaults to 999. Pass null to disable this default */
maxLength?: number | null;
error?: string; error?: string;
inputClassName?: string; inputClassName?: string;
@ -227,13 +258,13 @@ export type TextInput = ComponentType<PropsWithChildren<{
/** TextInput.Sizes.DEFAULT */ /** TextInput.Sizes.DEFAULT */
size?: string; size?: string;
} & Omit<HTMLProps<HTMLInputElement>, "onChange">>> & { } & Omit<HTMLProps<HTMLInputElement>, "onChange" | "maxLength">>> & {
Sizes: Record<"DEFAULT" | "MINI", string>; Sizes: Record<"DEFAULT" | "MINI", string>;
}; };
export type TextArea = ComponentType<PropsWithRef<Omit<HTMLProps<HTMLTextAreaElement>, "onChange"> & { export type TextArea = ComponentType<Omit<HTMLProps<HTMLTextAreaElement>, "onChange"> & {
onChange(v: string): void; onChange(v: string): void;
}>>; }>;
interface SelectOption { interface SelectOption {
disabled?: boolean; disabled?: boolean;

View file

@ -142,9 +142,12 @@ export const UploadHandler = {
promptToUpload: findByCodeLazy("#{intl::ATTACHMENT_TOO_MANY_ERROR_TITLE}") as (files: File[], channel: Channel, draftType: Number) => void promptToUpload: findByCodeLazy("#{intl::ATTACHMENT_TOO_MANY_ERROR_TITLE}") as (files: File[], channel: Channel, draftType: Number) => void
}; };
export const ApplicationAssetUtils = findByPropsLazy("fetchAssetIds", "getAssetImage") as { export const ApplicationAssetUtils = mapMangledModuleLazy("getAssetImage: size must === [", {
fetchAssetIds: (applicationId: string, e: string[]) => Promise<string[]>; fetchAssetIds: filters.byCode('.startsWith("http:")', ".dispatch({"),
}; getAssetFromImageURL: filters.byCode("].serialize(", ',":"'),
getAssetImage: filters.byCode("getAssetImage: size must === ["),
getAssets: filters.byCode(".assets")
});
export const Clipboard: t.Clipboard = mapMangledModuleLazy('queryCommandEnabled("copy")', { export const Clipboard: t.Clipboard = mapMangledModuleLazy('queryCommandEnabled("copy")', {
copy: filters.byCode(".copy("), copy: filters.byCode(".copy("),