mirror of
https://github.com/Vendicated/Vencord.git
synced 2024-09-20 06:30:35 +00:00
Merge branch 'Vendicated:main' into TablistApi
This commit is contained in:
commit
cf5886eac6
91 changed files with 2536 additions and 492 deletions
1
.npmrc
1
.npmrc
|
@ -1 +1,2 @@
|
|||
strict-peer-dependencies=false
|
||||
package-manager-strict=false
|
||||
|
|
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
|
@ -1,11 +1,9 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"eamodio.gitlens",
|
||||
"EditorConfig.EditorConfig",
|
||||
"ExodiusStudios.comment-anchors",
|
||||
"formulahendry.auto-rename-tag",
|
||||
"GregorBiswanger.json2ts",
|
||||
"stylelint.vscode-stylelint"
|
||||
"stylelint.vscode-stylelint",
|
||||
"Vendicated.vencord-companion"
|
||||
]
|
||||
}
|
||||
|
|
16
package.json
16
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "vencord",
|
||||
"private": "true",
|
||||
"version": "1.8.3",
|
||||
"version": "1.8.5",
|
||||
"description": "The cutest Discord client mod",
|
||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||
"bugs": {
|
||||
|
@ -19,16 +19,17 @@
|
|||
"scripts": {
|
||||
"build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs",
|
||||
"buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs",
|
||||
"watch": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs --watch",
|
||||
"generatePluginJson": "tsx scripts/generatePluginList.ts",
|
||||
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
|
||||
"inject": "node scripts/runInstaller.mjs",
|
||||
"uninject": "node scripts/runInstaller.mjs",
|
||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern src/userplugins",
|
||||
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
||||
"lint:fix": "pnpm lint --fix",
|
||||
"test": "pnpm build && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
|
||||
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
|
||||
"testTsc": "tsc --noEmit",
|
||||
"uninject": "node scripts/runInstaller.mjs",
|
||||
"watch": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs --watch"
|
||||
"testTsc": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@sapphi-red/web-noise-suppressor": "0.3.3",
|
||||
|
@ -65,11 +66,12 @@
|
|||
"standalone-electron-types": "^1.0.0",
|
||||
"stylelint": "^15.6.0",
|
||||
"stylelint-config-standard": "^33.0.0",
|
||||
"ts-patch": "^3.1.2",
|
||||
"tsx": "^3.12.7",
|
||||
"type-fest": "^3.9.0",
|
||||
"typescript": "^5.0.4",
|
||||
"zip-local": "^0.3.5",
|
||||
"zustand": "^3.7.2"
|
||||
"typescript": "^5.4.5",
|
||||
"typescript-transform-paths": "^3.4.7",
|
||||
"zip-local": "^0.3.5"
|
||||
},
|
||||
"packageManager": "pnpm@9.1.0",
|
||||
"pnpm": {
|
||||
|
|
7
packages/vencord-types/.gitignore
vendored
Normal file
7
packages/vencord-types/.gitignore
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
*
|
||||
!.*ignore
|
||||
!package.json
|
||||
!*.md
|
||||
!prepare.ts
|
||||
!index.d.ts
|
||||
!globals.d.ts
|
4
packages/vencord-types/.npmignore
Normal file
4
packages/vencord-types/.npmignore
Normal file
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
prepare.ts
|
||||
.gitignore
|
||||
HOW2PUB.md
|
5
packages/vencord-types/HOW2PUB.md
Normal file
5
packages/vencord-types/HOW2PUB.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# How to publish
|
||||
|
||||
1. run `pnpm generateTypes` in the project root
|
||||
2. bump package.json version
|
||||
3. npm publish
|
11
packages/vencord-types/README.md
Normal file
11
packages/vencord-types/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# Vencord Types
|
||||
|
||||
Typings for Vencord's api, published to npm
|
||||
|
||||
```sh
|
||||
npm i @vencord/types
|
||||
|
||||
yarn add @vencord/types
|
||||
|
||||
pnpm add @vencord/types
|
||||
```
|
24
packages/vencord-types/globals.d.ts
vendored
Normal file
24
packages/vencord-types/globals.d.ts
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
declare global {
|
||||
export var VencordNative: typeof import("./VencordNative").default;
|
||||
export var Vencord: typeof import("./Vencord");
|
||||
}
|
||||
|
||||
export { };
|
5
packages/vencord-types/index.d.ts
vendored
Normal file
5
packages/vencord-types/index.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
/* eslint-disable */
|
||||
|
||||
/// <reference path="Vencord.d.ts" />
|
||||
/// <reference path="globals.d.ts" />
|
||||
/// <reference path="modules.d.ts" />
|
28
packages/vencord-types/package.json
Normal file
28
packages/vencord-types/package.json
Normal file
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "@vencord/types",
|
||||
"private": false,
|
||||
"version": "0.1.3",
|
||||
"description": "",
|
||||
"types": "index.d.ts",
|
||||
"scripts": {
|
||||
"prepublishOnly": "tsx ./prepare.ts",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Vencord",
|
||||
"license": "GPL-3.0",
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"fs-extra": "^11.2.0",
|
||||
"tsx": "^3.12.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "^4.14.191",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.0.10",
|
||||
"discord-types": "^1.3.26",
|
||||
"standalone-electron-types": "^1.0.0",
|
||||
"type-fest": "^3.5.3"
|
||||
}
|
||||
}
|
47
packages/vencord-types/prepare.ts
Normal file
47
packages/vencord-types/prepare.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { cpSync, moveSync, readdirSync, rmSync } from "fs-extra";
|
||||
import { join } from "path";
|
||||
|
||||
readdirSync(join(__dirname, "src"))
|
||||
.forEach(child => moveSync(join(__dirname, "src", child), join(__dirname, child), { overwrite: true }));
|
||||
|
||||
const VencordSrc = join(__dirname, "..", "..", "src");
|
||||
|
||||
for (const file of ["preload.d.ts", "userplugins", "main", "debug", "src", "browser", "scripts"]) {
|
||||
rmSync(join(__dirname, file), { recursive: true, force: true });
|
||||
}
|
||||
|
||||
function copyDtsFiles(from: string, to: string) {
|
||||
for (const file of readdirSync(from, { withFileTypes: true })) {
|
||||
// bad
|
||||
if (from === VencordSrc && file.name === "globals.d.ts") continue;
|
||||
|
||||
const fullFrom = join(from, file.name);
|
||||
const fullTo = join(to, file.name);
|
||||
|
||||
if (file.isDirectory()) {
|
||||
copyDtsFiles(fullFrom, fullTo);
|
||||
} else if (file.name.endsWith(".d.ts")) {
|
||||
cpSync(fullFrom, fullTo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
copyDtsFiles(VencordSrc, __dirname);
|
1123
pnpm-lock.yaml
1123
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
|
@ -0,0 +1,2 @@
|
|||
packages:
|
||||
- packages/*
|
|
@ -303,8 +303,10 @@ async function runtime(token: string) {
|
|||
delete patch.predicate;
|
||||
delete patch.group;
|
||||
|
||||
if (!Array.isArray(patch.replacement))
|
||||
Vencord.Util.canonicalizeFind(patch);
|
||||
if (!Array.isArray(patch.replacement)) {
|
||||
patch.replacement = [patch.replacement];
|
||||
}
|
||||
|
||||
patch.replacement.forEach(r => {
|
||||
delete r.predicate;
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
*/
|
||||
|
||||
export * as Api from "./api";
|
||||
export * as Components from "./components";
|
||||
export * as Plugins from "./plugins";
|
||||
export * as Util from "./utils";
|
||||
export * as QuickCss from "./utils/quickCss";
|
||||
|
|
|
@ -100,6 +100,7 @@ export async function showNotification(data: NotificationData) {
|
|||
const n = new Notification(title, {
|
||||
body,
|
||||
icon,
|
||||
// @ts-expect-error ts is drunk
|
||||
image
|
||||
});
|
||||
n.onclick = onClick;
|
||||
|
|
|
@ -16,10 +16,12 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "./ExpandableHeader.css";
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { Text, Tooltip, useState } from "@webpack/common";
|
||||
export const cl = classNameFactory("vc-expandableheader-");
|
||||
import "./ExpandableHeader.css";
|
||||
|
||||
const cl = classNameFactory("vc-expandableheader-");
|
||||
|
||||
export interface ExpandableHeaderProps {
|
||||
onMoreClick?: () => void;
|
||||
|
@ -31,7 +33,7 @@ export interface ExpandableHeaderProps {
|
|||
buttons?: React.ReactNode[];
|
||||
}
|
||||
|
||||
export default function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipText, defaultState = false, onDropDownClick, headerText }: ExpandableHeaderProps) {
|
||||
export function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipText, defaultState = false, onDropDownClick, headerText }: ExpandableHeaderProps) {
|
||||
const [showContent, setShowContent] = useState(defaultState);
|
||||
|
||||
return (
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
||||
import { CodeBlock } from "@components/CodeBlock";
|
||||
import { debounce } from "@shared/debounce";
|
||||
import { Margins } from "@utils/margins";
|
||||
|
@ -47,7 +46,7 @@ const findCandidates = debounce(function ({ find, setModule, setError }) {
|
|||
|
||||
interface ReplacementComponentProps {
|
||||
module: [id: number, factory: Function];
|
||||
match: string | RegExp;
|
||||
match: string;
|
||||
replacement: string | ReplaceFn;
|
||||
setReplacementError(error: any): void;
|
||||
}
|
||||
|
@ -58,7 +57,13 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
|||
|
||||
const [patchedCode, matchResult, diff] = React.useMemo(() => {
|
||||
const src: string = fact.toString().replaceAll("\n", "");
|
||||
const canonicalMatch = canonicalizeMatch(match);
|
||||
|
||||
try {
|
||||
new RegExp(match);
|
||||
} catch (e) {
|
||||
return ["", [], []];
|
||||
}
|
||||
const canonicalMatch = canonicalizeMatch(new RegExp(match));
|
||||
try {
|
||||
const canonicalReplace = canonicalizeReplace(replacement, "YourPlugin");
|
||||
var patched = src.replace(canonicalMatch, canonicalReplace as string);
|
||||
|
@ -180,7 +185,8 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) {
|
|||
|
||||
return (
|
||||
<>
|
||||
<Forms.FormTitle>replacement</Forms.FormTitle>
|
||||
{/* FormTitle adds a class if className is not set, so we set it to an empty string to prevent that */}
|
||||
<Forms.FormTitle className="">replacement</Forms.FormTitle>
|
||||
<TextInput
|
||||
value={replacement?.toString()}
|
||||
onChange={onChange}
|
||||
|
@ -188,7 +194,7 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) {
|
|||
/>
|
||||
{!isFunc && (
|
||||
<div className="vc-text-selectable">
|
||||
<Forms.FormTitle>Cheat Sheet</Forms.FormTitle>
|
||||
<Forms.FormTitle className={Margins.top8}>Cheat Sheet</Forms.FormTitle>
|
||||
{Object.entries({
|
||||
"\\i": "Special regex escape sequence that matches identifiers (varnames, classnames, etc.)",
|
||||
"$$": "Insert a $",
|
||||
|
@ -220,11 +226,12 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) {
|
|||
|
||||
interface FullPatchInputProps {
|
||||
setFind(v: string): void;
|
||||
setParsedFind(v: string | RegExp): void;
|
||||
setMatch(v: string): void;
|
||||
setReplacement(v: string | ReplaceFn): void;
|
||||
}
|
||||
|
||||
function FullPatchInput({ setFind, setMatch, setReplacement }: FullPatchInputProps) {
|
||||
function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: FullPatchInputProps) {
|
||||
const [fullPatch, setFullPatch] = React.useState<string>("");
|
||||
const [fullPatchError, setFullPatchError] = React.useState<string>("");
|
||||
|
||||
|
@ -233,6 +240,7 @@ function FullPatchInput({ setFind, setMatch, setReplacement }: FullPatchInputPro
|
|||
setFullPatchError("");
|
||||
|
||||
setFind("");
|
||||
setParsedFind("");
|
||||
setMatch("");
|
||||
setReplacement("");
|
||||
return;
|
||||
|
@ -256,7 +264,8 @@ function FullPatchInput({ setFind, setMatch, setReplacement }: FullPatchInputPro
|
|||
if (!parsed.replacement.match) throw new Error("No 'replacement.match' field");
|
||||
if (!parsed.replacement.replace) throw new Error("No 'replacement.replace' field");
|
||||
|
||||
setFind(parsed.find);
|
||||
setFind(parsed.find instanceof RegExp ? parsed.find.toString() : parsed.find);
|
||||
setParsedFind(parsed.find);
|
||||
setMatch(parsed.replacement.match instanceof RegExp ? parsed.replacement.match.source : parsed.replacement.match);
|
||||
setReplacement(parsed.replacement.replace);
|
||||
setFullPatchError("");
|
||||
|
@ -266,7 +275,7 @@ function FullPatchInput({ setFind, setMatch, setReplacement }: FullPatchInputPro
|
|||
}
|
||||
|
||||
return <>
|
||||
<Forms.FormText>Paste your full JSON patch here to fill out the fields</Forms.FormText>
|
||||
<Forms.FormText className={Margins.bottom8}>Paste your full JSON patch here to fill out the fields</Forms.FormText>
|
||||
<TextArea value={fullPatch} onChange={setFullPatch} onBlur={update} />
|
||||
{fullPatchError !== "" && <Forms.FormText style={{ color: "var(--text-danger)" }}>{fullPatchError}</Forms.FormText>}
|
||||
</>;
|
||||
|
@ -274,6 +283,7 @@ function FullPatchInput({ setFind, setMatch, setReplacement }: FullPatchInputPro
|
|||
|
||||
function PatchHelper() {
|
||||
const [find, setFind] = React.useState<string>("");
|
||||
const [parsedFind, setParsedFind] = React.useState<string | RegExp>("");
|
||||
const [match, setMatch] = React.useState<string>("");
|
||||
const [replacement, setReplacement] = React.useState<string | ReplaceFn>("");
|
||||
|
||||
|
@ -281,34 +291,46 @@ function PatchHelper() {
|
|||
|
||||
const [module, setModule] = React.useState<[number, Function]>();
|
||||
const [findError, setFindError] = React.useState<string>();
|
||||
const [matchError, setMatchError] = React.useState<string>();
|
||||
|
||||
const code = React.useMemo(() => {
|
||||
return `
|
||||
{
|
||||
find: ${JSON.stringify(find)},
|
||||
find: ${parsedFind instanceof RegExp ? parsedFind.toString() : JSON.stringify(parsedFind)},
|
||||
replacement: {
|
||||
match: /${match.replace(/(?<!\\)\//g, "\\/")}/,
|
||||
replace: ${typeof replacement === "function" ? replacement.toString() : JSON.stringify(replacement)}
|
||||
}
|
||||
}
|
||||
`.trim();
|
||||
}, [find, match, replacement]);
|
||||
}, [parsedFind, match, replacement]);
|
||||
|
||||
function onFindChange(v: string) {
|
||||
setFindError(void 0);
|
||||
setFind(v);
|
||||
if (v.length) {
|
||||
findCandidates({ find: v, setModule, setError: setFindError });
|
||||
|
||||
try {
|
||||
let parsedFind = v as string | RegExp;
|
||||
if (/^\/.+?\/$/.test(v)) parsedFind = new RegExp(v.slice(1, -1));
|
||||
|
||||
setFindError(void 0);
|
||||
setParsedFind(parsedFind);
|
||||
|
||||
if (v.length) {
|
||||
findCandidates({ find: parsedFind, setModule, setError: setFindError });
|
||||
}
|
||||
} catch (e: any) {
|
||||
setFindError((e as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
function onMatchChange(v: string) {
|
||||
setMatch(v);
|
||||
|
||||
try {
|
||||
new RegExp(v);
|
||||
setFindError(void 0);
|
||||
setMatch(v);
|
||||
setMatchError(void 0);
|
||||
} catch (e: any) {
|
||||
setFindError((e as Error).message);
|
||||
setMatchError((e as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,11 +339,12 @@ function PatchHelper() {
|
|||
<Forms.FormTitle>full patch</Forms.FormTitle>
|
||||
<FullPatchInput
|
||||
setFind={onFindChange}
|
||||
setParsedFind={setParsedFind}
|
||||
setMatch={onMatchChange}
|
||||
setReplacement={setReplacement}
|
||||
/>
|
||||
|
||||
<Forms.FormTitle>find</Forms.FormTitle>
|
||||
<Forms.FormTitle className={Margins.top8}>find</Forms.FormTitle>
|
||||
<TextInput
|
||||
type="text"
|
||||
value={find}
|
||||
|
@ -329,19 +352,15 @@ function PatchHelper() {
|
|||
error={findError}
|
||||
/>
|
||||
|
||||
<Forms.FormTitle>match</Forms.FormTitle>
|
||||
<CheckedTextInput
|
||||
<Forms.FormTitle className={Margins.top8}>match</Forms.FormTitle>
|
||||
<TextInput
|
||||
type="text"
|
||||
value={match}
|
||||
onChange={onMatchChange}
|
||||
validate={v => {
|
||||
try {
|
||||
return (new RegExp(v), true);
|
||||
} catch (e) {
|
||||
return (e as Error).message;
|
||||
}
|
||||
}}
|
||||
error={matchError}
|
||||
/>
|
||||
|
||||
<div className={Margins.top8} />
|
||||
<ReplacementInput
|
||||
replacement={replacement}
|
||||
setReplacement={setReplacement}
|
||||
|
@ -352,7 +371,7 @@ function PatchHelper() {
|
|||
{module && (
|
||||
<ReplacementComponent
|
||||
module={module}
|
||||
match={new RegExp(match)}
|
||||
match={match}
|
||||
replacement={replacement}
|
||||
setReplacementError={setReplacementError}
|
||||
/>
|
||||
|
|
18
src/components/index.ts
Normal file
18
src/components/index.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export * from "./Badge";
|
||||
export * from "./CheckedTextInput";
|
||||
export * from "./CodeBlock";
|
||||
export * from "./DonateButton";
|
||||
export { default as ErrorBoundary } from "./ErrorBoundary";
|
||||
export * from "./ErrorCard";
|
||||
export * from "./ExpandableHeader";
|
||||
export * from "./Flex";
|
||||
export * from "./Heart";
|
||||
export * from "./Icons";
|
||||
export * from "./Link";
|
||||
export * from "./Switch";
|
|
@ -35,6 +35,7 @@ export const ALLOWED_PROTOCOLS = [
|
|||
"steam:",
|
||||
"spotify:",
|
||||
"com.epicgames.launcher:",
|
||||
"tidal:"
|
||||
];
|
||||
|
||||
export const IS_VANILLA = /* @__PURE__ */ process.argv.includes("--vanilla");
|
||||
|
|
2
src/modules.d.ts
vendored
2
src/modules.d.ts
vendored
|
@ -20,7 +20,7 @@
|
|||
/// <reference types="standalone-electron-types"/>
|
||||
|
||||
declare module "~plugins" {
|
||||
const plugins: Record<string, import("@utils/types").Plugin>;
|
||||
const plugins: Record<string, import("./utils/types").Plugin>;
|
||||
export default plugins;
|
||||
}
|
||||
|
||||
|
|
|
@ -26,55 +26,62 @@ import UpdaterTab from "@components/VencordSettings/UpdaterTab";
|
|||
import VencordTab from "@components/VencordSettings/VencordTab";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { React } from "@webpack/common";
|
||||
import { i18n, React } from "@webpack/common";
|
||||
|
||||
import gitHash from "~git-hash";
|
||||
|
||||
type SectionType = "HEADER" | "DIVIDER" | "CUSTOM";
|
||||
type SectionTypes = Record<SectionType, SectionType>;
|
||||
|
||||
export default definePlugin({
|
||||
name: "Settings",
|
||||
description: "Adds Settings UI and debug info",
|
||||
authors: [Devs.Ven, Devs.Megu],
|
||||
required: true,
|
||||
|
||||
patches: [{
|
||||
find: ".versionHash",
|
||||
replacement: [
|
||||
{
|
||||
match: /\[\(0,.{1,3}\.jsxs?\)\((.{1,10}),(\{[^{}}]+\{.{0,20}.versionHash,.+?\})\)," "/,
|
||||
replace: (m, component, props) => {
|
||||
props = props.replace(/children:\[.+\]/, "");
|
||||
return `${m},$self.makeInfoElements(${component}, ${props})`;
|
||||
patches: [
|
||||
{
|
||||
find: ".versionHash",
|
||||
replacement: [
|
||||
{
|
||||
match: /\[\(0,\i\.jsxs?\)\((.{1,10}),(\{[^{}}]+\{.{0,20}.versionHash,.+?\})\)," "/,
|
||||
replace: (m, component, props) => {
|
||||
props = props.replace(/children:\[.+\]/, "");
|
||||
return `${m},$self.makeInfoElements(${component}, ${props})`;
|
||||
}
|
||||
},
|
||||
{
|
||||
match: /copyValue:\i\.join\(" "\)/,
|
||||
replace: "$& + $self.getInfoString()"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "Messages.ACTIVITY_SETTINGS",
|
||||
replacement: {
|
||||
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
|
||||
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "useDefaultUserSettingsSections:function",
|
||||
replacement: {
|
||||
match: /(?<=useDefaultUserSettingsSections:function\(\){return )(\i)\}/,
|
||||
replace: "$self.wrapSettingsHook($1)}"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
||||
replacement: {
|
||||
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.UserSettingsSections\).*?(\i)\.default\.open\()/,
|
||||
replace: "$2.default.open($1);return;"
|
||||
}
|
||||
]
|
||||
}, {
|
||||
find: "Messages.ACTIVITY_SETTINGS",
|
||||
replacement: {
|
||||
get match() {
|
||||
switch (Settings.plugins.Settings.settingsLocation) {
|
||||
case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS/;
|
||||
case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS/;
|
||||
case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS/;
|
||||
case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/;
|
||||
case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/;
|
||||
case "aboveActivity":
|
||||
default:
|
||||
return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS/;
|
||||
}
|
||||
},
|
||||
replace: "...$self.makeSettingsCategories($1),$&"
|
||||
}
|
||||
}, {
|
||||
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
||||
replacement: {
|
||||
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.UserSettingsSections\).*?(\i)\.default\.open\()/,
|
||||
replace: "$2.default.open($1);return;"
|
||||
}
|
||||
}],
|
||||
],
|
||||
|
||||
customSections: [] as ((SectionTypes: Record<string, unknown>) => any)[],
|
||||
customSections: [] as ((SectionTypes: SectionTypes) => any)[],
|
||||
|
||||
makeSettingsCategories(SectionTypes: Record<string, unknown>) {
|
||||
makeSettingsCategories(SectionTypes: SectionTypes) {
|
||||
return [
|
||||
{
|
||||
section: SectionTypes.HEADER,
|
||||
|
@ -130,19 +137,63 @@ export default definePlugin({
|
|||
].filter(Boolean);
|
||||
},
|
||||
|
||||
isRightSpot({ header, settings }: { header?: string; settings?: string[]; }) {
|
||||
const firstChild = settings?.[0];
|
||||
// lowest two elements... sanity backup
|
||||
if (firstChild === "LOGOUT" || firstChild === "SOCIAL_LINKS") return true;
|
||||
|
||||
const { settingsLocation } = Settings.plugins.Settings;
|
||||
|
||||
if (settingsLocation === "bottom") return firstChild === "LOGOUT";
|
||||
if (settingsLocation === "belowActivity") return firstChild === "CHANGELOG";
|
||||
|
||||
if (!header) return;
|
||||
|
||||
const names = {
|
||||
top: i18n.Messages.USER_SETTINGS,
|
||||
aboveNitro: i18n.Messages.BILLING_SETTINGS,
|
||||
belowNitro: i18n.Messages.APP_SETTINGS,
|
||||
aboveActivity: i18n.Messages.ACTIVITY_SETTINGS
|
||||
};
|
||||
return header === names[settingsLocation];
|
||||
},
|
||||
|
||||
patchedSettings: new WeakSet(),
|
||||
|
||||
addSettings(elements: any[], element: { header?: string; settings: string[]; }, sectionTypes: SectionTypes) {
|
||||
if (this.patchedSettings.has(elements) || !this.isRightSpot(element)) return;
|
||||
|
||||
this.patchedSettings.add(elements);
|
||||
|
||||
elements.push(...this.makeSettingsCategories(sectionTypes));
|
||||
},
|
||||
|
||||
wrapSettingsHook(originalHook: (...args: any[]) => Record<string, unknown>[]) {
|
||||
return (...args: any[]) => {
|
||||
const elements = originalHook(...args);
|
||||
if (!this.patchedSettings.has(elements))
|
||||
elements.unshift(...this.makeSettingsCategories({
|
||||
HEADER: "HEADER",
|
||||
DIVIDER: "DIVIDER",
|
||||
CUSTOM: "CUSTOM"
|
||||
}));
|
||||
|
||||
return elements;
|
||||
};
|
||||
},
|
||||
|
||||
options: {
|
||||
settingsLocation: {
|
||||
type: OptionType.SELECT,
|
||||
description: "Where to put the Vencord settings section",
|
||||
options: [
|
||||
{ label: "At the very top", value: "top" },
|
||||
{ label: "Above the Nitro section", value: "aboveNitro" },
|
||||
{ label: "Above the Nitro section", value: "aboveNitro", default: true },
|
||||
{ label: "Below the Nitro section", value: "belowNitro" },
|
||||
{ label: "Above Activity Settings", value: "aboveActivity", default: true },
|
||||
{ label: "Above Activity Settings", value: "aboveActivity" },
|
||||
{ label: "Below Activity Settings", value: "belowActivity" },
|
||||
{ label: "At the very bottom", value: "bottom" },
|
||||
],
|
||||
restartNeeded: true
|
||||
]
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -169,15 +220,24 @@ export default definePlugin({
|
|||
return "";
|
||||
},
|
||||
|
||||
makeInfoElements(Component: React.ComponentType<React.PropsWithChildren>, props: React.PropsWithChildren) {
|
||||
getInfoRows() {
|
||||
const { electronVersion, chromiumVersion, additionalInfo } = this;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Component {...props}>Vencord {gitHash}{additionalInfo}</Component>
|
||||
{electronVersion && <Component {...props}>Electron {electronVersion}</Component>}
|
||||
{chromiumVersion && <Component {...props}>Chromium {chromiumVersion}</Component>}
|
||||
</>
|
||||
const rows = [`Vencord ${gitHash}${additionalInfo}`];
|
||||
|
||||
if (electronVersion) rows.push(`Electron ${electronVersion}`);
|
||||
if (chromiumVersion) rows.push(`Chromium ${chromiumVersion}`);
|
||||
|
||||
return rows;
|
||||
},
|
||||
|
||||
getInfoString() {
|
||||
return "\n" + this.getInfoRows().join("\n");
|
||||
},
|
||||
|
||||
makeInfoElements(Component: React.ComponentType<React.PropsWithChildren>, props: React.PropsWithChildren) {
|
||||
return this.getInfoRows().map((text, i) =>
|
||||
<Component key={i} {...props}>{text}</Component>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
5
src/plugins/automodContext/README.md
Normal file
5
src/plugins/automodContext/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# AutomodContext
|
||||
|
||||
Allows you to jump to the messages surrounding an automod hit
|
||||
|
||||
![Visualization](https://github.com/Vendicated/Vencord/assets/61953774/d13740c8-2062-4553-b975-82fd3d6cc08b)
|
73
src/plugins/automodContext/index.tsx
Normal file
73
src/plugins/automodContext/index.tsx
Normal file
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 { findByPropsLazy } from "@webpack";
|
||||
import { Button, ChannelStore, Text } from "@webpack/common";
|
||||
|
||||
const { selectChannel } = findByPropsLazy("selectChannel", "selectVoiceChannel");
|
||||
|
||||
function jumpToMessage(channelId: string, messageId: string) {
|
||||
const guildId = ChannelStore.getChannel(channelId)?.guild_id;
|
||||
|
||||
selectChannel({
|
||||
guildId,
|
||||
channelId,
|
||||
messageId,
|
||||
jumpType: "INSTANT"
|
||||
});
|
||||
}
|
||||
|
||||
function findChannelId(message: any): string | null {
|
||||
const { embeds: [embed] } = message;
|
||||
const channelField = embed.fields.find(({ rawName }) => rawName === "channel_id");
|
||||
|
||||
if (!channelField) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return channelField.rawValue;
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "AutomodContext",
|
||||
description: "Allows you to jump to the messages surrounding an automod hit.",
|
||||
authors: [Devs.JohnyTheCarrot],
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: ".Messages.GUILD_AUTOMOD_REPORT_ISSUES",
|
||||
replacement: {
|
||||
match: /\.Messages\.ACTIONS.+?}\)(?=,(\(0.{0,40}\.dot.*?}\)),)/,
|
||||
replace: (m, dot) => `${m},${dot},$self.renderJumpButton({message:arguments[0].message})`
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
renderJumpButton: ErrorBoundary.wrap(({ message }: { message: any; }) => {
|
||||
const channelId = findChannelId(message);
|
||||
|
||||
if (!channelId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
style={{ padding: "2px 8px" }}
|
||||
look={Button.Looks.LINK}
|
||||
size={Button.Sizes.SMALL}
|
||||
color={Button.Colors.LINK}
|
||||
onClick={() => jumpToMessage(channelId, message.id)}
|
||||
>
|
||||
<Text color="text-link" variant="text-xs/normal">
|
||||
Jump to Surrounding
|
||||
</Text>
|
||||
</Button>
|
||||
);
|
||||
}, { noop: true })
|
||||
});
|
|
@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings";
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { FluxDispatcher, i18n } from "@webpack/common";
|
||||
import { FluxDispatcher, i18n, useMemo } from "@webpack/common";
|
||||
|
||||
import FolderSideBar from "./FolderSideBar";
|
||||
|
||||
|
@ -117,8 +117,8 @@ export default definePlugin({
|
|||
},
|
||||
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
|
||||
{
|
||||
match: /(useStateFromStoresArray\).{0,25}let \i)=(\i\.\i.getGuildsTree\(\))/,
|
||||
replace: (_, rest, guildsTree) => `${rest}=$self.getGuildTree(!!arguments[0].isBetterFolders,${guildsTree},arguments[0].betterFoldersExpandedIds)`
|
||||
match: /\[(\i)\]=(\(0,\i\.useStateFromStoresArray\).{0,40}getGuildsTree\(\).+?}\))(?=,)/,
|
||||
replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0].isBetterFolders,betterFoldersOriginalTree,arguments[0].betterFoldersExpandedIds)`
|
||||
},
|
||||
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
|
||||
{
|
||||
|
@ -252,19 +252,21 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
|
||||
getGuildTree(isBetterFolders: boolean, oldTree: any, expandedFolderIds?: Set<any>) {
|
||||
if (!isBetterFolders || expandedFolderIds == null) return oldTree;
|
||||
getGuildTree(isBetterFolders: boolean, originalTree: any, expandedFolderIds?: Set<any>) {
|
||||
return useMemo(() => {
|
||||
if (!isBetterFolders || expandedFolderIds == null) return originalTree;
|
||||
|
||||
const newTree = new GuildsTree();
|
||||
// Children is every folder and guild which is not in a folder, this filters out only the expanded folders
|
||||
newTree.root.children = oldTree.root.children.filter(guildOrFolder => expandedFolderIds.has(guildOrFolder.id));
|
||||
// Nodes is every folder and guild, even if it's in a folder, this filters out only the expanded folders and guilds inside them
|
||||
newTree.nodes = Object.fromEntries(
|
||||
Object.entries(oldTree.nodes)
|
||||
.filter(([_, guildOrFolder]: any[]) => expandedFolderIds.has(guildOrFolder.id) || expandedFolderIds.has(guildOrFolder.parentId))
|
||||
);
|
||||
const newTree = new GuildsTree();
|
||||
// Children is every folder and guild which is not in a folder, this filters out only the expanded folders
|
||||
newTree.root.children = originalTree.root.children.filter(guildOrFolder => expandedFolderIds.has(guildOrFolder.id));
|
||||
// Nodes is every folder and guild, even if it's in a folder, this filters out only the expanded folders and guilds inside them
|
||||
newTree.nodes = Object.fromEntries(
|
||||
Object.entries(originalTree.nodes)
|
||||
.filter(([_, guildOrFolder]: any[]) => expandedFolderIds.has(guildOrFolder.id) || expandedFolderIds.has(guildOrFolder.parentId))
|
||||
);
|
||||
|
||||
return newTree;
|
||||
return newTree;
|
||||
}, [isBetterFolders, originalTree, expandedFolderIds]);
|
||||
},
|
||||
|
||||
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
|
||||
|
|
|
@ -61,7 +61,7 @@ export default definePlugin({
|
|||
find: ".popularApplicationCommandIds,",
|
||||
replacement: {
|
||||
match: /lastSection:(!?\i)}\),/,
|
||||
replace: "$&$self.patchPadding($1),"
|
||||
replace: "$&$self.patchPadding({lastSection:$1}),"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -81,12 +81,10 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
|
||||
patchPadding(lastSection: any) {
|
||||
if (!lastSection) return;
|
||||
patchPadding: ErrorBoundary.wrap(({ lastSection }) => {
|
||||
if (!lastSection) return null;
|
||||
return (
|
||||
<ErrorBoundary noop>
|
||||
<div className={UserPopoutSectionCssClasses.lastSection}></div>
|
||||
</ErrorBoundary>
|
||||
<div className={UserPopoutSectionCssClasses.lastSection} ></div>
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
|
||||
import { React, RestAPI, Tooltip } from "@webpack/common";
|
||||
import { Constants, React, RestAPI, Tooltip } from "@webpack/common";
|
||||
|
||||
import { RenameButton } from "./components/RenameButton";
|
||||
import { Session, SessionInfo } from "./types";
|
||||
|
@ -168,7 +168,7 @@ export default definePlugin({
|
|||
|
||||
async checkNewSessions() {
|
||||
const data = await RestAPI.get({
|
||||
url: "/auth/sessions"
|
||||
url: Constants.Endpoints.AUTH_SESSIONS
|
||||
});
|
||||
|
||||
for (const session of data.body.user_sessions) {
|
||||
|
|
|
@ -119,7 +119,7 @@ export default definePlugin({
|
|||
{ // Settings cog context menu
|
||||
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
||||
replacement: {
|
||||
match: /\(0,\i.default\)\(\)(?=\.filter\(\i=>\{let\{section:\i\}=)/,
|
||||
match: /\(0,\i.useDefaultUserSettingsSections\)\(\)(?=\.filter\(\i=>\{let\{section:\i\}=)/,
|
||||
replace: "$self.wrapMenu($&)"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,22 +24,20 @@ import { closeAllModals } from "@utils/modal";
|
|||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { maybePromptToUpdate } from "@utils/updater";
|
||||
import { filters, findBulk, proxyLazyWebpack } from "@webpack";
|
||||
import { FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common";
|
||||
import { DraftType, FluxDispatcher, NavigationRouter, SelectedChannelStore } from "@webpack/common";
|
||||
|
||||
const CrashHandlerLogger = new Logger("CrashHandler");
|
||||
const { ModalStack, DraftManager, DraftType, closeExpressionPicker } = proxyLazyWebpack(() => {
|
||||
const modules = findBulk(
|
||||
|
||||
const { ModalStack, DraftManager, closeExpressionPicker } = proxyLazyWebpack(() => {
|
||||
const [ModalStack, DraftManager, ExpressionManager] = findBulk(
|
||||
filters.byProps("pushLazy", "popAll"),
|
||||
filters.byProps("clearDraft", "saveDraft"),
|
||||
filters.byProps("DraftType"),
|
||||
filters.byProps("closeExpressionPicker", "openExpressionPicker"),
|
||||
);
|
||||
filters.byProps("closeExpressionPicker", "openExpressionPicker"),);
|
||||
|
||||
return {
|
||||
ModalStack: modules[0],
|
||||
DraftManager: modules[1],
|
||||
DraftType: modules[2]?.DraftType,
|
||||
closeExpressionPicker: modules[3]?.closeExpressionPicker,
|
||||
ModalStack,
|
||||
DraftManager,
|
||||
closeExpressionPicker: ExpressionManager?.closeExpressionPicker,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -137,8 +135,11 @@ export default definePlugin({
|
|||
try {
|
||||
const channelId = SelectedChannelStore.getChannelId();
|
||||
|
||||
DraftManager.clearDraft(channelId, DraftType.ChannelMessage);
|
||||
DraftManager.clearDraft(channelId, DraftType.FirstThreadMessage);
|
||||
for (const key in DraftType) {
|
||||
if (!Number.isNaN(Number(key))) continue;
|
||||
|
||||
DraftManager.clearDraft(channelId, DraftType[key]);
|
||||
}
|
||||
} catch (err) {
|
||||
CrashHandlerLogger.debug("Failed to clear drafts.", err);
|
||||
}
|
||||
|
|
68
src/plugins/ctrlEnterSend/index.ts
Normal file
68
src/plugins/ctrlEnterSend/index.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "CtrlEnterSend",
|
||||
authors: [Devs.UlyssesZhan],
|
||||
description: "Use Ctrl+Enter to send messages (customizable)",
|
||||
settings: definePluginSettings({
|
||||
submitRule: {
|
||||
description: "The way to send a message",
|
||||
type: OptionType.SELECT,
|
||||
options: [
|
||||
{
|
||||
label: "Ctrl+Enter (Enter or Shift+Enter for new line)",
|
||||
value: "ctrl+enter"
|
||||
},
|
||||
{
|
||||
label: "Shift+Enter (Enter for new line)",
|
||||
value: "shift+enter"
|
||||
},
|
||||
{
|
||||
label: "Enter (Shift+Enter for new line; Discord default)",
|
||||
value: "enter"
|
||||
}
|
||||
],
|
||||
default: "ctrl+enter"
|
||||
},
|
||||
sendMessageInTheMiddleOfACodeBlock: {
|
||||
description: "Whether to send a message in the middle of a code block",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
}
|
||||
}),
|
||||
patches: [
|
||||
{
|
||||
find: "KeyboardKeys.ENTER&&(!",
|
||||
replacement: {
|
||||
match: /(?<=(\i)\.which===\i\.KeyboardKeys.ENTER&&).{0,100}(\(0,\i\.hasOpenPlainTextCodeBlock\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/,
|
||||
replace: "$self.shouldSubmit($1, $2)"
|
||||
}
|
||||
}
|
||||
],
|
||||
shouldSubmit(event: KeyboardEvent, codeblock: boolean): boolean {
|
||||
let result = false;
|
||||
switch (this.settings.store.submitRule) {
|
||||
case "shift+enter":
|
||||
result = event.shiftKey;
|
||||
break;
|
||||
case "ctrl+enter":
|
||||
result = event.ctrlKey;
|
||||
break;
|
||||
case "enter":
|
||||
result = !event.shiftKey && !event.ctrlKey;
|
||||
break;
|
||||
}
|
||||
if (!this.settings.store.sendMessageInTheMiddleOfACodeBlock) {
|
||||
result &&= !codeblock;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
});
|
5
src/plugins/customidle/README.md
Normal file
5
src/plugins/customidle/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# CustomIdle
|
||||
|
||||
Lets you change the time until your status gets automatically set to idle. You can also prevent idling altogether.
|
||||
|
||||
![Plugin Configuration](https://github.com/Vendicated/Vencord/assets/45801973/4e5259b2-18e0-42e5-b69f-efc672ce1e0b)
|
94
src/plugins/customidle/index.ts
Normal file
94
src/plugins/customidle/index.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Notices } from "@api/index";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { makeRange } from "@components/PluginSettings/components";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { FluxDispatcher } from "@webpack/common";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
idleTimeout: {
|
||||
description: "Minutes before Discord goes idle (0 to disable auto-idle)",
|
||||
type: OptionType.SLIDER,
|
||||
markers: makeRange(0, 60, 5),
|
||||
default: 10,
|
||||
stickToMarkers: false,
|
||||
restartNeeded: true // Because of the setInterval patch
|
||||
},
|
||||
remainInIdle: {
|
||||
description: "When you come back to Discord, remain idle until you confirm you want to go online",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "CustomIdle",
|
||||
description: "Allows you to set the time before Discord goes idle (or disable auto-idle)",
|
||||
authors: [Devs.newwares],
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
find: "IDLE_DURATION:function(){return",
|
||||
replacement: {
|
||||
match: /(IDLE_DURATION:function\(\){return )\i/,
|
||||
replace: "$1$self.getIdleTimeout()"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: 'type:"IDLE",idle:',
|
||||
replacement: [
|
||||
{
|
||||
match: /Math\.min\((\i\.AfkTimeout\.getSetting\(\)\*\i\.default\.Millis\.SECOND),\i\.IDLE_DURATION\)/,
|
||||
replace: "$1" // Decouple idle from afk (phone notifications will remain at user setting or 10 min maximum)
|
||||
},
|
||||
{
|
||||
match: /\i\.default\.dispatch\({type:"IDLE",idle:!1}\)/,
|
||||
replace: "$self.handleOnline()"
|
||||
},
|
||||
{
|
||||
match: /(setInterval\(\i,\.25\*)\i\.IDLE_DURATION/,
|
||||
replace: "$1$self.getIntervalDelay()" // For web installs
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
getIntervalDelay() {
|
||||
return Math.min(6e5, this.getIdleTimeout());
|
||||
},
|
||||
|
||||
handleOnline() {
|
||||
if (!settings.store.remainInIdle) {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "IDLE",
|
||||
idle: false
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const backOnlineMessage = "Welcome back! Click the button to go online. Click the X to stay idle until reload.";
|
||||
if (
|
||||
Notices.currentNotice?.[1] === backOnlineMessage ||
|
||||
Notices.noticesQueue.some(([, noticeMessage]) => noticeMessage === backOnlineMessage)
|
||||
) return;
|
||||
|
||||
Notices.showNotice(backOnlineMessage, "Exit idle", () => {
|
||||
Notices.popNotice();
|
||||
FluxDispatcher.dispatch({
|
||||
type: "IDLE",
|
||||
idle: false
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
getIdleTimeout() { // milliseconds, default is 6e5
|
||||
const { idleTimeout } = settings.store;
|
||||
return idleTimeout === 0 ? Infinity : idleTimeout * 60000;
|
||||
}
|
||||
});
|
|
@ -9,7 +9,6 @@ import { proxyLazy } from "@utils/lazy";
|
|||
import { Logger } from "@utils/Logger";
|
||||
import { openModal } from "@utils/modal";
|
||||
import { OAuth2AuthorizeModal, showToast, Toasts, UserStore, zustandCreate, zustandPersist } from "@webpack/common";
|
||||
import type { StateStorage } from "zustand/middleware";
|
||||
|
||||
import { AUTHORIZE_URL, CLIENT_ID } from "../constants";
|
||||
|
||||
|
@ -23,7 +22,7 @@ interface AuthorizationState {
|
|||
isAuthorized: () => boolean;
|
||||
}
|
||||
|
||||
const indexedDBStorage: StateStorage = {
|
||||
const indexedDBStorage = {
|
||||
async getItem(name: string): Promise<string | null> {
|
||||
return DataStore.get(name).then(v => v ?? null);
|
||||
},
|
||||
|
@ -36,9 +35,9 @@ const indexedDBStorage: StateStorage = {
|
|||
};
|
||||
|
||||
// TODO: Move switching accounts subscription inside the store?
|
||||
export const useAuthorizationStore = proxyLazy(() => zustandCreate<AuthorizationState>(
|
||||
export const useAuthorizationStore = proxyLazy(() => zustandCreate(
|
||||
zustandPersist(
|
||||
(set, get) => ({
|
||||
(set: any, get: any) => ({
|
||||
token: null,
|
||||
tokens: {},
|
||||
init: () => { set({ token: get().tokens[UserStore.getCurrentUser().id] ?? null }); },
|
||||
|
@ -91,7 +90,7 @@ export const useAuthorizationStore = proxyLazy(() => zustandCreate<Authorization
|
|||
));
|
||||
},
|
||||
isAuthorized: () => !!get().token,
|
||||
}),
|
||||
} as AuthorizationState),
|
||||
{
|
||||
name: "decor-auth",
|
||||
getStorage: () => indexedDBStorage,
|
||||
|
|
|
@ -21,7 +21,7 @@ interface UserDecorationsState {
|
|||
clear: () => void;
|
||||
}
|
||||
|
||||
export const useCurrentUserDecorationsStore = proxyLazy(() => zustandCreate<UserDecorationsState>((set, get) => ({
|
||||
export const useCurrentUserDecorationsStore = proxyLazy(() => zustandCreate((set: any, get: any) => ({
|
||||
decorations: [],
|
||||
selectedDecoration: null,
|
||||
async fetch() {
|
||||
|
@ -53,4 +53,4 @@ export const useCurrentUserDecorationsStore = proxyLazy(() => zustandCreate<User
|
|||
useUsersDecorationsStore.getState().set(UserStore.getCurrentUser().id, decoration ? decorationToAsset(decoration) : null);
|
||||
},
|
||||
clear: () => set({ decorations: [], selectedDecoration: null })
|
||||
})));
|
||||
} as UserDecorationsState)));
|
||||
|
|
|
@ -30,7 +30,7 @@ interface UsersDecorationsState {
|
|||
set: (userId: string, decoration: string | null) => void;
|
||||
}
|
||||
|
||||
export const useUsersDecorationsStore = proxyLazy(() => zustandCreate<UsersDecorationsState>((set, get) => ({
|
||||
export const useUsersDecorationsStore = proxyLazy(() => zustandCreate((set: any, get: any) => ({
|
||||
usersDecorations: new Map<string, UserDecorationData>(),
|
||||
fetchQueue: new Set(),
|
||||
bulkFetch: debounce(async () => {
|
||||
|
@ -40,7 +40,7 @@ export const useUsersDecorationsStore = proxyLazy(() => zustandCreate<UsersDecor
|
|||
|
||||
set({ fetchQueue: new Set() });
|
||||
|
||||
const fetchIds = Array.from(fetchQueue);
|
||||
const fetchIds = [...fetchQueue];
|
||||
const fetchedUsersDecorations = await getUsersDecorations(fetchIds);
|
||||
|
||||
const newUsersDecorations = new Map(usersDecorations);
|
||||
|
@ -92,7 +92,7 @@ export const useUsersDecorationsStore = proxyLazy(() => zustandCreate<UsersDecor
|
|||
newUsersDecorations.set(userId, { asset: decoration, fetchedAt: new Date() });
|
||||
set({ usersDecorations: newUsersDecorations });
|
||||
}
|
||||
})));
|
||||
} as UsersDecorationsState)));
|
||||
|
||||
export function useUserDecorAvatarDecoration(user?: User): AvatarDecoration | null | undefined {
|
||||
const [decorAvatarDecoration, setDecorAvatarDecoration] = useState<string | null>(user ? useUsersDecorationsStore.getState().getAsset(user.id) ?? null : null);
|
||||
|
|
|
@ -15,7 +15,7 @@ import { openChangeDecorationModal } from "../modals/ChangeDecorationModal";
|
|||
|
||||
const CustomizationSection = findByCodeLazy(".customizationSectionBackground");
|
||||
|
||||
interface DecorSectionProps {
|
||||
export interface DecorSectionProps {
|
||||
hideTitle?: boolean;
|
||||
hideDivider?: boolean;
|
||||
noMargin?: boolean;
|
||||
|
|
|
@ -24,7 +24,7 @@ import { Margins } from "@utils/margins";
|
|||
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
|
||||
import { Constants, EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
|
||||
import { Promisable } from "type-fest";
|
||||
|
||||
const StickersStore = findStoreLazy("StickersStore");
|
||||
|
@ -64,7 +64,7 @@ async function fetchSticker(id: string) {
|
|||
if (cached) return cached;
|
||||
|
||||
const { body } = await RestAPI.get({
|
||||
url: `/stickers/${id}`
|
||||
url: Constants.Endpoints.STICKER(id)
|
||||
});
|
||||
|
||||
FluxDispatcher.dispatch({
|
||||
|
@ -83,7 +83,7 @@ async function cloneSticker(guildId: string, sticker: Sticker) {
|
|||
data.append("file", await fetchBlob(getUrl(sticker)));
|
||||
|
||||
const { body } = await RestAPI.post({
|
||||
url: `/guilds/${guildId}/stickers`,
|
||||
url: Constants.Endpoints.GUILD_STICKER_PACKS(guildId),
|
||||
body: data,
|
||||
});
|
||||
|
||||
|
@ -322,8 +322,9 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, props) =
|
|||
switch (favoriteableType) {
|
||||
case "emoji":
|
||||
const match = props.message.content.match(RegExp(`<a?:(\\w+)(?:~\\d+)?:${favoriteableId}>|https://cdn\\.discordapp\\.com/emojis/${favoriteableId}\\.`));
|
||||
if (!match) return;
|
||||
const name = match[1] ?? "FakeNitroEmoji";
|
||||
const reaction = props.message.reactions.find(reaction => reaction.emoji.id === favoriteableId);
|
||||
if (!match && !reaction) return;
|
||||
const name = (match && match[1]) ?? reaction?.emoji.name ?? "FakeNitroEmoji";
|
||||
|
||||
return buildMenuItem("Emoji", () => ({
|
||||
id: favoriteableId,
|
||||
|
|
|
@ -24,13 +24,12 @@ import { getCurrentGuild } from "@utils/discord";
|
|||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
|
||||
import { Alerts, ChannelStore, EmojiStore, FluxDispatcher, Forms, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
|
||||
import type { CustomEmoji } from "@webpack/types";
|
||||
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
|
||||
import type { Emoji } from "@webpack/types";
|
||||
import type { Message } from "discord-types/general";
|
||||
import { applyPalette, GIFEncoder, quantize } from "gifenc";
|
||||
import type { ReactElement, ReactNode } from "react";
|
||||
|
||||
const DRAFT_TYPE = 0;
|
||||
const StickerStore = findStoreLazy("StickersStore") as {
|
||||
getPremiumPacks(): StickerPack[];
|
||||
getAllGuildStickers(): Map<string, Sticker[]>;
|
||||
|
@ -39,6 +38,7 @@ const StickerStore = findStoreLazy("StickersStore") as {
|
|||
|
||||
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
|
||||
const ProtoUtils = findByPropsLazy("BINARY_READ_OPTIONS");
|
||||
const RoleSubscriptionEmojiUtils = findByPropsLazy("isUnusableRoleSubscriptionEmoji");
|
||||
|
||||
function searchProtoClassField(localName: string, protoClass: any) {
|
||||
const field = protoClass?.fields?.find((field: any) => field.localName === localName);
|
||||
|
@ -54,16 +54,22 @@ const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoCla
|
|||
|
||||
|
||||
const enum EmojiIntentions {
|
||||
REACTION = 0,
|
||||
STATUS = 1,
|
||||
COMMUNITY_CONTENT = 2,
|
||||
CHAT = 3,
|
||||
GUILD_STICKER_RELATED_EMOJI = 4,
|
||||
GUILD_ROLE_BENEFIT_EMOJI = 5,
|
||||
COMMUNITY_CONTENT_ONLY = 6,
|
||||
SOUNDBOARD = 7
|
||||
REACTION,
|
||||
STATUS,
|
||||
COMMUNITY_CONTENT,
|
||||
CHAT,
|
||||
GUILD_STICKER_RELATED_EMOJI,
|
||||
GUILD_ROLE_BENEFIT_EMOJI,
|
||||
COMMUNITY_CONTENT_ONLY,
|
||||
SOUNDBOARD,
|
||||
VOICE_CHANNEL_TOPIC,
|
||||
GIFT,
|
||||
AUTO_SUGGESTION,
|
||||
POLLS
|
||||
}
|
||||
|
||||
const IS_BYPASSEABLE_INTENTION = `[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`;
|
||||
|
||||
const enum StickerType {
|
||||
PNG = 1,
|
||||
APNG = 2,
|
||||
|
@ -198,37 +204,43 @@ export default definePlugin({
|
|||
patches: [
|
||||
{
|
||||
find: ".PREMIUM_LOCKED;",
|
||||
group: true,
|
||||
predicate: () => settings.store.enableEmojiBypass,
|
||||
replacement: [
|
||||
{
|
||||
// Create a variable for the intention of listing the emoji
|
||||
match: /(?<=,intention:(\i).+?;)/,
|
||||
replace: (_, intention) => `let fakeNitroIntention=${intention};`
|
||||
// Create a variable for the intention of using the emoji
|
||||
match: /(?<=\.USE_EXTERNAL_EMOJIS.+?;)(?<=intention:(\i).+?)/,
|
||||
replace: (_, intention) => `const fakeNitroIntention=${intention};`
|
||||
},
|
||||
{
|
||||
// Send the intention of listing the emoji to the nitro permission check functions
|
||||
match: /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g,
|
||||
replace: '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0'
|
||||
// Disallow the emoji for external if the intention doesn't allow it
|
||||
match: /&&!\i&&!\i(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/,
|
||||
replace: m => `${m}&&!${IS_BYPASSEABLE_INTENTION}`
|
||||
},
|
||||
{
|
||||
// Disallow the emoji if the intention doesn't allow it
|
||||
match: /(&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/,
|
||||
replace: (_, rest, canUseExternal) => `${rest}(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))`
|
||||
// Disallow the emoji for unavailable if the intention doesn't allow it
|
||||
match: /!\i\.available(?=\)return \i\.\i\.GUILD_SUBSCRIPTION_UNAVAILABLE;)/,
|
||||
replace: m => `${m}&&!${IS_BYPASSEABLE_INTENTION}`
|
||||
},
|
||||
{
|
||||
// Make the emoji always available if the intention allows it
|
||||
match: /if\(!\i\.available/,
|
||||
replace: m => `${m}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention))`
|
||||
// Disallow the emoji for premium locked if the intention doesn't allow it
|
||||
match: /!\i\.\i\.canUseEmojisEverywhere\(\i\)/,
|
||||
replace: m => `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
|
||||
},
|
||||
{
|
||||
// Allow animated emojis to be used if the intention allows it
|
||||
match: /(?<=\|\|)\i\.\i\.canUseAnimatedEmojis\(\i\)/,
|
||||
replace: m => `(${m}||${IS_BYPASSEABLE_INTENTION})`
|
||||
}
|
||||
]
|
||||
},
|
||||
// Allow emojis and animated emojis to be sent everywhere
|
||||
// Allows the usage of subscription-locked emojis
|
||||
{
|
||||
find: "canUseAnimatedEmojis:function",
|
||||
predicate: () => settings.store.enableEmojiBypass,
|
||||
find: "isUnusableRoleSubscriptionEmoji:function",
|
||||
replacement: {
|
||||
match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))(?=})/g,
|
||||
replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`
|
||||
match: /isUnusableRoleSubscriptionEmoji:function/,
|
||||
// Replace the original export with a func that always returns false and alias the original
|
||||
replace: "isUnusableRoleSubscriptionEmoji:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function"
|
||||
}
|
||||
},
|
||||
// Allow stickers to be sent everywhere
|
||||
|
@ -242,10 +254,10 @@ export default definePlugin({
|
|||
},
|
||||
// Make stickers always available
|
||||
{
|
||||
find: "\"SENDABLE\"",
|
||||
find: '"SENDABLE"',
|
||||
predicate: () => settings.store.enableStickerBypass,
|
||||
replacement: {
|
||||
match: /(\w+)\.available\?/,
|
||||
match: /\i\.available\?/,
|
||||
replace: "true?"
|
||||
}
|
||||
},
|
||||
|
@ -797,13 +809,16 @@ export default definePlugin({
|
|||
gif.finish();
|
||||
|
||||
const file = new File([gif.bytesView()], `${stickerId}.gif`, { type: "image/gif" });
|
||||
UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), DRAFT_TYPE);
|
||||
UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), DraftType.ChannelMessage);
|
||||
},
|
||||
|
||||
canUseEmote(e: CustomEmoji, channelId: string) {
|
||||
if (e.require_colons === false) return true;
|
||||
canUseEmote(e: Emoji, channelId: string) {
|
||||
if (e.type === "UNICODE") return true;
|
||||
if (e.available === false) return false;
|
||||
|
||||
const isUnusableRoleSubEmoji = RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji;
|
||||
if (isUnusableRoleSubEmoji(e, this.guildId)) return false;
|
||||
|
||||
if (this.canUseEmotes)
|
||||
return e.guildId === this.guildId || hasExternalEmojiPerms(channelId);
|
||||
else
|
||||
|
|
|
@ -20,7 +20,7 @@ import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption,
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { RestAPI, UserStore } from "@webpack/common";
|
||||
import { Constants, RestAPI, UserStore } from "@webpack/common";
|
||||
|
||||
const FriendInvites = findByPropsLazy("createFriendInvite");
|
||||
const { uuid4 } = findByPropsLazy("uuid4");
|
||||
|
@ -58,7 +58,7 @@ export default definePlugin({
|
|||
if (uses === 1) {
|
||||
const random = uuid4();
|
||||
const { body: { invite_suggestions } } = await RestAPI.post({
|
||||
url: "/friend-finder/find-friends",
|
||||
url: Constants.Endpoints.FRIEND_FINDER,
|
||||
body: {
|
||||
modified_contacts: {
|
||||
[random]: [1, "", ""]
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getCurrentChannel } from "@utils/discord";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { classes } from "@utils/misc";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Heading, React, RelationshipStore, Text } from "@webpack/common";
|
||||
|
@ -22,6 +24,7 @@ export default definePlugin({
|
|||
description: "Shows when you became friends with someone in the user popout",
|
||||
authors: [Devs.Elvyra],
|
||||
patches: [
|
||||
// User popup
|
||||
{
|
||||
find: ".AnalyticsSections.USER_PROFILE}",
|
||||
replacement: {
|
||||
|
@ -29,16 +32,34 @@ export default definePlugin({
|
|||
replace: "$&,$self.friendsSince({ userId: $1 })"
|
||||
}
|
||||
},
|
||||
// User DMs "User Profile" popup in the right
|
||||
{
|
||||
find: ".UserPopoutUpsellSource.PROFILE_PANEL,",
|
||||
replacement: {
|
||||
match: /\i.default,\{userId:(\i)}\)/,
|
||||
replace: "$&,$self.friendsSince({ userId: $1 })"
|
||||
}
|
||||
},
|
||||
// User Profile Modal
|
||||
{
|
||||
find: ".userInfoSectionHeader,",
|
||||
replacement: {
|
||||
match: /(\.Messages\.USER_PROFILE_MEMBER_SINCE.+?userId:(.+?),textClassName:)(\i\.userInfoText)}\)/,
|
||||
replace: (_, rest, userId, textClassName) => `${rest}!$self.getFriendSince(${userId}) ? ${textClassName} : void 0 }), $self.friendsSince({ userId: ${userId}, textClassName: ${textClassName} })`
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
friendsSince: ErrorBoundary.wrap(({ userId }: { userId: string; }) => {
|
||||
getFriendSince(userId: string) {
|
||||
try {
|
||||
return RelationshipStore.getSince(userId);
|
||||
} catch (err) {
|
||||
new Logger("FriendsSince").error(err);
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
friendsSince: ErrorBoundary.wrap(({ userId, textClassName }: { userId: string; textClassName?: string; }) => {
|
||||
const friendsSince = RelationshipStore.getSince(userId);
|
||||
if (!friendsSince) return null;
|
||||
|
||||
|
@ -61,7 +82,7 @@ export default definePlugin({
|
|||
<path d="M3 5v-.75C3 3.56 3.56 3 4.25 3s1.24.56 1.33 1.25C6.12 8.65 9.46 12 13 12h1a8 8 0 0 1 8 8 2 2 0 0 1-2 2 .21.21 0 0 1-.2-.15 7.65 7.65 0 0 0-1.32-2.3c-.15-.2-.42-.06-.39.17l.25 2c.02.15-.1.28-.25.28H9a2 2 0 0 1-2-2v-2.22c0-1.57-.67-3.05-1.53-4.37A15.85 15.85 0 0 1 3 5Z" />
|
||||
</svg>
|
||||
)}
|
||||
<Text variant="text-sm/normal" className={clydeMoreInfo.body}>
|
||||
<Text variant="text-sm/normal" className={classes(clydeMoreInfo.body, textClassName)}>
|
||||
{getCreatedAtDate(friendsSince, locale.getLocale())}
|
||||
</Text>
|
||||
</div>
|
||||
|
@ -69,4 +90,3 @@ export default definePlugin({
|
|||
);
|
||||
}, { noop: true })
|
||||
});
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import { registerCommand, unregisterCommand } from "@api/Commands";
|
|||
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { canonicalizeFind } from "@utils/patches";
|
||||
import { Patch, Plugin, StartAt } from "@utils/types";
|
||||
import { FluxDispatcher } from "@webpack/common";
|
||||
import { FluxEvents } from "@webpack/types";
|
||||
|
@ -83,8 +84,12 @@ for (const p of pluginsValues) {
|
|||
if (p.patches && isPluginEnabled(p.name)) {
|
||||
for (const patch of p.patches) {
|
||||
patch.plugin = p.name;
|
||||
if (!Array.isArray(patch.replacement))
|
||||
|
||||
canonicalizeFind(patch);
|
||||
if (!Array.isArray(patch.replacement)) {
|
||||
patch.replacement = [patch.replacement];
|
||||
}
|
||||
|
||||
patches.push(patch);
|
||||
}
|
||||
}
|
||||
|
@ -165,13 +170,14 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
|||
}
|
||||
try {
|
||||
p.start();
|
||||
p.started = true;
|
||||
} catch (e) {
|
||||
logger.error(`Failed to start ${name}\n`, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
p.started = true;
|
||||
|
||||
if (commands?.length) {
|
||||
logger.debug("Registering commands of plugin", name);
|
||||
for (const cmd of commands) {
|
||||
|
@ -201,6 +207,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
|||
|
||||
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
|
||||
const { name, commands, flux, contextMenus } = p;
|
||||
|
||||
if (p.stop) {
|
||||
logger.info("Stopping plugin", name);
|
||||
if (!p.started) {
|
||||
|
@ -209,13 +216,14 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
|
|||
}
|
||||
try {
|
||||
p.stop();
|
||||
p.started = false;
|
||||
} catch (e) {
|
||||
logger.error(`Failed to stop ${name}\n`, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
p.started = false;
|
||||
|
||||
if (commands?.length) {
|
||||
logger.debug("Unregistering commands of plugin", name);
|
||||
for (const cmd of commands) {
|
||||
|
|
|
@ -23,7 +23,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
|||
import { Devs } from "@utils/constants";
|
||||
import { getStegCloak } from "@utils/dependencies";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { ChannelStore, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common";
|
||||
import { ChannelStore, Constants, FluxDispatcher, RestAPI, Tooltip } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
import { buildDecModal } from "./components/DecryptionModal";
|
||||
|
@ -153,7 +153,7 @@ export default definePlugin({
|
|||
// Gets the Embed of a Link
|
||||
async getEmbed(url: URL): Promise<Object | {}> {
|
||||
const { body } = await RestAPI.post({
|
||||
url: "/unfurler/embed-urls",
|
||||
url: Constants.Endpoints.UNFURL_EMBED_URLS,
|
||||
body: {
|
||||
urls: [url]
|
||||
}
|
||||
|
|
|
@ -114,6 +114,11 @@ const settings = definePluginSettings({
|
|||
type: OptionType.BOOLEAN,
|
||||
default: false,
|
||||
},
|
||||
shareSong: {
|
||||
description: "show link to song on last.fm",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
},
|
||||
hideWithSpotify: {
|
||||
description: "hide last.fm presence if spotify is running",
|
||||
type: OptionType.BOOLEAN,
|
||||
|
@ -295,12 +300,7 @@ export default definePlugin({
|
|||
large_text: trackData.album || undefined,
|
||||
};
|
||||
|
||||
const buttons: ActivityButton[] = [
|
||||
{
|
||||
label: "View Song",
|
||||
url: trackData.url,
|
||||
},
|
||||
];
|
||||
const buttons: ActivityButton[] = [];
|
||||
|
||||
if (settings.store.shareUsername)
|
||||
buttons.push({
|
||||
|
@ -308,6 +308,12 @@ export default definePlugin({
|
|||
url: `https://www.last.fm/user/${settings.store.username}`,
|
||||
});
|
||||
|
||||
if (settings.store.shareSong)
|
||||
buttons.push({
|
||||
label: "View Song",
|
||||
url: trackData.url,
|
||||
});
|
||||
|
||||
const statusName = (() => {
|
||||
switch (settings.store.nameFormat) {
|
||||
case NameFormat.ArtistFirst:
|
||||
|
@ -333,7 +339,7 @@ export default definePlugin({
|
|||
state: trackData.artist,
|
||||
assets,
|
||||
|
||||
buttons: buttons.map(v => v.label),
|
||||
buttons: buttons.length ? buttons.map(v => v.label) : undefined,
|
||||
metadata: {
|
||||
button_urls: buttons.map(v => v.url),
|
||||
},
|
||||
|
|
|
@ -22,21 +22,35 @@ interface Diff {
|
|||
hours: number,
|
||||
minutes: number,
|
||||
seconds: number;
|
||||
milliseconds: number;
|
||||
}
|
||||
|
||||
const DISCORD_KT_DELAY = 1471228928;
|
||||
const HiddenVisually = findExportedComponentLazy("HiddenVisually");
|
||||
|
||||
export default definePlugin({
|
||||
name: "MessageLatency",
|
||||
description: "Displays an indicator for messages that took ≥n seconds to send",
|
||||
authors: [Devs.arHSM],
|
||||
|
||||
settings: definePluginSettings({
|
||||
latency: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "Threshold in seconds for latency indicator",
|
||||
default: 2
|
||||
},
|
||||
detectDiscordKotlin: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Detect old Discord Android clients",
|
||||
default: true
|
||||
},
|
||||
showMillis: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Show milliseconds",
|
||||
default: false
|
||||
}
|
||||
}),
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "showCommunicationDisabledStyles",
|
||||
|
@ -46,12 +60,14 @@ export default definePlugin({
|
|||
}
|
||||
}
|
||||
],
|
||||
stringDelta(delta: number) {
|
||||
|
||||
stringDelta(delta: number, showMillis: boolean) {
|
||||
const diff: Diff = {
|
||||
days: Math.round(delta / (60 * 60 * 24)),
|
||||
hours: Math.round((delta / (60 * 60)) % 24),
|
||||
minutes: Math.round((delta / (60)) % 60),
|
||||
seconds: Math.round(delta % 60),
|
||||
days: Math.round(delta / (60 * 60 * 24 * 1000)),
|
||||
hours: Math.round((delta / (60 * 60 * 1000)) % 24),
|
||||
minutes: Math.round((delta / (60 * 1000)) % 60),
|
||||
seconds: Math.round(delta / 1000 % 60),
|
||||
milliseconds: Math.round(delta % 1000)
|
||||
};
|
||||
|
||||
const str = (k: DiffKey) => diff[k] > 0 ? `${diff[k]} ${diff[k] > 1 ? k : k.substring(0, k.length - 1)}` : null;
|
||||
|
@ -63,7 +79,7 @@ export default definePlugin({
|
|||
return prev + (
|
||||
isNonNullish(s)
|
||||
? (prev !== ""
|
||||
? k === "seconds"
|
||||
? (showMillis ? k === "milliseconds" : k === "seconds")
|
||||
? " and "
|
||||
: " "
|
||||
: "") + s
|
||||
|
@ -71,49 +87,68 @@ export default definePlugin({
|
|||
);
|
||||
}, "");
|
||||
|
||||
return [ts || "0 seconds", diff.days === 17 && diff.hours === 1] as const;
|
||||
return ts || "0 seconds";
|
||||
},
|
||||
|
||||
latencyTooltipData(message: Message) {
|
||||
const { latency, detectDiscordKotlin, showMillis } = this.settings.store;
|
||||
const { id, nonce } = message;
|
||||
|
||||
// Message wasn't received through gateway
|
||||
if (!isNonNullish(nonce)) return null;
|
||||
|
||||
const delta = Math.round((SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce)) / 1000);
|
||||
let isDiscordKotlin = false;
|
||||
let delta = SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce); // milliseconds
|
||||
if (!showMillis) {
|
||||
delta = Math.round(delta / 1000) * 1000;
|
||||
}
|
||||
|
||||
// Old Discord Android clients have a delay of around 17 days
|
||||
// This is a workaround for that
|
||||
if (-delta >= DISCORD_KT_DELAY - 86400000) { // One day of padding for good measure
|
||||
isDiscordKotlin = detectDiscordKotlin;
|
||||
delta += DISCORD_KT_DELAY;
|
||||
}
|
||||
|
||||
// Thanks dziurwa (I hate you)
|
||||
// This is when the user's clock is ahead
|
||||
// Can't do anything if the clock is behind
|
||||
const abs = Math.abs(delta);
|
||||
const ahead = abs !== delta;
|
||||
const latencyMillis = latency * 1000;
|
||||
|
||||
const [stringDelta, isSuspectedKotlinDiscord] = this.stringDelta(abs);
|
||||
const isKotlinDiscord = ahead && isSuspectedKotlinDiscord;
|
||||
const stringDelta = abs >= latencyMillis ? this.stringDelta(abs, showMillis) : null;
|
||||
|
||||
// Also thanks dziurwa
|
||||
// 2 minutes
|
||||
const TROLL_LIMIT = 2 * 60;
|
||||
const { latency } = this.settings.store;
|
||||
const TROLL_LIMIT = 2 * 60 * 1000;
|
||||
|
||||
const fill: Fill = isKotlinDiscord
|
||||
const fill: Fill = isDiscordKotlin
|
||||
? ["status-positive", "status-positive", "text-muted"]
|
||||
: delta >= TROLL_LIMIT || ahead
|
||||
? ["text-muted", "text-muted", "text-muted"]
|
||||
: delta >= (latency * 2)
|
||||
: delta >= (latencyMillis * 2)
|
||||
? ["status-danger", "text-muted", "text-muted"]
|
||||
: ["status-warning", "status-warning", "text-muted"];
|
||||
|
||||
return abs >= latency ? { delta: stringDelta, ahead, fill, isKotlinDiscord } : null;
|
||||
return (abs >= latencyMillis || isDiscordKotlin) ? { delta: stringDelta, ahead, fill, isDiscordKotlin } : null;
|
||||
},
|
||||
|
||||
Tooltip() {
|
||||
return ErrorBoundary.wrap(({ message }: { message: Message; }) => {
|
||||
|
||||
const d = this.latencyTooltipData(message);
|
||||
|
||||
if (!isNonNullish(d)) return null;
|
||||
|
||||
let text: string;
|
||||
if (!d.delta) {
|
||||
text = "User is suspected to be on an old Discord Android client";
|
||||
} else {
|
||||
text = (d.ahead ? `This user's clock is ${d.delta} ahead.` : `This message was sent with a delay of ${d.delta}.`) + (d.isDiscordKotlin ? " User is suspected to be on an old Discord Android client." : "");
|
||||
}
|
||||
|
||||
return <Tooltip
|
||||
text={d.ahead ? `This user's clock is ${d.delta} ahead. ${d.isKotlinDiscord ? "User is suspected to be on an old mobile client" : ""}` : `This message was sent with a delay of ${d.delta}.`}
|
||||
text={text}
|
||||
position="top"
|
||||
>
|
||||
{
|
||||
|
@ -126,8 +161,9 @@ export default definePlugin({
|
|||
</Tooltip>;
|
||||
});
|
||||
},
|
||||
|
||||
Icon({ delta, fill, props }: {
|
||||
delta: string;
|
||||
delta: string | null;
|
||||
fill: Fill,
|
||||
props: {
|
||||
onClick(): void;
|
||||
|
@ -147,7 +183,7 @@ export default definePlugin({
|
|||
role="img"
|
||||
fill="none"
|
||||
style={{ marginRight: "8px", verticalAlign: -1 }}
|
||||
aria-label={delta}
|
||||
aria-label={delta ?? "Old Discord Android client"}
|
||||
aria-hidden="false"
|
||||
{...props}
|
||||
>
|
||||
|
|
|
@ -27,6 +27,7 @@ import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
|||
import {
|
||||
Button,
|
||||
ChannelStore,
|
||||
Constants,
|
||||
FluxDispatcher,
|
||||
GuildStore,
|
||||
IconUtils,
|
||||
|
@ -132,7 +133,7 @@ async function fetchMessage(channelID: string, messageID: string) {
|
|||
messageCache.set(messageID, { fetched: false });
|
||||
|
||||
const res = await RestAPI.get({
|
||||
url: `/channels/${channelID}/messages`,
|
||||
url: Constants.Endpoints.MESSAGES(channelID),
|
||||
query: {
|
||||
limit: 1,
|
||||
around: messageID
|
||||
|
@ -226,10 +227,8 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
|
|||
|
||||
const accessories = [] as (JSX.Element | null)[];
|
||||
|
||||
let match = null as RegExpMatchArray | null;
|
||||
while ((match = messageLinkRegex.exec(message.content!)) !== null) {
|
||||
const [_, channelID, messageID] = match;
|
||||
if (embeddedBy.includes(messageID)) {
|
||||
for (const [_, channelID, messageID] of message.content!.matchAll(messageLinkRegex)) {
|
||||
if (embeddedBy.includes(messageID) || embeddedBy.length > 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -377,9 +376,6 @@ export default definePlugin({
|
|||
if (!messageLinkRegex.test(props.message.content))
|
||||
return null;
|
||||
|
||||
// need to reset the regex because it's global
|
||||
messageLinkRegex.lastIndex = 0;
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<MessageEmbedAccessory
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
.messagelogger-deleted {
|
||||
background-color: rgba(240 71 71 / 15%) !important;
|
||||
background-color: hsla(var(--red-430-hsl, 0 85% 61%) / 15%) !important;
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
/* Message content highlighting */
|
||||
.messagelogger-deleted [class*="contents"] > :is(div, h1, h2, h3, p) {
|
||||
color: #f04747 !important;
|
||||
color: var(--status-danger, #f04747) !important;
|
||||
}
|
||||
|
||||
/* Bot "thinking" text highlighting */
|
||||
.messagelogger-deleted [class*="colorStandard"] {
|
||||
color: #f04747 !important;
|
||||
color: var(--status-danger, #f04747) !important;
|
||||
}
|
||||
|
||||
/* Embed highlighting */
|
||||
.messagelogger-deleted article :is(div, span, h1, h2, h3, p) {
|
||||
color: #f04747 !important;
|
||||
color: var(--status-danger, #f04747) !important;
|
||||
}
|
||||
|
||||
.messagelogger-deleted a {
|
||||
color: #be3535 !important;
|
||||
color: var(--red-460, #be3535) !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@ interface TagSettings {
|
|||
MODERATOR_STAFF: TagSetting,
|
||||
MODERATOR: TagSetting,
|
||||
VOICE_MODERATOR: TagSetting,
|
||||
TRIAL_MODERATOR: TagSetting,
|
||||
[k: string]: TagSetting;
|
||||
}
|
||||
|
||||
|
@ -93,6 +94,11 @@ const tags: Tag[] = [
|
|||
displayName: "VC Mod",
|
||||
description: "Can manage voice chats",
|
||||
permissions: ["MOVE_MEMBERS", "MUTE_MEMBERS", "DEAFEN_MEMBERS"]
|
||||
}, {
|
||||
name: "CHAT_MODERATOR",
|
||||
displayName: "Chat Mod",
|
||||
description: "Can timeout people",
|
||||
permissions: ["MODERATE_MEMBERS"]
|
||||
}
|
||||
];
|
||||
const defaultSettings = Object.fromEntries(
|
||||
|
@ -263,34 +269,14 @@ export default definePlugin({
|
|||
],
|
||||
|
||||
start() {
|
||||
if (settings.store.tagSettings) return;
|
||||
// @ts-ignore
|
||||
if (!settings.store.visibility_WEBHOOK) settings.store.tagSettings = defaultSettings;
|
||||
else {
|
||||
const newSettings = { ...defaultSettings };
|
||||
Object.entries(Vencord.PlainSettings.plugins.MoreUserTags).forEach(([name, value]) => {
|
||||
const [setting, tag] = name.split("_");
|
||||
if (setting === "visibility") {
|
||||
switch (value) {
|
||||
case "always":
|
||||
// its the default
|
||||
break;
|
||||
case "chat":
|
||||
newSettings[tag].showInNotChat = false;
|
||||
break;
|
||||
case "not-chat":
|
||||
newSettings[tag].showInChat = false;
|
||||
break;
|
||||
case "never":
|
||||
newSettings[tag].showInChat = false;
|
||||
newSettings[tag].showInNotChat = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
settings.store.tagSettings = newSettings;
|
||||
delete Vencord.Settings.plugins.MoreUserTags[name];
|
||||
});
|
||||
}
|
||||
settings.store.tagSettings ??= defaultSettings;
|
||||
|
||||
// newly added field might be missing from old users
|
||||
settings.store.tagSettings.CHAT_MODERATOR ??= {
|
||||
text: "Chat Mod",
|
||||
showInChat: true,
|
||||
showInNotChat: true
|
||||
};
|
||||
},
|
||||
|
||||
getPermissions(user: User, channel: Channel): string[] {
|
||||
|
@ -368,6 +354,15 @@ export default definePlugin({
|
|||
if (location === "chat" && !settings.tagSettings[tag.name].showInChat) continue;
|
||||
if (location === "not-chat" && !settings.tagSettings[tag.name].showInNotChat) continue;
|
||||
|
||||
// If the owner tag is disabled, and the user is the owner of the guild,
|
||||
// avoid adding other tags because the owner will always match the condition for them
|
||||
if (
|
||||
tag.name !== "OWNER" &&
|
||||
GuildStore.getGuild(channel?.guild_id)?.ownerId === user.id &&
|
||||
(location === "chat" && !settings.tagSettings.OWNER.showInChat) ||
|
||||
(location === "not-chat" && !settings.tagSettings.OWNER.showInNotChat)
|
||||
) continue;
|
||||
|
||||
if (
|
||||
tag.permissions?.some(perm => perms.includes(perm)) ||
|
||||
(tag.condition?.(message!, user, channel))
|
||||
|
|
|
@ -56,12 +56,12 @@ export default definePlugin({
|
|||
find: ".UserProfileSections.USER_INFO_CONNECTIONS:",
|
||||
replacement: {
|
||||
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
|
||||
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs($1,$2);"
|
||||
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
renderMutualGDMs: ErrorBoundary.wrap((user: User, onClose: () => void) => {
|
||||
renderMutualGDMs: ErrorBoundary.wrap(({ user, onClose }: { user: User, onClose: () => void; }) => {
|
||||
const entries = ChannelStore.getSortedPrivateChannels().filter(c => c.isGroupDM() && c.recipients.includes(user.id)).map(c => (
|
||||
<Clickable
|
||||
className={ProfileListClasses.listRow}
|
||||
|
|
5
src/plugins/noDefaultHangStatus/README.md
Normal file
5
src/plugins/noDefaultHangStatus/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# NoDefaultHangStatus
|
||||
|
||||
Disable the default hang status when joining voice channels
|
||||
|
||||
![Visualization](https://github.com/Vendicated/Vencord/assets/24937357/329a9742-236f-48f7-94ff-c3510eca505a)
|
24
src/plugins/noDefaultHangStatus/index.ts
Normal file
24
src/plugins/noDefaultHangStatus/index.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "NoDefaultHangStatus",
|
||||
description: "Disable the default hang status when joining voice channels",
|
||||
authors: [Devs.D3SOX],
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "HangStatusTypes.CHILLING)",
|
||||
replacement: {
|
||||
match: /{enableHangStatus:(\i),/,
|
||||
replace: "{_enableHangStatus:$1=false,"
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
50
src/plugins/noServerEmojis/index.ts
Normal file
50
src/plugins/noServerEmojis/index.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
shownEmojis: {
|
||||
description: "The types of emojis to show in the autocomplete menu.",
|
||||
type: OptionType.SELECT,
|
||||
default: "onlyUnicode",
|
||||
options: [
|
||||
{ label: "Only unicode emojis", value: "onlyUnicode" },
|
||||
{ label: "Unicode emojis and server emojis from current server", value: "currentServer" },
|
||||
{ label: "Unicode emojis and all server emojis (Discord default)", value: "all" }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "NoServerEmojis",
|
||||
authors: [Devs.UlyssesZhan],
|
||||
description: "Do not show server emojis in the autocomplete menu.",
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
find: "}searchWithoutFetchingLatest(",
|
||||
replacement: {
|
||||
match: /searchWithoutFetchingLatest.{20,300}get\((\i).{10,40}?reduce\(\((\i),(\i)\)=>\{/,
|
||||
replace: "$& if ($self.shouldSkip($1, $3)) return $2;"
|
||||
}
|
||||
}
|
||||
],
|
||||
shouldSkip(guildId: string, emoji: any) {
|
||||
if (emoji.type !== "GUILD_EMOJI") {
|
||||
return false;
|
||||
}
|
||||
if (settings.store.shownEmojis === "onlyUnicode") {
|
||||
return true;
|
||||
}
|
||||
if (settings.store.shownEmojis === "currentServer") {
|
||||
return emoji.guildId !== guildId;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
|
@ -26,6 +26,7 @@ const ShortUrlMatcher = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
|
|||
const SpotifyMatcher = /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user|episode)\/(.+)(?:\?.+?)?$/;
|
||||
const SteamMatcher = /^https:\/\/(steamcommunity\.com|(?:help|store)\.steampowered\.com)\/.+$/;
|
||||
const EpicMatcher = /^https:\/\/store\.epicgames\.com\/(.+)$/;
|
||||
const TidalMatcher = /^https:\/\/tidal\.com\/browse\/(track|album|artist|playlist|user|video|mix)\/(.+)(?:\?.+?)?$/;
|
||||
|
||||
const settings = definePluginSettings({
|
||||
spotify: {
|
||||
|
@ -42,6 +43,11 @@ const settings = definePluginSettings({
|
|||
type: OptionType.BOOLEAN,
|
||||
description: "Open Epic Games links in the Epic Games Launcher",
|
||||
default: true,
|
||||
},
|
||||
tidal: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Open Tidal links in the Tidal app",
|
||||
default: true,
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -49,7 +55,7 @@ const Native = VencordNative.pluginHelpers.OpenInApp as PluginNative<typeof impo
|
|||
|
||||
export default definePlugin({
|
||||
name: "OpenInApp",
|
||||
description: "Open Spotify, Steam and Epic Games URLs in their respective apps instead of your browser",
|
||||
description: "Open Spotify, Tidal, Steam and Epic Games URLs in their respective apps instead of your browser",
|
||||
authors: [Devs.Ven],
|
||||
settings,
|
||||
|
||||
|
@ -127,6 +133,19 @@ export default definePlugin({
|
|||
return true;
|
||||
}
|
||||
|
||||
tidal: {
|
||||
if (!settings.store.tidal) break tidal;
|
||||
|
||||
const match = TidalMatcher.exec(url);
|
||||
if (!match) break tidal;
|
||||
|
||||
const [, type, id] = match;
|
||||
VencordNative.native.openExternal(`tidal://${type}/${id}`);
|
||||
|
||||
event?.preventDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
// in case short url didn't end up being something we can handle
|
||||
if (event?.defaultPrevented) {
|
||||
window.open(url, "_blank");
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { FluxDispatcher } from "@webpack/common";
|
||||
|
@ -41,8 +41,9 @@ const settings = definePluginSettings({
|
|||
},
|
||||
});
|
||||
|
||||
migratePluginSettings("PartyMode", "Party mode 🎉");
|
||||
export default definePlugin({
|
||||
name: "Party mode 🎉",
|
||||
name: "PartyMode",
|
||||
description: "Allows you to use party mode cause the party never ends ✨",
|
||||
authors: [Devs.UwUDev],
|
||||
settings,
|
||||
|
|
|
@ -20,10 +20,26 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
|||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { GuildStore, i18n, RestAPI } from "@webpack/common";
|
||||
import { Constants, GuildStore, i18n, RestAPI } from "@webpack/common";
|
||||
|
||||
const { InvitesDisabledExperiment } = findByPropsLazy("InvitesDisabledExperiment");
|
||||
|
||||
function showDisableInvites(guildId: string) {
|
||||
// Once the experiment is removed, this should keep working
|
||||
const { enableInvitesDisabled } = InvitesDisabledExperiment?.getCurrentConfig?.({ guildId }) ?? { enableInvitesDisabled: true };
|
||||
// @ts-ignore
|
||||
return enableInvitesDisabled && !GuildStore.getGuild(guildId).hasFeature("INVITES_DISABLED");
|
||||
}
|
||||
|
||||
function disableInvites(guildId: string) {
|
||||
const guild = GuildStore.getGuild(guildId);
|
||||
const features = [...guild.features, "INVITES_DISABLED"];
|
||||
RestAPI.patch({
|
||||
url: Constants.Endpoints.GUILD(guildId),
|
||||
body: { features },
|
||||
});
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "PauseInvitesForever",
|
||||
tags: ["DisableInvitesForever"],
|
||||
|
@ -33,44 +49,29 @@ export default definePlugin({
|
|||
patches: [
|
||||
{
|
||||
find: "Messages.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION",
|
||||
replacement: [{
|
||||
match: /children:\i\.\i\.\i\.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION/,
|
||||
replace: "children: $self.renderInvitesLabel(arguments[0].guildId, setChecked)",
|
||||
},
|
||||
{
|
||||
match: /(\i\.hasDMsDisabled\)\(\i\),\[\i,(\i)\]=\i\.useState\(\i\))/,
|
||||
replace: "$1,setChecked=$2"
|
||||
}]
|
||||
group: true,
|
||||
replacement: [
|
||||
{
|
||||
match: /children:\i\.\i\.\i\.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION/,
|
||||
replace: "children: $self.renderInvitesLabel({guildId:arguments[0].guildId,setChecked})",
|
||||
},
|
||||
{
|
||||
match: /(\i\.hasDMsDisabled\)\(\i\),\[\i,(\i)\]=\i\.useState\(\i\))/,
|
||||
replace: "$1,setChecked=$2"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
showDisableInvites(guildId: string) {
|
||||
// Once the experiment is removed, this should keep working
|
||||
const { enableInvitesDisabled } = InvitesDisabledExperiment?.getCurrentConfig?.({ guildId }) ?? { enableInvitesDisabled: true };
|
||||
// @ts-ignore
|
||||
return enableInvitesDisabled && !GuildStore.getGuild(guildId).hasFeature("INVITES_DISABLED");
|
||||
},
|
||||
|
||||
disableInvites(guildId: string) {
|
||||
const guild = GuildStore.getGuild(guildId);
|
||||
const features = [...guild.features, "INVITES_DISABLED"];
|
||||
RestAPI.patch({
|
||||
url: `/guilds/${guild.id}`,
|
||||
body: { features },
|
||||
});
|
||||
},
|
||||
|
||||
renderInvitesLabel(guildId: string, setChecked: Function) {
|
||||
renderInvitesLabel: ErrorBoundary.wrap(({ guildId, setChecked }) => {
|
||||
return (
|
||||
<ErrorBoundary noop>
|
||||
<div>
|
||||
{i18n.Messages.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION}
|
||||
{this.showDisableInvites(guildId) && <a role="button" onClick={() => {
|
||||
setChecked(true);
|
||||
this.disableInvites(guildId);
|
||||
}}> Pause Indefinitely.</a>}
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
<div>
|
||||
{i18n.Messages.GUILD_INVITE_DISABLE_ACTION_SHEET_DESCRIPTION}
|
||||
{showDisableInvites(guildId) && <a role="button" onClick={() => {
|
||||
setChecked(true);
|
||||
disableInvites(guildId);
|
||||
}}> Pause Indefinitely.</a>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import ExpandableHeader from "@components/ExpandableHeader";
|
||||
import { ExpandableHeader } from "@components/ExpandableHeader";
|
||||
import { classes } from "@utils/misc";
|
||||
import { filters, findBulk, proxyLazyWebpack } from "@webpack";
|
||||
import { i18n, PermissionsBits, Text, Tooltip, useMemo, UserStore } from "@webpack/common";
|
||||
|
|
|
@ -21,10 +21,9 @@ import { Devs } from "@utils/constants";
|
|||
import { makeLazy } from "@utils/lazy";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { UploadHandler, UserUtils } from "@webpack/common";
|
||||
import { DraftType, UploadHandler, UploadManager, UserUtils } from "@webpack/common";
|
||||
import { applyPalette, GIFEncoder, quantize } from "gifenc";
|
||||
|
||||
const DRAFT_TYPE = 0;
|
||||
const DEFAULT_DELAY = 20;
|
||||
const DEFAULT_RESOLUTION = 128;
|
||||
const FRAMES = 10;
|
||||
|
@ -59,9 +58,12 @@ async function resolveImage(options: Argument[], ctx: CommandContext, noServerPf
|
|||
for (const opt of options) {
|
||||
switch (opt.name) {
|
||||
case "image":
|
||||
const upload = UploadStore.getUploads(ctx.channel.id, DRAFT_TYPE)[0];
|
||||
const upload = UploadStore.getUpload(ctx.channel.id, opt.name, DraftType.SlashCommand);
|
||||
if (upload) {
|
||||
if (!upload.isImage) throw "Upload is not an image";
|
||||
if (!upload.isImage) {
|
||||
UploadManager.clearAll(ctx.channel.id, DraftType.SlashCommand);
|
||||
throw "Upload is not an image";
|
||||
}
|
||||
return upload.item.file;
|
||||
}
|
||||
break;
|
||||
|
@ -73,10 +75,12 @@ async function resolveImage(options: Argument[], ctx: CommandContext, noServerPf
|
|||
return user.getAvatarURL(noServerPfp ? void 0 : ctx.guild?.id, 2048).replace(/\?size=\d+$/, "?size=2048");
|
||||
} catch (err) {
|
||||
console.error("[petpet] Failed to fetch user\n", err);
|
||||
UploadManager.clearAll(ctx.channel.id, DraftType.SlashCommand);
|
||||
throw "Failed to fetch user. Check the console for more info.";
|
||||
}
|
||||
}
|
||||
}
|
||||
UploadManager.clearAll(ctx.channel.id, DraftType.SlashCommand);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -130,6 +134,7 @@ export default definePlugin({
|
|||
var url = await resolveImage(opts, cmdCtx, noServerPfp);
|
||||
if (!url) throw "No Image specified!";
|
||||
} catch (err) {
|
||||
UploadManager.clearAll(cmdCtx.channel.id, DraftType.SlashCommand);
|
||||
sendBotMessage(cmdCtx.channel.id, {
|
||||
content: String(err),
|
||||
});
|
||||
|
@ -147,6 +152,8 @@ export default definePlugin({
|
|||
canvas.width = canvas.height = resolution;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
|
||||
UploadManager.clearAll(cmdCtx.channel.id, DraftType.SlashCommand);
|
||||
|
||||
for (let i = 0; i < FRAMES; i++) {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
|
@ -174,7 +181,7 @@ export default definePlugin({
|
|||
const file = new File([gif.bytesView()], "petpet.gif", { type: "image/gif" });
|
||||
// Immediately after the command finishes, Discord clears all input, including pending attachments.
|
||||
// Thus, setTimeout is needed to make this execute after Discord cleared the input
|
||||
setTimeout(() => UploadHandler.promptToUpload([file], cmdCtx.channel, DRAFT_TYPE), 10);
|
||||
setTimeout(() => UploadHandler.promptToUpload([file], cmdCtx.channel, DraftType.ChannelMessage), 10);
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
|
@ -83,7 +83,7 @@ export default definePlugin({
|
|||
// Rendering
|
||||
{
|
||||
match: /"renderRow",(\i)=>{(?<="renderDM",.+?(\i\.default),\{channel:.+?)/,
|
||||
replace: "$&if($self.isChannelIndex($1.section, $1.row))return $self.renderChannel($1.section,$1.row,$2);"
|
||||
replace: "$&if($self.isChannelIndex($1.section, $1.row))return $self.renderChannel($1.section,$1.row,$2)();"
|
||||
},
|
||||
{
|
||||
match: /"renderSection",(\i)=>{/,
|
||||
|
@ -320,25 +320,26 @@ export default definePlugin({
|
|||
</svg>
|
||||
</h2>
|
||||
);
|
||||
}),
|
||||
}, { noop: true }),
|
||||
|
||||
renderChannel(sectionIndex: number, index: number, ChannelComponent: React.ComponentType<ChannelComponentProps>) {
|
||||
const { channel, category } = this.getChannel(sectionIndex, index, this.instance.props.channels);
|
||||
return ErrorBoundary.wrap(() => {
|
||||
const { channel, category } = this.getChannel(sectionIndex, index, this.instance.props.channels);
|
||||
|
||||
if (!channel || !category) return null;
|
||||
if (this.isChannelHidden(sectionIndex, index)) return null;
|
||||
if (!channel || !category) return null;
|
||||
if (this.isChannelHidden(sectionIndex, index)) return null;
|
||||
|
||||
return (
|
||||
<ChannelComponent
|
||||
channel={channel}
|
||||
selected={this.instance.props.selectedChannelId === channel.id}
|
||||
>
|
||||
{channel.id}
|
||||
</ChannelComponent>
|
||||
);
|
||||
return (
|
||||
<ChannelComponent
|
||||
channel={channel}
|
||||
selected={this.instance.props.selectedChannelId === channel.id}
|
||||
>
|
||||
{channel.id}
|
||||
</ChannelComponent>
|
||||
);
|
||||
}, { noop: true });
|
||||
},
|
||||
|
||||
|
||||
getChannel(sectionIndex: number, index: number, channels: Record<string, Channel>) {
|
||||
const category = categories[sectionIndex - 1];
|
||||
if (!category) return { channel: null, category: null };
|
||||
|
|
|
@ -22,14 +22,34 @@ import { addServerListElement, removeServerListElement, ServerListRenderPosition
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { Button, FluxDispatcher, GuildChannelStore, GuildStore, React, ReadStateStore } from "@webpack/common";
|
||||
import { Channel } from "discord-types/general";
|
||||
|
||||
interface ThreadJoined {
|
||||
channel: Channel;
|
||||
joinTimestamp: number;
|
||||
}
|
||||
|
||||
type ThreadsJoined = Record<string, ThreadJoined>;
|
||||
type ThreadsJoinedByParent = Record<string, ThreadsJoined>;
|
||||
|
||||
interface ActiveJoinedThreadsStore {
|
||||
getActiveJoinedThreadsForGuild(guildId: string): ThreadsJoinedByParent;
|
||||
}
|
||||
|
||||
const ActiveJoinedThreadsStore: ActiveJoinedThreadsStore = findStoreLazy("ActiveJoinedThreadsStore");
|
||||
|
||||
function onClick() {
|
||||
const channels: Array<any> = [];
|
||||
|
||||
Object.values(GuildStore.getGuilds()).forEach(guild => {
|
||||
GuildChannelStore.getChannels(guild.id).SELECTABLE
|
||||
.concat(GuildChannelStore.getChannels(guild.id).VOCAL)
|
||||
GuildChannelStore.getChannels(guild.id).SELECTABLE // Array<{ channel, comparator }>
|
||||
.concat(GuildChannelStore.getChannels(guild.id).VOCAL) // Array<{ channel, comparator }>
|
||||
.concat(
|
||||
Object.values(ActiveJoinedThreadsStore.getActiveJoinedThreadsForGuild(guild.id))
|
||||
.flatMap(threadChannels => Object.values(threadChannels))
|
||||
)
|
||||
.forEach((c: { channel: { id: string; }; }) => {
|
||||
if (!ReadStateStore.hasUnread(c.channel.id)) return;
|
||||
|
||||
|
|
5
src/plugins/replaceGoogleSearch/README.md
Normal file
5
src/plugins/replaceGoogleSearch/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
# ReplaceGoogleSearch
|
||||
|
||||
Replaces the Google search with different Engines
|
||||
|
||||
![Visualization](https://github.com/Vendicated/Vencord/assets/61953774/8b8158d2-0407-4d7b-9dff-a8b9bdc1a122)
|
107
src/plugins/replaceGoogleSearch/index.tsx
Normal file
107
src/plugins/replaceGoogleSearch/index.tsx
Normal file
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Flex, Menu } from "@webpack/common";
|
||||
|
||||
const DefaultEngines = {
|
||||
Google: "https://www.google.com/search?q=",
|
||||
DuckDuckGo: "https://duckduckgo.com/",
|
||||
Bing: "https://www.bing.com/search?q=",
|
||||
Yahoo: "https://search.yahoo.com/search?p=",
|
||||
GitHub: "https://github.com/search?q=",
|
||||
Kagi: "https://kagi.com/search?q=",
|
||||
Yandex: "https://yandex.com/search/?text=",
|
||||
AOL: "https://search.aol.com/aol/search?q=",
|
||||
Baidu: "https://www.baidu.com/s?wd=",
|
||||
Wikipedia: "https://wikipedia.org/w/index.php?search=",
|
||||
} as const;
|
||||
|
||||
const settings = definePluginSettings({
|
||||
customEngineName: {
|
||||
description: "Name of the custom search engine",
|
||||
type: OptionType.STRING,
|
||||
placeholder: "Google"
|
||||
},
|
||||
customEngineURL: {
|
||||
description: "The URL of your Engine",
|
||||
type: OptionType.STRING,
|
||||
placeholder: "https://google.com/search?q="
|
||||
}
|
||||
});
|
||||
|
||||
function search(src: string, engine: string) {
|
||||
open(engine + encodeURIComponent(src), "_blank");
|
||||
}
|
||||
|
||||
function makeSearchItem(src: string) {
|
||||
let Engines = {};
|
||||
|
||||
if (settings.store.customEngineName && settings.store.customEngineURL) {
|
||||
Engines[settings.store.customEngineName] = settings.store.customEngineURL;
|
||||
}
|
||||
|
||||
Engines = { ...Engines, ...DefaultEngines };
|
||||
|
||||
return (
|
||||
<Menu.MenuItem
|
||||
label="Search Text"
|
||||
key="search-text"
|
||||
id="vc-search-text"
|
||||
>
|
||||
{Object.keys(Engines).map((engine, i) => {
|
||||
const key = "vc-search-content-" + engine;
|
||||
return (
|
||||
<Menu.MenuItem
|
||||
key={key}
|
||||
id={key}
|
||||
label={
|
||||
<Flex style={{ alignItems: "center", gap: "0.5em" }}>
|
||||
<img
|
||||
style={{
|
||||
borderRadius: "50%"
|
||||
}}
|
||||
aria-hidden="true"
|
||||
height={16}
|
||||
width={16}
|
||||
src={`https://www.google.com/s2/favicons?domain=${Engines[engine]}`}
|
||||
/>
|
||||
{engine}
|
||||
</Flex>
|
||||
}
|
||||
action={() => search(src, Engines[engine])}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Menu.MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
const messageContextMenuPatch: NavContextMenuPatchCallback = (children, _props) => {
|
||||
const selection = document.getSelection()?.toString();
|
||||
if (!selection) return;
|
||||
|
||||
const group = findGroupChildrenByChildId("search-google", children);
|
||||
if (group) {
|
||||
const idx = group.findIndex(c => c?.props?.id === "search-google");
|
||||
if (idx !== -1) group[idx] = makeSearchItem(selection);
|
||||
}
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "ReplaceGoogleSearch",
|
||||
description: "Replaces the Google search with different Engines",
|
||||
authors: [Devs.Moxxie, Devs.Ethan],
|
||||
|
||||
settings,
|
||||
|
||||
contextMenus: {
|
||||
"message": messageContextMenuPatch
|
||||
}
|
||||
});
|
|
@ -135,7 +135,7 @@ export default definePlugin({
|
|||
find: '"MessageActionCreators"',
|
||||
replacement: {
|
||||
match: /(?<=focusMessage\(\i\){.+?)(?=focus:{messageId:(\i)})/,
|
||||
replace: "before:$1,"
|
||||
replace: "after:$1,"
|
||||
}
|
||||
},
|
||||
// Force Server Home instead of Server Guide
|
||||
|
|
|
@ -20,7 +20,7 @@ import "./style.css";
|
|||
|
||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import ExpandableHeader from "@components/ExpandableHeader";
|
||||
import { ExpandableHeader } from "@components/ExpandableHeader";
|
||||
import { OpenExternalIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
|
|
@ -436,7 +436,7 @@ export default definePlugin({
|
|||
},
|
||||
},
|
||||
{
|
||||
find: ".shouldCloseDefaultModals",
|
||||
find: 'className:"channelMention",children',
|
||||
replacement: {
|
||||
// Show inside voice channel instead of trying to join them when clicking on a channel mention
|
||||
match: /(?<=getChannel\(\i\);if\(null!=(\i))(?=.{0,100}?selectVoiceChannel)/,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# ShowHiddenThings
|
||||
|
||||
Displays various moderator-only elements regardless of permissions.
|
||||
Displays various hidden & moderator-only things regardless of permissions.
|
||||
|
||||
## Features
|
||||
|
||||
|
@ -15,3 +15,5 @@ Displays various moderator-only elements regardless of permissions.
|
|||
![](https://github.com/Vendicated/Vencord/assets/47677887/3dac95dd-841c-4c15-ad87-2db7bd1e4dab)
|
||||
|
||||
- Disable filters in Server Discovery search that hide servers that don't meet discovery criteria
|
||||
|
||||
- Disable filters in Server Discovery search that hide NSFW & disallowed servers
|
||||
|
|
|
@ -41,13 +41,18 @@ const settings = definePluginSettings({
|
|||
description: "Disable filters in Server Discovery search that hide servers that don't meet discovery criteria.",
|
||||
default: true,
|
||||
},
|
||||
disableDisallowedDiscoveryFilters: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Disable filters in Server Discovery search that hide NSFW & disallowed servers.",
|
||||
default: true,
|
||||
},
|
||||
});
|
||||
|
||||
migratePluginSettings("ShowHiddenThings", "ShowTimeouts");
|
||||
export default definePlugin({
|
||||
name: "ShowHiddenThings",
|
||||
tags: ["ShowTimeouts", "ShowInvitesPaused", "ShowModView", "DisableDiscoveryFilters"],
|
||||
description: "Displays various moderator-only elements regardless of permissions.",
|
||||
description: "Displays various hidden & moderator-only things regardless of permissions.",
|
||||
authors: [Devs.Dolfies],
|
||||
patches: [
|
||||
{
|
||||
|
@ -81,6 +86,23 @@ export default definePlugin({
|
|||
match: /filters:\i\.join\(" AND "\),facets:\[/,
|
||||
replace: "facets:["
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "DiscoveryBannedSearchWords.includes",
|
||||
predicate: () => settings.store.disableDisallowedDiscoveryFilters,
|
||||
replacement: {
|
||||
match: /(?<=function\(\){)(?=.{0,130}DiscoveryBannedSearchWords\.includes)/,
|
||||
replace: "return false;"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "Endpoints.GUILD_DISCOVERY_VALID_TERM",
|
||||
predicate: () => settings.store.disableDisallowedDiscoveryFilters,
|
||||
all: true,
|
||||
replacement: {
|
||||
match: /\i\.HTTP\.get\(\{url:\i\.Endpoints\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0\}\);/g,
|
||||
replace: "Promise.resolve({ body: { valid: true } });"
|
||||
}
|
||||
}
|
||||
],
|
||||
settings,
|
||||
|
|
|
@ -9,11 +9,11 @@ import "./styles.css";
|
|||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Margins } from "@utils/margins";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findComponentLazy } from "@webpack";
|
||||
import { ChannelStore, Forms, GuildMemberStore, i18n, Text, Tooltip } from "@webpack/common";
|
||||
import { ChannelStore, GuildMemberStore, i18n, Text, Tooltip } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
import { FunctionComponent, ReactNode } from "react";
|
||||
|
||||
const CountDown = findComponentLazy(m => m.prototype?.render?.toString().includes(".MAX_AGE_NEVER"));
|
||||
|
||||
|
@ -26,7 +26,6 @@ const settings = definePluginSettings({
|
|||
displayStyle: {
|
||||
description: "How to display the timeout duration",
|
||||
type: OptionType.SELECT,
|
||||
restartNeeded: true,
|
||||
options: [
|
||||
{ label: "In the Tooltip", value: DisplayStyle.Tooltip },
|
||||
{ label: "Next to the timeout icon", value: DisplayStyle.Inline, default: true },
|
||||
|
@ -60,7 +59,7 @@ function renderTimeout(message: Message, inline: boolean) {
|
|||
export default definePlugin({
|
||||
name: "ShowTimeoutDuration",
|
||||
description: "Shows how much longer a user's timeout will last, either in the timeout icon tooltip or next to it",
|
||||
authors: [Devs.Ven],
|
||||
authors: [Devs.Ven, Devs.Sqaaakoi],
|
||||
|
||||
settings,
|
||||
|
||||
|
@ -70,33 +69,20 @@ export default definePlugin({
|
|||
replacement: [
|
||||
{
|
||||
match: /(\i)\.Tooltip,{(text:.{0,30}\.Messages\.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY)/,
|
||||
get replace() {
|
||||
if (settings.store.displayStyle === DisplayStyle.Inline)
|
||||
return "$self.TooltipWrapper,{vcProps:arguments[0],$2";
|
||||
|
||||
return "$1.Tooltip,{text:$self.renderTimeoutDuration(arguments[0])";
|
||||
}
|
||||
replace: "$self.TooltipWrapper,{message:arguments[0].message,$2"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
renderTimeoutDuration: ErrorBoundary.wrap(({ message }: { message: Message; }) => {
|
||||
return (
|
||||
<>
|
||||
<Forms.FormText>{i18n.Messages.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}</Forms.FormText>
|
||||
<Forms.FormText className={Margins.top8}>
|
||||
{renderTimeout(message, false)}
|
||||
</Forms.FormText>
|
||||
</>
|
||||
);
|
||||
}, { noop: true }),
|
||||
|
||||
TooltipWrapper: ErrorBoundary.wrap(({ vcProps: { message }, ...tooltipProps }: { vcProps: { message: Message; }; }) => {
|
||||
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)}
|
||||
/>;
|
||||
return (
|
||||
<div className="vc-std-wrapper">
|
||||
<Tooltip {...tooltipProps as any} />
|
||||
|
||||
<Tooltip text={text} children={children} />
|
||||
<Text variant="text-md/normal" color="status-danger">
|
||||
{renderTimeout(message, true)} timeout remaining
|
||||
</Text>
|
||||
|
|
|
@ -2,3 +2,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.vc-std-wrapper [class*="communicationDisabled"] {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
|
|
@ -26,10 +26,12 @@ export default definePlugin({
|
|||
description: "Adds Startup Timings to the Settings menu",
|
||||
authors: [Devs.Megu],
|
||||
patches: [{
|
||||
find: "UserSettingsSections.PAYMENT_FLOW_MODAL_TEST_PAGE,",
|
||||
find: "Messages.ACTIVITY_SETTINGS",
|
||||
replacement: {
|
||||
match: /{section:\i\.UserSettingsSections\.PAYMENT_FLOW_MODAL_TEST_PAGE/,
|
||||
replace: '{section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage},$&'
|
||||
match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/,
|
||||
replace: (_, commaOrSemi, settings, elements) => "" +
|
||||
`${commaOrSemi}${settings}?.[0]==="CHANGELOG"` +
|
||||
`&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`
|
||||
}
|
||||
}],
|
||||
StartupTimingPage
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# ThemeAttributes
|
||||
|
||||
This plugin adds data attributes to various elements inside Discord
|
||||
This plugin adds data attributes and CSS variables to various elements inside Discord
|
||||
|
||||
This allows themes to more easily theme those elements or even do things that otherwise wouldn't be possible
|
||||
|
||||
|
@ -19,3 +19,11 @@ This allows themes to more easily theme those elements or even do things that ot
|
|||
- `data-is-self` is a boolean indicating whether this is the current user's message
|
||||
|
||||
![image](https://github.com/Vendicated/Vencord/assets/45497981/34bd5053-3381-402f-82b2-9c812cc7e122)
|
||||
|
||||
## CSS Variables
|
||||
|
||||
### Avatars
|
||||
|
||||
`--avatar-url-<resolution>` contains a URL for the users avatar with the size attribute adjusted for the resolutions `128, 256, 512, 1024, 2048, 4096`.
|
||||
|
||||
![image](https://github.com/Vendicated/Vencord/assets/26598490/192ddac0-c827-472f-9933-fa99ff36f723)
|
||||
|
|
|
@ -9,10 +9,11 @@ import definePlugin from "@utils/types";
|
|||
import { UserStore } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
|
||||
export default definePlugin({
|
||||
name: "ThemeAttributes",
|
||||
description: "Adds data attributes to various elements for theming purposes",
|
||||
authors: [Devs.Ven],
|
||||
authors: [Devs.Ven, Devs.Board],
|
||||
|
||||
patches: [
|
||||
// Add data-tab-id to all tab bar items
|
||||
|
@ -32,9 +33,36 @@ export default definePlugin({
|
|||
match: /\.messageListItem(?=,"aria)/,
|
||||
replace: "$&,...$self.getMessageProps(arguments[0])"
|
||||
}
|
||||
},
|
||||
|
||||
// add --avatar-url-<resolution> css variable to avatar img elements
|
||||
// popout profiles
|
||||
{
|
||||
find: ".LABEL_WITH_ONLINE_STATUS",
|
||||
replacement: {
|
||||
match: /src:null!=\i\?(\i).{1,50}"aria-hidden":!0/,
|
||||
replace: "$&,style:$self.getAvatarStyles($1)"
|
||||
}
|
||||
},
|
||||
// chat avatars
|
||||
{
|
||||
find: "showCommunicationDisabledStyles",
|
||||
replacement: {
|
||||
match: /src:(\i),"aria-hidden":!0/,
|
||||
replace: "$&,style:$self.getAvatarStyles($1)"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
getAvatarStyles(src: string) {
|
||||
return Object.fromEntries(
|
||||
[128, 256, 512, 1024, 2048, 4096].map(size => [
|
||||
`--avatar-url-${size}`,
|
||||
`url(${src.replace(/\d+$/, String(size))})`
|
||||
])
|
||||
);
|
||||
},
|
||||
|
||||
getMessageProps(props: { message: Message; }) {
|
||||
const author = props.message?.author;
|
||||
const authorId = author?.id;
|
||||
|
|
|
@ -20,7 +20,7 @@ import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/Co
|
|||
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common";
|
||||
import { Constants, Menu, PermissionsBits, PermissionStore, RestAPI, UserStore } from "@webpack/common";
|
||||
|
||||
const EMBED_SUPPRESSED = 1 << 2;
|
||||
|
||||
|
@ -44,7 +44,7 @@ const messageContextMenuPatch: NavContextMenuPatchCallback = (children, { channe
|
|||
icon={isEmbedSuppressed ? ImageVisible : ImageInvisible}
|
||||
action={() =>
|
||||
RestAPI.patch({
|
||||
url: `/channels/${channel.id}/messages/${messageId}`,
|
||||
url: Constants.Endpoints.MESSAGE(channel.id, messageId),
|
||||
body: { flags: isEmbedSuppressed ? flags & ~EMBED_SUPPRESSED : flags | EMBED_SUPPRESSED }
|
||||
})
|
||||
}
|
||||
|
|
7
src/plugins/validReply/README.md
Normal file
7
src/plugins/validReply/README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# ValidReply
|
||||
|
||||
Fixes referenced (replied to) messages showing as "Message could not be loaded".
|
||||
|
||||
Hover the text to load the message!
|
||||
|
||||
![](https://github.com/Vendicated/Vencord/assets/45801973/d3286acf-e822-4b7f-a4e7-8ced18f581af)
|
106
src/plugins/validReply/index.ts
Normal file
106
src/plugins/validReply/index.ts
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { FluxDispatcher, RestAPI } from "@webpack/common";
|
||||
import { Message, User } from "discord-types/general";
|
||||
import { Channel } from "discord-types/general/index.js";
|
||||
|
||||
const enum ReferencedMessageState {
|
||||
Loaded,
|
||||
NotLoaded,
|
||||
Deleted
|
||||
}
|
||||
|
||||
interface Reply {
|
||||
baseAuthor: User,
|
||||
baseMessage: Message;
|
||||
channel: Channel;
|
||||
referencedMessage: { state: ReferencedMessageState; };
|
||||
compact: boolean;
|
||||
isReplyAuthorBlocked: boolean;
|
||||
}
|
||||
|
||||
const fetching = new Map<string, string>();
|
||||
let ReplyStore: any;
|
||||
|
||||
const { createMessageRecord } = findByPropsLazy("createMessageRecord");
|
||||
|
||||
export default definePlugin({
|
||||
name: "ValidReply",
|
||||
description: 'Fixes "Message could not be loaded" upon hovering over the reply',
|
||||
authors: [Devs.newwares],
|
||||
patches: [
|
||||
{
|
||||
find: "Messages.REPLY_QUOTE_MESSAGE_NOT_LOADED",
|
||||
replacement: {
|
||||
match: /Messages\.REPLY_QUOTE_MESSAGE_NOT_LOADED/,
|
||||
replace: "$&,onMouseEnter:()=>$self.fetchReply(arguments[0])"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "ReferencedMessageStore",
|
||||
replacement: {
|
||||
match: /constructor\(\)\{\i\(this,"_channelCaches",new Map\)/,
|
||||
replace: "$&;$self.setReplyStore(this);"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
setReplyStore(store: any) {
|
||||
ReplyStore = store;
|
||||
},
|
||||
|
||||
async fetchReply(reply: Reply) {
|
||||
const { channel_id: channelId, message_id: messageId } = reply.baseMessage.messageReference!;
|
||||
|
||||
if (fetching.has(messageId)) {
|
||||
return;
|
||||
}
|
||||
fetching.set(messageId, channelId);
|
||||
|
||||
RestAPI.get({
|
||||
url: `/channels/${channelId}/messages`,
|
||||
query: {
|
||||
limit: 1,
|
||||
around: messageId
|
||||
},
|
||||
retries: 2
|
||||
})
|
||||
.then(res => {
|
||||
const reply: Message | undefined = res?.body?.[0];
|
||||
if (!reply) return;
|
||||
|
||||
if (reply.id !== messageId) {
|
||||
ReplyStore.set(channelId, messageId, {
|
||||
state: ReferencedMessageState.Deleted
|
||||
});
|
||||
|
||||
FluxDispatcher.dispatch({
|
||||
type: "MESSAGE_DELETE",
|
||||
channelId: channelId,
|
||||
message: messageId
|
||||
});
|
||||
} else {
|
||||
ReplyStore.set(reply.channel_id, reply.id, {
|
||||
state: ReferencedMessageState.Loaded,
|
||||
message: createMessageRecord(reply)
|
||||
});
|
||||
|
||||
FluxDispatcher.dispatch({
|
||||
type: "MESSAGE_UPDATE",
|
||||
message: reply
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(() => { })
|
||||
.finally(() => {
|
||||
fetching.delete(messageId);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -18,28 +18,30 @@
|
|||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { isNonNullish } from "@utils/guards";
|
||||
import { sleep } from "@utils/misc";
|
||||
import { Queue } from "@utils/Queue";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Constants, FluxDispatcher, RestAPI, UserProfileStore, UserStore, useState } from "@webpack/common";
|
||||
import type { ComponentType, ReactNode } from "react";
|
||||
import { type ComponentType, type ReactNode } from "react";
|
||||
|
||||
// LYING to the type checker here
|
||||
const UserFlags = Constants.UserFlags as Record<string, number>;
|
||||
const badges: Record<string, ProfileBadge> = {
|
||||
"active_developer": { id: "active_developer", description: "Active Developer", icon: "6bdc42827a38498929a4920da12695d9", link: "https://support-dev.discord.com/hc/en-us/articles/10113997751447" },
|
||||
"bug_hunter_level_1": { id: "bug_hunter_level_1", description: "Discord Bug Hunter", icon: "2717692c7dca7289b35297368a940dd0", link: "https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs" },
|
||||
"bug_hunter_level_2": { id: "bug_hunter_level_2", description: "Discord Bug Hunter", icon: "848f79194d4be5ff5f81505cbd0ce1e6", link: "https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs" },
|
||||
"certified_moderator": { id: "certified_moderator", description: "Moderator Programs Alumni", icon: "fee1624003e2fee35cb398e125dc479b", link: "https://discord.com/safety" },
|
||||
"discord_employee": { id: "staff", description: "Discord Staff", icon: "5e74e9b61934fc1f67c65515d1f7e60d", link: "https://discord.com/company" },
|
||||
"hypesquad": { id: "hypesquad", description: "HypeSquad Events", icon: "bf01d1073931f921909045f3a39fd264", link: "https://discord.com/hypesquad" },
|
||||
"hypesquad_online_house_1": { id: "hypesquad_house_1", description: "HypeSquad Bravery", icon: "8a88d63823d8a71cd5e390baa45efa02", link: "https://discord.com/settings/hypesquad-online" },
|
||||
"hypesquad_online_house_2": { id: "hypesquad_house_2", description: "HypeSquad Brilliance", icon: "011940fd013da3f7fb926e4a1cd2e618", link: "https://discord.com/settings/hypesquad-online" },
|
||||
"hypesquad_online_house_3": { id: "hypesquad_house_3", description: "HypeSquad Balance", icon: "3aa41de486fa12454c3761e8e223442e", link: "https://discord.com/settings/hypesquad-online" },
|
||||
"partner": { id: "partner", description: "Partnered Server Owner", icon: "3f9748e53446a137a052f3454e2de41e", link: "https://discord.com/partners" },
|
||||
"premium": { id: "premium", description: "Subscriber", icon: "2ba85e8026a8614b640c2837bcdfe21b", link: "https://discord.com/settings/premium" },
|
||||
"premium_early_supporter": { id: "early_supporter", description: "Early Supporter", icon: "7060786766c9c840eb3019e725d2b358", link: "https://discord.com/settings/premium" },
|
||||
"verified_developer": { id: "verified_developer", description: "Early Verified Bot Developer", icon: "6df5892e0f35b051f8b61eace34f4967" },
|
||||
active_developer: { id: "active_developer", description: "Active Developer", icon: "6bdc42827a38498929a4920da12695d9", link: "https://support-dev.discord.com/hc/en-us/articles/10113997751447" },
|
||||
bug_hunter_level_1: { id: "bug_hunter_level_1", description: "Discord Bug Hunter", icon: "2717692c7dca7289b35297368a940dd0", link: "https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs" },
|
||||
bug_hunter_level_2: { id: "bug_hunter_level_2", description: "Discord Bug Hunter", icon: "848f79194d4be5ff5f81505cbd0ce1e6", link: "https://support.discord.com/hc/en-us/articles/360046057772-Discord-Bugs" },
|
||||
certified_moderator: { id: "certified_moderator", description: "Moderator Programs Alumni", icon: "fee1624003e2fee35cb398e125dc479b", link: "https://discord.com/safety" },
|
||||
discord_employee: { id: "staff", description: "Discord Staff", icon: "5e74e9b61934fc1f67c65515d1f7e60d", link: "https://discord.com/company" },
|
||||
get staff() { return this.discord_employee; },
|
||||
hypesquad: { id: "hypesquad", description: "HypeSquad Events", icon: "bf01d1073931f921909045f3a39fd264", link: "https://discord.com/hypesquad" },
|
||||
hypesquad_online_house_1: { id: "hypesquad_house_1", description: "HypeSquad Bravery", icon: "8a88d63823d8a71cd5e390baa45efa02", link: "https://discord.com/settings/hypesquad-online" },
|
||||
hypesquad_online_house_2: { id: "hypesquad_house_2", description: "HypeSquad Brilliance", icon: "011940fd013da3f7fb926e4a1cd2e618", link: "https://discord.com/settings/hypesquad-online" },
|
||||
hypesquad_online_house_3: { id: "hypesquad_house_3", description: "HypeSquad Balance", icon: "3aa41de486fa12454c3761e8e223442e", link: "https://discord.com/settings/hypesquad-online" },
|
||||
partner: { id: "partner", description: "Partnered Server Owner", icon: "3f9748e53446a137a052f3454e2de41e", link: "https://discord.com/partners" },
|
||||
premium: { id: "premium", description: "Subscriber", icon: "2ba85e8026a8614b640c2837bcdfe21b", link: "https://discord.com/settings/premium" },
|
||||
premium_early_supporter: { id: "early_supporter", description: "Early Supporter", icon: "7060786766c9c840eb3019e725d2b358", link: "https://discord.com/settings/premium" },
|
||||
verified_developer: { id: "verified_developer", description: "Early Verified Bot Developer", icon: "6df5892e0f35b051f8b61eace34f4967" },
|
||||
};
|
||||
|
||||
const fetching = new Set<string>();
|
||||
|
@ -73,7 +75,7 @@ async function getUser(id: string) {
|
|||
if (userObj)
|
||||
return userObj;
|
||||
|
||||
const user: any = await RestAPI.get({ url: `/users/${id}` }).then(response => {
|
||||
const user: any = await RestAPI.get({ url: Constants.Endpoints.USER(id) }).then(response => {
|
||||
FluxDispatcher.dispatch({
|
||||
type: "USER_UPDATE",
|
||||
user: response.body,
|
||||
|
@ -93,7 +95,8 @@ async function getUser(id: string) {
|
|||
userObj = UserStore.getUser(id);
|
||||
const fakeBadges: ProfileBadge[] = Object.entries(UserFlags)
|
||||
.filter(([_, flag]) => !isNaN(flag) && userObj.hasFlag(flag))
|
||||
.map(([key]) => badges[key.toLowerCase()]);
|
||||
.map(([key]) => badges[key.toLowerCase()])
|
||||
.filter(isNonNullish);
|
||||
if (user.premium_type || !user.bot && (user.banner || user.avatar?.startsWith?.("a_")))
|
||||
fakeBadges.push(badges.premium);
|
||||
|
||||
|
@ -202,6 +205,7 @@ export default definePlugin({
|
|||
return (
|
||||
<ErrorBoundary noop>
|
||||
<MentionWrapper
|
||||
key={"mention" + data.userId}
|
||||
RoleMention={RoleMention}
|
||||
UserMention={UserMention}
|
||||
data={data}
|
||||
|
|
|
@ -48,7 +48,7 @@ export default definePlugin({
|
|||
})),
|
||||
{
|
||||
// channel mentions
|
||||
find: ".shouldCloseDefaultModals",
|
||||
find: 'className:"channelMention",children',
|
||||
replacement: {
|
||||
match: /onClick:(\i)(?=,.{0,30}className:"channelMention".+?(\i)\.inContent)/,
|
||||
replace: (_, onClick, props) => ""
|
||||
|
|
|
@ -36,6 +36,10 @@ interface GuildContextProps {
|
|||
guild?: Guild;
|
||||
}
|
||||
|
||||
interface GroupDMContextProps {
|
||||
channel: Channel;
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
format: {
|
||||
type: OptionType.SELECT,
|
||||
|
@ -145,10 +149,27 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon
|
|||
));
|
||||
};
|
||||
|
||||
const GroupDMContext: NavContextMenuPatchCallback = (children, { channel }: GroupDMContextProps) => {
|
||||
if (!channel) return;
|
||||
|
||||
children.splice(-1, 0, (
|
||||
<Menu.MenuGroup>
|
||||
<Menu.MenuItem
|
||||
id="view-group-channel-icon"
|
||||
label="View Icon"
|
||||
action={() =>
|
||||
openImage(IconUtils.getChannelIconURL(channel)!)
|
||||
}
|
||||
icon={ImageIcon}
|
||||
/>
|
||||
</Menu.MenuGroup>
|
||||
));
|
||||
};
|
||||
|
||||
export default definePlugin({
|
||||
name: "ViewIcons",
|
||||
authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz],
|
||||
description: "Makes avatars and banners in user profiles clickable, and adds View Icon/Banner entries in the user and server context menu",
|
||||
authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz, Devs.nyx],
|
||||
description: "Makes avatars and banners in user profiles clickable, adds View Icon/Banner entries in the user, server and group channel context menu.",
|
||||
tags: ["ImageUtilities"],
|
||||
|
||||
settings,
|
||||
|
@ -157,11 +178,12 @@ export default definePlugin({
|
|||
|
||||
contextMenus: {
|
||||
"user-context": UserContext,
|
||||
"guild-context": GuildContext
|
||||
"guild-context": GuildContext,
|
||||
"gdm-context": GroupDMContext
|
||||
},
|
||||
|
||||
patches: [
|
||||
// Make pfps clickable
|
||||
// Profiles Modal pfp
|
||||
{
|
||||
find: "User Profile Modal - Context Menu",
|
||||
replacement: {
|
||||
|
@ -169,7 +191,7 @@ export default definePlugin({
|
|||
replace: "{src:$1,onClick:()=>$self.openImage($1)"
|
||||
}
|
||||
},
|
||||
// Make banners clickable
|
||||
// Banners
|
||||
{
|
||||
find: ".NITRO_BANNER,",
|
||||
replacement: {
|
||||
|
@ -180,12 +202,37 @@ export default definePlugin({
|
|||
'onClick:ev=>$1&&ev.target.style.backgroundImage&&$self.openImage($2),style:{cursor:$1?"pointer":void 0,'
|
||||
}
|
||||
},
|
||||
// User DMs "User Profile" popup in the right
|
||||
{
|
||||
find: ".avatarPositionPanel",
|
||||
replacement: {
|
||||
match: /(?<=avatarWrapperNonUserBot.{0,50})onClick:(\i\|\|\i)\?void 0(?<=,avatarSrc:(\i).+?)/,
|
||||
replace: "style:($1)?{cursor:\"pointer\"}:{},onClick:$1?()=>{$self.openImage($2)}"
|
||||
}
|
||||
},
|
||||
// Group DMs top small & large icon
|
||||
{
|
||||
find: /\.recipients\.length>=2(?!<isMultiUserDM.{0,50})/,
|
||||
replacement: {
|
||||
match: /null==\i\.icon\?.+?src:(\(0,\i\.getChannelIconURL\).+?\))(?=[,}])/,
|
||||
replace: (m, iconUrl) => `${m},onClick:()=>$self.openImage(${iconUrl})`
|
||||
}
|
||||
},
|
||||
// User DMs top small icon
|
||||
{
|
||||
find: ".cursorPointer:null,children",
|
||||
replacement: {
|
||||
match: /.Avatar,.+?src:(.+?\))(?=[,}])/,
|
||||
replace: (m, avatarUrl) => `${m},onClick:()=>$self.openImage(${avatarUrl})`
|
||||
}
|
||||
},
|
||||
// User Dms top large icon
|
||||
{
|
||||
find: 'experimentLocation:"empty_messages"',
|
||||
replacement: {
|
||||
match: /.Avatar,.+?src:(.+?\))(?=[,}])/,
|
||||
replace: (m, avatarUrl) => `${m},onClick:()=>$self.openImage(${avatarUrl})`
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
|
|
@ -28,7 +28,7 @@ import { useAwaiter } from "@utils/react";
|
|||
import definePlugin from "@utils/types";
|
||||
import { chooseFile } from "@utils/web";
|
||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { Button, Card, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common";
|
||||
import { Button, Card, Constants, FluxDispatcher, Forms, lodash, Menu, MessageActions, PermissionsBits, PermissionStore, RestAPI, SelectedChannelStore, showToast, SnowflakeUtils, Toasts, useEffect, useState } from "@webpack/common";
|
||||
import { ComponentType } from "react";
|
||||
|
||||
import { VoiceRecorderDesktop } from "./DesktopRecorder";
|
||||
|
@ -98,7 +98,7 @@ function sendAudio(blob: Blob, meta: AudioMetadata) {
|
|||
|
||||
upload.on("complete", () => {
|
||||
RestAPI.post({
|
||||
url: `/channels/${channelId}/messages`,
|
||||
url: Constants.Endpoints.MESSAGES(channelId),
|
||||
body: {
|
||||
flags: 1 << 13,
|
||||
channel_id: channelId,
|
||||
|
|
|
@ -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, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common";
|
||||
import { ChannelStore, Constants, FluxDispatcher, React, RestAPI, Tooltip } from "@webpack/common";
|
||||
import { CustomEmoji } from "@webpack/types";
|
||||
import { Message, ReactionEmoji, User } from "discord-types/general";
|
||||
|
||||
|
@ -36,7 +36,7 @@ let reactions: Record<string, ReactionCacheEntry>;
|
|||
function fetchReactions(msg: Message, emoji: ReactionEmoji, type: number) {
|
||||
const key = emoji.name + (emoji.id ? `:${emoji.id}` : "");
|
||||
return RestAPI.get({
|
||||
url: `/channels/${msg.channel_id}/messages/${msg.id}/reactions/${key}`,
|
||||
url: Constants.Endpoints.REACTIONS(msg.channel_id, msg.id, key),
|
||||
query: {
|
||||
limit: 100,
|
||||
type
|
||||
|
|
|
@ -68,7 +68,6 @@ interface Call {
|
|||
ringing: string[];
|
||||
}
|
||||
|
||||
const MuteStore = findByPropsLazy("isSuppressEveryoneEnabled");
|
||||
const Notifs = findByPropsLazy("makeTextChatNotification");
|
||||
const XSLog = new Logger("XSOverlay");
|
||||
|
||||
|
@ -115,13 +114,13 @@ const settings = definePluginSettings({
|
|||
},
|
||||
timeout: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "Notif duration (secs)",
|
||||
default: 1.0,
|
||||
description: "Notification duration (secs)",
|
||||
default: 3,
|
||||
},
|
||||
timeoutPerCharacter: {
|
||||
type: OptionType.NUMBER,
|
||||
description: "Duration multiplier per character",
|
||||
default: 0.5
|
||||
lengthBasedTimeout: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Extend duration with message length",
|
||||
default: true
|
||||
},
|
||||
opacity: {
|
||||
type: OptionType.SLIDER,
|
||||
|
@ -262,12 +261,11 @@ function shouldIgnoreForChannelType(channel: Channel) {
|
|||
}
|
||||
|
||||
function sendMsgNotif(titleString: string, content: string, message: Message) {
|
||||
const timeout = Math.max(settings.store.timeout, content.length * settings.store.timeoutPerCharacter);
|
||||
fetch(`https://cdn.discordapp.com/avatars/${message.author.id}/${message.author.avatar}.png?size=128`).then(response => response.arrayBuffer()).then(result => {
|
||||
const msgData = {
|
||||
messageType: 1,
|
||||
index: 0,
|
||||
timeout,
|
||||
timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout,
|
||||
height: calculateHeight(content),
|
||||
opacity: settings.store.opacity,
|
||||
volume: settings.store.volume,
|
||||
|
@ -286,7 +284,7 @@ function sendOtherNotif(content: string, titleString: string) {
|
|||
const msgData = {
|
||||
messageType: 1,
|
||||
index: 0,
|
||||
timeout: settings.store.timeout,
|
||||
timeout: settings.store.lengthBasedTimeout ? calculateTimeout(content) : settings.store.timeout,
|
||||
height: calculateHeight(content),
|
||||
opacity: settings.store.opacity,
|
||||
volume: settings.store.volume,
|
||||
|
@ -313,3 +311,10 @@ function calculateHeight(content: string) {
|
|||
if (content.length <= 300) return 200;
|
||||
return 250;
|
||||
}
|
||||
|
||||
function calculateTimeout(content: string) {
|
||||
if (content.length <= 100) return 3;
|
||||
if (content.length <= 200) return 4;
|
||||
if (content.length <= 300) return 5;
|
||||
return 6;
|
||||
}
|
||||
|
|
|
@ -378,10 +378,18 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||
name: "ProffDea",
|
||||
id: 609329952180928513n
|
||||
},
|
||||
UlyssesZhan: {
|
||||
name: "UlyssesZhan",
|
||||
id: 586808226058862623n
|
||||
},
|
||||
ant0n: {
|
||||
name: "ant0n",
|
||||
id: 145224646868860928n
|
||||
},
|
||||
Board: {
|
||||
name: "BoardTM",
|
||||
id: 285475344817848320n,
|
||||
},
|
||||
philipbry: {
|
||||
name: "philipbry",
|
||||
id: 554994003318276106n
|
||||
|
@ -414,6 +422,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||
name: "Av32000",
|
||||
id: 593436735380127770n,
|
||||
},
|
||||
Noxillio: {
|
||||
name: "Noxillio",
|
||||
id: 138616536502894592n,
|
||||
},
|
||||
Kyuuhachi: {
|
||||
name: "Kyuuhachi",
|
||||
id: 236588665420251137n,
|
||||
|
@ -434,6 +446,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||
name: "newwares",
|
||||
id: 421405303951851520n
|
||||
},
|
||||
JohnyTheCarrot: {
|
||||
name: "JohnyTheCarrot",
|
||||
id: 132819036282159104n
|
||||
},
|
||||
puv: {
|
||||
name: "puv",
|
||||
id: 469441552251355137n
|
||||
|
@ -477,7 +493,27 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||
ImBanana: {
|
||||
name: "Im_Banana",
|
||||
id: 635250116688871425n
|
||||
}
|
||||
},
|
||||
xocherry: {
|
||||
name: "xocherry",
|
||||
id: 221288171013406720n
|
||||
},
|
||||
ScattrdBlade: {
|
||||
name: "ScattrdBlade",
|
||||
id: 678007540608532491n
|
||||
},
|
||||
Moxxie: {
|
||||
name: "Moxxie",
|
||||
id: 712653921692155965n,
|
||||
},
|
||||
Ethan: {
|
||||
name: "Ethan",
|
||||
id: 721717126523781240n,
|
||||
},
|
||||
nyx: {
|
||||
name: "verticalsync",
|
||||
id: 328165170536775680n
|
||||
},
|
||||
} satisfies Record<string, Dev>);
|
||||
|
||||
// iife so #__PURE__ works correctly
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
import { MessageObject } from "@api/MessageEvents";
|
||||
import { ChannelStore, ComponentDispatch, FluxDispatcher, GuildStore, InviteActions, MaskedLink, MessageActions, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
|
||||
import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, InviteActions, MaskedLink, MessageActions, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
|
||||
import { Guild, Message, User } from "discord-types/general";
|
||||
|
||||
import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal";
|
||||
|
@ -162,7 +162,7 @@ export async function fetchUserProfile(id: string, options?: FetchUserProfileOpt
|
|||
FluxDispatcher.dispatch({ type: "USER_PROFILE_FETCH_START", userId: id });
|
||||
|
||||
const { body } = await RestAPI.get({
|
||||
url: `/users/${id}/profile`,
|
||||
url: Constants.Endpoints.USER_PROFILE(id),
|
||||
query: {
|
||||
with_mutual_guilds: false,
|
||||
with_mutual_friends_count: false,
|
||||
|
|
|
@ -23,9 +23,11 @@ export * from "./constants";
|
|||
export * from "./discord";
|
||||
export * from "./guards";
|
||||
export * from "./lazy";
|
||||
export * from "./lazyReact";
|
||||
export * from "./localStorage";
|
||||
export * from "./Logger";
|
||||
export * from "./margins";
|
||||
export * from "./mergeDefaults";
|
||||
export * from "./misc";
|
||||
export * from "./modal";
|
||||
export * from "./onlyOnce";
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { PatchReplacement, ReplaceFn } from "./types";
|
||||
import { Patch, PatchReplacement, ReplaceFn } from "./types";
|
||||
|
||||
export function canonicalizeMatch<T extends RegExp | string>(match: T): T {
|
||||
if (typeof match === "string") return match;
|
||||
|
@ -55,3 +55,9 @@ export function canonicalizeReplacement(replacement: Pick<PatchReplacement, "mat
|
|||
);
|
||||
Object.defineProperties(replacement, descriptors);
|
||||
}
|
||||
|
||||
export function canonicalizeFind(patch: Patch) {
|
||||
const descriptors = Object.getOwnPropertyDescriptors(patch);
|
||||
descriptors.find = canonicalizeDescriptor(descriptors.find, canonicalizeMatch);
|
||||
Object.defineProperties(patch, descriptors);
|
||||
}
|
||||
|
|
|
@ -29,14 +29,19 @@ export default function definePlugin<P extends PluginDef>(p: P & Record<string,
|
|||
export type ReplaceFn = (match: string, ...groups: string[]) => string;
|
||||
|
||||
export interface PatchReplacement {
|
||||
/** The match for the patch replacement. If you use a string it will be implicitly converted to a RegExp */
|
||||
match: string | RegExp;
|
||||
/** The replacement string or function which returns the string for the patch replacement */
|
||||
replace: string | ReplaceFn;
|
||||
/** A function which returns whether this patch replacement should be applied */
|
||||
predicate?(): boolean;
|
||||
}
|
||||
|
||||
export interface Patch {
|
||||
plugin: string;
|
||||
find: string;
|
||||
/** A string or RegExp which is only include/matched in the module code you wish to patch. Prefer only using a RegExp if a simple string test is not enough */
|
||||
find: string | RegExp;
|
||||
/** The replacement(s) for the module being patched */
|
||||
replacement: PatchReplacement | PatchReplacement[];
|
||||
/** Whether this patch should apply to multiple modules */
|
||||
all?: boolean;
|
||||
|
@ -44,6 +49,7 @@ export interface Patch {
|
|||
noWarn?: boolean;
|
||||
/** Only apply this set of replacements if all of them succeed. Use this if your replacements depend on each other */
|
||||
group?: boolean;
|
||||
/** A function which returns whether this patch should be applied */
|
||||
predicate?(): boolean;
|
||||
}
|
||||
|
||||
|
@ -238,7 +244,7 @@ export interface PluginSettingSliderDef {
|
|||
stickToMarkers?: boolean;
|
||||
}
|
||||
|
||||
interface IPluginOptionComponentProps {
|
||||
export interface IPluginOptionComponentProps {
|
||||
/**
|
||||
* Run this when the value changes.
|
||||
*
|
||||
|
|
|
@ -27,12 +27,7 @@ export const Flux: t.Flux = findByPropsLazy("connectStores");
|
|||
|
||||
export type GenericStore = t.FluxStore & Record<string, any>;
|
||||
|
||||
export enum DraftType {
|
||||
ChannelMessage = 0,
|
||||
ThreadSettings = 1,
|
||||
FirstThreadMessage = 2,
|
||||
ApplicationLauncherCommand = 3
|
||||
}
|
||||
export const { DraftType }: { DraftType: typeof t.DraftType; } = findByPropsLazy("DraftType");
|
||||
|
||||
export let MessageStore: Omit<Stores.MessageStore, "getMessages"> & {
|
||||
getMessages(chanId: string): any;
|
||||
|
|
12
src/webpack/common/types/stores.d.ts
vendored
12
src/webpack/common/types/stores.d.ts
vendored
|
@ -63,7 +63,7 @@ export interface CustomEmoji {
|
|||
originalName?: string;
|
||||
require_colons: boolean;
|
||||
roles: string[];
|
||||
url: string;
|
||||
type: "GUILD_EMOJI";
|
||||
}
|
||||
|
||||
export interface UnicodeEmoji {
|
||||
|
@ -75,6 +75,7 @@ export interface UnicodeEmoji {
|
|||
};
|
||||
index: number;
|
||||
surrogates: string;
|
||||
type: "UNICODE";
|
||||
uniqueName: string;
|
||||
useSpriteSheet: boolean;
|
||||
get allNamesString(): string;
|
||||
|
@ -173,6 +174,15 @@ export class DraftStore extends FluxStore {
|
|||
getThreadSettings(channelId: string): any | null;
|
||||
}
|
||||
|
||||
export enum DraftType {
|
||||
ChannelMessage,
|
||||
ThreadSettings,
|
||||
FirstThreadMessage,
|
||||
ApplicationLauncherCommand,
|
||||
Poll,
|
||||
SlashCommand,
|
||||
}
|
||||
|
||||
export class GuildStore extends FluxStore {
|
||||
getGuild(guildId: string): Guild;
|
||||
getGuildCount(): number;
|
||||
|
|
|
@ -119,6 +119,8 @@ export function showToast(message: string, type = ToastType.MESSAGE) {
|
|||
}
|
||||
|
||||
export const UserUtils = findByPropsLazy("getUser", "fetchCurrentUser") as { getUser: (id: string) => Promise<User>; };
|
||||
|
||||
export const UploadManager = findByPropsLazy("clearAll", "addFile");
|
||||
export const UploadHandler = findByPropsLazy("showUploadFileSizeExceededError", "promptToUpload") as {
|
||||
promptToUpload: (files: File[], channel: Channel, draftType: Number) => void;
|
||||
};
|
||||
|
@ -136,10 +138,10 @@ waitFor(["open", "saveAccountChanges"], m => SettingsRouter = m);
|
|||
|
||||
export const { Permissions: PermissionsBits } = findLazy(m => typeof m.Permissions?.ADMINISTRATOR === "bigint") as { Permissions: t.PermissionsBits; };
|
||||
|
||||
export const zustandCreate: typeof import("zustand").default = findByCodeLazy("will be removed in v4");
|
||||
export const zustandCreate = findByCodeLazy("will be removed in v4");
|
||||
|
||||
const persistFilter = filters.byCode("[zustand persist middleware]");
|
||||
export const { persist: zustandPersist }: typeof import("zustand/middleware") = findLazy(m => m.persist && persistFilter(m.persist));
|
||||
export const { persist: zustandPersist } = findLazy(m => m.persist && persistFilter(m.persist));
|
||||
|
||||
export const MessageActions = findByPropsLazy("editMessage", "sendMessage");
|
||||
export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal");
|
||||
|
|
|
@ -122,7 +122,7 @@ Object.defineProperty(Function.prototype, "m", {
|
|||
// When using react devtools or other extensions, we may also catch their webpack here.
|
||||
// This ensures we actually got the right one
|
||||
const { stack } = new Error();
|
||||
if (stack?.includes("discord.com") || stack?.includes("discordapp.com")) {
|
||||
if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && !Array.isArray(v)) {
|
||||
logger.info("Found Webpack module factory", stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "");
|
||||
patchFactories(v);
|
||||
}
|
||||
|
@ -257,7 +257,12 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
|
|||
for (let i = 0; i < patches.length; i++) {
|
||||
const patch = patches[i];
|
||||
if (patch.predicate && !patch.predicate()) continue;
|
||||
if (!code.includes(patch.find)) continue;
|
||||
|
||||
const moduleMatches = typeof patch.find === "string"
|
||||
? code.includes(patch.find)
|
||||
: patch.find.test(code);
|
||||
|
||||
if (!moduleMatches) continue;
|
||||
|
||||
patchedBy.add(patch.plugin);
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import { makeLazy, proxyLazy } from "@utils/lazy";
|
||||
import { LazyComponent } from "@utils/lazyReact";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { canonicalizeMatch } from "@utils/patches";
|
||||
|
@ -432,7 +432,7 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
|
|||
}
|
||||
|
||||
const [, rawChunkIds, entryPointId] = match;
|
||||
if (Number.isNaN(entryPointId)) {
|
||||
if (Number.isNaN(Number(entryPointId))) {
|
||||
const err = new Error("extractAndLoadChunks: Matcher didn't return a capturing group with the chunk ids array, or the entry point id returned as the second group wasn't a number");
|
||||
logger.warn(err, "Code:", code, "Matcher:", matcher);
|
||||
|
||||
|
@ -462,7 +462,7 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
|
|||
export function extractAndLoadChunksLazy(code: string[], matcher = DefaultExtractAndLoadChunksRegex) {
|
||||
if (IS_DEV) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
|
||||
|
||||
return () => extractAndLoadChunks(code, matcher);
|
||||
return makeLazy(() => extractAndLoadChunks(code, matcher));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,7 +29,15 @@
|
|||
"@webpack/types": ["./webpack/common/types"],
|
||||
"@webpack/common": ["./webpack/common"],
|
||||
"@webpack": ["./webpack/webpack"]
|
||||
}
|
||||
},
|
||||
|
||||
"plugins": [
|
||||
// Transform paths in output .d.ts files (Include this line if you output declarations files)
|
||||
{
|
||||
"transform": "typescript-transform-paths",
|
||||
"afterDeclarations": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["src/**/*", "browser/**/*", "scripts/**/*"]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue