1
0
Fork 1
mirror of https://github.com/Vendicated/Vencord.git synced 2025-01-09 17:36:23 +00:00

Merge branch 'main' into main

This commit is contained in:
Isaac 2024-11-25 15:23:08 +03:00 committed by GitHub
commit d87bbc1931
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
181 changed files with 1992 additions and 1687 deletions

View file

@ -5,6 +5,7 @@
// @author Vendicated (https://github.com/Vendicated)
// @namespace https://github.com/Vendicated/Vencord
// @supportURL https://github.com/Vendicated/Vencord
// @icon https://raw.githubusercontent.com/Vendicated/Vencord/refs/heads/main/browser/icon.png
// @license GPL-3.0
// @match *://*.discord.com/*
// @grant GM_xmlhttpRequest

View file

@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
"version": "1.10.1",
"version": "1.10.7",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
@ -35,6 +35,7 @@
"testTsc": "tsc --noEmit"
},
"dependencies": {
"@intrnl/xxhash64": "^0.1.2",
"@sapphi-red/web-noise-suppressor": "0.3.5",
"@vap/core": "0.0.12",
"@vap/shiki": "0.10.5",

View file

@ -16,6 +16,9 @@ importers:
.:
dependencies:
'@intrnl/xxhash64':
specifier: ^0.1.2
version: 0.1.2
'@sapphi-red/web-noise-suppressor':
specifier: 0.3.5
version: 0.3.5
@ -537,6 +540,9 @@ packages:
resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==}
engines: {node: '>=18.18'}
'@intrnl/xxhash64@0.1.2':
resolution: {integrity: sha512-1+lx7j99fdph+uy3EnjQyr39KQZ7LP56+aWOr6finJWpgYpvb7XrhFUqDwnEk/wpPC98nCjAT6RulpW3crWjlg==}
'@jridgewell/gen-mapping@0.3.5':
resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
engines: {node: '>=6.0.0'}
@ -2939,6 +2945,8 @@ snapshots:
'@humanwhocodes/retry@0.3.0': {}
'@intrnl/xxhash64@0.1.2': {}
'@jridgewell/gen-mapping@0.3.5':
dependencies:
'@jridgewell/set-array': 1.2.1

View file

@ -225,7 +225,7 @@ page.on("console", async e => {
plugin,
type,
id,
match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"),
match: regex.replace(/\(\?:\[A-Za-z_\$\]\[\\w\$\]\*\)/g, "\\i"),
error: await maybeGetError(e.args()[3])
});

View file

@ -99,7 +99,8 @@ export interface ChatBarButtonProps {
tooltip: string;
onClick: MouseEventHandler<HTMLButtonElement>;
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
buttonProps?: Omit<HTMLProps<HTMLButtonElement>, "size" | "onClick" | "onContextMenu">;
onAuxClick?: MouseEventHandler<HTMLButtonElement>;
buttonProps?: Omit<HTMLProps<HTMLButtonElement>, "size" | "onClick" | "onContextMenu" | "onAuxClick">;
}
export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
return (
@ -115,6 +116,7 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
onClick={props.onClick}
onContextMenu={props.onContextMenu}
onAuxClick={props.onAuxClick}
{...props.buttonProps}
>
<div className={ButtonWrapperClasses.buttonWrapper}>

View file

@ -90,19 +90,20 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
* A helper function for finding the children array of a group nested inside a context menu based on the id(s) of its children
* @param id The id of the child. If an array is specified, all ids will be tried
* @param children The context menu children
* @param matchSubstring Whether to check if the id is a substring of the child id
*/
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null>): Array<ReactElement | null> | null {
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null | undefined>, matchSubstring = false): Array<ReactElement | null | undefined> | null {
for (const child of children) {
if (child == null) continue;
if (Array.isArray(child)) {
const found = findGroupChildrenByChildId(id, child);
const found = findGroupChildrenByChildId(id, child, matchSubstring);
if (found !== null) return found;
}
if (
(Array.isArray(id) && id.some(id => child.props?.id === id))
|| child.props?.id === id
(Array.isArray(id) && id.some(id => matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id))
|| (matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id)
) return children;
let nextChildren = child.props?.children;
@ -112,7 +113,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
child.props.children = nextChildren;
}
const found = findGroupChildrenByChildId(id, nextChildren);
const found = findGroupChildrenByChildId(id, nextChildren, matchSubstring);
if (found !== null) return found;
}
}

View file

@ -1,11 +0,0 @@
.vc-expandableheader-center-flex {
display: flex;
place-items: center;
}
.vc-expandableheader-btn {
all: unset;
cursor: pointer;
width: 24px;
height: 24px;
}

View file

@ -1,121 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import "./ExpandableHeader.css";
import { classNameFactory } from "@api/Styles";
import { Text, Tooltip, useState } from "@webpack/common";
const cl = classNameFactory("vc-expandableheader-");
export interface ExpandableHeaderProps {
onMoreClick?: () => void;
moreTooltipText?: string;
onDropDownClick?: (state: boolean) => void;
defaultState?: boolean;
headerText: string;
children: React.ReactNode;
buttons?: React.ReactNode[];
forceOpen?: boolean;
}
export function ExpandableHeader({
children,
onMoreClick,
buttons,
moreTooltipText,
onDropDownClick,
headerText,
defaultState = false,
forceOpen = false,
}: ExpandableHeaderProps) {
const [showContent, setShowContent] = useState(defaultState || forceOpen);
return (
<>
<div style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "8px"
}}>
<Text
tag="h2"
variant="eyebrow"
style={{
color: "var(--header-primary)",
display: "inline"
}}
>
{headerText}
</Text>
<div className={cl("center-flex")}>
{
buttons ?? null
}
{
onMoreClick && // only show more button if callback is provided
<Tooltip text={moreTooltipText}>
{tooltipProps => (
<button
{...tooltipProps}
className={cl("btn")}
onClick={onMoreClick}>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
>
<path fill="var(--text-normal)" d="M7 12.001C7 10.8964 6.10457 10.001 5 10.001C3.89543 10.001 3 10.8964 3 12.001C3 13.1055 3.89543 14.001 5 14.001C6.10457 14.001 7 13.1055 7 12.001ZM14 12.001C14 10.8964 13.1046 10.001 12 10.001C10.8954 10.001 10 10.8964 10 12.001C10 13.1055 10.8954 14.001 12 14.001C13.1046 14.001 14 13.1055 14 12.001ZM19 10.001C20.1046 10.001 21 10.8964 21 12.001C21 13.1055 20.1046 14.001 19 14.001C17.8954 14.001 17 13.1055 17 12.001C17 10.8964 17.8954 10.001 19 10.001Z" />
</svg>
</button>
)}
</Tooltip>
}
<Tooltip text={showContent ? "Hide " + headerText : "Show " + headerText}>
{tooltipProps => (
<button
{...tooltipProps}
className={cl("btn")}
onClick={() => {
setShowContent(v => !v);
onDropDownClick?.(showContent);
}}
disabled={forceOpen}
>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
transform={showContent ? "scale(1 -1)" : "scale(1 1)"}
>
<path fill="var(--text-normal)" d="M16.59 8.59003L12 13.17L7.41 8.59003L6 10L12 16L18 10L16.59 8.59003Z" />
</svg>
</button>
)}
</Tooltip>
</div>
</div>
{showContent && children}
</>
);
}

View file

