mirror of
https://github.com/Vendicated/Vencord
synced 2024-09-13 04:29:24 -04:00
Merge branch 'dev' into channeltabs
This commit is contained in:
commit
2994f07269
46 changed files with 1155 additions and 689 deletions
|
@ -1,6 +1,12 @@
|
||||||
{
|
{
|
||||||
"extends": "stylelint-config-standard",
|
"extends": "stylelint-config-standard",
|
||||||
"rules": {
|
"rules": {
|
||||||
"indentation": 4
|
"indentation": 4,
|
||||||
|
"selector-class-pattern": [
|
||||||
|
"^[a-z][a-zA-Z0-9]*(-[a-z0-9][a-zA-Z0-9]*)*$",
|
||||||
|
{
|
||||||
|
"message": "Expected class selector to be kebab-case with camelCase segments"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"minimum_chrome_version": "91",
|
"minimum_chrome_version": "111",
|
||||||
|
|
||||||
"name": "Vencord Web",
|
"name": "Vencord Web",
|
||||||
"description": "The cutest Discord mod now in your browser",
|
"description": "The cutest Discord mod now in your browser",
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "vencord-firefox@vendicated.dev",
|
"id": "vencord-firefox@vendicated.dev",
|
||||||
"strict_min_version": "91.0"
|
"strict_min_version": "128.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.9.2",
|
"version": "1.9.4",
|
||||||
"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": {
|
||||||
|
|
|
@ -289,6 +289,8 @@ page.on("console", async e => {
|
||||||
|
|
||||||
page.on("error", e => console.error("[Error]", e.message));
|
page.on("error", e => console.error("[Error]", e.message));
|
||||||
page.on("pageerror", e => {
|
page.on("pageerror", e => {
|
||||||
|
if (e.message.includes("Sentry successfully disabled")) return;
|
||||||
|
|
||||||
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) {
|
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) {
|
||||||
console.error("[Page Error]", e.message);
|
console.error("[Page Error]", e.message);
|
||||||
report.otherErrors.push(e.message);
|
report.otherErrors.push(e.message);
|
||||||
|
|
|
@ -19,6 +19,8 @@
|
||||||
import * as DataStore from "@api/DataStore";
|
import * as DataStore from "@api/DataStore";
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
|
import { Flex } from "@components/Flex";
|
||||||
|
import { openNotificationSettingsModal } from "@components/VencordSettings/NotificationSettings";
|
||||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
||||||
|
@ -170,24 +172,31 @@ function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Button
|
<Flex>
|
||||||
disabled={log.length === 0}
|
<Button onClick={openNotificationSettingsModal}>
|
||||||
onClick={() => {
|
Notification Settings
|
||||||
Alerts.show({
|
</Button>
|
||||||
title: "Are you sure?",
|
|
||||||
body: `This will permanently remove ${log.length} notification${log.length === 1 ? "" : "s"}. This action cannot be undone.`,
|
<Button
|
||||||
async onConfirm() {
|
disabled={log.length === 0}
|
||||||
await DataStore.set(KEY, []);
|
color={Button.Colors.RED}
|
||||||
signals.forEach(x => x());
|
onClick={() => {
|
||||||
},
|
Alerts.show({
|
||||||
confirmText: "Do it!",
|
title: "Are you sure?",
|
||||||
confirmColor: "vc-notification-log-danger-btn",
|
body: `This will permanently remove ${log.length} notification${log.length === 1 ? "" : "s"}. This action cannot be undone.`,
|
||||||
cancelText: "Nevermind"
|
async onConfirm() {
|
||||||
});
|
await DataStore.set(KEY, []);
|
||||||
}}
|
signals.forEach(x => x());
|
||||||
>
|
},
|
||||||
Clear Notification Log
|
confirmText: "Do it!",
|
||||||
</Button>
|
confirmColor: "vc-notification-log-danger-btn",
|
||||||
|
cancelText: "Nevermind"
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Clear Notification Log
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalRoot>
|
</ModalRoot>
|
||||||
);
|
);
|
||||||
|
|
28
src/components/Grid.tsx
Normal file
28
src/components/Grid.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { CSSProperties } from "react";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
columns: number;
|
||||||
|
gap?: string;
|
||||||
|
inline?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Grid(props: Props & JSX.IntrinsicElements["div"]) {
|
||||||
|
const style: CSSProperties = {
|
||||||
|
display: props.inline ? "inline-grid" : "grid",
|
||||||
|
gridTemplateColumns: `repeat(${props.columns}, 1fr)`,
|
||||||
|
gap: props.gap,
|
||||||
|
...props.style
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div {...props} style={style}>
|
||||||
|
{props.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
|
@ -18,19 +18,17 @@
|
||||||
|
|
||||||
import "./iconStyles.css";
|
import "./iconStyles.css";
|
||||||
|
|
||||||
|
import { getTheme, Theme } from "@utils/discord";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { i18n } from "@webpack/common";
|
import { i18n } from "@webpack/common";
|
||||||
import type { PropsWithChildren, SVGProps } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
|
|
||||||
interface BaseIconProps extends IconProps {
|
interface BaseIconProps extends IconProps {
|
||||||
viewBox: string;
|
viewBox: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IconProps extends SVGProps<SVGSVGElement> {
|
type IconProps = JSX.IntrinsicElements["svg"];
|
||||||
className?: string;
|
type ImageProps = JSX.IntrinsicElements["img"];
|
||||||
height?: string | number;
|
|
||||||
width?: string | number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
|
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
|
||||||
return (
|
return (
|
||||||
|
@ -329,3 +327,103 @@ export function NotesIcon(props: IconProps) {
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function FolderIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "vc-folder-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M2 5a3 3 0 0 1 3-3h3.93a2 2 0 0 1 1.66.9L12 5h7a3 3 0 0 1 3 3v11a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LogIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "vc-log-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M3.11 8H6v10.82c0 .86.37 1.68 1 2.27.46.43 1.02.71 1.63.84A1 1 0 0 0 9 22h10a4 4 0 0 0 4-4v-1a2 2 0 0 0-2-2h-1V5a3 3 0 0 0-3-3H4.67c-.87 0-1.7.32-2.34.9-.63.6-1 1.42-1 2.28 0 .71.3 1.35.52 1.75a5.35 5.35 0 0 0 .48.7l.01.01h.01L3.11 7l-.76.65a1 1 0 0 0 .76.35Zm1.56-4c-.38 0-.72.14-.97.37-.24.23-.37.52-.37.81a1.69 1.69 0 0 0 .3.82H6v-.83c0-.29-.13-.58-.37-.8C5.4 4.14 5.04 4 4.67 4Zm5 13a3.58 3.58 0 0 1 0 3H19a2 2 0 0 0 2-2v-1H9.66ZM3.86 6.35ZM11 8a1 1 0 1 0 0 2h5a1 1 0 1 0 0-2h-5Zm-1 5a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2h-5a1 1 0 0 1-1-1Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function RestartIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "vc-restart-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 0 1 14.93-4H15a1 1 0 1 0 0 2h6a1 1 0 0 0 1-1V3a1 1 0 1 0-2 0v3a9.98 9.98 0 0 0-18 6 10 10 0 0 0 16.29 7.78 1 1 0 0 0-1.26-1.56A8 8 0 0 1 4 12Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PaintbrushIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "vc-paintbrush-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M15.35 7.24C15.9 6.67 16 5.8 16 5a3 3 0 1 1 3 3c-.8 0-1.67.09-2.24.65a1.5 1.5 0 0 0 0 2.11l1.12 1.12a3 3 0 0 1 0 4.24l-5 5a3 3 0 0 1-4.25 0l-5.76-5.75a3 3 0 0 1 0-4.24l4.04-4.04.97-.97a3 3 0 0 1 4.24 0l1.12 1.12c.58.58 1.52.58 2.1 0ZM6.9 9.9 4.3 12.54a1 1 0 0 0 0 1.42l2.17 2.17.83-.84a1 1 0 0 1 1.42 1.42l-.84.83.59.59 1.83-1.84a1 1 0 0 1 1.42 1.42l-1.84 1.83.17.17a1 1 0 0 0 1.42 0l2.63-2.62L6.9 9.9Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PencilIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "vc-pencil-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="m13.96 5.46 4.58 4.58a1 1 0 0 0 1.42 0l1.38-1.38a2 2 0 0 0 0-2.82l-3.18-3.18a2 2 0 0 0-2.82 0l-1.38 1.38a1 1 0 0 0 0 1.42ZM2.11 20.16l.73-4.22a3 3 0 0 1 .83-1.61l7.87-7.87a1 1 0 0 1 1.42 0l4.58 4.58a1 1 0 0 1 0 1.42l-7.87 7.87a3 3 0 0 1-1.6.83l-4.23.73a1.5 1.5 0 0 1-1.73-1.73Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
||||||
|
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
||||||
|
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
||||||
|
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
||||||
|
|
||||||
|
export function GithubIcon(props: ImageProps) {
|
||||||
|
const src = getTheme() === Theme.Light
|
||||||
|
? GithubIconLight
|
||||||
|
: GithubIconDark;
|
||||||
|
|
||||||
|
return <img {...props} src={src} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function WebsiteIcon(props: ImageProps) {
|
||||||
|
const src = getTheme() === Theme.Light
|
||||||
|
? WebsiteIconLight
|
||||||
|
: WebsiteIconDark;
|
||||||
|
|
||||||
|
return <img {...props} src={src} />;
|
||||||
|
}
|
||||||
|
|
|
@ -6,22 +6,16 @@
|
||||||
|
|
||||||
import "./LinkIconButton.css";
|
import "./LinkIconButton.css";
|
||||||
|
|
||||||
import { getTheme, Theme } from "@utils/discord";
|
|
||||||
import { MaskedLink, Tooltip } from "@webpack/common";
|
import { MaskedLink, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
import { GithubIcon, WebsiteIcon } from "..";
|
||||||
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
|
||||||
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
|
||||||
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
|
||||||
|
|
||||||
export function GithubIcon() {
|
export function GithubLinkIcon() {
|
||||||
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
|
return <GithubIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
|
||||||
return <img src={src} aria-hidden className={"vc-settings-modal-link-icon"} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WebsiteIcon() {
|
export function WebsiteLinkIcon() {
|
||||||
const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
|
return <WebsiteIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
|
||||||
return <img src={src} aria-hidden className={"vc-settings-modal-link-icon"} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -41,5 +35,5 @@ function LinkIcon({ text, href, Icon }: Props & { Icon: React.ComponentType; })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WebsiteButton = (props: Props) => <LinkIcon {...props} Icon={WebsiteIcon} />;
|
export const WebsiteButton = (props: Props) => <LinkIcon {...props} Icon={WebsiteLinkIcon} />;
|
||||||
export const GithubButton = (props: Props) => <LinkIcon {...props} Icon={GithubIcon} />;
|
export const GithubButton = (props: Props) => <LinkIcon {...props} Icon={GithubLinkIcon} />;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { gitRemote } from "@shared/vencordUserAgent";
|
||||||
import { proxyLazy } from "@utils/lazy";
|
import { proxyLazy } from "@utils/lazy";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes, isObjectEmpty } from "@utils/misc";
|
import { classes, isObjectEmpty } from "@utils/misc";
|
||||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
|
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { OptionType, Plugin } from "@utils/types";
|
import { OptionType, Plugin } from "@utils/types";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
||||||
|
@ -310,3 +310,13 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
</ModalRoot>
|
</ModalRoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function openPluginModal(plugin: Plugin, onRestartNeeded?: (pluginName: string) => void) {
|
||||||
|
openModal(modalProps => (
|
||||||
|
<PluginModal
|
||||||
|
{...modalProps}
|
||||||
|
plugin={plugin}
|
||||||
|
onRestartNeeded={() => onRestartNeeded?.(plugin.name)}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { showNotice } from "@api/Notices";
|
||||||
import { Settings, useSettings } from "@api/Settings";
|
import { Settings, useSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { CogWheel, InfoIcon } from "@components/Icons";
|
import { CogWheel, InfoIcon } from "@components/Icons";
|
||||||
import PluginModal from "@components/PluginSettings/PluginModal";
|
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||||
import { AddonCard } from "@components/VencordSettings/AddonCard";
|
import { AddonCard } from "@components/VencordSettings/AddonCard";
|
||||||
import { SettingsTab } from "@components/VencordSettings/shared";
|
import { SettingsTab } from "@components/VencordSettings/shared";
|
||||||
import { ChangeList } from "@utils/ChangeList";
|
import { ChangeList } from "@utils/ChangeList";
|
||||||
|
@ -31,7 +31,6 @@ import { proxyLazy } from "@utils/lazy";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes, isObjectEmpty } from "@utils/misc";
|
import { classes, isObjectEmpty } from "@utils/misc";
|
||||||
import { openModalLazy } from "@utils/modal";
|
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Plugin } from "@utils/types";
|
import { Plugin } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
@ -45,7 +44,7 @@ const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() =>
|
||||||
const cl = classNameFactory("vc-plugins-");
|
const cl = classNameFactory("vc-plugins-");
|
||||||
const logger = new Logger("PluginSettings", "#a6d189");
|
const logger = new Logger("PluginSettings", "#a6d189");
|
||||||
|
|
||||||
const InputStyles = findByPropsLazy("inputDefault", "inputWrapper");
|
const InputStyles = findByPropsLazy("inputWrapper", "inputDefault", "error");
|
||||||
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
|
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
|
||||||
|
|
||||||
|
|
||||||
|
@ -96,14 +95,6 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
|
||||||
|
|
||||||
const isEnabled = () => settings.enabled ?? false;
|
const isEnabled = () => settings.enabled ?? false;
|
||||||
|
|
||||||
function openModal() {
|
|
||||||
openModalLazy(async () => {
|
|
||||||
return modalProps => {
|
|
||||||
return <PluginModal {...modalProps} plugin={plugin} onRestartNeeded={() => onRestartNeeded(plugin.name)} />;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleEnabled() {
|
function toggleEnabled() {
|
||||||
const wasEnabled = isEnabled();
|
const wasEnabled = isEnabled();
|
||||||
|
|
||||||
|
@ -160,7 +151,11 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
infoButton={
|
infoButton={
|
||||||
<button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}>
|
<button
|
||||||
|
role="switch"
|
||||||
|
onClick={() => openPluginModal(plugin, onRestartNeeded)}
|
||||||
|
className={classes(ButtonClasses.button, cl("info-button"))}
|
||||||
|
>
|
||||||
{plugin.options && !isObjectEmpty(plugin.options)
|
{plugin.options && !isObjectEmpty(plugin.options)
|
||||||
? <CogWheel />
|
? <CogWheel />
|
||||||
: <InfoIcon />}
|
: <InfoIcon />}
|
||||||
|
@ -339,8 +334,8 @@ export default function PluginSettings() {
|
||||||
Filters
|
Filters
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
|
|
||||||
<div className={cl("filter-controls")}>
|
<div className={classes(Margins.bottom20, cl("filter-controls"))}>
|
||||||
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} className={Margins.bottom20} />
|
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} />
|
||||||
<div className={InputStyles.inputWrapper}>
|
<div className={InputStyles.inputWrapper}>
|
||||||
<Select
|
<Select
|
||||||
options={[
|
options={[
|
||||||
|
@ -353,6 +348,7 @@ export default function PluginSettings() {
|
||||||
select={onStatusChange}
|
select={onStatusChange}
|
||||||
isSelected={v => v === searchValue.status}
|
isSelected={v => v === searchValue.status}
|
||||||
closeOnSelect={true}
|
closeOnSelect={true}
|
||||||
|
className={InputStyles.inputDefault}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
import { showNotification } from "@api/Notifications";
|
import { showNotification } from "@api/Notifications";
|
||||||
import { Settings, useSettings } from "@api/Settings";
|
import { Settings, useSettings } from "@api/Settings";
|
||||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
import { CheckedTextInput } from "@components/CheckedTextInput";
|
||||||
|
import { Grid } from "@components/Grid";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
|
import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
|
@ -85,7 +86,9 @@ function SettingsSyncSection() {
|
||||||
size={Button.Sizes.SMALL}
|
size={Button.Sizes.SMALL}
|
||||||
disabled={!sectionEnabled}
|
disabled={!sectionEnabled}
|
||||||
onClick={() => putCloudSettings(true)}
|
onClick={() => putCloudSettings(true)}
|
||||||
>Sync to Cloud</Button>
|
>
|
||||||
|
Sync to Cloud
|
||||||
|
</Button>
|
||||||
<Tooltip text="This will overwrite your local settings with the ones on the cloud. Use wisely!">
|
<Tooltip text="This will overwrite your local settings with the ones on the cloud. Use wisely!">
|
||||||
{({ onMouseLeave, onMouseEnter }) => (
|
{({ onMouseLeave, onMouseEnter }) => (
|
||||||
<Button
|
<Button
|
||||||
|
@ -95,7 +98,9 @@ function SettingsSyncSection() {
|
||||||
color={Button.Colors.RED}
|
color={Button.Colors.RED}
|
||||||
disabled={!sectionEnabled}
|
disabled={!sectionEnabled}
|
||||||
onClick={() => getCloudSettings(true, true)}
|
onClick={() => getCloudSettings(true, true)}
|
||||||
>Sync from Cloud</Button>
|
>
|
||||||
|
Sync from Cloud
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Button
|
<Button
|
||||||
|
@ -103,7 +108,9 @@ function SettingsSyncSection() {
|
||||||
color={Button.Colors.RED}
|
color={Button.Colors.RED}
|
||||||
disabled={!sectionEnabled}
|
disabled={!sectionEnabled}
|
||||||
onClick={() => deleteCloudSettings()}
|
onClick={() => deleteCloudSettings()}
|
||||||
>Delete Cloud Settings</Button>
|
>
|
||||||
|
Delete Cloud Settings
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Forms.FormSection>
|
</Forms.FormSection>
|
||||||
);
|
);
|
||||||
|
@ -124,7 +131,12 @@ function CloudTab() {
|
||||||
<Switch
|
<Switch
|
||||||
key="backend"
|
key="backend"
|
||||||
value={settings.cloud.authenticated}
|
value={settings.cloud.authenticated}
|
||||||
onChange={v => { v && authorizeCloud(); if (!v) settings.cloud.authenticated = v; }}
|
onChange={v => {
|
||||||
|
if (v)
|
||||||
|
authorizeCloud();
|
||||||
|
else
|
||||||
|
settings.cloud.authenticated = v;
|
||||||
|
}}
|
||||||
note="This will request authorization if you have not yet set up cloud integrations."
|
note="This will request authorization if you have not yet set up cloud integrations."
|
||||||
>
|
>
|
||||||
Enable Cloud Integrations
|
Enable Cloud Integrations
|
||||||
|
@ -136,23 +148,43 @@ function CloudTab() {
|
||||||
<CheckedTextInput
|
<CheckedTextInput
|
||||||
key="backendUrl"
|
key="backendUrl"
|
||||||
value={settings.cloud.url}
|
value={settings.cloud.url}
|
||||||
onChange={v => { settings.cloud.url = v; settings.cloud.authenticated = false; deauthorizeCloud(); }}
|
onChange={async v => {
|
||||||
|
settings.cloud.url = v;
|
||||||
|
settings.cloud.authenticated = false;
|
||||||
|
deauthorizeCloud();
|
||||||
|
}}
|
||||||
validate={validateUrl}
|
validate={validateUrl}
|
||||||
/>
|
/>
|
||||||
<Button
|
|
||||||
className={Margins.top8}
|
<Grid columns={2} gap="1em" className={Margins.top8}>
|
||||||
size={Button.Sizes.MEDIUM}
|
<Button
|
||||||
color={Button.Colors.RED}
|
size={Button.Sizes.MEDIUM}
|
||||||
disabled={!settings.cloud.authenticated}
|
disabled={!settings.cloud.authenticated}
|
||||||
onClick={() => Alerts.show({
|
onClick={async () => {
|
||||||
title: "Are you sure?",
|
await deauthorizeCloud();
|
||||||
body: "Once your data is erased, we cannot recover it. There's no going back!",
|
settings.cloud.authenticated = false;
|
||||||
onConfirm: eraseAllData,
|
await authorizeCloud();
|
||||||
confirmText: "Erase it!",
|
}}
|
||||||
confirmColor: "vc-cloud-erase-data-danger-btn",
|
>
|
||||||
cancelText: "Nevermind"
|
Reauthorise
|
||||||
})}
|
</Button>
|
||||||
>Erase All Data</Button>
|
<Button
|
||||||
|
size={Button.Sizes.MEDIUM}
|
||||||
|
color={Button.Colors.RED}
|
||||||
|
disabled={!settings.cloud.authenticated}
|
||||||
|
onClick={() => Alerts.show({
|
||||||
|
title: "Are you sure?",
|
||||||
|
body: "Once your data is erased, we cannot recover it. There's no going back!",
|
||||||
|
onConfirm: eraseAllData,
|
||||||
|
confirmText: "Erase it!",
|
||||||
|
confirmColor: "vc-cloud-erase-data-danger-btn",
|
||||||
|
cancelText: "Nevermind"
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
Erase All Data
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Forms.FormDivider className={Margins.top16} />
|
<Forms.FormDivider className={Margins.top16} />
|
||||||
</Forms.FormSection >
|
</Forms.FormSection >
|
||||||
<SettingsSyncSection />
|
<SettingsSyncSection />
|
||||||
|
|
106
src/components/VencordSettings/NotificationSettings.tsx
Normal file
106
src/components/VencordSettings/NotificationSettings.tsx
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useSettings } from "@api/Settings";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
|
import { identity } from "@utils/misc";
|
||||||
|
import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
|
import { Forms, Select, Slider, Text } from "@webpack/common";
|
||||||
|
|
||||||
|
import { ErrorCard } from "..";
|
||||||
|
|
||||||
|
export function NotificationSettings() {
|
||||||
|
const settings = useSettings().notifications;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "1em 0" }}>
|
||||||
|
<Forms.FormTitle tag="h5">Notification Style</Forms.FormTitle>
|
||||||
|
{settings.useNative !== "never" && Notification?.permission === "denied" && (
|
||||||
|
<ErrorCard style={{ padding: "1em" }} className={Margins.bottom8}>
|
||||||
|
<Forms.FormTitle tag="h5">Desktop Notification Permission denied</Forms.FormTitle>
|
||||||
|
<Forms.FormText>You have denied Notification Permissions. Thus, Desktop notifications will not work!</Forms.FormText>
|
||||||
|
</ErrorCard>
|
||||||
|
)}
|
||||||
|
<Forms.FormText className={Margins.bottom8}>
|
||||||
|
Some plugins may show you notifications. These come in two styles:
|
||||||
|
<ul>
|
||||||
|
<li><strong>Vencord Notifications</strong>: These are in-app notifications</li>
|
||||||
|
<li><strong>Desktop Notifications</strong>: Native Desktop notifications (like when you get a ping)</li>
|
||||||
|
</ul>
|
||||||
|
</Forms.FormText>
|
||||||
|
<Select
|
||||||
|
placeholder="Notification Style"
|
||||||
|
options={[
|
||||||
|
{ label: "Only use Desktop notifications when Discord is not focused", value: "not-focused", default: true },
|
||||||
|
{ label: "Always use Desktop notifications", value: "always" },
|
||||||
|
{ label: "Always use Vencord notifications", value: "never" },
|
||||||
|
] satisfies Array<{ value: typeof settings["useNative"]; } & Record<string, any>>}
|
||||||
|
closeOnSelect={true}
|
||||||
|
select={v => settings.useNative = v}
|
||||||
|
isSelected={v => v === settings.useNative}
|
||||||
|
serialize={identity}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Position</Forms.FormTitle>
|
||||||
|
<Select
|
||||||
|
isDisabled={settings.useNative === "always"}
|
||||||
|
placeholder="Notification Position"
|
||||||
|
options={[
|
||||||
|
{ label: "Bottom Right", value: "bottom-right", default: true },
|
||||||
|
{ label: "Top Right", value: "top-right" },
|
||||||
|
] satisfies Array<{ value: typeof settings["position"]; } & Record<string, any>>}
|
||||||
|
select={v => settings.position = v}
|
||||||
|
isSelected={v => v === settings.position}
|
||||||
|
serialize={identity}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Timeout</Forms.FormTitle>
|
||||||
|
<Forms.FormText className={Margins.bottom16}>Set to 0s to never automatically time out</Forms.FormText>
|
||||||
|
<Slider
|
||||||
|
disabled={settings.useNative === "always"}
|
||||||
|
markers={[0, 1000, 2500, 5000, 10_000, 20_000]}
|
||||||
|
minValue={0}
|
||||||
|
maxValue={20_000}
|
||||||
|
initialValue={settings.timeout}
|
||||||
|
onValueChange={v => settings.timeout = v}
|
||||||
|
onValueRender={v => (v / 1000).toFixed(2) + "s"}
|
||||||
|
onMarkerRender={v => (v / 1000) + "s"}
|
||||||
|
stickToMarkers={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Log Limit</Forms.FormTitle>
|
||||||
|
<Forms.FormText className={Margins.bottom16}>
|
||||||
|
The amount of notifications to save in the log until old ones are removed.
|
||||||
|
Set to <code>0</code> to disable Notification log and <code>∞</code> to never automatically remove old Notifications
|
||||||
|
</Forms.FormText>
|
||||||
|
<Slider
|
||||||
|
markers={[0, 25, 50, 75, 100, 200]}
|
||||||
|
minValue={0}
|
||||||
|
maxValue={200}
|
||||||
|
stickToMarkers={true}
|
||||||
|
initialValue={settings.logLimit}
|
||||||
|
onValueChange={v => settings.logLimit = v}
|
||||||
|
onValueRender={v => v === 200 ? "∞" : v}
|
||||||
|
onMarkerRender={v => v === 200 ? "∞" : v}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function openNotificationSettingsModal() {
|
||||||
|
openModal(props => (
|
||||||
|
<ModalRoot {...props} size={ModalSize.MEDIUM}>
|
||||||
|
<ModalHeader>
|
||||||
|
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Notification Settings</Text>
|
||||||
|
<ModalCloseButton onClick={props.onClose} />
|
||||||
|
</ModalHeader>
|
||||||
|
|
||||||
|
<ModalContent>
|
||||||
|
<NotificationSettings />
|
||||||
|
</ModalContent>
|
||||||
|
</ModalRoot>
|
||||||
|
));
|
||||||
|
}
|
|
@ -16,24 +16,26 @@
|
||||||
* 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 { useSettings } from "@api/Settings";
|
import { Settings, useSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { DeleteIcon } from "@components/Icons";
|
import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import PluginModal from "@components/PluginSettings/PluginModal";
|
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||||
import type { UserThemeHeader } from "@main/themes";
|
import type { UserThemeHeader } from "@main/themes";
|
||||||
import { openInviteModal } from "@utils/discord";
|
import { openInviteModal } from "@utils/discord";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { openModal } from "@utils/modal";
|
|
||||||
import { showItemInFolder } from "@utils/native";
|
import { showItemInFolder } from "@utils/native";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { findByPropsLazy, findLazy } from "@webpack";
|
import { findByPropsLazy, findLazy } from "@webpack";
|
||||||
import { Button, Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
import { Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
||||||
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
||||||
|
|
||||||
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
import { AddonCard } from "./AddonCard";
|
import { AddonCard } from "./AddonCard";
|
||||||
|
import { QuickAction, QuickActionCard } from "./quickActions";
|
||||||
import { SettingsTab, wrapTab } from "./shared";
|
import { SettingsTab, wrapTab } from "./shared";
|
||||||
|
|
||||||
type FileInput = ComponentType<{
|
type FileInput = ComponentType<{
|
||||||
|
@ -213,60 +215,52 @@ function ThemesTab() {
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Forms.FormSection title="Local Themes">
|
<Forms.FormSection title="Local Themes">
|
||||||
<Card className="vc-settings-quick-actions-card">
|
<QuickActionCard>
|
||||||
<>
|
<>
|
||||||
{IS_WEB ?
|
{IS_WEB ?
|
||||||
(
|
(
|
||||||
<Button
|
<QuickAction
|
||||||
size={Button.Sizes.SMALL}
|
text={
|
||||||
disabled={themeDirPending}
|
<span style={{ position: "relative" }}>
|
||||||
>
|
Upload Theme
|
||||||
Upload Theme
|
<FileInput
|
||||||
<FileInput
|
ref={fileInputRef}
|
||||||
ref={fileInputRef}
|
onChange={onFileUpload}
|
||||||
onChange={onFileUpload}
|
multiple={true}
|
||||||
multiple={true}
|
filters={[{ extensions: ["css"] }]}
|
||||||
filters={[{ extensions: ["css"] }]}
|
/>
|
||||||
/>
|
</span>
|
||||||
</Button>
|
}
|
||||||
|
Icon={PlusIcon}
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<QuickAction
|
||||||
onClick={() => showItemInFolder(themeDir!)}
|
text="Open Themes Folder"
|
||||||
size={Button.Sizes.SMALL}
|
action={() => showItemInFolder(themeDir!)}
|
||||||
disabled={themeDirPending}
|
disabled={themeDirPending}
|
||||||
>
|
Icon={FolderIcon}
|
||||||
Open Themes Folder
|
/>
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
<Button
|
<QuickAction
|
||||||
onClick={refreshLocalThemes}
|
text="Load missing Themes"
|
||||||
size={Button.Sizes.SMALL}
|
action={refreshLocalThemes}
|
||||||
>
|
Icon={RestartIcon}
|
||||||
Load missing Themes
|
/>
|
||||||
</Button>
|
<QuickAction
|
||||||
<Button
|
text="Edit QuickCSS"
|
||||||
onClick={() => VencordNative.quickCss.openEditor()}
|
action={() => VencordNative.quickCss.openEditor()}
|
||||||
size={Button.Sizes.SMALL}
|
Icon={PaintbrushIcon}
|
||||||
>
|
/>
|
||||||
Edit QuickCSS
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
{Vencord.Settings.plugins.ClientTheme.enabled && (
|
{Settings.plugins.ClientTheme.enabled && (
|
||||||
<Button
|
<QuickAction
|
||||||
onClick={() => openModal(modalProps => (
|
text="Edit ClientTheme"
|
||||||
<PluginModal
|
action={() => openPluginModal(Plugins.ClientTheme)}
|
||||||
{...modalProps}
|
Icon={PencilIcon}
|
||||||
plugin={Vencord.Plugins.plugins.ClientTheme}
|
/>
|
||||||
onRestartNeeded={() => { }}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
size={Button.Sizes.SMALL}
|
|
||||||
>
|
|
||||||
Edit ClientTheme
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
</Card>
|
</QuickActionCard>
|
||||||
|
|
||||||
<div className={cl("grid")}>
|
<div className={cl("grid")}>
|
||||||
{userThemes?.map(theme => (
|
{userThemes?.map(theme => (
|
||||||
|
|
|
@ -17,16 +17,20 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
||||||
import { Settings, useSettings } from "@api/Settings";
|
import { useSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import DonateButton from "@components/DonateButton";
|
import DonateButton from "@components/DonateButton";
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||||
|
import { gitRemote } from "@shared/vencordUserAgent";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { identity } from "@utils/misc";
|
import { identity } from "@utils/misc";
|
||||||
import { relaunch, showItemInFolder } from "@utils/native";
|
import { relaunch, showItemInFolder } from "@utils/native";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Button, Card, Forms, React, Select, Slider, Switch } from "@webpack/common";
|
import { Button, Card, Forms, React, Select, Switch } from "@webpack/common";
|
||||||
|
|
||||||
|
import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "..";
|
||||||
|
import { openNotificationSettingsModal } from "./NotificationSettings";
|
||||||
|
import { QuickAction, QuickActionCard } from "./quickActions";
|
||||||
import { SettingsTab, wrapTab } from "./shared";
|
import { SettingsTab, wrapTab } from "./shared";
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-");
|
const cl = classNameFactory("vc-settings-");
|
||||||
|
@ -38,6 +42,7 @@ type KeysOfType<Object, Type> = {
|
||||||
[K in keyof Object]: Object[K] extends Type ? K : never;
|
[K in keyof Object]: Object[K] extends Type ? K : never;
|
||||||
}[keyof Object];
|
}[keyof Object];
|
||||||
|
|
||||||
|
|
||||||
function VencordSettings() {
|
function VencordSettings() {
|
||||||
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
|
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
|
||||||
fallbackValue: "Loading..."
|
fallbackValue: "Loading..."
|
||||||
|
@ -78,7 +83,7 @@ function VencordSettings() {
|
||||||
!IS_WEB && {
|
!IS_WEB && {
|
||||||
key: "transparent",
|
key: "transparent",
|
||||||
title: "Enable window transparency.",
|
title: "Enable window transparency.",
|
||||||
note: "You need a theme that supports transparency or this will do nothing. Will stop the window from being resizable. Requires a full restart"
|
note: "You need a theme that supports transparency or this will do nothing. WILL STOP THE WINDOW FROM BEING RESIZABLE!! Requires a full restart"
|
||||||
},
|
},
|
||||||
!IS_WEB && isWindows && {
|
!IS_WEB && isWindows && {
|
||||||
key: "winCtrlQ",
|
key: "winCtrlQ",
|
||||||
|
@ -96,45 +101,53 @@ function VencordSettings() {
|
||||||
<SettingsTab title="Vencord Settings">
|
<SettingsTab title="Vencord Settings">
|
||||||
<DonateCard image={donateImage} />
|
<DonateCard image={donateImage} />
|
||||||
<Forms.FormSection title="Quick Actions">
|
<Forms.FormSection title="Quick Actions">
|
||||||
<Card className={cl("quick-actions-card")}>
|
<QuickActionCard>
|
||||||
<React.Fragment>
|
<QuickAction
|
||||||
{!IS_WEB && (
|
Icon={LogIcon}
|
||||||
<Button
|
text="Notification Log"
|
||||||
onClick={relaunch}
|
action={openNotificationLogModal}
|
||||||
size={Button.Sizes.SMALL}>
|
/>
|
||||||
Restart Client
|
<QuickAction
|
||||||
</Button>
|
Icon={PaintbrushIcon}
|
||||||
)}
|
text="Edit QuickCSS"
|
||||||
<Button
|
action={() => VencordNative.quickCss.openEditor()}
|
||||||
onClick={() => VencordNative.quickCss.openEditor()}
|
/>
|
||||||
size={Button.Sizes.SMALL}
|
{!IS_WEB && (
|
||||||
disabled={settingsDir === "Loading..."}>
|
<QuickAction
|
||||||
Open QuickCSS File
|
Icon={RestartIcon}
|
||||||
</Button>
|
text="Relaunch Discord"
|
||||||
{!IS_WEB && (
|
action={relaunch}
|
||||||
<Button
|
/>
|
||||||
onClick={() => showItemInFolder(settingsDir)}
|
)}
|
||||||
size={Button.Sizes.SMALL}
|
{!IS_WEB && (
|
||||||
disabled={settingsDirPending}>
|
<QuickAction
|
||||||
Open Settings Folder
|
Icon={FolderIcon}
|
||||||
</Button>
|
text="Open Settings Folder"
|
||||||
)}
|
action={() => showItemInFolder(settingsDir)}
|
||||||
<Button
|
/>
|
||||||
onClick={() => VencordNative.native.openExternal("https://github.com/Vendicated/Vencord")}
|
)}
|
||||||
size={Button.Sizes.SMALL}
|
<QuickAction
|
||||||
disabled={settingsDirPending}>
|
Icon={GithubIcon}
|
||||||
Open in GitHub
|
text="View Source Code"
|
||||||
</Button>
|
action={() => VencordNative.native.openExternal("https://github.com/" + gitRemote)}
|
||||||
</React.Fragment>
|
/>
|
||||||
</Card>
|
</QuickActionCard>
|
||||||
</Forms.FormSection>
|
</Forms.FormSection>
|
||||||
|
|
||||||
<Forms.FormDivider />
|
<Forms.FormDivider />
|
||||||
|
|
||||||
<Forms.FormSection className={Margins.top16} title="Settings" tag="h5">
|
<Forms.FormSection className={Margins.top16} title="Settings" tag="h5">
|
||||||
<Forms.FormText className={Margins.bottom20}>
|
<Forms.FormText className={Margins.bottom20} style={{ color: "var(--text-muted)" }}>
|
||||||
Hint: You can change the position of this settings section in the settings of the "Settings" plugin!
|
Hint: You can change the position of this settings section in the
|
||||||
|
{" "}<Button
|
||||||
|
look={Button.Looks.BLANK}
|
||||||
|
style={{ color: "var(--text-link)", display: "inline-block" }}
|
||||||
|
onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}
|
||||||
|
>
|
||||||
|
settings of the Settings plugin
|
||||||
|
</Button>!
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
|
|
||||||
{Switches.map(s => s && (
|
{Switches.map(s => s && (
|
||||||
<Switch
|
<Switch
|
||||||
key={s.key}
|
key={s.key}
|
||||||
|
@ -212,94 +225,20 @@ function VencordSettings() {
|
||||||
serialize={identity} />
|
serialize={identity} />
|
||||||
</>}
|
</>}
|
||||||
|
|
||||||
{typeof Notification !== "undefined" && <NotificationSection settings={settings.notifications} />}
|
<Forms.FormSection className={Margins.top16} title="Vencord Notifications" tag="h5">
|
||||||
|
<Flex>
|
||||||
|
<Button onClick={openNotificationSettingsModal}>
|
||||||
|
Notification Settings
|
||||||
|
</Button>
|
||||||
|
<Button onClick={openNotificationLogModal}>
|
||||||
|
View Notification Log
|
||||||
|
</Button>
|
||||||
|
</Flex>
|
||||||
|
</Forms.FormSection>
|
||||||
</SettingsTab>
|
</SettingsTab>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function NotificationSection({ settings }: { settings: typeof Settings["notifications"]; }) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Forms.FormTitle tag="h5">Notification Style</Forms.FormTitle>
|
|
||||||
{settings.useNative !== "never" && Notification?.permission === "denied" && (
|
|
||||||
<ErrorCard style={{ padding: "1em" }} className={Margins.bottom8}>
|
|
||||||
<Forms.FormTitle tag="h5">Desktop Notification Permission denied</Forms.FormTitle>
|
|
||||||
<Forms.FormText>You have denied Notification Permissions. Thus, Desktop notifications will not work!</Forms.FormText>
|
|
||||||
</ErrorCard>
|
|
||||||
)}
|
|
||||||
<Forms.FormText className={Margins.bottom8}>
|
|
||||||
Some plugins may show you notifications. These come in two styles:
|
|
||||||
<ul>
|
|
||||||
<li><strong>Vencord Notifications</strong>: These are in-app notifications</li>
|
|
||||||
<li><strong>Desktop Notifications</strong>: Native Desktop notifications (like when you get a ping)</li>
|
|
||||||
</ul>
|
|
||||||
</Forms.FormText>
|
|
||||||
<Select
|
|
||||||
placeholder="Notification Style"
|
|
||||||
options={[
|
|
||||||
{ label: "Only use Desktop notifications when Discord is not focused", value: "not-focused", default: true },
|
|
||||||
{ label: "Always use Desktop notifications", value: "always" },
|
|
||||||
{ label: "Always use Vencord notifications", value: "never" },
|
|
||||||
] satisfies Array<{ value: typeof settings["useNative"]; } & Record<string, any>>}
|
|
||||||
closeOnSelect={true}
|
|
||||||
select={v => settings.useNative = v}
|
|
||||||
isSelected={v => v === settings.useNative}
|
|
||||||
serialize={identity}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Position</Forms.FormTitle>
|
|
||||||
<Select
|
|
||||||
isDisabled={settings.useNative === "always"}
|
|
||||||
placeholder="Notification Position"
|
|
||||||
options={[
|
|
||||||
{ label: "Bottom Right", value: "bottom-right", default: true },
|
|
||||||
{ label: "Top Right", value: "top-right" },
|
|
||||||
] satisfies Array<{ value: typeof settings["position"]; } & Record<string, any>>}
|
|
||||||
select={v => settings.position = v}
|
|
||||||
isSelected={v => v === settings.position}
|
|
||||||
serialize={identity}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Timeout</Forms.FormTitle>
|
|
||||||
<Forms.FormText className={Margins.bottom16}>Set to 0s to never automatically time out</Forms.FormText>
|
|
||||||
<Slider
|
|
||||||
disabled={settings.useNative === "always"}
|
|
||||||
markers={[0, 1000, 2500, 5000, 10_000, 20_000]}
|
|
||||||
minValue={0}
|
|
||||||
maxValue={20_000}
|
|
||||||
initialValue={settings.timeout}
|
|
||||||
onValueChange={v => settings.timeout = v}
|
|
||||||
onValueRender={v => (v / 1000).toFixed(2) + "s"}
|
|
||||||
onMarkerRender={v => (v / 1000) + "s"}
|
|
||||||
stickToMarkers={false}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Log Limit</Forms.FormTitle>
|
|
||||||
<Forms.FormText className={Margins.bottom16}>
|
|
||||||
The amount of notifications to save in the log until old ones are removed.
|
|
||||||
Set to <code>0</code> to disable Notification log and <code>∞</code> to never automatically remove old Notifications
|
|
||||||
</Forms.FormText>
|
|
||||||
<Slider
|
|
||||||
markers={[0, 25, 50, 75, 100, 200]}
|
|
||||||
minValue={0}
|
|
||||||
maxValue={200}
|
|
||||||
stickToMarkers={true}
|
|
||||||
initialValue={settings.logLimit}
|
|
||||||
onValueChange={v => settings.logLimit = v}
|
|
||||||
onValueRender={v => v === 200 ? "∞" : v}
|
|
||||||
onMarkerRender={v => v === 200 ? "∞" : v}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onClick={openNotificationLogModal}
|
|
||||||
disabled={settings.logLimit === 0}
|
|
||||||
>
|
|
||||||
Open Notification Log
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
interface DonateCardProps {
|
interface DonateCardProps {
|
||||||
image: string;
|
image: string;
|
||||||
}
|
}
|
||||||
|
|
33
src/components/VencordSettings/quickActions.css
Normal file
33
src/components/VencordSettings/quickActions.css
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
.vc-settings-quickActions-card {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(200px, max-content));
|
||||||
|
gap: 0.5em;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.5em 0;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-settings-quickActions-pill {
|
||||||
|
all: unset;
|
||||||
|
background: var(--background-secondary);
|
||||||
|
color: var(--header-secondary);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-settings-quickActions-pill:hover {
|
||||||
|
background: var(--background-secondary-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-settings-quickActions-pill:focus-visible {
|
||||||
|
outline: 2px solid var(--focus-primary);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-settings-quickActions-img {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
39
src/components/VencordSettings/quickActions.tsx
Normal file
39
src/components/VencordSettings/quickActions.tsx
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./quickActions.css";
|
||||||
|
|
||||||
|
import { classNameFactory } from "@api/Styles";
|
||||||
|
import { Card } from "@webpack/common";
|
||||||
|
import type { ComponentType, PropsWithChildren, ReactNode } from "react";
|
||||||
|
|
||||||
|
const cl = classNameFactory("vc-settings-quickActions-");
|
||||||
|
|
||||||
|
export interface QuickActionProps {
|
||||||
|
Icon: ComponentType<{ className?: string; }>;
|
||||||
|
text: ReactNode;
|
||||||
|
action?: () => void;
|
||||||
|
disabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QuickAction(props: QuickActionProps) {
|
||||||
|
const { Icon, action, text, disabled } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button className={cl("pill")} onClick={action} disabled={disabled}>
|
||||||
|
<Icon className={cl("img")} />
|
||||||
|
{text}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function QuickActionCard(props: PropsWithChildren) {
|
||||||
|
return (
|
||||||
|
<Card className={cl("card")}>
|
||||||
|
{props.children}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
|
@ -10,17 +10,6 @@
|
||||||
margin-bottom: -2px;
|
margin-bottom: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-settings-quick-actions-card {
|
|
||||||
padding: 1em;
|
|
||||||
display: flex;
|
|
||||||
gap: 1em;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
flex-grow: 1;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-settings-donate {
|
.vc-settings-donate {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
|
@ -71,9 +72,60 @@ export default definePlugin({
|
||||||
|
|
||||||
startAt: StartAt.Init,
|
startAt: StartAt.Init,
|
||||||
start() {
|
start() {
|
||||||
|
// Sentry is initialized in its own WebpackInstance.
|
||||||
|
// It has everything it needs preloaded, so, it doesn't include any chunk loading functionality.
|
||||||
|
// Because of that, its WebpackInstance doesnt export wreq.m or wreq.c
|
||||||
|
|
||||||
|
// To circuvent this and disable Sentry we are gonna hook when wreq.g of its WebpackInstance is set.
|
||||||
|
// When that happens we are gonna forcefully throw an error and abort everything.
|
||||||
|
Object.defineProperty(Function.prototype, "g", {
|
||||||
|
configurable: true,
|
||||||
|
|
||||||
|
set(v: any) {
|
||||||
|
Object.defineProperty(this, "g", {
|
||||||
|
value: v,
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
writable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Ensure this is most likely the Sentry WebpackInstance.
|
||||||
|
// Function.g is a very generic property and is not uncommon for another WebpackInstance (or even a React component: <g></g>) to include it
|
||||||
|
const { stack } = new Error();
|
||||||
|
if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || !String(this).includes("exports:{}") || this.c != null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assetPath = stack?.match(/\/assets\/.+?\.js/)?.[0];
|
||||||
|
if (!assetPath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const srcRequest = new XMLHttpRequest();
|
||||||
|
srcRequest.open("GET", assetPath, false);
|
||||||
|
srcRequest.send();
|
||||||
|
|
||||||
|
// Final condition to see if this is the Sentry WebpackInstance
|
||||||
|
if (!srcRequest.responseText.includes("window.DiscordSentry=")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
new Logger("NoTrack", "#8caaee").info("Disabling Sentry by erroring its WebpackInstance");
|
||||||
|
|
||||||
|
Reflect.deleteProperty(Function.prototype, "g");
|
||||||
|
Reflect.deleteProperty(window, "DiscordSentry");
|
||||||
|
|
||||||
|
throw new Error("Sentry successfully disabled");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Object.defineProperty(window, "DiscordSentry", {
|
Object.defineProperty(window, "DiscordSentry", {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
|
||||||
set() {
|
set() {
|
||||||
|
new Logger("NoTrack", "#8caaee").error("Failed to disable Sentry. Falling back to deleting window.DiscordSentry");
|
||||||
|
|
||||||
|
Reflect.deleteProperty(Function.prototype, "g");
|
||||||
Reflect.deleteProperty(window, "DiscordSentry");
|
Reflect.deleteProperty(window, "DiscordSentry");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addAccessory } from "@api/MessageAccessories";
|
import { addAccessory } from "@api/MessageAccessories";
|
||||||
|
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";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
|
@ -32,12 +33,12 @@ import { onlyOnce } from "@utils/onlyOnce";
|
||||||
import { makeCodeblock } from "@utils/text";
|
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, Toasts, UserStore } from "@webpack/common";
|
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
import plugins, { PluginMeta } from "~plugins";
|
import plugins, { PluginMeta } from "~plugins";
|
||||||
|
|
||||||
import settings from "./settings";
|
import SettingsPlugin from "./settings";
|
||||||
|
|
||||||
const VENCORD_GUILD_ID = "1015060230222131221";
|
const VENCORD_GUILD_ID = "1015060230222131221";
|
||||||
const VENBOT_USER_ID = "1017176847865352332";
|
const VENBOT_USER_ID = "1017176847865352332";
|
||||||
|
@ -86,7 +87,7 @@ async function generateDebugInfoMessage() {
|
||||||
const info = {
|
const info = {
|
||||||
Vencord:
|
Vencord:
|
||||||
`v${VERSION} • [${gitHash}](<https://github.com/Vendicated/Vencord/commit/${gitHash}>)` +
|
`v${VERSION} • [${gitHash}](<https://github.com/Vendicated/Vencord/commit/${gitHash}>)` +
|
||||||
`${settings.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`,
|
`${SettingsPlugin.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`,
|
||||||
Client: `${RELEASE_CHANNEL} ~ ${client}`,
|
Client: `${RELEASE_CHANNEL} ~ ${client}`,
|
||||||
Platform: window.navigator.platform
|
Platform: window.navigator.platform
|
||||||
};
|
};
|
||||||
|
@ -132,6 +133,10 @@ function generatePluginList() {
|
||||||
|
|
||||||
const checkForUpdatesOnce = onlyOnce(checkForUpdates);
|
const checkForUpdatesOnce = onlyOnce(checkForUpdates);
|
||||||
|
|
||||||
|
const settings = definePluginSettings({}).withPrivateSettings<{
|
||||||
|
dismissedDevBuildWarning?: boolean;
|
||||||
|
}>();
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "SupportHelper",
|
name: "SupportHelper",
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -139,6 +144,8 @@ export default definePlugin({
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["CommandsAPI", "UserSettingsAPI", "MessageAccessoriesAPI"],
|
dependencies: ["CommandsAPI", "UserSettingsAPI", "MessageAccessoriesAPI"],
|
||||||
|
|
||||||
|
settings,
|
||||||
|
|
||||||
patches: [{
|
patches: [{
|
||||||
find: ".BEGINNING_DM.format",
|
find: ".BEGINNING_DM.format",
|
||||||
replacement: {
|
replacement: {
|
||||||
|
@ -207,17 +214,22 @@ export default definePlugin({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const repo = await VencordNative.updater.getRepo();
|
if (!IS_STANDALONE && !settings.store.dismissedDevBuildWarning) {
|
||||||
if (repo.ok && !repo.value.includes("Vendicated/Vencord")) {
|
|
||||||
return Alerts.show({
|
return Alerts.show({
|
||||||
title: "Hold on!",
|
title: "Hold on!",
|
||||||
body: <div>
|
body: <div>
|
||||||
<Forms.FormText>You are using a fork of Vencord, which we do not provide support for!</Forms.FormText>
|
<Forms.FormText>You are using a custom build of Vencord, which we do not provide support for!</Forms.FormText>
|
||||||
|
|
||||||
<Forms.FormText className={Margins.top8}>
|
<Forms.FormText className={Margins.top8}>
|
||||||
Please either switch to an <Link href="https://vencord.dev/download">officially supported version of Vencord</Link>, or
|
We only provide support for <Link href="https://vencord.dev/download">official builds</Link>.
|
||||||
contact your package maintainer for support instead.
|
Either <Link href="https://vencord.dev/download">switch to an official build</Link> or figure your issue out yourself.
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</div>
|
|
||||||
|
<Text variant="text-md/bold" className={Margins.top8}>You will be banned from receiving support if you ignore this rule.</Text>
|
||||||
|
</div>,
|
||||||
|
confirmText: "Understood",
|
||||||
|
secondaryConfirmText: "Don't show again",
|
||||||
|
onConfirmSecondary: () => settings.store.dismissedDevBuildWarning = true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,28 +16,34 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
source: {
|
||||||
|
description: "Source to replace ban GIF with (Video or Gif)",
|
||||||
|
type: OptionType.STRING,
|
||||||
|
default: "https://i.imgur.com/wp5q52C.mp4",
|
||||||
|
restartNeeded: true,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "BANger",
|
name: "BANger",
|
||||||
description: "Replaces the GIF in the ban dialogue with a custom one.",
|
description: "Replaces the GIF in the ban dialogue with a custom one.",
|
||||||
authors: [Devs.Xinto, Devs.Glitch],
|
authors: [Devs.Xinto, Devs.Glitch],
|
||||||
|
settings,
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "BAN_CONFIRM_TITLE.",
|
find: "BAN_CONFIRM_TITLE.",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /src:\i\("?\d+"?\)/g,
|
match: /src:\i\("?\d+"?\)/g,
|
||||||
replace: "src: Vencord.Settings.plugins.BANger.source"
|
replace: "src:$self.source"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
options: {
|
get source() {
|
||||||
source: {
|
return settings.store.source;
|
||||||
description: "Source to replace ban GIF with (Video or Gif)",
|
|
||||||
type: OptionType.STRING,
|
|
||||||
default: "https://i.imgur.com/wp5q52C.mp4",
|
|
||||||
restartNeeded: 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 { Settings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { canonicalizeMatch } from "@utils/patches";
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
|
@ -25,10 +25,26 @@ import { findByPropsLazy } from "@webpack";
|
||||||
|
|
||||||
const UserPopoutSectionCssClasses = findByPropsLazy("section", "lastSection");
|
const UserPopoutSectionCssClasses = findByPropsLazy("section", "lastSection");
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
hide: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Hide notes",
|
||||||
|
default: false,
|
||||||
|
restartNeeded: true
|
||||||
|
},
|
||||||
|
noSpellCheck: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Disable spellcheck in notes",
|
||||||
|
disabled: () => Settings.plugins.BetterNotesBox.hide,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "BetterNotesBox",
|
name: "BetterNotesBox",
|
||||||
description: "Hide notes or disable spellcheck (Configure in settings!!)",
|
description: "Hide notes or disable spellcheck (Configure in settings!!)",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
|
settings,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
|
@ -36,7 +52,7 @@ export default definePlugin({
|
||||||
all: true,
|
all: true,
|
||||||
// Some modules match the find but the replacement is returned untouched
|
// Some modules match the find but the replacement is returned untouched
|
||||||
noWarn: true,
|
noWarn: true,
|
||||||
predicate: () => Vencord.Settings.plugins.BetterNotesBox.hide,
|
predicate: () => settings.store.hide,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /hideNote:.+?(?=([,}].*?\)))/g,
|
match: /hideNote:.+?(?=([,}].*?\)))/g,
|
||||||
replace: (m, rest) => {
|
replace: (m, rest) => {
|
||||||
|
@ -54,7 +70,7 @@ export default definePlugin({
|
||||||
find: "Messages.NOTE_PLACEHOLDER",
|
find: "Messages.NOTE_PLACEHOLDER",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.NOTE_PLACEHOLDER,/,
|
match: /\.NOTE_PLACEHOLDER,/,
|
||||||
replace: "$&spellCheck:!Vencord.Settings.plugins.BetterNotesBox.noSpellCheck,"
|
replace: "$&spellCheck:!$self.noSpellCheck,"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -66,25 +82,14 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
options: {
|
|
||||||
hide: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Hide notes",
|
|
||||||
default: false,
|
|
||||||
restartNeeded: true
|
|
||||||
},
|
|
||||||
noSpellCheck: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Disable spellcheck in notes",
|
|
||||||
disabled: () => Settings.plugins.BetterNotesBox.hide,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
patchPadding: ErrorBoundary.wrap(({ lastSection }) => {
|
patchPadding: ErrorBoundary.wrap(({ lastSection }) => {
|
||||||
if (!lastSection) return null;
|
if (!lastSection) return null;
|
||||||
return (
|
return (
|
||||||
<div className={UserPopoutSectionCssClasses.lastSection} ></div>
|
<div className={UserPopoutSectionCssClasses.lastSection} ></div>
|
||||||
);
|
);
|
||||||
})
|
}),
|
||||||
|
|
||||||
|
get noSpellCheck() {
|
||||||
|
return settings.store.noSpellCheck;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
5
src/plugins/consoleJanitor/README.md
Normal file
5
src/plugins/consoleJanitor/README.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# ConsoleJanitor
|
||||||
|
|
||||||
|
Disables annoying console messages/errors. This plugin mainly removes errors/warnings that happen all the time and noisy/spammy logging messages.
|
||||||
|
|
||||||
|
Some of the disabled messages include the "notosans-400-normalitalic" error and MessageActionCreators, Routing/Utils loggers.
|
152
src/plugins/consoleJanitor/index.ts
Normal file
152
src/plugins/consoleJanitor/index.ts
Normal file
|
@ -0,0 +1,152 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
const Noop = () => { };
|
||||||
|
const NoopLogger = {
|
||||||
|
logDangerously: Noop,
|
||||||
|
log: Noop,
|
||||||
|
verboseDangerously: Noop,
|
||||||
|
verbose: Noop,
|
||||||
|
info: Noop,
|
||||||
|
warn: Noop,
|
||||||
|
error: Noop,
|
||||||
|
trace: Noop,
|
||||||
|
time: Noop,
|
||||||
|
fileOnly: Noop
|
||||||
|
};
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
disableNoisyLoggers: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Disable noisy loggers like the MessageActionCreators",
|
||||||
|
default: false,
|
||||||
|
restartNeeded: true
|
||||||
|
},
|
||||||
|
disableSpotifyLogger: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Disable the Spotify logger, which leaks account information and access token",
|
||||||
|
default: true,
|
||||||
|
restartNeeded: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "ConsoleJanitor",
|
||||||
|
description: "Disables annoying console messages/errors",
|
||||||
|
authors: [Devs.Nuckyz],
|
||||||
|
settings,
|
||||||
|
|
||||||
|
NoopLogger: () => NoopLogger,
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: 'console.warn("Window state not initialized"',
|
||||||
|
replacement: {
|
||||||
|
match: /console\.warn\("Window state not initialized",\i\),/,
|
||||||
|
replace: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "is not a valid locale.",
|
||||||
|
replacement: {
|
||||||
|
match: /\i\.error\(""\.concat\(\i," is not a valid locale."\)\);/,
|
||||||
|
replace: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "notosans-400-normalitalic",
|
||||||
|
replacement: {
|
||||||
|
match: /,"notosans-.+?"/g,
|
||||||
|
replace: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: 'console.warn("[DEPRECATED] Please use `subscribeWithSelector` middleware");',
|
||||||
|
all: true,
|
||||||
|
replacement: {
|
||||||
|
match: /console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\);/,
|
||||||
|
replace: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "RPCServer:WSS",
|
||||||
|
replacement: {
|
||||||
|
match: /\i\.error\("Error: "\.concat\((\i)\.message/,
|
||||||
|
replace: '!$1.message.includes("EADDRINUSE")&&$&'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "Tried getting Dispatch instance before instantiated",
|
||||||
|
replacement: {
|
||||||
|
match: /null==\i&&\i\.warn\("Tried getting Dispatch instance before instantiated"\),/,
|
||||||
|
replace: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "Unable to determine render window for element",
|
||||||
|
replacement: {
|
||||||
|
match: /console\.warn\("Unable to determine render window for element",\i\),/,
|
||||||
|
replace: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "failed to send analytics events",
|
||||||
|
replacement: {
|
||||||
|
match: /console\.error\("\[analytics\] failed to send analytics events query: "\.concat\(\i\)\)/,
|
||||||
|
replace: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "Slow dispatch on",
|
||||||
|
replacement: {
|
||||||
|
match: /\i\.totalTime>100&&\i\.verbose\("Slow dispatch on ".+?\)\);/,
|
||||||
|
replace: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...[
|
||||||
|
'("MessageActionCreators")', '("ChannelMessages")',
|
||||||
|
'("Routing/Utils")', '("RTCControlSocket")',
|
||||||
|
'("ConnectionEventFramerateReducer")', '("RTCLatencyTestManager")',
|
||||||
|
'("OverlayBridgeStore")', '("RPCServer:WSS")', '("RPCServer:IPC")'
|
||||||
|
].map(logger => ({
|
||||||
|
find: logger,
|
||||||
|
predicate: () => settings.store.disableNoisyLoggers,
|
||||||
|
all: true,
|
||||||
|
replacement: {
|
||||||
|
match: new RegExp(String.raw`new \i\.\i${logger.replace(/([()])/g, "\\$1")}`),
|
||||||
|
replace: `$self.NoopLogger${logger}`
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
{
|
||||||
|
find: '"Experimental codecs: "',
|
||||||
|
predicate: () => settings.store.disableNoisyLoggers,
|
||||||
|
replacement: {
|
||||||
|
match: /new \i\.\i\("Connection\("\.concat\(\i,"\)"\)\)/,
|
||||||
|
replace: "$self.NoopLogger()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: '"Handling ping: "',
|
||||||
|
predicate: () => settings.store.disableNoisyLoggers,
|
||||||
|
replacement: {
|
||||||
|
match: /new \i\.\i\("RTCConnection\("\.concat.+?\)\)(?=,)/,
|
||||||
|
replace: "$self.NoopLogger()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: '("Spotify")',
|
||||||
|
predicate: () => settings.store.disableSpotifyLogger,
|
||||||
|
replacement: {
|
||||||
|
match: /new \i\.\i\("Spotify"\)/,
|
||||||
|
replace: "$self.NoopLogger()"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
|
@ -40,9 +40,9 @@ export default definePlugin({
|
||||||
}),
|
}),
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".ENTER&&(!",
|
find: "!this.hasOpenCodeBlock()",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/,
|
match: /!(\i).shiftKey&&!(this.hasOpenCodeBlock\(\))&&\(.{0,100}?\)/,
|
||||||
replace: "$self.shouldSubmit($1, $2)"
|
replace: "$self.shouldSubmit($1, $2)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,50 +4,64 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getCurrentChannel } from "@utils/discord";
|
import { getCurrentChannel } from "@utils/discord";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { classes } from "@utils/misc";
|
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack";
|
||||||
import { Heading, React, RelationshipStore, Text } from "@webpack/common";
|
import { Heading, RelationshipStore, Text } from "@webpack/common";
|
||||||
|
|
||||||
const container = findByPropsLazy("memberSinceWrapper");
|
const containerWrapper = findByPropsLazy("memberSinceWrapper");
|
||||||
|
const container = findByPropsLazy("memberSince");
|
||||||
const getCreatedAtDate = findByCodeLazy('month:"short",day:"numeric"');
|
const getCreatedAtDate = findByCodeLazy('month:"short",day:"numeric"');
|
||||||
const locale = findByPropsLazy("getLocale");
|
const locale = findByPropsLazy("getLocale");
|
||||||
const lastSection = findByPropsLazy("lastSection");
|
const lastSection = findByPropsLazy("lastSection");
|
||||||
|
const section = findLazy((m: any) => m.section !== void 0 && Object.values(m).length === 1);
|
||||||
const cl = classNameFactory("vc-friendssince-");
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "FriendsSince",
|
name: "FriendsSince",
|
||||||
description: "Shows when you became friends with someone in the user popout",
|
description: "Shows when you became friends with someone in the user popout",
|
||||||
authors: [Devs.Elvyra],
|
authors: [Devs.Elvyra, Devs.Antti],
|
||||||
patches: [
|
patches: [
|
||||||
// User popup
|
// User popup - old layout
|
||||||
{
|
{
|
||||||
find: ".USER_PROFILE}};return",
|
find: ".USER_PROFILE}};return",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /,{userId:(\i.id).{0,30}}\)/,
|
match: /,{userId:(\i.id).{0,30}}\)/,
|
||||||
replace: "$&,$self.friendsSince({ userId: $1 })"
|
replace: "$&,$self.friendsSinceOld({ userId: $1 })"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// User DMs "User Profile" popup in the right
|
// DM User Sidebar - old layout
|
||||||
{
|
{
|
||||||
find: ".PROFILE_PANEL,",
|
find: ".PROFILE_PANEL,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /,{userId:([^,]+?)}\)/,
|
match: /,{userId:([^,]+?)}\)/,
|
||||||
replace: "$&,$self.friendsSince({ userId: $1 })"
|
replace: "$&,$self.friendsSinceOld({ userId: $1 })"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// User Profile Modal
|
// User Profile Modal - old layout
|
||||||
{
|
{
|
||||||
find: ".userInfoSectionHeader,",
|
find: ".userInfoSectionHeader,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\.Messages\.USER_PROFILE_MEMBER_SINCE.+?userId:(.+?),textClassName:)(\i\.userInfoText)}\)/,
|
match: /(\.Messages\.USER_PROFILE_MEMBER_SINCE.+?userId:(.+?),textClassName:)(\i\.userInfoText)}\)/,
|
||||||
replace: (_, rest, userId, textClassName) => `${rest}!$self.getFriendSince(${userId}) ? ${textClassName} : void 0 }), $self.friendsSince({ userId: ${userId}, textClassName: ${textClassName} })`
|
replace: (_, rest, userId, textClassName) => `${rest}!$self.getFriendSince(${userId}) ? ${textClassName} : void 0 }), $self.friendsSinceOld({ userId: ${userId}, textClassName: ${textClassName} })`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// DM User Sidebar - new layout
|
||||||
|
{
|
||||||
|
find: ".PANEL}),nicknameIcons",
|
||||||
|
replacement: {
|
||||||
|
match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id)}\)}\)/,
|
||||||
|
replace: "$&,$self.friendsSinceNew({userId:$1,isSidebar:true})"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// User Profile Modal - new layout
|
||||||
|
{
|
||||||
|
find: "action:\"PRESS_APP_CONNECTION\"",
|
||||||
|
replacement: {
|
||||||
|
match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id),.{0,100}}\)}\),/,
|
||||||
|
replace: "$&,$self.friendsSinceNew({userId:$1,isSidebar:false}),"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -63,7 +77,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
friendsSince: ErrorBoundary.wrap(({ userId, textClassName }: { userId: string; textClassName?: string; }) => {
|
friendsSinceOld: ErrorBoundary.wrap(({ userId, textClassName }: { userId: string; textClassName?: string; }) => {
|
||||||
if (!RelationshipStore.isFriend(userId)) return null;
|
if (!RelationshipStore.isFriend(userId)) return null;
|
||||||
|
|
||||||
const friendsSince = RelationshipStore.getSince(userId);
|
const friendsSince = RelationshipStore.getSince(userId);
|
||||||
|
@ -71,11 +85,11 @@ export default definePlugin({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={lastSection.section}>
|
<div className={lastSection.section}>
|
||||||
<Heading variant="eyebrow" className={cl("title")}>
|
<Heading variant="eyebrow">
|
||||||
Friends Since
|
Friends Since
|
||||||
</Heading>
|
</Heading>
|
||||||
|
|
||||||
<div className={container.memberSinceWrapper}>
|
<div className={containerWrapper.memberSinceWrapper}>
|
||||||
{!!getCurrentChannel()?.guild_id && (
|
{!!getCurrentChannel()?.guild_id && (
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
@ -88,11 +102,55 @@ export default definePlugin({
|
||||||
<path d="M3 5v-.75C3 3.56 3.56 3 4.25 3s1.24.56 1.33 1.25C6.12 8.65 9.46 12 13 12h1a8 8 0 0 1 8 8 2 2 0 0 1-2 2 .21.21 0 0 1-.2-.15 7.65 7.65 0 0 0-1.32-2.3c-.15-.2-.42-.06-.39.17l.25 2c.02.15-.1.28-.25.28H9a2 2 0 0 1-2-2v-2.22c0-1.57-.67-3.05-1.53-4.37A15.85 15.85 0 0 1 3 5Z" />
|
<path d="M3 5v-.75C3 3.56 3.56 3 4.25 3s1.24.56 1.33 1.25C6.12 8.65 9.46 12 13 12h1a8 8 0 0 1 8 8 2 2 0 0 1-2 2 .21.21 0 0 1-.2-.15 7.65 7.65 0 0 0-1.32-2.3c-.15-.2-.42-.06-.39.17l.25 2c.02.15-.1.28-.25.28H9a2 2 0 0 1-2-2v-2.22c0-1.57-.67-3.05-1.53-4.37A15.85 15.85 0 0 1 3 5Z" />
|
||||||
</svg>
|
</svg>
|
||||||
)}
|
)}
|
||||||
<Text variant="text-sm/normal" className={classes(cl("body"), textClassName)}>
|
<Text variant="text-sm/normal" className={textClassName}>
|
||||||
{getCreatedAtDate(friendsSince, locale.getLocale())}
|
{getCreatedAtDate(friendsSince, locale.getLocale())}
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}, { noop: true })
|
}, { noop: true }),
|
||||||
|
|
||||||
|
friendsSinceNew: ErrorBoundary.wrap(({ userId, isSidebar }: { userId: string; isSidebar: boolean; }) => {
|
||||||
|
if (!RelationshipStore.isFriend(userId)) return null;
|
||||||
|
|
||||||
|
const friendsSince = RelationshipStore.getSince(userId);
|
||||||
|
if (!friendsSince) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className={section.section}>
|
||||||
|
<Heading variant="text-xs/semibold" style={isSidebar ? {} : { color: "var(--header-secondary)" }}>
|
||||||
|
Friends Since
|
||||||
|
</Heading>
|
||||||
|
|
||||||
|
{
|
||||||
|
isSidebar ? (
|
||||||
|
<Text variant="text-sm/normal">
|
||||||
|
{getCreatedAtDate(friendsSince, locale.getLocale())}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
<div className={containerWrapper.memberSinceWrapper}>
|
||||||
|
<div className={container.memberSince}>
|
||||||
|
{!!getCurrentChannel()?.guild_id && (
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
width="16"
|
||||||
|
height="16"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="var(--interactive-normal)"
|
||||||
|
>
|
||||||
|
<path d="M13 10a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" />
|
||||||
|
<path d="M3 5v-.75C3 3.56 3.56 3 4.25 3s1.24.56 1.33 1.25C6.12 8.65 9.46 12 13 12h1a8 8 0 0 1 8 8 2 2 0 0 1-2 2 .21.21 0 0 1-.2-.15 7.65 7.65 0 0 0-1.32-2.3c-.15-.2-.42-.06-.39.17l.25 2c.02.15-.1.28-.25.28H9a2 2 0 0 1-2-2v-2.22c0-1.57-.67-3.05-1.53-4.37A15.85 15.85 0 0 1 3 5Z" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
<Text variant="text-sm/normal">
|
||||||
|
{getCreatedAtDate(friendsSince, locale.getLocale())}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}, { noop: true }),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
/* copy pasted from discord */
|
|
||||||
|
|
||||||
.vc-friendssince-title {
|
|
||||||
display: flex;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 6px
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-friendssince-body {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 18px
|
|
||||||
}
|
|
|
@ -59,11 +59,15 @@ export function addPatch(newPatch: Omit<Patch, "plugin">, pluginName: string) {
|
||||||
delete patch.group;
|
delete patch.group;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (patch.predicate && !patch.predicate()) return;
|
||||||
|
|
||||||
canonicalizeFind(patch);
|
canonicalizeFind(patch);
|
||||||
if (!Array.isArray(patch.replacement)) {
|
if (!Array.isArray(patch.replacement)) {
|
||||||
patch.replacement = [patch.replacement];
|
patch.replacement = [patch.replacement];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
patch.replacement = patch.replacement.filter(({ predicate }) => !predicate || predicate());
|
||||||
|
|
||||||
if (IS_REPORTER) {
|
if (IS_REPORTER) {
|
||||||
patch.replacement.forEach(r => {
|
patch.replacement.forEach(r => {
|
||||||
delete r.predicate;
|
delete r.predicate;
|
||||||
|
|
|
@ -337,7 +337,7 @@ export default definePlugin({
|
||||||
// Pass through editHistory & deleted & original attachments to the "edited message" transformer
|
// Pass through editHistory & deleted & original attachments to the "edited message" transformer
|
||||||
match: /(?<=null!=\i\.edited_timestamp\)return )\i\(\i,\{reactions:(\i)\.reactions.{0,50}\}\)/,
|
match: /(?<=null!=\i\.edited_timestamp\)return )\i\(\i,\{reactions:(\i)\.reactions.{0,50}\}\)/,
|
||||||
replace:
|
replace:
|
||||||
"Object.assign($&,{ deleted:$1.deleted, editHistory:$1.editHistory, attachments:$1.attachments })"
|
"Object.assign($&,{ deleted:$1.deleted, editHistory:$1.editHistory })"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,11 +8,15 @@
|
||||||
.emoji,
|
.emoji,
|
||||||
[data-type="sticker"],
|
[data-type="sticker"],
|
||||||
iframe,
|
iframe,
|
||||||
.messagelogger-deleted-attachment:not([class*="hiddenAttachment_"]),
|
.messagelogger-deleted-attachment,
|
||||||
[class|="inlineMediaEmbed"]
|
[class|="inlineMediaEmbed"]
|
||||||
) {
|
) {
|
||||||
filter: grayscale(1) !important;
|
filter: grayscale(1) !important;
|
||||||
transition: 150ms filter ease-in-out;
|
transition: 150ms filter ease-in-out;
|
||||||
|
|
||||||
|
&[class*="hiddenMosaicItem_"] {
|
||||||
|
filter: grayscale(1) blur(var(--custom-message-attachment-spoiler-blur-radius, 44px)) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.messagelogger-deleted
|
.messagelogger-deleted
|
||||||
|
@ -23,8 +27,7 @@
|
||||||
iframe,
|
iframe,
|
||||||
.messagelogger-deleted-attachment,
|
.messagelogger-deleted-attachment,
|
||||||
[class|="inlineMediaEmbed"]
|
[class|="inlineMediaEmbed"]
|
||||||
):hover,
|
):hover {
|
||||||
.messagelogger-deleted {
|
|
||||||
filter: grayscale(0) !important;
|
filter: grayscale(0) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ import UserPermissions from "./components/UserPermissions";
|
||||||
import { getSortedRoles, sortPermissionOverwrites } from "./utils";
|
import { getSortedRoles, sortPermissionOverwrites } from "./utils";
|
||||||
|
|
||||||
const PopoutClasses = findByPropsLazy("container", "scroller", "list");
|
const PopoutClasses = findByPropsLazy("container", "scroller", "list");
|
||||||
const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "text");
|
const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "banner");
|
||||||
|
|
||||||
export const enum PermissionsSortOrder {
|
export const enum PermissionsSortOrder {
|
||||||
HighestRole,
|
HighestRole,
|
||||||
|
|
|
@ -37,8 +37,7 @@ import { getCurrentUserInfo, readNotification } from "./reviewDbApi";
|
||||||
import { settings } from "./settings";
|
import { settings } from "./settings";
|
||||||
import { showToast } from "./utils";
|
import { showToast } from "./utils";
|
||||||
|
|
||||||
const PopoutClasses = findByPropsLazy("container", "scroller", "list");
|
const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "banner");
|
||||||
const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "text");
|
|
||||||
|
|
||||||
const guildPopoutPatch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild, onClose(): void; }) => {
|
const guildPopoutPatch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild, onClose(): void; }) => {
|
||||||
if (!guild) return;
|
if (!guild) return;
|
||||||
|
@ -87,7 +86,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".VIEW_FULL_PROFILE,",
|
find: ".BITE_SIZE,user:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=\.BITE_SIZE,children:\[)\(0,\i\.jsx\)\(\i\.\i,\{user:(\i),/,
|
match: /(?<=\.BITE_SIZE,children:\[)\(0,\i\.jsx\)\(\i\.\i,\{user:(\i),/,
|
||||||
replace: "$self.BiteSizeReviewsButton({user:$1}),$&"
|
replace: "$self.BiteSizeReviewsButton({user:$1}),$&"
|
||||||
|
@ -181,9 +180,9 @@ export default definePlugin({
|
||||||
onClick={() => openReviewsModal(user.id, user.username)}
|
onClick={() => openReviewsModal(user.id, user.username)}
|
||||||
look={Button.Looks.FILLED}
|
look={Button.Looks.FILLED}
|
||||||
size={Button.Sizes.NONE}
|
size={Button.Sizes.NONE}
|
||||||
color={RoleButtonClasses.color}
|
color={RoleButtonClasses.bannerColor}
|
||||||
className={classes(RoleButtonClasses.button, RoleButtonClasses.banner)}
|
className={classes(RoleButtonClasses.button, RoleButtonClasses.icon, RoleButtonClasses.banner)}
|
||||||
innerClassName={classes(RoleButtonClasses.buttonInner, RoleButtonClasses.banner)}
|
innerClassName={classes(RoleButtonClasses.buttonInner, RoleButtonClasses.icon, RoleButtonClasses.banner)}
|
||||||
>
|
>
|
||||||
<NotesIcon height={16} width={16} />
|
<NotesIcon height={16} width={16} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
6
src/plugins/showAllRoles/README.md
Normal file
6
src/plugins/showAllRoles/README.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# ShowAllRoles
|
||||||
|
|
||||||
|
Display all roles on the new profiles instead of limiting them to the default two rows.
|
||||||
|
|
||||||
|
![image](https://github.com/Vendicated/Vencord/assets/71079641/3f021f03-c6f9-4fe5-83ac-a1891b5e4b37)
|
||||||
|
|
23
src/plugins/showAllRoles/index.ts
Normal file
23
src/plugins/showAllRoles/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "ShowAllRoles",
|
||||||
|
description: "Show all roles in new profiles.",
|
||||||
|
authors: [Devs.Luna],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: ".Messages.VIEW_ALL_ROLES",
|
||||||
|
replacement: {
|
||||||
|
match: /(\i)\.slice\(0,\i\)/,
|
||||||
|
replace: "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
|
@ -257,7 +257,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '"alt+shift+down"',
|
find: '"alt+shift+down"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=getChannel\(\i\);return null!=(\i))(?=.{0,150}?>0\)&&\(0,\i\.\i\)\(\i\))/,
|
match: /(?<=getChannel\(\i\);return null!=(\i))(?=.{0,200}?>0\)&&\(0,\i\.\i\)\(\i\))/,
|
||||||
replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})`
|
replace: (_, channel) => `&&!$self.isHiddenChannel(${channel})`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -265,8 +265,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".APPLICATION_STORE&&null!=",
|
find: ".APPLICATION_STORE&&null!=",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=getState\(\)\.channelId.{0,30}?\(0,\i\.\i\)\(\i\))(?=\.map\()/,
|
match: /getState\(\)\.channelId.+?(?=\.map\(\i=>\i\.id)/,
|
||||||
replace: ".filter(e=>!$self.isHiddenChannel(e))"
|
replace: "$&.filter(e=>!$self.isHiddenChannel(e))"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -477,12 +477,17 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
|
|
||||||
isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) {
|
isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) {
|
||||||
if (!channel) return false;
|
try {
|
||||||
|
if (!channel) return false;
|
||||||
|
|
||||||
if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId);
|
if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId);
|
||||||
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false;
|
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false;
|
||||||
|
|
||||||
return !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || checkConnect && !PermissionStore.can(PermissionsBits.CONNECT, channel);
|
return !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || checkConnect && !PermissionStore.can(PermissionsBits.CONNECT, channel);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("[ViewHiddenChannels#isHiddenChannel]: ", e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
resolveGuildChannels(channels: Record<string | number, Array<{ channel: Channel; comparator: number; }> | string | number>, shouldIncludeHidden: boolean) {
|
resolveGuildChannels(channels: Record<string | number, Array<{ channel: Channel; comparator: number; }> | string | number>, shouldIncludeHidden: boolean) {
|
||||||
|
|
|
@ -77,7 +77,7 @@ const settings = definePluginSettings({
|
||||||
});
|
});
|
||||||
|
|
||||||
function stringToRegex(str: string) {
|
function stringToRegex(str: string) {
|
||||||
const match = str.match(/^(\/)?(.+?)(?:\/([gimsuy]*))?$/); // Regex to match regex
|
const match = str.match(/^(\/)?(.+?)(?:\/([gimsuyv]*))?$/); // Regex to match regex
|
||||||
return match
|
return match
|
||||||
? new RegExp(
|
? new RegExp(
|
||||||
match[2], // Pattern
|
match[2], // Pattern
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
# Urban Dictionary
|
|
||||||
|
|
||||||
Use /urban slash command to search for a definition for a word on [Urban Dictionary](https://www.urbandictionary.com/).
|
|
||||||
|
|
||||||
## Preview
|
|
||||||
|
|
||||||
![preview](https://i.imgur.com/1zwzj38.png)
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
- Enable this plugin
|
|
||||||
- Set plugin settings as desired
|
|
||||||
- Type /urban and start getting definitions right into your Discord client.
|
|
|
@ -1,105 +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 { ApplicationCommandOptionType, sendBotMessage } from "@api/Commands";
|
|
||||||
import { ApplicationCommandInputType } from "@api/Commands/types";
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
resultsAmount: {
|
|
||||||
type: OptionType.NUMBER,
|
|
||||||
description: "The amount of results you want to get (more gives better results, but is slower)",
|
|
||||||
default: 10
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "UrbanDictionary",
|
|
||||||
description: "Search for a word on Urban Dictionary via /urban slash command",
|
|
||||||
authors: [Devs.jewdev],
|
|
||||||
dependencies: ["CommandsAPI"],
|
|
||||||
settings,
|
|
||||||
commands: [
|
|
||||||
{
|
|
||||||
name: "urban",
|
|
||||||
description: "Returns the definition of a word from Urban Dictionary",
|
|
||||||
inputType: ApplicationCommandInputType.BUILT_IN,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
type: ApplicationCommandOptionType.STRING,
|
|
||||||
name: "word",
|
|
||||||
description: "The word to search for on Urban Dictionary",
|
|
||||||
required: true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
execute: async (args, ctx) => {
|
|
||||||
try {
|
|
||||||
const query: string = encodeURIComponent(args[0].value);
|
|
||||||
const { list } = await fetch(`https://api.urbandictionary.com/v0/define?term=${query}&per_page=${settings.store.resultsAmount}`).then(response => response.json());
|
|
||||||
|
|
||||||
if (!list.length)
|
|
||||||
return void sendBotMessage(ctx.channel.id, { content: "No results found." });
|
|
||||||
|
|
||||||
const definition = list.reduce((prev, curr) => {
|
|
||||||
return prev.thumbs_up > curr.thumbs_up ? prev : curr;
|
|
||||||
});
|
|
||||||
|
|
||||||
const linkify = (text: string) => text
|
|
||||||
.replaceAll("\r\n", "\n")
|
|
||||||
.replace(/([*>_`~\\])/gsi, "\\$1")
|
|
||||||
.replace(/\[(.+?)\]/g, (_, word) => `[${word}](https://www.urbandictionary.com/define.php?term=${encodeURIComponent(word)} "Define '${word}' on Urban Dictionary")`)
|
|
||||||
.trim();
|
|
||||||
|
|
||||||
return void sendBotMessage(ctx.channel.id, {
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
type: "rich",
|
|
||||||
author: {
|
|
||||||
name: `Uploaded by "${definition.author}"`,
|
|
||||||
url: `https://www.urbandictionary.com/author.php?author=${encodeURIComponent(definition.author)}`,
|
|
||||||
},
|
|
||||||
title: definition.word,
|
|
||||||
url: `https://www.urbandictionary.com/define.php?term=${encodeURIComponent(definition.word)}`,
|
|
||||||
description: linkify(definition.definition),
|
|
||||||
fields: [
|
|
||||||
{
|
|
||||||
name: "Example",
|
|
||||||
value: linkify(definition.example),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Want more definitions?",
|
|
||||||
value: `Check out [more definitions](https://www.urbandictionary.com/define.php?term=${query} "Define "${args[0].value}" on Urban Dictionary") on Urban Dictionary.`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
color: 0xFF9900,
|
|
||||||
footer: { text: `👍 ${definition.thumbs_up.toString()} | 👎 ${definition.thumbs_down.toString()}`, icon_url: "https://www.urbandictionary.com/favicon.ico" },
|
|
||||||
timestamp: new Date(definition.written_on).toISOString(),
|
|
||||||
},
|
|
||||||
] as any,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
sendBotMessage(ctx.channel.id, {
|
|
||||||
content: `Something went wrong: \`${error}\``,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
|
@ -213,8 +213,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".canUsePremiumProfileCustomization,{avatarSrc:",
|
find: ".canUsePremiumProfileCustomization,{avatarSrc:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /children:\(0,\i\.jsx\)\(\i,{src:(\i)/,
|
match: /\.avatar,\i\.clickable\),onClick:\i,(?<=avatarSrc:(\i).+?)/,
|
||||||
replace: "style:{cursor:\"pointer\"},onClick:()=>{$self.openImage($1)},$&"
|
replace: "$&style:{cursor:\"pointer\"},onClick:()=>{$self.openImage($1)},"
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -200,6 +200,34 @@ export default definePlugin({
|
||||||
match: /supports\(\i\)\{switch\(\i\)\{(case (\i).\i)/,
|
match: /supports\(\i\)\{switch\(\i\)\{(case (\i).\i)/,
|
||||||
replace: "$&.DISABLE_VIDEO:return true;$1"
|
replace: "$&.DISABLE_VIDEO:return true;$1"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".Messages.SEARCH_WITH_GOOGLE",
|
||||||
|
replacement: {
|
||||||
|
match: /\i\.isPlatformEmbedded/,
|
||||||
|
replace: "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".Messages.COPY,hint:",
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /\i\.isPlatformEmbedded/,
|
||||||
|
replace: "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /\i\.\i\.copy/,
|
||||||
|
replace: "Vencord.Webpack.Common.Clipboard.copy"
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
// Automod add filter words
|
||||||
|
{
|
||||||
|
find: '("interactionUsernameProfile',
|
||||||
|
replacement:
|
||||||
|
{
|
||||||
|
match: /\i\.isPlatformEmbedded(?=.{0,50}\.tagName)/,
|
||||||
|
replace: "true"
|
||||||
|
},
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "Wikisearch",
|
|
||||||
description: "Searches Wikipedia for your requested query. (/wikisearch)",
|
|
||||||
authors: [Devs.Samu],
|
|
||||||
dependencies: ["CommandsAPI"],
|
|
||||||
commands: [
|
|
||||||
{
|
|
||||||
name: "wikisearch",
|
|
||||||
description: "Searches Wikipedia for your request.",
|
|
||||||
inputType: ApplicationCommandInputType.BUILT_IN,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: "search",
|
|
||||||
description: "Word to search for",
|
|
||||||
type: ApplicationCommandOptionType.STRING,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
],
|
|
||||||
execute: async (_, ctx) => {
|
|
||||||
const word = findOption(_, "search", "");
|
|
||||||
|
|
||||||
if (!word) {
|
|
||||||
return sendBotMessage(ctx.channel.id, {
|
|
||||||
content: "No word was defined!"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const dataSearchParams = new URLSearchParams({
|
|
||||||
action: "query",
|
|
||||||
format: "json",
|
|
||||||
list: "search",
|
|
||||||
formatversion: "2",
|
|
||||||
origin: "*",
|
|
||||||
srsearch: word
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await fetch("https://en.wikipedia.org/w/api.php?" + dataSearchParams).then(response => response.json())
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
sendBotMessage(ctx.channel.id, { content: "There was an error. Check the console for more info" });
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!data) return;
|
|
||||||
|
|
||||||
if (!data.query?.search?.length) {
|
|
||||||
console.log(data);
|
|
||||||
return sendBotMessage(ctx.channel.id, { content: "No results given" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const altData = await fetch(`https://en.wikipedia.org/w/api.php?action=query&format=json&prop=info%7Cdescription%7Cimages%7Cimageinfo%7Cpageimages&list=&meta=&indexpageids=1&pageids=${data.query.search[0].pageid}&formatversion=2&origin=*`)
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(data => data.query.pages[0])
|
|
||||||
.catch(err => {
|
|
||||||
console.log(err);
|
|
||||||
sendBotMessage(ctx.channel.id, { content: "There was an error. Check the console for more info" });
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!altData) return;
|
|
||||||
|
|
||||||
const thumbnailData = altData.thumbnail;
|
|
||||||
|
|
||||||
const thumbnail = thumbnailData && {
|
|
||||||
url: thumbnailData.source.replace(/(50px-)/ig, "1000px-"),
|
|
||||||
height: thumbnailData.height * 100,
|
|
||||||
width: thumbnailData.width * 100
|
|
||||||
};
|
|
||||||
|
|
||||||
sendBotMessage(ctx.channel.id, {
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
type: "rich",
|
|
||||||
title: data.query.search[0].title,
|
|
||||||
url: `https://wikipedia.org/w/index.php?curid=${data.query.search[0].pageid}`,
|
|
||||||
color: "0x8663BE",
|
|
||||||
description: data.query.search[0].snippet.replace(/( |<([^>]+)>)/ig, "").replace(/(")/ig, "\"") + "...",
|
|
||||||
image: thumbnail,
|
|
||||||
footer: {
|
|
||||||
text: "Powered by the Wikimedia API",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
] as any
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
|
@ -533,6 +533,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
keifufu: {
|
keifufu: {
|
||||||
name: "keifufu",
|
name: "keifufu",
|
||||||
id: 469588398110146590n
|
id: 469588398110146590n
|
||||||
|
},
|
||||||
|
Antti: {
|
||||||
|
name: "Antti",
|
||||||
|
id: 312974985876471810n
|
||||||
}
|
}
|
||||||
} satisfies Record<string, Dev>);
|
} satisfies Record<string, Dev>);
|
||||||
|
|
||||||
|
|
62
src/webpack/common/types/utils.d.ts
vendored
62
src/webpack/common/types/utils.d.ts
vendored
|
@ -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 { Guild, GuildMember } from "discord-types/general";
|
import { Guild, GuildMember, User } from "discord-types/general";
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
import { LiteralUnion } from "type-fest";
|
import { LiteralUnion } from "type-fest";
|
||||||
|
|
||||||
|
@ -256,3 +256,63 @@ export interface PopoutActions {
|
||||||
close(key: string): void;
|
close(key: string): void;
|
||||||
setAlwaysOnTop(key: string, alwaysOnTop: boolean): void;
|
setAlwaysOnTop(key: string, alwaysOnTop: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UserNameUtilsTagInclude = LiteralUnion<"auto" | "always" | "never", string>;
|
||||||
|
export interface UserNameUtilsTagOptions {
|
||||||
|
forcePomelo?: boolean;
|
||||||
|
identifiable?: UserNameUtilsTagInclude;
|
||||||
|
decoration?: UserNameUtilsTagInclude;
|
||||||
|
mode?: "full" | "username";
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UsernameUtils {
|
||||||
|
getGlobalName(user: User): string;
|
||||||
|
getFormattedName(user: User, useTagInsteadOfUsername?: boolean): string;
|
||||||
|
getName(user: User): string;
|
||||||
|
useName(user: User): string;
|
||||||
|
getUserTag(user: User, options?: UserNameUtilsTagOptions): string;
|
||||||
|
useUserTag(user: User, options?: UserNameUtilsTagOptions): string;
|
||||||
|
|
||||||
|
|
||||||
|
useDirectMessageRecipient: any;
|
||||||
|
humanizeStatus: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DisplayProfile {
|
||||||
|
userId: string;
|
||||||
|
banner?: string;
|
||||||
|
bio?: string;
|
||||||
|
pronouns?: string;
|
||||||
|
accentColor?: number;
|
||||||
|
themeColors?: number[];
|
||||||
|
popoutAnimationParticleType?: any;
|
||||||
|
profileEffectId?: string;
|
||||||
|
_userProfile?: any;
|
||||||
|
_guildMemberProfile?: any;
|
||||||
|
canUsePremiumProfileCustomization: boolean;
|
||||||
|
canEditThemes: boolean;
|
||||||
|
premiumGuildSince: Date | null;
|
||||||
|
premiumSince: Date | null;
|
||||||
|
premiumType?: number;
|
||||||
|
primaryColor?: number;
|
||||||
|
|
||||||
|
getBadges(): Array<{
|
||||||
|
id: string;
|
||||||
|
description: string;
|
||||||
|
icon: string;
|
||||||
|
link?: string;
|
||||||
|
}>;
|
||||||
|
getBannerURL(options: { canAnimate: boolean; size: number; }): string;
|
||||||
|
getLegacyUsername(): string | null;
|
||||||
|
hasFullProfile(): boolean;
|
||||||
|
hasPremiumCustomization(): boolean;
|
||||||
|
hasThemeColors(): boolean;
|
||||||
|
isUsingGuildMemberBanner(): boolean;
|
||||||
|
isUsingGuildMemberBio(): boolean;
|
||||||
|
isUsingGuildMemberPronouns(): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DisplayProfileUtils {
|
||||||
|
getDisplayProfile(userId: string, guildId?: string, customStores?: any): DisplayProfile | null;
|
||||||
|
useDisplayProfile(userId: string, guildId?: string, customStores?: any): DisplayProfile | null;
|
||||||
|
}
|
||||||
|
|
|
@ -77,6 +77,25 @@ const ToastPosition = {
|
||||||
BOTTOM: 1
|
BOTTOM: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface ToastData {
|
||||||
|
message: string,
|
||||||
|
id: string,
|
||||||
|
/**
|
||||||
|
* Toasts.Type
|
||||||
|
*/
|
||||||
|
type: number,
|
||||||
|
options?: ToastOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToastOptions {
|
||||||
|
/**
|
||||||
|
* Toasts.Position
|
||||||
|
*/
|
||||||
|
position?: number;
|
||||||
|
component?: React.ReactNode,
|
||||||
|
duration?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export const Toasts = {
|
export const Toasts = {
|
||||||
Type: ToastType,
|
Type: ToastType,
|
||||||
Position: ToastPosition,
|
Position: ToastPosition,
|
||||||
|
@ -85,23 +104,9 @@ export const Toasts = {
|
||||||
|
|
||||||
// hack to merge with the following interface, dunno if there's a better way
|
// hack to merge with the following interface, dunno if there's a better way
|
||||||
...{} as {
|
...{} as {
|
||||||
show(data: {
|
show(data: ToastData): void;
|
||||||
message: string,
|
|
||||||
id: string,
|
|
||||||
/**
|
|
||||||
* Toasts.Type
|
|
||||||
*/
|
|
||||||
type: number,
|
|
||||||
options?: {
|
|
||||||
/**
|
|
||||||
* Toasts.Position
|
|
||||||
*/
|
|
||||||
position?: number;
|
|
||||||
component?: React.ReactNode,
|
|
||||||
duration?: number;
|
|
||||||
};
|
|
||||||
}): void;
|
|
||||||
pop(): void;
|
pop(): void;
|
||||||
|
create(message: string, type: number, options?: ToastOptions): ToastData;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -109,18 +114,15 @@ export const Toasts = {
|
||||||
waitFor("showToast", m => {
|
waitFor("showToast", m => {
|
||||||
Toasts.show = m.showToast;
|
Toasts.show = m.showToast;
|
||||||
Toasts.pop = m.popToast;
|
Toasts.pop = m.popToast;
|
||||||
|
Toasts.create = m.createToast;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show a simple toast. If you need more options, use Toasts.show manually
|
* Show a simple toast. If you need more options, use Toasts.show manually
|
||||||
*/
|
*/
|
||||||
export function showToast(message: string, type = ToastType.MESSAGE) {
|
export function showToast(message: string, type = ToastType.MESSAGE, options?: ToastOptions) {
|
||||||
Toasts.show({
|
Toasts.show(Toasts.create(message, type, options));
|
||||||
id: Toasts.genId(),
|
|
||||||
message,
|
|
||||||
type
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserUtils = {
|
export const UserUtils = {
|
||||||
|
@ -180,3 +182,9 @@ export const PopoutActions: t.PopoutActions = mapMangledModuleLazy('type:"POPOUT
|
||||||
close: filters.byCode('type:"POPOUT_WINDOW_CLOSE"'),
|
close: filters.byCode('type:"POPOUT_WINDOW_CLOSE"'),
|
||||||
setAlwaysOnTop: filters.byCode('type:"POPOUT_WINDOW_SET_ALWAYS_ON_TOP"'),
|
setAlwaysOnTop: filters.byCode('type:"POPOUT_WINDOW_SET_ALWAYS_ON_TOP"'),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const UsernameUtils: t.UsernameUtils = findByPropsLazy("useName", "getGlobalName");
|
||||||
|
export const DisplayProfileUtils: t.DisplayProfileUtils = mapMangledModuleLazy(/=\i\.getUserProfile\(\i\),\i=\i\.getGuildMemberProfile\(/, {
|
||||||
|
getDisplayProfile: filters.byCode(".getGuildMemberProfile("),
|
||||||
|
useDisplayProfile: filters.byCode(/\[\i\.\i,\i\.\i],\(\)=>/)
|
||||||
|
});
|
||||||
|
|
|
@ -56,58 +56,56 @@ Object.defineProperty(window, WEBPACK_CHUNK, {
|
||||||
// normally, this is populated via webpackGlobal.push, which we patch below.
|
// normally, this is populated via webpackGlobal.push, which we patch below.
|
||||||
// However, Discord has their .m prepopulated.
|
// However, Discord has their .m prepopulated.
|
||||||
// Thus, we use this hack to immediately access their wreq.m and patch all already existing factories
|
// Thus, we use this hack to immediately access their wreq.m and patch all already existing factories
|
||||||
//
|
|
||||||
// Update: Discord now has TWO webpack instances. Their normal one and sentry
|
|
||||||
// Sentry does not push chunks to the global at all, so this same patch now also handles their sentry modules
|
|
||||||
Object.defineProperty(Function.prototype, "m", {
|
Object.defineProperty(Function.prototype, "m", {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
|
||||||
set(v: any) {
|
set(v: any) {
|
||||||
// When using react devtools or other extensions, we may also catch their webpack here.
|
|
||||||
// This ensures we actually got the right one
|
|
||||||
const { stack } = new Error();
|
|
||||||
if ((stack?.includes("discord.com") || stack?.includes("discordapp.com")) && !Array.isArray(v)) {
|
|
||||||
const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "";
|
|
||||||
|
|
||||||
logger.info("Found Webpack module factory", fileName);
|
|
||||||
patchFactories(v);
|
|
||||||
|
|
||||||
// Define a setter for the bundlePath property of WebpackRequire. Only the main Webpack has this property.
|
|
||||||
// So if the setter is called, this means we can initialize the internal references to WebpackRequire.
|
|
||||||
Object.defineProperty(this, "p", {
|
|
||||||
configurable: true,
|
|
||||||
|
|
||||||
set(this: WebpackInstance, bundlePath: string) {
|
|
||||||
Object.defineProperty(this, "p", {
|
|
||||||
value: bundlePath,
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
clearTimeout(setterTimeout);
|
|
||||||
|
|
||||||
if (bundlePath !== "/assets/") return;
|
|
||||||
|
|
||||||
logger.info(`Main Webpack found in ${fileName}, initializing internal references to WebpackRequire`);
|
|
||||||
_initWebpack(this);
|
|
||||||
|
|
||||||
for (const beforeInitListener of beforeInitListeners) {
|
|
||||||
beforeInitListener(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// setImmediate to clear this property setter if this is not the main Webpack.
|
|
||||||
// If this is the main Webpack, wreq.p will always be set before the timeout runs.
|
|
||||||
const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "p"), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.defineProperty(this, "m", {
|
Object.defineProperty(this, "m", {
|
||||||
value: v,
|
value: v,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
writable: true
|
writable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// When using react devtools or other extensions, we may also catch their webpack here.
|
||||||
|
// This ensures we actually got the right one
|
||||||
|
const { stack } = new Error();
|
||||||
|
if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || Array.isArray(v)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1] ?? "";
|
||||||
|
logger.info("Found Webpack module factory", fileName);
|
||||||
|
|
||||||
|
patchFactories(v);
|
||||||
|
|
||||||
|
// Define a setter for the bundlePath property of WebpackRequire. Only the main Webpack has this property.
|
||||||
|
// So if the setter is called, this means we can initialize the internal references to WebpackRequire.
|
||||||
|
Object.defineProperty(this, "p", {
|
||||||
|
configurable: true,
|
||||||
|
|
||||||
|
set(this: WebpackInstance, bundlePath: string) {
|
||||||
|
Object.defineProperty(this, "p", {
|
||||||
|
value: bundlePath,
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
writable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(setterTimeout);
|
||||||
|
if (bundlePath !== "/assets/") return;
|
||||||
|
|
||||||
|
logger.info(`Main Webpack found in ${fileName}, initializing internal references to WebpackRequire`);
|
||||||
|
_initWebpack(this);
|
||||||
|
|
||||||
|
for (const beforeInitListener of beforeInitListeners) {
|
||||||
|
beforeInitListener(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// setImmediate to clear this property setter if this is not the main Webpack.
|
||||||
|
// If this is the main Webpack, wreq.p will always be set before the timeout runs.
|
||||||
|
const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "p"), 0);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -261,7 +259,6 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
|
||||||
|
|
||||||
for (let i = 0; i < patches.length; i++) {
|
for (let i = 0; i < patches.length; i++) {
|
||||||
const patch = patches[i];
|
const patch = patches[i];
|
||||||
if (patch.predicate && !patch.predicate()) continue;
|
|
||||||
|
|
||||||
const moduleMatches = typeof patch.find === "string"
|
const moduleMatches = typeof patch.find === "string"
|
||||||
? code.includes(patch.find)
|
? code.includes(patch.find)
|
||||||
|
@ -277,8 +274,6 @@ function patchFactories(factories: Record<string, (module: any, exports: any, re
|
||||||
|
|
||||||
// We change all patch.replacement to array in plugins/index
|
// We change all patch.replacement to array in plugins/index
|
||||||
for (const replacement of patch.replacement as PatchReplacement[]) {
|
for (const replacement of patch.replacement as PatchReplacement[]) {
|
||||||
if (replacement.predicate && !replacement.predicate()) continue;
|
|
||||||
|
|
||||||
const lastMod = mod;
|
const lastMod = mod;
|
||||||
const lastCode = code;
|
const lastCode = code;
|
||||||
|
|
||||||
|
|
|
@ -38,31 +38,43 @@ export let cache: WebpackInstance["c"];
|
||||||
|
|
||||||
export type FilterFn = (mod: any) => boolean;
|
export type FilterFn = (mod: any) => boolean;
|
||||||
|
|
||||||
|
type PropsFilter = Array<string>;
|
||||||
|
type CodeFilter = Array<string | RegExp>;
|
||||||
|
type StoreNameFilter = string;
|
||||||
|
|
||||||
|
const stringMatches = (s: string, filter: CodeFilter) =>
|
||||||
|
filter.every(f =>
|
||||||
|
typeof f === "string"
|
||||||
|
? s.includes(f)
|
||||||
|
: f.test(s)
|
||||||
|
);
|
||||||
|
|
||||||
export const filters = {
|
export const filters = {
|
||||||
byProps: (...props: string[]): FilterFn =>
|
byProps: (...props: PropsFilter): FilterFn =>
|
||||||
props.length === 1
|
props.length === 1
|
||||||
? m => m[props[0]] !== void 0
|
? m => m[props[0]] !== void 0
|
||||||
: m => props.every(p => m[p] !== void 0),
|
: m => props.every(p => m[p] !== void 0),
|
||||||
|
|
||||||
byCode: (...code: string[]): FilterFn => m => {
|
byCode: (...code: CodeFilter): FilterFn => {
|
||||||
if (typeof m !== "function") return false;
|
code = code.map(canonicalizeMatch);
|
||||||
const s = Function.prototype.toString.call(m);
|
return m => {
|
||||||
for (const c of code) {
|
if (typeof m !== "function") return false;
|
||||||
if (!s.includes(c)) return false;
|
return stringMatches(Function.prototype.toString.call(m), code);
|
||||||
}
|
};
|
||||||
return true;
|
|
||||||
},
|
},
|
||||||
byStoreName: (name: string): FilterFn => m =>
|
byStoreName: (name: StoreNameFilter): FilterFn => m =>
|
||||||
m.constructor?.displayName === name,
|
m.constructor?.displayName === name,
|
||||||
|
|
||||||
componentByCode: (...code: string[]): FilterFn => {
|
componentByCode: (...code: CodeFilter): FilterFn => {
|
||||||
const filter = filters.byCode(...code);
|
const filter = filters.byCode(...code);
|
||||||
return m => {
|
return m => {
|
||||||
if (filter(m)) return true;
|
if (filter(m)) return true;
|
||||||
if (!m.$$typeof) return false;
|
if (!m.$$typeof) return false;
|
||||||
if (m.type && m.type.render) return filter(m.type.render); // memo + forwardRef
|
if (m.type)
|
||||||
if (m.type) return filter(m.type); // memos
|
return m.type.render
|
||||||
if (m.render) return filter(m.render); // forwardRefs
|
? filter(m.type.render) // memo + forwardRef
|
||||||
|
: filter(m.type); // memo
|
||||||
|
if (m.render) return filter(m.render); // forwardRef
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -245,15 +257,9 @@ export const findBulk = traceFunction("findBulk", function findBulk(...filterFns
|
||||||
* Find the id of the first module factory that includes all the given code
|
* Find the id of the first module factory that includes all the given code
|
||||||
* @returns string or null
|
* @returns string or null
|
||||||
*/
|
*/
|
||||||
export const findModuleId = traceFunction("findModuleId", function findModuleId(...code: string[]) {
|
export const findModuleId = traceFunction("findModuleId", function findModuleId(...code: CodeFilter) {
|
||||||
outer:
|
|
||||||
for (const id in wreq.m) {
|
for (const id in wreq.m) {
|
||||||
const str = wreq.m[id].toString();
|
if (stringMatches(wreq.m[id].toString(), code)) return id;
|
||||||
|
|
||||||
for (const c of code) {
|
|
||||||
if (!str.includes(c)) continue outer;
|
|
||||||
}
|
|
||||||
return id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const err = new Error("Didn't find module with code(s):\n" + code.join("\n"));
|
const err = new Error("Didn't find module with code(s):\n" + code.join("\n"));
|
||||||
|
@ -272,7 +278,7 @@ export const findModuleId = traceFunction("findModuleId", function findModuleId(
|
||||||
* Find the first module factory that includes all the given code
|
* Find the first module factory that includes all the given code
|
||||||
* @returns The module factory or null
|
* @returns The module factory or null
|
||||||
*/
|
*/
|
||||||
export function findModuleFactory(...code: string[]) {
|
export function findModuleFactory(...code: CodeFilter) {
|
||||||
const id = findModuleId(...code);
|
const id = findModuleId(...code);
|
||||||
if (!id) return null;
|
if (!id) return null;
|
||||||
|
|
||||||
|
@ -325,7 +331,7 @@ export function findLazy(filter: FilterFn) {
|
||||||
/**
|
/**
|
||||||
* Find the first module that has the specified properties
|
* Find the first module that has the specified properties
|
||||||
*/
|
*/
|
||||||
export function findByProps(...props: string[]) {
|
export function findByProps(...props: PropsFilter) {
|
||||||
const res = find(filters.byProps(...props), { isIndirect: true });
|
const res = find(filters.byProps(...props), { isIndirect: true });
|
||||||
if (!res)
|
if (!res)
|
||||||
handleModuleNotFound("findByProps", ...props);
|
handleModuleNotFound("findByProps", ...props);
|
||||||
|
@ -335,7 +341,7 @@ export function findByProps(...props: string[]) {
|
||||||
/**
|
/**
|
||||||
* Find the first module that has the specified properties, lazily
|
* Find the first module that has the specified properties, lazily
|
||||||
*/
|
*/
|
||||||
export function findByPropsLazy(...props: string[]) {
|
export function findByPropsLazy(...props: PropsFilter) {
|
||||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findByProps", props]);
|
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findByProps", props]);
|
||||||
|
|
||||||
return proxyLazy(() => findByProps(...props));
|
return proxyLazy(() => findByProps(...props));
|
||||||
|
@ -344,7 +350,7 @@ export function findByPropsLazy(...props: string[]) {
|
||||||
/**
|
/**
|
||||||
* Find the first function that includes all the given code
|
* Find the first function that includes all the given code
|
||||||
*/
|
*/
|
||||||
export function findByCode(...code: string[]) {
|
export function findByCode(...code: CodeFilter) {
|
||||||
const res = find(filters.byCode(...code), { isIndirect: true });
|
const res = find(filters.byCode(...code), { isIndirect: true });
|
||||||
if (!res)
|
if (!res)
|
||||||
handleModuleNotFound("findByCode", ...code);
|
handleModuleNotFound("findByCode", ...code);
|
||||||
|
@ -354,7 +360,7 @@ export function findByCode(...code: string[]) {
|
||||||
/**
|
/**
|
||||||
* Find the first function that includes all the given code, lazily
|
* Find the first function that includes all the given code, lazily
|
||||||
*/
|
*/
|
||||||
export function findByCodeLazy(...code: string[]) {
|
export function findByCodeLazy(...code: CodeFilter) {
|
||||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findByCode", code]);
|
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findByCode", code]);
|
||||||
|
|
||||||
return proxyLazy(() => findByCode(...code));
|
return proxyLazy(() => findByCode(...code));
|
||||||
|
@ -363,7 +369,7 @@ export function findByCodeLazy(...code: string[]) {
|
||||||
/**
|
/**
|
||||||
* Find a store by its displayName
|
* Find a store by its displayName
|
||||||
*/
|
*/
|
||||||
export function findStore(name: string) {
|
export function findStore(name: StoreNameFilter) {
|
||||||
const res = find(filters.byStoreName(name), { isIndirect: true });
|
const res = find(filters.byStoreName(name), { isIndirect: true });
|
||||||
if (!res)
|
if (!res)
|
||||||
handleModuleNotFound("findStore", name);
|
handleModuleNotFound("findStore", name);
|
||||||
|
@ -373,7 +379,7 @@ export function findStore(name: string) {
|
||||||
/**
|
/**
|
||||||
* Find a store by its displayName, lazily
|
* Find a store by its displayName, lazily
|
||||||
*/
|
*/
|
||||||
export function findStoreLazy(name: string) {
|
export function findStoreLazy(name: StoreNameFilter) {
|
||||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findStore", [name]]);
|
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findStore", [name]]);
|
||||||
|
|
||||||
return proxyLazy(() => findStore(name));
|
return proxyLazy(() => findStore(name));
|
||||||
|
@ -382,7 +388,7 @@ export function findStoreLazy(name: string) {
|
||||||
/**
|
/**
|
||||||
* Finds the component which includes all the given code. Checks for plain components, memos and forwardRefs
|
* Finds the component which includes all the given code. Checks for plain components, memos and forwardRefs
|
||||||
*/
|
*/
|
||||||
export function findComponentByCode(...code: string[]) {
|
export function findComponentByCode(...code: CodeFilter) {
|
||||||
const res = find(filters.componentByCode(...code), { isIndirect: true });
|
const res = find(filters.componentByCode(...code), { isIndirect: true });
|
||||||
if (!res)
|
if (!res)
|
||||||
handleModuleNotFound("findComponentByCode", ...code);
|
handleModuleNotFound("findComponentByCode", ...code);
|
||||||
|
@ -407,7 +413,7 @@ export function findComponentLazy<T extends object = any>(filter: FilterFn) {
|
||||||
/**
|
/**
|
||||||
* Finds the first component that includes all the given code, lazily
|
* Finds the first component that includes all the given code, lazily
|
||||||
*/
|
*/
|
||||||
export function findComponentByCodeLazy<T extends object = any>(...code: string[]) {
|
export function findComponentByCodeLazy<T extends object = any>(...code: CodeFilter) {
|
||||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findComponentByCode", code]);
|
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findComponentByCode", code]);
|
||||||
|
|
||||||
return LazyComponent<T>(() => {
|
return LazyComponent<T>(() => {
|
||||||
|
@ -421,7 +427,7 @@ export function findComponentByCodeLazy<T extends object = any>(...code: string[
|
||||||
/**
|
/**
|
||||||
* Finds the first component that is exported by the first prop name, lazily
|
* Finds the first component that is exported by the first prop name, lazily
|
||||||
*/
|
*/
|
||||||
export function findExportedComponentLazy<T extends object = any>(...props: string[]) {
|
export function findExportedComponentLazy<T extends object = any>(...props: PropsFilter) {
|
||||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findExportedComponent", props]);
|
if (IS_REPORTER) lazyWebpackSearchHistory.push(["findExportedComponent", props]);
|
||||||
|
|
||||||
return LazyComponent<T>(() => {
|
return LazyComponent<T>(() => {
|
||||||
|
@ -445,10 +451,13 @@ export function findExportedComponentLazy<T extends object = any>(...props: stri
|
||||||
* closeModal: filters.byCode("key==")
|
* closeModal: filters.byCode("key==")
|
||||||
* })
|
* })
|
||||||
*/
|
*/
|
||||||
export const mapMangledModule = traceFunction("mapMangledModule", function mapMangledModule<S extends string>(code: string, mappers: Record<S, FilterFn>): Record<S, any> {
|
export const mapMangledModule = traceFunction("mapMangledModule", function mapMangledModule<S extends string>(code: string | RegExp | CodeFilter, mappers: Record<S, FilterFn>): Record<S, any> {
|
||||||
|
if (!Array.isArray(code)) code = [code];
|
||||||
|
code = code.map(canonicalizeMatch);
|
||||||
|
|
||||||
const exports = {} as Record<S, any>;
|
const exports = {} as Record<S, any>;
|
||||||
|
|
||||||
const id = findModuleId(code);
|
const id = findModuleId(...code);
|
||||||
if (id === null)
|
if (id === null)
|
||||||
return exports;
|
return exports;
|
||||||
|
|
||||||
|
@ -482,7 +491,7 @@ export const mapMangledModule = traceFunction("mapMangledModule", function mapMa
|
||||||
* closeModal: filters.byCode("key==")
|
* closeModal: filters.byCode("key==")
|
||||||
* })
|
* })
|
||||||
*/
|
*/
|
||||||
export function mapMangledModuleLazy<S extends string>(code: string, mappers: Record<S, FilterFn>): Record<S, any> {
|
export function mapMangledModuleLazy<S extends string>(code: string | RegExp | CodeFilter, mappers: Record<S, FilterFn>): Record<S, any> {
|
||||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["mapMangledModule", [code, mappers]]);
|
if (IS_REPORTER) lazyWebpackSearchHistory.push(["mapMangledModule", [code, mappers]]);
|
||||||
|
|
||||||
return proxyLazy(() => mapMangledModule(code, mappers));
|
return proxyLazy(() => mapMangledModule(code, mappers));
|
||||||
|
@ -497,7 +506,7 @@ export const ChunkIdsRegex = /\("([^"]+?)"\)/g;
|
||||||
* @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the first lazy chunk loading found in the module factory
|
* @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the first lazy chunk loading found in the module factory
|
||||||
* @returns A promise that resolves with a boolean whether the chunks were loaded
|
* @returns A promise that resolves with a boolean whether the chunks were loaded
|
||||||
*/
|
*/
|
||||||
export async function extractAndLoadChunks(code: string[], matcher: RegExp = DefaultExtractAndLoadChunksRegex) {
|
export async function extractAndLoadChunks(code: CodeFilter, matcher: RegExp = DefaultExtractAndLoadChunksRegex) {
|
||||||
const module = findModuleFactory(...code);
|
const module = findModuleFactory(...code);
|
||||||
if (!module) {
|
if (!module) {
|
||||||
const err = new Error("extractAndLoadChunks: Couldn't find module factory");
|
const err = new Error("extractAndLoadChunks: Couldn't find module factory");
|
||||||
|
@ -562,7 +571,7 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def
|
||||||
* @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the first lazy chunk loading found in the module factory
|
* @param matcher A RegExp that returns the chunk ids array as the first capture group and the entry point id as the second. Defaults to a matcher that captures the first lazy chunk loading found in the module factory
|
||||||
* @returns A function that returns a promise that resolves with a boolean whether the chunks were loaded, on first call
|
* @returns A function that returns a promise that resolves with a boolean whether the chunks were loaded, on first call
|
||||||
*/
|
*/
|
||||||
export function extractAndLoadChunksLazy(code: string[], matcher = DefaultExtractAndLoadChunksRegex) {
|
export function extractAndLoadChunksLazy(code: CodeFilter, matcher = DefaultExtractAndLoadChunksRegex) {
|
||||||
if (IS_REPORTER) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
|
if (IS_REPORTER) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
|
||||||
|
|
||||||
return makeLazy(() => extractAndLoadChunks(code, matcher));
|
return makeLazy(() => extractAndLoadChunks(code, matcher));
|
||||||
|
@ -572,7 +581,7 @@ export function extractAndLoadChunksLazy(code: string[], matcher = DefaultExtrac
|
||||||
* Wait for a module that matches the provided filter to be registered,
|
* Wait for a module that matches the provided filter to be registered,
|
||||||
* then call the callback with the module as the first argument
|
* then call the callback with the module as the first argument
|
||||||
*/
|
*/
|
||||||
export function waitFor(filter: string | string[] | FilterFn, callback: CallbackFn, { isIndirect = false }: { isIndirect?: boolean; } = {}) {
|
export function waitFor(filter: string | PropsFilter | FilterFn, callback: CallbackFn, { isIndirect = false }: { isIndirect?: boolean; } = {}) {
|
||||||
if (IS_REPORTER && !isIndirect) lazyWebpackSearchHistory.push(["waitFor", Array.isArray(filter) ? filter : [filter]]);
|
if (IS_REPORTER && !isIndirect) lazyWebpackSearchHistory.push(["waitFor", Array.isArray(filter) ? filter : [filter]]);
|
||||||
|
|
||||||
if (typeof filter === "string")
|
if (typeof filter === "string")
|
||||||
|
@ -593,21 +602,18 @@ export function waitFor(filter: string | string[] | FilterFn, callback: Callback
|
||||||
/**
|
/**
|
||||||
* Search modules by keyword. This searches the factory methods,
|
* Search modules by keyword. This searches the factory methods,
|
||||||
* meaning you can search all sorts of things, displayName, methodName, strings somewhere in the code, etc
|
* meaning you can search all sorts of things, displayName, methodName, strings somewhere in the code, etc
|
||||||
* @param filters One or more strings or regexes
|
* @param code One or more strings or regexes
|
||||||
* @returns Mapping of found modules
|
* @returns Mapping of found modules
|
||||||
*/
|
*/
|
||||||
export function search(...filters: Array<string | RegExp>) {
|
export function search(...code: CodeFilter) {
|
||||||
const results = {} as Record<number, Function>;
|
const results = {} as Record<number, Function>;
|
||||||
const factories = wreq.m;
|
const factories = wreq.m;
|
||||||
outer:
|
|
||||||
for (const id in factories) {
|
for (const id in factories) {
|
||||||
const factory = factories[id].original ?? factories[id];
|
const factory = factories[id].original ?? factories[id];
|
||||||
const str: string = factory.toString();
|
|
||||||
for (const filter of filters) {
|
if (stringMatches(factory.toString(), code))
|
||||||
if (typeof filter === "string" && !str.includes(filter)) continue outer;
|
results[id] = factory;
|
||||||
if (filter instanceof RegExp && !filter.test(str)) continue outer;
|
|
||||||
}
|
|
||||||
results[id] = factory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
|
|
Loading…
Reference in a new issue