1
0
Fork 1
mirror of https://github.com/Vendicated/Vencord.git synced 2025-01-09 09:26:22 +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
*/
// @ts-check
import stylistic from "@stylistic/eslint-plugin";
import pathAlias from "eslint-plugin-path-alias";
import react from "eslint-plugin-react";
import header from "eslint-plugin-simple-header";
import simpleImportSort from "eslint-plugin-simple-import-sort";
import unusedImports from "eslint-plugin-unused-imports";
@ -15,6 +14,22 @@ import tseslint from "typescript-eslint";
export default tseslint.config(
{ 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"],
plugins: {
@ -23,7 +38,7 @@ export default tseslint.config(
"@typescript-eslint": tseslint.plugin,
"simple-import-sort": simpleImportSort,
"unused-imports": unusedImports,
"path-alias": pathAlias,
"path-alias": pathAlias
},
settings: {
"import/resolver": {

View file

@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
"version": "1.10.8",
"version": "1.10.9",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
@ -41,48 +41,49 @@
"@vap/shiki": "0.10.5",
"fflate": "^0.8.2",
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
"monaco-editor": "^0.50.0",
"nanoid": "^5.0.7",
"monaco-editor": "^0.52.2",
"nanoid": "^5.0.9",
"virtual-merge": "^1.0.1"
},
"devDependencies": {
"@stylistic/eslint-plugin": "^2.6.1",
"@types/chrome": "^0.0.269",
"@types/diff": "^5.2.1",
"@types/lodash": "^4.17.7",
"@types/node": "^22.0.3",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@stylistic/eslint-plugin": "^2.12.1",
"@types/chrome": "^0.0.287",
"@types/diff": "^6.0.0",
"@types/lodash": "^4.17.14",
"@types/node": "^22.10.5",
"@types/react": "^19.0.2",
"@types/react-dom": "^19.0.2",
"@types/yazl": "^2.4.5",
"diff": "^5.2.0",
"diff": "^7.0.0",
"discord-types": "^1.3.26",
"esbuild": "^0.15.18",
"eslint": "^9.8.0",
"eslint": "^9.17.0",
"eslint-import-resolver-alias": "^1.1.2",
"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-unused-imports": "^4.0.1",
"highlight.js": "10.7.3",
"eslint-plugin-unused-imports": "^4.1.4",
"highlight.js": "11.7.0",
"html-minifier-terser": "^7.2.0",
"moment": "^2.30.1",
"puppeteer-core": "^22.15.0",
"moment": "^2.22.2",
"puppeteer-core": "^23.11.1",
"standalone-electron-types": "^1.0.0",
"stylelint": "^16.8.1",
"stylelint": "^16.12.0",
"stylelint-config-standard": "^36.0.1",
"ts-patch": "^3.2.1",
"ts-pattern": "^5.3.1",
"tsx": "^4.16.5",
"type-fest": "^4.23.0",
"typescript": "^5.5.4",
"typescript-eslint": "^8.0.0",
"typescript-transform-paths": "^3.4.7",
"ts-patch": "^3.3.0",
"ts-pattern": "^5.6.0",
"tsx": "^4.19.2",
"type-fest": "^4.31.0",
"typescript": "^5.7.2",
"typescript-eslint": "^8.19.0",
"typescript-transform-paths": "^3.5.3",
"zip-local": "^0.3.5"
},
"packageManager": "pnpm@9.1.0",
"pnpm": {
"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"
},
"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 { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
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>;
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 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 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
*/
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");
@ -70,7 +70,7 @@ export function addGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback)
* @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> {
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);
@ -92,7 +92,7 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
* @param children The context menu children
* @param matchSubstring Whether to check if the id is a substring of the child id
*/
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null | 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) {
if (child == null) continue;
@ -124,7 +124,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
interface ContextMenuProps {
contextMenuApiArguments?: Array<any>;
navId: string;
children: Array<ReactElement | null>;
children: Array<ReactElement<any> | null>;
"aria-label": string;
onSelect: (() => void) | undefined;
onClose: (callback: (...args: Array<any>) => any) => void;
@ -162,7 +162,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
return props;
}
function cloneMenuChildren(obj: ReactElement | Array<ReactElement | null> | null) {
function cloneMenuChildren(obj: ReactElement<any> | Array<ReactElement<any> | null> | null) {
if (Array.isArray(obj)) {
return obj.map(cloneMenuChildren);
}

View file

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

View file

@ -16,6 +16,8 @@
* 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 Accessory = {
callback: AccessoryCallback;

View file

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

View file

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

View file

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

View file

@ -16,7 +16,7 @@
* 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 (
<div className="vc-plugins-badge" style={{
backgroundColor: color,

View file

@ -27,7 +27,7 @@ interface Props<T = any> {
/** Render nothing if an error occurs */
noop?: boolean;
/** 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 */
onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;
/** Custom error message */
@ -80,10 +80,14 @@ const ErrorBoundary = LazyComponent(() => {
if (this.props.noop) return null;
if (this.props.fallback)
return <this.props.fallback
children={this.props.children}
{...this.state}
/>;
return (
<this.props.fallback
wrappedProps={this.props.wrappedProps}
{...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.";

View file

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

View file

@ -27,7 +27,7 @@ export function Heart() {
>
<path
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"
/>
</svg>

View file

@ -20,7 +20,7 @@ import "./iconStyles.css";
import { getIntlMessage } from "@utils/discord";
import { classes } from "@utils/misc";
import type { PropsWithChildren } from "react";
import type { JSX, PropsWithChildren } from "react";
interface BaseIconProps extends IconProps {
viewBox: string;
@ -55,7 +55,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
className={classes(className, "vc-link-icon")}
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" />
<rect width={width} height={height} />
</g>
@ -122,8 +122,8 @@ export function InfoIcon(props: IconProps) {
>
<path
fill="currentColor"
fill-rule="evenodd"
d="M23 12a11 11 0 1 1-22 0 11 11 0 0 1 22 0Zm-9.5-4.75a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm-.77 3.96a1 1 0 1 0-1.96-.42l-1.04 4.86a2.77 2.77 0 0 0 4.31 2.83l.24-.17a1 1 0 1 0-1.16-1.62l-.24.17a.77.77 0 0 1-1.2-.79l1.05-4.86Z" clip-rule="evenodd"
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" clipRule="evenodd"
/>
</Icon>
);
@ -212,9 +212,9 @@ export function CogWheel(props: IconProps) {
>
<path
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"
clip-rule="evenodd"
clipRule="evenodd"
/>
</Icon>
);
@ -262,7 +262,7 @@ export function PlusIcon(props: IconProps) {
viewBox="0 0 18 18"
>
<polygon
fill-rule="nonzero"
fillRule="nonzero"
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"
/>

View file

@ -44,7 +44,7 @@ function ContributorModal({ user }: { user: User; }) {
useEffect(() => {
if (!profile && !user.bot && user.id)
fetchUserProfile(user.id);
}, [user.id]);
}, [user.id, user.bot, profile]);
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.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]);
}
})();
}, []);
}, [plugin.authors]);
async function saveAndClose() {
if (!plugin.options) {

View file

@ -35,6 +35,7 @@ import { useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types";
import { findByPropsLazy } from "@webpack";
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";
@ -387,7 +388,7 @@ function makeDependencyList(deps: string[]) {
return (
<React.Fragment>
<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>
);
}

View file

@ -111,9 +111,9 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
}
function renderDiff() {
return diff?.map(p => {
return diff?.map((p, idx) => {
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!",
body: (
<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>
)
});
@ -87,7 +87,7 @@ function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof
return (
<Card style={{ padding: "0 0.5em" }}>
{updates.map(({ hash, author, message }) => (
<div style={{
<div key={hash} style={{
marginTop: "0.5em",
marginBottom: "0.5em"
}}>

View file

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

View file

@ -71,13 +71,16 @@ export async function installExt(id: string) {
// React Devtools v4.25
// 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
// 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://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, {
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);
}

View file

@ -59,6 +59,14 @@ export default definePlugin({
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 { checkForUpdates, isOutdated, update } from "@utils/updater";
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 plugins, { PluginMeta } from "~plugins";

View file

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

View file

@ -4,6 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { canonicalizeMatch } from "@utils/patches";
import { execFile } from "child_process";
import { promisify } from "util";
@ -26,7 +27,7 @@ interface RemoteData {
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null;
const APPLE_MUSIC_BUNDLE_REGEX = /<script type="module" crossorigin src="([a-zA-Z0-9.\-/]+)"><\/script>/;
const APPLE_MUSIC_TOKEN_REGEX = /\w+="([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)",\w+="x-apple-jingle-correlation-key"/;
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;

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
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
match: /(?<=\.wrapper,children:\[)/,
match: /(?<=\.isExpanded\),children:\[)/,
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"
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>
);

View file

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

View file

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

View file

@ -67,9 +67,16 @@ export default definePlugin({
patches: [
{
find: 'react-spring: The "interpolate" function',
find: "https://github.com/highlightjs/highlight.js/issues/2277",
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: ""
}
},
@ -126,7 +133,7 @@ export default definePlugin({
{
find: "Slow dispatch on",
replacement: {
match: /\i\.totalTime>100&&\i\.verbose\("Slow dispatch on ".+?\)\);/,
match: /\i\.totalTime>\i&&\i\.verbose\("Slow dispatch on ".+?\)\);/,
replace: ""
}
},

View file

@ -20,6 +20,7 @@ import { Devs } from "@utils/constants";
import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
import { runtimeHashMessageKey } from "@utils/intlHash";
import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
import { ModalAPI } from "@utils/modal";
import { relaunch } from "@utils/native";
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
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,
@ -144,6 +148,8 @@ function makeShortcuts() {
me: { getter: () => Common.UserStore.getCurrentUser(), preload: false },
meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false },
messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false },
openModal: { getter: () => ModalAPI.openModal },
openModalLazy: { getter: () => ModalAPI.openModalLazy },
Stores: {
getter: () => Object.fromEntries(

View file

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

View file

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

View file

@ -16,11 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
migratePluginSettings("DisableCallIdle", "DisableDMCallIdle");
export default definePlugin({
name: "DisableCallIdle",
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"
}}>
{guilds.map(g => (
<Tooltip text={g.name}>
<Tooltip key={g.id} text={g.name}>
{({ onMouseLeave, onMouseEnter }) => (
<div
onMouseLeave={onMouseLeave}

View file

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

View file

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

View file

@ -57,7 +57,7 @@ export default definePlugin({
{
// https://regex101.com/r/x2mobQ/1
// 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)
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 definePlugin, { OptionType } from "@utils/types";
import { Menu, ReactDOM } from "@webpack/common";
import { JSX } from "react";
import type { Root } from "react-dom/client";
import { Magnifier, MagnifierProps } from "./components/Magnifier";

View file

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

View file

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

View file

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

View file

@ -114,7 +114,7 @@ function SettingsComponent() {
return (
<Flex flexDirection="column">
{tags.map(t => (
<Card style={{ padding: "1em 1em 0" }}>
<Card key={t.name} style={{ padding: "1em 1em 0" }}>
<Forms.FormTitle style={{ width: "fit-content" }}>
<Tooltip text={t.description}>
{({ onMouseEnter, onMouseLeave }) => (
@ -183,8 +183,8 @@ export default definePlugin({
{
find: ".ORIGINAL_POSTER=",
replacement: {
match: /\((\i)=\{\}\)\)\[(\i)\.BOT/,
replace: "($1=$self.getTagTypes()))[$2.BOT"
match: /(?=(\i)\[\i\.BOT)/,
replace: "$self.genTagTypes($1);"
}
},
{
@ -280,8 +280,7 @@ export default definePlugin({
.filter(Boolean);
},
getTagTypes() {
const obj = {};
genTagTypes(obj) {
let i = 100;
tags.forEach(({ name }) => {
obj[name] = ++i;
@ -291,7 +290,6 @@ export default definePlugin({
obj[`${name}-OP`] = ++i;
obj[i] = `${name}-OP`;
});
return obj;
},
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 { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, useMemo, UserStore } from "@webpack/common";
import { Channel, User } from "discord-types/general";
import { JSX } from "react";
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
const UserUtils = findByPropsLazy("getGlobalName");
@ -55,6 +56,7 @@ function getMutualGDMCountText(user: User) {
function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) {
return mutualDms.map(c => (
<Clickable
key={c.id}
className={ProfileListClasses.listRow}
onClick={() => {
onClose();

View file

@ -20,7 +20,7 @@ import {
findGroupChildrenByChildId,
NavContextMenuPatchCallback
} from "@api/ContextMenu";
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/Settings";
import { CogWheel } from "@components/Icons";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
@ -115,8 +115,6 @@ function applyDefaultSettings(guildId: string | null) {
}
}
migratePluginSettings("NewGuildSettings", "MuteNewGuild");
export default definePlugin({
name: "NewGuildSettings",
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,
replacement: [
{
match: /(?<=MESSAGE_CREATE:function\((\i)\){)/,
replace: (_, props) => `if($self.isBlocked(${props}.message))return;`
match: /(?<=function (\i)\((\i)\){)(?=.*MESSAGE_CREATE:\1)/,
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)
// In short, only the red badge is hidden. Button visibility behavior isn't changed.
{
find: ".getSpamChannelsCount()",
find: ".getSpamChannelsCount();return",
predicate: () => settings.store.hideMessageRequestsCount,
replacement: {
match: /(?<=getSpamChannelsCount\(\),\i=)\i\.getMessageRequestsCount\(\)/,
match: /(?<=getSpamChannelsCount\(\);return )\i\.getMessageRequestsCount\(\)/,
replace: "$self.getRealMessageRequestCount()"
}
},

View file

@ -16,7 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/Settings";
import { getUserSettingLazy } from "@api/UserSettings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
@ -28,11 +27,16 @@ export default definePlugin({
name: "NoScreensharePreview",
description: "Disables screenshare previews from being sent.",
authors: [Devs.Nuckyz],
start() {
if (!DisableStreamPreviews.getSetting()) {
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],
patches: [
{
// Clicking on search results to jump
find: '.id,"Search Results"',
replacement: {
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
replace: "if(false)$1"
}
replacement: [
{
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
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()",
replacement: {
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
replace: "if(false)$1"
}
replacement: [
{
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
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",
replacement: {
match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
replace: "false?$1"
}
replacement: [
{
match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
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",
},
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}`,
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/>.
*/
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { FluxDispatcher } from "@webpack/common";
@ -41,7 +41,6 @@ const settings = definePluginSettings({
},
});
migratePluginSettings("PartyMode", "Party mode 🎉");
export default definePlugin({
name: "PartyMode",
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 (
<div
key={index}
className={cl("modal-list-item-btn")}
onClick={() => selectItem(index)}
role="button"
@ -178,7 +179,7 @@ function RolesAndUsersPermissionsComponent({ permissions, guild, modalProps, hea
<div className={cl("modal-divider")} />
<ScrollerThin className={cl("modal-perms")} orientation="auto">
{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")}>
{(() => {
const { permissions, overwriteAllow, overwriteDeny } = selectedItem;

View file

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

View file

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

View file

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

View file

@ -71,7 +71,7 @@ export default definePlugin({
replacement: [
{
// 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))"
},
{
@ -96,8 +96,8 @@ export default definePlugin({
// Fix Row Height
{
match: /(?<="getRowHeight",.{1,100}return 1===)\i/,
replace: "($&-$self.categoryLen())"
match: /(\.startsWith\("section-divider"\).+?return 1===)(\i)/,
replace: "$1($2-$self.categoryLen())"
},
{
match: /"getRowHeight",\((\i),(\i)\)=>{/,
@ -124,7 +124,7 @@ export default definePlugin({
{
find: ".FRIENDS},\"friends\"",
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,"
}
},

View file

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

View file

@ -159,7 +159,7 @@ export default LazyComponent(() => {
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 && (
@ -170,7 +170,13 @@ export default LazyComponent(() => {
<div className={cl("review-comment")}>
{(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)}
</div>

View file

@ -147,7 +147,7 @@ const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
viewBox="0 0 24 24"
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" />
<rect width="24" height="24" />
</g>

View file

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

View file

@ -5,7 +5,6 @@
*/
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
import { migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
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({
name: "ServerInfo",
description: "Allows you to view info about a server",

View file

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

View file

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

View file

@ -97,7 +97,7 @@ function ConnectionsComponent({ id, theme }: { id: string, theme: string; }) {
gap: getSpacingPx(settings.store.iconSpacing),
flexWrap: "wrap"
}}>
{connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} />)}
{connections.map(connection => <CompactConnectionComponent connection={connection} theme={theme} key={connection.id} />)}
</Flex>
);
}
@ -137,6 +137,7 @@ function CompactConnectionComponent({ connection, theme }: { connection: Connect
className="vc-user-connection"
href={url}
target="_blank"
rel="noreferrer"
onClick={e => {
if (Vencord.Plugins.isPluginEnabled("OpenInApp")) {
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">
<Text variant="text-lg/bold">Available tags:</Text>
<div className="shc-lock-screen-tags">
{availableTags.map(tag => <TagComponent tag={tag} />)}
{availableTags.map(tag => <TagComponent tag={tag} key={tag.id} />)}
</div>
</div>
}

View file

@ -168,7 +168,7 @@ export default definePlugin({
},
// 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`
},
// 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/>.
*/
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType, PluginSettingDef } from "@utils/types";
@ -30,17 +30,16 @@ const opt = (description: string) => ({
const settings = definePluginSettings({
showTimeouts: opt("Show member timeout icons in chat."),
showInvitesPaused: opt("Show the invites paused tooltip in the server list."),
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."),
showModView: opt("Show the member mod view context menu item in all servers.")
});
migratePluginSettings("ShowHiddenThings", "ShowTimeouts");
export default definePlugin({
name: "ShowHiddenThings",
tags: ["ShowTimeouts", "ShowInvitesPaused", "ShowModView", "DisableDiscoveryFilters"],
description: "Displays various hidden & moderator-only things regardless of permissions.",
authors: [Devs.Dolfies],
settings,
patches: [
{
find: "showCommunicationDisabledStyles",
@ -83,43 +82,6 @@ export default definePlugin({
match: /\i(?=\?null)/,
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; }) => {
if (settings.store.displayStyle === DisplayStyle.Tooltip) return <Tooltip
children={children}
text={renderTimeout(message, false)}
/>;
if (settings.store.displayStyle === DisplayStyle.Tooltip)
return <Tooltip text={renderTimeout(message, false)}>{children}</Tooltip>;
return (
<div className="vc-std-wrapper">
<Tooltip text={text} children={children} />
<Tooltip text={text}>{children}</Tooltip>
<Text variant="text-md/normal" color="status-danger">
{renderTimeout(message, true)} timeout remaining
</Text>

View file

@ -78,7 +78,7 @@ const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
{!enabled && <>
<mask id="vc-silent-msg-mask">
<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>
<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 { Settings } from "@api/Settings";
import { Flex } from "@components/Flex";
import { ImageIcon, LinkIcon, OpenExternalIcon } from "@components/Icons";
import { debounce } from "@shared/debounce";
@ -130,7 +131,9 @@ function Controls() {
>
<Shuffle />
</Button>
<Button onClick={() => SpotifyStore.prev()}>
<Button onClick={() => {
Settings.plugins.SpotifyControls.previousButtonRestartsTrack && SpotifyStore.position > 3000 ? SpotifyStore.seek(0) : SpotifyStore.prev();
}}>
<SkipPrev />
</Button>
<Button onClick={() => SpotifyStore.setPlaying(!isPlaying)}>

View file

@ -44,6 +44,11 @@ export default definePlugin({
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",
default: false
},
previousButtonRestartsTrack: {
type: OptionType.BOOLEAN,
description: "Restart currently playing track when pressing the previous button if playtime is >3s",
default: true
}
},
patches: [

View file

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

View file

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

View file

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

View file

@ -94,7 +94,7 @@ export default definePlugin({
find: "AudioContextSettingsMigrated",
replacement: [
{
match: /(?<=isLocalMute\(\i,\i\),volume:.+?volume:)\i(?=})/,
match: /(?<=isLocalMute\(\i,\i\),volume:(\i).+?\i\(\i,\i,)\1(?=\))/,
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
{
find: '"MediaEngineStore"',
find: '="MediaEngineStore",',
replacement: [
{
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 definePlugin from "@utils/types";
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 { Message, ReactionEmoji, User } from "discord-types/general";
@ -113,8 +113,8 @@ export default definePlugin({
{
find: '"MessageReactionsStore"',
replacement: {
match: /(?<=CONNECTION_OPEN:function\(\){)(\i)={}/,
replace: "$&;$self.reactions=$1"
match: /function (\i)\(\){(\i)={}(?=.*CONNECTION_OPEN:\1)/,
replace: "$&;$self.reactions=$2;"
}
},
{
@ -134,18 +134,21 @@ export default definePlugin({
renderUsers(props: RootObject) {
return props.message.reactions.length > 10 ? null : (
<ErrorBoundary noop>
<this._renderUsers {...props} />
<this.UsersComponent {...props} />
</ErrorBoundary>
);
},
_renderUsers({ message, emoji, type }: RootObject) {
UsersComponent({ message, emoji, type }: RootObject) {
const forceUpdate = useForceUpdater();
React.useLayoutEffect(() => { // bc need to prevent autoscrolling
useLayoutEffect(() => { // bc need to prevent autoscrolling
if (Scroll?.scrollCounter > 0) {
Scroll.setAutomaticAnchor(null);
}
});
React.useEffect(() => {
useEffect(() => {
const cb = (e: any) => {
if (e.messageId === message.id)
forceUpdate();
@ -153,7 +156,7 @@ export default definePlugin({
FluxDispatcher.subscribe("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 users = Object.values(reactions).filter(Boolean) as User[];

View file

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

View file

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

View file

@ -17,6 +17,7 @@
*/
import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common";
import { ActionDispatch } from "react";
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
*/
export function useForceUpdater(): () => void;
export function useForceUpdater(withDep: true): [unknown, () => void];
export function useForceUpdater(): ActionDispatch<[]>;
export function useForceUpdater(withDep: true): [any, ActionDispatch<[]>];
export function useForceUpdater(withDep?: true) {
const r = useReducer(x => x + 1, 0);
return withDep ? r : r[1];

View file

@ -19,6 +19,7 @@
import { Command } from "@api/Commands";
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { FluxEvents } from "@webpack/types";
import { JSX } from "react";
import { Promisable } from "type-fest";
// 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 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", {
match: filters.byCode("return new"),