@ -18,9 +18,8 @@
import "./iconStyles.css";
import { getTheme, Theme } from "@utils/discord";
import { getIntlMessage } from "@utils/discord";
import { classes } from "@utils/misc";
import { i18n } from "@webpack/common";
import type { PropsWithChildren } from "react";
interface BaseIconProps extends IconProps {
@ -123,8 +122,8 @@ export function InfoIcon(props: IconProps) {
>
<path
fill="currentColor"
transform="translate(2 2)"
d="M9,7 L11,7 L11,5 L9,5 L9,7 Z M10,18 C5.59,18 2,14.41 2,10 C2,5.59 5.59,2 10,2 C14.41,2 18,5.59 18,10 C18,14.41 14.41,18 10,18 L10,18 Z M10,4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16,4.4771525 0,10 C-1.33226763e-15,12.6521649 1.0535684,15.195704 2.92893219,17.0710678 C4.80429597,18.9464316 7.3478351,20 10,20 C12.6521649,20 15.195704,18.9464316 17.0710678,17.0710678 C18.9464316,15.195704 20,12.6521649 20,10 C20,7.3478351 18.9464316,4.80429597 17.0710678,2.92893219 C15.195704,1.0535684 12.6521649,2.22044605e-16 10,0 L10,4.4408921e-16 Z M9,15 L11,15 L11,9 L9,9 L9,15 L9,15 Z"
fill-rule="evenodd"
d="M23 12a11 11 0 1 1-22 0 11 11 0 0 1 22 0Zm-9.5-4.75a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm-.77 3.96a1 1 0 1 0-1.96-.42l-1.04 4.86a2.77 2.77 0 0 0 4.31 2.83l.24-.17a1 1 0 1 0-1.16-1.62l-.24.17a.77.77 0 0 1-1.2-.79l1.05-4.86Z" clip-rule="evenodd"
/>
</Icon>
);
@ -133,7 +132,7 @@ export function InfoIcon(props: IconProps) {
export function OwnerCrownIcon(props: IconProps) {
return (
<Icon
aria-label={i18n.Messages.GUILD_OWNER}
aria-label={getIntlMessage("GUILD_OWNER")}
{...props}
className={classes(props.className, "vc-owner-crown-icon")}
role="img"
@ -212,9 +211,10 @@ export function CogWheel(props: IconProps) {
viewBox="0 0 24 24"
>
<path
clipRule="evenodd"
fill="currentColor"
d="M19.738 10H22V14H19.739C19.498 14.931 19.1 15.798 18.565 16.564L20 18L18 20L16.565 18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069 19.498 8.203 19.099 7.436 18.564L6 20L4 18L5.436 16.564C4.901 15.799 4.502 14.932 4.262 14H2V10H4.262C4.502 9.068 4.9 8.202 5.436 7.436L4 6L6 4L7.436 5.436C8.202 4.9 9.068 4.502 10 4.262V2H14V4.261C14.932 4.502 15.797 4.9 16.565 5.435L18 3.999L20 5.999L18.564 7.436C19.099 8.202 19.498 9.069 19.738 10ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16Z"
fill-rule="evenodd"
d="M10.56 1.1c-.46.05-.7.53-.64.98.18 1.16-.19 2.2-.98 2.53-.8.33-1.79-.15-2.49-1.1-.27-.36-.78-.52-1.14-.24-.77.59-1.45 1.27-2.04 2.04-.28.36-.12.87.24 1.14.96.7 1.43 1.7 1.1 2.49-.33.8-1.37 1.16-2.53.98-.45-.07-.93.18-.99.64a11.1 11.1 0 0 0 0 2.88c.06.46.54.7.99.64 1.16-.18 2.2.19 2.53.98.33.8-.14 1.79-1.1 2.49-.36.27-.52.78-.24 1.14.59.77 1.27 1.45 2.04 2.04.36.28.87.12 1.14-.24.7-.95 1.7-1.43 2.49-1.1.8.33 1.16 1.37.98 2.53-.07.45.18.93.64.99a11.1 11.1 0 0 0 2.88 0c.46-.06.7-.54.64-.99-.18-1.16.19-2.2.98-2.53.8-.33 1.79.14 2.49 1.1.27.36.78.52 1.14.24.77-.59 1.45-1.27 2.04-2.04.28-.36.12-.87-.24-1.14-.96-.7-1.43-1.7-1.1-2.49.33-.8 1.37-1.16 2.53-.98.45.07.93-.18.99-.64a11.1 11.1 0 0 0 0-2.88c-.06-.46-.54-.7-.99-.64-1.16.18-2.2-.19-2.53-.98-.33-.8.14-1.79 1.1-2.49.36-.27.52-.78.24-1.14a11.07 11.07 0 0 0-2.04-2.04c-.36-.28-.87-.12-1.14.24-.7.96-1.7 1.43-2.49 1.1-.8-.33-1.16-1.37-.98-2.53.07-.45-.18-.93-.64-.99a11.1 11.1 0 0 0-2.88 0ZM16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
clip-rule="evenodd"
/>
</Icon>
);
@ -407,23 +407,30 @@ export function PencilIcon(props: IconProps) {
);
}
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
export function GithubIcon(props: ImageProps) {
const src = getTheme() === Theme.Light
? GithubIconLight
: GithubIconDark;
return <img {...props} src={src} />;
export function GithubIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="-3 -3 30 30"
>
<path
fill={props.fill || "currentColor"}
d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.385.6.11.82-.26.82-.577v-2.17c-3.338.726-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.757-1.333-1.757-1.09-.745.083-.73.083-.73 1.205.084 1.84 1.237 1.84 1.237 1.07 1.835 2.807 1.305 3.492.998.108-.775.42-1.305.763-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.467-2.38 1.235-3.22-.123-.303-.535-1.523.117-3.176 0 0 1.008-.322 3.3 1.23.957-.266 1.98-.398 3-.403 1.02.005 2.043.137 3 .403 2.29-1.552 3.297-1.23 3.297-1.23.653 1.653.24 2.873.118 3.176.77.84 1.233 1.91 1.233 3.22 0 4.61-2.803 5.625-5.475 5.92.43.37.823 1.102.823 2.222v3.293c0 .32.218.694.825.577C20.565 21.797 24 17.298 24 12c0-6.63-5.37-12-12-12z"
/>
</Icon>
);
}
export function WebsiteIcon(props: ImageProps) {
const src = getTheme() === Theme.Light
? WebsiteIconLight
: WebsiteIconDark;
return <img {...props} src={src} />;
export function WebsiteIcon(props: IconProps) {
return (
<Icon
{...props}
viewBox="0 0 24 24"
>
<path
fill={props.fill || "currentColor"}
d="M12 2C6.486 2 2 6.486 2 12s4.486 10 10 10 10-4.486 10-10S17.514 2 12 2zM4 12c0-.899.156-1.762.431-2.569L6 11l2 2v2l2 2 1 1v1.931C7.061 19.436 4 16.072 4 12zm14.33 4.873C17.677 16.347 16.687 16 16 16v-1a2 2 0 0 0-2-2h-4v-3a2 2 0 0 0 2-2V7h1a2 2 0 0 0 2-2v-.411C17.928 5.778 20 8.65 20 12a7.947 7.947 0 0 1-1.67 4.873z"
/>
</Icon>
);
}

View file

@ -6,16 +6,19 @@
import "./LinkIconButton.css";
import { getTheme, Theme } from "@utils/discord";
import { MaskedLink, Tooltip } from "@webpack/common";
import { GithubIcon, WebsiteIcon } from "..";
export function GithubLinkIcon() {
return <GithubIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF";
return <GithubIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />;
}
export function WebsiteLinkIcon() {
return <WebsiteIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
const theme = getTheme() === Theme.Light ? "#000000" : "#FFFFFF";
return <WebsiteIcon aria-hidden fill={theme} className={"vc-settings-modal-link-icon"} />;
}
interface Props {

View file

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Margins } from "@utils/margins";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { OptionType, PluginOptionNumber } from "@utils/types";
import { Forms, React, TextInput } from "@webpack/common";
@ -54,7 +56,8 @@ export function SettingNumericComponent({ option, pluginSettings, definedSetting
return (
<Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle>
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
<TextInput
type="number"
pattern="-?[0-9]+"

View file

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Margins } from "@utils/margins";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { PluginOptionSelect } from "@utils/types";
import { Forms, React, Select } from "@webpack/common";
@ -44,7 +46,8 @@ export function SettingSelectComponent({ option, pluginSettings, definedSettings
return (
<Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle>
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
<Forms.FormText className={Margins.bottom16} type="description">{option.description}</Forms.FormText>
<Select
isDisabled={option.disabled?.call(definedSettings) ?? false}
options={option.options}

View file

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Margins } from "@utils/margins";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { PluginOptionSlider } from "@utils/types";
import { Forms, React, Slider } from "@webpack/common";
@ -50,7 +52,8 @@ export function SettingSliderComponent({ option, pluginSettings, definedSettings
return (
<Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle>
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
<Slider
disabled={option.disabled?.call(definedSettings) ?? false}
markers={option.markers}

View file

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Margins } from "@utils/margins";
import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { PluginOptionString } from "@utils/types";
import { Forms, React, TextInput } from "@webpack/common";
@ -41,7 +43,8 @@ export function SettingTextComponent({ option, pluginSettings, definedSettings,
return (
<Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle>
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
<TextInput
type="text"
value={state}

View file

@ -93,7 +93,7 @@ interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
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));

View file

@ -247,7 +247,7 @@ function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: Fu
}
try {
const parsed = (0, eval)(`(${fullPatch})`) as Patch;
const parsed = (0, eval)(`([${fullPatch}][0])`) as Patch;
if (!parsed.find) throw new Error("No 'find' field");
if (!parsed.replacement) throw new Error("No 'replacement' field");

View file

@ -10,7 +10,6 @@ export * from "./CodeBlock";
export * from "./DonateButton";
export { default as ErrorBoundary } from "./ErrorBoundary";
export * from "./ErrorCard";
export * from "./ExpandableHeader";
export * from "./Flex";
export * from "./Heart";
export * from "./Icons";

View file

@ -27,13 +27,16 @@ export async function loadLazyChunks() {
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
let foundCssDebuggingLoad = false;
async function searchAndLoadLazyChunks(factoryCode: string) {
// Workaround to avoid loading the CSS debugging chunk which turns the app pink
const hasCssDebuggingLoad = foundCssDebuggingLoad ? false : (foundCssDebuggingLoad = factoryCode.includes(".cssDebuggingEnabled&&"));
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>();
// Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
// the chunk containing the component
const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
const shouldForceDefer = false;
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => Number(m[1])) : [];
@ -45,6 +48,16 @@ export async function loadLazyChunks() {
let invalidChunkGroup = false;
for (const id of chunkIds) {
if (hasCssDebuggingLoad) {
if (chunkIds.length > 1) {
throw new Error("Found multiple chunks in factory that loads the CSS debugging chunk");
}
invalidChunks.add(id);
invalidChunkGroup = true;
break;
}
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))

View file

@ -17,7 +17,7 @@
*/
import { onceDefined } from "@shared/onceDefined";
import electron, { app, BrowserWindowConstructorOptions, Menu } from "electron";
import electron, { app, BrowserWindowConstructorOptions, Menu, nativeTheme } from "electron";
import { dirname, join } from "path";
import { initIpc } from "./ipcMain";
@ -100,6 +100,19 @@ if (!IS_VANILLA) {
super(options);
initIpc(this);
// Workaround for https://github.com/electron/electron/issues/43367. Vesktop also has its own workaround
// @TODO: Remove this when the issue is fixed
if (IS_DISCORD_DESKTOP) {
this.webContents.on("devtools-opened", () => {
if (!nativeTheme.shouldUseDarkColors) return;
nativeTheme.themeSource = "light";
setTimeout(() => {
nativeTheme.themeSource = "dark";
}, 100);
});
}
} else super(options);
}
}

View file

@ -0,0 +1,24 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "DynamicImageModalAPI",
authors: [Devs.sadan, Devs.Nuckyz],
description: "Allows you to omit either width or height when opening an image modal",
patches: [
{
find: "SCALE_DOWN:",
replacement: {
match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/,
replace: (_, width, height) => `!((null == ${width} || 0 === ${width}) && (null == ${height} || 0 === ${height}))`
}
}
]
});

View file

@ -31,7 +31,7 @@ export default definePlugin({
match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/,
replace: "$&vencordProps=$1,"
}, {
match: /\.Messages\.GUILD_OWNER(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
}
]

View file

@ -25,7 +25,7 @@ export default definePlugin({
authors: [Devs.Cyn],
patches: [
{
find: ".Messages.REMOVE_ATTACHMENT_BODY",
find: "#{intl::REMOVE_ATTACHMENT_BODY}",
replacement: {
match: /(?<=.container\)?,children:)(\[.+?\])/,
replace: "Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)",

View file

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

View file

@ -25,7 +25,7 @@ export default definePlugin({
authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
patches: [
{
find: ".Messages.EDIT_TEXTAREA_HELP",
find: "#{intl::EDIT_TEXTAREA_HELP}",
replacement: {
match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/,
replace: (match, args) => "" +

View file

@ -24,9 +24,9 @@ export default definePlugin({
description: "API to add buttons to message popovers.",
authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz],
patches: [{
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL",
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
replacement: {
match: /\.jsx\)\((\i\.\i),\{label:\i\.\i\.Messages\.MESSAGE_ACTION_REPLY.{0,200}?"reply-self".{0,50}?\}\):null(?=,.+?message:(\i))/,
match: /\.jsx\)\((\i\.\i),\{label:\i\.\i\.string\(\i\.\i#{intl::MESSAGE_ACTION_REPLY}.{0,200}?"reply-self".{0,50}?\}\):null(?=,.+?message:(\i))/,
replace: "$&,Vencord.Api.MessagePopover._buildPopoverElements($1,$2)"
}
}],

View file

@ -25,16 +25,16 @@ export default definePlugin({
description: "Api required for plugins that modify the server list",
patches: [
{
find: "Messages.DISCODO_DISABLED",
find: "#{intl::DISCODO_DISABLED}",
replacement: {
match: /(?<=Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
match: /(?<=#{intl::DISCODO_DISABLED}.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
}
},
{
find: "Messages.SERVERS,children",
find: "#{intl::SERVERS}),children",
replacement: {
match: /(?<=Messages\.SERVERS,children:)\i\.map\(\i\)/,
match: /(?<=#{intl::SERVERS}\),children:)\i\.map\(\i\)/,
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
}
}

View file

@ -59,15 +59,7 @@ export default definePlugin({
replace: "$&return;"
}
]
},
{
find: ".installedLogHooks)",
replacement: {
// if getDebugLogging() returns false, the hooks don't get installed.
match: "getDebugLogging(){",
replace: "getDebugLogging(){return false;"
}
},
],
startAt: StartAt.Init,

View file

@ -25,8 +25,9 @@ import ThemesTab from "@components/VencordSettings/ThemesTab";
import UpdaterTab from "@components/VencordSettings/UpdaterTab";
import VencordTab from "@components/VencordSettings/VencordTab";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { i18n, React } from "@webpack/common";
import { React } from "@webpack/common";
import gitHash from "~git-hash";
@ -57,20 +58,20 @@ export default definePlugin({
]
},
{
find: "Messages.ACTIVITY_SETTINGS",
find: ".SEARCH_NO_RESULTS&&0===",
replacement: [
{
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
},
{
match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,60}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
}
]
},
{
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
replacement: {
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
replace: "$2.open($1);return;"
@ -148,13 +149,18 @@ export default definePlugin({
if (!header) return;
try {
const names = {
top: i18n.Messages.USER_SETTINGS,
aboveNitro: i18n.Messages.BILLING_SETTINGS,
belowNitro: i18n.Messages.APP_SETTINGS,
aboveActivity: i18n.Messages.ACTIVITY_SETTINGS
top: getIntlMessage("USER_SETTINGS"),
aboveNitro: getIntlMessage("BILLING_SETTINGS"),
belowNitro: getIntlMessage("APP_SETTINGS"),
aboveActivity: getIntlMessage("ACTIVITY_SETTINGS")
};
return header === names[settingsLocation];
} catch {
return firstChild === "PREMIUM";
}
},
patchedSettings: new WeakSet(),
@ -197,7 +203,7 @@ export default definePlugin({
},
get electronVersion() {
return VencordNative.native.getVersions().electron || window.armcord?.electron || null;
return VencordNative.native.getVersions().electron || window.legcord?.electron || null;
},
get chromiumVersion() {

View file

@ -77,7 +77,7 @@ async function generateDebugInfoMessage() {
const client = (() => {
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
if ("legcord" in window) return `Legcord v${window.legcord.version}`;
// @ts-expect-error
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
@ -142,15 +142,15 @@ export default definePlugin({
required: true,
description: "Helps us provide support to you",
authors: [Devs.Ven],
dependencies: ["CommandsAPI", "UserSettingsAPI", "MessageAccessoriesAPI"],
dependencies: ["UserSettingsAPI", "MessageAccessoriesAPI"],
settings,
patches: [{
find: ".BEGINNING_DM.format",
find: "#{intl::BEGINNING_DM}",
replacement: {
match: /BEGINNING_DM\.format\(\{.+?\}\),(?=.{0,100}userId:(\i\.getRecipientId\(\)))/,
replace: "$& $self.ContributorDmWarningCard({ userId: $1 }),"
match: /#{intl::BEGINNING_DM},{.+?}\),(?=.{0,300}(\i)\.isMultiUserDM)/,
replace: "$& $self.renderContributorDmWarningCard({ channel: $1 }),"
}
}],
@ -235,7 +235,8 @@ export default definePlugin({
}
},
ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => {
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
const userId = channel.getRecipientId();
if (!isPluginDev(userId)) return null;
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;

View file

@ -69,7 +69,7 @@ export default definePlugin({
patches: [
{
find: ".Messages.ACCOUNT_SPEAKING_WHILE_MUTED",
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
group: true,
replacement: [
{

View file

@ -41,7 +41,7 @@ export default definePlugin({
},
{
// Status emojis
find: ".Messages.GUILD_OWNER,",
find: "#{intl::GUILD_OWNER}",
replacement: {
match: /(?<=\.activityEmoji,.+?animate:)\i/,
replace: "!0"

View file

@ -28,10 +28,18 @@ export default definePlugin({
patches: [
{
find: 'action:"EXPAND_ROLES"',
replacement: {
replacement: [
{
match: /(roles:\i(?=.+?(\i)\(!0\)[,;]\i\({action:"EXPAND_ROLES"}\)).+?\[\i,\2\]=\i\.useState\()!1\)/,
replace: (_, rest, setExpandedRoles) => `${rest}!0)`
},
{
// Fix not calculating non-expanded roles because the above patch makes the default "expanded",
// which makes the collapse button never show up and calculation never occur
match: /(?<=useLayoutEffect\(\(\)=>{if\()\i/,
replace: isExpanded => "false"
}
]
}
]
});

View file

@ -71,7 +71,7 @@ export default definePlugin({
description: "Anonymise uploaded file names",
patches: [
{
find: "instantBatchUpload:function",
find: "instantBatchUpload:",
replacement: {
match: /uploadFiles:(\i),/,
replace:
@ -86,9 +86,9 @@ export default definePlugin({
}
},
{
find: ".Messages.ATTACHMENT_UTILITIES_SPOILER",
find: "#{intl::ATTACHMENT_UTILITIES_SPOILER}",
replacement: {
match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}\i\.\i\.Messages\.ATTACHMENT_UTILITIES_SPOILER)/,
match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}#{intl::ATTACHMENT_UTILITIES_SPOILER})/,
replace: "arguments[0].canEdit!==false?$self.renderIcon(arguments[0]):null,"
},
},

View file

@ -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,

View file

@ -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 = /<script type="module" crossorigin src="([a-zA-Z0-9.\-/]+)"><\/script>/;
const APPLE_MUSIC_TOKEN_REGEX = /\w+="([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)",\w+="x-apple-jingle-correlation-key"/;
let cachedToken: string | undefined = undefined;
const getToken = async () => {
if (cachedToken) return cachedToken;
const html = await fetch("https://music.apple.com/").then(r => r.text());
const bundleUrl = new URL(html.match(APPLE_MUSIC_BUNDLE_REGEX)![1], "https://music.apple.com/");
const bundle = await fetch(bundleUrl).then(r => r.text());
const token = bundle.match(APPLE_MUSIC_TOKEN_REGEX)![1];
cachedToken = token;
return token;
};
async function fetchRemoteData({ id, name, artist, album }: { id: string, name: string, artist: string, album: string; }) {
if (id === cachedRemoteData?.id) {
if ("data" in cachedRemoteData) return cachedRemoteData.data;
@ -58,21 +50,39 @@ async function fetchRemoteData({ id, name, artist, album }: { id: string, name:
}
try {
const [songData, artistData] = await Promise.all([
fetch(makeSearchUrl("songs", artist + " " + album + " " + name), requestOptions).then(r => r.json()),
fetch(makeSearchUrl("artists", artist.split(/ *[,&] */)[0]), requestOptions).then(r => r.json())
]);
const dataUrl = new URL("https://amp-api-edge.music.apple.com/v1/catalog/us/search");
dataUrl.searchParams.set("platform", "web");
dataUrl.searchParams.set("l", "en-US");
dataUrl.searchParams.set("limit", "1");
dataUrl.searchParams.set("with", "serverBubbles");
dataUrl.searchParams.set("types", "songs");
dataUrl.searchParams.set("term", `${name} ${artist} ${album}`);
dataUrl.searchParams.set("include[songs]", "artists");
const appleMusicLink = songData?.songs?.data[0]?.attributes.url;
const songLink = songData?.songs?.data[0]?.id ? `https://song.link/i/${songData?.songs?.data[0]?.id}` : undefined;
const token = await getToken();
const albumArtwork = songData?.songs?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512");
const artistArtwork = artistData?.artists?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512");
const songData = await fetch(dataUrl, {
headers: {
"accept": "*/*",
"accept-language": "en-US,en;q=0.9",
"authorization": `Bearer ${token}`,
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
"origin": "https://music.apple.com",
},
})
.then(r => r.json())
.then(data => data.results.song.data[0]);
cachedRemoteData = {
id,
data: { appleMusicLink, songLink, albumArtwork, artistArtwork }
data: {
appleMusicLink: songData.attributes.url,
songLink: `https://song.link/i/${songData.id}`,
albumArtwork: songData.attributes.artwork.url.replace("{w}x{h}", "512x512"),
artistArtwork: songData.relationships.artists.data[0].attributes.artwork.url.replace("{w}x{h}", "512x512"),
}
};
return cachedRemoteData.data;
} catch (e) {
console.error("[AppleMusicRichPresence] Failed to fetch remote data:", e);

View file

@ -73,8 +73,8 @@ export default definePlugin({
},
async start() {
// ArmCord comes with its own arRPC implementation, so this plugin just confuses users
if ("armcord" in window) return;
// Legcord comes with its own arRPC implementation, so this plugin just confuses users
if ("legcord" in window) return;
if (ws) ws.close();
ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket

View file

@ -36,7 +36,7 @@ export default definePlugin({
settings,
patches: [
{
find: "BAN_CONFIRM_TITLE.",
find: "#{intl::BAN_CONFIRM_TITLE}",
replacement: {
match: /src:\i\("?\d+"?\)/g,
replace: "src:$self.source"

View file

@ -0,0 +1,11 @@
# Better Folders
Better Folders offers a variety of options to improve your folder experience
Always show the folder icon, regardless of if the folder is open or not
Only have one folder open at a time
Open folders in a sidebar:
![A folder open in a separate sidebar](https://github.com/user-attachments/assets/432d3146-8091-4bae-9c1e-c19046c72947)

View file

@ -18,9 +18,10 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { FluxDispatcher, i18n, useMemo } from "@webpack/common";
import { FluxDispatcher, useMemo } from "@webpack/common";
import FolderSideBar from "./FolderSideBar";
@ -30,9 +31,9 @@ enum FolderIconDisplay {
MoreThanOneFolderExpanded
}
const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
const SortedGuildStore = findStoreLazy("SortedGuildStore");
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
const SortedGuildStore = findStoreLazy("SortedGuildStore");
const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
let lastGuildId = null as string | null;
@ -118,17 +119,17 @@ export default definePlugin({
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
{
match: /\[(\i)\]=(\(0,\i\.\i\).{0,40}getGuildsTree\(\).+?}\))(?=,)/,
replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0].isBetterFolders,betterFoldersOriginalTree,arguments[0].betterFoldersExpandedIds)`
replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0]?.isBetterFolders,betterFoldersOriginalTree,arguments[0]?.betterFoldersExpandedIds)`
},
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
{
match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/,
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0].isBetterFolders))"
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0]?.isBetterFolders))"
},
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
{
match: /unreadMentionsIndicatorBottom,.+?}\)\]/,
replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0].isBetterFolders))"
replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0]?.isBetterFolders))"
},
// Export the isBetterFolders variable to the folders component
{
@ -167,31 +168,31 @@ export default definePlugin({
{
predicate: () => settings.store.keepIcons,
match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i),.+?;)(?=let)/,
replace: (_, isExpanded) => `${isExpanded}=!!arguments[0].isBetterFolders&&${isExpanded};`
replace: (_, isExpanded) => `${isExpanded}=!!arguments[0]?.isBetterFolders&&${isExpanded};`
},
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
{
predicate: () => !settings.store.keepIcons,
match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/,
replace: "!!arguments[0].isBetterFolders&&"
match: /(?<=#{intl::SERVER_FOLDER_PLACEHOLDER}.+?useTransition\)\()/,
replace: "$self.shouldShowTransition(arguments[0])&&"
},
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
{
predicate: () => !settings.store.keepIcons,
match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/,
replace: (m, isExpanded) => `${m}!arguments[0].isBetterFolders&&${isExpanded}?null:`
replace: (m, isExpanded) => `${m}$self.shouldRenderContents(arguments[0],${isExpanded})?null:`
},
{
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.wrapper,children:\[)/,
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)&&"
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&"
},
{
// Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/,
replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)?null:"
replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)?null:"
}
]
},
@ -200,12 +201,12 @@ export default definePlugin({
predicate: () => settings.store.sidebar,
replacement: {
// Render the Better Folders sidebar
match: /(?<=({className:\i\.guilds,themeOverride:\i})\))/,
replace: ",$self.FolderSideBar($1)"
match: /(container.{0,50}({className:\i\.guilds,themeOverride:\i})\))/,
replace: "$1,$self.FolderSideBar({...$2})"
}
},
{
find: ".Messages.DISCODO_DISABLED",
find: "#{intl::DISCODO_DISABLED}",
predicate: () => settings.store.closeAllHomeButton,
replacement: {
// Close all folders when clicking the home button
@ -276,7 +277,11 @@ export default definePlugin({
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
return child => {
if (isBetterFolders) {
return child?.props?.["aria-label"] === i18n.Messages.SERVERS;
try {
return child?.props?.["aria-label"] === getIntlMessage("SERVERS");
} catch (e) {
console.error(e);
}
}
return true;
};
@ -306,7 +311,20 @@ export default definePlugin({
}
},
FolderSideBar: guildsBarProps => <FolderSideBar {...guildsBarProps} />,
shouldShowTransition(props: any) {
// Pending guilds
if (props?.folderNode?.id === 1) return true;
closeFolders
return !!props?.isBetterFolders;
},
shouldRenderContents(props: any, isExpanded: boolean) {
// Pending guilds
if (props?.folderNode?.id === 1) return false;
return !props?.isBetterFolders && isExpanded;
},
FolderSideBar,
closeFolders,
});

View file

@ -34,9 +34,9 @@ export default definePlugin({
},
},
{
find: ".Messages.GIF,",
find: "#{intl::GIF}",
replacement: {
match: /alt:(\i)=(\i\.\i\.Messages\.GIF)(?=,[^}]*\}=(\i))/,
match: /alt:(\i)=(\i\.\i\.string\(\i\.\i#{intl::GIF}\))(?=,[^}]*\}=(\i))/,
replace:
// rename prop so we can always use default value
"alt_$$:$1=$self.altify($3)||$2",

View file

@ -63,9 +63,9 @@ export default definePlugin({
}
},
{
find: "Messages.NOTE_PLACEHOLDER",
find: "#{intl::NOTE_PLACEHOLDER}",
replacement: {
match: /\.NOTE_PLACEHOLDER,/,
match: /#{intl::NOTE_PLACEHOLDER}\),/,
replace: "$&spellCheck:!$self.noSpellCheck,"
}
}

View file

@ -99,7 +99,11 @@ export default definePlugin({
id="vc-view-role-icon"
label="View Role Icon"
action={() => {
openImageModal(`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`);
openImageModal({
url: `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`,
height: 128,
width: 128
});
}}
icon={ImageIcon}
/>

View file

@ -47,7 +47,7 @@ export default definePlugin({
},
{
find: ".ADD_ROLE_A11Y_LABEL",
find: "#{intl::ADD_ROLE_A11Y_LABEL}",
all: true,
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
noWarn: true,

View file

@ -60,7 +60,7 @@ export default definePlugin({
patches: [
{
find: "Messages.AUTH_SESSIONS_SESSION_LOG_OUT",
find: "#{intl::AUTH_SESSIONS_SESSION_LOG_OUT}",
replacement: [
// Replace children with a single label with state
{

View file

@ -0,0 +1,69 @@
/*
* 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 { getIntlMessage } from "@utils/discord";
import { isObjectEmpty } from "@utils/misc";
import { Alerts, Menu, useMemo, useState } from "@webpack/common";
import Plugins from "~plugins";
function onRestartNeeded() {
Alerts.show({
title: "Restart required",
body: <p>You have changed settings that require a restart.</p>,
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 (
<>
<Menu.MenuControlItem
id="vc-plugins-search"
control={(props, ref) => (
<Menu.MenuSearchControl
{...props}
query={query}
onChange={setQuery}
ref={ref}
placeholder={getIntlMessage("SEARCH")}
/>
)}
/>
{!!plugins.length && <Menu.MenuSeparator />}
{plugins.map(p => (
<Menu.MenuItem
key={p.name}
id={p.name}
label={p.name}
action={() => openPluginModal(p, onRestartNeeded)}
/>
))}
</>
);
}

View file

@ -7,12 +7,15 @@
import { definePluginSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { waitFor } from "@webpack";
import { ComponentDispatch, FocusLock, i18n, Menu, useEffect, useRef } from "@webpack/common";
import { ComponentDispatch, FocusLock, 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("");
@ -109,7 +112,7 @@ export default definePlugin({
predicate: () => settings.store.disableFade
},
{ // Load menu TOC eagerly
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
find: "#{intl::USER_SETTINGS_WITH_BUILD_OVERRIDE}",
replacement: {
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
replace: "$&(async ()=>$2)(),"
@ -117,14 +120,22 @@ export default definePlugin({
predicate: () => settings.store.eagerLoad
},
{ // Settings cog context menu
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
replacement: {
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
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.
//
@ -149,7 +160,7 @@ export default definePlugin({
if (item.section === "HEADER") {
items.push({ label: item.label, items: [] });
} else if (item.section === "DIVIDER") {
items.push({ label: i18n.Messages.OTHER_OPTIONS, items: [] });
items.push({ label: getIntlMessage("OTHER_OPTIONS"), items: [] });
} else {
items.at(-1)!.items.push(item);
}

View file

@ -57,7 +57,11 @@ export const handleViewPreview = async ({ guildId, channelId, ownerId }: Applica
const previewUrl = await ApplicationStreamPreviewStore.getPreviewURL(guildId, channelId, ownerId);
if (!previewUrl) return;
openImageModal(previewUrl);
openImageModal({
url: previewUrl,
height: 720,
width: 1280
});
};
export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => {

View file

@ -45,8 +45,8 @@ export default definePlugin({
{
find: ".embedWrapper,embed",
replacement: [{
match: /\.embedWrapper(?=.+?channel_id:(\i)\.id)/g,
replace: "$&+($1.nsfw?' vc-nsfw-img':'')"
match: /\.container/,
replace: "$&+(this.props.channel.nsfw? ' vc-nsfw-img': '')"
}]
}
],

View file

@ -155,4 +155,5 @@ export const defaultRules = [
"igshid",
"igsh",
"share_id@reddit.com",
"si@soundcloud.com",
];

View file

@ -14,7 +14,7 @@ import definePlugin, { OptionType, StartAt } from "@utils/types";
import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common";
const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
const colorPresets = [
"#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D",
@ -110,7 +110,7 @@ const settings = definePluginSettings({
export default definePlugin({
name: "ClientTheme",
authors: [Devs.F53, Devs.Nuckyz],
authors: [Devs.Nuckyz],
description: "Recreation of the old client theme experiment. Add a color to your Discord client theme",
settings,

View file

@ -1,5 +1,5 @@
# ConsoleJanitor
Disables annoying console messages/errors. This plugin mainly removes errors/warnings that happen all the time and noisy/spammy logging messages.
Disables annoying console messages/errors. This plugin mainly removes errors/warnings that happen all the time and Discord logger messages.
Some of the disabled messages include the "notosans-400-normalitalic" error and MessageActionCreators, Routing/Utils loggers.
One of the disabled messages is the "Window state not initialized" warning, for example.

View file

@ -6,7 +6,7 @@
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin, { OptionType, StartAt } from "@utils/types";
const Noop = () => { };
const NoopLogger = {
@ -22,10 +22,12 @@ const NoopLogger = {
fileOnly: Noop
};
const logAllow = new Set();
const settings = definePluginSettings({
disableNoisyLoggers: {
disableLoggers: {
type: OptionType.BOOLEAN,
description: "Disable noisy loggers like the MessageActionCreators",
description: "Disables Discords loggers",
default: false,
restartNeeded: true
},
@ -34,18 +36,43 @@ const settings = definePluginSettings({
description: "Disable the Spotify logger, which leaks account information and access token",
default: true,
restartNeeded: true
},
whitelistedLoggers: {
type: OptionType.STRING,
description: "Semi colon separated list of loggers to allow even if others are hidden",
default: "GatewaySocket; Routing/Utils",
onChange(newVal: string) {
logAllow.clear();
newVal.split(";").map(x => x.trim()).forEach(logAllow.add.bind(logAllow));
}
}
});
export default definePlugin({
name: "ConsoleJanitor",
description: "Disables annoying console messages/errors",
authors: [Devs.Nuckyz],
authors: [Devs.Nuckyz, Devs.sadan],
settings,
startAt: StartAt.Init,
start() {
logAllow.clear();
this.settings.store.whitelistedLoggers?.split(";").map(x => x.trim()).forEach(logAllow.add.bind(logAllow));
},
NoopLogger: () => NoopLogger,
shouldLog(logger: string) {
return logAllow.has(logger);
},
patches: [
{
find: 'react-spring: The "interpolate" function',
replacement: {
match: /,console.warn\('react-spring: The "interpolate" function is deprecated in v10 \(use "to" instead\)'\)/,
replace: ""
}
},
{
find: 'console.warn("Window state not initialized"',
replacement: {
@ -103,34 +130,13 @@ export default definePlugin({
replace: ""
}
},
...[
'("MessageActionCreators")', '("ChannelMessages")',
'("Routing/Utils")', '("RTCControlSocket")',
'("ConnectionEventFramerateReducer")', '("RTCLatencyTestManager")',
'("OverlayBridgeStore")', '("RPCServer:WSS")', '("RPCServer:IPC")'
].map(logger => ({
find: logger,
predicate: () => settings.store.disableNoisyLoggers,
all: true,
replacement: {
match: new RegExp(String.raw`new \i\.\i${logger.replace(/([()])/g, "\\$1")}`),
replace: `$self.NoopLogger${logger}`
}
})),
// Patches discords generic logger function
{
find: '"Experimental codecs: "',
predicate: () => settings.store.disableNoisyLoggers,
find: "Σ:",
predicate: () => settings.store.disableLoggers,
replacement: {
match: /new \i\.\i\("Connection\("\.concat\(\i,"\)"\)\)/,
replace: "$self.NoopLogger()"
}
},
{
find: '"Handling ping: "',
predicate: () => settings.store.disableNoisyLoggers,
replacement: {
match: /new \i\.\i\("RTCConnection\("\.concat.+?\)\)(?=,)/,
replace: "$self.NoopLogger()"
match: /(?<=&&)(?=console)/,
replace: "$self.shouldLog(arguments[0])&&"
}
},
{

View file

@ -18,6 +18,7 @@
import { Devs } from "@utils/constants";
import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
import { runtimeHashMessageKey } from "@utils/intlHash";
import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
import { relaunch } from "@utils/native";
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
@ -104,6 +105,7 @@ function makeShortcuts() {
canonicalizeMatch,
canonicalizeReplace,
canonicalizeReplacement,
runtimeHashMessageKey,
fakeRender: (component: ComponentType, props: any) => {
const prevWin = fakeRenderWin?.deref();
const win = prevWin?.closed === false

View file

@ -25,7 +25,7 @@ export default definePlugin({
authors: [Devs.Obsidian, Devs.Nuckyz],
patches: [
{
find: ".Messages.PREVIEW_BYTES_LEFT.format(",
find: "#{intl::PREVIEW_BYTES_LEFT}",
replacement: {
match: /\.footerGap.+?url:\i,fileName:\i,fileSize:\i}\),(?<=fileContents:(\i),bytesLeft:(\i).+?)/g,
replace: "$&$self.addCopyButton({fileContents:$1,bytesLeft:$2}),"

View file

@ -1,6 +1,7 @@
.vc-cfc-button {
color: var(--interactive-normal);
cursor: pointer;
padding-left: 4px;
}
.vc-cfc-button:hover {

View file

@ -67,7 +67,7 @@ export default definePlugin({
patches: [
{
find: ".Messages.ERRORS_UNEXPECTED_CRASH",
find: "#{intl::ERRORS_UNEXPECTED_CRASH}",
replacement: {
match: /this\.setState\((.+?)\)/,
replace: "$self.handleCrash(this,$1);"
@ -175,7 +175,7 @@ export default definePlugin({
}
if (settings.store.attemptToNavigateToHome) {
try {
NavigationRouter.transitionTo("/channels/@me");
NavigationRouter.transitionToGuild("@me");
} catch (err) {
CrashHandlerLogger.debug("Failed to navigate to home", err);
}

View file

@ -37,8 +37,8 @@ export default definePlugin({
find: 'type:"IDLE",idle:',
replacement: [
{
match: /(?<=Date\.now\(\)-\i>)\i\.\i/,
replace: "$self.getIdleTimeout()"
match: /(?<=Date\.now\(\)-\i>)\i\.\i\|\|/,
replace: "$self.getIdleTimeout()||"
},
{
match: /Math\.min\((\i\.\i\.getSetting\(\)\*\i\.\i\.\i\.SECOND),\i\.\i\)/,

View file

@ -41,7 +41,7 @@ export default definePlugin({
{
find: "DefaultCustomizationSections",
replacement: {
match: /(?<=USER_SETTINGS_AVATAR_DECORATION},"decoration"\),)/,
match: /(?<=#{intl::USER_SETTINGS_AVATAR_DECORATION}\)},"decoration"\),)/,
replace: "$self.DecorSection(),"
}
},
@ -54,7 +54,7 @@ export default definePlugin({
replace: "$self.DecorationGridItem=$&"
},
{
match: /(?<==)\i=>{let{user:\i,avatarDecoration.{300,600}decorationGridItemChurned/,
match: /(?<==)\i=>{let{user:\i,avatarDecoration/,
replace: "$self.DecorationGridDecoration=$&"
},
// Remove NEW label from decor avatar decorations

View file

@ -93,7 +93,7 @@ export const useAuthorizationStore = proxyLazy(() => zustandCreate(
} as AuthorizationState),
{
name: "decor-auth",
getStorage: () => indexedDBStorage,
storage: indexedDBStorage,
partialize: state => ({ tokens: state.tokens }),
onRehydrateStorage: () => state => state?.init()
}

View file

@ -95,10 +95,13 @@ export const useUsersDecorationsStore = proxyLazy(() => zustandCreate((set: any,
} as UsersDecorationsState)));
export function useUserDecorAvatarDecoration(user?: User): AvatarDecoration | null | undefined {
try {
const [decorAvatarDecoration, setDecorAvatarDecoration] = useState<string | null>(user ? useUsersDecorationsStore.getState().getAsset(user.id) ?? null : null);
useEffect(() => {
const destructor = useUsersDecorationsStore.subscribe(
const destructor = (() => {
try {
return useUsersDecorationsStore.subscribe(
state => {
if (!user) return;
const newDecorAvatarDecoration = state.getAsset(user.id);
@ -106,13 +109,25 @@ export function useUserDecorAvatarDecoration(user?: User): AvatarDecoration | nu
if (decorAvatarDecoration !== newDecorAvatarDecoration) setDecorAvatarDecoration(newDecorAvatarDecoration);
}
);
} catch {
return () => { };
}
})();
try {
if (user) {
const { fetch: fetchUserDecorAvatarDecoration } = useUsersDecorationsStore.getState();
fetchUserDecorAvatarDecoration(user.id);
}
} catch { }
return destructor;
}, []);
return decorAvatarDecoration ? { asset: decorAvatarDecoration, skuId: SKU_ID } : null;
} catch (e) {
console.error(e);
}
return null;
}

View file

@ -5,7 +5,8 @@
*/
import { PlusIcon } from "@components/Icons";
import { i18n, Text } from "@webpack/common";
import { getIntlMessage } from "@utils/discord";
import { Text } from "@webpack/common";
import { HTMLProps } from "react";
import { DecorationGridItem } from ".";
@ -24,7 +25,7 @@ export default function DecorationGridCreate(props: DecorationGridCreateProps) {
variant="text-xs/normal"
color="header-primary"
>
{i18n.Messages.CREATE}
{getIntlMessage("CREATE")}
</Text>
</DecorationGridItem >;
}

View file

@ -5,7 +5,8 @@
*/
import { NoEntrySignIcon } from "@components/Icons";
import { i18n, Text } from "@webpack/common";
import { getIntlMessage } from "@utils/discord";
import { Text } from "@webpack/common";
import { HTMLProps } from "react";
import { DecorationGridItem } from ".";
@ -24,7 +25,7 @@ export default function DecorationGridNone(props: DecorationGridNoneProps) {
variant="text-xs/normal"
color="header-primary"
>
{i18n.Messages.NONE}
{getIntlMessage("NONE")}
</Text>
</DecorationGridItem >;
}

View file

@ -10,6 +10,7 @@ import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins";
import { classes, copyWithToast } from "@utils/misc";
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Queue } from "@utils/Queue";
import { findComponentByCodeLazy } from "@webpack";
import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Text, Tooltip, useEffect, UserStore, UserUtils, useState } from "@webpack/common";
import { User } from "discord-types/general";
@ -49,6 +50,8 @@ interface SectionHeaderProps {
section: Section;
}
const fetchAuthorsQueue = new Queue();
function SectionHeader({ section }: SectionHeaderProps) {
const hasSubtitle = typeof section.subtitle !== "undefined";
const hasAuthorIds = typeof section.authorIds !== "undefined";
@ -56,17 +59,18 @@ function SectionHeader({ section }: SectionHeaderProps) {
const [authors, setAuthors] = useState<User[]>([]);
useEffect(() => {
(async () => {
fetchAuthorsQueue.push(async () => {
if (!section.authorIds) return;
for (const authorId of section.authorIds) {
const author = UserStore.getUser(authorId) ?? await UserUtils.getUser(authorId);
const author = UserStore.getUser(authorId) ?? await UserUtils.getUser(authorId).catch(() => null);
if (author == null) continue;
setAuthors(authors => [...authors, author]);
}
})();
});
}, [section.authorIds]);
return <div>
<Flex>
<Forms.FormTitle style={{ flexGrow: 1 }}>{section.title}</Forms.FormTitle>

View file

@ -20,7 +20,7 @@ import { AvatarDecorationModalPreview } from "../components";
const FileUpload = findComponentByCodeLazy("fileUploadInput,");
const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE=3]="POSITIVE', {
HelpMessageTypes: filters.byProps("POSITIVE", "WARNING"),
HelpMessageTypes: filters.byProps("POSITIVE", "WARNING", "INFO"),
HelpMessage: filters.byCode(".iconDiv")
});
@ -119,8 +119,8 @@ function CreateDecorationModal(props: ModalProps) {
/>
</div>
</div>
<Forms.FormText type="description" className={Margins.bottom16}>
<br />You can receive updates on your decoration's review by joining <Link
<HelpMessage messageType={HelpMessageTypes.INFO} className={Margins.bottom8}>
To receive updates on your decoration's review, join <Link
href={`https://discord.gg/${INVITE_KEY}`}
onClick={async e => {
e.preventDefault();
@ -138,8 +138,8 @@ function CreateDecorationModal(props: ModalProps) {
}}
>
Decor's Discord server
</Link>.
</Forms.FormText>
</Link> and allow direct messages.
</HelpMessage>
</ErrorBoundary>
</ModalContent>
<ModalFooter className={cl("modal-footer")}>

View file

@ -27,7 +27,7 @@ export default definePlugin({
authors: [Devs.Nuckyz],
patches: [
{
find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
find: "#{intl::BOT_CALL_IDLE_DISCONNECT_2}",
replacement: {
match: /,?(?=\i\(this,"idleTimeout",new \i\.\i\))/,
replace: ";return;"

View file

@ -310,7 +310,8 @@ function buildMenuItem(type: "Emoji" | "Sticker", fetchData: () => Promisable<Om
}
function isGifUrl(url: string) {
return new URL(url).pathname.endsWith(".gif");
const u = new URL(url);
return u.pathname.endsWith(".gif") || u.searchParams.get("animated") === "true";
}
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {

View file

@ -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 { findByPropsLazy } from "@webpack";
import { findByPropsLazy, findLazy } from "@webpack";
import { Forms, React } from "@webpack/common";
import hideBugReport from "./hideBugReport.css?managed";
const KbdStyles = findByPropsLazy("key", "combo");
const BugReporterExperiment = findLazy(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: () => {

View file

@ -20,11 +20,11 @@ import { addPreEditListener, addPreSendListener, removePreEditListener, removePr
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord";
import { getCurrentGuild, getEmojiURL } from "@utils/discord";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin, { OptionType, Patch } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import type { Emoji } from "@webpack/types";
import type { Message } from "discord-types/general";
import { applyPalette, GIFEncoder, quantize } from "gifenc";
@ -194,6 +194,26 @@ const hasExternalStickerPerms = (channelId: string) => hasPermission(channelId,
const hasEmbedPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.EMBED_LINKS);
const hasAttachmentPerms = (channelId: string) => hasPermission(channelId, PermissionsBits.ATTACH_FILES);
function makeBypassPatches(): Omit<Patch, "plugin"> {
const mapping: Array<{ func: string, predicate?: () => boolean; }> = [
{ func: "canUseCustomStickersEverywhere", predicate: () => settings.store.enableStickerBypass },
{ func: "canUseHighVideoUploadQuality", predicate: () => settings.store.enableStreamQualityBypass },
{ func: "canStreamQuality", predicate: () => settings.store.enableStreamQualityBypass },
{ func: "canUseClientThemes" },
{ func: "canUseCustomNotificationSounds" },
{ func: "canUsePremiumAppIcons" }
];
return {
find: "canUseCustomStickersEverywhere:",
replacement: mapping.map(({ func, predicate }) => ({
match: new RegExp(String.raw`(?<=${func}:function\(\i(?:,\i)?\){)`),
replace: "return true;",
predicate
}))
};
}
export default definePlugin({
name: "FakeNitro",
authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.fawn, Devs.captain, Devs.Nuckyz, Devs.AutumnVN],
@ -203,6 +223,17 @@ export default definePlugin({
settings,
patches: [
// General bypass patches
makeBypassPatches(),
// Patch the emoji picker in voice calls to not be bypassed by fake nitro
{
find: "emojiItemDisabled]",
predicate: () => settings.store.enableEmojiBypass,
replacement: {
match: /CHAT/,
replace: "STATUS"
}
},
{
find: ".PREMIUM_LOCKED;",
group: true,
@ -243,15 +274,6 @@ export default definePlugin({
replace: (_, rest1, rest2) => `${rest1},fakeNitroOriginal){if(!fakeNitroOriginal)return false;${rest2}`
}
},
// Allow stickers to be sent everywhere
{
find: "canUseCustomStickersEverywhere:function",
predicate: () => settings.store.enableStickerBypass,
replacement: {
match: /canUseCustomStickersEverywhere:function\(\i\){/,
replace: "$&return true;"
},
},
// Make stickers always available
{
find: '"SENDABLE"',
@ -261,37 +283,15 @@ export default definePlugin({
replace: "true?"
}
},
// Allow streaming with high quality
{
find: "canUseHighVideoUploadQuality:function",
predicate: () => settings.store.enableStreamQualityBypass,
replacement: [
"canUseHighVideoUploadQuality",
"canStreamQuality",
].map(func => {
return {
match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"),
replace: "$&return true;"
};
})
},
// Remove boost requirements to stream with high quality
{
find: "STREAM_FPS_OPTION.format",
find: "#{intl::STREAM_FPS_OPTION}",
predicate: () => settings.store.enableStreamQualityBypass,
replacement: {
match: /guildPremiumTier:\i\.\i\.TIER_\d,?/g,
replace: ""
}
},
// Allow client themes to be changeable
{
find: "canUseClientThemes:function",
replacement: {
match: /canUseClientThemes:function\(\i\){/,
replace: "$&return true;"
}
},
{
find: '"UserSettingsProtoStore"',
replacement: [
@ -350,13 +350,13 @@ export default definePlugin({
{
// Filter attachments to remove fake nitro stickers or emojis
predicate: () => settings.store.transformStickers,
match: /renderAttachments\(\i\){let{attachments:(\i).+?;/,
match: /renderAttachments\(\i\){.+?{attachments:(\i).+?;/,
replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});`
}
]
},
{
find: ".Messages.STICKER_POPOUT_UNJOINED_PRIVATE_GUILD_DESCRIPTION.format",
find: "#{intl::STICKER_POPOUT_UNJOINED_PRIVATE_GUILD_DESCRIPTION}",
predicate: () => settings.store.transformStickers,
replacement: [
{
@ -381,7 +381,7 @@ export default definePlugin({
}
},
{
find: ".Messages.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION",
find: "#{intl::EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION}",
predicate: () => settings.store.transformEmojis,
replacement: {
// Add the fake nitro emoji notice
@ -389,14 +389,6 @@ export default definePlugin({
replace: (_, reactNode, props) => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!${props}?.fakeNitroNode?.fake)`
}
},
// Allow using custom app icons
{
find: "canUsePremiumAppIcons:function",
replacement: {
match: /canUsePremiumAppIcons:function\(\i\){/,
replace: "$&return true;"
}
},
// Separate patch for allowing using custom app icons
{
find: /\.getCurrentDesktopIcon.{0,25}\.isPremium/,
@ -412,14 +404,6 @@ export default definePlugin({
match: /(?<=type:"(?:SOUNDBOARD_SOUNDS_RECEIVED|GUILD_SOUNDBOARD_SOUND_CREATE|GUILD_SOUNDBOARD_SOUND_UPDATE|GUILD_SOUNDBOARD_SOUNDS_UPDATE)".+?available:)\i\.available/g,
replace: "true"
}
},
// Allow using custom notification sounds
{
find: "canUseCustomNotificationSounds:function",
replacement: {
match: /canUseCustomNotificationSounds:function\(\i\){/,
replace: "$&return true;"
}
}
],
@ -936,7 +920,7 @@ export default definePlugin({
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
const url = new URL(IconUtils.getEmojiURL({ id: emoji.id, animated: emoji.animated, size: s.emojiSize }));
const url = new URL(getEmojiURL(emoji.id, emoji.animated, s.emojiSize));
url.searchParams.set("size", s.emojiSize.toString());
url.searchParams.set("name", emoji.name);
@ -969,7 +953,7 @@ export default definePlugin({
hasBypass = true;
const url = new URL(IconUtils.getEmojiURL({ id: emoji.id, animated: emoji.animated, size: s.emojiSize }));
const url = new URL(getEmojiURL(emoji.id, emoji.animated, s.emojiSize));
url.searchParams.set("size", s.emojiSize.toString());
url.searchParams.set("name", emoji.name);

View file

@ -108,10 +108,10 @@ interface ProfileModalProps {
isTryItOutFlow: boolean;
}
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
const ProfileModal = findComponentByCodeLazy<ProfileModalProps>("isTryItOutFlow:", "pendingThemeColors:", "pendingAvatarDecoration:", "EDIT_PROFILE_BANNER");
const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i(\("?.+?"?\)).then\(\i\.bind\(\i,"?(.+?)"?\)\)/);
const requireColorPicker = extractAndLoadChunksLazy(["#{intl::USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON}"], /createPromise:\(\)=>\i\.\i(\("?.+?"?\)).then\(\i\.bind\(\i,"?(.+?)"?\)\)/);
export default definePlugin({
name: "FakeProfileThemes",
@ -126,9 +126,9 @@ export default definePlugin({
}
},
{
find: ".USER_SETTINGS_RESET_PROFILE_THEME",
find: "#{intl::USER_SETTINGS_RESET_PROFILE_THEME}",
replacement: {
match: /RESET_PROFILE_THEME}\)(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/,
match: /#{intl::USER_SETTINGS_RESET_PROFILE_THEME}\)}\)(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/,
replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})"
}
}

View file

@ -0,0 +1,3 @@
# Fix Images Quality
Prevents images from being loaded as webp, which can cause quality loss

View file

@ -8,16 +8,15 @@ import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "NoDefaultHangStatus",
description: "Disable the default hang status when joining voice channels",
authors: [Devs.D3SOX],
name: "FixImagesQuality",
description: "Prevents images from being loaded as webp, which can cause quality loss",
authors: [Devs.Nuckyz],
patches: [
{
find: ".CHILLING)",
find: "getFormatQuality(){",
replacement: {
match: /{enableHangStatus:(\i),/,
replace: "{_enableHangStatus:$1=false,"
match: /(?<=null;return )\i\.\i&&\(\i\|\|!\i\.isAnimated.+?:(?=\i&&\(\i="png"\))/,
replace: ""
}
}
]

View file

@ -27,7 +27,7 @@ export default definePlugin({
authors: [Devs.D3SOX, Devs.Nickyux],
patches: [
{
find: ".Messages.GUILD_OWNER,",
find: "#{intl::GUILD_OWNER}",
replacement: {
match: /,isOwner:(\i),/,
replace: ",_isOwner:$1=$self.isGuildOwner(e),"

View file

@ -27,7 +27,6 @@ export default definePlugin({
name: "FriendInvites",
description: "Create and manage friend invite links via slash commands (/create friend invite, /view friend invites, /revoke friend invites).",
authors: [Devs.afn, Devs.Dziurwa],
dependencies: ["CommandsAPI"],
commands: [
{
name: "create friend invite",

View file

@ -26,7 +26,7 @@ export default definePlugin({
{
find: ".PANEL}),nicknameIcons",
replacement: {
match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id)}\)}\)/,
match: /#{intl::USER_PROFILE_MEMBER_SINCE}\),.{0,100}userId:(\i\.id)}\)}\)/,
replace: "$&,$self.FriendsSinceComponent({userId:$1,isSidebar:true})"
}
},
@ -34,7 +34,7 @@ export default definePlugin({
{
find: "action:\"PRESS_APP_CONNECTION\"",
replacement: {
match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id),.{0,100}}\)}\),/,
match: /#{intl::USER_PROFILE_MEMBER_SINCE}\),.{0,100}userId:(\i\.id),.{0,100}}\)}\),/,
replace: "$&,$self.FriendsSinceComponent({userId:$1,isSidebar:false}),"
}
}

View file

@ -0,0 +1,5 @@
# FullSearchContext
Makes the message context menu in message search results have all options you'd expect.
![](https://github.com/user-attachments/assets/472d1327-3935-44c7-b7c4-0978b5348550)

View file

@ -0,0 +1,112 @@
/*
* Vencord, a modification for Discord's desktop app
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { NoopComponent } from "@utils/react";
import definePlugin from "@utils/types";
import { filters, findByPropsLazy, waitFor } from "@webpack";
import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common";
import { Message } from "discord-types/general";
const { useMessageMenu } = findByPropsLazy("useMessageMenu");
interface CopyIdMenuItemProps {
id: string;
label: string;
}
let CopyIdMenuItem: (props: CopyIdMenuItemProps) => React.ReactElement | null = NoopComponent;
waitFor(filters.componentByCode('"devmode-copy-id-".concat'), m => CopyIdMenuItem = m);
function MessageMenu({ message, channel, onHeightUpdate }) {
const canReport = message.author &&
!(message.author.id === UserStore.getCurrentUser().id || message.author.system);
return useMessageMenu({
navId: "message-actions",
ariaLabel: getIntlMessage("MESSAGE_UTILITIES_A11Y_LABEL"),
message,
channel,
canReport,
onHeightUpdate,
onClose: () => ContextMenuApi.closeContextMenu(),
textSelection: "",
favoriteableType: null,
favoriteableId: null,
favoriteableName: null,
itemHref: void 0,
itemSrc: void 0,
itemSafeSrc: void 0,
itemTextContent: void 0,
isFullSearchContextMenu: true
});
}
interface MessageActionsProps {
message: Message;
isFullSearchContextMenu?: boolean;
}
const contextMenuPatch: NavContextMenuPatchCallback = (children, props: MessageActionsProps) => {
if (props?.isFullSearchContextMenu == null) return;
const group = findGroupChildrenByChildId("devmode-copy-id", children, true);
group?.push(
CopyIdMenuItem({ id: props.message.author.id, label: getIntlMessage("COPY_ID_AUTHOR") })
);
};
migratePluginSettings("FullSearchContext", "SearchReply");
export default definePlugin({
name: "FullSearchContext",
description: "Makes the message context menu in message search results have all options you'd expect",
authors: [Devs.Ven, Devs.Aria],
patches: [{
find: "onClick:this.handleMessageClick,",
replacement: {
match: /this(?=\.handleContextMenu\(\i,\i\))/,
replace: "$self"
}
}],
handleContextMenu(event: React.MouseEvent, message: Message) {
const channel = ChannelStore.getChannel(message.channel_id);
if (!channel) return;
event.stopPropagation();
ContextMenuApi.openContextMenu(event, contextMenuProps =>
<MessageMenu
message={message}
channel={channel}
onHeightUpdate={contextMenuProps.onHeightUpdate}
/>
);
},
contextMenus: {
"message-actions": contextMenuPatch
}
});

View file

@ -92,7 +92,7 @@ export default definePlugin({
patches: [
{
find: ".Messages.ACCOUNT_SPEAKING_WHILE_MUTED",
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
replacement: {
match: /this\.renderNameZone\(\).+?children:\[/,
replace: "$&$self.GameActivityToggleButton(),"

View file

@ -166,7 +166,7 @@ export default definePlugin({
patches: [
{
find: "Messages.WELCOME_CTA_LABEL",
find: "#{intl::WELCOME_CTA_LABEL}",
replacement: {
match: /innerClassName:\i\.welcomeCTAButton,(?<={channel:\i,message:\i}=(\i).{0,400}?)/,
replace: "$&onContextMenu:(vcEvent)=>$self.pickSticker(vcEvent, $1),"

View file

@ -244,7 +244,7 @@ export default definePlugin({
patches: [
{
find: '="LocalActivityStore",',
find: '"LocalActivityStore"',
replacement: [
{
match: /HANG_STATUS.+?(?=!\i\(\)\(\i,\i\)&&)(?<=(\i)\.push.+?)/,
@ -253,16 +253,16 @@ export default definePlugin({
]
},
{
find: '="ActivityTrackingStore",',
find: '"ActivityTrackingStore"',
replacement: {
match: /getVisibleRunningGames\(\).+?;(?=for)(?<=(\i)=\i\.\i\.getVisibleRunningGames.+?)/,
replace: (m, runningGames) => `${m}${runningGames}=${runningGames}.filter(({id,name})=>$self.isActivityNotIgnored({type:0,application_id:id,name}));`
}
},
{
find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY",
find: "#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}",
replacement: {
match: /\.Messages\.SETTINGS_GAMES_TOGGLE_OVERLAY.+?}\(\),(?<={overlay:\i,.+?=(\i),.+?)(?=!(\i))/,
match: /#{intl::SETTINGS_GAMES_TOGGLE_OVERLAY}.+?}\(\),(?<={overlay:\i,.+?=(\i),.+?)(?=!(\i))/,
replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`
}
},

View file

@ -156,14 +156,10 @@ export default definePlugin({
patches: [
{
find: "Messages.OPEN_IN_BROWSER",
find: ".contain,SCALE_DOWN:",
replacement: {
// there are 2 image thingies. one for carosuel and one for the single image.
// so thats why i added global flag.
// also idk if this patch is good, should it be more specific?
// https://regex101.com/r/xfvNvV/1
match: /return.{1,200}\.wrapper.{1,200}src:\i,/g,
replace: `$&id: '${ELEMENT_ID}',`
match: /\.slide,\i\),/g,
replace: `$&id:"${ELEMENT_ID}",`
}
},
@ -183,15 +179,13 @@ export default definePlugin({
{
match: /componentWillUnmount\(\){/,
replace: "$&$self.unMountMagnifier();"
},
{
match: /componentDidUpdate\(\i\){/,
replace: "$&$self.updateMagnifier(this);"
}
]
},
{
find: ".carouselModal",
replacement: {
match: /(?<=\.carouselModal.{0,100}onClick:)\i,/,
replace: "()=>{},"
}
}
],
@ -226,6 +220,11 @@ export default definePlugin({
}
},
updateMagnifier(instance) {
this.unMountMagnifier();
this.renderMagnifier(instance);
},
unMountMagnifier() {
this.root?.unmount();
this.currentMagnifierElement = null;

View file

@ -23,12 +23,3 @@
/* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */
}
/* make the carousel take up less space so we can click the backdrop and exit out of it */
[class*="modalCarouselWrapper_"] {
top: 0 !important;
}
[class*="carouselModal_"] {
height: 0 !important;
}

View file

@ -32,7 +32,7 @@ export default definePlugin({
patches: [
// Counts header
{
find: ".FRIENDS_ALL_HEADER",
find: "#{intl::FRIENDS_ALL_HEADER}",
replacement: {
match: /toString\(\)\}\);case (\i\.\i)\.BLOCKED/,
replace: 'toString()});case $1.IMPLICIT:return "Implicit — "+arguments[1];case $1.BLOCKED'
@ -48,9 +48,9 @@ export default definePlugin({
},
// Sections header
{
find: ".FRIENDS_SECTION_ONLINE",
find: "#{intl::FRIENDS_SECTION_ONLINE}",
replacement: {
match: /(\(0,\i\.jsx\)\(\i\.TabBar\.Item,\{id:\i\.\i)\.BLOCKED,className:([^\s]+?)\.item,children:\i\.\i\.Messages\.BLOCKED\}\)/,
match: /(\(0,\i\.jsx\)\(\i\.TabBar\.Item,\{id:\i\.\i)\.BLOCKED,className:([^\s]+?)\.item,children:\i\.\i\.string\(\i\.\i#{intl::BLOCKED}\)\}\)/,
replace: "$1.IMPLICIT,className:$2.item,children:\"Implicit\"}),$&"
},
},
@ -117,7 +117,7 @@ export default definePlugin({
wrapSort(comparator: Function, row: any) {
return row.type === 5
? -UserAffinitiesStore.getUserAffinity(row.user.id)?.affinity ?? 0
? -(UserAffinitiesStore.getUserAffinity(row.user.id)?.affinity ?? 0)
: comparator(row);
},

View file

@ -105,6 +105,11 @@ for (const p of pluginsValues) if (isPluginEnabled(p.name)) {
settings[d].enabled = true;
dep.isDependency = true;
});
if (p.commands?.length) {
Plugins.CommandsAPI.isDependency = true;
settings.CommandsAPI.enabled = true;
}
}
for (const p of pluginsValues) {

View file

@ -111,7 +111,7 @@ export default definePlugin({
patches: [
{
// Indicator
find: ".Messages.MESSAGE_EDITED,",
find: "#{intl::MESSAGE_EDITED}",
replacement: {
match: /let\{className:\i,message:\i[^}]*\}=(\i)/,
replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&"

View file

@ -19,7 +19,7 @@
import * as DataStore from "@api/DataStore";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { ChannelStore, NavigationRouter, SelectedChannelStore, SelectedGuildStore } from "@webpack/common";
import { ChannelRouter, ChannelStore, NavigationRouter, SelectedChannelStore, SelectedGuildStore } from "@webpack/common";
export interface LogoutEvent {
type: "LOGOUT";
@ -40,16 +40,21 @@ interface PreviousChannel {
let isSwitchingAccount = false;
let previousCache: PreviousChannel | undefined;
function attemptToNavigateToChannel(guildId: string | null, channelId: string) {
if (!ChannelStore.hasChannel(channelId)) return;
NavigationRouter.transitionTo(`/channels/${guildId ?? "@me"}/${channelId}`);
}
export default definePlugin({
name: "KeepCurrentChannel",
description: "Attempt to navigate to the channel you were in before switching accounts or loading Discord.",
authors: [Devs.Nuckyz],
patches: [
{
find: '"Switching accounts"',
replacement: {
match: /goHomeAfterSwitching:\i/,
replace: "goHomeAfterSwitching:!1"
}
}
],
flux: {
LOGOUT(e: LogoutEvent) {
({ isSwitchingAccount } = e);
@ -59,8 +64,13 @@ export default definePlugin({
if (!isSwitchingAccount) return;
isSwitchingAccount = false;
if (previousCache?.channelId)
attemptToNavigateToChannel(previousCache.guildId, previousCache.channelId);
if (previousCache?.channelId) {
if (ChannelStore.hasChannel(previousCache.channelId)) {
ChannelRouter.transitionToChannel(previousCache.channelId);
} else {
NavigationRouter.transitionToGuild("@me");
}
}
},
async CHANNEL_SELECT({ guildId, channelId }: ChannelSelectEvent) {
@ -84,7 +94,7 @@ export default definePlugin({
await DataStore.set("KeepCurrentChannel_previousData", previousCache);
} else if (previousCache.channelId) {
attemptToNavigateToChannel(previousCache.guildId, previousCache.channelId);
ChannelRouter.transitionToChannel(previousCache.channelId);
}
}
});

View file

@ -62,7 +62,7 @@ export default definePlugin({
patches: [
{
find: ".LOADING_DID_YOU_KNOW",
find: "#{intl::LOADING_DID_YOU_KNOW}",
replacement: [
{
match: /"_loadingText".+?(?=(\i)\[.{0,10}\.random)/,

View file

@ -74,7 +74,7 @@ export default definePlugin({
{
find: ".invitesDisabledTooltip",
replacement: {
match: /\.VIEW_AS_ROLES_MENTIONS_WARNING.{0,100}(?=])/,
match: /#{intl::VIEW_AS_ROLES_MENTIONS_WARNING}.{0,100}(?=])/,
replace: "$&,$self.renderTooltip(arguments[0].guild)"
},
predicate: () => settings.store.toolTip

View file

@ -74,7 +74,7 @@ export default definePlugin({
if (msg.deleted === true) return;
if (isMe) {
if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id)) return;
if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id) || msg.state !== "SENT") return;
MessageActions.startEditMessage(channel.id, msg.id, msg.content);
event.preventDefault();

View file

@ -24,12 +24,12 @@ import { Settings } from "@api/Settings";
import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { proxyLazy } from "@utils/lazy";
import { getIntlMessage } from "@utils/discord";
import { Logger } from "@utils/Logger";
import { classes } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { ChannelStore, FluxDispatcher, i18n, Menu, MessageStore, Parser, SelectedChannelStore, Timestamp, UserStore, useStateFromStores } from "@webpack/common";
import { findByPropsLazy } from "@webpack";
import { ChannelStore, FluxDispatcher, Menu, MessageStore, Parser, SelectedChannelStore, Timestamp, UserStore, useStateFromStores } from "@webpack/common";
import { Message } from "discord-types/general";
import overlayStyle from "./deleteStyleOverlay.css?managed";
@ -43,7 +43,6 @@ interface MLMessage extends Message {
}
const styles = findByPropsLazy("edited", "communicationDisabled", "isSystemMessage");
const getMessage = findByCodeLazy('replace(/^\\n+|\\n+$/g,"")');
function addDeleteStyle() {
if (Settings.plugins.MessageLogger.deleteStyle === "text") {
@ -178,7 +177,7 @@ export default definePlugin({
isEdited={true}
isInline={false}
>
<span className={styles.edited}>{" "}({i18n.Messages.MESSAGE_EDITED})</span>
<span className={styles.edited}>{" "}({getIntlMessage("MESSAGE_EDITED")})</span>
</Timestamp>
</div>
))}
@ -312,9 +311,33 @@ export default definePlugin({
);
},
Messages: proxyLazy(() => ({
DELETED_MESSAGE_COUNT: getMessage("{count, plural, =0 {No deleted messages} one {{count} deleted message} other {{count} deleted messages}}")
})),
// DELETED_MESSAGE_COUNT: getMessage("{count, plural, =0 {No deleted messages} one {{count} deleted message} other {{count} deleted messages}}")
// TODO: Find a better way to generate intl messages
DELETED_MESSAGE_COUNT: () => ({
ast: [[
6,
"count",
{
"=0": ["No deleted messages"],
one: [
[
1,
"count"
],
" deleted message"
],
other: [
[
1,
"count"
],
" deleted messages"
]
},
0,
"cardinal"
]]
}),
patches: [
{
@ -445,7 +468,7 @@ export default definePlugin({
{
// Message content renderer
find: "Messages.MESSAGE_EDITED,\")\"",
find: "#{intl::MESSAGE_EDITED}",
replacement: [
{
// Render editHistory in the deepest div for message content
@ -497,7 +520,7 @@ export default definePlugin({
},
{
// Message group rendering
find: "Messages.NEW_MESSAGES_ESTIMATED_WITH_DATE",
find: "#{intl::NEW_MESSAGES_ESTIMATED_WITH_DATE}",
replacement: [
{
match: /(\i).type===\i\.\i\.MESSAGE_GROUP_BLOCKED\|\|/,
@ -505,7 +528,7 @@ export default definePlugin({
},
{
match: /(\i).type===\i\.\i\.MESSAGE_GROUP_BLOCKED\?.*?:/,
replace: '$&$1.type==="MESSAGE_GROUP_DELETED"?$self.Messages.DELETED_MESSAGE_COUNT:',
replace: '$&$1.type==="MESSAGE_GROUP_DELETED"?$self.DELETED_MESSAGE_COUNT:',
},
],
predicate: () => Settings.plugins.MessageLogger.collapseDeleted

View file

@ -82,7 +82,6 @@ export default definePlugin({
default: true
}
},
dependencies: ["CommandsAPI"],
async start() {
for (const tag of await getTags()) createTagCommand(tag);

View file

@ -33,7 +33,6 @@ export default definePlugin({
name: "MoreCommands",
description: "echo, lenny, mock",
authors: [Devs.Arjix, Devs.echo, Devs.Samu],
dependencies: ["CommandsAPI"],
commands: [
{
name: "echo",

View file

@ -24,7 +24,6 @@ export default definePlugin({
name: "MoreKaomoji",
description: "Adds more Kaomoji to discord. ヽ(´▽`)/",
authors: [Devs.JacobTm],
dependencies: ["CommandsAPI"],
commands: [
{ name: "dissatisfaction", description: " " },
{ name: "smug", description: "ಠ_ಠ" },

View file

@ -19,6 +19,7 @@
import { definePluginSettings } from "@api/Settings";
import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord";
import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findLazy } from "@webpack";
@ -187,13 +188,13 @@ export default definePlugin({
}
},
{
find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP_OFFICIAL,",
find: "#{intl::DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP_OFFICIAL}",
replacement: [
// make the tag show the right text
{
match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=.{0,40}(\i\.\i\.Messages)\.APP_TAG/,
replace: (_, origSwitch, variant, tags, displayedText, strings) =>
`${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}], ${strings})}`
match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=(.{0,40}#{intl::APP_TAG}\))/,
replace: (_, origSwitch, variant, tags, displayedText, originalText) =>
`${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}],${originalText})}`
},
// show OP tags correctly
{
@ -217,7 +218,7 @@ export default definePlugin({
},
// in the member list
{
find: ".Messages.GUILD_OWNER,",
find: "#{intl::GUILD_OWNER}",
replacement: {
match: /(?<type>\i)=\(null==.{0,100}\.BOT;return null!=(?<user>\i)&&\i\.bot/,
replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }); return typeof $<type> === 'number'"
@ -232,7 +233,7 @@ export default definePlugin({
}
},
{
find: ".Messages.USER_PROFILE_PRONOUNS",
find: "#{intl::USER_PROFILE_PRONOUNS}",
replacement: {
match: /(?=,hideBotTag:!0)/,
replace: ",moreTags_channelId:arguments[0].moreTags_channelId"
@ -249,8 +250,8 @@ export default definePlugin({
match: /user:\i,nick:\i,/,
replace: "$&moreTags_channelId,"
}, {
match: /,botType:(\i),(?<=user:(\i).+?)/g,
replace: ",botType:$self.getTag({user:$2,channelId:moreTags_channelId,origType:$1,location:'not-chat'}),"
match: /,botType:(\i),botVerified:(\i),(?!discriminatorClass:)(?<=user:(\i).+?)/g,
replace: ",botType:$self.getTag({user:$3,channelId:moreTags_channelId,origType:$1,location:'not-chat'}),botVerified:$2,"
}
]
},
@ -295,22 +296,26 @@ export default definePlugin({
isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]),
getTagText(passedTagName: string, strings: Record<string, string>) {
if (!passedTagName) return strings.APP_TAG;
getTagText(passedTagName: string, originalText: string) {
try {
const [tagName, variant] = passedTagName.split("-");
if (!passedTagName) return getIntlMessage("APP_TAG");
const tag = tags.find(({ name }) => tagName === name);
if (!tag) return strings.APP_TAG;
if (variant === "BOT" && tagName !== "WEBHOOK" && this.settings.store.dontShowForBots) return strings.APP_TAG;
if (!tag) return getIntlMessage("APP_TAG");
if (variant === "BOT" && tagName !== "WEBHOOK" && this.settings.store.dontShowForBots) return getIntlMessage("APP_TAG");
const tagText = settings.store.tagSettings?.[tag.name]?.text || tag.displayName;
switch (variant) {
case "OP":
return `${strings.BOT_TAG_FORUM_ORIGINAL_POSTER}${tagText}`;
return `${getIntlMessage("BOT_TAG_FORUM_ORIGINAL_POSTER")}${tagText}`;
case "BOT":
return `${strings.APP_TAG}${tagText}`;
return `${getIntlMessage("APP_TAG")}${tagText}`;
default:
return tagText;
}
} catch {
return originalText;
}
},
getTag({

View file

@ -28,7 +28,7 @@ const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
const UserUtils = findByPropsLazy("getGlobalName");
const ProfileListClasses = findByPropsLazy("emptyIconFriends", "emptyIconGuilds");
const ExpandableList = findComponentByCodeLazy(".mutualFriendItem]");
const ExpandableList = findComponentByCodeLazy('"PRESS_SECTION"');
const GuildLabelClasses = findByPropsLazy("guildNick", "guildAvatarWithoutIcon");
function getGroupDMName(channel: Channel) {
@ -142,16 +142,15 @@ export default definePlugin({
const mutualGDms = getMutualGroupDms(user.id);
if (mutualGDms.length === 0) return null;
const header = getMutualGDMCountText(user);
return (
<>
{Divider}
<ExpandableList
className={listStyle}
header={header}
isLoadingHeader={false}
children={renderClickableGDMs(mutualGDms, () => { })}
listClassName={listStyle}
header={"Mutual Groups"}
isLoading={false}
items={renderClickableGDMs(mutualGDms, () => { })}
/>
</>
);

View file

@ -18,30 +18,37 @@
import { Settings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { runtimeHashMessageKey } from "@utils/intlHash";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { i18n } from "@webpack/common";
import { Message } from "discord-types/general";
const RelationshipStore = findByPropsLazy("getRelationships", "isBlocked");
interface MessageDeleteProps {
// Internal intl message for BLOCKED_MESSAGE_COUNT
collapsedReason: () => any;
}
export default definePlugin({
name: "NoBlockedMessages",
description: "Hides all blocked messages from chat completely.",
authors: [Devs.rushii, Devs.Samu],
patches: [
{
find: "Messages.BLOCKED_MESSAGES_HIDE",
find: "#{intl::BLOCKED_MESSAGES_HIDE}",
replacement: [
{
match: /let\{[^}]*collapsedReason[^}]*\}/,
replace: "return null;$&"
replace: "if($self.shouldHide(arguments[0]))return null;$&"
}
]
},
...[
'="MessageStore",',
'"displayName","ReadStateStore")'
'"MessageStore"',
'"ReadStateStore"'
].map(find => ({
find,
predicate: () => Settings.plugins.NoBlockedMessages.ignoreBlockedMessages === true,
@ -68,5 +75,14 @@ export default definePlugin({
} catch (e) {
new Logger("NoBlockedMessages").error("Failed to check if user is blocked:", e);
}
},
shouldHide(props: MessageDeleteProps) {
try {
return props.collapsedReason() === i18n.t[runtimeHashMessageKey("BLOCKED_MESSAGE_COUNT")]();
} catch (e) {
console.error(e);
}
return false;
}
});

View file

@ -1,5 +0,0 @@
# NoDefaultHangStatus
Disable the default hang status when joining voice channels
![Visualization](https://github.com/Vendicated/Vencord/assets/24937357/329a9742-236f-48f7-94ff-c3510eca505a)

View file

@ -20,7 +20,7 @@ const settings = definePluginSettings({
export default definePlugin({
name: "NoMosaic",
authors: [Devs.AutumnVN],
description: "Removes Discord new image mosaic",
description: "Removes Discord image mosaic",
tags: ["image", "mosaic", "media"],
settings,
@ -29,8 +29,8 @@ export default definePlugin({
{
find: '=>"IMAGE"===',
replacement: {
match: /=>"IMAGE"===\i\|\|"VIDEO"===\i;/,
replace: "=>false;"
match: /=>"IMAGE"===\i\|\|"VIDEO"===\i(?:\|\|("VISUAL_PLACEHOLDER"===\i))?;/,
replace: (_, visualPlaceholderPred) => visualPlaceholderPred != null ? `=>${visualPlaceholderPred};` : "=>false;"
}
},
{

View file

@ -25,7 +25,7 @@ export default definePlugin({
authors: [Devs.nekohaxx],
patches: [
{
find: "Messages.ONBOARDING_COVER_WELCOME_SUBTITLE",
find: "#{intl::ONBOARDING_COVER_WELCOME_SUBTITLE}",
replacement: {
match: "3e3",
replace: "0"

View file

@ -74,7 +74,7 @@ export default definePlugin({
// This prevents the Message Requests tab from always hiding due to the previous patch (and is compatible with spam requests)
// In short, only the red badge is hidden. Button visibility behavior isn't changed.
{
find: ".getSpamChannelsCount(),",
find: ".getSpamChannelsCount()",
predicate: () => settings.store.hideMessageRequestsCount,
replacement: {
match: /(?<=getSpamChannelsCount\(\),\i=)\i\.getMessageRequestsCount\(\)/,
@ -87,7 +87,7 @@ export default definePlugin({
replacement: {
// The two groups inside the first group grab the minified names of the variables,
// they are then referenced later to find unviewedTrialCount + unviewedDiscountCount.
match: /(?<=\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.{0,200}\i=)\1\+\2/,
match: /(?<=\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.{0,300}\i=)\1\+\2/,
replace: "0"
}
}

View file

@ -28,21 +28,21 @@ export default definePlugin({
{
find: '.id,"Search Results"',
replacement: {
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}UNBLOCK_TO_JUMP_TITLE)/,
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
replace: "if(false)$1"
}
},
{
find: "renderJumpButton()",
replacement: {
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}UNBLOCK_TO_JUMP_TITLE)/,
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
replace: "if(false)$1"
}
},
{
find: "flash:!0,returnMessageId",
replacement: {
match: /.\?(.{1,10}\.show\({.{1,50}UNBLOCK_TO_JUMP_TITLE)/,
match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
replace: "false?$1"
}
}

Some files were not shown because too many files have changed in this diff Show more