Merge branch 'main' of https://www.coastalcommits.com/CoastalCommitsArchival/github.com-Vendicated-Vencord
This commit is contained in:
commit
d3096d6f27
59 changed files with 1826 additions and 253 deletions
1
.npmrc
1
.npmrc
|
@ -1 +1,2 @@
|
||||||
strict-peer-dependencies=false
|
strict-peer-dependencies=false
|
||||||
|
package-manager-strict=false
|
||||||
|
|
16
package.json
16
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.8.4",
|
"version": "1.8.5",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -19,16 +19,17 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs",
|
"build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs",
|
||||||
"buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.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",
|
"generatePluginJson": "tsx scripts/generatePluginList.ts",
|
||||||
|
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
|
||||||
"inject": "node scripts/runInstaller.mjs",
|
"inject": "node scripts/runInstaller.mjs",
|
||||||
|
"uninject": "node scripts/runInstaller.mjs",
|
||||||
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern src/userplugins",
|
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern src/userplugins",
|
||||||
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
||||||
"lint:fix": "pnpm lint --fix",
|
"lint:fix": "pnpm lint --fix",
|
||||||
"test": "pnpm build && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
|
"test": "pnpm build && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
|
||||||
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
|
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
|
||||||
"testTsc": "tsc --noEmit",
|
"testTsc": "tsc --noEmit"
|
||||||
"uninject": "node scripts/runInstaller.mjs",
|
|
||||||
"watch": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs --watch"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sapphi-red/web-noise-suppressor": "0.3.3",
|
"@sapphi-red/web-noise-suppressor": "0.3.3",
|
||||||
|
@ -65,11 +66,12 @@
|
||||||
"standalone-electron-types": "^1.0.0",
|
"standalone-electron-types": "^1.0.0",
|
||||||
"stylelint": "^15.6.0",
|
"stylelint": "^15.6.0",
|
||||||
"stylelint-config-standard": "^33.0.0",
|
"stylelint-config-standard": "^33.0.0",
|
||||||
|
"ts-patch": "^3.1.2",
|
||||||
"tsx": "^3.12.7",
|
"tsx": "^3.12.7",
|
||||||
"type-fest": "^3.9.0",
|
"type-fest": "^3.9.0",
|
||||||
"typescript": "^5.0.4",
|
"typescript": "^5.4.5",
|
||||||
"zip-local": "^0.3.5",
|
"typescript-transform-paths": "^3.4.7",
|
||||||
"zustand": "^3.7.2"
|
"zip-local": "^0.3.5"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.1.0",
|
"packageManager": "pnpm@9.1.0",
|
||||||
"pnpm": {
|
"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.predicate;
|
||||||
delete patch.group;
|
delete patch.group;
|
||||||
|
|
||||||
if (!Array.isArray(patch.replacement))
|
Vencord.Util.canonicalizeFind(patch);
|
||||||
|
if (!Array.isArray(patch.replacement)) {
|
||||||
patch.replacement = [patch.replacement];
|
patch.replacement = [patch.replacement];
|
||||||
|
}
|
||||||
|
|
||||||
patch.replacement.forEach(r => {
|
patch.replacement.forEach(r => {
|
||||||
delete r.predicate;
|
delete r.predicate;
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * as Api from "./api";
|
export * as Api from "./api";
|
||||||
|
export * as Components from "./components";
|
||||||
export * as Plugins from "./plugins";
|
export * as Plugins from "./plugins";
|
||||||
export * as Util from "./utils";
|
export * as Util from "./utils";
|
||||||
export * as QuickCss from "./utils/quickCss";
|
export * as QuickCss from "./utils/quickCss";
|
||||||
|
|
|
@ -100,6 +100,7 @@ export async function showNotification(data: NotificationData) {
|
||||||
const n = new Notification(title, {
|
const n = new Notification(title, {
|
||||||
body,
|
body,
|
||||||
icon,
|
icon,
|
||||||
|
// @ts-expect-error ts is drunk
|
||||||
image
|
image
|
||||||
});
|
});
|
||||||
n.onclick = onClick;
|
n.onclick = onClick;
|
||||||
|
|
|
@ -16,10 +16,12 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./ExpandableHeader.css";
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Text, Tooltip, useState } from "@webpack/common";
|
import { Text, Tooltip, useState } from "@webpack/common";
|
||||||
export const cl = classNameFactory("vc-expandableheader-");
|
|
||||||
import "./ExpandableHeader.css";
|
const cl = classNameFactory("vc-expandableheader-");
|
||||||
|
|
||||||
export interface ExpandableHeaderProps {
|
export interface ExpandableHeaderProps {
|
||||||
onMoreClick?: () => void;
|
onMoreClick?: () => void;
|
||||||
|
@ -31,7 +33,7 @@ export interface ExpandableHeaderProps {
|
||||||
buttons?: React.ReactNode[];
|
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);
|
const [showContent, setShowContent] = useState(defaultState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
|
||||||
import { CodeBlock } from "@components/CodeBlock";
|
import { CodeBlock } from "@components/CodeBlock";
|
||||||
import { debounce } from "@shared/debounce";
|
import { debounce } from "@shared/debounce";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
|
@ -47,7 +46,7 @@ const findCandidates = debounce(function ({ find, setModule, setError }) {
|
||||||
|
|
||||||
interface ReplacementComponentProps {
|
interface ReplacementComponentProps {
|
||||||
module: [id: number, factory: Function];
|
module: [id: number, factory: Function];
|
||||||
match: string | RegExp;
|
match: string;
|
||||||
replacement: string | ReplaceFn;
|
replacement: string | ReplaceFn;
|
||||||
setReplacementError(error: any): void;
|
setReplacementError(error: any): void;
|
||||||
}
|
}
|
||||||
|
@ -58,7 +57,13 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
||||||
|
|
||||||
const [patchedCode, matchResult, diff] = React.useMemo(() => {
|
const [patchedCode, matchResult, diff] = React.useMemo(() => {
|
||||||
const src: string = fact.toString().replaceAll("\n", "");
|
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 {
|
try {
|
||||||
const canonicalReplace = canonicalizeReplace(replacement, "YourPlugin");
|
const canonicalReplace = canonicalizeReplace(replacement, "YourPlugin");
|
||||||
var patched = src.replace(canonicalMatch, canonicalReplace as string);
|
var patched = src.replace(canonicalMatch, canonicalReplace as string);
|
||||||
|
@ -286,6 +291,7 @@ function PatchHelper() {
|
||||||
|
|
||||||
const [module, setModule] = React.useState<[number, Function]>();
|
const [module, setModule] = React.useState<[number, Function]>();
|
||||||
const [findError, setFindError] = React.useState<string>();
|
const [findError, setFindError] = React.useState<string>();
|
||||||
|
const [matchError, setMatchError] = React.useState<string>();
|
||||||
|
|
||||||
const code = React.useMemo(() => {
|
const code = React.useMemo(() => {
|
||||||
return `
|
return `
|
||||||
|
@ -300,20 +306,16 @@ function PatchHelper() {
|
||||||
}, [parsedFind, match, replacement]);
|
}, [parsedFind, match, replacement]);
|
||||||
|
|
||||||
function onFindChange(v: string) {
|
function onFindChange(v: string) {
|
||||||
setFindError(void 0);
|
|
||||||
setFind(v);
|
setFind(v);
|
||||||
}
|
|
||||||
|
|
||||||
function onFindBlur() {
|
|
||||||
try {
|
try {
|
||||||
let parsedFind = find as string | RegExp;
|
let parsedFind = v as string | RegExp;
|
||||||
if (/^\/.+?\/$/.test(find)) parsedFind = new RegExp(find.slice(1, -1));
|
if (/^\/.+?\/$/.test(v)) parsedFind = new RegExp(v.slice(1, -1));
|
||||||
|
|
||||||
setFindError(void 0);
|
setFindError(void 0);
|
||||||
setFind(find);
|
|
||||||
setParsedFind(parsedFind);
|
setParsedFind(parsedFind);
|
||||||
|
|
||||||
if (find.length) {
|
if (v.length) {
|
||||||
findCandidates({ find: parsedFind, setModule, setError: setFindError });
|
findCandidates({ find: parsedFind, setModule, setError: setFindError });
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
@ -322,12 +324,13 @@ function PatchHelper() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMatchChange(v: string) {
|
function onMatchChange(v: string) {
|
||||||
|
setMatch(v);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
new RegExp(v);
|
new RegExp(v);
|
||||||
setFindError(void 0);
|
setMatchError(void 0);
|
||||||
setMatch(v);
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
setFindError((e as Error).message);
|
setMatchError((e as Error).message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -346,21 +349,15 @@ function PatchHelper() {
|
||||||
type="text"
|
type="text"
|
||||||
value={find}
|
value={find}
|
||||||
onChange={onFindChange}
|
onChange={onFindChange}
|
||||||
onBlur={onFindBlur}
|
|
||||||
error={findError}
|
error={findError}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Forms.FormTitle className={Margins.top8}>match</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.top8}>match</Forms.FormTitle>
|
||||||
<CheckedTextInput
|
<TextInput
|
||||||
|
type="text"
|
||||||
value={match}
|
value={match}
|
||||||
onChange={onMatchChange}
|
onChange={onMatchChange}
|
||||||
validate={v => {
|
error={matchError}
|
||||||
try {
|
|
||||||
return (new RegExp(v), true);
|
|
||||||
} catch (e) {
|
|
||||||
return (e as Error).message;
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className={Margins.top8} />
|
<div className={Margins.top8} />
|
||||||
|
@ -374,7 +371,7 @@ function PatchHelper() {
|
||||||
{module && (
|
{module && (
|
||||||
<ReplacementComponent
|
<ReplacementComponent
|
||||||
module={module}
|
module={module}
|
||||||
match={new RegExp(match)}
|
match={match}
|
||||||
replacement={replacement}
|
replacement={replacement}
|
||||||
setReplacementError={setReplacementError}
|
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";
|
2
src/modules.d.ts
vendored
2
src/modules.d.ts
vendored
|
@ -20,7 +20,7 @@
|
||||||
/// <reference types="standalone-electron-types"/>
|
/// <reference types="standalone-electron-types"/>
|
||||||
|
|
||||||
declare module "~plugins" {
|
declare module "~plugins" {
|
||||||
const plugins: Record<string, import("@utils/types").Plugin>;
|
const plugins: Record<string, import("./utils/types").Plugin>;
|
||||||
export default plugins;
|
export default plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -56,7 +56,6 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// Discord Canary
|
|
||||||
{
|
{
|
||||||
find: "Messages.ACTIVITY_SETTINGS",
|
find: "Messages.ACTIVITY_SETTINGS",
|
||||||
replacement: {
|
replacement: {
|
||||||
|
|
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 })
|
||||||
|
});
|
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 { Logger } from "@utils/Logger";
|
||||||
import { openModal } from "@utils/modal";
|
import { openModal } from "@utils/modal";
|
||||||
import { OAuth2AuthorizeModal, showToast, Toasts, UserStore, zustandCreate, zustandPersist } from "@webpack/common";
|
import { OAuth2AuthorizeModal, showToast, Toasts, UserStore, zustandCreate, zustandPersist } from "@webpack/common";
|
||||||
import type { StateStorage } from "zustand/middleware";
|
|
||||||
|
|
||||||
import { AUTHORIZE_URL, CLIENT_ID } from "../constants";
|
import { AUTHORIZE_URL, CLIENT_ID } from "../constants";
|
||||||
|
|
||||||
|
@ -23,7 +22,7 @@ interface AuthorizationState {
|
||||||
isAuthorized: () => boolean;
|
isAuthorized: () => boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const indexedDBStorage: StateStorage = {
|
const indexedDBStorage = {
|
||||||
async getItem(name: string): Promise<string | null> {
|
async getItem(name: string): Promise<string | null> {
|
||||||
return DataStore.get(name).then(v => v ?? null);
|
return DataStore.get(name).then(v => v ?? null);
|
||||||
},
|
},
|
||||||
|
@ -36,9 +35,9 @@ const indexedDBStorage: StateStorage = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Move switching accounts subscription inside the store?
|
// TODO: Move switching accounts subscription inside the store?
|
||||||
export const useAuthorizationStore = proxyLazy(() => zustandCreate<AuthorizationState>(
|
export const useAuthorizationStore = proxyLazy(() => zustandCreate(
|
||||||
zustandPersist(
|
zustandPersist(
|
||||||
(set, get) => ({
|
(set: any, get: any) => ({
|
||||||
token: null,
|
token: null,
|
||||||
tokens: {},
|
tokens: {},
|
||||||
init: () => { set({ token: get().tokens[UserStore.getCurrentUser().id] ?? null }); },
|
init: () => { set({ token: get().tokens[UserStore.getCurrentUser().id] ?? null }); },
|
||||||
|
@ -91,7 +90,7 @@ export const useAuthorizationStore = proxyLazy(() => zustandCreate<Authorization
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
isAuthorized: () => !!get().token,
|
isAuthorized: () => !!get().token,
|
||||||
}),
|
} as AuthorizationState),
|
||||||
{
|
{
|
||||||
name: "decor-auth",
|
name: "decor-auth",
|
||||||
getStorage: () => indexedDBStorage,
|
getStorage: () => indexedDBStorage,
|
||||||
|
|
|
@ -21,7 +21,7 @@ interface UserDecorationsState {
|
||||||
clear: () => void;
|
clear: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useCurrentUserDecorationsStore = proxyLazy(() => zustandCreate<UserDecorationsState>((set, get) => ({
|
export const useCurrentUserDecorationsStore = proxyLazy(() => zustandCreate((set: any, get: any) => ({
|
||||||
decorations: [],
|
decorations: [],
|
||||||
selectedDecoration: null,
|
selectedDecoration: null,
|
||||||
async fetch() {
|
async fetch() {
|
||||||
|
@ -53,4 +53,4 @@ export const useCurrentUserDecorationsStore = proxyLazy(() => zustandCreate<User
|
||||||
useUsersDecorationsStore.getState().set(UserStore.getCurrentUser().id, decoration ? decorationToAsset(decoration) : null);
|
useUsersDecorationsStore.getState().set(UserStore.getCurrentUser().id, decoration ? decorationToAsset(decoration) : null);
|
||||||
},
|
},
|
||||||
clear: () => set({ decorations: [], selectedDecoration: null })
|
clear: () => set({ decorations: [], selectedDecoration: null })
|
||||||
})));
|
} as UserDecorationsState)));
|
||||||
|
|
|
@ -30,7 +30,7 @@ interface UsersDecorationsState {
|
||||||
set: (userId: string, decoration: string | null) => void;
|
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>(),
|
usersDecorations: new Map<string, UserDecorationData>(),
|
||||||
fetchQueue: new Set(),
|
fetchQueue: new Set(),
|
||||||
bulkFetch: debounce(async () => {
|
bulkFetch: debounce(async () => {
|
||||||
|
@ -40,7 +40,7 @@ export const useUsersDecorationsStore = proxyLazy(() => zustandCreate<UsersDecor
|
||||||
|
|
||||||
set({ fetchQueue: new Set() });
|
set({ fetchQueue: new Set() });
|
||||||
|
|
||||||
const fetchIds = Array.from(fetchQueue);
|
const fetchIds = [...fetchQueue];
|
||||||
const fetchedUsersDecorations = await getUsersDecorations(fetchIds);
|
const fetchedUsersDecorations = await getUsersDecorations(fetchIds);
|
||||||
|
|
||||||
const newUsersDecorations = new Map(usersDecorations);
|
const newUsersDecorations = new Map(usersDecorations);
|
||||||
|
@ -92,7 +92,7 @@ export const useUsersDecorationsStore = proxyLazy(() => zustandCreate<UsersDecor
|
||||||
newUsersDecorations.set(userId, { asset: decoration, fetchedAt: new Date() });
|
newUsersDecorations.set(userId, { asset: decoration, fetchedAt: new Date() });
|
||||||
set({ usersDecorations: newUsersDecorations });
|
set({ usersDecorations: newUsersDecorations });
|
||||||
}
|
}
|
||||||
})));
|
} as UsersDecorationsState)));
|
||||||
|
|
||||||
export function useUserDecorAvatarDecoration(user?: User): AvatarDecoration | null | undefined {
|
export function useUserDecorAvatarDecoration(user?: User): AvatarDecoration | null | undefined {
|
||||||
const [decorAvatarDecoration, setDecorAvatarDecoration] = useState<string | null>(user ? useUsersDecorationsStore.getState().getAsset(user.id) ?? null : null);
|
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");
|
const CustomizationSection = findByCodeLazy(".customizationSectionBackground");
|
||||||
|
|
||||||
interface DecorSectionProps {
|
export interface DecorSectionProps {
|
||||||
hideTitle?: boolean;
|
hideTitle?: boolean;
|
||||||
hideDivider?: boolean;
|
hideDivider?: boolean;
|
||||||
noMargin?: boolean;
|
noMargin?: boolean;
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
|
import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
|
||||||
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
|
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
|
||||||
import type { CustomEmoji } from "@webpack/types";
|
import type { Emoji } from "@webpack/types";
|
||||||
import type { Message } from "discord-types/general";
|
import type { Message } from "discord-types/general";
|
||||||
import { applyPalette, GIFEncoder, quantize } from "gifenc";
|
import { applyPalette, GIFEncoder, quantize } from "gifenc";
|
||||||
import type { ReactElement, ReactNode } from "react";
|
import type { ReactElement, ReactNode } from "react";
|
||||||
|
@ -54,16 +54,22 @@ const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoCla
|
||||||
|
|
||||||
|
|
||||||
const enum EmojiIntentions {
|
const enum EmojiIntentions {
|
||||||
REACTION = 0,
|
REACTION,
|
||||||
STATUS = 1,
|
STATUS,
|
||||||
COMMUNITY_CONTENT = 2,
|
COMMUNITY_CONTENT,
|
||||||
CHAT = 3,
|
CHAT,
|
||||||
GUILD_STICKER_RELATED_EMOJI = 4,
|
GUILD_STICKER_RELATED_EMOJI,
|
||||||
GUILD_ROLE_BENEFIT_EMOJI = 5,
|
GUILD_ROLE_BENEFIT_EMOJI,
|
||||||
COMMUNITY_CONTENT_ONLY = 6,
|
COMMUNITY_CONTENT_ONLY,
|
||||||
SOUNDBOARD = 7
|
SOUNDBOARD,
|
||||||
|
VOICE_CHANNEL_TOPIC,
|
||||||
|
GIFT,
|
||||||
|
AUTO_SUGGESTION,
|
||||||
|
POLLS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const IS_BYPASSEABLE_INTENTION = `[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`;
|
||||||
|
|
||||||
const enum StickerType {
|
const enum StickerType {
|
||||||
PNG = 1,
|
PNG = 1,
|
||||||
APNG = 2,
|
APNG = 2,
|
||||||
|
@ -198,37 +204,43 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".PREMIUM_LOCKED;",
|
find: ".PREMIUM_LOCKED;",
|
||||||
|
group: true,
|
||||||
predicate: () => settings.store.enableEmojiBypass,
|
predicate: () => settings.store.enableEmojiBypass,
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Create a variable for the intention of listing the emoji
|
// Create a variable for the intention of using the emoji
|
||||||
match: /(?<=,intention:(\i).+?;)/,
|
match: /(?<=\.USE_EXTERNAL_EMOJIS.+?;)(?<=intention:(\i).+?)/,
|
||||||
replace: (_, intention) => `let fakeNitroIntention=${intention};`
|
replace: (_, intention) => `const fakeNitroIntention=${intention};`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Send the intention of listing the emoji to the nitro permission check functions
|
// Disallow the emoji for external if the intention doesn't allow it
|
||||||
match: /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g,
|
match: /&&!\i&&!\i(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/,
|
||||||
replace: '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0'
|
replace: m => `${m}&&!${IS_BYPASSEABLE_INTENTION}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Disallow the emoji if the intention doesn't allow it
|
// Disallow the emoji for unavailable if the intention doesn't allow it
|
||||||
match: /(&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/,
|
match: /!\i\.available(?=\)return \i\.\i\.GUILD_SUBSCRIPTION_UNAVAILABLE;)/,
|
||||||
replace: (_, rest, canUseExternal) => `${rest}(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))`
|
replace: m => `${m}&&!${IS_BYPASSEABLE_INTENTION}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Make the emoji always available if the intention allows it
|
// Disallow the emoji for premium locked if the intention doesn't allow it
|
||||||
match: /if\(!\i\.available/,
|
match: /!\i\.\i\.canUseEmojisEverywhere\(\i\)/,
|
||||||
replace: m => `${m}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention))`
|
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",
|
find: "isUnusableRoleSubscriptionEmoji:function",
|
||||||
predicate: () => settings.store.enableEmojiBypass,
|
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))(?=})/g,
|
match: /isUnusableRoleSubscriptionEmoji:function/,
|
||||||
replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`
|
// 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
|
// Allow stickers to be sent everywhere
|
||||||
|
@ -242,10 +254,10 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// Make stickers always available
|
// Make stickers always available
|
||||||
{
|
{
|
||||||
find: "\"SENDABLE\"",
|
find: '"SENDABLE"',
|
||||||
predicate: () => settings.store.enableStickerBypass,
|
predicate: () => settings.store.enableStickerBypass,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\w+)\.available\?/,
|
match: /\i\.available\?/,
|
||||||
replace: "true?"
|
replace: "true?"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -408,15 +420,6 @@ export default definePlugin({
|
||||||
match: /canUseCustomNotificationSounds:function\(\i\){/,
|
match: /canUseCustomNotificationSounds:function\(\i\){/,
|
||||||
replace: "$&return true;"
|
replace: "$&return true;"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
// Allows the usage of subscription-locked emojis
|
|
||||||
{
|
|
||||||
find: "isUnusableRoleSubscriptionEmoji:function",
|
|
||||||
replacement: {
|
|
||||||
match: /isUnusableRoleSubscriptionEmoji:function/,
|
|
||||||
// replace the original export with a func that always returns false and alias the original
|
|
||||||
replace: "isUnusableRoleSubscriptionEmoji:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -809,8 +812,8 @@ export default definePlugin({
|
||||||
UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), DraftType.ChannelMessage);
|
UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), DraftType.ChannelMessage);
|
||||||
},
|
},
|
||||||
|
|
||||||
canUseEmote(e: CustomEmoji, channelId: string) {
|
canUseEmote(e: Emoji, channelId: string) {
|
||||||
if (e.require_colons === false) return true;
|
if (e.type === "UNICODE") return true;
|
||||||
if (e.available === false) return false;
|
if (e.available === false) return false;
|
||||||
|
|
||||||
const isUnusableRoleSubEmoji = RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji;
|
const isUnusableRoleSubEmoji = RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji;
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getCurrentChannel } from "@utils/discord";
|
import { getCurrentChannel } from "@utils/discord";
|
||||||
|
import { Logger } from "@utils/Logger";
|
||||||
|
import { classes } from "@utils/misc";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Heading, React, RelationshipStore, Text } from "@webpack/common";
|
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",
|
description: "Shows when you became friends with someone in the user popout",
|
||||||
authors: [Devs.Elvyra],
|
authors: [Devs.Elvyra],
|
||||||
patches: [
|
patches: [
|
||||||
|
// User popup
|
||||||
{
|
{
|
||||||
find: ".AnalyticsSections.USER_PROFILE}",
|
find: ".AnalyticsSections.USER_PROFILE}",
|
||||||
replacement: {
|
replacement: {
|
||||||
|
@ -29,16 +32,34 @@ export default definePlugin({
|
||||||
replace: "$&,$self.friendsSince({ userId: $1 })"
|
replace: "$&,$self.friendsSince({ userId: $1 })"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// User DMs "User Profile" popup in the right
|
||||||
{
|
{
|
||||||
find: ".UserPopoutUpsellSource.PROFILE_PANEL,",
|
find: ".UserPopoutUpsellSource.PROFILE_PANEL,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\i.default,\{userId:(\i)}\)/,
|
match: /\i.default,\{userId:(\i)}\)/,
|
||||||
replace: "$&,$self.friendsSince({ userId: $1 })"
|
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);
|
const friendsSince = RelationshipStore.getSince(userId);
|
||||||
if (!friendsSince) return null;
|
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" />
|
<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>
|
</svg>
|
||||||
)}
|
)}
|
||||||
<Text variant="text-sm/normal" className={clydeMoreInfo.body}>
|
<Text variant="text-sm/normal" className={classes(clydeMoreInfo.body, textClassName)}>
|
||||||
{getCreatedAtDate(friendsSince, locale.getLocale())}
|
{getCreatedAtDate(friendsSince, locale.getLocale())}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
@ -69,4 +90,3 @@ export default definePlugin({
|
||||||
);
|
);
|
||||||
}, { noop: true })
|
}, { noop: true })
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { registerCommand, unregisterCommand } from "@api/Commands";
|
||||||
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
|
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
|
import { canonicalizeFind } from "@utils/patches";
|
||||||
import { Patch, Plugin, StartAt } from "@utils/types";
|
import { Patch, Plugin, StartAt } from "@utils/types";
|
||||||
import { FluxDispatcher } from "@webpack/common";
|
import { FluxDispatcher } from "@webpack/common";
|
||||||
import { FluxEvents } from "@webpack/types";
|
import { FluxEvents } from "@webpack/types";
|
||||||
|
@ -83,8 +84,12 @@ for (const p of pluginsValues) {
|
||||||
if (p.patches && isPluginEnabled(p.name)) {
|
if (p.patches && isPluginEnabled(p.name)) {
|
||||||
for (const patch of p.patches) {
|
for (const patch of p.patches) {
|
||||||
patch.plugin = p.name;
|
patch.plugin = p.name;
|
||||||
if (!Array.isArray(patch.replacement))
|
|
||||||
|
canonicalizeFind(patch);
|
||||||
|
if (!Array.isArray(patch.replacement)) {
|
||||||
patch.replacement = [patch.replacement];
|
patch.replacement = [patch.replacement];
|
||||||
|
}
|
||||||
|
|
||||||
patches.push(patch);
|
patches.push(patch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,13 +170,14 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
p.start();
|
p.start();
|
||||||
p.started = true;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`Failed to start ${name}\n`, e);
|
logger.error(`Failed to start ${name}\n`, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.started = true;
|
||||||
|
|
||||||
if (commands?.length) {
|
if (commands?.length) {
|
||||||
logger.debug("Registering commands of plugin", name);
|
logger.debug("Registering commands of plugin", name);
|
||||||
for (const cmd of commands) {
|
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) {
|
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
|
||||||
const { name, commands, flux, contextMenus } = p;
|
const { name, commands, flux, contextMenus } = p;
|
||||||
|
|
||||||
if (p.stop) {
|
if (p.stop) {
|
||||||
logger.info("Stopping plugin", name);
|
logger.info("Stopping plugin", name);
|
||||||
if (!p.started) {
|
if (!p.started) {
|
||||||
|
@ -209,13 +216,14 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
p.stop();
|
p.stop();
|
||||||
p.started = false;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error(`Failed to stop ${name}\n`, e);
|
logger.error(`Failed to stop ${name}\n`, e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.started = false;
|
||||||
|
|
||||||
if (commands?.length) {
|
if (commands?.length) {
|
||||||
logger.debug("Unregistering commands of plugin", name);
|
logger.debug("Unregistering commands of plugin", name);
|
||||||
for (const cmd of commands) {
|
for (const cmd of commands) {
|
||||||
|
|
|
@ -114,6 +114,11 @@ const settings = definePluginSettings({
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
shareSong: {
|
||||||
|
description: "show link to song on last.fm",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
hideWithSpotify: {
|
hideWithSpotify: {
|
||||||
description: "hide last.fm presence if spotify is running",
|
description: "hide last.fm presence if spotify is running",
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
|
@ -295,12 +300,7 @@ export default definePlugin({
|
||||||
large_text: trackData.album || undefined,
|
large_text: trackData.album || undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttons: ActivityButton[] = [
|
const buttons: ActivityButton[] = [];
|
||||||
{
|
|
||||||
label: "View Song",
|
|
||||||
url: trackData.url,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
if (settings.store.shareUsername)
|
if (settings.store.shareUsername)
|
||||||
buttons.push({
|
buttons.push({
|
||||||
|
@ -308,6 +308,12 @@ export default definePlugin({
|
||||||
url: `https://www.last.fm/user/${settings.store.username}`,
|
url: `https://www.last.fm/user/${settings.store.username}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (settings.store.shareSong)
|
||||||
|
buttons.push({
|
||||||
|
label: "View Song",
|
||||||
|
url: trackData.url,
|
||||||
|
});
|
||||||
|
|
||||||
const statusName = (() => {
|
const statusName = (() => {
|
||||||
switch (settings.store.nameFormat) {
|
switch (settings.store.nameFormat) {
|
||||||
case NameFormat.ArtistFirst:
|
case NameFormat.ArtistFirst:
|
||||||
|
@ -333,7 +339,7 @@ export default definePlugin({
|
||||||
state: trackData.artist,
|
state: trackData.artist,
|
||||||
assets,
|
assets,
|
||||||
|
|
||||||
buttons: buttons.map(v => v.label),
|
buttons: buttons.length ? buttons.map(v => v.label) : undefined,
|
||||||
metadata: {
|
metadata: {
|
||||||
button_urls: buttons.map(v => v.url),
|
button_urls: buttons.map(v => v.url),
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,9 +22,10 @@ interface Diff {
|
||||||
hours: number,
|
hours: number,
|
||||||
minutes: number,
|
minutes: number,
|
||||||
seconds: number;
|
seconds: number;
|
||||||
|
milliseconds: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DISCORD_KT_DELAY = 1471228.928;
|
const DISCORD_KT_DELAY = 1471228928;
|
||||||
const HiddenVisually = findExportedComponentLazy("HiddenVisually");
|
const HiddenVisually = findExportedComponentLazy("HiddenVisually");
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
|
@ -42,6 +43,11 @@ export default definePlugin({
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Detect old Discord Android clients",
|
description: "Detect old Discord Android clients",
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
showMillis: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Show milliseconds",
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
@ -55,12 +61,13 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
stringDelta(delta: number) {
|
stringDelta(delta: number, showMillis: boolean) {
|
||||||
const diff: Diff = {
|
const diff: Diff = {
|
||||||
days: Math.round(delta / (60 * 60 * 24)),
|
days: Math.round(delta / (60 * 60 * 24 * 1000)),
|
||||||
hours: Math.round((delta / (60 * 60)) % 24),
|
hours: Math.round((delta / (60 * 60 * 1000)) % 24),
|
||||||
minutes: Math.round((delta / (60)) % 60),
|
minutes: Math.round((delta / (60 * 1000)) % 60),
|
||||||
seconds: Math.round(delta % 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;
|
const str = (k: DiffKey) => diff[k] > 0 ? `${diff[k]} ${diff[k] > 1 ? k : k.substring(0, k.length - 1)}` : null;
|
||||||
|
@ -72,7 +79,7 @@ export default definePlugin({
|
||||||
return prev + (
|
return prev + (
|
||||||
isNonNullish(s)
|
isNonNullish(s)
|
||||||
? (prev !== ""
|
? (prev !== ""
|
||||||
? k === "seconds"
|
? (showMillis ? k === "milliseconds" : k === "seconds")
|
||||||
? " and "
|
? " and "
|
||||||
: " "
|
: " "
|
||||||
: "") + s
|
: "") + s
|
||||||
|
@ -84,18 +91,21 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
latencyTooltipData(message: Message) {
|
latencyTooltipData(message: Message) {
|
||||||
const { latency, detectDiscordKotlin } = this.settings.store;
|
const { latency, detectDiscordKotlin, showMillis } = this.settings.store;
|
||||||
const { id, nonce } = message;
|
const { id, nonce } = message;
|
||||||
|
|
||||||
// Message wasn't received through gateway
|
// Message wasn't received through gateway
|
||||||
if (!isNonNullish(nonce)) return null;
|
if (!isNonNullish(nonce)) return null;
|
||||||
|
|
||||||
let isDiscordKotlin = false;
|
let isDiscordKotlin = false;
|
||||||
let delta = Math.round((SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce)) / 1000);
|
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
|
// Old Discord Android clients have a delay of around 17 days
|
||||||
// This is a workaround for that
|
// This is a workaround for that
|
||||||
if (-delta >= DISCORD_KT_DELAY - 86400) { // One day of padding for good measure
|
if (-delta >= DISCORD_KT_DELAY - 86400000) { // One day of padding for good measure
|
||||||
isDiscordKotlin = detectDiscordKotlin;
|
isDiscordKotlin = detectDiscordKotlin;
|
||||||
delta += DISCORD_KT_DELAY;
|
delta += DISCORD_KT_DELAY;
|
||||||
}
|
}
|
||||||
|
@ -105,22 +115,23 @@ export default definePlugin({
|
||||||
// Can't do anything if the clock is behind
|
// Can't do anything if the clock is behind
|
||||||
const abs = Math.abs(delta);
|
const abs = Math.abs(delta);
|
||||||
const ahead = abs !== delta;
|
const ahead = abs !== delta;
|
||||||
|
const latencyMillis = latency * 1000;
|
||||||
|
|
||||||
const stringDelta = abs >= latency ? this.stringDelta(abs) : null;
|
const stringDelta = abs >= latencyMillis ? this.stringDelta(abs, showMillis) : null;
|
||||||
|
|
||||||
// Also thanks dziurwa
|
// Also thanks dziurwa
|
||||||
// 2 minutes
|
// 2 minutes
|
||||||
const TROLL_LIMIT = 2 * 60;
|
const TROLL_LIMIT = 2 * 60 * 1000;
|
||||||
|
|
||||||
const fill: Fill = isDiscordKotlin
|
const fill: Fill = isDiscordKotlin
|
||||||
? ["status-positive", "status-positive", "text-muted"]
|
? ["status-positive", "status-positive", "text-muted"]
|
||||||
: delta >= TROLL_LIMIT || ahead
|
: delta >= TROLL_LIMIT || ahead
|
||||||
? ["text-muted", "text-muted", "text-muted"]
|
? ["text-muted", "text-muted", "text-muted"]
|
||||||
: delta >= (latency * 2)
|
: delta >= (latencyMillis * 2)
|
||||||
? ["status-danger", "text-muted", "text-muted"]
|
? ["status-danger", "text-muted", "text-muted"]
|
||||||
: ["status-warning", "status-warning", "text-muted"];
|
: ["status-warning", "status-warning", "text-muted"];
|
||||||
|
|
||||||
return (abs >= latency || isDiscordKotlin) ? { delta: stringDelta, ahead, fill, isDiscordKotlin } : null;
|
return (abs >= latencyMillis || isDiscordKotlin) ? { delta: stringDelta, ahead, fill, isDiscordKotlin } : null;
|
||||||
},
|
},
|
||||||
|
|
||||||
Tooltip() {
|
Tooltip() {
|
||||||
|
|
|
@ -227,10 +227,8 @@ function MessageEmbedAccessory({ message }: { message: Message; }) {
|
||||||
|
|
||||||
const accessories = [] as (JSX.Element | null)[];
|
const accessories = [] as (JSX.Element | null)[];
|
||||||
|
|
||||||
let match = null as RegExpMatchArray | null;
|
for (const [_, channelID, messageID] of message.content!.matchAll(messageLinkRegex)) {
|
||||||
while ((match = messageLinkRegex.exec(message.content!)) !== null) {
|
if (embeddedBy.includes(messageID) || embeddedBy.length > 2) {
|
||||||
const [_, channelID, messageID] = match;
|
|
||||||
if (embeddedBy.includes(messageID)) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,9 +376,6 @@ export default definePlugin({
|
||||||
if (!messageLinkRegex.test(props.message.content))
|
if (!messageLinkRegex.test(props.message.content))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
// need to reset the regex because it's global
|
|
||||||
messageLinkRegex.lastIndex = 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<MessageEmbedAccessory
|
<MessageEmbedAccessory
|
||||||
|
|
|
@ -354,6 +354,15 @@ export default definePlugin({
|
||||||
if (location === "chat" && !settings.tagSettings[tag.name].showInChat) continue;
|
if (location === "chat" && !settings.tagSettings[tag.name].showInChat) continue;
|
||||||
if (location === "not-chat" && !settings.tagSettings[tag.name].showInNotChat) 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 (
|
if (
|
||||||
tag.permissions?.some(perm => perms.includes(perm)) ||
|
tag.permissions?.some(perm => perms.includes(perm)) ||
|
||||||
(tag.condition?.(message!, user, channel))
|
(tag.condition?.(message!, user, channel))
|
||||||
|
|
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,"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { FluxDispatcher } from "@webpack/common";
|
import { FluxDispatcher } from "@webpack/common";
|
||||||
|
@ -41,8 +41,9 @@ const settings = definePluginSettings({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migratePluginSettings("PartyMode", "Party mode 🎉");
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "Party mode 🎉",
|
name: "PartyMode",
|
||||||
description: "Allows you to use party mode cause the party never ends ✨",
|
description: "Allows you to use party mode cause the party never ends ✨",
|
||||||
authors: [Devs.UwUDev],
|
authors: [Devs.UwUDev],
|
||||||
settings,
|
settings,
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import ExpandableHeader from "@components/ExpandableHeader";
|
import { ExpandableHeader } from "@components/ExpandableHeader";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { filters, findBulk, proxyLazyWebpack } from "@webpack";
|
import { filters, findBulk, proxyLazyWebpack } from "@webpack";
|
||||||
import { i18n, PermissionsBits, Text, Tooltip, useMemo, UserStore } from "@webpack/common";
|
import { i18n, PermissionsBits, Text, Tooltip, useMemo, UserStore } from "@webpack/common";
|
||||||
|
|
|
@ -22,14 +22,34 @@ import { addServerListElement, removeServerListElement, ServerListRenderPosition
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
import { findStoreLazy } from "@webpack";
|
||||||
import { Button, FluxDispatcher, GuildChannelStore, GuildStore, React, ReadStateStore } from "@webpack/common";
|
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() {
|
function onClick() {
|
||||||
const channels: Array<any> = [];
|
const channels: Array<any> = [];
|
||||||
|
|
||||||
Object.values(GuildStore.getGuilds()).forEach(guild => {
|
Object.values(GuildStore.getGuilds()).forEach(guild => {
|
||||||
GuildChannelStore.getChannels(guild.id).SELECTABLE
|
GuildChannelStore.getChannels(guild.id).SELECTABLE // Array<{ channel, comparator }>
|
||||||
.concat(GuildChannelStore.getChannels(guild.id).VOCAL)
|
.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; }; }) => {
|
.forEach((c: { channel: { id: string; }; }) => {
|
||||||
if (!ReadStateStore.hasUnread(c.channel.id)) return;
|
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"',
|
find: '"MessageActionCreators"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=focusMessage\(\i\){.+?)(?=focus:{messageId:(\i)})/,
|
match: /(?<=focusMessage\(\i\){.+?)(?=focus:{messageId:(\i)})/,
|
||||||
replace: "before:$1,"
|
replace: "after:$1,"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Force Server Home instead of Server Guide
|
// Force Server Home instead of Server Guide
|
||||||
|
|
|
@ -20,7 +20,7 @@ import "./style.css";
|
||||||
|
|
||||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import ExpandableHeader from "@components/ExpandableHeader";
|
import { ExpandableHeader } from "@components/ExpandableHeader";
|
||||||
import { OpenExternalIcon } from "@components/Icons";
|
import { OpenExternalIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
|
@ -436,7 +436,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".shouldCloseDefaultModals",
|
find: 'className:"channelMention",children',
|
||||||
replacement: {
|
replacement: {
|
||||||
// Show inside voice channel instead of trying to join them when clicking on a channel mention
|
// 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)/,
|
match: /(?<=getChannel\(\i\);if\(null!=(\i))(?=.{0,100}?selectVoiceChannel)/,
|
||||||
|
|
|
@ -9,11 +9,11 @@ import "./styles.css";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findComponentLazy } from "@webpack";
|
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 { Message } from "discord-types/general";
|
||||||
|
import { FunctionComponent, ReactNode } from "react";
|
||||||
|
|
||||||
const CountDown = findComponentLazy(m => m.prototype?.render?.toString().includes(".MAX_AGE_NEVER"));
|
const CountDown = findComponentLazy(m => m.prototype?.render?.toString().includes(".MAX_AGE_NEVER"));
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ const settings = definePluginSettings({
|
||||||
displayStyle: {
|
displayStyle: {
|
||||||
description: "How to display the timeout duration",
|
description: "How to display the timeout duration",
|
||||||
type: OptionType.SELECT,
|
type: OptionType.SELECT,
|
||||||
restartNeeded: true,
|
|
||||||
options: [
|
options: [
|
||||||
{ label: "In the Tooltip", value: DisplayStyle.Tooltip },
|
{ label: "In the Tooltip", value: DisplayStyle.Tooltip },
|
||||||
{ label: "Next to the timeout icon", value: DisplayStyle.Inline, default: true },
|
{ label: "Next to the timeout icon", value: DisplayStyle.Inline, default: true },
|
||||||
|
@ -60,7 +59,7 @@ function renderTimeout(message: Message, inline: boolean) {
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "ShowTimeoutDuration",
|
name: "ShowTimeoutDuration",
|
||||||
description: "Shows how much longer a user's timeout will last, either in the timeout icon tooltip or next to it",
|
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,
|
settings,
|
||||||
|
|
||||||
|
@ -70,33 +69,20 @@ export default definePlugin({
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(\i)\.Tooltip,{(text:.{0,30}\.Messages\.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY)/,
|
match: /(\i)\.Tooltip,{(text:.{0,30}\.Messages\.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY)/,
|
||||||
get replace() {
|
replace: "$self.TooltipWrapper,{message:arguments[0].message,$2"
|
||||||
if (settings.store.displayStyle === DisplayStyle.Inline)
|
|
||||||
return "$self.TooltipWrapper,{vcProps:arguments[0],$2";
|
|
||||||
|
|
||||||
return "$1.Tooltip,{text:$self.renderTimeoutDuration(arguments[0])";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
renderTimeoutDuration: ErrorBoundary.wrap(({ message }: { message: Message; }) => {
|
TooltipWrapper: ErrorBoundary.wrap(({ message, children, text }: { message: Message; children: FunctionComponent<any>; text: ReactNode; }) => {
|
||||||
return (
|
if (settings.store.displayStyle === DisplayStyle.Tooltip) return <Tooltip
|
||||||
<>
|
children={children}
|
||||||
<Forms.FormText>{i18n.Messages.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY}</Forms.FormText>
|
text={renderTimeout(message, false)}
|
||||||
<Forms.FormText className={Margins.top8}>
|
/>;
|
||||||
{renderTimeout(message, false)}
|
|
||||||
</Forms.FormText>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}, { noop: true }),
|
|
||||||
|
|
||||||
TooltipWrapper: ErrorBoundary.wrap(({ vcProps: { message }, ...tooltipProps }: { vcProps: { message: Message; }; }) => {
|
|
||||||
return (
|
return (
|
||||||
<div className="vc-std-wrapper">
|
<div className="vc-std-wrapper">
|
||||||
<Tooltip {...tooltipProps as any} />
|
<Tooltip text={text} children={children} />
|
||||||
|
|
||||||
<Text variant="text-md/normal" color="status-danger">
|
<Text variant="text-md/normal" color="status-danger">
|
||||||
{renderTimeout(message, true)} timeout remaining
|
{renderTimeout(message, true)} timeout remaining
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
@ -2,3 +2,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-std-wrapper [class*="communicationDisabled"] {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ export default definePlugin({
|
||||||
patches: [{
|
patches: [{
|
||||||
find: "Messages.ACTIVITY_SETTINGS",
|
find: "Messages.ACTIVITY_SETTINGS",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\))/,
|
match: /(?<=}\)([,;])(\i\.settings)\.forEach.+?(\i)\.push.+}\)}\))/,
|
||||||
replace: (_, commaOrSemi, settings, elements) => "" +
|
replace: (_, commaOrSemi, settings, elements) => "" +
|
||||||
`${commaOrSemi}${settings}?.[0]==="CHANGELOG"` +
|
`${commaOrSemi}${settings}?.[0]==="CHANGELOG"` +
|
||||||
`&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`
|
`&&${elements}.push({section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage})`
|
||||||
|
|
|
@ -48,7 +48,7 @@ export default definePlugin({
|
||||||
})),
|
})),
|
||||||
{
|
{
|
||||||
// channel mentions
|
// channel mentions
|
||||||
find: ".shouldCloseDefaultModals",
|
find: 'className:"channelMention",children',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /onClick:(\i)(?=,.{0,30}className:"channelMention".+?(\i)\.inContent)/,
|
match: /onClick:(\i)(?=,.{0,30}className:"channelMention".+?(\i)\.inContent)/,
|
||||||
replace: (_, onClick, props) => ""
|
replace: (_, onClick, props) => ""
|
||||||
|
|
|
@ -36,6 +36,10 @@ interface GuildContextProps {
|
||||||
guild?: Guild;
|
guild?: Guild;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GroupDMContextProps {
|
||||||
|
channel: Channel;
|
||||||
|
}
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
format: {
|
format: {
|
||||||
type: OptionType.SELECT,
|
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({
|
export default definePlugin({
|
||||||
name: "ViewIcons",
|
name: "ViewIcons",
|
||||||
authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz],
|
authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz, Devs.nyx],
|
||||||
description: "Makes avatars and banners in user profiles clickable, and adds View Icon/Banner entries in the user and server context menu",
|
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"],
|
tags: ["ImageUtilities"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
@ -157,11 +178,12 @@ export default definePlugin({
|
||||||
|
|
||||||
contextMenus: {
|
contextMenus: {
|
||||||
"user-context": UserContext,
|
"user-context": UserContext,
|
||||||
"guild-context": GuildContext
|
"guild-context": GuildContext,
|
||||||
|
"gdm-context": GroupDMContext
|
||||||
},
|
},
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
// Make pfps clickable
|
// Profiles Modal pfp
|
||||||
{
|
{
|
||||||
find: "User Profile Modal - Context Menu",
|
find: "User Profile Modal - Context Menu",
|
||||||
replacement: {
|
replacement: {
|
||||||
|
@ -169,7 +191,7 @@ export default definePlugin({
|
||||||
replace: "{src:$1,onClick:()=>$self.openImage($1)"
|
replace: "{src:$1,onClick:()=>$self.openImage($1)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Make banners clickable
|
// Banners
|
||||||
{
|
{
|
||||||
find: ".NITRO_BANNER,",
|
find: ".NITRO_BANNER,",
|
||||||
replacement: {
|
replacement: {
|
||||||
|
@ -180,12 +202,37 @@ export default definePlugin({
|
||||||
'onClick:ev=>$1&&ev.target.style.backgroundImage&&$self.openImage($2),style:{cursor:$1?"pointer":void 0,'
|
'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",
|
find: ".avatarPositionPanel",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=avatarWrapperNonUserBot.{0,50})onClick:(\i\|\|\i)\?void 0(?<=,avatarSrc:(\i).+?)/,
|
match: /(?<=avatarWrapperNonUserBot.{0,50})onClick:(\i\|\|\i)\?void 0(?<=,avatarSrc:(\i).+?)/,
|
||||||
replace: "style:($1)?{cursor:\"pointer\"}:{},onClick:$1?()=>{$self.openImage($2)}"
|
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})`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -422,6 +422,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
name: "Av32000",
|
name: "Av32000",
|
||||||
id: 593436735380127770n,
|
id: 593436735380127770n,
|
||||||
},
|
},
|
||||||
|
Noxillio: {
|
||||||
|
name: "Noxillio",
|
||||||
|
id: 138616536502894592n,
|
||||||
|
},
|
||||||
Kyuuhachi: {
|
Kyuuhachi: {
|
||||||
name: "Kyuuhachi",
|
name: "Kyuuhachi",
|
||||||
id: 236588665420251137n,
|
id: 236588665420251137n,
|
||||||
|
@ -442,6 +446,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
name: "newwares",
|
name: "newwares",
|
||||||
id: 421405303951851520n
|
id: 421405303951851520n
|
||||||
},
|
},
|
||||||
|
JohnyTheCarrot: {
|
||||||
|
name: "JohnyTheCarrot",
|
||||||
|
id: 132819036282159104n
|
||||||
|
},
|
||||||
puv: {
|
puv: {
|
||||||
name: "puv",
|
name: "puv",
|
||||||
id: 469441552251355137n
|
id: 469441552251355137n
|
||||||
|
@ -490,6 +498,18 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
name: "ScattrdBlade",
|
name: "ScattrdBlade",
|
||||||
id: 678007540608532491n
|
id: 678007540608532491n
|
||||||
},
|
},
|
||||||
|
Moxxie: {
|
||||||
|
name: "Moxxie",
|
||||||
|
id: 712653921692155965n,
|
||||||
|
},
|
||||||
|
Ethan: {
|
||||||
|
name: "Ethan",
|
||||||
|
id: 721717126523781240n,
|
||||||
|
},
|
||||||
|
nyx: {
|
||||||
|
name: "verticalsync",
|
||||||
|
id: 328165170536775680n
|
||||||
|
},
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
// iife so #__PURE__ works correctly
|
// iife so #__PURE__ works correctly
|
||||||
|
|
|
@ -23,9 +23,11 @@ export * from "./constants";
|
||||||
export * from "./discord";
|
export * from "./discord";
|
||||||
export * from "./guards";
|
export * from "./guards";
|
||||||
export * from "./lazy";
|
export * from "./lazy";
|
||||||
|
export * from "./lazyReact";
|
||||||
export * from "./localStorage";
|
export * from "./localStorage";
|
||||||
export * from "./Logger";
|
export * from "./Logger";
|
||||||
export * from "./margins";
|
export * from "./margins";
|
||||||
|
export * from "./mergeDefaults";
|
||||||
export * from "./misc";
|
export * from "./misc";
|
||||||
export * from "./modal";
|
export * from "./modal";
|
||||||
export * from "./onlyOnce";
|
export * from "./onlyOnce";
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { PatchReplacement, ReplaceFn } from "./types";
|
import { Patch, PatchReplacement, ReplaceFn } from "./types";
|
||||||
|
|
||||||
export function canonicalizeMatch<T extends RegExp | string>(match: T): T {
|
export function canonicalizeMatch<T extends RegExp | string>(match: T): T {
|
||||||
if (typeof match === "string") return match;
|
if (typeof match === "string") return match;
|
||||||
|
@ -55,3 +55,9 @@ export function canonicalizeReplacement(replacement: Pick<PatchReplacement, "mat
|
||||||
);
|
);
|
||||||
Object.defineProperties(replacement, descriptors);
|
Object.defineProperties(replacement, descriptors);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function canonicalizeFind(patch: Patch) {
|
||||||
|
const descriptors = Object.getOwnPropertyDescriptors(patch);
|
||||||
|
descriptors.find = canonicalizeDescriptor(descriptors.find, canonicalizeMatch);
|
||||||
|
Object.defineProperties(patch, descriptors);
|
||||||
|
}
|
||||||
|
|
|
@ -244,7 +244,7 @@ export interface PluginSettingSliderDef {
|
||||||
stickToMarkers?: boolean;
|
stickToMarkers?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IPluginOptionComponentProps {
|
export interface IPluginOptionComponentProps {
|
||||||
/**
|
/**
|
||||||
* Run this when the value changes.
|
* Run this when the value changes.
|
||||||
*
|
*
|
||||||
|
|
3
src/webpack/common/types/stores.d.ts
vendored
3
src/webpack/common/types/stores.d.ts
vendored
|
@ -63,7 +63,7 @@ export interface CustomEmoji {
|
||||||
originalName?: string;
|
originalName?: string;
|
||||||
require_colons: boolean;
|
require_colons: boolean;
|
||||||
roles: string[];
|
roles: string[];
|
||||||
url: string;
|
type: "GUILD_EMOJI";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UnicodeEmoji {
|
export interface UnicodeEmoji {
|
||||||
|
@ -75,6 +75,7 @@ export interface UnicodeEmoji {
|
||||||
};
|
};
|
||||||
index: number;
|
index: number;
|
||||||
surrogates: string;
|
surrogates: string;
|
||||||
|
type: "UNICODE";
|
||||||
uniqueName: string;
|
uniqueName: string;
|
||||||
useSpriteSheet: boolean;
|
useSpriteSheet: boolean;
|
||||||
get allNamesString(): string;
|
get allNamesString(): string;
|
||||||
|
|
|
@ -138,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 { 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]");
|
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 MessageActions = findByPropsLazy("editMessage", "sendMessage");
|
||||||
export const UserProfileActions = findByPropsLazy("openUserProfileModal", "closeUserProfileModal");
|
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.
|
// When using react devtools or other extensions, we may also catch their webpack here.
|
||||||
// This ensures we actually got the right one
|
// This ensures we actually got the right one
|
||||||
const { stack } = new Error();
|
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] ?? "");
|
logger.info("Found Webpack module factory", stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "");
|
||||||
patchFactories(v);
|
patchFactories(v);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { proxyLazy } from "@utils/lazy";
|
import { makeLazy, proxyLazy } from "@utils/lazy";
|
||||||
import { LazyComponent } from "@utils/lazyReact";
|
import { LazyComponent } from "@utils/lazyReact";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { canonicalizeMatch } from "@utils/patches";
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
|
@ -462,7 +462,7 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
|
||||||
export function extractAndLoadChunksLazy(code: string[], matcher = DefaultExtractAndLoadChunksRegex) {
|
export function extractAndLoadChunksLazy(code: string[], matcher = DefaultExtractAndLoadChunksRegex) {
|
||||||
if (IS_DEV) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
|
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/types": ["./webpack/common/types"],
|
||||||
"@webpack/common": ["./webpack/common"],
|
"@webpack/common": ["./webpack/common"],
|
||||||
"@webpack": ["./webpack/webpack"]
|
"@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/**/*"]
|
"include": ["src/**/*", "browser/**/*", "scripts/**/*"]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue