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

Merge branch 'dev' into fix/betterFolders

This commit is contained in:
sadan 2025-01-07 23:44:03 -05:00
commit ad4e2e6594
No known key found for this signature in database
87 changed files with 2270 additions and 1721 deletions

View file

@ -4,10 +4,9 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
// @ts-check
import stylistic from "@stylistic/eslint-plugin"; import stylistic from "@stylistic/eslint-plugin";
import pathAlias from "eslint-plugin-path-alias"; import pathAlias from "eslint-plugin-path-alias";
import react from "eslint-plugin-react";
import header from "eslint-plugin-simple-header"; import header from "eslint-plugin-simple-header";
import simpleImportSort from "eslint-plugin-simple-import-sort"; import simpleImportSort from "eslint-plugin-simple-import-sort";
import unusedImports from "eslint-plugin-unused-imports"; import unusedImports from "eslint-plugin-unused-imports";
@ -15,6 +14,22 @@ import tseslint from "typescript-eslint";
export default tseslint.config( export default tseslint.config(
{ ignores: ["dist", "browser", "packages/vencord-types"] }, { ignores: ["dist", "browser", "packages/vencord-types"] },
{
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
settings: {
react: {
version: "18"
}
},
...react.configs.flat.recommended,
rules: {
...react.configs.flat.recommended.rules,
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"react/display-name": "off",
"react/no-unescaped-entities": "off",
}
},
{ {
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"], files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
plugins: { plugins: {
@ -23,7 +38,7 @@ export default tseslint.config(
"@typescript-eslint": tseslint.plugin, "@typescript-eslint": tseslint.plugin,
"simple-import-sort": simpleImportSort, "simple-import-sort": simpleImportSort,
"unused-imports": unusedImports, "unused-imports": unusedImports,
"path-alias": pathAlias, "path-alias": pathAlias
}, },
settings: { settings: {
"import/resolver": { "import/resolver": {

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.10.8", "version": "1.10.9",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": { "bugs": {
@ -41,48 +41,49 @@
"@vap/shiki": "0.10.5", "@vap/shiki": "0.10.5",
"fflate": "^0.8.2", "fflate": "^0.8.2",
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3", "gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
"monaco-editor": "^0.50.0", "monaco-editor": "^0.52.2",
"nanoid": "^5.0.7", "nanoid": "^5.0.9",
"virtual-merge": "^1.0.1" "virtual-merge": "^1.0.1"
}, },
"devDependencies": { "devDependencies": {
"@stylistic/eslint-plugin": "^2.6.1", "@stylistic/eslint-plugin": "^2.12.1",
"@types/chrome": "^0.0.269", "@types/chrome": "^0.0.287",
"@types/diff": "^5.2.1", "@types/diff": "^6.0.0",
"@types/lodash": "^4.17.7", "@types/lodash": "^4.17.14",
"@types/node": "^22.0.3", "@types/node": "^22.10.5",
"@types/react": "^18.3.3", "@types/react": "^19.0.2",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^19.0.2",
"@types/yazl": "^2.4.5", "@types/yazl": "^2.4.5",
"diff": "^5.2.0", "diff": "^7.0.0",
"discord-types": "^1.3.26", "discord-types": "^1.3.26",
"esbuild": "^0.15.18", "esbuild": "^0.15.18",
"eslint": "^9.8.0", "eslint": "^9.17.0",
"eslint-import-resolver-alias": "^1.1.2", "eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-path-alias": "2.1.0", "eslint-plugin-path-alias": "2.1.0",
"eslint-plugin-simple-header": "^1.1.1", "eslint-plugin-react": "^7.37.3",
"eslint-plugin-simple-header": "^1.2.1",
"eslint-plugin-simple-import-sort": "^12.1.1", "eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unused-imports": "^4.0.1", "eslint-plugin-unused-imports": "^4.1.4",
"highlight.js": "10.7.3", "highlight.js": "11.7.0",
"html-minifier-terser": "^7.2.0", "html-minifier-terser": "^7.2.0",
"moment": "^2.30.1", "moment": "^2.22.2",
"puppeteer-core": "^22.15.0", "puppeteer-core": "^23.11.1",
"standalone-electron-types": "^1.0.0", "standalone-electron-types": "^1.0.0",
"stylelint": "^16.8.1", "stylelint": "^16.12.0",
"stylelint-config-standard": "^36.0.1", "stylelint-config-standard": "^36.0.1",
"ts-patch": "^3.2.1", "ts-patch": "^3.3.0",
"ts-pattern": "^5.3.1", "ts-pattern": "^5.6.0",
"tsx": "^4.16.5", "tsx": "^4.19.2",
"type-fest": "^4.23.0", "type-fest": "^4.31.0",
"typescript": "^5.5.4", "typescript": "^5.7.2",
"typescript-eslint": "^8.0.0", "typescript-eslint": "^8.19.0",
"typescript-transform-paths": "^3.4.7", "typescript-transform-paths": "^3.5.3",
"zip-local": "^0.3.5" "zip-local": "^0.3.5"
}, },
"packageManager": "pnpm@9.1.0", "packageManager": "pnpm@9.1.0",
"pnpm": { "pnpm": {
"patchedDependencies": { "patchedDependencies": {
"eslint@9.8.0": "patches/eslint@9.8.0.patch", "eslint@9.17.0": "patches/eslint@9.17.0.patch",
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch" "eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
}, },
"peerDependencyRules": { "peerDependencyRules": {

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@ import { Logger } from "@utils/Logger";
import { waitFor } from "@webpack"; import { waitFor } from "@webpack";
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common"; import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
import { Channel } from "discord-types/general"; import { Channel } from "discord-types/general";
import { HTMLProps, MouseEventHandler, ReactNode } from "react"; import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>; let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m); waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);

View file

@ -24,13 +24,13 @@ import type { ReactElement } from "react";
* @param children The rendered context menu elements * @param children The rendered context menu elements
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
*/ */
export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => void; export type NavContextMenuPatchCallback = (children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
/** /**
* @param navId The navId of the context menu being patched * @param navId The navId of the context menu being patched
* @param children The rendered context menu elements * @param children The rendered context menu elements
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example * @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
*/ */
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => void; export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
const ContextMenuLogger = new Logger("ContextMenu"); const ContextMenuLogger = new Logger("ContextMenu");
@ -70,7 +70,7 @@ export function addGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback)
* @returns Whether the patch was successfully removed from the context menu(s) * @returns Whether the patch was successfully removed from the context menu(s)
*/ */
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> { export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
const navIds = Array.isArray(navId) ? navId : [navId as string]; const navIds: string[] = Array.isArray(navId) ? navId : [navId];
const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false); const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false);
@ -92,7 +92,7 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
* @param children The context menu children * @param children The context menu children
* @param matchSubstring Whether to check if the id is a substring of the child id * @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 | undefined>, matchSubstring = false): Array<ReactElement | null | undefined> | null { export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement<any> | null | undefined>, matchSubstring = false): Array<ReactElement<any> | null | undefined> | null {
for (const child of children) { for (const child of children) {
if (child == null) continue; if (child == null) continue;
@ -124,7 +124,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
interface ContextMenuProps { interface ContextMenuProps {
contextMenuApiArguments?: Array<any>; contextMenuApiArguments?: Array<any>;
navId: string; navId: string;
children: Array<ReactElement | null>; children: Array<ReactElement<any> | null>;
"aria-label": string; "aria-label": string;
onSelect: (() => void) | undefined; onSelect: (() => void) | undefined;
onClose: (callback: (...args: Array<any>) => any) => void; onClose: (callback: (...args: Array<any>) => any) => void;
@ -162,7 +162,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
return props; return props;
} }
function cloneMenuChildren(obj: ReactElement | Array<ReactElement | null> | null) { function cloneMenuChildren(obj: ReactElement<any> | Array<ReactElement<any> | null> | null) {
if (Array.isArray(obj)) { if (Array.isArray(obj)) {
return obj.map(cloneMenuChildren); return obj.map(cloneMenuChildren);
} }

View file

@ -17,6 +17,7 @@
*/ */
import { Channel, User } from "discord-types/general/index.js"; import { Channel, User } from "discord-types/general/index.js";
import { JSX } from "react";
interface DecoratorProps { interface DecoratorProps {
activities: any[]; activities: any[];

View file

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { JSX } from "react";
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>; export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>;
export type Accessory = { export type Accessory = {
callback: AccessoryCallback; callback: AccessoryCallback;

View file

@ -17,6 +17,7 @@
*/ */
import { Channel, Message } from "discord-types/general/index.js"; import { Channel, Message } from "discord-types/general/index.js";
import { JSX } from "react";
interface DecorationProps { interface DecorationProps {
author: { author: {

View file

@ -17,6 +17,7 @@
*/ */
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { JSX } from "react";
const logger = new Logger("ServerListAPI"); const logger = new Logger("ServerListAPI");

View file

@ -23,7 +23,7 @@ import { Logger } from "@utils/Logger";
import { mergeDefaults } from "@utils/mergeDefaults"; import { mergeDefaults } from "@utils/mergeDefaults";
import { putCloudSettings } from "@utils/settingsSync"; import { putCloudSettings } from "@utils/settingsSync";
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types"; import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
import { React } from "@webpack/common"; import { React, useEffect } from "@webpack/common";
import plugins from "~plugins"; import plugins from "~plugins";
@ -192,7 +192,7 @@ export const Settings = SettingsStore.store;
export function useSettings(paths?: UseSettings<Settings>[]) { export function useSettings(paths?: UseSettings<Settings>[]) {
const [, forceUpdate] = React.useReducer(() => ({}), {}); const [, forceUpdate] = React.useReducer(() => ({}), {});
React.useEffect(() => { useEffect(() => {
if (paths) { if (paths) {
paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate)); paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate)); return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
@ -200,7 +200,7 @@ export function useSettings(paths?: UseSettings<Settings>[]) {
SettingsStore.addGlobalChangeListener(forceUpdate); SettingsStore.addGlobalChangeListener(forceUpdate);
return () => SettingsStore.removeGlobalChangeListener(forceUpdate); return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
} }
}, []); }, [paths]);
return SettingsStore.store; return SettingsStore.store;
} }

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
export function Badge({ text, color }): JSX.Element { export function Badge({ text, color }) {
return ( return (
<div className="vc-plugins-badge" style={{ <div className="vc-plugins-badge" style={{
backgroundColor: color, backgroundColor: color,

View file

@ -27,7 +27,7 @@ interface Props<T = any> {
/** Render nothing if an error occurs */ /** Render nothing if an error occurs */
noop?: boolean; noop?: boolean;
/** Fallback component to render if an error occurs */ /** Fallback component to render if an error occurs */
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; }>>; fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; wrappedProps: T; }>>;
/** called when an error occurs. The props property is only available if using .wrap */ /** called when an error occurs. The props property is only available if using .wrap */
onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void; onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;
/** Custom error message */ /** Custom error message */
@ -80,10 +80,14 @@ const ErrorBoundary = LazyComponent(() => {
if (this.props.noop) return null; if (this.props.noop) return null;
if (this.props.fallback) if (this.props.fallback)
return <this.props.fallback return (
children={this.props.children} <this.props.fallback
wrappedProps={this.props.wrappedProps}
{...this.state} {...this.state}
/>; >
{this.props.children}
</this.props.fallback>
);
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console."; const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { CSSProperties } from "react"; import { CSSProperties, JSX } from "react";
interface Props { interface Props {
columns: number; columns: number;

View file

@ -27,7 +27,7 @@ export function Heart() {
> >
<path <path
fill="#db61a2" fill="#db61a2"
fill-rule="evenodd" fillRule="evenodd"
d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z" d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z"
/> />
</svg> </svg>

View file

@ -20,7 +20,7 @@ import "./iconStyles.css";
import { getIntlMessage } from "@utils/discord"; import { getIntlMessage } from "@utils/discord";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import type { PropsWithChildren } from "react"; import type { JSX, PropsWithChildren } from "react";
interface BaseIconProps extends IconProps { interface BaseIconProps extends IconProps {
viewBox: string; viewBox: string;
@ -55,7 +55,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
className={classes(className, "vc-link-icon")} className={classes(className, "vc-link-icon")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<g fill="none" fill-rule="evenodd"> <g fill="none" fillRule="evenodd">
<path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" /> <path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" />
<rect width={width} height={height} /> <rect width={width} height={height} />
</g> </g>
@ -122,8 +122,8 @@ export function InfoIcon(props: IconProps) {
> >
<path <path
fill="currentColor" fill="currentColor"
fill-rule="evenodd" fillRule="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" 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" clipRule="evenodd"
/> />
</Icon> </Icon>
); );
@ -212,9 +212,9 @@ export function CogWheel(props: IconProps) {
> >
<path <path
fill="currentColor" fill="currentColor"
fill-rule="evenodd" fillRule="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" 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" clipRule="evenodd"
/> />
</Icon> </Icon>
); );
@ -262,7 +262,7 @@ export function PlusIcon(props: IconProps) {
viewBox="0 0 18 18" viewBox="0 0 18 18"
> >
<polygon <polygon
fill-rule="nonzero" fillRule="nonzero"
fill="currentColor" fill="currentColor"
points="15 10 10 10 10 15 8 15 8 10 3 10 3 8 8 8 8 3 10 3 10 8 15 8" points="15 10 10 10 10 15 8 15 8 10 3 10 3 8 8 8 8 3 10 3 10 8 15 8"
/> />

View file

@ -44,7 +44,7 @@ function ContributorModal({ user }: { user: User; }) {
useEffect(() => { useEffect(() => {
if (!profile && !user.bot && user.id) if (!profile && !user.bot && user.id)
fetchUserProfile(user.id); fetchUserProfile(user.id);
}, [user.id]); }, [user.id, user.bot, profile]);
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name; const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name; const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;

View file

@ -109,7 +109,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
setAuthors(a => [...a, author]); setAuthors(a => [...a, author]);
} }
})(); })();
}, []); }, [plugin.authors]);
async function saveAndClose() { async function saveAndClose() {
if (!plugin.options) { if (!plugin.options) {

View file

@ -35,6 +35,7 @@ import { useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types"; import { Plugin } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common"; import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
import { JSX } from "react";
import Plugins, { ExcludedPlugins } from "~plugins"; import Plugins, { ExcludedPlugins } from "~plugins";
@ -387,7 +388,7 @@ function makeDependencyList(deps: string[]) {
return ( return (
<React.Fragment> <React.Fragment>
<Forms.FormText>This plugin is required by:</Forms.FormText> <Forms.FormText>This plugin is required by:</Forms.FormText>
{deps.map((dep: string) => <Forms.FormText className={cl("dep-text")}>{dep}</Forms.FormText>)} {deps.map((dep: string) => <Forms.FormText key={dep} className={cl("dep-text")}>{dep}</Forms.FormText>)}
</React.Fragment> </React.Fragment>
); );
} }

View file

@ -111,9 +111,9 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
} }
function renderDiff() { function renderDiff() {
return diff?.map(p => { return diff?.map((p, idx) => {
const color = p.added ? "lime" : p.removed ? "red" : "grey"; const color = p.added ? "lime" : p.removed ? "red" : "grey";
return <div style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>; return <div key={idx} style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
}); });
} }

View file

@ -61,7 +61,7 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
title: "Oops!", title: "Oops!",
body: ( body: (
<ErrorCard> <ErrorCard>
{err.split("\n").map(line => <div>{Parser.parse(line)}</div>)} {err.split("\n").map((line, idx) => <div key={idx}>{Parser.parse(line)}</div>)}
</ErrorCard> </ErrorCard>
) )
}); });
@ -87,7 +87,7 @@ function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof
return ( return (
<Card style={{ padding: "0 0.5em" }}> <Card style={{ padding: "0 0.5em" }}>
{updates.map(({ hash, author, message }) => ( {updates.map(({ hash, author, message }) => (
<div style={{ <div key={hash} style={{
marginTop: "0.5em", marginTop: "0.5em",
marginBottom: "0.5em" marginBottom: "0.5em"
}}> }}>

View file

@ -17,7 +17,7 @@
*/ */
import { onceDefined } from "@shared/onceDefined"; import { onceDefined } from "@shared/onceDefined";
import electron, { app, BrowserWindowConstructorOptions, Menu, nativeTheme } from "electron"; import electron, { app, BrowserWindowConstructorOptions, Menu } from "electron";
import { dirname, join } from "path"; import { dirname, join } from "path";
import { initIpc } from "./ipcMain"; import { initIpc } from "./ipcMain";
@ -100,19 +100,6 @@ if (!IS_VANILLA) {
super(options); super(options);
initIpc(this); 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); } else super(options);
} }
} }

View file

@ -71,13 +71,16 @@ export async function installExt(id: string) {
// React Devtools v4.25 // React Devtools v4.25
// v4.27 is broken in Electron, see https://github.com/facebook/react/issues/25843 // v4.27 is broken in Electron, see https://github.com/facebook/react/issues/25843
// Unfortunately, Google does not serve old versions, so this is the only way // Unfortunately, Google does not serve old versions, so this is the only way
// This zip file is pinned to long commit hash so it cannot be changed remotely
? "https://raw.githubusercontent.com/Vendicated/random-files/f6f550e4c58ac5f2012095a130406c2ab25b984d/fmkadmapgofadopljbjfkapdkoienihi.zip" ? "https://raw.githubusercontent.com/Vendicated/random-files/f6f550e4c58ac5f2012095a130406c2ab25b984d/fmkadmapgofadopljbjfkapdkoienihi.zip"
: `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=32`; : `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=${process.versions.chrome}`;
const buf = await get(url, { const buf = await get(url, {
headers: { headers: {
"User-Agent": "Vencord (https://github.com/Vendicated/Vencord)" "User-Agent": `Electron ${process.versions.electron} ~ Vencord (https://github.com/Vendicated/Vencord)`
} }
}); });
await extract(crxToZip(buf), extDir).catch(console.error); await extract(crxToZip(buf), extDir).catch(console.error);
} }

View file

@ -59,6 +59,14 @@ export default definePlugin({
replace: "$&return;" replace: "$&return;"
} }
] ]
},
{
find: ".BetterDiscord||null!=",
replacement: {
// Make hasClientMods return false
match: /(?=let \i=window;)/,
replace: "return false;"
}
} }
], ],

View file

@ -34,6 +34,7 @@ import { makeCodeblock } from "@utils/text";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { checkForUpdates, isOutdated, update } from "@utils/updater"; import { checkForUpdates, isOutdated, update } from "@utils/updater";
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common"; import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
import { JSX } from "react";
import gitHash from "~git-hash"; import gitHash from "~git-hash";
import plugins, { PluginMeta } from "~plugins"; import plugins, { PluginMeta } from "~plugins";

View file

@ -85,7 +85,7 @@ export default definePlugin({
replace: "$&onRequestClose:$self.onPopoutClose," replace: "$&onRequestClose:$self.onPopoutClose,"
}, },
{ {
match: /(?<=.avatarWrapper,)/, match: /(?<=\.avatarWrapper,)/,
replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu," replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
} }
] ]

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { canonicalizeMatch } from "@utils/patches";
import { execFile } from "child_process"; import { execFile } from "child_process";
import { promisify } from "util"; import { promisify } from "util";
@ -26,7 +27,7 @@ interface RemoteData {
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null; 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_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"/; const APPLE_MUSIC_TOKEN_REGEX = canonicalizeMatch(/Promise.allSettled\(\i\)\}const \i="([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)"/);
let cachedToken: string | undefined = undefined; let cachedToken: string | undefined = undefined;

View file

@ -185,7 +185,7 @@ export default definePlugin({
{ {
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar // Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always, predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.wrapper,children:\[)/, match: /(?<=\.isExpanded\),children:\[)/,
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&" replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&"
}, },
{ {

View file

@ -99,7 +99,7 @@ export const UnknownIcon = (props: React.PropsWithChildren<SVGProps<SVGSVGElemen
fill="currentColor" fill="currentColor"
viewBox="0 0 16 16" viewBox="0 0 16 16"
> >
<path fill-rule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z" /> <path fillRule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z" />
</svg> </svg>
); );

View file

@ -173,7 +173,7 @@ export default definePlugin({
} }
return this; return this;
}, },
map(render: (item: SettingsEntry) => ReactElement) { map(render: (item: SettingsEntry) => ReactElement<any>) {
return items return items
.filter(a => a.items.length > 0) .filter(a => a.items.length > 0)
.map(({ label, items }) => { .map(({ label, items }) => {
@ -181,11 +181,14 @@ export default definePlugin({
if (label) { if (label) {
return ( return (
<Menu.MenuItem <Menu.MenuItem
key={label}
id={label.replace(/\W/, "_")} id={label.replace(/\W/, "_")}
label={label} label={label}
children={children}
action={children[0].props.action} action={children[0].props.action}
/>); >
{children}
</Menu.MenuItem>
);
} else { } else {
return children; return children;
} }

View file

@ -75,10 +75,11 @@ export default definePlugin({
patches: [{ patches: [{
find: "renderConnectionStatus(){", find: "renderConnectionStatus(){",
replacement: { replacement: {
match: /(?<=renderConnectionStatus\(\)\{.+\.channel,children:)\i/, match: /(?<=renderConnectionStatus\(\){.+\.channel,children:).+?}\):\i(?=}\))/,
replace: "[$&, $self.renderTimer(this.props.channel.id)]" replace: "[$&, $self.renderTimer(this.props.channel.id)]"
} }
}], }],
renderTimer(channelId: string) { renderTimer(channelId: string) {
return <ErrorBoundary noop> return <ErrorBoundary noop>
<this.Timer channelId={channelId} /> <this.Timer channelId={channelId} />

View file

@ -67,9 +67,16 @@ export default definePlugin({
patches: [ patches: [
{ {
find: 'react-spring: The "interpolate" function', find: "https://github.com/highlightjs/highlight.js/issues/2277",
replacement: { replacement: {
match: /,console.warn\('react-spring: The "interpolate" function is deprecated in v10 \(use "to" instead\)'\)/, match: /(?<=&&\()console.log\(`Deprecated.+?`\),/,
replace: ""
}
},
{
find: 'The "interpolate" function is deprecated in v10 (use "to" instead)',
replacement: {
match: /,console.warn\(\i\+'The "interpolate" function is deprecated in v10 \(use "to" instead\)'\)/,
replace: "" replace: ""
} }
}, },
@ -126,7 +133,7 @@ export default definePlugin({
{ {
find: "Slow dispatch on", find: "Slow dispatch on",
replacement: { replacement: {
match: /\i\.totalTime>100&&\i\.verbose\("Slow dispatch on ".+?\)\);/, match: /\i\.totalTime>\i&&\i\.verbose\("Slow dispatch on ".+?\)\);/,
replace: "" replace: ""
} }
}, },

View file

@ -20,6 +20,7 @@ import { Devs } from "@utils/constants";
import { getCurrentChannel, getCurrentGuild } from "@utils/discord"; import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
import { runtimeHashMessageKey } from "@utils/intlHash"; import { runtimeHashMessageKey } from "@utils/intlHash";
import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy"; import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
import { ModalAPI } from "@utils/modal";
import { relaunch } from "@utils/native"; import { relaunch } from "@utils/native";
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches"; import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
import definePlugin, { PluginNative, StartAt } from "@utils/types"; import definePlugin, { PluginNative, StartAt } from "@utils/types";
@ -132,7 +133,10 @@ function makeShortcuts() {
}); });
} }
Common.ReactDOM.render(Common.React.createElement(component, props), doc.body.appendChild(document.createElement("div"))); const root = Common.ReactDOM.createRoot(doc.body.appendChild(document.createElement("div")));
root.render(Common.React.createElement(component, props));
doc.addEventListener("close", () => root.unmount(), { once: true });
}, },
preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true, preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true,
@ -144,6 +148,8 @@ function makeShortcuts() {
me: { getter: () => Common.UserStore.getCurrentUser(), preload: false }, me: { getter: () => Common.UserStore.getCurrentUser(), preload: false },
meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false }, meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false },
messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false }, messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false },
openModal: { getter: () => ModalAPI.openModal },
openModalLazy: { getter: () => ModalAPI.openModalLazy },
Stores: { Stores: {
getter: () => Object.fromEntries( getter: () => Object.fromEntries(

View file

@ -5,6 +5,7 @@
*/ */
import { React } from "@webpack/common"; import { React } from "@webpack/common";
import { JSX } from "react";
import { cl } from "../"; import { cl } from "../";

View file

@ -7,6 +7,7 @@
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { React } from "@webpack/common"; import { React } from "@webpack/common";
import { JSX } from "react";
import { cl } from "../"; import { cl } from "../";
import Grid, { GridProps } from "./Grid"; import Grid, { GridProps } from "./Grid";

View file

@ -16,11 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
migratePluginSettings("DisableCallIdle", "DisableDMCallIdle");
export default definePlugin({ export default definePlugin({
name: "DisableCallIdle", name: "DisableCallIdle",
description: "Disables automatically getting kicked from a DM voice call after 3 minutes and being moved to an AFK voice channel.", description: "Disables automatically getting kicked from a DM voice call after 3 minutes and being moved to an AFK voice channel.",

View file

@ -211,7 +211,7 @@ function CloneModal({ data }: { data: Sticker | Emoji; }) {
alignItems: "center" alignItems: "center"
}}> }}>
{guilds.map(g => ( {guilds.map(g => (
<Tooltip text={g.name}> <Tooltip key={g.id} text={g.name}>
{({ onMouseLeave, onMouseEnter }) => ( {({ onMouseLeave, onMouseEnter }) => (
<div <div
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}

View file

@ -207,8 +207,8 @@ function makeBypassPatches(): Omit<Patch, "plugin"> {
return { return {
find: "canUseCustomStickersEverywhere:", find: "canUseCustomStickersEverywhere:",
replacement: mapping.map(({ func, predicate }) => ({ replacement: mapping.map(({ func, predicate }) => ({
match: new RegExp(String.raw`(?<=${func}:function\(\i(?:,\i)?\){)`), match: new RegExp(String.raw`(?<=${func}:)\i`),
replace: "return true;", replace: "() => true",
predicate predicate
})) }))
}; };
@ -297,8 +297,8 @@ export default definePlugin({
replacement: [ replacement: [
{ {
// Overwrite incoming connection settings proto with our local settings // Overwrite incoming connection settings proto with our local settings
match: /CONNECTION_OPEN:function\((\i)\){/, match: /function (\i)\((\i)\){(?=.*CONNECTION_OPEN:\1)/,
replace: (m, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);` replace: (m, funcName, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);`
}, },
{ {
// Overwrite non local proto changes with our local settings // Overwrite non local proto changes with our local settings
@ -514,7 +514,7 @@ export default definePlugin({
return array.filter(item => item != null); return array.filter(item => item != null);
}, },
ensureChildrenIsArray(child: ReactElement) { ensureChildrenIsArray(child: ReactElement<any>) {
if (!Array.isArray(child.props.children)) child.props.children = [child.props.children]; if (!Array.isArray(child.props.children)) child.props.children = [child.props.children];
}, },
@ -524,7 +524,7 @@ export default definePlugin({
let nextIndex = content.length; let nextIndex = content.length;
const transformLinkChild = (child: ReactElement) => { const transformLinkChild = (child: ReactElement<any>) => {
if (settings.store.transformEmojis) { if (settings.store.transformEmojis) {
const fakeNitroMatch = child.props.href.match(fakeNitroEmojiRegex); const fakeNitroMatch = child.props.href.match(fakeNitroEmojiRegex);
if (fakeNitroMatch) { if (fakeNitroMatch) {
@ -558,7 +558,7 @@ export default definePlugin({
return child; return child;
}; };
const transformChild = (child: ReactElement) => { const transformChild = (child: ReactElement<any>) => {
if (child?.props?.trusted != null) return transformLinkChild(child); if (child?.props?.trusted != null) return transformLinkChild(child);
if (child?.props?.children != null) { if (child?.props?.children != null) {
if (!Array.isArray(child.props.children)) { if (!Array.isArray(child.props.children)) {
@ -574,7 +574,7 @@ export default definePlugin({
return child; return child;
}; };
const modifyChild = (child: ReactElement) => { const modifyChild = (child: ReactElement<any>) => {
const newChild = transformChild(child); const newChild = transformChild(child);
if (newChild?.type === "ul" || newChild?.type === "ol") { if (newChild?.type === "ul" || newChild?.type === "ol") {
@ -601,7 +601,7 @@ export default definePlugin({
return newChild; return newChild;
}; };
const modifyChildren = (children: Array<ReactElement>) => { const modifyChildren = (children: Array<ReactElement<any>>) => {
for (const [index, child] of children.entries()) children[index] = modifyChild(child); for (const [index, child] of children.entries()) children[index] = modifyChild(child);
children = this.clearEmptyArrayItems(children); children = this.clearEmptyArrayItems(children);

View file

@ -29,6 +29,7 @@ import definePlugin, { OptionType } from "@utils/types";
import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack"; import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
import { Button, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common"; import { Button, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
import { ReactElement } from "react";
import virtualMerge from "virtual-merge"; import virtualMerge from "virtual-merge";
interface UserProfile extends User { interface UserProfile extends User {
@ -87,7 +88,7 @@ const settings = definePluginSettings({
interface ColorPickerProps { interface ColorPickerProps {
color: number | null; color: number | null;
label: React.ReactElement; label: ReactElement<any>;
showEyeDropper?: boolean; showEyeDropper?: boolean;
suggestedColors?: string[]; suggestedColors?: string[];
onChange(value: number | null): void; onChange(value: number | null): void;

View file

@ -57,7 +57,7 @@ export default definePlugin({
{ {
// https://regex101.com/r/x2mobQ/1 // https://regex101.com/r/x2mobQ/1
// searchEmojis(...,maxCount: stuff) ... endEmojis = emojis.slice(0, maxCount - gifResults.length) // searchEmojis(...,maxCount: stuff) ... endEmojis = emojis.slice(0, maxCount - gifResults.length)
match: /,maxCount:(\i)(.{1,500}\i)=(\i)\.slice\(0,(\i-\i\.length)\)/, match: /,maxCount:(\i)(.{1,500}\i)=(\i)\.slice\(0,(Math\.max\(\i,\i(?:-\i\.length){2}\))\)/,
// ,maxCount:Infinity ... endEmojis = (emojis.sliceTo = n, emojis) // ,maxCount:Infinity ... endEmojis = (emojis.sliceTo = n, emojis)
replace: ",maxCount:Infinity$2=($3.sliceTo = $4, $3)" replace: ",maxCount:Infinity$2=($3.sliceTo = $4, $3)"
} }

View file

@ -0,0 +1,9 @@
# Full User In Chatbox
Adds the full user mention to the textbox
Adds the avatar if you have mentioned avatars enabled
Provides the full context menu to make it easy to access the users profile, and other common actions
https://github.com/user-attachments/assets/cd9edb33-99c8-4c8d-b669-8cddd05f4b45

View file

@ -0,0 +1,47 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { findComponentByCodeLazy } from "@webpack";
import { ReactNode } from "react";
const UserMentionComponent = findComponentByCodeLazy(".USER_MENTION)");
interface UserMentionComponentProps {
id: string;
channelId: string;
guildId: string;
OriginalComponent: ReactNode;
}
export default definePlugin({
name: "FullUserInChatbox",
description: "Makes the user mention in the chatbox have more functionalities, like left/right clicking",
authors: [Devs.sadan],
patches: [
{
find: ':"text":',
replacement: {
match: /(hidePersonalInformation\).+?)(if\(null!=\i\){.+?return \i)(?=})/,
replace: "$1return $self.UserMentionComponent({...arguments[0],OriginalComponent:(()=>{$2})()});"
}
}
],
UserMentionComponent: ErrorBoundary.wrap((props: UserMentionComponentProps) => (
<UserMentionComponent
// This seems to be constant
className="mention"
userId={props.id}
channelId={props.channelId}
/>
), {
fallback: ({ wrappedProps }) => wrappedProps.OriginalComponent
})
});

View file

@ -24,6 +24,7 @@ import { debounce } from "@shared/debounce";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Menu, ReactDOM } from "@webpack/common"; import { Menu, ReactDOM } from "@webpack/common";
import { JSX } from "react";
import type { Root } from "react-dom/client"; import type { Root } from "react-dom/client";
import { Magnifier, MagnifierProps } from "./components/Magnifier"; import { Magnifier, MagnifierProps } from "./components/Magnifier";

View file

@ -41,6 +41,7 @@ import {
UserStore UserStore
} from "@webpack/common"; } from "@webpack/common";
import { Channel, Message } from "discord-types/general"; import { Channel, Message } from "discord-types/general";
import { JSX } from "react";
const messageCache = new Map<string, { const messageCache = new Map<string, {
message?: Message; message?: Message;
@ -347,10 +348,10 @@ function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
? parse(message.content) ? parse(message.content)
: [noContent(message.attachments.length, message.embeds.length)] : [noContent(message.attachments.length, message.embeds.length)]
} }
{images.map(a => { {images.map((a, idx) => {
const { width, height } = computeWidthAndHeight(a.width, a.height); const { width, height } = computeWidthAndHeight(a.width, a.height);
return ( return (
<div> <div key={idx}>
<img src={a.url} width={width} height={height} /> <img src={a.url} width={width} height={height} />
</div> </div>
); );

View file

@ -69,6 +69,7 @@ export function HistoryModal({ modalProps, message }: { modalProps: ModalProps;
{timestamps.map((timestamp, index) => ( {timestamps.map((timestamp, index) => (
<TabBar.Item <TabBar.Item
key={index}
className="vc-settings-tab-bar-item" className="vc-settings-tab-bar-item"
id={index} id={index}
> >

View file

@ -169,8 +169,8 @@ export default definePlugin({
return Settings.plugins.MessageLogger.inlineEdits && ( return Settings.plugins.MessageLogger.inlineEdits && (
<> <>
{message.editHistory?.map(edit => ( {message.editHistory?.map((edit, idx) => (
<div className="messagelogger-edited"> <div key={idx} className="messagelogger-edited">
{parseEditContent(edit.content, message)} {parseEditContent(edit.content, message)}
<Timestamp <Timestamp
timestamp={edit.timestamp} timestamp={edit.timestamp}
@ -304,7 +304,7 @@ export default definePlugin({
{...props} {...props}
className={classes("messagelogger-edit-marker", className)} className={classes("messagelogger-edit-marker", className)}
onClick={() => openHistoryModal(message)} onClick={() => openHistoryModal(message)}
aria-role="button" role="button"
> >
{children} {children}
</span> </span>
@ -346,35 +346,35 @@ export default definePlugin({
replacement: [ replacement: [
{ {
// Add deleted=true to all target messages in the MESSAGE_DELETE event // Add deleted=true to all target messages in the MESSAGE_DELETE event
match: /MESSAGE_DELETE:function\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?},/, match: /function (?=.+?MESSAGE_DELETE:(\i))\1\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?}(?=function)/,
replace: replace:
"MESSAGE_DELETE:function($1){" + "function $1($2){" +
" var cache = $2getOrCreate($1.channelId);" + " var cache = $3getOrCreate($2.channelId);" +
" cache = $self.handleDelete(cache, $1, false);" + " cache = $self.handleDelete(cache, $2, false);" +
" $2commit(cache);" + " $3commit(cache);" +
"}," "}"
}, },
{ {
// Add deleted=true to all target messages in the MESSAGE_DELETE_BULK event // Add deleted=true to all target messages in the MESSAGE_DELETE_BULK event
match: /MESSAGE_DELETE_BULK:function\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?},/, match: /function (?=.+?MESSAGE_DELETE_BULK:(\i))\1\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?}(?=function)/,
replace: replace:
"MESSAGE_DELETE_BULK:function($1){" + "function $1($2){" +
" var cache = $2getOrCreate($1.channelId);" + " var cache = $3getOrCreate($2.channelId);" +
" cache = $self.handleDelete(cache, $1, true);" + " cache = $self.handleDelete(cache, $2, true);" +
" $2commit(cache);" + " $3commit(cache);" +
"}," "}"
}, },
{ {
// Add current cached content + new edit time to cached message's editHistory // Add current cached content + new edit time to cached message's editHistory
match: /(MESSAGE_UPDATE:function\((\i)\).+?)\.update\((\i)/, match: /(function (\i)\((\i)\).+?)\.update\((\i)(?=.*MESSAGE_UPDATE:\2)/,
replace: "$1" + replace: "$1" +
".update($3,m =>" + ".update($4,m =>" +
" (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message, true)) ? m :" + " (($3.message.flags & 64) === 64 || $self.shouldIgnore($3.message, true)) ? m :" +
" $2.message.edited_timestamp && $2.message.content !== m.content ?" + " $3.message.edited_timestamp && $3.message.content !== m.content ?" +
" m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($2.message, m)]) :" + " m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($3.message, m)]) :" +
" m" + " m" +
")" + ")" +
".update($3" ".update($4"
}, },
{ {
// fix up key (edit last message) attempting to edit a deleted message // fix up key (edit last message) attempting to edit a deleted message
@ -488,12 +488,12 @@ export default definePlugin({
find: '"ReferencedMessageStore"', find: '"ReferencedMessageStore"',
replacement: [ replacement: [
{ {
match: /MESSAGE_DELETE:function\((\i)\).+?},/, match: /MESSAGE_DELETE:\i,/,
replace: "MESSAGE_DELETE:function($1){}," replace: "MESSAGE_DELETE:()=>{},"
}, },
{ {
match: /MESSAGE_DELETE_BULK:function\((\i)\).+?},/, match: /MESSAGE_DELETE_BULK:\i,/,
replace: "MESSAGE_DELETE_BULK:function($1){}," replace: "MESSAGE_DELETE_BULK:()=>{},"
} }
] ]
}, },

View file

@ -114,7 +114,7 @@ function SettingsComponent() {
return ( return (
<Flex flexDirection="column"> <Flex flexDirection="column">
{tags.map(t => ( {tags.map(t => (
<Card style={{ padding: "1em 1em 0" }}> <Card key={t.name} style={{ padding: "1em 1em 0" }}>
<Forms.FormTitle style={{ width: "fit-content" }}> <Forms.FormTitle style={{ width: "fit-content" }}>
<Tooltip text={t.description}> <Tooltip text={t.description}>
{({ onMouseEnter, onMouseLeave }) => ( {({ onMouseEnter, onMouseLeave }) => (
@ -183,8 +183,8 @@ export default definePlugin({
{ {
find: ".ORIGINAL_POSTER=", find: ".ORIGINAL_POSTER=",
replacement: { replacement: {
match: /\((\i)=\{\}\)\)\[(\i)\.BOT/, match: /(?=(\i)\[\i\.BOT)/,
replace: "($1=$self.getTagTypes()))[$2.BOT" replace: "$self.genTagTypes($1);"
} }
}, },
{ {
@ -280,8 +280,7 @@ export default definePlugin({
.filter(Boolean); .filter(Boolean);
}, },
getTagTypes() { genTagTypes(obj) {
const obj = {};
let i = 100; let i = 100;
tags.forEach(({ name }) => { tags.forEach(({ name }) => {
obj[name] = ++i; obj[name] = ++i;
@ -291,7 +290,6 @@ export default definePlugin({
obj[`${name}-OP`] = ++i; obj[`${name}-OP`] = ++i;
obj[i] = `${name}-OP`; obj[i] = `${name}-OP`;
}); });
return obj;
}, },
isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]), isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]),

View file

@ -24,6 +24,7 @@ import definePlugin from "@utils/types";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, useMemo, UserStore } from "@webpack/common"; import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, useMemo, UserStore } from "@webpack/common";
import { Channel, User } from "discord-types/general"; import { Channel, User } from "discord-types/general";
import { JSX } from "react";
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel"); const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
const UserUtils = findByPropsLazy("getGlobalName"); const UserUtils = findByPropsLazy("getGlobalName");
@ -55,6 +56,7 @@ function getMutualGDMCountText(user: User) {
function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) { function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) {
return mutualDms.map(c => ( return mutualDms.map(c => (
<Clickable <Clickable
key={c.id}
className={ProfileListClasses.listRow} className={ProfileListClasses.listRow}
onClick={() => { onClick={() => {
onClose(); onClose();

View file

@ -20,7 +20,7 @@ import {
findGroupChildrenByChildId, findGroupChildrenByChildId,
NavContextMenuPatchCallback NavContextMenuPatchCallback
} from "@api/ContextMenu"; } from "@api/ContextMenu";
import { definePluginSettings, migratePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { CogWheel } from "@components/Icons"; import { CogWheel } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
@ -115,8 +115,6 @@ function applyDefaultSettings(guildId: string | null) {
} }
} }
migratePluginSettings("NewGuildSettings", "MuteNewGuild");
export default definePlugin({ export default definePlugin({
name: "NewGuildSettings", name: "NewGuildSettings",
description: "Automatically mute new servers and change various other settings upon joining", description: "Automatically mute new servers and change various other settings upon joining",

View file

@ -54,8 +54,8 @@ export default definePlugin({
predicate: () => Settings.plugins.NoBlockedMessages.ignoreBlockedMessages === true, predicate: () => Settings.plugins.NoBlockedMessages.ignoreBlockedMessages === true,
replacement: [ replacement: [
{ {
match: /(?<=MESSAGE_CREATE:function\((\i)\){)/, match: /(?<=function (\i)\((\i)\){)(?=.*MESSAGE_CREATE:\1)/,
replace: (_, props) => `if($self.isBlocked(${props}.message))return;` replace: (_, _funcName, props) => `if($self.isBlocked(${props}.message))return;`
} }
] ]
})) }))

View file

@ -74,10 +74,10 @@ export default definePlugin({
// This prevents the Message Requests tab from always hiding due to the previous patch (and is compatible with spam requests) // 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. // In short, only the red badge is hidden. Button visibility behavior isn't changed.
{ {
find: ".getSpamChannelsCount()", find: ".getSpamChannelsCount();return",
predicate: () => settings.store.hideMessageRequestsCount, predicate: () => settings.store.hideMessageRequestsCount,
replacement: { replacement: {
match: /(?<=getSpamChannelsCount\(\),\i=)\i\.getMessageRequestsCount\(\)/, match: /(?<=getSpamChannelsCount\(\);return )\i\.getMessageRequestsCount\(\)/,
replace: "$self.getRealMessageRequestCount()" replace: "$self.getRealMessageRequestCount()"
} }
}, },

View file

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { Settings } from "@api/Settings";
import { getUserSettingLazy } from "@api/UserSettings"; import { getUserSettingLazy } from "@api/UserSettings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
@ -28,11 +27,16 @@ export default definePlugin({
name: "NoScreensharePreview", name: "NoScreensharePreview",
description: "Disables screenshare previews from being sent.", description: "Disables screenshare previews from being sent.",
authors: [Devs.Nuckyz], authors: [Devs.Nuckyz],
start() { start() {
if (!DisableStreamPreviews.getSetting()) { if (!DisableStreamPreviews.getSetting()) {
DisableStreamPreviews.updateSetting(true); DisableStreamPreviews.updateSetting(true);
} }
},
Settings.plugins.NoScreensharePreview.enabled = false; stop() {
if (DisableStreamPreviews.getSetting()) {
DisableStreamPreviews.updateSetting(false);
}
} }
}); });

View file

@ -0,0 +1,5 @@
# No Unblock To Jump
Removes the popup preventing you to jump to a message from a blocked/ignored user (eg: in search results)
![A modal popup telling you to unblock a user to jump their message](https://github.com/user-attachments/assets/0e4b859d-f3b3-4101-9a83-829afb473d1e)

View file

@ -26,25 +26,46 @@ export default definePlugin({
authors: [Devs.dzshn], authors: [Devs.dzshn],
patches: [ patches: [
{ {
// Clicking on search results to jump
find: '.id,"Search Results"', find: '.id,"Search Results"',
replacement: { replacement: [
{
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/, match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
replace: "if(false)$1" replace: "if(false)$1"
}
}, },
{ {
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNIGNORE_TO_JUMP_TITLE})/,
replace: "if(false)$1"
},
]
},
{
// Jump buttton in top right corner of messages
find: "renderJumpButton()", find: "renderJumpButton()",
replacement: { replacement: [
{
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/, match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
replace: "if(false)$1" replace: "if(false)$1"
}
}, },
{ {
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNIGNORE_TO_JUMP_TITLE})/,
replace: "if(false)$1"
},
]
},
{
// Clicking on replied messages to jump
find: "flash:!0,returnMessageId", find: "flash:!0,returnMessageId",
replacement: { replacement: [
{
match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/, match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
replace: "false?$1" replace: "false?$1"
} },
{
match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNIGNORE_TO_JUMP_TITLE})/,
replace: "false?$1"
},
]
} }
] ]
}); });

View file

@ -57,7 +57,7 @@ const UrlReplacementRules: Record<string, URLReplacementRule> = {
description: "Open Tidal links in the Tidal app", description: "Open Tidal links in the Tidal app",
}, },
itunes: { itunes: {
match: /^https:\/\/music\.apple\.com\/([a-z]{2}\/)?(album|artist|playlist|song|curator)\/([^/?#]+)\/?([^/?#]+)?(?:\?.*)?(?:#.*)?$/, match: /^https:\/\/(?:geo\.)?music\.apple\.com\/([a-z]{2}\/)?(album|artist|playlist|song|curator)\/([^/?#]+)\/?([^/?#]+)?(?:\?.*)?(?:#.*)?$/,
replace: (_, lang, type, name, id) => id ? `itunes://music.apple.com/us/${type}/${name}/${id}` : `itunes://music.apple.com/us/${type}/${name}`, replace: (_, lang, type, name, id) => id ? `itunes://music.apple.com/us/${type}/${name}/${id}` : `itunes://music.apple.com/us/${type}/${name}`,
description: "Open Apple Music links in the iTunes app" description: "Open Apple Music links in the iTunes app"
}, },

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings, migratePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType, ReporterTestable } from "@utils/types"; import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { FluxDispatcher } from "@webpack/common"; import { FluxDispatcher } from "@webpack/common";
@ -41,7 +41,6 @@ const settings = definePluginSettings({
}, },
}); });
migratePluginSettings("PartyMode", "Party mode 🎉");
export default definePlugin({ export default definePlugin({
name: "PartyMode", name: "PartyMode",
description: "Allows you to use party mode cause the party never ends ✨", description: "Allows you to use party mode cause the party never ends ✨",

View file

@ -113,6 +113,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
return ( return (
<div <div
key={index}
className={cl("modal-list-item-btn")} className={cl("modal-list-item-btn")}
onClick={() => selectItem(index)} onClick={() => selectItem(index)}
role="button" role="button"
@ -178,7 +179,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
<div className={cl("modal-divider")} /> <div className={cl("modal-divider")} />
<ScrollerThin className={cl("modal-perms")} orientation="auto"> <ScrollerThin className={cl("modal-perms")} orientation="auto">
{Object.values(PermissionsBits).map(bit => ( {Object.values(PermissionsBits).map(bit => (
<div className={cl("modal-perms-item")}> <div key={bit} className={cl("modal-perms-item")}>
<div className={cl("modal-perms-item-icon")}> <div className={cl("modal-perms-item-icon")}>
{(() => { {(() => {
const { permissions, overwriteAllow, overwriteDeny } = selectedItem; const { permissions, overwriteAllow, overwriteDeny } = selectedItem;

View file

@ -192,6 +192,7 @@ function UserPermissionsComponent({ guild, guildMember, closePopout }: { guild:
<div className={classes(RoleRootClasses.root)}> <div className={classes(RoleRootClasses.root)}>
{userPermissions.map(({ permission, roleColor, roleName }) => ( {userPermissions.map(({ permission, roleColor, roleName }) => (
<Tooltip <Tooltip
key={permission}
text={<GrantedByTooltip roleName={roleName} roleColor={roleColor} />} text={<GrantedByTooltip roleName={roleName} roleColor={roleColor} />}
tooltipClassName={cl("granted-by-container")} tooltipClassName={cl("granted-by-container")}
tooltipContentClassName={cl("granted-by-content")} tooltipContentClassName={cl("granted-by-content")}

View file

@ -30,13 +30,13 @@ export default definePlugin({
{ {
find: ".removeMosaicItemHoverButton),", find: ".removeMosaicItemHoverButton),",
replacement: { replacement: {
match: /\.nonMediaMosaicItem\]:.{0,40}children:\[(?<=showDownload:(\i).+?isVisualMediaType:(\i).+?)/, match: /(\.nonMediaMosaicItem\]:.{0,40}children:)(\i.slice\(\i\))(?<=showDownload:(\i).+?isVisualMediaType:(\i).+?)/,
replace: "$&$1&&$2&&$self.renderPiPButton()," replace: (_, rest, origChildren, showDownload, isVisualMediaType) => `${rest}[${showDownload}&&${isVisualMediaType}&&$self.PictureInPictureButton(),...${origChildren}]`
} }
} }
], ],
renderPiPButton: ErrorBoundary.wrap(() => { PictureInPictureButton: ErrorBoundary.wrap(() => {
return ( return (
<Tooltip text="Toggle Picture in Picture"> <Tooltip text="Toggle Picture in Picture">
{tooltipProps => ( {tooltipProps => (

View file

@ -33,7 +33,8 @@ function createPinMenuItem(channelId: string) {
{ {
categories.map(category => ( categories.map(category => (
<Menu.MenuItem <Menu.MenuItem
id={`pin-category-${category.name}`} key={category.id}
id={`pin-category-${category.id}`}
label={category.name} label={category.name}
action={() => addChannelToCategory(channelId, category.id).then(forceUpdate)} action={() => addChannelToCategory(channelId, category.id).then(forceUpdate)}
/> />

View file

@ -71,7 +71,7 @@ export default definePlugin({
replacement: [ replacement: [
{ {
// Filter out pinned channels from the private channel list // Filter out pinned channels from the private channel list
match: /(?<=\i,{channels:\i,)privateChannelIds:(\i)/, match: /(?<=channels:\i,)privateChannelIds:(\i)(?=,listRef:)/,
replace: "privateChannelIds:$1.filter(c=>!$self.isPinned(c))" replace: "privateChannelIds:$1.filter(c=>!$self.isPinned(c))"
}, },
{ {
@ -96,8 +96,8 @@ export default definePlugin({
// Fix Row Height // Fix Row Height
{ {
match: /(?<="getRowHeight",.{1,100}return 1===)\i/, match: /(\.startsWith\("section-divider"\).+?return 1===)(\i)/,
replace: "($&-$self.categoryLen())" replace: "$1($2-$self.categoryLen())"
}, },
{ {
match: /"getRowHeight",\((\i),(\i)\)=>{/, match: /"getRowHeight",\((\i),(\i)\)=>{/,
@ -124,7 +124,7 @@ export default definePlugin({
{ {
find: ".FRIENDS},\"friends\"", find: ".FRIENDS},\"friends\"",
replacement: { replacement: {
match: /(?<=\i=\i=>{).{1,100}premiumTabSelected.{1,800}showDMHeader:.+?,/, match: /(?<=\i=\i=>{).{1,100}premiumTabSelected.{0,950}showDMHeader:.+?,/,
replace: "let forceUpdate = Vencord.Util.useForceUpdater();$&_forceUpdate:forceUpdate," replace: "let forceUpdate = Vencord.Util.useForceUpdater();$&_forceUpdate:forceUpdate,"
} }
}, },

View file

@ -20,7 +20,7 @@ import { definePluginSettings, Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { ChannelStore, FluxDispatcher as Dispatcher, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common"; import { ChannelStore, ComponentDispatch, FluxDispatcher as Dispatcher, MessageStore, PermissionsBits, PermissionStore, SelectedChannelStore, UserStore } from "@webpack/common";
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
const Kangaroo = findByPropsLazy("jumpToMessage"); const Kangaroo = findByPropsLazy("jumpToMessage");
@ -60,24 +60,24 @@ export default definePlugin({
settings, settings,
start() { start() {
Dispatcher.subscribe("DELETE_PENDING_REPLY", onDeletePendingReply);
Dispatcher.subscribe("MESSAGE_END_EDIT", onEndEdit);
Dispatcher.subscribe("MESSAGE_START_EDIT", onStartEdit);
Dispatcher.subscribe("CREATE_PENDING_REPLY", onCreatePendingReply);
document.addEventListener("keydown", onKeydown); document.addEventListener("keydown", onKeydown);
}, },
stop() { stop() {
Dispatcher.unsubscribe("DELETE_PENDING_REPLY", onDeletePendingReply);
Dispatcher.unsubscribe("MESSAGE_END_EDIT", onEndEdit);
Dispatcher.unsubscribe("MESSAGE_START_EDIT", onStartEdit);
Dispatcher.unsubscribe("CREATE_PENDING_REPLY", onCreatePendingReply);
document.removeEventListener("keydown", onKeydown); document.removeEventListener("keydown", onKeydown);
}, },
});
const onDeletePendingReply = () => replyIdx = -1; flux: {
const onEndEdit = () => editIdx = -1; DELETE_PENDING_REPLY() {
replyIdx = -1;
},
MESSAGE_END_EDIT() {
editIdx = -1;
},
MESSAGE_START_EDIT: onStartEdit,
CREATE_PENDING_REPLY: onCreatePendingReply
}
});
function calculateIdx(messages: Message[], id: string) { function calculateIdx(messages: Message[], id: string) {
const idx = messages.findIndex(m => m.id === id); const idx = messages.findIndex(m => m.id === id);
@ -109,6 +109,8 @@ function onKeydown(e: KeyboardEvent) {
if (!isUp && e.key !== "ArrowDown") return; if (!isUp && e.key !== "ArrowDown") return;
if (!isCtrl(e) || isAltOrMeta(e)) return; if (!isCtrl(e) || isAltOrMeta(e)) return;
e.preventDefault();
if (e.shiftKey) if (e.shiftKey)
nextEdit(isUp); nextEdit(isUp);
else else
@ -194,9 +196,10 @@ function nextReply(isUp: boolean) {
channel, channel,
message, message,
shouldMention: shouldMention(message), shouldMention: shouldMention(message),
showMentionToggle: channel.guild_id !== null && message.author.id !== meId, showMentionToggle: channel.isPrivate() && message.author.id !== meId,
_isQuickReply: true _isQuickReply: true
}); });
ComponentDispatch.dispatchToLastSubscribed("TEXTAREA_FOCUS");
jumpIfOffScreen(channel.id, message.id); jumpIfOffScreen(channel.id, message.id);
} }

View file

@ -159,7 +159,7 @@ export default LazyComponent(() => {
onClick={() => openBlockModal()} onClick={() => openBlockModal()}
/> />
)} )}
{review.sender.badges.map(badge => <ReviewBadge {...badge} />)} {review.sender.badges.map((badge, idx) => <ReviewBadge key={idx} {...badge} />)}
{ {
!settings.store.hideTimestamps && review.type !== ReviewType.System && ( !settings.store.hideTimestamps && review.type !== ReviewType.System && (
@ -170,7 +170,13 @@ export default LazyComponent(() => {
<div className={cl("review-comment")}> <div className={cl("review-comment")}>
{(review.comment.length > 200 && !showAll) {(review.comment.length > 200 && !showAll)
? [Parser.parseGuildEventDescription(review.comment.substring(0, 200)), "...", <br />, (<a onClick={() => setShowAll(true)}>Read more</a>)] ? (
<>
{Parser.parseGuildEventDescription(review.comment.substring(0, 200))}...
<br />
<a onClick={() => setShowAll(true)}>Read more</a>]
</>
)
: Parser.parseGuildEventDescription(review.comment)} : Parser.parseGuildEventDescription(review.comment)}
</div> </div>

View file

@ -147,7 +147,7 @@ const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
viewBox="0 0 24 24" viewBox="0 0 24 24"
style={{ scale: "1.2" }} style={{ scale: "1.2" }}
> >
<g fill="none" fill-rule="evenodd"> <g fill="none" fillRule="evenodd">
<path fill="currentColor" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7v-5z" /> <path fill="currentColor" d="M19 3h-1V1h-2v2H8V1H6v2H5c-1.11 0-1.99.9-1.99 2L3 19a2 2 0 0 0 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V8h14v11zM7 10h5v5H7v-5z" />
<rect width="24" height="24" /> <rect width="24" height="24" />
</g> </g>

View file

@ -247,6 +247,7 @@ function UserList(type: "friends" | "blocked", guild: Guild, ids: string[], setC
<ScrollerThin fade className={cl("scroller")}> <ScrollerThin fade className={cl("scroller")}>
{members.map(id => {members.map(id =>
<FriendRow <FriendRow
key={id}
user={UserStore.getUser(id)} user={UserStore.getUser(id)}
status={PresenceStore.getStatus(id) || "offline"} status={PresenceStore.getStatus(id) || "offline"}
onSelect={() => openUserProfile(id)} onSelect={() => openUserProfile(id)}

View file

@ -5,7 +5,6 @@
*/ */
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Menu } from "@webpack/common"; import { Menu } from "@webpack/common";
@ -25,7 +24,6 @@ const Patch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild;
); );
}; };
migratePluginSettings("ServerInfo", "ServerProfile"); // what was I thinking with this name lmao
export default definePlugin({ export default definePlugin({
name: "ServerInfo", name: "ServerInfo",
description: "Allows you to view info about a server", description: "Allows you to view info about a server",

View file

@ -17,6 +17,7 @@
*/ */
import { Clipboard } from "@webpack/common"; import { Clipboard } from "@webpack/common";
import { JSX } from "react";
import { cl } from "../utils/misc"; import { cl } from "../utils/misc";
import { CopyButton } from "./CopyButton"; import { CopyButton } from "./CopyButton";

View file

@ -18,6 +18,7 @@
import type { IThemedToken } from "@vap/shiki"; import type { IThemedToken } from "@vap/shiki";
import { hljs } from "@webpack/common"; import { hljs } from "@webpack/common";
import { JSX } from "react";
import { cl } from "../utils/misc"; import { cl } from "../utils/misc";
import { ThemeBase } from "./Highlighter"; import { ThemeBase } from "./Highlighter";
@ -41,12 +42,12 @@ export const Code = ({
if (useHljs) { if (useHljs) {
try { try {
const { value: hljsHtml } = hljs.highlight(lang!, content, true); const { value: hljsHtml } = hljs.highlight(content, { language: lang!, ignoreIllegals: true });
lines = hljsHtml lines = hljsHtml
.split("\n") .split("\n")
.map((line, i) => <span key={i} dangerouslySetInnerHTML={{ __html: line }} />); .map((line, i) => <span key={i} dangerouslySetInnerHTML={{ __html: line }} />);
} catch { } catch {
lines = content.split("\n").map(line => <span>{line}</span>); lines = content.split("\n").map((line, idx) => <span key={idx}>{line}</span>);
} }
} else { } else {
const renderTokens = const renderTokens =
@ -55,11 +56,11 @@ export const Code = ({
.split("\n") .split("\n")
.map(line => [{ color: theme.plainColor, content: line } as IThemedToken]); .map(line => [{ color: theme.plainColor, content: line } as IThemedToken]);
lines = renderTokens.map(line => { lines = renderTokens.map((line, idx) => {
// [Cynthia] this makes it so when you highlight the codeblock // [Cynthia] this makes it so when you highlight the codeblock
// empty lines are also selected and copied when you Ctrl+C. // empty lines are also selected and copied when you Ctrl+C.
if (line.length === 0) { if (line.length === 0) {
return <span>{"\n"}</span>; return <span key={idx}>{"\n"}</span>;
} }
return ( return (

View file

@ -97,7 +97,7 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) {
gap: getSpacingPx(settings.store.iconSpacing), gap: getSpacingPx(settings.store.iconSpacing),
flexWrap: "wrap" flexWrap: "wrap"
}}> }}>
{connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} />)} {connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} key={connection.id} />)}
</Flex> </Flex>
); );
} }
@ -137,6 +137,7 @@ function CompactConnectionComponent({ connection, theme }: { connection: Connect
className="vc-user-connection" className="vc-user-connection"
href={url} href={url}
target="_blank" target="_blank"
rel="noreferrer"
onClick={e => { onClick={e => {
if (Vencord.Plugins.isPluginEnabled("OpenInApp")) { if (Vencord.Plugins.isPluginEnabled("OpenInApp")) {
const OpenInApp = Vencord.Plugins.plugins.OpenInApp as any as typeof import("../openInApp").default; const OpenInApp = Vencord.Plugins.plugins.OpenInApp as any as typeof import("../openInApp").default;

View file

@ -268,7 +268,7 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) {
<div className="shc-lock-screen-tags-container"> <div className="shc-lock-screen-tags-container">
<Text variant="text-lg/bold">Available tags:</Text> <Text variant="text-lg/bold">Available tags:</Text>
<div className="shc-lock-screen-tags"> <div className="shc-lock-screen-tags">
{availableTags.map(tag => <TagComponent tag={tag} />)} {availableTags.map(tag => <TagComponent tag={tag} key={tag.id} />)}
</div> </div>
</div> </div>
} }

View file

@ -168,7 +168,7 @@ export default definePlugin({
}, },
// Add the hidden eye icon if the channel is hidden // Add the hidden eye icon if the channel is hidden
{ {
match: /\.name\),.{0,120}\.children.+?:null(?<=,channel:(\i).+?)/, match: /\.name,{.{0,140}\.children.+?:null(?<=,channel:(\i).+?)/,
replace: (m, channel) => `${m},$self.isHiddenChannel(${channel})?$self.HiddenChannelIcon():null` replace: (m, channel) => `${m},$self.isHiddenChannel(${channel})?$self.HiddenChannelIcon():null`
}, },
// Make voice channels also appear as muted if they are muted // Make voice channels also appear as muted if they are muted

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import { definePluginSettings, migratePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin, { OptionType, PluginSettingDef } from "@utils/types"; import definePlugin, { OptionType, PluginSettingDef } from "@utils/types";
@ -30,17 +30,16 @@ const opt = (description: string) => ({
const settings = definePluginSettings({ const settings = definePluginSettings({
showTimeouts: opt("Show member timeout icons in chat."), showTimeouts: opt("Show member timeout icons in chat."),
showInvitesPaused: opt("Show the invites paused tooltip in the server list."), showInvitesPaused: opt("Show the invites paused tooltip in the server list."),
showModView: opt("Show the member mod view context menu item in all servers."), showModView: opt("Show the member mod view context menu item in all servers.")
disableDiscoveryFilters: opt("Disable filters in Server Discovery search that hide servers that don't meet discovery criteria."),
disableDisallowedDiscoveryFilters: opt("Disable filters in Server Discovery search that hide NSFW & disallowed servers."),
}); });
migratePluginSettings("ShowHiddenThings", "ShowTimeouts");
export default definePlugin({ export default definePlugin({
name: "ShowHiddenThings", name: "ShowHiddenThings",
tags: ["ShowTimeouts", "ShowInvitesPaused", "ShowModView", "DisableDiscoveryFilters"], tags: ["ShowTimeouts", "ShowInvitesPaused", "ShowModView", "DisableDiscoveryFilters"],
description: "Displays various hidden & moderator-only things regardless of permissions.", description: "Displays various hidden & moderator-only things regardless of permissions.",
authors: [Devs.Dolfies], authors: [Devs.Dolfies],
settings,
patches: [ patches: [
{ {
find: "showCommunicationDisabledStyles", find: "showCommunicationDisabledStyles",
@ -83,43 +82,6 @@ export default definePlugin({
match: /\i(?=\?null)/, match: /\i(?=\?null)/,
replace: "false" replace: "false"
} }
},
{
find: "prod_discoverable_guilds",
predicate: () => settings.store.disableDiscoveryFilters,
replacement: {
match: /\{"auto_removed:.*?\}/,
replace: "{}"
} }
}, ]
// remove the 200 server minimum
{
find: '">200"',
predicate: () => settings.store.disableDiscoveryFilters,
replacement: {
match: '">200"',
replace: '">0"'
}
},
// empty word filter
{
find: '"pepe","nude"',
predicate: () => settings.store.disableDisallowedDiscoveryFilters,
replacement: {
match: /(?<=[?=])\["pepe",.+?\]/,
replace: "[]",
},
},
// patch request that queries if term is allowed
{
find: ".GUILD_DISCOVERY_VALID_TERM,query:",
predicate: () => settings.store.disableDisallowedDiscoveryFilters,
all: true,
replacement: {
match: /\i\.\i\.get\(\{url:\i\.\i\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0,rejectWithError:!1\}\)/g,
replace: "Promise.resolve({ body: { valid: true } })"
}
}
],
settings,
}); });

View file

@ -84,13 +84,12 @@ export default definePlugin({
], ],
TooltipWrapper: ErrorBoundary.wrap(({ message, children, text }: { message: Message; children: FunctionComponent<any>; text: ReactNode; }) => { TooltipWrapper: ErrorBoundary.wrap(({ message, children, text }: { message: Message; children: FunctionComponent<any>; text: ReactNode; }) => {
if (settings.store.displayStyle === DisplayStyle.Tooltip) return <Tooltip if (settings.store.displayStyle === DisplayStyle.Tooltip)
children={children} return <Tooltip text={renderTimeout(message, false)}>{children}</Tooltip>;
text={renderTimeout(message, false)}
/>;
return ( return (
<div className="vc-std-wrapper"> <div className="vc-std-wrapper">
<Tooltip text={text} children={children} /> <Tooltip text={text}>{children}</Tooltip>
<Text variant="text-md/normal" color="status-danger"> <Text variant="text-md/normal" color="status-danger">
{renderTimeout(message, true)} timeout remaining {renderTimeout(message, true)} timeout remaining
</Text> </Text>

View file

@ -78,7 +78,7 @@ const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
{!enabled && <> {!enabled && <>
<mask id="vc-silent-msg-mask"> <mask id="vc-silent-msg-mask">
<path fill="#fff" d="M0 0h24v24H0Z" /> <path fill="#fff" d="M0 0h24v24H0Z" />
<path stroke="#000" stroke-width="5.99068" d="M0 24 24 0" /> <path stroke="#000" strokeWidth="5.99068" d="M0 24 24 0" />
</mask> </mask>
<path fill="var(--status-danger)" d="m21.178 1.70703 1.414 1.414L4.12103 21.593l-1.414-1.415L21.178 1.70703Z" /> <path fill="var(--status-danger)" d="m21.178 1.70703 1.414 1.414L4.12103 21.593l-1.414-1.415L21.178 1.70703Z" />
</>} </>}

View file

@ -18,6 +18,7 @@
import "./spotifyStyles.css"; import "./spotifyStyles.css";
import { Settings } from "@api/Settings";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons"; import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons";
import { debounce } from "@shared/debounce"; import { debounce } from "@shared/debounce";
@ -130,7 +131,9 @@ function Controls() {
> >
<Shuffle /> <Shuffle />
</Button> </Button>
<Button onClick={() => SpotifyStore.prev()}> <Button onClick={() => {
Settings.plugins.SpotifyControls.previousButtonRestartsTrack && SpotifyStore.position > 3000 ? SpotifyStore.seek(0) : SpotifyStore.prev();
}}>
<SkipPrev /> <SkipPrev />
</Button> </Button>
<Button onClick={() => SpotifyStore.setPlaying(!isPlaying)}> <Button onClick={() => SpotifyStore.setPlaying(!isPlaying)}>

View file

@ -44,6 +44,11 @@ export default definePlugin({
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Open Spotify URIs instead of Spotify URLs. Will only work if you have Spotify installed and might not work on all platforms", description: "Open Spotify URIs instead of Spotify URLs. Will only work if you have Spotify installed and might not work on all platforms",
default: false default: false
},
previousButtonRestartsTrack: {
type: OptionType.BOOLEAN,
description: "Restart currently playing track when pressing the previous button if playtime is >3s",
default: true
} }
}, },
patches: [ patches: [

View file

@ -120,8 +120,8 @@ function ServerTrace({ trace }: ServerTraceProps) {
<Forms.FormSection title="Server Trace" tag="h2"> <Forms.FormSection title="Server Trace" tag="h2">
<code> <code>
<Flex flexDirection="column" style={{ color: "var(--header-primary)", gap: 5, userSelect: "text" }}> <Flex flexDirection="column" style={{ color: "var(--header-primary)", gap: 5, userSelect: "text" }}>
{lines.map(line => ( {lines.map((line, idx) => (
<span>{line}</span> <span key={idx}>{line}</span>
))} ))}
</Flex> </Flex>
</code> </code>

View file

@ -163,7 +163,7 @@ export default definePlugin({
{ {
find: "UNREAD_IMPORTANT:", find: "UNREAD_IMPORTANT:",
replacement: { replacement: {
match: /\.name\),.{0,120}\.children.+?:null(?<=,channel:(\i).+?)/, match: /\.name,{.{0,140}\.children.+?:null(?<=,channel:(\i).+?)/,
replace: "$&,$self.TypingIndicator($1.id,$1.getGuildId())" replace: "$&,$self.TypingIndicator($1.id,$1.getGuildId())"
} }
}, },

View file

@ -23,6 +23,7 @@ import { openUserProfile } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/common"; import { Avatar, GuildMemberStore, React, RelationshipStore } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
import { PropsWithChildren } from "react";
const settings = definePluginSettings({ const settings = definePluginSettings({
showAvatars: { showAvatars: {
@ -91,34 +92,31 @@ export default definePlugin({
name: "TypingTweaks", name: "TypingTweaks",
description: "Show avatars and role colours in the typing indicator", description: "Show avatars and role colours in the typing indicator",
authors: [Devs.zt], authors: [Devs.zt],
settings,
patches: [ patches: [
{
find: "#{intl::THREE_USERS_TYPING}",
replacement: [
{
// Style the indicator and add function call to modify the children before rendering // Style the indicator and add function call to modify the children before rendering
{ match: /(?<=children:\[(\i)\.length>0.{0,200}?"aria-atomic":!0,children:)\i(?<=guildId:(\i).+?)/,
find: "getCooldownTextStyle", replace: "$self.renderTypingUsers({ users: $1, guildId: $2, children: $& }),style:$self.TYPING_TEXT_STYLE"
replacement: {
match: /(?<=children:\[(\i)\.length>0.{0,200}?"aria-atomic":!0,children:)\i/,
replace: "$self.mutateChildren(this.props, $1, $&), style: $self.TYPING_TEXT_STYLE"
}
}, },
{
// Changes the indicator to keep the user object when creating the list of typing users // Changes the indicator to keep the user object when creating the list of typing users
{ match: /\.map\((\i)=>\i\.\i\.getName\(\i,\i\.id,\1\)\)/,
find: "getCooldownTextStyle", replace: ""
replacement: {
match: /(?<=map\(\i=>)\i\.\i\.getName\(\i,this\.props\.channel\.id,(\i)\)/,
replace: "$1"
}
}, },
{
// Adds the alternative formatting for several users typing // Adds the alternative formatting for several users typing
{ match: /(,{a:(\i),b:(\i),c:\i}\):\i\.length>3&&\(\i=)\i\.\i\.string\(\i\.\i#{intl::SEVERAL_USERS_TYPING}\)(?<=(\i)\.length.+?)/,
find: "getCooldownTextStyle", replace: (_, rest, a, b, users) => `${rest}$self.buildSeveralUsers({ a: ${a}, b: ${b}, count: ${users}.length - 2 })`,
replacement: {
match: /(,{a:(\i),b:(\i),c:\i}\):)\i\.\i\.string\(\i\.\i#{intl::SEVERAL_USERS_TYPING}\)(?<=(\i)\.length.+?)/,
replace: (_, rest, a, b, users) => `${rest}$self.buildSeveralUsers({ a: ${a}, b: ${b}, count: ${users}.length - 2 })`
},
predicate: () => settings.store.alternativeFormatting predicate: () => settings.store.alternativeFormatting
} }
]
}
], ],
settings,
TYPING_TEXT_STYLE: { TYPING_TEXT_STYLE: {
display: "grid", display: "grid",
@ -128,7 +126,7 @@ export default definePlugin({
buildSeveralUsers, buildSeveralUsers,
mutateChildren(props: any, users: User[], children: any) { renderTypingUsers: ErrorBoundary.wrap(({ guildId, users, children }: PropsWithChildren<{ guildId: string, users: User[]; }>) => {
try { try {
if (!Array.isArray(children)) { if (!Array.isArray(children)) {
return children; return children;
@ -136,15 +134,17 @@ export default definePlugin({
let element = 0; let element = 0;
return children.map(c => return children.map(c => {
c.type === "strong" || (typeof c !== "string" && !React.isValidElement(c)) if (c.type !== "strong" && !(typeof c !== "string" && !React.isValidElement(c)))
? <TypingUser {...props} user={users[element++]} /> return c;
: c
); const user = users[element++];
return <TypingUser key={user.id} guildId={guildId} user={user} />;
});
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
return children; return children;
} }, { noop: true })
}); });

View file

@ -25,6 +25,7 @@ import { wordsToTitle } from "@utils/text";
import definePlugin, { OptionType, PluginOptionsItem, ReporterTestable } from "@utils/types"; import definePlugin, { OptionType, PluginOptionsItem, ReporterTestable } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common"; import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common";
import { ReactElement } from "react";
interface VoiceState { interface VoiceState {
userId: string; userId: string;
@ -289,7 +290,7 @@ export default definePlugin({
description: "Undeafen Message (only self for now)", description: "Undeafen Message (only self for now)",
default: "{{USER}} undeafened" default: "{{USER}} undeafened"
} }
}; } satisfies Record<string, PluginOptionsItem>;
}, },
settingsAboutComponent({ tempSettings: s }) { settingsAboutComponent({ tempSettings: s }) {
@ -303,7 +304,7 @@ export default definePlugin({
[], [],
); );
let errorComponent: React.ReactElement | null = null; let errorComponent: ReactElement<any> | null = null;
if (!hasVoices) { if (!hasVoices) {
let error = "No narrator voices found. "; let error = "No narrator voices found. ";
error += navigator.platform?.toLowerCase().includes("linux") error += navigator.platform?.toLowerCase().includes("linux")

View file

@ -94,7 +94,7 @@ export default definePlugin({
find: "AudioContextSettingsMigrated", find: "AudioContextSettingsMigrated",
replacement: [ replacement: [
{ {
match: /(?<=isLocalMute\(\i,\i\),volume:.+?volume:)\i(?=})/, match: /(?<=isLocalMute\(\i,\i\),volume:(\i).+?\i\(\i,\i,)\1(?=\))/,
replace: "$&>200?200:$&" replace: "$&>200?200:$&"
}, },
{ {
@ -109,7 +109,7 @@ export default definePlugin({
}, },
// Prevent the MediaEngineStore from overwriting our LocalVolumes above 200 with the ones the Discord Audio Context Settings sync sends // Prevent the MediaEngineStore from overwriting our LocalVolumes above 200 with the ones the Discord Audio Context Settings sync sends
{ {
find: '"MediaEngineStore"', find: '="MediaEngineStore",',
replacement: [ replacement: [
{ {
match: /(\.settings\.audioContextSettings.+?)(\i\[\i\])=(\i\.volume)(.+?setLocalVolume\(\i,).+?\)/, match: /(\.settings\.audioContextSettings.+?)(\i\[\i\])=(\i\.volume)(.+?setLocalVolume\(\i,).+?\)/,

View file

@ -23,7 +23,7 @@ import { Queue } from "@utils/Queue";
import { useForceUpdater } from "@utils/react"; import { useForceUpdater } from "@utils/react";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack"; import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common"; import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip, useEffect, useLayoutEffect } from "@webpack/common";
import { CustomEmoji } from "@webpack/types"; import { CustomEmoji } from "@webpack/types";
import { Message, ReactionEmoji, User } from "discord-types/general"; import { Message, ReactionEmoji, User } from "discord-types/general";
@ -113,8 +113,8 @@ export default definePlugin({
{ {
find: '"MessageReactionsStore"', find: '"MessageReactionsStore"',
replacement: { replacement: {
match: /(?<=CONNECTION_OPEN:function\(\){)(\i)={}/, match: /function (\i)\(\){(\i)={}(?=.*CONNECTION_OPEN:\1)/,
replace: "$&;$self.reactions=$1" replace: "$&;$self.reactions=$2;"
} }
}, },
{ {
@ -134,18 +134,21 @@ export default definePlugin({
renderUsers(props: RootObject) { renderUsers(props: RootObject) {
return props.message.reactions.length > 10 ? null : ( return props.message.reactions.length > 10 ? null : (
<ErrorBoundary noop> <ErrorBoundary noop>
<this._renderUsers {...props} /> <this.UsersComponent {...props} />
</ErrorBoundary> </ErrorBoundary>
); );
}, },
_renderUsers({ message, emoji, type }: RootObject) {
UsersComponent({ message, emoji, type }: RootObject) {
const forceUpdate = useForceUpdater(); const forceUpdate = useForceUpdater();
React.useLayoutEffect(() => { // bc need to prevent autoscrolling
useLayoutEffect(() => { // bc need to prevent autoscrolling
if (Scroll?.scrollCounter > 0) { if (Scroll?.scrollCounter > 0) {
Scroll.setAutomaticAnchor(null); Scroll.setAutomaticAnchor(null);
} }
}); });
React.useEffect(() => {
useEffect(() => {
const cb = (e: any) => { const cb = (e: any) => {
if (e.messageId === message.id) if (e.messageId === message.id)
forceUpdate(); forceUpdate();
@ -153,7 +156,7 @@ export default definePlugin({
FluxDispatcher.subscribe("MESSAGE_REACTION_ADD_USERS", cb); FluxDispatcher.subscribe("MESSAGE_REACTION_ADD_USERS", cb);
return () => FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD_USERS", cb); return () => FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD_USERS", cb);
}, [message.id]); }, [message.id, forceUpdate]);
const reactions = getReactionsWithQueue(message, emoji, type); const reactions = getReactionsWithQueue(message, emoji, type);
const users = Object.values(reactions).filter(Boolean) as User[]; const users = Object.values(reactions).filter(Boolean) as User[];

View file

@ -4,12 +4,10 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
// The entire code of this plugin can be found in native.ts // The entire code of this plugin can be found in native.ts
migratePluginSettings("YoutubeAdblock", "WatchTogetherAdblock");
export default definePlugin({ export default definePlugin({
name: "YoutubeAdblock", name: "YoutubeAdblock",
description: "Block ads in YouTube embeds and the WatchTogether activity via AdGuard", description: "Block ads in YouTube embeds and the WatchTogether activity via AdGuard",

View file

@ -47,7 +47,7 @@ export interface ModalOptions {
onCloseCallback?: (() => void); onCloseCallback?: (() => void);
} }
type RenderFunction = (props: ModalProps) => ReactNode; type RenderFunction = (props: ModalProps) => ReactNode | Promise<ReactNode>;
export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as { export const Modals = findByPropsLazy("ModalRoot", "ModalCloseButton") as {
ModalRoot: ComponentType<PropsWithChildren<{ ModalRoot: ComponentType<PropsWithChildren<{
@ -141,35 +141,32 @@ export const ModalContent = LazyComponent(() => Modals.ModalContent);
export const ModalFooter = LazyComponent(() => Modals.ModalFooter); export const ModalFooter = LazyComponent(() => Modals.ModalFooter);
export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton); export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton);
const ModalAPI = findByPropsLazy("openModalLazy"); export const ModalAPI = findByPropsLazy("openModalLazy");
/** /**
* Wait for the render promise to resolve, then open a modal with it. * Wait for the render promise to resolve, then open a modal with it.
* This is equivalent to render().then(openModal) * This is equivalent to render().then(openModal)
* You should use the Modal components exported by this file * You should use the Modal components exported by this file
*/ */
export function openModalLazy(render: () => Promise<RenderFunction>, options?: ModalOptions & { contextKey?: string; }): Promise<string> { export const openModalLazy: (render: () => Promise<RenderFunction>, options?: ModalOptions & { contextKey?: string; }) => Promise<string>
return ModalAPI.openModalLazy(render, options); = proxyLazyWebpack(() => ModalAPI.openModalLazy);
}
/** /**
* Open a Modal with the given render function. * Open a Modal with the given render function.
* You should use the Modal components exported by this file * You should use the Modal components exported by this file
*/ */
export function openModal(render: RenderFunction, options?: ModalOptions, contextKey?: string): string { export const openModal: (render: RenderFunction, options?: ModalOptions, contextKey?: string) => string
return ModalAPI.openModal(render, options, contextKey); = proxyLazyWebpack(() => ModalAPI.openModal);
}
/** /**
* Close a modal by its key * Close a modal by its key
*/ */
export function closeModal(modalKey: string, contextKey?: string): void { export const closeModal: (modalKey: string, contextKey?: string) => void
return ModalAPI.closeModal(modalKey, contextKey); = proxyLazyWebpack(() => ModalAPI.closeModal);
}
/** /**
* Close all open modals * Close all open modals
*/ */
export function closeAllModals(): void { export const closeAllModals: () => void
return ModalAPI.closeAllModals(); = proxyLazyWebpack(() => ModalAPI.closeAllModals);
}

View file

@ -17,6 +17,7 @@
*/ */
import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common"; import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common";
import { ActionDispatch } from "react";
import { checkIntersecting } from "./misc"; import { checkIntersecting } from "./misc";
@ -117,8 +118,8 @@ export function useAwaiter<T>(factory: () => Promise<T>, providedOpts?: AwaiterO
/** /**
* Returns a function that can be used to force rerender react components * Returns a function that can be used to force rerender react components
*/ */
export function useForceUpdater(): () => void; export function useForceUpdater(): ActionDispatch<[]>;
export function useForceUpdater(withDep: true): [unknown, () => void]; export function useForceUpdater(withDep: true): [any, ActionDispatch<[]>];
export function useForceUpdater(withDep?: true) { export function useForceUpdater(withDep?: true) {
const r = useReducer(x => x + 1, 0); const r = useReducer(x => x + 1, 0);
return withDep ? r : r[1]; return withDep ? r : r[1];

View file

@ -19,6 +19,7 @@
import { Command } from "@api/Commands"; import { Command } from "@api/Commands";
import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { FluxEvents } from "@webpack/types"; import { FluxEvents } from "@webpack/types";
import { JSX } from "react";
import { Promisable } from "type-fest"; import { Promisable } from "type-fest";
// exists to export default definePlugin({...}) // exists to export default definePlugin({...})

View file

@ -48,7 +48,7 @@ export const Constants: t.Constants = mapMangledModuleLazy('ME:"/users/@me"', {
export const RestAPI: t.RestAPI = findLazy(m => typeof m === "object" && m.del && m.put); export const RestAPI: t.RestAPI = findLazy(m => typeof m === "object" && m.del && m.put);
export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear"); export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear");
export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight", "registerLanguage"); export const hljs: typeof import("highlight.js").default = findByPropsLazy("highlight", "registerLanguage");
export const { match, P }: Pick<typeof import("ts-pattern"), "match" | "P"> = mapMangledModuleLazy("@ts-pattern/matcher", { export const { match, P }: Pick<typeof import("ts-pattern"), "match" | "P"> = mapMangledModuleLazy("@ts-pattern/matcher", {
match: filters.byCode("return new"), match: filters.byCode("return new"),