mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-02-24 15:35:11 +00:00
Merge remote-tracking branch 'origin/main' into feat/nestedfolders
This commit is contained in:
commit
c61993256a
164 changed files with 4097 additions and 3429 deletions
|
@ -4,10 +4,9 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// @ts-check
|
|
||||||
|
|
||||||
import stylistic from "@stylistic/eslint-plugin";
|
import stylistic from "@stylistic/eslint-plugin";
|
||||||
import pathAlias from "eslint-plugin-path-alias";
|
import pathAlias from "eslint-plugin-path-alias";
|
||||||
|
import react from "eslint-plugin-react";
|
||||||
import header from "eslint-plugin-simple-header";
|
import header from "eslint-plugin-simple-header";
|
||||||
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
||||||
import unusedImports from "eslint-plugin-unused-imports";
|
import unusedImports from "eslint-plugin-unused-imports";
|
||||||
|
@ -15,6 +14,22 @@ import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
export default tseslint.config(
|
export default tseslint.config(
|
||||||
{ ignores: ["dist", "browser", "packages/vencord-types"] },
|
{ ignores: ["dist", "browser", "packages/vencord-types"] },
|
||||||
|
{
|
||||||
|
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
|
||||||
|
settings: {
|
||||||
|
react: {
|
||||||
|
version: "18"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...react.configs.flat.recommended,
|
||||||
|
rules: {
|
||||||
|
...react.configs.flat.recommended.rules,
|
||||||
|
"react/react-in-jsx-scope": "off",
|
||||||
|
"react/prop-types": "off",
|
||||||
|
"react/display-name": "off",
|
||||||
|
"react/no-unescaped-entities": "off",
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
|
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
|
||||||
plugins: {
|
plugins: {
|
||||||
|
@ -23,7 +38,7 @@ export default tseslint.config(
|
||||||
"@typescript-eslint": tseslint.plugin,
|
"@typescript-eslint": tseslint.plugin,
|
||||||
"simple-import-sort": simpleImportSort,
|
"simple-import-sort": simpleImportSort,
|
||||||
"unused-imports": unusedImports,
|
"unused-imports": unusedImports,
|
||||||
"path-alias": pathAlias,
|
"path-alias": pathAlias
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
"import/resolver": {
|
"import/resolver": {
|
||||||
|
@ -90,7 +105,13 @@ export default tseslint.config(
|
||||||
"no-invalid-regexp": "error",
|
"no-invalid-regexp": "error",
|
||||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||||
"no-duplicate-imports": "error",
|
"no-duplicate-imports": "error",
|
||||||
"dot-notation": "error",
|
"@typescript-eslint/dot-notation": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allowPrivateClassPropertyAccess": true,
|
||||||
|
"allowProtectedClassPropertyAccess": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"no-useless-escape": [
|
"no-useless-escape": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
|
53
package.json
53
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.10.8",
|
"version": "1.11.3",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -41,48 +41,49 @@
|
||||||
"@vap/shiki": "0.10.5",
|
"@vap/shiki": "0.10.5",
|
||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
||||||
"monaco-editor": "^0.50.0",
|
"monaco-editor": "^0.52.2",
|
||||||
"nanoid": "^5.0.7",
|
"nanoid": "^5.0.9",
|
||||||
"virtual-merge": "^1.0.1"
|
"virtual-merge": "^1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/eslint-plugin": "^2.6.1",
|
"@stylistic/eslint-plugin": "^2.12.1",
|
||||||
"@types/chrome": "^0.0.269",
|
"@types/chrome": "^0.0.287",
|
||||||
"@types/diff": "^5.2.1",
|
"@types/diff": "^6.0.0",
|
||||||
"@types/lodash": "^4.17.7",
|
"@types/lodash": "^4.17.14",
|
||||||
"@types/node": "^22.0.3",
|
"@types/node": "^22.10.5",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react": "^19.0.2",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^19.0.2",
|
||||||
"@types/yazl": "^2.4.5",
|
"@types/yazl": "^2.4.5",
|
||||||
"diff": "^5.2.0",
|
"diff": "^7.0.0",
|
||||||
"discord-types": "^1.3.26",
|
"discord-types": "^1.3.26",
|
||||||
"esbuild": "^0.15.18",
|
"esbuild": "^0.15.18",
|
||||||
"eslint": "^9.8.0",
|
"eslint": "^9.17.0",
|
||||||
"eslint-import-resolver-alias": "^1.1.2",
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
"eslint-plugin-path-alias": "2.1.0",
|
"eslint-plugin-path-alias": "2.1.0",
|
||||||
"eslint-plugin-simple-header": "^1.1.1",
|
"eslint-plugin-react": "^7.37.3",
|
||||||
|
"eslint-plugin-simple-header": "^1.2.1",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
"eslint-plugin-unused-imports": "^4.0.1",
|
"eslint-plugin-unused-imports": "^4.1.4",
|
||||||
"highlight.js": "10.7.3",
|
"highlight.js": "11.7.0",
|
||||||
"html-minifier-terser": "^7.2.0",
|
"html-minifier-terser": "^7.2.0",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.22.2",
|
||||||
"puppeteer-core": "^22.15.0",
|
"puppeteer-core": "^23.11.1",
|
||||||
"standalone-electron-types": "^1.0.0",
|
"standalone-electron-types": "^1.0.0",
|
||||||
"stylelint": "^16.8.1",
|
"stylelint": "^16.12.0",
|
||||||
"stylelint-config-standard": "^36.0.1",
|
"stylelint-config-standard": "^36.0.1",
|
||||||
"ts-patch": "^3.2.1",
|
"ts-patch": "^3.3.0",
|
||||||
"ts-pattern": "^5.3.1",
|
"ts-pattern": "^5.6.0",
|
||||||
"tsx": "^4.16.5",
|
"tsx": "^4.19.2",
|
||||||
"type-fest": "^4.23.0",
|
"type-fest": "^4.31.0",
|
||||||
"typescript": "^5.5.4",
|
"typescript": "^5.7.2",
|
||||||
"typescript-eslint": "^8.0.0",
|
"typescript-eslint": "^8.19.0",
|
||||||
"typescript-transform-paths": "^3.4.7",
|
"typescript-transform-paths": "^3.5.3",
|
||||||
"zip-local": "^0.3.5"
|
"zip-local": "^0.3.5"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.1.0",
|
"packageManager": "pnpm@9.1.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"eslint@9.8.0": "patches/eslint@9.8.0.patch",
|
"eslint@9.17.0": "patches/eslint@9.17.0.patch",
|
||||||
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
|
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
|
||||||
},
|
},
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
|
|
3259
pnpm-lock.yaml
generated
3259
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -37,7 +37,8 @@ const CANARY = process.env.USE_CANARY === "true";
|
||||||
|
|
||||||
const browser = await pup.launch({
|
const browser = await pup.launch({
|
||||||
headless: true,
|
headless: true,
|
||||||
executablePath: process.env.CHROMIUM_BIN
|
executablePath: process.env.CHROMIUM_BIN,
|
||||||
|
args: ["--no-sandbox"]
|
||||||
});
|
});
|
||||||
|
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
|
|
|
@ -57,7 +57,7 @@ const Badges = new Set<ProfileBadge>();
|
||||||
* Register a new badge with the Badges API
|
* Register a new badge with the Badges API
|
||||||
* @param badge The badge to register
|
* @param badge The badge to register
|
||||||
*/
|
*/
|
||||||
export function addBadge(badge: ProfileBadge) {
|
export function addProfileBadge(badge: ProfileBadge) {
|
||||||
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
|
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
|
||||||
Badges.add(badge);
|
Badges.add(badge);
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ export function addBadge(badge: ProfileBadge) {
|
||||||
* Unregister a badge from the Badges API
|
* Unregister a badge from the Badges API
|
||||||
* @param badge The badge to remove
|
* @param badge The badge to remove
|
||||||
*/
|
*/
|
||||||
export function removeBadge(badge: ProfileBadge) {
|
export function removeProfileBadge(badge: ProfileBadge) {
|
||||||
return Badges.delete(badge);
|
return Badges.delete(badge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,20 +100,3 @@ export interface BadgeUserArgs {
|
||||||
userId: string;
|
userId: string;
|
||||||
guildId: string;
|
guildId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConnectedAccount {
|
|
||||||
type: string;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
verified: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Profile {
|
|
||||||
connectedAccounts: ConnectedAccount[];
|
|
||||||
premiumType: number;
|
|
||||||
premiumSince: string;
|
|
||||||
premiumGuildSince?: any;
|
|
||||||
lastFetched: number;
|
|
||||||
profileFetchFailed: boolean;
|
|
||||||
application?: any;
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,9 +9,9 @@ import "./ChatButton.css";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { waitFor } from "@webpack";
|
import { waitFor } from "@webpack";
|
||||||
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||||
import { Channel } from "discord-types/general";
|
import { Channel } from "discord-types/general";
|
||||||
import { HTMLProps, MouseEventHandler, ReactNode } from "react";
|
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
|
||||||
|
|
||||||
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
|
let ChannelTextAreaClasses: Record<"button" | "buttonContainer", string>;
|
||||||
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);
|
waitFor(["buttonContainer", "channelTextArea"], m => ChannelTextAreaClasses = m);
|
||||||
|
@ -74,9 +74,9 @@ export interface ChatBarProps {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChatBarButton = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
||||||
|
|
||||||
const buttonFactories = new Map<string, ChatBarButton>();
|
const buttonFactories = new Map<string, ChatBarButtonFactory>();
|
||||||
const logger = new Logger("ChatButtons");
|
const logger = new Logger("ChatButtons");
|
||||||
|
|
||||||
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||||
|
@ -91,7 +91,7 @@ export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button);
|
export const addChatBarButton = (id: string, button: ChatBarButtonFactory) => buttonFactories.set(id, button);
|
||||||
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
||||||
|
|
||||||
export interface ChatBarButtonProps {
|
export interface ChatBarButtonProps {
|
||||||
|
@ -110,7 +110,7 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
||||||
<Button
|
<Button
|
||||||
aria-label={props.tooltip}
|
aria-label={props.tooltip}
|
||||||
size=""
|
size=""
|
||||||
look={ButtonLooks.BLANK}
|
look={Button.Looks.BLANK}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
|
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
|
||||||
|
|
|
@ -24,13 +24,13 @@ import type { ReactElement } from "react";
|
||||||
* @param children The rendered context menu elements
|
* @param children The rendered context menu elements
|
||||||
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
||||||
*/
|
*/
|
||||||
export type NavContextMenuPatchCallback = (children: Array<ReactElement | null>, ...args: Array<any>) => void;
|
export type NavContextMenuPatchCallback = (children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
|
||||||
/**
|
/**
|
||||||
* @param navId The navId of the context menu being patched
|
* @param navId The navId of the context menu being patched
|
||||||
* @param children The rendered context menu elements
|
* @param children The rendered context menu elements
|
||||||
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
* @param args Any arguments passed into making the context menu, like the guild, channel, user or message for example
|
||||||
*/
|
*/
|
||||||
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement | null>, ...args: Array<any>) => void;
|
export type GlobalContextMenuPatchCallback = (navId: string, children: Array<ReactElement<any> | null>, ...args: Array<any>) => void;
|
||||||
|
|
||||||
const ContextMenuLogger = new Logger("ContextMenu");
|
const ContextMenuLogger = new Logger("ContextMenu");
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ export function addGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback)
|
||||||
* @returns Whether the patch was successfully removed from the context menu(s)
|
* @returns Whether the patch was successfully removed from the context menu(s)
|
||||||
*/
|
*/
|
||||||
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
|
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
|
||||||
const navIds = Array.isArray(navId) ? navId : [navId as string];
|
const navIds: string[] = Array.isArray(navId) ? navId : [navId];
|
||||||
|
|
||||||
const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false);
|
const results = navIds.map(id => navPatches.get(id)?.delete(patch) ?? false);
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
|
||||||
* @param children The context menu children
|
* @param children The context menu children
|
||||||
* @param matchSubstring Whether to check if the id is a substring of the child id
|
* @param matchSubstring Whether to check if the id is a substring of the child id
|
||||||
*/
|
*/
|
||||||
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null | undefined>, matchSubstring = false): Array<ReactElement | null | undefined> | null {
|
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement<any> | null | undefined>, matchSubstring = false): Array<ReactElement<any> | null | undefined> | null {
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
if (child == null) continue;
|
if (child == null) continue;
|
||||||
|
|
||||||
|
@ -122,9 +122,9 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContextMenuProps {
|
interface ContextMenuProps {
|
||||||
contextMenuApiArguments?: Array<any>;
|
contextMenuAPIArguments?: Array<any>;
|
||||||
navId: string;
|
navId: string;
|
||||||
children: Array<ReactElement | null>;
|
children: Array<ReactElement<any> | null>;
|
||||||
"aria-label": string;
|
"aria-label": string;
|
||||||
onSelect: (() => void) | undefined;
|
onSelect: (() => void) | undefined;
|
||||||
onClose: (callback: (...args: Array<any>) => any) => void;
|
onClose: (callback: (...args: Array<any>) => any) => void;
|
||||||
|
@ -136,7 +136,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
children: cloneMenuChildren(props.children),
|
children: cloneMenuChildren(props.children),
|
||||||
};
|
};
|
||||||
|
|
||||||
props.contextMenuApiArguments ??= [];
|
props.contextMenuAPIArguments ??= [];
|
||||||
const contextMenuPatches = navPatches.get(props.navId);
|
const contextMenuPatches = navPatches.get(props.navId);
|
||||||
|
|
||||||
if (!Array.isArray(props.children)) props.children = [props.children];
|
if (!Array.isArray(props.children)) props.children = [props.children];
|
||||||
|
@ -144,7 +144,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
if (contextMenuPatches) {
|
if (contextMenuPatches) {
|
||||||
for (const patch of contextMenuPatches) {
|
for (const patch of contextMenuPatches) {
|
||||||
try {
|
try {
|
||||||
patch(props.children, ...props.contextMenuApiArguments);
|
patch(props.children, ...props.contextMenuAPIArguments);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
|
|
||||||
for (const patch of globalPatches) {
|
for (const patch of globalPatches) {
|
||||||
try {
|
try {
|
||||||
patch(props.navId, props.children, ...props.contextMenuApiArguments);
|
patch(props.navId, props.children, ...props.contextMenuAPIArguments);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error("Global patch errored,", err);
|
ContextMenuLogger.error("Global patch errored,", err);
|
||||||
}
|
}
|
||||||
|
@ -162,7 +162,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
return props;
|
return props;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cloneMenuChildren(obj: ReactElement | Array<ReactElement | null> | null) {
|
function cloneMenuChildren(obj: ReactElement<any> | Array<ReactElement<any> | null> | null) {
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
return obj.map(cloneMenuChildren);
|
return obj.map(cloneMenuChildren);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,9 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Channel, User } from "discord-types/general/index.js";
|
import { Channel, User } from "discord-types/general/index.js";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
interface DecoratorProps {
|
interface DecoratorProps {
|
||||||
activities: any[];
|
activities: any[];
|
||||||
|
@ -38,27 +40,32 @@ interface DecoratorProps {
|
||||||
user: User;
|
user: User;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
export type Decorator = (props: DecoratorProps) => JSX.Element | null;
|
export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null;
|
||||||
type OnlyIn = "guilds" | "dms";
|
type OnlyIn = "guilds" | "dms";
|
||||||
|
|
||||||
export const decorators = new Map<string, { decorator: Decorator, onlyIn?: OnlyIn; }>();
|
export const decorators = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>();
|
||||||
|
|
||||||
export function addDecorator(identifier: string, decorator: Decorator, onlyIn?: OnlyIn) {
|
export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) {
|
||||||
decorators.set(identifier, { decorator, onlyIn });
|
decorators.set(identifier, { render, onlyIn });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeDecorator(identifier: string) {
|
export function removeMemberListDecorator(identifier: string) {
|
||||||
decorators.delete(identifier);
|
decorators.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
|
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
|
||||||
const isInGuild = !!(props.guildId);
|
const isInGuild = !!(props.guildId);
|
||||||
return Array.from(decorators.values(), decoratorObj => {
|
return Array.from(
|
||||||
const { decorator, onlyIn } = decoratorObj;
|
decorators.entries(),
|
||||||
// this can most likely be done cleaner
|
([key, { render: Decorator, onlyIn }]) => {
|
||||||
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
|
if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild))
|
||||||
return decorator(props);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
});
|
|
||||||
|
return (
|
||||||
|
<ErrorBoundary noop key={key} message={`Failed to render ${key} Member List Decorator`}>
|
||||||
|
<Decorator {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -16,26 +16,29 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>;
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
export type Accessory = {
|
import { JSX, ReactNode } from "react";
|
||||||
callback: AccessoryCallback;
|
|
||||||
|
export type MessageAccessoryFactory = (props: Record<string, any>) => ReactNode;
|
||||||
|
export type MessageAccessory = {
|
||||||
|
render: MessageAccessoryFactory;
|
||||||
position?: number;
|
position?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const accessories = new Map<String, Accessory>();
|
export const accessories = new Map<string, MessageAccessory>();
|
||||||
|
|
||||||
export function addAccessory(
|
export function addMessageAccessory(
|
||||||
identifier: string,
|
identifier: string,
|
||||||
callback: AccessoryCallback,
|
render: MessageAccessoryFactory,
|
||||||
position?: number
|
position?: number
|
||||||
) {
|
) {
|
||||||
accessories.set(identifier, {
|
accessories.set(identifier, {
|
||||||
callback,
|
render,
|
||||||
position,
|
position,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeAccessory(identifier: string) {
|
export function removeMessageAccessory(identifier: string) {
|
||||||
accessories.delete(identifier);
|
accessories.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,15 +46,12 @@ export function _modifyAccessories(
|
||||||
elements: JSX.Element[],
|
elements: JSX.Element[],
|
||||||
props: Record<string, any>
|
props: Record<string, any>
|
||||||
) {
|
) {
|
||||||
for (const accessory of accessories.values()) {
|
for (const [key, accessory] of accessories.entries()) {
|
||||||
let accessories = accessory.callback(props);
|
const res = (
|
||||||
if (accessories == null)
|
<ErrorBoundary message={`Failed to render ${key} Message Accessory`} key={key}>
|
||||||
continue;
|
<accessory.render {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
if (!Array.isArray(accessories))
|
);
|
||||||
accessories = [accessories];
|
|
||||||
else if (accessories.length === 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
elements.splice(
|
elements.splice(
|
||||||
accessory.position != null
|
accessory.position != null
|
||||||
|
@ -60,7 +60,7 @@ export function _modifyAccessories(
|
||||||
: accessory.position
|
: accessory.position
|
||||||
: elements.length,
|
: elements.length,
|
||||||
0,
|
0,
|
||||||
...accessories.filter(e => e != null) as JSX.Element[]
|
res
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,9 +16,11 @@
|
||||||
* 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 ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Channel, Message } from "discord-types/general/index.js";
|
import { Channel, Message } from "discord-types/general/index.js";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
interface DecorationProps {
|
export interface MessageDecorationProps {
|
||||||
author: {
|
author: {
|
||||||
/**
|
/**
|
||||||
* Will be username if the user has no nickname
|
* Will be username if the user has no nickname
|
||||||
|
@ -44,20 +46,25 @@ interface DecorationProps {
|
||||||
message: Message;
|
message: Message;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
export type Decoration = (props: DecorationProps) => JSX.Element | null;
|
export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null;
|
||||||
|
|
||||||
export const decorations = new Map<string, Decoration>();
|
export const decorations = new Map<string, MessageDecorationFactory>();
|
||||||
|
|
||||||
export function addDecoration(identifier: string, decoration: Decoration) {
|
export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) {
|
||||||
decorations.set(identifier, decoration);
|
decorations.set(identifier, decoration);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeDecoration(identifier: string) {
|
export function removeMessageDecoration(identifier: string) {
|
||||||
decorations.delete(identifier);
|
decorations.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function __addDecorationsToMessage(props: DecorationProps): (JSX.Element | null)[] {
|
export function __addDecorationsToMessage(props: MessageDecorationProps): (JSX.Element | null)[] {
|
||||||
return [...decorations.values()].map(decoration => {
|
return Array.from(
|
||||||
return decoration(props);
|
decorations.entries(),
|
||||||
});
|
([key, Decoration]) => (
|
||||||
|
<ErrorBoundary noop message={`Failed to render ${key} Message Decoration`} key={key}>
|
||||||
|
<Decoration {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -73,11 +73,11 @@ export interface MessageExtra {
|
||||||
openWarningPopout: (props: any) => any;
|
openWarningPopout: (props: any) => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
export type MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
||||||
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
||||||
|
|
||||||
const sendListeners = new Set<SendListener>();
|
const sendListeners = new Set<MessageSendListener>();
|
||||||
const editListeners = new Set<EditListener>();
|
const editListeners = new Set<MessageEditListener>();
|
||||||
|
|
||||||
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
|
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
|
||||||
extra.replyOptions = replyOptions;
|
extra.replyOptions = replyOptions;
|
||||||
|
@ -111,29 +111,29 @@ export async function _handlePreEdit(channelId: string, messageId: string, messa
|
||||||
/**
|
/**
|
||||||
* Note: This event fires off before a message is sent, allowing you to edit the message.
|
* Note: This event fires off before a message is sent, allowing you to edit the message.
|
||||||
*/
|
*/
|
||||||
export function addPreSendListener(listener: SendListener) {
|
export function addMessagePreSendListener(listener: MessageSendListener) {
|
||||||
sendListeners.add(listener);
|
sendListeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Note: This event fires off before a message's edit is applied, allowing you to further edit the message.
|
* Note: This event fires off before a message's edit is applied, allowing you to further edit the message.
|
||||||
*/
|
*/
|
||||||
export function addPreEditListener(listener: EditListener) {
|
export function addMessagePreEditListener(listener: MessageEditListener) {
|
||||||
editListeners.add(listener);
|
editListeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
export function removePreSendListener(listener: SendListener) {
|
export function removeMessagePreSendListener(listener: MessageSendListener) {
|
||||||
return sendListeners.delete(listener);
|
return sendListeners.delete(listener);
|
||||||
}
|
}
|
||||||
export function removePreEditListener(listener: EditListener) {
|
export function removeMessagePreEditListener(listener: MessageEditListener) {
|
||||||
return editListeners.delete(listener);
|
return editListeners.delete(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Message clicks
|
// Message clicks
|
||||||
type ClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
|
export type MessageClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
|
||||||
|
|
||||||
const listeners = new Set<ClickListener>();
|
const listeners = new Set<MessageClickListener>();
|
||||||
|
|
||||||
export function _handleClick(message: Message, channel: Channel, event: MouseEvent) {
|
export function _handleClick(message: Message, channel: Channel, event: MouseEvent) {
|
||||||
// message object may be outdated, so (try to) fetch latest one
|
// message object may be outdated, so (try to) fetch latest one
|
||||||
|
@ -147,11 +147,11 @@ export function _handleClick(message: Message, channel: Channel, event: MouseEve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addClickListener(listener: ClickListener) {
|
export function addMessageClickListener(listener: MessageClickListener) {
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeClickListener(listener: ClickListener) {
|
export function removeMessageClickListener(listener: MessageClickListener) {
|
||||||
return listeners.delete(listener);
|
return listeners.delete(listener);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import type { ComponentType, MouseEventHandler } from "react";
|
||||||
|
|
||||||
const logger = new Logger("MessagePopover");
|
const logger = new Logger("MessagePopover");
|
||||||
|
|
||||||
export interface ButtonItem {
|
export interface MessagePopoverButtonItem {
|
||||||
key?: string,
|
key?: string,
|
||||||
label: string,
|
label: string,
|
||||||
icon: ComponentType<any>,
|
icon: ComponentType<any>,
|
||||||
|
@ -33,23 +33,23 @@ export interface ButtonItem {
|
||||||
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type getButtonItem = (message: Message) => ButtonItem | null;
|
export type MessagePopoverButtonFactory = (message: Message) => MessagePopoverButtonItem | null;
|
||||||
|
|
||||||
export const buttons = new Map<string, getButtonItem>();
|
export const buttons = new Map<string, MessagePopoverButtonFactory>();
|
||||||
|
|
||||||
export function addButton(
|
export function addMessagePopoverButton(
|
||||||
identifier: string,
|
identifier: string,
|
||||||
item: getButtonItem,
|
item: MessagePopoverButtonFactory,
|
||||||
) {
|
) {
|
||||||
buttons.set(identifier, item);
|
buttons.set(identifier, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeButton(identifier: string) {
|
export function removeMessagePopoverButton(identifier: string) {
|
||||||
buttons.delete(identifier);
|
buttons.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function _buildPopoverElements(
|
export function _buildPopoverElements(
|
||||||
Component: React.ComponentType<ButtonItem>,
|
Component: React.ComponentType<MessagePopoverButtonItem>,
|
||||||
message: Message
|
message: Message
|
||||||
) {
|
) {
|
||||||
const items: React.ReactNode[] = [];
|
const items: React.ReactNode[] = [];
|
||||||
|
|
|
@ -16,40 +16,36 @@
|
||||||
* 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 { Logger } from "@utils/Logger";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { ComponentType } from "react";
|
||||||
const logger = new Logger("ServerListAPI");
|
|
||||||
|
|
||||||
export const enum ServerListRenderPosition {
|
export const enum ServerListRenderPosition {
|
||||||
Above,
|
Above,
|
||||||
In,
|
In,
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderFunctionsAbove = new Set<Function>();
|
const componentsAbove = new Set<ComponentType>();
|
||||||
const renderFunctionsIn = new Set<Function>();
|
const componentsBelow = new Set<ComponentType>();
|
||||||
|
|
||||||
function getRenderFunctions(position: ServerListRenderPosition) {
|
function getRenderFunctions(position: ServerListRenderPosition) {
|
||||||
return position === ServerListRenderPosition.Above ? renderFunctionsAbove : renderFunctionsIn;
|
return position === ServerListRenderPosition.Above ? componentsAbove : componentsBelow;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
export function addServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||||
getRenderFunctions(position).add(renderFunction);
|
getRenderFunctions(position).add(renderFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||||
getRenderFunctions(position).delete(renderFunction);
|
getRenderFunctions(position).delete(renderFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const renderAll = (position: ServerListRenderPosition) => {
|
export const renderAll = (position: ServerListRenderPosition) => {
|
||||||
const ret: Array<JSX.Element> = [];
|
return Array.from(
|
||||||
|
getRenderFunctions(position),
|
||||||
for (const renderFunction of getRenderFunctions(position)) {
|
(Component, i) => (
|
||||||
try {
|
<ErrorBoundary noop key={i}>
|
||||||
ret.unshift(renderFunction());
|
<Component />
|
||||||
} catch (e) {
|
</ErrorBoundary>
|
||||||
logger.error("Failed to render server list element:", e);
|
)
|
||||||
}
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
};
|
|
@ -23,7 +23,7 @@ import { Logger } from "@utils/Logger";
|
||||||
import { mergeDefaults } from "@utils/mergeDefaults";
|
import { mergeDefaults } from "@utils/mergeDefaults";
|
||||||
import { putCloudSettings } from "@utils/settingsSync";
|
import { putCloudSettings } from "@utils/settingsSync";
|
||||||
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
|
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
|
||||||
import { React } from "@webpack/common";
|
import { React, useEffect } from "@webpack/common";
|
||||||
|
|
||||||
import plugins from "~plugins";
|
import plugins from "~plugins";
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ export const Settings = SettingsStore.store;
|
||||||
export function useSettings(paths?: UseSettings<Settings>[]) {
|
export function useSettings(paths?: UseSettings<Settings>[]) {
|
||||||
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
const [, forceUpdate] = React.useReducer(() => ({}), {});
|
||||||
|
|
||||||
React.useEffect(() => {
|
useEffect(() => {
|
||||||
if (paths) {
|
if (paths) {
|
||||||
paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
|
paths.forEach(p => SettingsStore.addChangeListener(p, forceUpdate));
|
||||||
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
|
return () => paths.forEach(p => SettingsStore.removeChangeListener(p, forceUpdate));
|
||||||
|
@ -200,7 +200,7 @@ export function useSettings(paths?: UseSettings<Settings>[]) {
|
||||||
SettingsStore.addGlobalChangeListener(forceUpdate);
|
SettingsStore.addGlobalChangeListener(forceUpdate);
|
||||||
return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
|
return () => SettingsStore.removeGlobalChangeListener(forceUpdate);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [paths]);
|
||||||
|
|
||||||
return SettingsStore.store;
|
return SettingsStore.store;
|
||||||
}
|
}
|
||||||
|
@ -220,6 +220,17 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function migratePluginSetting(pluginName: string, oldSetting: string, newSetting: string) {
|
||||||
|
const settings = SettingsStore.plain.plugins[pluginName];
|
||||||
|
if (!settings) return;
|
||||||
|
|
||||||
|
if (!Object.hasOwn(settings, oldSetting) || Object.hasOwn(settings, newSetting)) return;
|
||||||
|
|
||||||
|
settings[newSetting] = settings[oldSetting];
|
||||||
|
delete settings[oldSetting];
|
||||||
|
SettingsStore.markAsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
export function definePluginSettings<
|
export function definePluginSettings<
|
||||||
Def extends SettingsDefinition,
|
Def extends SettingsDefinition,
|
||||||
Checks extends SettingsChecks<Def>,
|
Checks extends SettingsChecks<Def>,
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function Badge({ text, color }): JSX.Element {
|
export function Badge({ text, color }) {
|
||||||
return (
|
return (
|
||||||
<div className="vc-plugins-badge" style={{
|
<div className="vc-plugins-badge" style={{
|
||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
|
|
|
@ -27,7 +27,7 @@ interface Props<T = any> {
|
||||||
/** Render nothing if an error occurs */
|
/** Render nothing if an error occurs */
|
||||||
noop?: boolean;
|
noop?: boolean;
|
||||||
/** Fallback component to render if an error occurs */
|
/** Fallback component to render if an error occurs */
|
||||||
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; }>>;
|
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; wrappedProps: T; }>>;
|
||||||
/** called when an error occurs. The props property is only available if using .wrap */
|
/** called when an error occurs. The props property is only available if using .wrap */
|
||||||
onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;
|
onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;
|
||||||
/** Custom error message */
|
/** Custom error message */
|
||||||
|
@ -70,8 +70,7 @@ const ErrorBoundary = LazyComponent(() => {
|
||||||
|
|
||||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||||
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
||||||
logger.error("A component threw an Error\n", error);
|
logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack);
|
||||||
logger.error("Component Stack", errorInfo.componentStack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -80,10 +79,14 @@ const ErrorBoundary = LazyComponent(() => {
|
||||||
if (this.props.noop) return null;
|
if (this.props.noop) return null;
|
||||||
|
|
||||||
if (this.props.fallback)
|
if (this.props.fallback)
|
||||||
return <this.props.fallback
|
return (
|
||||||
children={this.props.children}
|
<this.props.fallback
|
||||||
|
wrappedProps={this.props.wrappedProps}
|
||||||
{...this.state}
|
{...this.state}
|
||||||
/>;
|
>
|
||||||
|
{this.props.children}
|
||||||
|
</this.props.fallback>
|
||||||
|
);
|
||||||
|
|
||||||
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";
|
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CSSProperties } from "react";
|
import { CSSProperties, JSX } from "react";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
columns: number;
|
columns: number;
|
||||||
|
|
|
@ -27,7 +27,7 @@ export function Heart() {
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="#db61a2"
|
fill="#db61a2"
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z"
|
d="M4.25 2.5c-1.336 0-2.75 1.164-2.75 3 0 2.15 1.58 4.144 3.365 5.682A20.565 20.565 0 008 13.393a20.561 20.561 0 003.135-2.211C12.92 9.644 14.5 7.65 14.5 5.5c0-1.836-1.414-3-2.75-3-1.373 0-2.609.986-3.029 2.456a.75.75 0 01-1.442 0C6.859 3.486 5.623 2.5 4.25 2.5zM8 14.25l-.345.666-.002-.001-.006-.003-.018-.01a7.643 7.643 0 01-.31-.17 22.075 22.075 0 01-3.434-2.414C2.045 10.731 0 8.35 0 5.5 0 2.836 2.086 1 4.25 1 5.797 1 7.153 1.802 8 3.02 8.847 1.802 10.203 1 11.75 1 13.914 1 16 2.836 16 5.5c0 2.85-2.045 5.231-3.885 6.818a22.08 22.08 0 01-3.744 2.584l-.018.01-.006.003h-.002L8 14.25zm0 0l.345.666a.752.752 0 01-.69 0L8 14.25z"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
@ -20,7 +20,7 @@ import "./iconStyles.css";
|
||||||
|
|
||||||
import { getIntlMessage } from "@utils/discord";
|
import { getIntlMessage } from "@utils/discord";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import type { PropsWithChildren } from "react";
|
import type { JSX, PropsWithChildren } from "react";
|
||||||
|
|
||||||
interface BaseIconProps extends IconProps {
|
interface BaseIconProps extends IconProps {
|
||||||
viewBox: string;
|
viewBox: string;
|
||||||
|
@ -55,7 +55,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
|
||||||
className={classes(className, "vc-link-icon")}
|
className={classes(className, "vc-link-icon")}
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g fill="none" fill-rule="evenodd">
|
<g fill="none" fillRule="evenodd">
|
||||||
<path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" />
|
<path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" />
|
||||||
<rect width={width} height={height} />
|
<rect width={width} height={height} />
|
||||||
</g>
|
</g>
|
||||||
|
@ -122,8 +122,8 @@ export function InfoIcon(props: IconProps) {
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
d="M23 12a11 11 0 1 1-22 0 11 11 0 0 1 22 0Zm-9.5-4.75a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm-.77 3.96a1 1 0 1 0-1.96-.42l-1.04 4.86a2.77 2.77 0 0 0 4.31 2.83l.24-.17a1 1 0 1 0-1.16-1.62l-.24.17a.77.77 0 0 1-1.2-.79l1.05-4.86Z" clip-rule="evenodd"
|
d="M23 12a11 11 0 1 1-22 0 11 11 0 0 1 22 0Zm-9.5-4.75a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Zm-.77 3.96a1 1 0 1 0-1.96-.42l-1.04 4.86a2.77 2.77 0 0 0 4.31 2.83l.24-.17a1 1 0 1 0-1.16-1.62l-.24.17a.77.77 0 0 1-1.2-.79l1.05-4.86Z" clipRule="evenodd"
|
||||||
/>
|
/>
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
@ -212,9 +212,9 @@ export function CogWheel(props: IconProps) {
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
fill-rule="evenodd"
|
fillRule="evenodd"
|
||||||
d="M10.56 1.1c-.46.05-.7.53-.64.98.18 1.16-.19 2.2-.98 2.53-.8.33-1.79-.15-2.49-1.1-.27-.36-.78-.52-1.14-.24-.77.59-1.45 1.27-2.04 2.04-.28.36-.12.87.24 1.14.96.7 1.43 1.7 1.1 2.49-.33.8-1.37 1.16-2.53.98-.45-.07-.93.18-.99.64a11.1 11.1 0 0 0 0 2.88c.06.46.54.7.99.64 1.16-.18 2.2.19 2.53.98.33.8-.14 1.79-1.1 2.49-.36.27-.52.78-.24 1.14.59.77 1.27 1.45 2.04 2.04.36.28.87.12 1.14-.24.7-.95 1.7-1.43 2.49-1.1.8.33 1.16 1.37.98 2.53-.07.45.18.93.64.99a11.1 11.1 0 0 0 2.88 0c.46-.06.7-.54.64-.99-.18-1.16.19-2.2.98-2.53.8-.33 1.79.14 2.49 1.1.27.36.78.52 1.14.24.77-.59 1.45-1.27 2.04-2.04.28-.36.12-.87-.24-1.14-.96-.7-1.43-1.7-1.1-2.49.33-.8 1.37-1.16 2.53-.98.45.07.93-.18.99-.64a11.1 11.1 0 0 0 0-2.88c-.06-.46-.54-.7-.99-.64-1.16.18-2.2-.19-2.53-.98-.33-.8.14-1.79 1.1-2.49.36-.27.52-.78.24-1.14a11.07 11.07 0 0 0-2.04-2.04c-.36-.28-.87-.12-1.14.24-.7.96-1.7 1.43-2.49 1.1-.8-.33-1.16-1.37-.98-2.53.07-.45-.18-.93-.64-.99a11.1 11.1 0 0 0-2.88 0ZM16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
|
d="M10.56 1.1c-.46.05-.7.53-.64.98.18 1.16-.19 2.2-.98 2.53-.8.33-1.79-.15-2.49-1.1-.27-.36-.78-.52-1.14-.24-.77.59-1.45 1.27-2.04 2.04-.28.36-.12.87.24 1.14.96.7 1.43 1.7 1.1 2.49-.33.8-1.37 1.16-2.53.98-.45-.07-.93.18-.99.64a11.1 11.1 0 0 0 0 2.88c.06.46.54.7.99.64 1.16-.18 2.2.19 2.53.98.33.8-.14 1.79-1.1 2.49-.36.27-.52.78-.24 1.14.59.77 1.27 1.45 2.04 2.04.36.28.87.12 1.14-.24.7-.95 1.7-1.43 2.49-1.1.8.33 1.16 1.37.98 2.53-.07.45.18.93.64.99a11.1 11.1 0 0 0 2.88 0c.46-.06.7-.54.64-.99-.18-1.16.19-2.2.98-2.53.8-.33 1.79.14 2.49 1.1.27.36.78.52 1.14.24.77-.59 1.45-1.27 2.04-2.04.28-.36.12-.87-.24-1.14-.96-.7-1.43-1.7-1.1-2.49.33-.8 1.37-1.16 2.53-.98.45.07.93-.18.99-.64a11.1 11.1 0 0 0 0-2.88c-.06-.46-.54-.7-.99-.64-1.16.18-2.2-.19-2.53-.98-.33-.8.14-1.79 1.1-2.49.36-.27.52-.78.24-1.14a11.07 11.07 0 0 0-2.04-2.04c-.36-.28-.87-.12-1.14.24-.7.96-1.7 1.43-2.49 1.1-.8-.33-1.16-1.37-.98-2.53.07-.45-.18-.93-.64-.99a11.1 11.1 0 0 0-2.88 0ZM16 12a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"
|
||||||
clip-rule="evenodd"
|
clipRule="evenodd"
|
||||||
/>
|
/>
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
@ -262,7 +262,7 @@ export function PlusIcon(props: IconProps) {
|
||||||
viewBox="0 0 18 18"
|
viewBox="0 0 18 18"
|
||||||
>
|
>
|
||||||
<polygon
|
<polygon
|
||||||
fill-rule="nonzero"
|
fillRule="nonzero"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
points="15 10 10 10 10 15 8 15 8 10 3 10 3 8 8 8 8 3 10 3 10 8 15 8"
|
points="15 10 10 10 10 15 8 15 8 10 3 10 3 8 8 8 8 3 10 3 10 8 15 8"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -44,7 +44,7 @@ function ContributorModal({ user }: { user: User; }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!profile && !user.bot && user.id)
|
if (!profile && !user.bot && user.id)
|
||||||
fetchUserProfile(user.id);
|
fetchUserProfile(user.id);
|
||||||
}, [user.id]);
|
}, [user.id, user.bot, profile]);
|
||||||
|
|
||||||
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
|
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
|
||||||
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;
|
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;
|
||||||
|
|
|
@ -37,6 +37,7 @@ import { Constructor } from "type-fest";
|
||||||
import { PluginMeta } from "~plugins";
|
import { PluginMeta } from "~plugins";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ISettingCustomElementProps,
|
||||||
ISettingElementProps,
|
ISettingElementProps,
|
||||||
SettingBooleanComponent,
|
SettingBooleanComponent,
|
||||||
SettingCustomComponent,
|
SettingCustomComponent,
|
||||||
|
@ -74,14 +75,15 @@ function makeDummyUser(user: { username: string; id?: string; avatar?: string; }
|
||||||
return newUser;
|
return newUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any>>> = {
|
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any> | ISettingCustomElementProps<any>>> = {
|
||||||
[OptionType.STRING]: SettingTextComponent,
|
[OptionType.STRING]: SettingTextComponent,
|
||||||
[OptionType.NUMBER]: SettingNumericComponent,
|
[OptionType.NUMBER]: SettingNumericComponent,
|
||||||
[OptionType.BIGINT]: SettingNumericComponent,
|
[OptionType.BIGINT]: SettingNumericComponent,
|
||||||
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
||||||
[OptionType.SELECT]: SettingSelectComponent,
|
[OptionType.SELECT]: SettingSelectComponent,
|
||||||
[OptionType.SLIDER]: SettingSliderComponent,
|
[OptionType.SLIDER]: SettingSliderComponent,
|
||||||
[OptionType.COMPONENT]: SettingCustomComponent
|
[OptionType.COMPONENT]: SettingCustomComponent,
|
||||||
|
[OptionType.CUSTOM]: () => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
||||||
|
@ -109,7 +111,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
setAuthors(a => [...a, author]);
|
setAuthors(a => [...a, author]);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}, []);
|
}, [plugin.authors]);
|
||||||
|
|
||||||
async function saveAndClose() {
|
async function saveAndClose() {
|
||||||
if (!plugin.options) {
|
if (!plugin.options) {
|
||||||
|
@ -129,7 +131,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
for (const [key, value] of Object.entries(tempSettings)) {
|
for (const [key, value] of Object.entries(tempSettings)) {
|
||||||
const option = plugin.options[key];
|
const option = plugin.options[key];
|
||||||
pluginSettings[key] = value;
|
pluginSettings[key] = value;
|
||||||
option?.onChange?.(value);
|
|
||||||
|
if (option.type === OptionType.CUSTOM) continue;
|
||||||
if (option?.restartNeeded) restartNeeded = true;
|
if (option?.restartNeeded) restartNeeded = true;
|
||||||
}
|
}
|
||||||
if (restartNeeded) onRestartNeeded();
|
if (restartNeeded) onRestartNeeded();
|
||||||
|
@ -141,7 +144,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
||||||
} else {
|
} else {
|
||||||
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
||||||
if (setting.hidden) return null;
|
if (setting.type === OptionType.CUSTOM || setting.hidden) return null;
|
||||||
|
|
||||||
function onChange(newValue: any) {
|
function onChange(newValue: any) {
|
||||||
setTempSettings(s => ({ ...s, [key]: newValue }));
|
setTempSettings(s => ({ ...s, [key]: newValue }));
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
|
|
||||||
import { PluginOptionComponent } from "@utils/types";
|
import { PluginOptionComponent } from "@utils/types";
|
||||||
|
|
||||||
import { ISettingElementProps } from ".";
|
import { ISettingCustomElementProps } from ".";
|
||||||
|
|
||||||
export function SettingCustomComponent({ option, onChange, onError }: ISettingElementProps<PluginOptionComponent>) {
|
export function SettingCustomComponent({ option, onChange, onError }: ISettingCustomElementProps<PluginOptionComponent>) {
|
||||||
return option.component({ setValue: onChange, setError: onError, option });
|
return option.component({ setValue: onChange, setError: onError, option });
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { DefinedSettings, PluginOptionBase } from "@utils/types";
|
import { DefinedSettings, PluginOptionBase } from "@utils/types";
|
||||||
|
|
||||||
export interface ISettingElementProps<T extends PluginOptionBase> {
|
interface ISettingElementPropsBase<T> {
|
||||||
option: T;
|
option: T;
|
||||||
onChange(newValue: any): void;
|
onChange(newValue: any): void;
|
||||||
pluginSettings: {
|
pluginSettings: {
|
||||||
|
@ -30,6 +30,9 @@ export interface ISettingElementProps<T extends PluginOptionBase> {
|
||||||
definedSettings?: DefinedSettings;
|
definedSettings?: DefinedSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ISettingElementProps<T extends PluginOptionBase> = ISettingElementPropsBase<T>;
|
||||||
|
export type ISettingCustomElementProps<T extends Omit<PluginOptionBase, "description" | "placeholder">> = ISettingElementPropsBase<T>;
|
||||||
|
|
||||||
export * from "../../Badge";
|
export * from "../../Badge";
|
||||||
export * from "./SettingBooleanComponent";
|
export * from "./SettingBooleanComponent";
|
||||||
export * from "./SettingCustomComponent";
|
export * from "./SettingCustomComponent";
|
||||||
|
|
|
@ -35,6 +35,7 @@ import { useAwaiter } from "@utils/react";
|
||||||
import { Plugin } from "@utils/types";
|
import { Plugin } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
|
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
import Plugins, { ExcludedPlugins } from "~plugins";
|
import Plugins, { ExcludedPlugins } from "~plugins";
|
||||||
|
|
||||||
|
@ -387,7 +388,7 @@ function makeDependencyList(deps: string[]) {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Forms.FormText>This plugin is required by:</Forms.FormText>
|
<Forms.FormText>This plugin is required by:</Forms.FormText>
|
||||||
{deps.map((dep: string) => <Forms.FormText className={cl("dep-text")}>{dep}</Forms.FormText>)}
|
{deps.map((dep: string) => <Forms.FormText key={dep} className={cl("dep-text")}>{dep}</Forms.FormText>)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,9 +111,9 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderDiff() {
|
function renderDiff() {
|
||||||
return diff?.map(p => {
|
return diff?.map((p, idx) => {
|
||||||
const color = p.added ? "lime" : p.removed ? "red" : "grey";
|
const color = p.added ? "lime" : p.removed ? "red" : "grey";
|
||||||
return <div style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
|
return <div key={idx} style={{ color, userSelect: "text", wordBreak: "break-all", lineBreak: "anywhere" }}>{p.value}</div>;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
|
||||||
title: "Oops!",
|
title: "Oops!",
|
||||||
body: (
|
body: (
|
||||||
<ErrorCard>
|
<ErrorCard>
|
||||||
{err.split("\n").map(line => <div>{Parser.parse(line)}</div>)}
|
{err.split("\n").map((line, idx) => <div key={idx}>{Parser.parse(line)}</div>)}
|
||||||
</ErrorCard>
|
</ErrorCard>
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
@ -87,7 +87,7 @@ function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof
|
||||||
return (
|
return (
|
||||||
<Card style={{ padding: "0 0.5em" }}>
|
<Card style={{ padding: "0 0.5em" }}>
|
||||||
{updates.map(({ hash, author, message }) => (
|
{updates.map(({ hash, author, message }) => (
|
||||||
<div style={{
|
<div key={hash} style={{
|
||||||
marginTop: "0.5em",
|
marginTop: "0.5em",
|
||||||
marginBottom: "0.5em"
|
marginBottom: "0.5em"
|
||||||
}}>
|
}}>
|
||||||
|
|
|
@ -62,14 +62,21 @@ async function runReporter() {
|
||||||
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let logMessage = searchType;
|
let logMessage = searchType;
|
||||||
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||||
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
|
if (args[0].$$vencordProps != null) {
|
||||||
else if (method === "mapMangledModule") {
|
logMessage += `(${args[0].$$vencordProps.map(arg => `"${arg}"`).join(", ")})`;
|
||||||
|
} else {
|
||||||
|
logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
||||||
|
}
|
||||||
|
} else if (method === "extractAndLoadChunks") {
|
||||||
|
logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
|
||||||
|
} else if (method === "mapMangledModule") {
|
||||||
const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null);
|
const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null);
|
||||||
|
|
||||||
logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`;
|
logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`;
|
||||||
|
} else {
|
||||||
|
logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
||||||
}
|
}
|
||||||
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
|
||||||
|
|
||||||
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { onceDefined } from "@shared/onceDefined";
|
import { onceDefined } from "@shared/onceDefined";
|
||||||
import electron, { app, BrowserWindowConstructorOptions, Menu, nativeTheme } from "electron";
|
import electron, { app, BrowserWindowConstructorOptions, Menu } from "electron";
|
||||||
import { dirname, join } from "path";
|
import { dirname, join } from "path";
|
||||||
|
|
||||||
import { initIpc } from "./ipcMain";
|
import { initIpc } from "./ipcMain";
|
||||||
|
@ -100,19 +100,6 @@ if (!IS_VANILLA) {
|
||||||
|
|
||||||
super(options);
|
super(options);
|
||||||
initIpc(this);
|
initIpc(this);
|
||||||
|
|
||||||
// Workaround for https://github.com/electron/electron/issues/43367. Vesktop also has its own workaround
|
|
||||||
// @TODO: Remove this when the issue is fixed
|
|
||||||
if (IS_DISCORD_DESKTOP) {
|
|
||||||
this.webContents.on("devtools-opened", () => {
|
|
||||||
if (!nativeTheme.shouldUseDarkColors) return;
|
|
||||||
|
|
||||||
nativeTheme.themeSource = "light";
|
|
||||||
setTimeout(() => {
|
|
||||||
nativeTheme.themeSource = "dark";
|
|
||||||
}, 100);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else super(options);
|
} else super(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,13 +71,16 @@ export async function installExt(id: string) {
|
||||||
// React Devtools v4.25
|
// React Devtools v4.25
|
||||||
// v4.27 is broken in Electron, see https://github.com/facebook/react/issues/25843
|
// v4.27 is broken in Electron, see https://github.com/facebook/react/issues/25843
|
||||||
// Unfortunately, Google does not serve old versions, so this is the only way
|
// Unfortunately, Google does not serve old versions, so this is the only way
|
||||||
|
// This zip file is pinned to long commit hash so it cannot be changed remotely
|
||||||
? "https://raw.githubusercontent.com/Vendicated/random-files/f6f550e4c58ac5f2012095a130406c2ab25b984d/fmkadmapgofadopljbjfkapdkoienihi.zip"
|
? "https://raw.githubusercontent.com/Vendicated/random-files/f6f550e4c58ac5f2012095a130406c2ab25b984d/fmkadmapgofadopljbjfkapdkoienihi.zip"
|
||||||
: `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=32`;
|
: `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=${process.versions.chrome}`;
|
||||||
|
|
||||||
const buf = await get(url, {
|
const buf = await get(url, {
|
||||||
headers: {
|
headers: {
|
||||||
"User-Agent": "Vencord (https://github.com/Vendicated/Vencord)"
|
"User-Agent": `Electron ${process.versions.electron} ~ Vencord (https://github.com/Vendicated/Vencord)`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
await extract(crxToZip(buf), extDir).catch(console.error);
|
await extract(crxToZip(buf), extDir).catch(console.error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { isPluginDev } from "@utils/misc";
|
import { isPluginDev } from "@utils/misc";
|
||||||
import { closeModal, Modals, openModal } from "@utils/modal";
|
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Forms, Toasts, UserStore } from "@webpack/common";
|
import { Forms, Toasts, UserStore } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
@ -79,7 +79,7 @@ export default definePlugin({
|
||||||
replace: "...$1.props,$& $1.image??"
|
replace: "...$1.props,$& $1.image??"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=text:(\i)\.description,.{0,200})children:/,
|
match: /(?<="aria-label":(\i)\.description,.{0,200})children:/,
|
||||||
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
|
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
|
||||||
},
|
},
|
||||||
// conditionally override their onClick with badge.onClick if it exists
|
// conditionally override their onClick with badge.onClick if it exists
|
||||||
|
@ -102,8 +102,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
userProfileBadge: ContributorBadge,
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
Vencord.Api.Badges.addBadge(ContributorBadge);
|
|
||||||
await loadBadges();
|
await loadBadges();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -143,8 +144,8 @@ export default definePlugin({
|
||||||
closeModal(modalKey);
|
closeModal(modalKey);
|
||||||
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
|
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
|
||||||
}}>
|
}}>
|
||||||
<Modals.ModalRoot {...props}>
|
<ModalRoot {...props}>
|
||||||
<Modals.ModalHeader>
|
<ModalHeader>
|
||||||
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
||||||
<Forms.FormTitle
|
<Forms.FormTitle
|
||||||
tag="h2"
|
tag="h2"
|
||||||
|
@ -158,8 +159,8 @@ export default definePlugin({
|
||||||
Vencord Donor
|
Vencord Donor
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Modals.ModalHeader>
|
</ModalHeader>
|
||||||
<Modals.ModalContent>
|
<ModalContent>
|
||||||
<Flex>
|
<Flex>
|
||||||
<img
|
<img
|
||||||
role="presentation"
|
role="presentation"
|
||||||
|
@ -182,13 +183,13 @@ export default definePlugin({
|
||||||
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</div>
|
</div>
|
||||||
</Modals.ModalContent>
|
</ModalContent>
|
||||||
<Modals.ModalFooter>
|
<ModalFooter>
|
||||||
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
||||||
<DonateButton />
|
<DonateButton />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Modals.ModalFooter>
|
</ModalFooter>
|
||||||
</Modals.ModalRoot>
|
</ModalRoot>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,11 +12,16 @@ export default definePlugin({
|
||||||
description: "API to add buttons to the chat input",
|
description: "API to add buttons to the chat input",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
|
|
||||||
patches: [{
|
patches: [
|
||||||
|
{
|
||||||
find: '"sticker")',
|
find: '"sticker")',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /return\(!\i\.\i&&(?=\(\i\.isDM.+?(\i)\.push\(.{0,50}"gift")/,
|
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||||
replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)&&"
|
match: /return\((!)?\i\.\i(?:\|\||&&)(?=\(\i\.isDM.+?(\i)\.push)/,
|
||||||
|
replace: (m, not, children) => not
|
||||||
|
? `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),true)&&`
|
||||||
|
: `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),false)||`
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,12 +34,22 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".Menu,{",
|
find: "navId:",
|
||||||
all: true,
|
all: true,
|
||||||
replacement: {
|
noWarn: true,
|
||||||
match: /Menu,{(?<=\.jsxs?\)\(\i\.Menu,{)/g,
|
replacement: [
|
||||||
replace: "$&contextMenuApiArguments:typeof arguments!=='undefined'?arguments:[],"
|
{
|
||||||
|
match: /navId:(?=.+?([,}].*?\)))/g,
|
||||||
|
replace: (m, rest) => {
|
||||||
|
// Check if this navId: match is a destructuring statement, ignore it if it is
|
||||||
|
const destructuringMatch = rest.match(/}=.+/);
|
||||||
|
if (destructuringMatch == null) {
|
||||||
|
return `contextMenuAPIArguments:typeof arguments!=='undefined'?arguments:[],${m}`;
|
||||||
|
}
|
||||||
|
return m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
68
src/plugins/_api/menuItemDemangler.ts
Normal file
68
src/plugins/_api/menuItemDemangler.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
// duplicate values have multiple branches with different types. Just include all to be safe
|
||||||
|
const nameMap = {
|
||||||
|
radio: "MenuRadioItem",
|
||||||
|
separator: "MenuSeparator",
|
||||||
|
checkbox: "MenuCheckboxItem",
|
||||||
|
groupstart: "MenuGroup",
|
||||||
|
|
||||||
|
control: "MenuControlItem",
|
||||||
|
compositecontrol: "MenuControlItem",
|
||||||
|
|
||||||
|
item: "MenuItem",
|
||||||
|
customitem: "MenuItem",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "MenuItemDemanglerAPI",
|
||||||
|
description: "Demangles Discord's Menu Item module",
|
||||||
|
authors: [Devs.Ven],
|
||||||
|
required: true,
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: '"Menu API',
|
||||||
|
replacement: {
|
||||||
|
match: /function.{0,80}type===(\i\.\i)\).{0,50}navigable:.+?Menu API/s,
|
||||||
|
replace: (m, mod) => {
|
||||||
|
const nameAssignments = [] as string[];
|
||||||
|
|
||||||
|
// if (t.type === m.MenuItem)
|
||||||
|
const typeCheckRe = canonicalizeMatch(/\(\i\.type===(\i\.\i)\)/g);
|
||||||
|
// push({type:"item"})
|
||||||
|
const pushTypeRe = /type:"(\w+)"/g;
|
||||||
|
|
||||||
|
let typeMatch: RegExpExecArray | null;
|
||||||
|
// for each if (t.type === ...)
|
||||||
|
while ((typeMatch = typeCheckRe.exec(m)) !== null) {
|
||||||
|
// extract the current menu item
|
||||||
|
const item = typeMatch[1];
|
||||||
|
// Set the starting index of the second regex to that of the first to start
|
||||||
|
// matching from after the if
|
||||||
|
pushTypeRe.lastIndex = typeCheckRe.lastIndex;
|
||||||
|
// extract the first type: "..."
|
||||||
|
const type = pushTypeRe.exec(m)?.[1];
|
||||||
|
if (type && type in nameMap) {
|
||||||
|
const name = nameMap[type];
|
||||||
|
nameAssignments.push(`Object.defineProperty(${item},"name",{value:"${name}"})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nameAssignments.length < 6) {
|
||||||
|
console.warn("[MenuItemDemanglerAPI] Expected to at least remap 6 items, only remapped", nameAssignments.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge all our redefines with the actual module
|
||||||
|
return `${nameAssignments.join(";")};${m}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
|
@ -31,20 +31,17 @@ export default definePlugin({
|
||||||
replace: (match, args) => "" +
|
replace: (match, args) => "" +
|
||||||
`async ${match}` +
|
`async ${match}` +
|
||||||
`if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` +
|
`if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` +
|
||||||
"return Promise.resolve({shoudClear:true,shouldRefocus:true});"
|
"return Promise.resolve({shouldClear:false,shouldRefocus:true});"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".handleSendMessage,onResize",
|
find: ".handleSendMessage,onResize",
|
||||||
replacement: {
|
replacement: {
|
||||||
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
|
// https://regex101.com/r/hBlXpl/1
|
||||||
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
|
match: /let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);(?<=\)\(({.+?})\)\.then.+?)/,
|
||||||
match: /(type:this\.props\.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptionsForReply\(\i\);)(?<=\)\(({.+?})\)\.then.+?)/,
|
replace: (m, parsedMessage, channel, replyOptions, extra) => m +
|
||||||
// props.chatInputType...then((async function(isMessageValid)... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); if(await Vencord.api...) return { shoudClear:true, shouldRefocus:true };
|
|
||||||
replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" +
|
|
||||||
`${rest1}async ${rest2}` +
|
|
||||||
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
|
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
|
||||||
"return{shoudClear:true,shouldRefocus:true};"
|
"return{shouldClear:false,shouldRefocus:true};"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -52,8 +49,7 @@ export default definePlugin({
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
|
match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
|
||||||
replace: (m, message, channel, event) =>
|
replace: (m, message, channel, event) =>
|
||||||
// the message param is shadowed by the event param, so need to alias them
|
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg,vcChan,${event});`
|
||||||
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg, vcChan, ${event});`
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -27,9 +27,9 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
|
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=:null),(.{0,40}togglePopout:.+?}\))\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/,
|
match: /(?<=:null),(.{0,40}togglePopout:.+?}\)),(.+?)\]}\):null,(?<=\((\i\.\i),{label:.+?:null,(\i&&!\i)\?\(0,\i\.jsxs?\)\(\i\.Fragment.+?message:(\i).+?)/,
|
||||||
replace: (_, ReactButton, ButtonComponent, showReactButton, message) => "" +
|
replace: (_, ReactButton, PotionButton, ButtonComponent, showReactButton, message) => "" +
|
||||||
`]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,`
|
`]}):null,Vencord.Api.MessagePopover._buildPopoverElements(${ButtonComponent},${message}),${showReactButton}?${ReactButton}:null,${showReactButton}&&${PotionButton},`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
|
match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
|
||||||
replace: "if($1.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&"
|
replace: "if($1?.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,6 +59,14 @@ export default definePlugin({
|
||||||
replace: "$&return;"
|
replace: "$&return;"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".BetterDiscord||null!=",
|
||||||
|
replacement: {
|
||||||
|
// Make hasClientMods return false
|
||||||
|
match: /(?=let \i=window;)/,
|
||||||
|
replace: "return false;"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,8 @@ export default definePlugin({
|
||||||
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
|
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||||
|
match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?(?:function\(\){return |\(\)=>))\2/,
|
||||||
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
|
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -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 { addAccessory } from "@api/MessageAccessories";
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
@ -34,6 +33,7 @@ import { makeCodeblock } from "@utils/text";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { checkForUpdates, isOutdated, update } from "@utils/updater";
|
import { checkForUpdates, isOutdated, update } from "@utils/updater";
|
||||||
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
|
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
import plugins, { PluginMeta } from "~plugins";
|
import plugins, { PluginMeta } from "~plugins";
|
||||||
|
@ -142,7 +142,7 @@ export default definePlugin({
|
||||||
required: true,
|
required: true,
|
||||||
description: "Helps us provide support to you",
|
description: "Helps us provide support to you",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["UserSettingsAPI", "MessageAccessoriesAPI"],
|
dependencies: ["UserSettingsAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
@ -235,23 +235,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
|
renderMessageAccessory(props) {
|
||||||
const userId = channel.getRecipientId();
|
|
||||||
if (!isPluginDev(userId)) return null;
|
|
||||||
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
|
||||||
Please do not private message Vencord plugin developers for support!
|
|
||||||
<br />
|
|
||||||
Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")}
|
|
||||||
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}, { noop: true }),
|
|
||||||
|
|
||||||
start() {
|
|
||||||
addAccessory("vencord-debug", props => {
|
|
||||||
const buttons = [] as JSX.Element[];
|
const buttons = [] as JSX.Element[];
|
||||||
|
|
||||||
const shouldAddUpdateButton =
|
const shouldAddUpdateButton =
|
||||||
|
@ -328,6 +312,20 @@ export default definePlugin({
|
||||||
return buttons.length
|
return buttons.length
|
||||||
? <Flex>{buttons}</Flex>
|
? <Flex>{buttons}</Flex>
|
||||||
: null;
|
: null;
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
|
||||||
|
const userId = channel.getRecipientId();
|
||||||
|
if (!isPluginDev(userId)) return null;
|
||||||
|
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
||||||
|
Please do not private message Vencord plugin developers for support!
|
||||||
|
<br />
|
||||||
|
Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")}
|
||||||
|
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}, { noop: true }),
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,14 +16,14 @@ import { User } from "discord-types/general";
|
||||||
interface UserProfileProps {
|
interface UserProfileProps {
|
||||||
popoutProps: Record<string, any>;
|
popoutProps: Record<string, any>;
|
||||||
currentUser: User;
|
currentUser: User;
|
||||||
originalPopout: () => React.ReactNode;
|
originalRenderPopout: () => React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cannot be undefined");
|
const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cannot be undefined");
|
||||||
const styles = findByPropsLazy("accountProfilePopoutWrapper");
|
const styles = findByPropsLazy("accountProfilePopoutWrapper");
|
||||||
|
|
||||||
let openAlternatePopout = false;
|
let openAlternatePopout = false;
|
||||||
let accountPanelRef: React.MutableRefObject<Record<PropertyKey, any> | null> = { current: null };
|
let accountPanelRef: React.RefObject<Record<PropertyKey, any> | null> = { current: null };
|
||||||
|
|
||||||
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
|
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
|
||||||
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
|
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
|
||||||
|
@ -73,19 +73,19 @@ export default definePlugin({
|
||||||
group: true,
|
group: true,
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?<=\.SIZE_32\)}\);)/,
|
match: /(?<=\.AVATAR_SIZE\);)/,
|
||||||
replace: "$self.useAccountPanelRef();"
|
replace: "$self.useAccountPanelRef();"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/,
|
match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/,
|
||||||
replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalPopout:()=>{${originalPopout}}})`
|
replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalRenderPopout:()=>{${originalPopout}}})`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /\.AVATAR,children:.+?(?=renderPopout:)/,
|
match: /\.AVATAR,children:.+?(?=renderPopout:)/,
|
||||||
replace: "$&onRequestClose:$self.onPopoutClose,"
|
replace: "$&onRequestClose:$self.onPopoutClose,"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=.avatarWrapper,)/,
|
match: /(?<=\.avatarWrapper,)/,
|
||||||
replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
|
replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -112,17 +112,17 @@ export default definePlugin({
|
||||||
openAlternatePopout = false;
|
openAlternatePopout = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalPopout }: UserProfileProps) => {
|
UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalRenderPopout }: UserProfileProps) => {
|
||||||
if (
|
if (
|
||||||
(settings.store.prioritizeServerProfile && openAlternatePopout) ||
|
(settings.store.prioritizeServerProfile && openAlternatePopout) ||
|
||||||
(!settings.store.prioritizeServerProfile && !openAlternatePopout)
|
(!settings.store.prioritizeServerProfile && !openAlternatePopout)
|
||||||
) {
|
) {
|
||||||
return originalPopout();
|
return originalRenderPopout();
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentChannel = getCurrentChannel();
|
const currentChannel = getCurrentChannel();
|
||||||
if (currentChannel?.getGuildId() == null) {
|
if (currentChannel?.getGuildId() == null) {
|
||||||
return originalPopout();
|
return originalRenderPopout();
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -41,10 +41,10 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Status emojis
|
// Status emojis
|
||||||
find: "#{intl::GUILD_OWNER}",
|
find: "#{intl::GUILD_OWNER}),children:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=\.activityEmoji,.+?animate:)\i/,
|
match: /(\.CUSTOM_STATUS.+?animate:)\i/,
|
||||||
replace: "!0"
|
replace: (_, rest) => `${rest}!0`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
import { execFile } from "child_process";
|
import { execFile } from "child_process";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ interface RemoteData {
|
||||||
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null;
|
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null;
|
||||||
|
|
||||||
const APPLE_MUSIC_BUNDLE_REGEX = /<script type="module" crossorigin src="([a-zA-Z0-9.\-/]+)"><\/script>/;
|
const APPLE_MUSIC_BUNDLE_REGEX = /<script type="module" crossorigin src="([a-zA-Z0-9.\-/]+)"><\/script>/;
|
||||||
const APPLE_MUSIC_TOKEN_REGEX = /\w+="([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)",\w+="x-apple-jingle-correlation-key"/;
|
const APPLE_MUSIC_TOKEN_REGEX = canonicalizeMatch(/Promise.allSettled\(\i\)\}const \i="([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)"/);
|
||||||
|
|
||||||
let cachedToken: string | undefined = undefined;
|
let cachedToken: string | undefined = undefined;
|
||||||
|
|
||||||
|
|
|
@ -17,14 +17,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { useStateFromStores } from "@webpack/common";
|
import { Animations, useStateFromStores } from "@webpack/common";
|
||||||
import type { CSSProperties } from "react";
|
import type { CSSProperties } from "react";
|
||||||
|
|
||||||
import { ExpandedGuildFolderStore, settings, SortedGuildStore, NestMode } from ".";
|
import { ExpandedGuildFolderStore, settings, SortedGuildStore, NestMode } from ".";
|
||||||
|
|
||||||
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
|
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
|
||||||
const Animations = findByPropsLazy("a", "animated", "useTransition");
|
|
||||||
const GuildsBar = findComponentByCodeLazy('("guildsnav")');
|
const GuildsBar = findComponentByCodeLazy('("guildsnav")');
|
||||||
|
|
||||||
function generateSidebar(guildsBarProps, expandedFolders, id: number) {
|
function generateSidebar(guildsBarProps, expandedFolders, id: number) {
|
||||||
|
|
|
@ -139,7 +139,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
|
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
|
||||||
{
|
{
|
||||||
match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/,
|
match: /lastTargetNode:\i\[\i\.length-1\].+?}\)(?::null)?\](?=}\))/,
|
||||||
replace: "$&.filter($self.makeGuildsBarGuildListFilter(arguments[0]?.betterFoldersId ?? 0))"
|
replace: "$&.filter($self.makeGuildsBarGuildListFilter(arguments[0]?.betterFoldersId ?? 0))"
|
||||||
},
|
},
|
||||||
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
|
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
|
||||||
|
@ -189,8 +189,8 @@ export default definePlugin({
|
||||||
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
|
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
|
||||||
{
|
{
|
||||||
predicate: () => !settings.store.keepIcons,
|
predicate: () => !settings.store.keepIcons,
|
||||||
match: /(?<=#{intl::SERVER_FOLDER_PLACEHOLDER}.+?useTransition\)\()/,
|
match: /(?=,\{from:\{height)/,
|
||||||
replace: "$self.shouldShowTransition(arguments[0])&&"
|
replace: "&&$self.shouldShowTransition(arguments[0])"
|
||||||
},
|
},
|
||||||
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
|
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
|
||||||
{
|
{
|
||||||
|
@ -201,7 +201,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
|
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
|
||||||
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||||
match: /(?<=\.wrapper,children:\[)/,
|
match: /(?<=\.isExpanded\),children:\[)/,
|
||||||
replace: "$self.shouldShowFolderIconAndBackground(arguments[0],arguments[0]?.betterFoldersExpandedIds)&&"
|
replace: "$self.shouldShowFolderIconAndBackground(arguments[0],arguments[0]?.betterFoldersExpandedIds)&&"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -83,7 +83,7 @@ export default definePlugin({
|
||||||
if (!role) return;
|
if (!role) return;
|
||||||
|
|
||||||
if (role.colorString) {
|
if (role.colorString) {
|
||||||
children.push(
|
children.unshift(
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="vc-copy-role-color"
|
id="vc-copy-role-color"
|
||||||
label="Copy Role Color"
|
label="Copy Role Color"
|
||||||
|
@ -93,6 +93,20 @@ export default definePlugin({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
|
||||||
|
children.unshift(
|
||||||
|
<Menu.MenuItem
|
||||||
|
id="vc-edit-role"
|
||||||
|
label="Edit Role"
|
||||||
|
action={async () => {
|
||||||
|
await GuildSettingsActions.open(guild.id, "ROLES");
|
||||||
|
GuildSettingsActions.selectRole(id);
|
||||||
|
}}
|
||||||
|
icon={PencilIcon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (role.icon) {
|
if (role.icon) {
|
||||||
children.push(
|
children.push(
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
|
@ -110,20 +124,6 @@ export default definePlugin({
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
|
|
||||||
children.push(
|
|
||||||
<Menu.MenuItem
|
|
||||||
id="vc-edit-role"
|
|
||||||
label="Edit Role"
|
|
||||||
action={async () => {
|
|
||||||
await GuildSettingsActions.open(guild.id, "ROLES");
|
|
||||||
GuildSettingsActions.selectRole(id);
|
|
||||||
}}
|
|
||||||
icon={PencilIcon}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -99,7 +99,7 @@ export const UnknownIcon = (props: React.PropsWithChildren<SVGProps<SVGSVGElemen
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
>
|
>
|
||||||
<path fill-rule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z" />
|
<path fillRule="evenodd" d="M4.475 5.458c-.284 0-.514-.237-.47-.517C4.28 3.24 5.576 2 7.825 2c2.25 0 3.767 1.36 3.767 3.215 0 1.344-.665 2.288-1.79 2.973-1.1.659-1.414 1.118-1.414 2.01v.03a.5.5 0 0 1-.5.5h-.77a.5.5 0 0 1-.5-.495l-.003-.2c-.043-1.221.477-2.001 1.645-2.712 1.03-.632 1.397-1.135 1.397-2.028 0-.979-.758-1.698-1.926-1.698-1.009 0-1.71.529-1.938 1.402-.066.254-.278.461-.54.461h-.777ZM7.496 14c.622 0 1.095-.474 1.095-1.09 0-.618-.473-1.092-1.095-1.092-.606 0-1.087.474-1.087 1.091S6.89 14 7.496 14Z" />
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ 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 definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { Constants, React, RestAPI, Tooltip } from "@webpack/common";
|
import { Constants, React, RestAPI, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
import { RenameButton } from "./components/RenameButton";
|
import { RenameButton } from "./components/RenameButton";
|
||||||
|
@ -34,7 +34,7 @@ const UserSettingsModal = findByPropsLazy("saveAccountChanges", "open");
|
||||||
const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer");
|
const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer");
|
||||||
const SessionIconClasses = findByPropsLazy("sessionIcon");
|
const SessionIconClasses = findByPropsLazy("sessionIcon");
|
||||||
|
|
||||||
const BlobMask = findExportedComponentLazy("BlobMask");
|
const BlobMask = findComponentByCodeLazy("!1,lowerBadgeSize:");
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
backgroundCheck: {
|
backgroundCheck: {
|
||||||
|
|
|
@ -101,8 +101,8 @@ export default definePlugin({
|
||||||
find: 'minimal:"contentColumnMinimal"',
|
find: 'minimal:"contentColumnMinimal"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /\(0,\i\.useTransition\)\((\i)/,
|
match: /(?=\(0,\i\.\i\)\((\i),\{from:\{position:"absolute")/,
|
||||||
replace: "(_cb=>_cb(void 0,$1))||$&"
|
replace: "(_cb=>_cb(void 0,$1))||"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /\i\.animated\.div/,
|
match: /\i\.animated\.div/,
|
||||||
|
@ -114,7 +114,7 @@ export default definePlugin({
|
||||||
{ // Load menu TOC eagerly
|
{ // Load menu TOC eagerly
|
||||||
find: "#{intl::USER_SETTINGS_WITH_BUILD_OVERRIDE}",
|
find: "#{intl::USER_SETTINGS_WITH_BUILD_OVERRIDE}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
|
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await [^};]*?\)\)).*?,(?=\1\(this)/,
|
||||||
replace: "$&(async ()=>$2)(),"
|
replace: "$&(async ()=>$2)(),"
|
||||||
},
|
},
|
||||||
predicate: () => settings.store.eagerLoad
|
predicate: () => settings.store.eagerLoad
|
||||||
|
@ -173,7 +173,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
map(render: (item: SettingsEntry) => ReactElement) {
|
map(render: (item: SettingsEntry) => ReactElement<any>) {
|
||||||
return items
|
return items
|
||||||
.filter(a => a.items.length > 0)
|
.filter(a => a.items.length > 0)
|
||||||
.map(({ label, items }) => {
|
.map(({ label, items }) => {
|
||||||
|
@ -181,11 +181,14 @@ export default definePlugin({
|
||||||
if (label) {
|
if (label) {
|
||||||
return (
|
return (
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
|
key={label}
|
||||||
id={label.replace(/\W/, "_")}
|
id={label.replace(/\W/, "_")}
|
||||||
label={label}
|
label={label}
|
||||||
children={children}
|
|
||||||
action={children[0].props.action}
|
action={children[0].props.action}
|
||||||
/>);
|
>
|
||||||
|
{children}
|
||||||
|
</Menu.MenuItem>
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return children;
|
return children;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '"ChannelAttachButton"',
|
find: '"ChannelAttachButton"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),\.\.\.(\i),/,
|
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/,
|
||||||
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
|
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -75,8 +75,8 @@ export default definePlugin({
|
||||||
patches: [{
|
patches: [{
|
||||||
find: "renderConnectionStatus(){",
|
find: "renderConnectionStatus(){",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=renderConnectionStatus\(\)\{.+\.channel,children:)\i(?=\})/,
|
match: /(renderConnectionStatus\(\){.+\.channel,children:)(.+?}\):\i)(?=}\))/,
|
||||||
replace: "[$&, $self.renderTimer(this.props.channel.id)]"
|
replace: "$1[$2,$self.renderTimer(this.props.channel.id)]"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addPreEditListener,
|
MessageObject
|
||||||
addPreSendListener,
|
|
||||||
MessageObject,
|
|
||||||
removePreEditListener,
|
|
||||||
removePreSendListener
|
|
||||||
} from "@api/MessageEvents";
|
} from "@api/MessageEvents";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
@ -36,7 +32,18 @@ export default definePlugin({
|
||||||
name: "ClearURLs",
|
name: "ClearURLs",
|
||||||
description: "Removes tracking garbage from URLs",
|
description: "Removes tracking garbage from URLs",
|
||||||
authors: [Devs.adryd],
|
authors: [Devs.adryd],
|
||||||
dependencies: ["MessageEventsAPI"],
|
|
||||||
|
start() {
|
||||||
|
this.createRules();
|
||||||
|
},
|
||||||
|
|
||||||
|
onBeforeMessageSend(_, msg) {
|
||||||
|
return this.onSend(msg);
|
||||||
|
},
|
||||||
|
|
||||||
|
onBeforeMessageEdit(_cid, _mid, msg) {
|
||||||
|
return this.onSend(msg);
|
||||||
|
},
|
||||||
|
|
||||||
escapeRegExp(str: string) {
|
escapeRegExp(str: string) {
|
||||||
return (str && reHasRegExpChar.test(str))
|
return (str && reHasRegExpChar.test(str))
|
||||||
|
@ -133,17 +140,4 @@ export default definePlugin({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
start() {
|
|
||||||
this.createRules();
|
|
||||||
this.preSend = addPreSendListener((_, msg) => this.onSend(msg));
|
|
||||||
this.preEdit = addPreEditListener((_cid, _mid, msg) =>
|
|
||||||
this.onSend(msg)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
removePreSendListener(this.preSend);
|
|
||||||
removePreEditListener(this.preEdit);
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -91,15 +91,12 @@ function ThemeSettings() {
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
color: {
|
color: {
|
||||||
description: "Color your Discord client theme will be based around. Light mode isn't supported",
|
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
default: "313338",
|
default: "313338",
|
||||||
component: () => <ThemeSettings />
|
component: ThemeSettings
|
||||||
},
|
},
|
||||||
resetColor: {
|
resetColor: {
|
||||||
description: "Reset Theme Color",
|
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
default: "313338",
|
|
||||||
component: () => (
|
component: () => (
|
||||||
<Button onClick={() => onPickColor(0x313338)}>
|
<Button onClick={() => onPickColor(0x313338)}>
|
||||||
Reset Theme Color
|
Reset Theme Color
|
||||||
|
|
|
@ -67,9 +67,16 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: 'react-spring: The "interpolate" function',
|
find: "https://github.com/highlightjs/highlight.js/issues/2277",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /,console.warn\('react-spring: The "interpolate" function is deprecated in v10 \(use "to" instead\)'\)/,
|
match: /\(console.log\(`Deprecated.+?`\),/,
|
||||||
|
replace: "("
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: 'The "interpolate" function is deprecated in v10 (use "to" instead)',
|
||||||
|
replacement: {
|
||||||
|
match: /,console.warn\(\i\+'The "interpolate" function is deprecated in v10 \(use "to" instead\)'\)/,
|
||||||
replace: ""
|
replace: ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -88,10 +95,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: 'console.warn("[DEPRECATED] Please use `subscribeWithSelector` middleware");',
|
find: '"AppCrashedFatalReport: getLastCrash not supported."',
|
||||||
all: true,
|
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\);/,
|
match: /console\.log\("AppCrashedFatalReport: getLastCrash not supported\."\);/,
|
||||||
replace: ""
|
replace: ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -126,7 +132,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "Slow dispatch on",
|
find: "Slow dispatch on",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\i\.totalTime>100&&\i\.verbose\("Slow dispatch on ".+?\)\);/,
|
match: /\i\.totalTime>\i&&\i\.verbose\("Slow dispatch on ".+?\)\);/,
|
||||||
replace: ""
|
replace: ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { Devs } from "@utils/constants";
|
||||||
import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
|
import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
|
||||||
import { runtimeHashMessageKey } from "@utils/intlHash";
|
import { runtimeHashMessageKey } from "@utils/intlHash";
|
||||||
import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
|
import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
|
||||||
|
import { ModalAPI } from "@utils/modal";
|
||||||
import { relaunch } from "@utils/native";
|
import { relaunch } from "@utils/native";
|
||||||
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
|
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
|
||||||
import definePlugin, { PluginNative, StartAt } from "@utils/types";
|
import definePlugin, { PluginNative, StartAt } from "@utils/types";
|
||||||
|
@ -62,7 +63,7 @@ function makeShortcuts() {
|
||||||
default:
|
default:
|
||||||
const uniqueMatches = [...new Set(matches)];
|
const uniqueMatches = [...new Set(matches)];
|
||||||
if (uniqueMatches.length > 1)
|
if (uniqueMatches.length > 1)
|
||||||
console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches);
|
console.warn(`Warning: This filter matches ${uniqueMatches.length} exports. Make it more specific!\n`, uniqueMatches);
|
||||||
|
|
||||||
return matches[0];
|
return matches[0];
|
||||||
}
|
}
|
||||||
|
@ -132,7 +133,10 @@ function makeShortcuts() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Common.ReactDOM.render(Common.React.createElement(component, props), doc.body.appendChild(document.createElement("div")));
|
const root = Common.ReactDOM.createRoot(doc.body.appendChild(document.createElement("div")));
|
||||||
|
root.render(Common.React.createElement(component, props));
|
||||||
|
|
||||||
|
doc.addEventListener("close", () => root.unmount(), { once: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true,
|
preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true,
|
||||||
|
@ -144,6 +148,8 @@ function makeShortcuts() {
|
||||||
me: { getter: () => Common.UserStore.getCurrentUser(), preload: false },
|
me: { getter: () => Common.UserStore.getCurrentUser(), preload: false },
|
||||||
meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false },
|
meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false },
|
||||||
messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false },
|
messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false },
|
||||||
|
openModal: { getter: () => ModalAPI.openModal },
|
||||||
|
openModalLazy: { getter: () => ModalAPI.openModalLazy },
|
||||||
|
|
||||||
Stores: {
|
Stores: {
|
||||||
getter: () => Object.fromEntries(
|
getter: () => Object.fromEntries(
|
||||||
|
@ -159,11 +165,38 @@ function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) {
|
||||||
const currentVal = val.getter();
|
const currentVal = val.getter();
|
||||||
if (!currentVal || val.preload === false) return currentVal;
|
if (!currentVal || val.preload === false) return currentVal;
|
||||||
|
|
||||||
const value = currentVal[SYM_LAZY_GET]
|
function unwrapProxy(value: any) {
|
||||||
? forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED]
|
if (value[SYM_LAZY_GET]) {
|
||||||
: currentVal;
|
forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED];
|
||||||
|
} else if (value.$$vencordInternal) {
|
||||||
|
return forceLoad ? value.$$vencordInternal() : value;
|
||||||
|
}
|
||||||
|
|
||||||
if (value) define(window.shortcutList, key, { value });
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = unwrapProxy(currentVal);
|
||||||
|
if (typeof value === "object" && value !== null) {
|
||||||
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
||||||
|
|
||||||
|
for (const propKey in descriptors) {
|
||||||
|
if (value[propKey] == null) continue;
|
||||||
|
|
||||||
|
const descriptor = descriptors[propKey];
|
||||||
|
if (descriptor.writable === true || descriptor.set != null) {
|
||||||
|
const currentValue = value[propKey];
|
||||||
|
const newValue = unwrapProxy(currentValue);
|
||||||
|
if (newValue != null && currentValue !== newValue) {
|
||||||
|
value[propKey] = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
define(window.shortcutList, key, { value });
|
||||||
|
define(window, key, { value });
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
@ -173,6 +206,16 @@ export default definePlugin({
|
||||||
description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.",
|
description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: 'this,"_changeCallbacks",',
|
||||||
|
replacement: {
|
||||||
|
match: /\i\(this,"_changeCallbacks",/,
|
||||||
|
replace: "Reflect.defineProperty(this,Symbol.toStringTag,{value:this.getName(),configurable:!0,writable:!0,enumerable:!1}),$&"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
startAt: StartAt.Init,
|
startAt: StartAt.Init,
|
||||||
start() {
|
start() {
|
||||||
const shortcuts = makeShortcuts();
|
const shortcuts = makeShortcuts();
|
||||||
|
|
|
@ -42,10 +42,11 @@ export default definePlugin({
|
||||||
// Only one of the two patches will be at effect; Discord often updates to switch between them.
|
// Only one of the two patches will be at effect; Discord often updates to switch between them.
|
||||||
// See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673
|
// See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673
|
||||||
{
|
{
|
||||||
find: ".ENTER&&(!",
|
find: ".selectPreviousCommandOption(",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/,
|
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||||
replace: "$self.shouldSubmit($1, $2)"
|
match: /(?<=(\i)\.which(?:!==|===)\i\.\i.ENTER(\|\||&&)).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=(?:\|\||&&)\(\i\.preventDefault)/,
|
||||||
|
replace: (_, event, condition, codeblock) => `${condition === "||" ? "!" : ""}$self.shouldSubmit(${event},${codeblock})`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
import { definePluginSettings, Settings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
import { ErrorCard } from "@components/ErrorCard";
|
||||||
|
import { Flex } from "@components/Flex";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { isTruthy } from "@utils/guards";
|
import { isTruthy } from "@utils/guards";
|
||||||
|
@ -27,15 +28,14 @@ import { classes } from "@utils/misc";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByCodeLazy, findComponentByCodeLazy } from "@webpack";
|
import { findByCodeLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
|
import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, React, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color");
|
const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color");
|
||||||
const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile");
|
const ActivityView = findComponentByCodeLazy(".party?(0", ".card");
|
||||||
|
|
||||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||||
|
|
||||||
async function getApplicationAsset(key: string): Promise<string> {
|
async function getApplicationAsset(key: string): Promise<string> {
|
||||||
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
|
|
||||||
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
|
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ const settings = definePluginSettings({
|
||||||
value: TimestampMode.NOW
|
value: TimestampMode.NOW
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Same as your current time",
|
label: "Same as your current time (not reset after 24h)",
|
||||||
value: TimestampMode.TIME
|
value: TimestampMode.TIME
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -269,6 +269,7 @@ function isStreamLinkDisabled() {
|
||||||
|
|
||||||
function isStreamLinkValid(value: string) {
|
function isStreamLinkValid(value: string) {
|
||||||
if (!isStreamLinkDisabled() && !/https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+/.test(value)) return "Streaming link must be a valid URL.";
|
if (!isStreamLinkDisabled() && !/https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+/.test(value)) return "Streaming link must be a valid URL.";
|
||||||
|
if (value && value.length > 512) return "Streaming link must be not longer than 512 characters.";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,8 +278,9 @@ function isTimestampDisabled() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isImageKeyValid(value: string) {
|
function isImageKeyValid(value: string) {
|
||||||
if (/https?:\/\/(?!i\.)?imgur\.com\//.test(value)) return "Imgur link must be a direct link to the image. (e.g. https://i.imgur.com/...)";
|
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//.test(value)) return "Don't use a Discord link. Use an Imgur image link instead.";
|
||||||
if (/https?:\/\/(?!media\.)?tenor\.com\//.test(value)) return "Tenor link must be a direct link to the image. (e.g. https://media.tenor.com/...)";
|
if (/https?:\/\/(?!i\.)?imgur\.com\//.test(value)) return "Imgur link must be a direct link to the image (e.g. https://i.imgur.com/...). Right click the image and click 'Copy image address'";
|
||||||
|
if (/https?:\/\/(?!media\.)?tenor\.com\//.test(value)) return "Tenor link must be a direct link to the image (e.g. https://media.tenor.com/...). Right click the GIF and click 'Copy image address'";
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,13 +392,24 @@ async function setRpc(disable?: boolean) {
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "CustomRPC",
|
name: "CustomRPC",
|
||||||
description: "Allows you to set a custom rich presence.",
|
description: "Add a fully customisable Rich Presence (Game status) to your Discord profile",
|
||||||
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
|
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
|
||||||
dependencies: ["UserSettingsAPI"],
|
dependencies: ["UserSettingsAPI"],
|
||||||
start: setRpc,
|
start: setRpc,
|
||||||
stop: () => setRpc(true),
|
stop: () => setRpc(true),
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: ".party?(0",
|
||||||
|
all: true,
|
||||||
|
replacement: {
|
||||||
|
match: /\i\.id===\i\.id\?null:/,
|
||||||
|
replace: ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
settingsAboutComponent: () => {
|
settingsAboutComponent: () => {
|
||||||
const activity = useAwaiter(createActivity);
|
const activity = useAwaiter(createActivity);
|
||||||
const gameActivityEnabled = ShowCurrentGame.useSetting();
|
const gameActivityEnabled = ShowCurrentGame.useSetting();
|
||||||
|
@ -410,7 +423,7 @@ export default definePlugin({
|
||||||
style={{ padding: "1em" }}
|
style={{ padding: "1em" }}
|
||||||
>
|
>
|
||||||
<Forms.FormTitle>Notice</Forms.FormTitle>
|
<Forms.FormTitle>Notice</Forms.FormTitle>
|
||||||
<Forms.FormText>Game activity isn't enabled, people won't be able to see your custom rich presence!</Forms.FormText>
|
<Forms.FormText>Activity Sharing isn't enabled, people won't be able to see your custom rich presence!</Forms.FormText>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
color={Button.Colors.TRANSPARENT}
|
color={Button.Colors.TRANSPARENT}
|
||||||
|
@ -422,24 +435,33 @@ export default definePlugin({
|
||||||
</ErrorCard>
|
</ErrorCard>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<Flex flexDirection="column" style={{ gap: ".5em" }} className={Margins.top16}>
|
||||||
<Forms.FormText>
|
<Forms.FormText>
|
||||||
Go to <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
|
Go to the <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
|
||||||
get the application ID.
|
get the application ID.
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<Forms.FormText>
|
<Forms.FormText>
|
||||||
Upload images in the Rich Presence tab to get the image keys.
|
Upload images in the Rich Presence tab to get the image keys.
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<Forms.FormText>
|
<Forms.FormText>
|
||||||
If you want to use image link, download your image and reupload the image to <Link href="https://imgur.com">Imgur</Link> and get the image link by right-clicking the image and select "Copy image address".
|
If you want to use an image link, download your image and reupload the image to <Link href="https://imgur.com">Imgur</Link> and get the image link by right-clicking the image and selecting "Copy image address".
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
|
<Forms.FormText>
|
||||||
|
You can't see your own buttons on your profile, but everyone else can see it fine.
|
||||||
|
</Forms.FormText>
|
||||||
|
<Forms.FormText>
|
||||||
|
Some weird unicode text ("fonts" 𝖑𝖎𝖐𝖊 𝖙𝖍𝖎𝖘) may cause the rich presence to not show up, try using normal letters instead.
|
||||||
|
</Forms.FormText>
|
||||||
|
</Flex>
|
||||||
|
|
||||||
<Forms.FormDivider className={Margins.top8} />
|
<Forms.FormDivider className={Margins.top8} />
|
||||||
|
|
||||||
<div style={{ width: "284px", ...profileThemeStyle, padding: 8, marginTop: 8, borderRadius: 8, background: "var(--bg-mod-faint)" }}>
|
<div style={{ width: "284px", ...profileThemeStyle, marginTop: 8, borderRadius: 8, background: "var(--bg-mod-faint)" }}>
|
||||||
{activity[0] && <ActivityComponent activity={activity[0]} channelId={SelectedChannelStore.getChannelId()}
|
{activity[0] && <ActivityView
|
||||||
guild={GuildStore.getGuild(SelectedGuildStore.getLastSelectedGuildId())}
|
activity={activity[0]}
|
||||||
application={{ id: settings.store.appID }}
|
user={UserStore.getCurrentUser()}
|
||||||
user={UserStore.getCurrentUser()} />}
|
currentUser={UserStore.getCurrentUser()}
|
||||||
|
/>}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -17,7 +17,6 @@ import DecorSection from "./ui/components/DecorSection";
|
||||||
export const settings = definePluginSettings({
|
export const settings = definePluginSettings({
|
||||||
changeDecoration: {
|
changeDecoration: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "Change your avatar decoration",
|
|
||||||
component() {
|
component() {
|
||||||
if (!Vencord.Plugins.plugins.Decor.started) return <Forms.FormText>
|
if (!Vencord.Plugins.plugins.Decor.started) return <Forms.FormText>
|
||||||
Enable Decor and restart your client to change your avatar decoration.
|
Enable Decor and restart your client to change your avatar decoration.
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { React } from "@webpack/common";
|
import { React } from "@webpack/common";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
import { cl } from "../";
|
import { cl } from "../";
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { React } from "@webpack/common";
|
import { React } from "@webpack/common";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
import { cl } from "../";
|
import { cl } from "../";
|
||||||
import Grid, { GridProps } from "./Grid";
|
import Grid, { GridProps } from "./Grid";
|
||||||
|
|
|
@ -16,11 +16,9 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { migratePluginSettings } from "@api/Settings";
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
migratePluginSettings("DisableCallIdle", "DisableDMCallIdle");
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "DisableCallIdle",
|
name: "DisableCallIdle",
|
||||||
description: "Disables automatically getting kicked from a DM voice call after 3 minutes and being moved to an AFK voice channel.",
|
description: "Disables automatically getting kicked from a DM voice call after 3 minutes and being moved to an AFK voice channel.",
|
||||||
|
|
|
@ -211,7 +211,7 @@ function CloneModal({ data }: { data: Sticker | Emoji; }) {
|
||||||
alignItems: "center"
|
alignItems: "center"
|
||||||
}}>
|
}}>
|
||||||
{guilds.map(g => (
|
{guilds.map(g => (
|
||||||
<Tooltip text={g.name}>
|
<Tooltip key={g.id} text={g.name}>
|
||||||
{({ onMouseLeave, onMouseEnter }) => (
|
{({ onMouseLeave, onMouseEnter }) => (
|
||||||
<div
|
<div
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
|
|
|
@ -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 { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
|
import { addMessagePreEditListener, addMessagePreSendListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
|
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
|
||||||
|
@ -207,8 +207,8 @@ function makeBypassPatches(): Omit<Patch, "plugin"> {
|
||||||
return {
|
return {
|
||||||
find: "canUseCustomStickersEverywhere:",
|
find: "canUseCustomStickersEverywhere:",
|
||||||
replacement: mapping.map(({ func, predicate }) => ({
|
replacement: mapping.map(({ func, predicate }) => ({
|
||||||
match: new RegExp(String.raw`(?<=${func}:function\(\i(?:,\i)?\){)`),
|
match: new RegExp(String.raw`(?<=${func}:)\i`),
|
||||||
replace: "return true;",
|
replace: "() => true",
|
||||||
predicate
|
predicate
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
|
@ -235,7 +235,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".PREMIUM_LOCKED;",
|
find: ".GUILD_SUBSCRIPTION_UNAVAILABLE;",
|
||||||
group: true,
|
group: true,
|
||||||
predicate: () => settings.store.enableEmojiBypass,
|
predicate: () => settings.store.enableEmojiBypass,
|
||||||
replacement: [
|
replacement: [
|
||||||
|
@ -256,8 +256,11 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Disallow the emoji for premium locked if the intention doesn't allow it
|
// Disallow the emoji for premium locked if the intention doesn't allow it
|
||||||
match: /!\i\.\i\.canUseEmojisEverywhere\(\i\)/,
|
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||||
replace: m => `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
|
match: /(!)?(\i\.\i\.canUseEmojisEverywhere\(\i\))/,
|
||||||
|
replace: (m, not) => not
|
||||||
|
? `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
|
||||||
|
: `(${m}||${IS_BYPASSEABLE_INTENTION})`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Allow animated emojis to be used if the intention allows it
|
// Allow animated emojis to be used if the intention allows it
|
||||||
|
@ -297,8 +300,8 @@ export default definePlugin({
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Overwrite incoming connection settings proto with our local settings
|
// Overwrite incoming connection settings proto with our local settings
|
||||||
match: /CONNECTION_OPEN:function\((\i)\){/,
|
match: /function (\i)\((\i)\){(?=.*CONNECTION_OPEN:\1)/,
|
||||||
replace: (m, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);`
|
replace: (m, funcName, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Overwrite non local proto changes with our local settings
|
// Overwrite non local proto changes with our local settings
|
||||||
|
@ -391,7 +394,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
// Separate patch for allowing using custom app icons
|
// Separate patch for allowing using custom app icons
|
||||||
{
|
{
|
||||||
find: /\.getCurrentDesktopIcon.{0,25}\.isPremium/,
|
find: "?24:30,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/,
|
match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/,
|
||||||
replace: "true"
|
replace: "true"
|
||||||
|
@ -514,7 +517,7 @@ export default definePlugin({
|
||||||
return array.filter(item => item != null);
|
return array.filter(item => item != null);
|
||||||
},
|
},
|
||||||
|
|
||||||
ensureChildrenIsArray(child: ReactElement) {
|
ensureChildrenIsArray(child: ReactElement<any>) {
|
||||||
if (!Array.isArray(child.props.children)) child.props.children = [child.props.children];
|
if (!Array.isArray(child.props.children)) child.props.children = [child.props.children];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -524,7 +527,7 @@ export default definePlugin({
|
||||||
|
|
||||||
let nextIndex = content.length;
|
let nextIndex = content.length;
|
||||||
|
|
||||||
const transformLinkChild = (child: ReactElement) => {
|
const transformLinkChild = (child: ReactElement<any>) => {
|
||||||
if (settings.store.transformEmojis) {
|
if (settings.store.transformEmojis) {
|
||||||
const fakeNitroMatch = child.props.href.match(fakeNitroEmojiRegex);
|
const fakeNitroMatch = child.props.href.match(fakeNitroEmojiRegex);
|
||||||
if (fakeNitroMatch) {
|
if (fakeNitroMatch) {
|
||||||
|
@ -558,7 +561,7 @@ export default definePlugin({
|
||||||
return child;
|
return child;
|
||||||
};
|
};
|
||||||
|
|
||||||
const transformChild = (child: ReactElement) => {
|
const transformChild = (child: ReactElement<any>) => {
|
||||||
if (child?.props?.trusted != null) return transformLinkChild(child);
|
if (child?.props?.trusted != null) return transformLinkChild(child);
|
||||||
if (child?.props?.children != null) {
|
if (child?.props?.children != null) {
|
||||||
if (!Array.isArray(child.props.children)) {
|
if (!Array.isArray(child.props.children)) {
|
||||||
|
@ -574,7 +577,7 @@ export default definePlugin({
|
||||||
return child;
|
return child;
|
||||||
};
|
};
|
||||||
|
|
||||||
const modifyChild = (child: ReactElement) => {
|
const modifyChild = (child: ReactElement<any>) => {
|
||||||
const newChild = transformChild(child);
|
const newChild = transformChild(child);
|
||||||
|
|
||||||
if (newChild?.type === "ul" || newChild?.type === "ol") {
|
if (newChild?.type === "ul" || newChild?.type === "ol") {
|
||||||
|
@ -601,7 +604,7 @@ export default definePlugin({
|
||||||
return newChild;
|
return newChild;
|
||||||
};
|
};
|
||||||
|
|
||||||
const modifyChildren = (children: Array<ReactElement>) => {
|
const modifyChildren = (children: Array<ReactElement<any>>) => {
|
||||||
for (const [index, child] of children.entries()) children[index] = modifyChild(child);
|
for (const [index, child] of children.entries()) children[index] = modifyChild(child);
|
||||||
|
|
||||||
children = this.clearEmptyArrayItems(children);
|
children = this.clearEmptyArrayItems(children);
|
||||||
|
@ -853,7 +856,7 @@ export default definePlugin({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.preSend = addPreSendListener(async (channelId, messageObj, extra) => {
|
this.preSend = addMessagePreSendListener(async (channelId, messageObj, extra) => {
|
||||||
const { guildId } = this;
|
const { guildId } = this;
|
||||||
|
|
||||||
let hasBypass = false;
|
let hasBypass = false;
|
||||||
|
@ -941,7 +944,7 @@ export default definePlugin({
|
||||||
return { cancel: false };
|
return { cancel: false };
|
||||||
});
|
});
|
||||||
|
|
||||||
this.preEdit = addPreEditListener(async (channelId, __, messageObj) => {
|
this.preEdit = addMessagePreEditListener(async (channelId, __, messageObj) => {
|
||||||
if (!s.enableEmojiBypass) return;
|
if (!s.enableEmojiBypass) return;
|
||||||
|
|
||||||
let hasBypass = false;
|
let hasBypass = false;
|
||||||
|
@ -973,7 +976,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
removePreSendListener(this.preSend);
|
removeMessagePreSendListener(this.preSend);
|
||||||
removePreEditListener(this.preEdit);
|
removeMessagePreEditListener(this.preEdit);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,6 +29,7 @@ import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
|
import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { Button, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common";
|
import { Button, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
import { ReactElement } from "react";
|
||||||
import virtualMerge from "virtual-merge";
|
import virtualMerge from "virtual-merge";
|
||||||
|
|
||||||
interface UserProfile extends User {
|
interface UserProfile extends User {
|
||||||
|
@ -87,7 +88,7 @@ const settings = definePluginSettings({
|
||||||
|
|
||||||
interface ColorPickerProps {
|
interface ColorPickerProps {
|
||||||
color: number | null;
|
color: number | null;
|
||||||
label: React.ReactElement;
|
label: ReactElement<any>;
|
||||||
showEyeDropper?: boolean;
|
showEyeDropper?: boolean;
|
||||||
suggestedColors?: string[];
|
suggestedColors?: string[];
|
||||||
onChange(value: number | null): void;
|
onChange(value: number | null): void;
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
// https://regex101.com/r/x2mobQ/1
|
// https://regex101.com/r/x2mobQ/1
|
||||||
// searchEmojis(...,maxCount: stuff) ... endEmojis = emojis.slice(0, maxCount - gifResults.length)
|
// searchEmojis(...,maxCount: stuff) ... endEmojis = emojis.slice(0, maxCount - gifResults.length)
|
||||||
match: /,maxCount:(\i)(.{1,500}\i)=(\i)\.slice\(0,(\i-\i\.length)\)/,
|
match: /,maxCount:(\i)(.{1,500}\i)=(\i)\.slice\(0,(Math\.max\(\i,\i(?:-\i\.length){2}\))\)/,
|
||||||
// ,maxCount:Infinity ... endEmojis = (emojis.sliceTo = n, emojis)
|
// ,maxCount:Infinity ... endEmojis = (emojis.sliceTo = n, emojis)
|
||||||
replace: ",maxCount:Infinity$2=($3.sliceTo = $4, $3)"
|
replace: ",maxCount:Infinity$2=($3.sliceTo = $4, $3)"
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ export default definePlugin({
|
||||||
authors: [Devs.Nuckyz],
|
authors: [Devs.Nuckyz],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "getFormatQuality(){",
|
find: ".handleImageLoad)",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=null;return )\i\.\i&&\(\i\|\|!\i\.isAnimated.+?:(?=\i&&\(\i="png"\))/,
|
match: /(?<=null;return )\i\.\i&&\(\i\|\|!\i\.isAnimated.+?:(?=\i&&\(\i="png"\))/,
|
||||||
replace: ""
|
replace: ""
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
authors: [Devs.D3SOX, Devs.Nickyux],
|
authors: [Devs.D3SOX, Devs.Nickyux],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "#{intl::GUILD_OWNER}",
|
find: "#{intl::GUILD_OWNER}),children:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /,isOwner:(\i),/,
|
match: /,isOwner:(\i),/,
|
||||||
replace: ",_isOwner:$1=$self.isGuildOwner(e),"
|
replace: ",_isOwner:$1=$self.isGuildOwner(e),"
|
||||||
|
|
|
@ -22,11 +22,11 @@ import { Devs } from "@utils/constants";
|
||||||
import { getIntlMessage } from "@utils/discord";
|
import { getIntlMessage } from "@utils/discord";
|
||||||
import { NoopComponent } from "@utils/react";
|
import { NoopComponent } from "@utils/react";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { filters, findByPropsLazy, waitFor } from "@webpack";
|
import { filters, findByCodeLazy, waitFor } from "@webpack";
|
||||||
import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common";
|
import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
const { useMessageMenu } = findByPropsLazy("useMessageMenu");
|
const useMessageMenu = findByCodeLazy(".MESSAGE,commandTargetId:");
|
||||||
|
|
||||||
interface CopyIdMenuItemProps {
|
interface CopyIdMenuItemProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
9
src/plugins/fullUserInChatbox/README.md
Normal file
9
src/plugins/fullUserInChatbox/README.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# Full User In Chatbox
|
||||||
|
|
||||||
|
Adds the full user mention to the textbox
|
||||||
|
|
||||||
|
Adds the avatar if you have mentioned avatars enabled
|
||||||
|
|
||||||
|
Provides the full context menu to make it easy to access the users profile, and other common actions
|
||||||
|
|
||||||
|
https://github.com/user-attachments/assets/cd9edb33-99c8-4c8d-b669-8cddd05f4b45
|
53
src/plugins/fullUserInChatbox/index.tsx
Normal file
53
src/plugins/fullUserInChatbox/index.tsx
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
import { findComponentByCodeLazy } from "@webpack";
|
||||||
|
import { UserStore, useStateFromStores } from "@webpack/common";
|
||||||
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
|
const UserMentionComponent = findComponentByCodeLazy(".USER_MENTION)");
|
||||||
|
|
||||||
|
interface UserMentionComponentProps {
|
||||||
|
id: string;
|
||||||
|
channelId: string;
|
||||||
|
guildId: string;
|
||||||
|
originalComponent: () => ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "FullUserInChatbox",
|
||||||
|
description: "Makes the user mention in the chatbox have more functionalities, like left/right clicking",
|
||||||
|
authors: [Devs.sadan],
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: ':"text":',
|
||||||
|
replacement: {
|
||||||
|
match: /(hidePersonalInformation\).+?)(if\(null!=\i\){.+?return \i)(?=})/,
|
||||||
|
replace: "$1return $self.UserMentionComponent({...arguments[0],originalComponent:()=>{$2}});"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
UserMentionComponent: ErrorBoundary.wrap((props: UserMentionComponentProps) => {
|
||||||
|
const user = useStateFromStores([UserStore], () => UserStore.getUser(props.id));
|
||||||
|
if (user == null) {
|
||||||
|
return props.originalComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return <UserMentionComponent
|
||||||
|
// This seems to be constant
|
||||||
|
className="mention"
|
||||||
|
userId={props.id}
|
||||||
|
channelId={props.channelId}
|
||||||
|
/>;
|
||||||
|
}, {
|
||||||
|
fallback: ({ wrappedProps: { originalComponent } }) => originalComponent()
|
||||||
|
})
|
||||||
|
});
|
|
@ -26,7 +26,7 @@ import { findComponentByCodeLazy } from "@webpack";
|
||||||
|
|
||||||
import style from "./style.css?managed";
|
import style from "./style.css?managed";
|
||||||
|
|
||||||
const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:");
|
const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON");
|
||||||
|
|
||||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { get, set } from "@api/DataStore";
|
import { get, set } from "@api/DataStore";
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
|
||||||
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { ChannelStore } from "@webpack/common";
|
import { ChannelStore } from "@webpack/common";
|
||||||
|
import { MessageSnapshot } from "@webpack/types";
|
||||||
|
|
||||||
let style: HTMLStyleElement;
|
let style: HTMLStyleElement;
|
||||||
|
|
||||||
|
@ -38,18 +38,14 @@ export default definePlugin({
|
||||||
name: "HideAttachments",
|
name: "HideAttachments",
|
||||||
description: "Hide attachments and Embeds for individual messages via hover button",
|
description: "Hide attachments and Embeds for individual messages via hover button",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["MessagePopoverAPI"],
|
|
||||||
|
|
||||||
async start() {
|
renderMessagePopoverButton(msg) {
|
||||||
style = document.createElement("style");
|
// @ts-ignore - discord-types lags behind discord.
|
||||||
style.id = "VencordHideAttachments";
|
const hasAttachmentsInShapshots = msg.messageSnapshots.some(
|
||||||
document.head.appendChild(style);
|
(snapshot: MessageSnapshot) => snapshot?.message.attachments.length
|
||||||
|
);
|
||||||
|
|
||||||
await getHiddenMessages();
|
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length && !hasAttachmentsInShapshots) return null;
|
||||||
await this.buildCss();
|
|
||||||
|
|
||||||
addButton("HideAttachments", msg => {
|
|
||||||
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length) return null;
|
|
||||||
|
|
||||||
const isHidden = hiddenMessages.has(msg.id);
|
const isHidden = hiddenMessages.has(msg.id);
|
||||||
|
|
||||||
|
@ -60,13 +56,20 @@ export default definePlugin({
|
||||||
channel: ChannelStore.getChannel(msg.channel_id),
|
channel: ChannelStore.getChannel(msg.channel_id),
|
||||||
onClick: () => this.toggleHide(msg.id)
|
onClick: () => this.toggleHide(msg.id)
|
||||||
};
|
};
|
||||||
});
|
},
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
style = document.createElement("style");
|
||||||
|
style.id = "VencordHideAttachments";
|
||||||
|
document.head.appendChild(style);
|
||||||
|
|
||||||
|
await getHiddenMessages();
|
||||||
|
await this.buildCss();
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
style.remove();
|
style.remove();
|
||||||
hiddenMessages.clear();
|
hiddenMessages.clear();
|
||||||
removeButton("HideAttachments");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async buildCss() {
|
async buildCss() {
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "hasFlag:{writable",
|
find: "hasFlag:{writable",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /if\((\i)<=(?:1<<30|1073741824)\)return/,
|
match: /if\((\i)<=(?:0x40000000|(?:1<<30|1073741824))\)return/,
|
||||||
replace: "if($1===(1<<20))return false;$&",
|
replace: "if($1===(1<<20))return false;$&",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as DataStore from "@api/DataStore";
|
|
||||||
import { definePluginSettings, Settings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
@ -62,7 +61,7 @@ const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(ac
|
||||||
|
|
||||||
function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
||||||
const s = settings.use(["ignoredActivities"]);
|
const s = settings.use(["ignoredActivities"]);
|
||||||
const { ignoredActivities = [] } = s;
|
const { ignoredActivities } = s;
|
||||||
|
|
||||||
if (ignoredActivities.some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
|
if (ignoredActivities.some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
|
||||||
return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)");
|
return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)");
|
||||||
|
@ -71,11 +70,9 @@ function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
||||||
function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity) {
|
function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const ignoredActivityIndex = getIgnoredActivities().findIndex(act => act.id === activity.id);
|
const ignoredActivityIndex = settings.store.ignoredActivities.findIndex(act => act.id === activity.id);
|
||||||
if (ignoredActivityIndex === -1) settings.store.ignoredActivities = getIgnoredActivities().concat(activity);
|
if (ignoredActivityIndex === -1) settings.store.ignoredActivities.push(activity);
|
||||||
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
|
else settings.store.ignoredActivities.splice(ignoredActivityIndex, 1);
|
||||||
|
|
||||||
recalculateActivities();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function recalculateActivities() {
|
function recalculateActivities() {
|
||||||
|
@ -150,8 +147,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) {
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
importCustomRPC: {
|
importCustomRPC: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "",
|
component: ImportCustomRPCComponent
|
||||||
component: () => <ImportCustomRPCComponent />
|
|
||||||
},
|
},
|
||||||
listMode: {
|
listMode: {
|
||||||
type: OptionType.SELECT,
|
type: OptionType.SELECT,
|
||||||
|
@ -171,7 +167,6 @@ const settings = definePluginSettings({
|
||||||
},
|
},
|
||||||
idsList: {
|
idsList: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "",
|
|
||||||
default: "",
|
default: "",
|
||||||
onChange(newValue: string) {
|
onChange(newValue: string) {
|
||||||
const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean));
|
const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean));
|
||||||
|
@ -209,14 +204,13 @@ const settings = definePluginSettings({
|
||||||
description: "Ignore all competing activities (These are normally special game activities)",
|
description: "Ignore all competing activities (These are normally special game activities)",
|
||||||
default: false,
|
default: false,
|
||||||
onChange: recalculateActivities
|
onChange: recalculateActivities
|
||||||
|
},
|
||||||
|
ignoredActivities: {
|
||||||
|
type: OptionType.CUSTOM,
|
||||||
|
default: [] as IgnoredActivity[],
|
||||||
|
onChange: recalculateActivities
|
||||||
}
|
}
|
||||||
}).withPrivateSettings<{
|
});
|
||||||
ignoredActivities: IgnoredActivity[];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
function getIgnoredActivities() {
|
|
||||||
return settings.store.ignoredActivities ??= [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function isActivityTypeIgnored(type: number, id?: string) {
|
function isActivityTypeIgnored(type: number, id?: string) {
|
||||||
if (id && settings.store.idsList.includes(id)) {
|
if (id && settings.store.idsList.includes(id)) {
|
||||||
|
@ -247,7 +241,7 @@ export default definePlugin({
|
||||||
find: '"LocalActivityStore"',
|
find: '"LocalActivityStore"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /HANG_STATUS.+?(?=!\i\(\)\(\i,\i\)&&)(?<=(\i)\.push.+?)/,
|
match: /\.LISTENING.+?(?=!?\i\(\)\(\i,\i\))(?<=(\i)\.push.+?)/,
|
||||||
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
|
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -284,29 +278,14 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
// Migrate allowedIds
|
if (settings.store.ignoredActivities.length !== 0) {
|
||||||
if (Settings.plugins.IgnoreActivities.allowedIds) {
|
|
||||||
settings.store.idsList = Settings.plugins.IgnoreActivities.allowedIds;
|
|
||||||
delete Settings.plugins.IgnoreActivities.allowedIds; // Remove allowedIds
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldIgnoredActivitiesData = await DataStore.get<Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities");
|
|
||||||
|
|
||||||
if (oldIgnoredActivitiesData != null) {
|
|
||||||
settings.store.ignoredActivities = Array.from(oldIgnoredActivitiesData.values())
|
|
||||||
.map(activity => ({ ...activity, name: "Unknown Name" }));
|
|
||||||
|
|
||||||
DataStore.del("IgnoreActivities_ignoredActivities");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getIgnoredActivities().length !== 0) {
|
|
||||||
const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[];
|
const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[];
|
||||||
|
|
||||||
for (const [index, ignoredActivity] of getIgnoredActivities().entries()) {
|
for (const [index, ignoredActivity] of settings.store.ignoredActivities.entries()) {
|
||||||
if (ignoredActivity.type !== ActivitiesTypes.Game) continue;
|
if (ignoredActivity.type !== ActivitiesTypes.Game) continue;
|
||||||
|
|
||||||
if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
|
if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
|
||||||
getIgnoredActivities().splice(index, 1);
|
settings.store.ignoredActivities.splice(index, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,11 +295,11 @@ export default definePlugin({
|
||||||
if (isActivityTypeIgnored(props.type, props.application_id)) return false;
|
if (isActivityTypeIgnored(props.type, props.application_id)) return false;
|
||||||
|
|
||||||
if (props.application_id != null) {
|
if (props.application_id != null) {
|
||||||
return !getIgnoredActivities().some(activity => activity.id === props.application_id) || (settings.store.listMode === FilterMode.Whitelist && settings.store.idsList.includes(props.application_id));
|
return !settings.store.ignoredActivities.some(activity => activity.id === props.application_id) || (settings.store.listMode === FilterMode.Whitelist && settings.store.idsList.includes(props.application_id));
|
||||||
} else {
|
} else {
|
||||||
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
||||||
if (exePath) {
|
if (exePath) {
|
||||||
return !getIgnoredActivities().some(activity => activity.id === exePath);
|
return !settings.store.ignoredActivities.some(activity => activity.id === exePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { debounce } from "@shared/debounce";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { Menu, ReactDOM } from "@webpack/common";
|
import { Menu, ReactDOM } from "@webpack/common";
|
||||||
|
import { JSX } from "react";
|
||||||
import type { Root } from "react-dom/client";
|
import type { Root } from "react-dom/client";
|
||||||
|
|
||||||
import { Magnifier, MagnifierProps } from "./components/Magnifier";
|
import { Magnifier, MagnifierProps } from "./components/Magnifier";
|
||||||
|
@ -80,7 +81,12 @@ export const settings = definePluginSettings({
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const imageContextMenuPatch: NavContextMenuPatchCallback = children => {
|
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
||||||
|
// Discord re-uses the image context menu for links to for the copy and open buttons
|
||||||
|
if ("href" in props) return;
|
||||||
|
// emojis in user statuses
|
||||||
|
if (props.target?.classList?.contains("emoji")) return;
|
||||||
|
|
||||||
const { square, nearestNeighbour } = settings.use(["square", "nearestNeighbour"]);
|
const { square, nearestNeighbour } = settings.use(["square", "nearestNeighbour"]);
|
||||||
|
|
||||||
children.push(
|
children.push(
|
||||||
|
|
|
@ -50,9 +50,9 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "#{intl::FRIENDS_SECTION_ONLINE}",
|
find: "#{intl::FRIENDS_SECTION_ONLINE}",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\(0,\i\.jsx\)\(\i\.TabBar\.Item,\{id:\i\.\i)\.BLOCKED,className:([^\s]+?)\.item,children:\i\.\i\.string\(\i\.\i#{intl::BLOCKED}\)\}\)/,
|
match: /,{id:(\i\.\i)\.BLOCKED,show:.+?className:(\i\.item)/,
|
||||||
replace: "$1.IMPLICIT,className:$2.item,children:\"Implicit\"}),$&"
|
replace: (rest, relationShipTypes, className) => `,{id:${relationShipTypes}.IMPLICIT,show:true,className:${className},content:"Implicit"}${rest}`
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
// Sections content
|
// Sections content
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,12 +16,19 @@
|
||||||
* 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 { addProfileBadge, removeProfileBadge } from "@api/Badges";
|
||||||
|
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||||
import { registerCommand, unregisterCommand } from "@api/Commands";
|
import { registerCommand, unregisterCommand } from "@api/Commands";
|
||||||
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
|
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
|
||||||
import { Settings } from "@api/Settings";
|
import { addMemberListDecorator, removeMemberListDecorator } from "@api/MemberListDecorators";
|
||||||
|
import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories";
|
||||||
|
import { addMessageDecoration, removeMessageDecoration } from "@api/MessageDecorations";
|
||||||
|
import { addMessageClickListener, addMessagePreEditListener, addMessagePreSendListener, removeMessageClickListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||||
|
import { addMessagePopoverButton, removeMessagePopoverButton } from "@api/MessagePopover";
|
||||||
|
import { Settings, SettingsStore } from "@api/Settings";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { canonicalizeFind } from "@utils/patches";
|
import { canonicalizeFind } from "@utils/patches";
|
||||||
import { Patch, Plugin, ReporterTestable, StartAt } from "@utils/types";
|
import { Patch, Plugin, PluginDef, ReporterTestable, 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,6 +90,13 @@ function isReporterTestable(p: Plugin, part: ReporterTestable) {
|
||||||
: (p.reporterTestable & part) === part;
|
: (p.reporterTestable & part) === part;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const pluginKeysToBind: Array<keyof PluginDef & `${"on" | "render"}${string}`> = [
|
||||||
|
"onBeforeMessageEdit", "onBeforeMessageSend", "onMessageClick",
|
||||||
|
"renderChatBarButton", "renderMemberListDecorator", "renderMessageAccessory", "renderMessageDecoration", "renderMessagePopoverButton"
|
||||||
|
];
|
||||||
|
|
||||||
|
const neededApiPlugins = new Set<string>();
|
||||||
|
|
||||||
// First round-trip to mark and force enable dependencies
|
// First round-trip to mark and force enable dependencies
|
||||||
//
|
//
|
||||||
// FIXME: might need to revisit this if there's ever nested (dependencies of dependencies) dependencies since this only
|
// FIXME: might need to revisit this if there's ever nested (dependencies of dependencies) dependencies since this only
|
||||||
|
@ -106,22 +120,46 @@ for (const p of pluginsValues) if (isPluginEnabled(p.name)) {
|
||||||
dep.isDependency = true;
|
dep.isDependency = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (p.commands?.length) {
|
if (p.commands?.length) neededApiPlugins.add("CommandsAPI");
|
||||||
Plugins.CommandsAPI.isDependency = true;
|
if (p.onBeforeMessageEdit || p.onBeforeMessageSend || p.onMessageClick) neededApiPlugins.add("MessageEventsAPI");
|
||||||
settings.CommandsAPI.enabled = true;
|
if (p.renderChatBarButton) neededApiPlugins.add("ChatInputButtonAPI");
|
||||||
|
if (p.renderMemberListDecorator) neededApiPlugins.add("MemberListDecoratorsAPI");
|
||||||
|
if (p.renderMessageAccessory) neededApiPlugins.add("MessageAccessoriesAPI");
|
||||||
|
if (p.renderMessageDecoration) neededApiPlugins.add("MessageDecorationsAPI");
|
||||||
|
if (p.renderMessagePopoverButton) neededApiPlugins.add("MessagePopoverAPI");
|
||||||
|
if (p.userProfileBadge) neededApiPlugins.add("BadgeAPI");
|
||||||
|
|
||||||
|
for (const key of pluginKeysToBind) {
|
||||||
|
p[key] &&= p[key].bind(p) as any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const p of neededApiPlugins) {
|
||||||
|
Plugins[p].isDependency = true;
|
||||||
|
settings[p].enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
for (const p of pluginsValues) {
|
for (const p of pluginsValues) {
|
||||||
if (p.settings) {
|
if (p.settings) {
|
||||||
p.settings.pluginName = p.name;
|
|
||||||
p.options ??= {};
|
p.options ??= {};
|
||||||
for (const [name, def] of Object.entries(p.settings.def)) {
|
|
||||||
|
p.settings.pluginName = p.name;
|
||||||
|
for (const name in p.settings.def) {
|
||||||
|
const def = p.settings.def[name];
|
||||||
const checks = p.settings.checks?.[name];
|
const checks = p.settings.checks?.[name];
|
||||||
p.options[name] = { ...def, ...checks };
|
p.options[name] = { ...def, ...checks };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (p.options) {
|
||||||
|
for (const name in p.options) {
|
||||||
|
const opt = p.options[name];
|
||||||
|
if (opt.onChange != null) {
|
||||||
|
SettingsStore.addChangeListener(`plugins.${p.name}.${name}`, opt.onChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (p.patches && isPluginEnabled(p.name)) {
|
if (p.patches && isPluginEnabled(p.name)) {
|
||||||
if (!IS_REPORTER || isReporterTestable(p, ReporterTestable.Patches)) {
|
if (!IS_REPORTER || isReporterTestable(p, ReporterTestable.Patches)) {
|
||||||
for (const patch of p.patches) {
|
for (const patch of p.patches) {
|
||||||
|
@ -215,7 +253,11 @@ export function subscribeAllPluginsFluxEvents(fluxDispatcher: typeof FluxDispatc
|
||||||
}
|
}
|
||||||
|
|
||||||
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
|
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
|
||||||
const { name, commands, contextMenus } = p;
|
const {
|
||||||
|
name, commands, contextMenus, userProfileBadge,
|
||||||
|
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
|
||||||
|
renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton
|
||||||
|
} = p;
|
||||||
|
|
||||||
if (p.start) {
|
if (p.start) {
|
||||||
logger.info("Starting plugin", name);
|
logger.info("Starting plugin", name);
|
||||||
|
@ -249,7 +291,6 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
||||||
subscribePluginFluxEvents(p, FluxDispatcher);
|
subscribePluginFluxEvents(p, FluxDispatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (contextMenus) {
|
if (contextMenus) {
|
||||||
logger.debug("Adding context menus patches of plugin", name);
|
logger.debug("Adding context menus patches of plugin", name);
|
||||||
for (const navId in contextMenus) {
|
for (const navId in contextMenus) {
|
||||||
|
@ -257,11 +298,27 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userProfileBadge) addProfileBadge(userProfileBadge);
|
||||||
|
|
||||||
|
if (onBeforeMessageEdit) addMessagePreEditListener(onBeforeMessageEdit);
|
||||||
|
if (onBeforeMessageSend) addMessagePreSendListener(onBeforeMessageSend);
|
||||||
|
if (onMessageClick) addMessageClickListener(onMessageClick);
|
||||||
|
|
||||||
|
if (renderChatBarButton) addChatBarButton(name, renderChatBarButton);
|
||||||
|
if (renderMemberListDecorator) addMemberListDecorator(name, renderMemberListDecorator);
|
||||||
|
if (renderMessageDecoration) addMessageDecoration(name, renderMessageDecoration);
|
||||||
|
if (renderMessageAccessory) addMessageAccessory(name, renderMessageAccessory);
|
||||||
|
if (renderMessagePopoverButton) addMessagePopoverButton(name, renderMessagePopoverButton);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}, p => `startPlugin ${p.name}`);
|
}, p => `startPlugin ${p.name}`);
|
||||||
|
|
||||||
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
|
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
|
||||||
const { name, commands, contextMenus } = p;
|
const {
|
||||||
|
name, commands, contextMenus, userProfileBadge,
|
||||||
|
onBeforeMessageEdit, onBeforeMessageSend, onMessageClick,
|
||||||
|
renderChatBarButton, renderMemberListDecorator, renderMessageAccessory, renderMessageDecoration, renderMessagePopoverButton
|
||||||
|
} = p;
|
||||||
|
|
||||||
if (p.stop) {
|
if (p.stop) {
|
||||||
logger.info("Stopping plugin", name);
|
logger.info("Stopping plugin", name);
|
||||||
|
@ -300,5 +357,17 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userProfileBadge) removeProfileBadge(userProfileBadge);
|
||||||
|
|
||||||
|
if (onBeforeMessageEdit) removeMessagePreEditListener(onBeforeMessageEdit);
|
||||||
|
if (onBeforeMessageSend) removeMessagePreSendListener(onBeforeMessageSend);
|
||||||
|
if (onMessageClick) removeMessageClickListener(onMessageClick);
|
||||||
|
|
||||||
|
if (renderChatBarButton) removeChatBarButton(name);
|
||||||
|
if (renderMemberListDecorator) removeMemberListDecorator(name);
|
||||||
|
if (renderMessageDecoration) removeMessageDecoration(name);
|
||||||
|
if (renderMessageAccessory) removeMessageAccessory(name);
|
||||||
|
if (renderMessagePopoverButton) removeMessagePopoverButton(name);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}, p => `stopPlugin ${p.name}`);
|
}, p => `stopPlugin ${p.name}`);
|
||||||
|
|
|
@ -16,8 +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 { addChatBarButton, ChatBarButton } from "@api/ChatButtons";
|
import { ChatBarButton, ChatBarButtonFactory } from "@api/ChatButtons";
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
|
||||||
import { updateMessage } from "@api/MessageUpdater";
|
import { updateMessage } from "@api/MessageUpdater";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
@ -66,7 +65,7 @@ function Indicator() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatBarIcon: ChatBarButton = ({ isMainChat }) => {
|
const ChatBarIcon: ChatBarButtonFactory = ({ isMainChat }) => {
|
||||||
if (!isMainChat) return null;
|
if (!isMainChat) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -104,7 +103,7 @@ export default definePlugin({
|
||||||
name: "InvisibleChat",
|
name: "InvisibleChat",
|
||||||
description: "Encrypt your Messages in a non-suspicious way!",
|
description: "Encrypt your Messages in a non-suspicious way!",
|
||||||
authors: [Devs.SammCheese],
|
authors: [Devs.SammCheese],
|
||||||
dependencies: ["MessagePopoverAPI", "ChatInputButtonAPI", "MessageUpdaterAPI"],
|
dependencies: ["MessageUpdaterAPI"],
|
||||||
reporterTestable: ReporterTestable.Patches,
|
reporterTestable: ReporterTestable.Patches,
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
@ -125,7 +124,11 @@ export default definePlugin({
|
||||||
/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/,
|
/(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/,
|
||||||
),
|
),
|
||||||
async start() {
|
async start() {
|
||||||
addButton("InvisibleChat", message => {
|
const { default: StegCloak } = await getStegCloak();
|
||||||
|
steggo = new StegCloak(true, false);
|
||||||
|
},
|
||||||
|
|
||||||
|
renderMessagePopoverButton(message) {
|
||||||
return this.INV_REGEX.test(message?.content)
|
return this.INV_REGEX.test(message?.content)
|
||||||
? {
|
? {
|
||||||
label: "Decrypt Message",
|
label: "Decrypt Message",
|
||||||
|
@ -142,18 +145,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
});
|
|
||||||
|
|
||||||
addChatBarButton("InvisibleChat", ChatBarIcon);
|
|
||||||
|
|
||||||
const { default: StegCloak } = await getStegCloak();
|
|
||||||
steggo = new StegCloak(true, false);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
renderChatBarButton: ChatBarIcon,
|
||||||
removeButton("InvisibleChat");
|
|
||||||
removeButton("InvisibleChat");
|
|
||||||
},
|
|
||||||
|
|
||||||
// Gets the Embed of a Link
|
// Gets the Embed of a Link
|
||||||
async getEmbed(url: URL): Promise<Object | {}> {
|
async getEmbed(url: URL): Promise<Object | {}> {
|
||||||
|
|
17
src/plugins/ircColors/README.md
Normal file
17
src/plugins/ircColors/README.md
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
# IrcColors
|
||||||
|
|
||||||
|
Makes username colors in chat unique, like in IRC clients
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Improves chat readability by assigning every user an unique nickname color,
|
||||||
|
making distinguishing between different users easier. Inspired by the feature
|
||||||
|
in many IRC clients, such as HexChat or WeeChat.
|
||||||
|
|
||||||
|
Keep in mind this overrides role colors in chat, so if you wish to know
|
||||||
|
someone's role color without checking their profile, enable the role dot: go to
|
||||||
|
**User Settings**, **Accessibility** and switch **Role Colors** to **Show role
|
||||||
|
colors next to names**.
|
||||||
|
|
||||||
|
Created for use with the [Compact++](https://gitlab.com/Grzesiek11/compactplusplus-discord-theme)
|
||||||
|
theme.
|
109
src/plugins/ircColors/index.ts
Normal file
109
src/plugins/ircColors/index.ts
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
/*
|
||||||
|
* 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 { definePluginSettings } from "@api/Settings";
|
||||||
|
import { hash as h64 } from "@intrnl/xxhash64";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { useMemo } from "@webpack/common";
|
||||||
|
|
||||||
|
// Calculate a CSS color string based on the user ID
|
||||||
|
function calculateNameColorForUser(id?: string) {
|
||||||
|
const { lightness } = settings.use(["lightness"]);
|
||||||
|
const idHash = useMemo(() => id ? h64(id) : null, [id]);
|
||||||
|
|
||||||
|
return idHash && `hsl(${idHash % 360n}, 100%, ${lightness}%)`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
lightness: {
|
||||||
|
description: "Lightness, in %. Change if the colors are too light or too dark",
|
||||||
|
type: OptionType.NUMBER,
|
||||||
|
default: 70,
|
||||||
|
},
|
||||||
|
memberListColors: {
|
||||||
|
description: "Replace role colors in the member list",
|
||||||
|
restartNeeded: true,
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
applyColorOnlyToUsersWithoutColor: {
|
||||||
|
description: "Apply colors only to users who don't have a predefined color",
|
||||||
|
restartNeeded: false,
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
applyColorOnlyInDms: {
|
||||||
|
description: "Apply colors only in direct messages; do not apply colors in servers.",
|
||||||
|
restartNeeded: false,
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "IrcColors",
|
||||||
|
description: "Makes username colors in chat unique, like in IRC clients",
|
||||||
|
authors: [Devs.Grzesiek11, Devs.jamesbt365],
|
||||||
|
settings,
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: '="SYSTEM_TAG"',
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=className:\i\.username,style:.{0,50}:void 0,)/,
|
||||||
|
replace: "style:{color:$self.calculateNameColorForMessageContext(arguments[0])},"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "#{intl::GUILD_OWNER}),children:",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=\.MEMBER_LIST}\),\[\]\),)(.+?color:)null!=.{0,50}?(?=,)/,
|
||||||
|
replace: (_, rest) => `ircColor=$self.calculateNameColorForListContext(arguments[0]),${rest}ircColor`
|
||||||
|
},
|
||||||
|
predicate: () => settings.store.memberListColors
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
calculateNameColorForMessageContext(context: any) {
|
||||||
|
const id = context?.message?.author?.id;
|
||||||
|
const colorString = context?.author?.colorString;
|
||||||
|
const color = calculateNameColorForUser(id);
|
||||||
|
|
||||||
|
if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) {
|
||||||
|
return colorString;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (!settings.store.applyColorOnlyToUsersWithoutColor || !colorString)
|
||||||
|
? color
|
||||||
|
: colorString;
|
||||||
|
},
|
||||||
|
calculateNameColorForListContext(context: any) {
|
||||||
|
const id = context?.user?.id;
|
||||||
|
const colorString = context?.colorString;
|
||||||
|
const color = calculateNameColorForUser(id);
|
||||||
|
|
||||||
|
if (settings.store.applyColorOnlyInDms && !context?.channel?.isPrivate()) {
|
||||||
|
return colorString;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (!settings.store.applyColorOnlyToUsersWithoutColor || !colorString)
|
||||||
|
? color
|
||||||
|
: colorString;
|
||||||
|
}
|
||||||
|
});
|
|
@ -86,7 +86,7 @@ const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f";
|
||||||
|
|
||||||
const logger = new Logger("LastFMRichPresence");
|
const logger = new Logger("LastFMRichPresence");
|
||||||
|
|
||||||
const presenceStore = findByPropsLazy("getLocalPresence");
|
const PresenceStore = findByPropsLazy("getLocalPresence");
|
||||||
|
|
||||||
async function getApplicationAsset(key: string): Promise<string> {
|
async function getApplicationAsset(key: string): Promise<string> {
|
||||||
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
|
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
|
||||||
|
@ -124,6 +124,11 @@ const settings = definePluginSettings({
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
|
hideWithActivity: {
|
||||||
|
description: "Hide Last.fm presence if you have any other presence",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
statusName: {
|
statusName: {
|
||||||
description: "custom status text",
|
description: "custom status text",
|
||||||
type: OptionType.STRING,
|
type: OptionType.STRING,
|
||||||
|
@ -274,13 +279,17 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
async getActivity(): Promise<Activity | null> {
|
async getActivity(): Promise<Activity | null> {
|
||||||
if (settings.store.hideWithSpotify) {
|
if (settings.store.hideWithActivity) {
|
||||||
for (const activity of presenceStore.getActivities()) {
|
if (PresenceStore.getActivities().some(a => a.application_id !== applicationId)) {
|
||||||
if (activity.type === ActivityType.LISTENING && activity.application_id !== applicationId) {
|
|
||||||
// there is already music status because of Spotify or richerCider (probably more)
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (settings.store.hideWithSpotify) {
|
||||||
|
if (PresenceStore.getActivities().some(a => a.type === ActivityType.LISTENING && a.application_id !== applicationId)) {
|
||||||
|
// there is already music status because of Spotify or richerCider (probably more)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const trackData = await this.fetchTrackData();
|
const trackData = await this.fetchTrackData();
|
||||||
|
|
|
@ -57,7 +57,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".ROLE_MENTION)",
|
find: ".ROLE_MENTION)",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /children:\[\i&&.{0,50}\.RoleDot.{0,300},\i(?=\])/,
|
match: /children:\[\i&&.{0,100}className:\i.roleDot,.{0,200},\i(?=\])/,
|
||||||
replace: "$&,$self.renderRoleIcon(arguments[0])"
|
replace: "$&,$self.renderRoleIcon(arguments[0])"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
|
@ -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 { addClickListener, removeClickListener } from "@api/MessageEvents";
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } 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";
|
||||||
|
@ -57,15 +56,20 @@ export default definePlugin({
|
||||||
name: "MessageClickActions",
|
name: "MessageClickActions",
|
||||||
description: "Hold Backspace and click to delete, double click to edit/reply",
|
description: "Hold Backspace and click to delete, double click to edit/reply",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["MessageEventsAPI"],
|
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
document.addEventListener("keydown", keydown);
|
document.addEventListener("keydown", keydown);
|
||||||
document.addEventListener("keyup", keyup);
|
document.addEventListener("keyup", keyup);
|
||||||
|
},
|
||||||
|
|
||||||
this.onClick = addClickListener((msg: any, channel, event) => {
|
stop() {
|
||||||
|
document.removeEventListener("keydown", keydown);
|
||||||
|
document.removeEventListener("keyup", keyup);
|
||||||
|
},
|
||||||
|
|
||||||
|
onMessageClick(msg: any, channel, event) {
|
||||||
const isMe = msg.author.id === UserStore.getCurrentUser().id;
|
const isMe = msg.author.id === UserStore.getCurrentUser().id;
|
||||||
if (!isDeletePressed) {
|
if (!isDeletePressed) {
|
||||||
if (event.detail < 2) return;
|
if (event.detail < 2) return;
|
||||||
|
@ -111,12 +115,5 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
|
||||||
removeClickListener(this.onClick);
|
|
||||||
document.removeEventListener("keydown", keydown);
|
|
||||||
document.removeEventListener("keyup", keyup);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { isNonNullish } from "@utils/guards";
|
import { isNonNullish } from "@utils/guards";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findExportedComponentLazy } from "@webpack";
|
import { findComponentByCodeLazy } from "@webpack";
|
||||||
import { SnowflakeUtils, Tooltip } from "@webpack/common";
|
import { SnowflakeUtils, Tooltip } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ interface Diff {
|
||||||
}
|
}
|
||||||
|
|
||||||
const DISCORD_KT_DELAY = 1471228928;
|
const DISCORD_KT_DELAY = 1471228928;
|
||||||
const HiddenVisually = findExportedComponentLazy("HiddenVisually");
|
const HiddenVisually = findComponentByCodeLazy(".hiddenVisually]:");
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MessageLatency",
|
name: "MessageLatency",
|
||||||
|
@ -162,7 +162,7 @@ export default definePlugin({
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</Tooltip>;
|
</Tooltip>;
|
||||||
});
|
}, { noop: true });
|
||||||
},
|
},
|
||||||
|
|
||||||
Icon({ delta, fill, props }: {
|
Icon({ delta, fill, props }: {
|
||||||
|
|
|
@ -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 { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories";
|
||||||
import { updateMessage } from "@api/MessageUpdater";
|
import { updateMessage } from "@api/MessageUpdater";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
|
@ -41,6 +41,7 @@ import {
|
||||||
UserStore
|
UserStore
|
||||||
} from "@webpack/common";
|
} from "@webpack/common";
|
||||||
import { Channel, Message } from "discord-types/general";
|
import { Channel, Message } from "discord-types/general";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
const messageCache = new Map<string, {
|
const messageCache = new Map<string, {
|
||||||
message?: Message;
|
message?: Message;
|
||||||
|
@ -119,11 +120,11 @@ const settings = definePluginSettings({
|
||||||
},
|
},
|
||||||
clearMessageCache: {
|
clearMessageCache: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "Clear the linked message cache",
|
component: () => (
|
||||||
component: () =>
|
|
||||||
<Button onClick={() => messageCache.clear()}>
|
<Button onClick={() => messageCache.clear()}>
|
||||||
Clear the linked message cache
|
Clear the linked message cache
|
||||||
</Button>
|
</Button>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -347,10 +348,10 @@ function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
||||||
? parse(message.content)
|
? parse(message.content)
|
||||||
: [noContent(message.attachments.length, message.embeds.length)]
|
: [noContent(message.attachments.length, message.embeds.length)]
|
||||||
}
|
}
|
||||||
{images.map(a => {
|
{images.map((a, idx) => {
|
||||||
const { width, height } = computeWidthAndHeight(a.width, a.height);
|
const { width, height } = computeWidthAndHeight(a.width, a.height);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div key={idx}>
|
||||||
<img src={a.url} width={width} height={height} />
|
<img src={a.url} width={width} height={height} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -372,7 +373,7 @@ export default definePlugin({
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
addAccessory("messageLinkEmbed", props => {
|
addMessageAccessory("messageLinkEmbed", props => {
|
||||||
if (!messageLinkRegex.test(props.message.content))
|
if (!messageLinkRegex.test(props.message.content))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
@ -390,6 +391,6 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
removeAccessory("messageLinkEmbed");
|
removeMessageAccessory("messageLinkEmbed");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -69,6 +69,7 @@ export function HistoryModal({ modalProps, message }: { modalProps: ModalProps;
|
||||||
|
|
||||||
{timestamps.map((timestamp, index) => (
|
{timestamps.map((timestamp, index) => (
|
||||||
<TabBar.Item
|
<TabBar.Item
|
||||||
|
key={index}
|
||||||
className="vc-settings-tab-bar-item"
|
className="vc-settings-tab-bar-item"
|
||||||
id={index}
|
id={index}
|
||||||
>
|
>
|
||||||
|
|
|
@ -169,8 +169,8 @@ export default definePlugin({
|
||||||
|
|
||||||
return Settings.plugins.MessageLogger.inlineEdits && (
|
return Settings.plugins.MessageLogger.inlineEdits && (
|
||||||
<>
|
<>
|
||||||
{message.editHistory?.map(edit => (
|
{message.editHistory?.map((edit, idx) => (
|
||||||
<div className="messagelogger-edited">
|
<div key={idx} className="messagelogger-edited">
|
||||||
{parseEditContent(edit.content, message)}
|
{parseEditContent(edit.content, message)}
|
||||||
<Timestamp
|
<Timestamp
|
||||||
timestamp={edit.timestamp}
|
timestamp={edit.timestamp}
|
||||||
|
@ -211,7 +211,8 @@ export default definePlugin({
|
||||||
collapseDeleted: {
|
collapseDeleted: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Whether to collapse deleted messages, similar to blocked messages",
|
description: "Whether to collapse deleted messages, similar to blocked messages",
|
||||||
default: false
|
default: false,
|
||||||
|
restartNeeded: true,
|
||||||
},
|
},
|
||||||
logEdits: {
|
logEdits: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
|
@ -304,7 +305,7 @@ export default definePlugin({
|
||||||
{...props}
|
{...props}
|
||||||
className={classes("messagelogger-edit-marker", className)}
|
className={classes("messagelogger-edit-marker", className)}
|
||||||
onClick={() => openHistoryModal(message)}
|
onClick={() => openHistoryModal(message)}
|
||||||
aria-role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</span>
|
</span>
|
||||||
|
@ -346,35 +347,35 @@ export default definePlugin({
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Add deleted=true to all target messages in the MESSAGE_DELETE event
|
// Add deleted=true to all target messages in the MESSAGE_DELETE event
|
||||||
match: /MESSAGE_DELETE:function\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?},/,
|
match: /function (?=.+?MESSAGE_DELETE:(\i))\1\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?}(?=function)/,
|
||||||
replace:
|
replace:
|
||||||
"MESSAGE_DELETE:function($1){" +
|
"function $1($2){" +
|
||||||
" var cache = $2getOrCreate($1.channelId);" +
|
" var cache = $3getOrCreate($2.channelId);" +
|
||||||
" cache = $self.handleDelete(cache, $1, false);" +
|
" cache = $self.handleDelete(cache, $2, false);" +
|
||||||
" $2commit(cache);" +
|
" $3commit(cache);" +
|
||||||
"},"
|
"}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Add deleted=true to all target messages in the MESSAGE_DELETE_BULK event
|
// Add deleted=true to all target messages in the MESSAGE_DELETE_BULK event
|
||||||
match: /MESSAGE_DELETE_BULK:function\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?},/,
|
match: /function (?=.+?MESSAGE_DELETE_BULK:(\i))\1\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?}(?=function)/,
|
||||||
replace:
|
replace:
|
||||||
"MESSAGE_DELETE_BULK:function($1){" +
|
"function $1($2){" +
|
||||||
" var cache = $2getOrCreate($1.channelId);" +
|
" var cache = $3getOrCreate($2.channelId);" +
|
||||||
" cache = $self.handleDelete(cache, $1, true);" +
|
" cache = $self.handleDelete(cache, $2, true);" +
|
||||||
" $2commit(cache);" +
|
" $3commit(cache);" +
|
||||||
"},"
|
"}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Add current cached content + new edit time to cached message's editHistory
|
// Add current cached content + new edit time to cached message's editHistory
|
||||||
match: /(MESSAGE_UPDATE:function\((\i)\).+?)\.update\((\i)/,
|
match: /(function (\i)\((\i)\).+?)\.update\((\i)(?=.*MESSAGE_UPDATE:\2)/,
|
||||||
replace: "$1" +
|
replace: "$1" +
|
||||||
".update($3,m =>" +
|
".update($4,m =>" +
|
||||||
" (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message, true)) ? m :" +
|
" (($3.message.flags & 64) === 64 || $self.shouldIgnore($3.message, true)) ? m :" +
|
||||||
" $2.message.edited_timestamp && $2.message.content !== m.content ?" +
|
" $3.message.edited_timestamp && $3.message.content !== m.content ?" +
|
||||||
" m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($2.message, m)]) :" +
|
" m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($3.message, m)]) :" +
|
||||||
" m" +
|
" m" +
|
||||||
")" +
|
")" +
|
||||||
".update($3"
|
".update($4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// fix up key (edit last message) attempting to edit a deleted message
|
// fix up key (edit last message) attempting to edit a deleted message
|
||||||
|
@ -488,19 +489,19 @@ export default definePlugin({
|
||||||
find: '"ReferencedMessageStore"',
|
find: '"ReferencedMessageStore"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /MESSAGE_DELETE:function\((\i)\).+?},/,
|
match: /MESSAGE_DELETE:\i,/,
|
||||||
replace: "MESSAGE_DELETE:function($1){},"
|
replace: "MESSAGE_DELETE:()=>{},"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /MESSAGE_DELETE_BULK:function\((\i)\).+?},/,
|
match: /MESSAGE_DELETE_BULK:\i,/,
|
||||||
replace: "MESSAGE_DELETE_BULK:function($1){},"
|
replace: "MESSAGE_DELETE_BULK:()=>{},"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
// Message context base menu
|
// Message context base menu
|
||||||
find: "useMessageMenu:",
|
find: ".MESSAGE,commandTargetId:",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Remove the first section if message is deleted
|
// Remove the first section if message is deleted
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands";
|
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands";
|
||||||
import * as DataStore from "@api/DataStore";
|
import * as DataStore from "@api/DataStore";
|
||||||
import { Settings } from "@api/Settings";
|
import { definePluginSettings } 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";
|
||||||
|
|
||||||
|
@ -29,23 +29,23 @@ const MessageTagsMarker = Symbol("MessageTags");
|
||||||
interface Tag {
|
interface Tag {
|
||||||
name: string;
|
name: string;
|
||||||
message: string;
|
message: string;
|
||||||
enabled: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTags = () => DataStore.get(DATA_KEY).then<Tag[]>(t => t ?? []);
|
function getTags() {
|
||||||
const getTag = (name: string) => DataStore.get(DATA_KEY).then<Tag | null>((t: Tag[]) => (t ?? []).find((tt: Tag) => tt.name === name) ?? null);
|
return settings.store.tagsList;
|
||||||
const addTag = async (tag: Tag) => {
|
}
|
||||||
const tags = await getTags();
|
|
||||||
tags.push(tag);
|
function getTag(name: string) {
|
||||||
DataStore.set(DATA_KEY, tags);
|
return settings.store.tagsList[name] ?? null;
|
||||||
return tags;
|
}
|
||||||
};
|
|
||||||
const removeTag = async (name: string) => {
|
function addTag(tag: Tag) {
|
||||||
let tags = await getTags();
|
settings.store.tagsList[tag.name] = tag;
|
||||||
tags = await tags.filter((t: Tag) => t.name !== name);
|
}
|
||||||
DataStore.set(DATA_KEY, tags);
|
|
||||||
return tags;
|
function removeTag(name: string) {
|
||||||
};
|
delete settings.store.tagsList[name];
|
||||||
|
}
|
||||||
|
|
||||||
function createTagCommand(tag: Tag) {
|
function createTagCommand(tag: Tag) {
|
||||||
registerCommand({
|
registerCommand({
|
||||||
|
@ -53,14 +53,14 @@ function createTagCommand(tag: Tag) {
|
||||||
description: tag.name,
|
description: tag.name,
|
||||||
inputType: ApplicationCommandInputType.BUILT_IN_TEXT,
|
inputType: ApplicationCommandInputType.BUILT_IN_TEXT,
|
||||||
execute: async (_, ctx) => {
|
execute: async (_, ctx) => {
|
||||||
if (!await getTag(tag.name)) {
|
if (!getTag(tag.name)) {
|
||||||
sendBotMessage(ctx.channel.id, {
|
sendBotMessage(ctx.channel.id, {
|
||||||
content: `${EMOTE} The tag **${tag.name}** does not exist anymore! Please reload ur Discord to fix :)`
|
content: `${EMOTE} The tag **${tag.name}** does not exist anymore! Please reload ur Discord to fix :)`
|
||||||
});
|
});
|
||||||
return { content: `/${tag.name}` };
|
return { content: `/${tag.name}` };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Settings.plugins.MessageTags.clyde) sendBotMessage(ctx.channel.id, {
|
if (settings.store.clyde) sendBotMessage(ctx.channel.id, {
|
||||||
content: `${EMOTE} The tag **${tag.name}** has been sent!`
|
content: `${EMOTE} The tag **${tag.name}** has been sent!`
|
||||||
});
|
});
|
||||||
return { content: tag.message.replaceAll("\\n", "\n") };
|
return { content: tag.message.replaceAll("\\n", "\n") };
|
||||||
|
@ -69,22 +69,38 @@ function createTagCommand(tag: Tag) {
|
||||||
}, "CustomTags");
|
}, "CustomTags");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
export default definePlugin({
|
|
||||||
name: "MessageTags",
|
|
||||||
description: "Allows you to save messages and to use them with a simple command.",
|
|
||||||
authors: [Devs.Luna],
|
|
||||||
options: {
|
|
||||||
clyde: {
|
clyde: {
|
||||||
name: "Clyde message on send",
|
name: "Clyde message on send",
|
||||||
description: "If enabled, clyde will send you an ephemeral message when a tag was used.",
|
description: "If enabled, clyde will send you an ephemeral message when a tag was used.",
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
default: true
|
default: true
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
tagsList: {
|
||||||
|
type: OptionType.CUSTOM,
|
||||||
|
default: {} as Record<string, Tag>,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "MessageTags",
|
||||||
|
description: "Allows you to save messages and to use them with a simple command.",
|
||||||
|
authors: [Devs.Luna],
|
||||||
|
settings,
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
for (const tag of await getTags()) createTagCommand(tag);
|
// TODO(OptionType.CUSTOM Related): Remove DataStore tags migration once enough time has passed
|
||||||
|
const oldTags = await DataStore.get<Tag[]>(DATA_KEY);
|
||||||
|
if (oldTags != null) {
|
||||||
|
// @ts-ignore
|
||||||
|
settings.store.tagsList = Object.fromEntries(oldTags.map(oldTag => (delete oldTag.enabled, [oldTag.name, oldTag])));
|
||||||
|
await DataStore.del(DATA_KEY);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tags = getTags();
|
||||||
|
for (const tagName in tags) {
|
||||||
|
createTagCommand(tags[tagName]);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
commands: [
|
commands: [
|
||||||
|
@ -153,19 +169,18 @@ export default definePlugin({
|
||||||
const name: string = findOption(args[0].options, "tag-name", "");
|
const name: string = findOption(args[0].options, "tag-name", "");
|
||||||
const message: string = findOption(args[0].options, "message", "");
|
const message: string = findOption(args[0].options, "message", "");
|
||||||
|
|
||||||
if (await getTag(name))
|
if (getTag(name))
|
||||||
return sendBotMessage(ctx.channel.id, {
|
return sendBotMessage(ctx.channel.id, {
|
||||||
content: `${EMOTE} A Tag with the name **${name}** already exists!`
|
content: `${EMOTE} A Tag with the name **${name}** already exists!`
|
||||||
});
|
});
|
||||||
|
|
||||||
const tag = {
|
const tag = {
|
||||||
name: name,
|
name: name,
|
||||||
enabled: true,
|
|
||||||
message: message
|
message: message
|
||||||
};
|
};
|
||||||
|
|
||||||
createTagCommand(tag);
|
createTagCommand(tag);
|
||||||
await addTag(tag);
|
addTag(tag);
|
||||||
|
|
||||||
sendBotMessage(ctx.channel.id, {
|
sendBotMessage(ctx.channel.id, {
|
||||||
content: `${EMOTE} Successfully created the tag **${name}**!`
|
content: `${EMOTE} Successfully created the tag **${name}**!`
|
||||||
|
@ -175,13 +190,13 @@ export default definePlugin({
|
||||||
case "delete": {
|
case "delete": {
|
||||||
const name: string = findOption(args[0].options, "tag-name", "");
|
const name: string = findOption(args[0].options, "tag-name", "");
|
||||||
|
|
||||||
if (!await getTag(name))
|
if (!getTag(name))
|
||||||
return sendBotMessage(ctx.channel.id, {
|
return sendBotMessage(ctx.channel.id, {
|
||||||
content: `${EMOTE} A Tag with the name **${name}** does not exist!`
|
content: `${EMOTE} A Tag with the name **${name}** does not exist!`
|
||||||
});
|
});
|
||||||
|
|
||||||
unregisterCommand(name);
|
unregisterCommand(name);
|
||||||
await removeTag(name);
|
removeTag(name);
|
||||||
|
|
||||||
sendBotMessage(ctx.channel.id, {
|
sendBotMessage(ctx.channel.id, {
|
||||||
content: `${EMOTE} Successfully deleted the tag **${name}**!`
|
content: `${EMOTE} Successfully deleted the tag **${name}**!`
|
||||||
|
@ -192,10 +207,8 @@ export default definePlugin({
|
||||||
sendBotMessage(ctx.channel.id, {
|
sendBotMessage(ctx.channel.id, {
|
||||||
embeds: [
|
embeds: [
|
||||||
{
|
{
|
||||||
// @ts-ignore
|
|
||||||
title: "All Tags:",
|
title: "All Tags:",
|
||||||
// @ts-ignore
|
description: Object.values(getTags())
|
||||||
description: (await getTags())
|
|
||||||
.map(tag => `\`${tag.name}\`: ${tag.message.slice(0, 72).replaceAll("\\n", " ")}${tag.message.length > 72 ? "..." : ""}`)
|
.map(tag => `\`${tag.name}\`: ${tag.message.slice(0, 72).replaceAll("\\n", " ")}${tag.message.length > 72 ? "..." : ""}`)
|
||||||
.join("\n") || `${EMOTE} Woops! There are no tags yet, use \`/tags create\` to create one!`,
|
.join("\n") || `${EMOTE} Woops! There are no tags yet, use \`/tags create\` to create one!`,
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -208,7 +221,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
case "preview": {
|
case "preview": {
|
||||||
const name: string = findOption(args[0].options, "tag-name", "");
|
const name: string = findOption(args[0].options, "tag-name", "");
|
||||||
const tag = await getTag(name);
|
const tag = getTag(name);
|
||||||
|
|
||||||
if (!tag)
|
if (!tag)
|
||||||
return sendBotMessage(ctx.channel.id, {
|
return sendBotMessage(ctx.channel.id, {
|
||||||
|
|
|
@ -1,374 +0,0 @@
|
||||||
/*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import { Flex } from "@components/Flex";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import { getIntlMessage } from "@utils/discord";
|
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
|
||||||
import { findByCodeLazy, findLazy } from "@webpack";
|
|
||||||
import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip } from "@webpack/common";
|
|
||||||
import type { Permissions, RC } from "@webpack/types";
|
|
||||||
import type { Channel, Guild, Message, User } from "discord-types/general";
|
|
||||||
|
|
||||||
interface Tag {
|
|
||||||
// name used for identifying, must be alphanumeric + underscores
|
|
||||||
name: string;
|
|
||||||
// name shown on the tag itself, can be anything probably; automatically uppercase'd
|
|
||||||
displayName: string;
|
|
||||||
description: string;
|
|
||||||
permissions?: Permissions[];
|
|
||||||
condition?(message: Message | null, user: User, channel: Channel): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface TagSetting {
|
|
||||||
text: string;
|
|
||||||
showInChat: boolean;
|
|
||||||
showInNotChat: boolean;
|
|
||||||
}
|
|
||||||
interface TagSettings {
|
|
||||||
WEBHOOK: TagSetting,
|
|
||||||
OWNER: TagSetting,
|
|
||||||
ADMINISTRATOR: TagSetting,
|
|
||||||
MODERATOR_STAFF: TagSetting,
|
|
||||||
MODERATOR: TagSetting,
|
|
||||||
VOICE_MODERATOR: TagSetting,
|
|
||||||
TRIAL_MODERATOR: TagSetting,
|
|
||||||
[k: string]: TagSetting;
|
|
||||||
}
|
|
||||||
|
|
||||||
// PermissionStore.computePermissions will not work here since it only gets permissions for the current user
|
|
||||||
const computePermissions: (options: {
|
|
||||||
user?: { id: string; } | string | null;
|
|
||||||
context?: Guild | Channel | null;
|
|
||||||
overwrites?: Channel["permissionOverwrites"] | null;
|
|
||||||
checkElevated?: boolean /* = true */;
|
|
||||||
excludeGuildPermissions?: boolean /* = false */;
|
|
||||||
}) => bigint = findByCodeLazy(".getCurrentUser()", ".computeLurkerPermissionsAllowList()");
|
|
||||||
|
|
||||||
const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; };
|
|
||||||
|
|
||||||
const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot();
|
|
||||||
|
|
||||||
const tags: Tag[] = [
|
|
||||||
{
|
|
||||||
name: "WEBHOOK",
|
|
||||||
displayName: "Webhook",
|
|
||||||
description: "Messages sent by webhooks",
|
|
||||||
condition: isWebhook
|
|
||||||
}, {
|
|
||||||
name: "OWNER",
|
|
||||||
displayName: "Owner",
|
|
||||||
description: "Owns the server",
|
|
||||||
condition: (_, user, channel) => GuildStore.getGuild(channel?.guild_id)?.ownerId === user.id
|
|
||||||
}, {
|
|
||||||
name: "ADMINISTRATOR",
|
|
||||||
displayName: "Admin",
|
|
||||||
description: "Has the administrator permission",
|
|
||||||
permissions: ["ADMINISTRATOR"]
|
|
||||||
}, {
|
|
||||||
name: "MODERATOR_STAFF",
|
|
||||||
displayName: "Staff",
|
|
||||||
description: "Can manage the server, channels or roles",
|
|
||||||
permissions: ["MANAGE_GUILD", "MANAGE_CHANNELS", "MANAGE_ROLES"]
|
|
||||||
}, {
|
|
||||||
name: "MODERATOR",
|
|
||||||
displayName: "Mod",
|
|
||||||
description: "Can manage messages or kick/ban people",
|
|
||||||
permissions: ["MANAGE_MESSAGES", "KICK_MEMBERS", "BAN_MEMBERS"]
|
|
||||||
}, {
|
|
||||||
name: "VOICE_MODERATOR",
|
|
||||||
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(
|
|
||||||
tags.map(({ name, displayName }) => [name, { text: displayName, showInChat: true, showInNotChat: true }])
|
|
||||||
) as TagSettings;
|
|
||||||
|
|
||||||
function SettingsComponent() {
|
|
||||||
const tagSettings = settings.store.tagSettings ??= defaultSettings;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Flex flexDirection="column">
|
|
||||||
{tags.map(t => (
|
|
||||||
<Card style={{ padding: "1em 1em 0" }}>
|
|
||||||
<Forms.FormTitle style={{ width: "fit-content" }}>
|
|
||||||
<Tooltip text={t.description}>
|
|
||||||
{({ onMouseEnter, onMouseLeave }) => (
|
|
||||||
<div
|
|
||||||
onMouseEnter={onMouseEnter}
|
|
||||||
onMouseLeave={onMouseLeave}
|
|
||||||
>
|
|
||||||
{t.displayName} Tag <Tag type={Tag.Types[t.name]} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
</Forms.FormTitle>
|
|
||||||
|
|
||||||
<TextInput
|
|
||||||
type="text"
|
|
||||||
value={tagSettings[t.name]?.text ?? t.displayName}
|
|
||||||
placeholder={`Text on tag (default: ${t.displayName})`}
|
|
||||||
onChange={v => tagSettings[t.name].text = v}
|
|
||||||
className={Margins.bottom16}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Switch
|
|
||||||
value={tagSettings[t.name]?.showInChat ?? true}
|
|
||||||
onChange={v => tagSettings[t.name].showInChat = v}
|
|
||||||
hideBorder
|
|
||||||
>
|
|
||||||
Show in messages
|
|
||||||
</Switch>
|
|
||||||
|
|
||||||
<Switch
|
|
||||||
value={tagSettings[t.name]?.showInNotChat ?? true}
|
|
||||||
onChange={v => tagSettings[t.name].showInNotChat = v}
|
|
||||||
hideBorder
|
|
||||||
>
|
|
||||||
Show in member list and profiles
|
|
||||||
</Switch>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</Flex>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
dontShowForBots: {
|
|
||||||
description: "Don't show extra tags for bots (excluding webhooks)",
|
|
||||||
type: OptionType.BOOLEAN
|
|
||||||
},
|
|
||||||
dontShowBotTag: {
|
|
||||||
description: "Only show extra tags for bots / Hide [BOT] text",
|
|
||||||
type: OptionType.BOOLEAN
|
|
||||||
},
|
|
||||||
tagSettings: {
|
|
||||||
type: OptionType.COMPONENT,
|
|
||||||
component: SettingsComponent,
|
|
||||||
description: "fill me"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "MoreUserTags",
|
|
||||||
description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)",
|
|
||||||
authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN],
|
|
||||||
settings,
|
|
||||||
patches: [
|
|
||||||
// add tags to the tag list
|
|
||||||
{
|
|
||||||
find: ".ORIGINAL_POSTER=",
|
|
||||||
replacement: {
|
|
||||||
match: /\((\i)=\{\}\)\)\[(\i)\.BOT/,
|
|
||||||
replace: "($1=$self.getTagTypes()))[$2.BOT"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: "#{intl::DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP_OFFICIAL}",
|
|
||||||
replacement: [
|
|
||||||
// make the tag show the right text
|
|
||||||
{
|
|
||||||
match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=(.{0,40}#{intl::APP_TAG}\))/,
|
|
||||||
replace: (_, origSwitch, variant, tags, displayedText, originalText) =>
|
|
||||||
`${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}],${originalText})}`
|
|
||||||
},
|
|
||||||
// show OP tags correctly
|
|
||||||
{
|
|
||||||
match: /(\i)=(\i)===\i(?:\.\i)?\.ORIGINAL_POSTER/,
|
|
||||||
replace: "$1=$self.isOPTag($2)"
|
|
||||||
},
|
|
||||||
// add HTML data attributes (for easier theming)
|
|
||||||
{
|
|
||||||
match: /.botText,children:(\i)}\)]/,
|
|
||||||
replace: "$&,'data-tag':$1.toLowerCase()"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
// in messages
|
|
||||||
{
|
|
||||||
find: ".Types.ORIGINAL_POSTER",
|
|
||||||
replacement: {
|
|
||||||
match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)/,
|
|
||||||
replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// in the member list
|
|
||||||
{
|
|
||||||
find: "#{intl::GUILD_OWNER}",
|
|
||||||
replacement: {
|
|
||||||
match: /(?<type>\i)=\(null==.{0,100}\.BOT;return null!=(?<user>\i)&&\i\.bot/,
|
|
||||||
replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }); return typeof $<type> === 'number'"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// pass channel id down props to be used in profiles
|
|
||||||
{
|
|
||||||
find: ".hasAvatarForGuild(null==",
|
|
||||||
replacement: {
|
|
||||||
match: /(?=usernameIcon:)/,
|
|
||||||
replace: "moreTags_channelId:arguments[0].channelId,"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
find: "#{intl::USER_PROFILE_PRONOUNS}",
|
|
||||||
replacement: {
|
|
||||||
match: /(?=,hideBotTag:!0)/,
|
|
||||||
replace: ",moreTags_channelId:arguments[0].moreTags_channelId"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// in profiles
|
|
||||||
{
|
|
||||||
find: ",overrideDiscriminator:",
|
|
||||||
group: true,
|
|
||||||
replacement: [
|
|
||||||
{
|
|
||||||
// prevent channel id from getting ghosted
|
|
||||||
// it's either this or extremely long lookbehind
|
|
||||||
match: /user:\i,nick:\i,/,
|
|
||||||
replace: "$&moreTags_channelId,"
|
|
||||||
}, {
|
|
||||||
match: /,botType:(\i),botVerified:(\i),(?!discriminatorClass:)(?<=user:(\i).+?)/g,
|
|
||||||
replace: ",botType:$self.getTag({user:$3,channelId:moreTags_channelId,origType:$1,location:'not-chat'}),botVerified:$2,"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
],
|
|
||||||
|
|
||||||
start() {
|
|
||||||
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[] {
|
|
||||||
const guild = GuildStore.getGuild(channel?.guild_id);
|
|
||||||
if (!guild) return [];
|
|
||||||
|
|
||||||
const permissions = computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
|
|
||||||
return Object.entries(PermissionsBits)
|
|
||||||
.map(([perm, permInt]) =>
|
|
||||||
permissions & permInt ? perm : ""
|
|
||||||
)
|
|
||||||
.filter(Boolean);
|
|
||||||
},
|
|
||||||
|
|
||||||
getTagTypes() {
|
|
||||||
const obj = {};
|
|
||||||
let i = 100;
|
|
||||||
tags.forEach(({ name }) => {
|
|
||||||
obj[name] = ++i;
|
|
||||||
obj[i] = name;
|
|
||||||
obj[`${name}-BOT`] = ++i;
|
|
||||||
obj[i] = `${name}-BOT`;
|
|
||||||
obj[`${name}-OP`] = ++i;
|
|
||||||
obj[i] = `${name}-OP`;
|
|
||||||
});
|
|
||||||
return obj;
|
|
||||||
},
|
|
||||||
|
|
||||||
isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]),
|
|
||||||
|
|
||||||
getTagText(passedTagName: string, originalText: string) {
|
|
||||||
try {
|
|
||||||
const [tagName, variant] = passedTagName.split("-");
|
|
||||||
if (!passedTagName) return getIntlMessage("APP_TAG");
|
|
||||||
const tag = tags.find(({ name }) => tagName === name);
|
|
||||||
if (!tag) return getIntlMessage("APP_TAG");
|
|
||||||
if (variant === "BOT" && tagName !== "WEBHOOK" && this.settings.store.dontShowForBots) return getIntlMessage("APP_TAG");
|
|
||||||
|
|
||||||
const tagText = settings.store.tagSettings?.[tag.name]?.text || tag.displayName;
|
|
||||||
switch (variant) {
|
|
||||||
case "OP":
|
|
||||||
return `${getIntlMessage("BOT_TAG_FORUM_ORIGINAL_POSTER")} • ${tagText}`;
|
|
||||||
case "BOT":
|
|
||||||
return `${getIntlMessage("APP_TAG")} • ${tagText}`;
|
|
||||||
default:
|
|
||||||
return tagText;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
return originalText;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getTag({
|
|
||||||
message, user, channelId, origType, location, channel
|
|
||||||
}: {
|
|
||||||
message?: Message,
|
|
||||||
user: User & { isClyde(): boolean; },
|
|
||||||
channel?: Channel & { isForumPost(): boolean; isMediaPost(): boolean; },
|
|
||||||
channelId?: string;
|
|
||||||
origType?: number;
|
|
||||||
location: "chat" | "not-chat";
|
|
||||||
}): number | null {
|
|
||||||
if (!user)
|
|
||||||
return null;
|
|
||||||
if (location === "chat" && user.id === "1")
|
|
||||||
return Tag.Types.OFFICIAL;
|
|
||||||
if (user.isClyde())
|
|
||||||
return Tag.Types.AI;
|
|
||||||
|
|
||||||
let type = typeof origType === "number" ? origType : null;
|
|
||||||
|
|
||||||
channel ??= ChannelStore.getChannel(channelId!) as any;
|
|
||||||
if (!channel) return type;
|
|
||||||
|
|
||||||
const settings = this.settings.store;
|
|
||||||
const perms = this.getPermissions(user, channel);
|
|
||||||
|
|
||||||
for (const tag of tags) {
|
|
||||||
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))
|
|
||||||
) {
|
|
||||||
if ((channel.isForumPost() || channel.isMediaPost()) && channel.ownerId === user.id)
|
|
||||||
type = Tag.Types[`${tag.name}-OP`];
|
|
||||||
else if (user.bot && !isWebhook(message!, user) && !settings.dontShowBotTag)
|
|
||||||
type = Tag.Types[`${tag.name}-BOT`];
|
|
||||||
else
|
|
||||||
type = Tag.Types[tag.name];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -24,6 +24,7 @@ import definePlugin from "@utils/types";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, useMemo, UserStore } from "@webpack/common";
|
import { Avatar, ChannelStore, Clickable, IconUtils, RelationshipStore, ScrollerThin, useMemo, UserStore } from "@webpack/common";
|
||||||
import { Channel, User } from "discord-types/general";
|
import { Channel, User } from "discord-types/general";
|
||||||
|
import { JSX } from "react";
|
||||||
|
|
||||||
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
|
const SelectedChannelActionCreators = findByPropsLazy("selectPrivateChannel");
|
||||||
const UserUtils = findByPropsLazy("getGlobalName");
|
const UserUtils = findByPropsLazy("getGlobalName");
|
||||||
|
@ -55,6 +56,7 @@ function getMutualGDMCountText(user: User) {
|
||||||
function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) {
|
function renderClickableGDMs(mutualDms: Channel[], onClose: () => void) {
|
||||||
return mutualDms.map(c => (
|
return mutualDms.map(c => (
|
||||||
<Clickable
|
<Clickable
|
||||||
|
key={c.id}
|
||||||
className={ProfileListClasses.listRow}
|
className={ProfileListClasses.listRow}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClose();
|
onClose();
|
||||||
|
|
|
@ -20,7 +20,7 @@ import {
|
||||||
findGroupChildrenByChildId,
|
findGroupChildrenByChildId,
|
||||||
NavContextMenuPatchCallback
|
NavContextMenuPatchCallback
|
||||||
} from "@api/ContextMenu";
|
} from "@api/ContextMenu";
|
||||||
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { CogWheel } from "@components/Icons";
|
import { CogWheel } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
@ -115,8 +115,6 @@ function applyDefaultSettings(guildId: string | null) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
migratePluginSettings("NewGuildSettings", "MuteNewGuild");
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "NewGuildSettings",
|
name: "NewGuildSettings",
|
||||||
description: "Automatically mute new servers and change various other settings upon joining",
|
description: "Automatically mute new servers and change various other settings upon joining",
|
||||||
|
|
|
@ -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 { Settings } from "@api/Settings";
|
import { definePluginSettings, migratePluginSetting } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { runtimeHashMessageKey } from "@utils/intlHash";
|
import { runtimeHashMessageKey } from "@utils/intlHash";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
|
@ -32,10 +32,29 @@ interface MessageDeleteProps {
|
||||||
collapsedReason: () => any;
|
collapsedReason: () => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove this migration once enough time has passed
|
||||||
|
migratePluginSetting("NoBlockedMessages", "ignoreBlockedMessages", "ignoreMessages");
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
ignoreMessages: {
|
||||||
|
description: "Completely ignores incoming messages from blocked and ignored (if enabled) users",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: false,
|
||||||
|
restartNeeded: true
|
||||||
|
},
|
||||||
|
applyToIgnoredUsers: {
|
||||||
|
description: "Additionally apply to 'ignored' users",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
restartNeeded: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "NoBlockedMessages",
|
name: "NoBlockedMessages",
|
||||||
description: "Hides all blocked messages from chat completely.",
|
description: "Hides all blocked/ignored messages from chat completely",
|
||||||
authors: [Devs.rushii, Devs.Samu],
|
authors: [Devs.rushii, Devs.Samu, Devs.jamesbt365],
|
||||||
|
settings,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "#{intl::BLOCKED_MESSAGES_HIDE}",
|
find: "#{intl::BLOCKED_MESSAGES_HIDE}",
|
||||||
|
@ -51,38 +70,40 @@ export default definePlugin({
|
||||||
'"ReadStateStore"'
|
'"ReadStateStore"'
|
||||||
].map(find => ({
|
].map(find => ({
|
||||||
find,
|
find,
|
||||||
predicate: () => Settings.plugins.NoBlockedMessages.ignoreBlockedMessages === true,
|
predicate: () => settings.store.ignoreMessages,
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?<=MESSAGE_CREATE:function\((\i)\){)/,
|
match: /(?<=function (\i)\((\i)\){)(?=.*MESSAGE_CREATE:\1)/,
|
||||||
replace: (_, props) => `if($self.isBlocked(${props}.message))return;`
|
replace: (_, _funcName, props) => `if($self.shouldIgnoreMessage(${props}.message))return;`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}))
|
}))
|
||||||
],
|
],
|
||||||
options: {
|
|
||||||
ignoreBlockedMessages: {
|
|
||||||
description: "Completely ignores (recent) incoming messages from blocked users (locally).",
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
default: false,
|
|
||||||
restartNeeded: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
isBlocked(message: Message) {
|
shouldIgnoreMessage(message: Message) {
|
||||||
try {
|
try {
|
||||||
return RelationshipStore.isBlocked(message.author.id);
|
if (RelationshipStore.isBlocked(message.author.id)) {
|
||||||
} catch (e) {
|
return true;
|
||||||
new Logger("NoBlockedMessages").error("Failed to check if user is blocked:", e);
|
|
||||||
}
|
}
|
||||||
},
|
return settings.store.applyToIgnoredUsers && RelationshipStore.isIgnored(message.author.id);
|
||||||
|
|
||||||
shouldHide(props: MessageDeleteProps) {
|
|
||||||
try {
|
|
||||||
return props.collapsedReason() === i18n.t[runtimeHashMessageKey("BLOCKED_MESSAGE_COUNT")]();
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
new Logger("NoBlockedMessages").error("Failed to check if user is blocked or ignored:", e);
|
||||||
}
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
shouldHide(props: MessageDeleteProps): boolean {
|
||||||
|
try {
|
||||||
|
const collapsedReason = props.collapsedReason();
|
||||||
|
const blockedReason = i18n.t[runtimeHashMessageKey("BLOCKED_MESSAGE_COUNT")]();
|
||||||
|
const ignoredReason = settings.store.applyToIgnoredUsers
|
||||||
|
? i18n.t[runtimeHashMessageKey("IGNORED_MESSAGE_COUNT")]()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return collapsedReason === blockedReason || collapsedReason === ignoredReason;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -74,10 +74,10 @@ export default definePlugin({
|
||||||
// This prevents the Message Requests tab from always hiding due to the previous patch (and is compatible with spam requests)
|
// This prevents the Message Requests tab from always hiding due to the previous patch (and is compatible with spam requests)
|
||||||
// In short, only the red badge is hidden. Button visibility behavior isn't changed.
|
// In short, only the red badge is hidden. Button visibility behavior isn't changed.
|
||||||
{
|
{
|
||||||
find: ".getSpamChannelsCount()",
|
find: ".getSpamChannelsCount();return",
|
||||||
predicate: () => settings.store.hideMessageRequestsCount,
|
predicate: () => settings.store.hideMessageRequestsCount,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=getSpamChannelsCount\(\),\i=)\i\.getMessageRequestsCount\(\)/,
|
match: /(?<=getSpamChannelsCount\(\);return )\i\.getMessageRequestsCount\(\)/,
|
||||||
replace: "$self.getRealMessageRequestCount()"
|
replace: "$self.getRealMessageRequestCount()"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* 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/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { Settings } from "@api/Settings";
|
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
|
|
||||||
const DisableStreamPreviews = getUserSettingLazy<boolean>("voiceAndVideo", "disableStreamPreviews")!;
|
|
||||||
|
|
||||||
// @TODO: Delete this plugin in the future
|
|
||||||
export default definePlugin({
|
|
||||||
name: "NoScreensharePreview",
|
|
||||||
description: "Disables screenshare previews from being sent.",
|
|
||||||
authors: [Devs.Nuckyz],
|
|
||||||
start() {
|
|
||||||
if (!DisableStreamPreviews.getSetting()) {
|
|
||||||
DisableStreamPreviews.updateSetting(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
Settings.plugins.NoScreensharePreview.enabled = false;
|
|
||||||
}
|
|
||||||
});
|
|
5
src/plugins/noUnblockToJump/README.md
Normal file
5
src/plugins/noUnblockToJump/README.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# No Unblock To Jump
|
||||||
|
|
||||||
|
Removes the popup preventing you to jump to a message from a blocked/ignored user (eg: in search results)
|
||||||
|
|
||||||
|

|
|
@ -26,25 +26,46 @@ export default definePlugin({
|
||||||
authors: [Devs.dzshn],
|
authors: [Devs.dzshn],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
|
// Clicking on search results to jump
|
||||||
find: '.id,"Search Results"',
|
find: '.id,"Search Results"',
|
||||||
replacement: {
|
replacement: [
|
||||||
|
{
|
||||||
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
|
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
|
||||||
replace: "if(false)$1"
|
replace: "if(false)$1"
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNIGNORE_TO_JUMP_TITLE})/,
|
||||||
|
replace: "if(false)$1"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Jump buttton in top right corner of messages
|
||||||
find: "renderJumpButton()",
|
find: "renderJumpButton()",
|
||||||
replacement: {
|
replacement: [
|
||||||
|
{
|
||||||
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
|
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
|
||||||
replace: "if(false)$1"
|
replace: "if(false)$1"
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "flash:!0,returnMessageId",
|
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}#{intl::UNIGNORE_TO_JUMP_TITLE})/,
|
||||||
replacement: {
|
replace: "if(false)$1"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Clicking on replied messages to jump
|
||||||
|
find: '("interactionUsernameProfile',
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
|
match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNBLOCK_TO_JUMP_TITLE})/,
|
||||||
replace: "false?$1"
|
replace: "false?$1"
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
match: /.\?(.{1,10}\.show\({.{1,50}#{intl::UNIGNORE_TO_JUMP_TITLE})/,
|
||||||
|
replace: "false?$1"
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,9 +25,9 @@ export default definePlugin({
|
||||||
settings,
|
settings,
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "_ensureAudio(){",
|
find: "ensureAudio(){",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?=Math\.min\(\i\.\i\.getOutputVolume\(\)\/100)/,
|
match: /(?=Math\.min\(\i\.\i\.getOutputVolume\(\)\/100)/g,
|
||||||
replace: "$self.settings.store.notificationVolume/100*"
|
replace: "$self.settings.store.notificationVolume/100*"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -57,7 +57,7 @@ const UrlReplacementRules: Record<string, URLReplacementRule> = {
|
||||||
description: "Open Tidal links in the Tidal app",
|
description: "Open Tidal links in the Tidal app",
|
||||||
},
|
},
|
||||||
itunes: {
|
itunes: {
|
||||||
match: /^https:\/\/music\.apple\.com\/([a-z]{2}\/)?(album|artist|playlist|song|curator)\/([^/?#]+)\/?([^/?#]+)?(?:\?.*)?(?:#.*)?$/,
|
match: /^https:\/\/(?:geo\.)?music\.apple\.com\/([a-z]{2}\/)?(album|artist|playlist|song|curator)\/([^/?#]+)\/?([^/?#]+)?(?:\?.*)?(?:#.*)?$/,
|
||||||
replace: (_, lang, type, name, id) => id ? `itunes://music.apple.com/us/${type}/${name}/${id}` : `itunes://music.apple.com/us/${type}/${name}`,
|
replace: (_, lang, type, name, id) => id ? `itunes://music.apple.com/us/${type}/${name}/${id}` : `itunes://music.apple.com/us/${type}/${name}`,
|
||||||
description: "Open Apple Music links in the iTunes app"
|
description: "Open Apple Music links in the iTunes app"
|
||||||
},
|
},
|
||||||
|
@ -100,8 +100,9 @@ export default definePlugin({
|
||||||
replace: "true"
|
replace: "true"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /!\(0,\i\.isDesktop\)\(\)/,
|
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||||
replace: "false"
|
match: /(!)?\(0,\i\.isDesktop\)\(\)/,
|
||||||
|
replace: (_, not) => not ? "false" : "true"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
|
import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
|
||||||
import { FluxDispatcher } from "@webpack/common";
|
import { FluxDispatcher } from "@webpack/common";
|
||||||
|
@ -41,7 +41,6 @@ const settings = definePluginSettings({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
migratePluginSettings("PartyMode", "Party mode 🎉");
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "PartyMode",
|
name: "PartyMode",
|
||||||
description: "Allows you to use party mode cause the party never ends ✨",
|
description: "Allows you to use party mode cause the party never ends ✨",
|
||||||
|
|
|
@ -46,8 +46,9 @@ export default definePlugin({
|
||||||
find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}",
|
find: "#{intl::ONBOARDING_CHANNEL_THRESHOLD_WARNING}",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /{(\i:function\(\){return \i},?){2}}/,
|
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||||
replace: m => m.replaceAll(canonicalizeMatch(/return \i/g), "return ()=>Promise.resolve(true)")
|
match: /{(?:\i:(?:function\(\){return |\(\)=>)\i}?,?){2}}/,
|
||||||
|
replace: m => m.replaceAll(canonicalizeMatch(/(function\(\){return |\(\)=>)\i/g), "$1()=>Promise.resolve(true)")
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
predicate: () => settings.store.onboarding
|
predicate: () => settings.store.onboarding
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue