Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8465140bc4 | ||
|
e6ccb751a0 | ||
|
dfc7a15083 | ||
|
37003edae9 | ||
|
faa90eccd3 | ||
|
c91b0df607 | ||
|
f56d99e133 | ||
|
c690662802 | ||
|
4918d699d5 | ||
|
5ec517875e | ||
|
cf56ad985b | ||
|
c09d1558f7 |
10
README.md
10
README.md
@ -4,12 +4,14 @@ The cutest Discord client mod
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Super easy to install (one click installer)
|
- Super easy to install (Download Installer, open, click install button, done)
|
||||||
- 90+ plugins built in: [See a list](https://gist.github.com/Vendicated/8696cde7b92548064a3ae92ead84d033)
|
- 100+ plugins built in: [See a list](https://gist.github.com/Vendicated/8696cde7b92548064a3ae92ead84d033)
|
||||||
- Some highlights: SpotifyControls, Experiments, NoTrack, MessageLogger, QuickReply, Free Emotes/Stickers, CustomCommands, ShowHiddenChannels, PronounDB
|
- Some highlights: SpotifyControls, Experiments, NoTrack, MessageLogger, QuickReply, Free Emotes/Stickers, CustomCommands, ShowHiddenChannels, PronounDB
|
||||||
|
- Fairly lightweight despite the many inbuilt plugins
|
||||||
- Excellent Browser Support: Run Vencord in your Browser via extension or UserScript
|
- Excellent Browser Support: Run Vencord in your Browser via extension or UserScript
|
||||||
|
- Works on any Discord branch: Stable, Canary or PTB all work (though for the best experience I recommend stable!)
|
||||||
- Custom CSS and Themes: Inbuilt css editor with support to import any css files (including BetterDiscord themes)
|
- Custom CSS and Themes: Inbuilt css editor with support to import any css files (including BetterDiscord themes)
|
||||||
- Works in all Electron versions (Confirmed working on versions 13-23)
|
- Privacy friendly, blocks Discord analytics & crash reporting out of the box and has no telemetry
|
||||||
- Maintained very actively, broken plugins are usually fixed within 12 hours
|
- Maintained very actively, broken plugins are usually fixed within 12 hours
|
||||||
|
|
||||||
## Installing / Uninstalling
|
## Installing / Uninstalling
|
||||||
@ -20,7 +22,7 @@ The cutest Discord client mod
|
|||||||
|
|
||||||
[![Get it on the Firefox Webstore](https://blog.mozilla.org/addons/files/2015/11/get-the-addon.png)](https://addons.mozilla.org/en-GB/firefox/addon/vencord-web/) [![Get it on the Chrome Webstore](https://storage.googleapis.com/web-dev-uploads/image/WlD8wC6g8khYWPJUsQceQkhXSlv1/UV4C4ybeBTsZt43U4xis.png)](https://chrome.google.com/webstore/detail/vencord-web/cbghhgpcnddeihccjmnadmkaejncjndb)
|
[![Get it on the Firefox Webstore](https://blog.mozilla.org/addons/files/2015/11/get-the-addon.png)](https://addons.mozilla.org/en-GB/firefox/addon/vencord-web/) [![Get it on the Chrome Webstore](https://storage.googleapis.com/web-dev-uploads/image/WlD8wC6g8khYWPJUsQceQkhXSlv1/UV4C4ybeBTsZt43U4xis.png)](https://chrome.google.com/webstore/detail/vencord-web/cbghhgpcnddeihccjmnadmkaejncjndb)
|
||||||
|
|
||||||
Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that QuickCSS and plugins making use of external resources will not work with the UserScript.
|
Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that the CSS Editor, Themes loaded from remote sources and co. will not work in the UserScript. Use the extension if you need any of those
|
||||||
|
|
||||||
## Building from Source
|
## Building from Source
|
||||||
|
|
||||||
|
@ -92,6 +92,7 @@ function GM_fetch(url, opt) {
|
|||||||
resp.arrayBuffer = () => blobTo("arrayBuffer", blob);
|
resp.arrayBuffer = () => blobTo("arrayBuffer", blob);
|
||||||
resp.text = () => blobTo("text", blob);
|
resp.text = () => blobTo("text", blob);
|
||||||
resp.json = async () => JSON.parse(await blobTo("text", blob));
|
resp.json = async () => JSON.parse(await blobTo("text", blob));
|
||||||
|
resp.headers = new Headers(parseHeaders(resp.responseHeaders));
|
||||||
resolve(resp);
|
resolve(resp);
|
||||||
};
|
};
|
||||||
options.ontimeout = () => reject("fetch timeout");
|
options.ontimeout = () => reject("fetch timeout");
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.0.8",
|
"version": "1.0.9",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
|
@ -95,3 +95,12 @@ async function init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
if (!IS_WEB && Settings.winNativeTitleBar && navigator.platform.toLowerCase().startsWith("win")) {
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
document.head.append(Object.assign(document.createElement("style"), {
|
||||||
|
id: "vencord-native-titlebar-style",
|
||||||
|
textContent: "[class*=titleBar-]{display: none!important}"
|
||||||
|
}));
|
||||||
|
}, { once: true });
|
||||||
|
}
|
||||||
|
@ -89,4 +89,6 @@ export default ErrorBoundary.wrap(function NotificationComponent({
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
|
}, {
|
||||||
|
onError: ({ props }) => props.onClose!()
|
||||||
});
|
});
|
||||||
|
@ -34,6 +34,7 @@ export interface Settings {
|
|||||||
frameless: boolean;
|
frameless: boolean;
|
||||||
transparent: boolean;
|
transparent: boolean;
|
||||||
winCtrlQ: boolean;
|
winCtrlQ: boolean;
|
||||||
|
winNativeTitleBar: boolean;
|
||||||
plugins: {
|
plugins: {
|
||||||
[plugin: string]: {
|
[plugin: string]: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -57,6 +58,7 @@ const DefaultSettings: Settings = {
|
|||||||
frameless: false,
|
frameless: false,
|
||||||
transparent: false,
|
transparent: false,
|
||||||
winCtrlQ: false,
|
winCtrlQ: false,
|
||||||
|
winNativeTitleBar: false,
|
||||||
plugins: {},
|
plugins: {},
|
||||||
|
|
||||||
notifications: {
|
notifications: {
|
||||||
@ -90,7 +92,7 @@ function makeProxy(settings: any, root = settings, path = ""): Settings {
|
|||||||
// Return empty for plugins with no settings
|
// Return empty for plugins with no settings
|
||||||
if (path === "plugins" && p in plugins)
|
if (path === "plugins" && p in plugins)
|
||||||
return target[p] = makeProxy({
|
return target[p] = makeProxy({
|
||||||
enabled: plugins[p].required ?? false
|
enabled: plugins[p].required ?? plugins[p].enabledByDefault ?? false
|
||||||
}, root, `plugins.${p}`);
|
}, root, `plugins.${p}`);
|
||||||
|
|
||||||
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
|
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
|
||||||
|
@ -23,15 +23,18 @@ import { React } from "@webpack/common";
|
|||||||
|
|
||||||
import { ErrorCard } from "./ErrorCard";
|
import { ErrorCard } from "./ErrorCard";
|
||||||
|
|
||||||
interface Props {
|
interface Props<T = any> {
|
||||||
/** Render nothing if an error occurs */
|
/** Render nothing if an error occurs */
|
||||||
noop?: boolean;
|
noop?: boolean;
|
||||||
/** Fallback component to render if an error occurs */
|
/** Fallback component to render if an error occurs */
|
||||||
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; }>>;
|
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; }>>;
|
||||||
/** called when an error occurs */
|
/** called when an error occurs. The props property is only available if using .wrap */
|
||||||
onError?(error: Error, errorInfo: React.ErrorInfo): void;
|
onError?(data: { error: Error, errorInfo: React.ErrorInfo, props: T; }): void;
|
||||||
/** Custom error message */
|
/** Custom error message */
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|
||||||
|
/** The props passed to the wrapped component. Only used by wrap */
|
||||||
|
wrappedProps?: T;
|
||||||
}
|
}
|
||||||
|
|
||||||
const color = "#e78284";
|
const color = "#e78284";
|
||||||
@ -66,7 +69,7 @@ const ErrorBoundary = LazyComponent(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||||
this.props.onError?.(error, errorInfo);
|
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
||||||
logger.error("A component threw an Error\n", error);
|
logger.error("A component threw an Error\n", error);
|
||||||
logger.error("Component Stack", errorInfo.componentStack);
|
logger.error("Component Stack", errorInfo.componentStack);
|
||||||
}
|
}
|
||||||
@ -102,11 +105,11 @@ const ErrorBoundary = LazyComponent(() => {
|
|||||||
};
|
};
|
||||||
}) as
|
}) as
|
||||||
React.ComponentType<React.PropsWithChildren<Props>> & {
|
React.ComponentType<React.PropsWithChildren<Props>> & {
|
||||||
wrap<T extends object = any>(Component: React.ComponentType<T>, errorBoundaryProps?: Props): React.ComponentType<T>;
|
wrap<T extends object = any>(Component: React.ComponentType<T>, errorBoundaryProps?: Omit<Props<T>, "wrappedProps">): React.ComponentType<T>;
|
||||||
};
|
};
|
||||||
|
|
||||||
ErrorBoundary.wrap = (Component, errorBoundaryProps) => props => (
|
ErrorBoundary.wrap = (Component, errorBoundaryProps) => props => (
|
||||||
<ErrorBoundary {...errorBoundaryProps}>
|
<ErrorBoundary {...errorBoundaryProps} wrappedProps={props}>
|
||||||
<Component {...props} />
|
<Component {...props} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
|
@ -17,10 +17,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { debounce } from "@utils/debounce";
|
import { debounce } from "@utils/debounce";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
import { makeCodeblock } from "@utils/misc";
|
import { makeCodeblock } from "@utils/misc";
|
||||||
import { canonicalizeMatch, canonicalizeReplace, ReplaceFn } from "@utils/patches";
|
import { canonicalizeMatch, canonicalizeReplace, ReplaceFn } from "@utils/patches";
|
||||||
import { search } from "@webpack";
|
import { search } from "@webpack";
|
||||||
import { Button, Clipboard, Forms, Margins, Parser, React, Switch, Text, TextInput } from "@webpack/common";
|
import { Button, Clipboard, Forms, Parser, React, Switch, Text, TextInput } from "@webpack/common";
|
||||||
|
|
||||||
import { CheckedTextInput } from "./CheckedTextInput";
|
import { CheckedTextInput } from "./CheckedTextInput";
|
||||||
import ErrorBoundary from "./ErrorBoundary";
|
import ErrorBoundary from "./ErrorBoundary";
|
||||||
@ -128,7 +129,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{!!diff?.length && (
|
{!!diff?.length && (
|
||||||
<Button className={Margins.marginTop20} onClick={() => {
|
<Button className={Margins.top20} onClick={() => {
|
||||||
try {
|
try {
|
||||||
Function(patchedCode.replace(/^function\(/, "function patchedModule("));
|
Function(patchedCode.replace(/^function\(/, "function patchedModule("));
|
||||||
setCompileResult([true, "Compiled successfully"]);
|
setCompileResult([true, "Compiled successfully"]);
|
||||||
@ -202,7 +203,7 @@ function ReplacementInput({ replacement, setReplacement, replacementError }) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Switch
|
<Switch
|
||||||
className={Margins.marginTop8}
|
className={Margins.top8}
|
||||||
value={isFunc}
|
value={isFunc}
|
||||||
onChange={setIsFunc}
|
onChange={setIsFunc}
|
||||||
note="'replacement' will be evaled if this is toggled"
|
note="'replacement' will be evaled if this is toggled"
|
||||||
@ -256,7 +257,7 @@ function PatchHelper() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Text variant="heading-md/normal" tag="h2" className={Margins.marginBottom8}>Patch Helper</Text>
|
<Text variant="heading-md/normal" tag="h2" className={Margins.bottom8}>Patch Helper</Text>
|
||||||
<Forms.FormTitle>find</Forms.FormTitle>
|
<Forms.FormTitle>find</Forms.FormTitle>
|
||||||
<TextInput
|
<TextInput
|
||||||
type="text"
|
type="text"
|
||||||
@ -296,7 +297,7 @@ function PatchHelper() {
|
|||||||
|
|
||||||
{!!(find && match && replacement) && (
|
{!!(find && match && replacement) && (
|
||||||
<>
|
<>
|
||||||
<Forms.FormTitle className={Margins.marginTop20}>Code</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
|
||||||
<div style={{ userSelect: "text" }}>{Parser.parse(makeCodeblock(code, "ts"))}</div>
|
<div style={{ userSelect: "text" }}>{Parser.parse(makeCodeblock(code, "ts"))}</div>
|
||||||
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
|
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
|
||||||
</>
|
</>
|
||||||
|
@ -30,11 +30,12 @@ import PluginModal from "@components/PluginSettings/PluginModal";
|
|||||||
import { Switch } from "@components/Switch";
|
import { Switch } from "@components/Switch";
|
||||||
import { ChangeList } from "@utils/ChangeList";
|
import { ChangeList } from "@utils/ChangeList";
|
||||||
import Logger from "@utils/Logger";
|
import Logger from "@utils/Logger";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
import { classes, LazyComponent, useAwaiter } from "@utils/misc";
|
import { classes, LazyComponent, useAwaiter } from "@utils/misc";
|
||||||
import { openModalLazy } from "@utils/modal";
|
import { openModalLazy } from "@utils/modal";
|
||||||
import { Plugin } from "@utils/types";
|
import { Plugin } from "@utils/types";
|
||||||
import { findByCode, findByPropsLazy } from "@webpack";
|
import { findByCode, findByPropsLazy } from "@webpack";
|
||||||
import { Alerts, Button, Card, Forms, Margins, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
|
import { Alerts, Button, Card, Forms, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
@ -296,15 +297,15 @@ export default ErrorBoundary.wrap(function PluginSettings() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection className={Margins.marginTop16}>
|
<Forms.FormSection className={Margins.top16}>
|
||||||
<ReloadRequiredCard required={changes.hasChanges} />
|
<ReloadRequiredCard required={changes.hasChanges} />
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}>
|
<Forms.FormTitle tag="h5" className={classes(Margins.top20, Margins.bottom8)}>
|
||||||
Filters
|
Filters
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
|
|
||||||
<div className={cl("filter-controls")}>
|
<div className={cl("filter-controls")}>
|
||||||
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} className={Margins.marginBottom20} />
|
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} className={Margins.bottom20} />
|
||||||
<div className={InputStyles.inputWrapper}>
|
<div className={InputStyles.inputWrapper}>
|
||||||
<Select
|
<Select
|
||||||
className={InputStyles.inputDefault}
|
className={InputStyles.inputDefault}
|
||||||
@ -321,15 +322,15 @@ export default ErrorBoundary.wrap(function PluginSettings() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Forms.FormTitle className={Margins.marginTop20}>Plugins</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle>
|
||||||
|
|
||||||
<div className={cl("grid")}>
|
<div className={cl("grid")}>
|
||||||
{plugins}
|
{plugins}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Forms.FormDivider className={Margins.marginTop20} />
|
<Forms.FormDivider className={Margins.top20} />
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}>
|
<Forms.FormTitle tag="h5" className={classes(Margins.top20, Margins.bottom8)}>
|
||||||
Required Plugins
|
Required Plugins
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
<div className={cl("grid")}>
|
<div className={cl("grid")}>
|
||||||
|
@ -18,25 +18,26 @@
|
|||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync";
|
import { downloadSettingsBackup, uploadSettingsBackup } from "@utils/settingsSync";
|
||||||
import { Button, Card, Forms, Margins, Text } from "@webpack/common";
|
import { Button, Card, Forms, Text } from "@webpack/common";
|
||||||
|
|
||||||
function BackupRestoreTab() {
|
function BackupRestoreTab() {
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection title="Settings Sync" className={Margins.marginTop16}>
|
<Forms.FormSection title="Settings Sync" className={Margins.top16}>
|
||||||
<Card className={classes("vc-settings-card", "vc-backup-restore-card")}>
|
<Card className={classes("vc-settings-card", "vc-backup-restore-card")}>
|
||||||
<Flex flexDirection="column">
|
<Flex flexDirection="column">
|
||||||
<strong>Warning</strong>
|
<strong>Warning</strong>
|
||||||
<span>Importing a settings file will overwrite your current settings.</span>
|
<span>Importing a settings file will overwrite your current settings.</span>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
<Text variant="text-md/normal" className={Margins.marginBottom8}>
|
<Text variant="text-md/normal" className={Margins.bottom8}>
|
||||||
You can import and export your Vencord settings as a JSON file.
|
You can import and export your Vencord settings as a JSON file.
|
||||||
This allows you to easily transfer your settings to another device,
|
This allows you to easily transfer your settings to another device,
|
||||||
or recover your settings after reinstalling Vencord or Discord.
|
or recover your settings after reinstalling Vencord or Discord.
|
||||||
</Text>
|
</Text>
|
||||||
<Text variant="text-md/normal" className={Margins.marginBottom8}>
|
<Text variant="text-md/normal" className={Margins.bottom8}>
|
||||||
Settings Export contains:
|
Settings Export contains:
|
||||||
<ul>
|
<ul>
|
||||||
<li>— Custom QuickCSS</li>
|
<li>— Custom QuickCSS</li>
|
||||||
|
@ -19,9 +19,10 @@
|
|||||||
import { useSettings } from "@api/settings";
|
import { useSettings } from "@api/settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
import { useAwaiter } from "@utils/misc";
|
import { useAwaiter } from "@utils/misc";
|
||||||
import { findLazy } from "@webpack";
|
import { findLazy } from "@webpack";
|
||||||
import { Card, Forms, Margins, React, TextArea } from "@webpack/common";
|
import { Card, Forms, React, TextArea } from "@webpack/common";
|
||||||
|
|
||||||
const TextAreaProps = findLazy(m => typeof m.textarea === "string");
|
const TextAreaProps = findLazy(m => typeof m.textarea === "string");
|
||||||
|
|
||||||
@ -51,7 +52,7 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Forms.FormTitle className={Margins.marginTop20} tag="h5">Validator</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle>
|
||||||
<Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText>
|
<Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText>
|
||||||
<div>
|
<div>
|
||||||
{themeLinks.map(link => (
|
{themeLinks.map(link => (
|
||||||
@ -93,7 +94,7 @@ export default ErrorBoundary.wrap(function () {
|
|||||||
<Forms.FormTitle tag="h5">Paste links to .css / .theme.css files here</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Paste links to .css / .theme.css files here</Forms.FormTitle>
|
||||||
<Forms.FormText>One link per line</Forms.FormText>
|
<Forms.FormText>One link per line</Forms.FormText>
|
||||||
<Forms.FormText>Make sure to use the raw links or github.io links!</Forms.FormText>
|
<Forms.FormText>Make sure to use the raw links or github.io links!</Forms.FormText>
|
||||||
<Forms.FormDivider className={Margins.marginTop8 + " " + Margins.marginBottom8} />
|
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} />
|
||||||
<Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle>
|
||||||
<div style={{ marginBottom: ".5em" }}>
|
<div style={{ marginBottom: ".5em" }}>
|
||||||
<Link style={{ marginRight: ".5em" }} href="https://betterdiscord.app/themes">
|
<Link style={{ marginRight: ".5em" }} href="https://betterdiscord.app/themes">
|
||||||
|
@ -22,9 +22,10 @@ import { ErrorCard } from "@components/ErrorCard";
|
|||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { handleComponentFailed } from "@components/handleComponentFailed";
|
import { handleComponentFailed } from "@components/handleComponentFailed";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
import { classes, useAwaiter } from "@utils/misc";
|
import { classes, useAwaiter } from "@utils/misc";
|
||||||
import { changes, checkForUpdates, getRepo, isNewer, rebuild, update, updateError, UpdateLogger } from "@utils/updater";
|
import { changes, checkForUpdates, getRepo, isNewer, rebuild, update, updateError, UpdateLogger } from "@utils/updater";
|
||||||
import { Alerts, Button, Card, Forms, Margins, Parser, React, Switch, Toasts } from "@webpack/common";
|
import { Alerts, Button, Card, Forms, Parser, React, Switch, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
|
|
||||||
@ -109,14 +110,14 @@ function Updatable(props: CommonProps) {
|
|||||||
</ErrorCard>
|
</ErrorCard>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Forms.FormText className={Margins.marginBottom8}>
|
<Forms.FormText className={Margins.bottom8}>
|
||||||
{isOutdated ? `There are ${updates.length} Updates` : "Up to Date!"}
|
{isOutdated ? `There are ${updates.length} Updates` : "Up to Date!"}
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isOutdated && <Changes updates={updates} {...props} />}
|
{isOutdated && <Changes updates={updates} {...props} />}
|
||||||
|
|
||||||
<Flex className={classes(Margins.marginBottom8, Margins.marginTop8)}>
|
<Flex className={classes(Margins.bottom8, Margins.top8)}>
|
||||||
{isOutdated && <Button
|
{isOutdated && <Button
|
||||||
size={Button.Sizes.SMALL}
|
size={Button.Sizes.SMALL}
|
||||||
disabled={isUpdating || isChecking}
|
disabled={isUpdating || isChecking}
|
||||||
@ -175,7 +176,7 @@ function Updatable(props: CommonProps) {
|
|||||||
function Newer(props: CommonProps) {
|
function Newer(props: CommonProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Forms.FormText className={Margins.marginBottom8}>
|
<Forms.FormText className={Margins.bottom8}>
|
||||||
Your local copy has more recent commits. Please stash or reset them.
|
Your local copy has more recent commits. Please stash or reset them.
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<Changes {...props} updates={changes} />
|
<Changes {...props} updates={changes} />
|
||||||
@ -199,7 +200,7 @@ function Updater() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection className={Margins.marginTop16}>
|
<Forms.FormSection className={Margins.top16}>
|
||||||
<Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle>
|
||||||
<Switch
|
<Switch
|
||||||
value={settings.notifyAboutUpdates}
|
value={settings.notifyAboutUpdates}
|
||||||
@ -225,7 +226,7 @@ function Updater() {
|
|||||||
</Link>
|
</Link>
|
||||||
)} (<HashLink hash={gitHash} repo={repo} disabled={repoPending} />)</Forms.FormText>
|
)} (<HashLink hash={gitHash} repo={repo} disabled={repoPending} />)</Forms.FormText>
|
||||||
|
|
||||||
<Forms.FormDivider className={Margins.marginTop8 + " " + Margins.marginBottom8} />
|
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} />
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5">Updates</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Updates</Forms.FormTitle>
|
||||||
|
|
||||||
|
@ -63,11 +63,15 @@ function VencordSettings() {
|
|||||||
title: "Enable React Developer Tools",
|
title: "Enable React Developer Tools",
|
||||||
note: "Requires a full restart"
|
note: "Requires a full restart"
|
||||||
},
|
},
|
||||||
!IS_WEB && !isWindows && {
|
!IS_WEB && (!isWindows ? {
|
||||||
key: "frameless",
|
key: "frameless",
|
||||||
title: "Disable the window frame",
|
title: "Disable the window frame",
|
||||||
note: "Requires a full restart"
|
note: "Requires a full restart"
|
||||||
},
|
} : {
|
||||||
|
key: "winNativeTitleBar",
|
||||||
|
title: "Use Windows' native title bar instead of Discord's custom one",
|
||||||
|
note: "Requires a full restart"
|
||||||
|
}),
|
||||||
!IS_WEB && {
|
!IS_WEB && {
|
||||||
key: "transparent",
|
key: "transparent",
|
||||||
title: "Enable window transparency",
|
title: "Enable window transparency",
|
||||||
|
@ -20,6 +20,7 @@ import "./settingsStyles.css";
|
|||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { handleComponentFailed } from "@components/handleComponentFailed";
|
||||||
import { findByCodeLazy } from "@webpack";
|
import { findByCodeLazy } from "@webpack";
|
||||||
import { Forms, SettingsRouter, Text } from "@webpack/common";
|
import { Forms, SettingsRouter, Text } from "@webpack/common";
|
||||||
|
|
||||||
@ -61,8 +62,8 @@ function Settings(props: SettingsProps) {
|
|||||||
<Text variant="heading-md/normal" tag="h2">Vencord Settings</Text>
|
<Text variant="heading-md/normal" tag="h2">Vencord Settings</Text>
|
||||||
|
|
||||||
<TabBar
|
<TabBar
|
||||||
type={TabBar.Types.TOP}
|
type="top"
|
||||||
look={TabBar.Looks.BRAND}
|
look="brand"
|
||||||
className={cl("tab-bar")}
|
className={cl("tab-bar")}
|
||||||
selectedItem={tab}
|
selectedItem={tab}
|
||||||
onItemSelect={SettingsRouter.open}
|
onItemSelect={SettingsRouter.open}
|
||||||
@ -83,7 +84,7 @@ function Settings(props: SettingsProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function (props: SettingsProps) {
|
export default function (props: SettingsProps) {
|
||||||
return <ErrorBoundary>
|
return <ErrorBoundary onError={handleComponentFailed}>
|
||||||
<Settings tab={props.tab} />
|
<Settings tab={props.tab} />
|
||||||
</ErrorBoundary>;
|
</ErrorBoundary>;
|
||||||
}
|
}
|
||||||
|
@ -16,29 +16,12 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isOutdated, rebuild, update } from "@utils/updater";
|
import { maybePromptToUpdate } from "@utils/updater";
|
||||||
|
|
||||||
export async function handleComponentFailed() {
|
export function handleComponentFailed() {
|
||||||
if (isOutdated) {
|
maybePromptToUpdate(
|
||||||
setImmediate(async () => {
|
|
||||||
const wantsUpdate = confirm(
|
|
||||||
"Uh Oh! Failed to render this Page." +
|
"Uh Oh! Failed to render this Page." +
|
||||||
" However, there is an update available that might fix it." +
|
" However, there is an update available that might fix it." +
|
||||||
" Would you like to update and restart now?"
|
" Would you like to update and restart now?"
|
||||||
);
|
);
|
||||||
if (wantsUpdate) {
|
|
||||||
try {
|
|
||||||
await update();
|
|
||||||
await rebuild();
|
|
||||||
if (IS_WEB)
|
|
||||||
location.reload();
|
|
||||||
else
|
|
||||||
DiscordNative.app.relaunch();
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
alert("That also failed :( Try updating or reinstalling with the installer!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,10 @@ if (!process.argv.includes("--vanilla")) {
|
|||||||
options.webPreferences.sandbox = false;
|
options.webPreferences.sandbox = false;
|
||||||
if (settings.frameless) {
|
if (settings.frameless) {
|
||||||
options.frame = false;
|
options.frame = false;
|
||||||
|
} else if (process.platform === "win32" && settings.winNativeTitleBar) {
|
||||||
|
delete options.frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.transparent) {
|
if (settings.transparent) {
|
||||||
options.transparent = true;
|
options.transparent = true;
|
||||||
options.backgroundColor = "#00000000";
|
options.backgroundColor = "#00000000";
|
||||||
|
@ -24,9 +24,10 @@ import { Heart } from "@components/Heart";
|
|||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import IpcEvents from "@utils/IpcEvents";
|
import IpcEvents from "@utils/IpcEvents";
|
||||||
import Logger from "@utils/Logger";
|
import Logger from "@utils/Logger";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
import { closeModal, Modals, openModal } from "@utils/modal";
|
import { closeModal, Modals, openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Forms, Margins } from "@webpack/common";
|
import { Forms } from "@webpack/common";
|
||||||
|
|
||||||
const CONTRIBUTOR_BADGE = "https://media.discordapp.net/stickers/1026517526106087454.webp";
|
const CONTRIBUTOR_BADGE = "https://media.discordapp.net/stickers/1026517526106087454.webp";
|
||||||
|
|
||||||
@ -150,7 +151,7 @@ export default definePlugin({
|
|||||||
<Forms.FormText>
|
<Forms.FormText>
|
||||||
This Badge is a special perk for Vencord Donors
|
This Badge is a special perk for Vencord Donors
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<Forms.FormText className={Margins.marginTop20}>
|
<Forms.FormText className={Margins.top20}>
|
||||||
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</div>
|
</div>
|
||||||
|
@ -59,7 +59,7 @@ export default definePlugin({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
wp: Webpack,
|
wp: Vencord.Webpack,
|
||||||
wpc: Webpack.wreq.c,
|
wpc: Webpack.wreq.c,
|
||||||
wreq: Webpack.wreq,
|
wreq: Webpack.wreq,
|
||||||
wpsearch: search,
|
wpsearch: search,
|
||||||
|
134
src/plugins/crashHandler.ts
Normal file
134
src/plugins/crashHandler.ts
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
/*
|
||||||
|
* 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 { showNotification } from "@api/Notifications";
|
||||||
|
import { definePluginSettings } from "@api/settings";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import Logger from "@utils/Logger";
|
||||||
|
import { closeAllModals } from "@utils/modal";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { maybePromptToUpdate } from "@utils/updater";
|
||||||
|
import { FluxDispatcher, NavigationRouter } from "@webpack/common";
|
||||||
|
import type { ReactElement } from "react";
|
||||||
|
|
||||||
|
const CrashHandlerLogger = new Logger("CrashHandler");
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
attemptToPreventCrashes: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Whether to attempt to prevent Discord crashes.",
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
attemptToNavigateToHome: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Whether to attempt to navigate to the home when preventing Discord crashes.",
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "CrashHandler",
|
||||||
|
description: "Utility plugin for handling and possibly recovering from Crashes without a restart",
|
||||||
|
authors: [Devs.Nuckyz],
|
||||||
|
enabledByDefault: true,
|
||||||
|
|
||||||
|
popAllModals: undefined as (() => void) | undefined,
|
||||||
|
|
||||||
|
settings,
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: ".Messages.ERRORS_UNEXPECTED_CRASH",
|
||||||
|
replacement: {
|
||||||
|
match: /(?=this\.setState\()/,
|
||||||
|
replace: "$self.handleCrash(this)||"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: 'dispatch({type:"MODAL_POP_ALL"})',
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=(?<popAll>\i)=function\(\){\(0,\i\.\i\)\(\);\i\.\i\.dispatch\({type:"MODAL_POP_ALL"}\).+};)/,
|
||||||
|
replace: "$self.popAllModals=$<popAll>;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
handleCrash(_this: ReactElement & { forceUpdate: () => void; }) {
|
||||||
|
try {
|
||||||
|
maybePromptToUpdate("Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?", true);
|
||||||
|
|
||||||
|
if (settings.store.attemptToPreventCrashes) {
|
||||||
|
this.handlePreventCrash(_this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
} catch (err) {
|
||||||
|
CrashHandlerLogger.error("Failed to handle crash", err);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
handlePreventCrash(_this: ReactElement & { forceUpdate: () => void; }) {
|
||||||
|
try {
|
||||||
|
showNotification({
|
||||||
|
color: "#eed202",
|
||||||
|
title: "Discord has crashed!",
|
||||||
|
body: "Attempting to recover...",
|
||||||
|
});
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
try {
|
||||||
|
FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" });
|
||||||
|
} catch (err) {
|
||||||
|
CrashHandlerLogger.debug("Failed to close open context menu.", err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.popAllModals?.();
|
||||||
|
} catch (err) {
|
||||||
|
CrashHandlerLogger.debug("Failed to close old modals.", err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
closeAllModals();
|
||||||
|
} catch (err) {
|
||||||
|
CrashHandlerLogger.debug("Failed to close all open modals.", err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
FluxDispatcher.dispatch({ type: "USER_PROFILE_MODAL_CLOSE" });
|
||||||
|
} catch (err) {
|
||||||
|
CrashHandlerLogger.debug("Failed to close user popout.", err);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
FluxDispatcher.dispatch({ type: "LAYER_POP_ALL" });
|
||||||
|
} catch (err) {
|
||||||
|
CrashHandlerLogger.debug("Failed to pop all layers.", err);
|
||||||
|
}
|
||||||
|
if (settings.store.attemptToNavigateToHome) {
|
||||||
|
try {
|
||||||
|
NavigationRouter.transitionTo("/channels/@me");
|
||||||
|
} catch (err) {
|
||||||
|
CrashHandlerLogger.debug("Failed to navigate to home", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
_this.forceUpdate();
|
||||||
|
} catch (err) {
|
||||||
|
CrashHandlerLogger.debug("Failed to update crash handler component.", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
@ -19,6 +19,7 @@
|
|||||||
import { definePluginSettings } from "@api/settings";
|
import { definePluginSettings } from "@api/settings";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { isTruthy } from "@utils/guards";
|
||||||
import { useAwaiter } from "@utils/misc";
|
import { useAwaiter } from "@utils/misc";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
|
import { filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
|
||||||
@ -56,11 +57,11 @@ interface ActivityAssets {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface Activity {
|
interface Activity {
|
||||||
state: string;
|
state?: string;
|
||||||
details?: string;
|
details?: string;
|
||||||
timestamps?: {
|
timestamps?: {
|
||||||
start?: Number;
|
start?: number;
|
||||||
end?: Number;
|
end?: number;
|
||||||
};
|
};
|
||||||
assets?: ActivityAssets;
|
assets?: ActivityAssets;
|
||||||
buttons?: Array<string>;
|
buttons?: Array<string>;
|
||||||
@ -70,7 +71,7 @@ interface Activity {
|
|||||||
button_urls?: Array<string>;
|
button_urls?: Array<string>;
|
||||||
};
|
};
|
||||||
type: ActivityType;
|
type: ActivityType;
|
||||||
flags: Number;
|
flags: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ActivityType {
|
enum ActivityType {
|
||||||
@ -93,13 +94,13 @@ const numOpt = (description: string) => ({
|
|||||||
onChange: setRpc
|
onChange: setRpc
|
||||||
}) as const;
|
}) as const;
|
||||||
|
|
||||||
const choice = (label: string, value: any, _default?: Boolean) => ({
|
const choice = (label: string, value: any, _default?: boolean) => ({
|
||||||
label,
|
label,
|
||||||
value,
|
value,
|
||||||
default: _default
|
default: _default
|
||||||
}) as const;
|
}) as const;
|
||||||
|
|
||||||
const choiceOpt = (description: string, options) => ({
|
const choiceOpt = <T,>(description: string, options: T) => ({
|
||||||
type: OptionType.SELECT,
|
type: OptionType.SELECT,
|
||||||
description,
|
description,
|
||||||
onChange: setRpc,
|
onChange: setRpc,
|
||||||
@ -173,13 +174,13 @@ async function createActivity(): Promise<Activity | undefined> {
|
|||||||
activity.buttons = [
|
activity.buttons = [
|
||||||
buttonOneText,
|
buttonOneText,
|
||||||
buttonTwoText
|
buttonTwoText
|
||||||
].filter(Boolean);
|
].filter(isTruthy);
|
||||||
|
|
||||||
activity.metadata = {
|
activity.metadata = {
|
||||||
button_urls: [
|
button_urls: [
|
||||||
buttonOneURL,
|
buttonOneURL,
|
||||||
buttonTwoURL
|
buttonTwoURL
|
||||||
].filter(Boolean)
|
].filter(isTruthy)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,12 +207,10 @@ async function createActivity(): Promise<Activity | undefined> {
|
|||||||
delete activity[k];
|
delete activity[k];
|
||||||
}
|
}
|
||||||
|
|
||||||
// WHAT DO YOU WANT FROM ME
|
|
||||||
// eslint-disable-next-line consistent-return
|
|
||||||
return activity;
|
return activity;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setRpc(disable?: Boolean) {
|
async function setRpc(disable?: boolean) {
|
||||||
const activity: Activity | undefined = await createActivity();
|
const activity: Activity | undefined = await createActivity();
|
||||||
|
|
||||||
FluxDispatcher.dispatch({
|
FluxDispatcher.dispatch({
|
||||||
|
@ -20,11 +20,12 @@ import { migratePluginSettings, Settings } from "@api/settings";
|
|||||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
import { CheckedTextInput } from "@components/CheckedTextInput";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import Logger from "@utils/Logger";
|
import Logger from "@utils/Logger";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
import { makeLazy } from "@utils/misc";
|
import { makeLazy } from "@utils/misc";
|
||||||
import { ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
import { ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||||
import { Forms, GuildStore, Margins, Menu, PermissionStore, React, Toasts, Tooltip, UserStore } from "@webpack/common";
|
import { Forms, GuildStore, Menu, PermissionStore, React, Toasts, Tooltip, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
const MANAGE_EMOJIS_AND_STICKERS = 1n << 30n;
|
const MANAGE_EMOJIS_AND_STICKERS = 1n << 30n;
|
||||||
|
|
||||||
@ -96,7 +97,7 @@ function CloneModal({ id, name: emojiName, isAnimated }: { id: string; name: str
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Forms.FormTitle className={Margins.marginTop20}>Custom Name</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.top20}>Custom Name</Forms.FormTitle>
|
||||||
<CheckedTextInput
|
<CheckedTextInput
|
||||||
value={name}
|
value={name}
|
||||||
onChange={setName}
|
onChange={setName}
|
||||||
|
@ -34,7 +34,7 @@ interface Activity {
|
|||||||
state: string;
|
state: string;
|
||||||
details?: string;
|
details?: string;
|
||||||
timestamps?: {
|
timestamps?: {
|
||||||
start?: Number;
|
start?: number;
|
||||||
};
|
};
|
||||||
assets?: ActivityAssets;
|
assets?: ActivityAssets;
|
||||||
buttons?: Array<string>;
|
buttons?: Array<string>;
|
||||||
@ -43,8 +43,8 @@ interface Activity {
|
|||||||
metadata?: {
|
metadata?: {
|
||||||
button_urls?: Array<string>;
|
button_urls?: Array<string>;
|
||||||
};
|
};
|
||||||
type: Number;
|
type: number;
|
||||||
flags: Number;
|
flags: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TrackData {
|
interface TrackData {
|
||||||
|
@ -24,7 +24,7 @@ migratePluginSettings("NoDevtoolsWarning", "STFU");
|
|||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "NoDevtoolsWarning",
|
name: "NoDevtoolsWarning",
|
||||||
description: "Disables the 'HOLD UP' banner in the console",
|
description: "Disables the 'HOLD UP' banner in the console. As a side effect, also prevents Discord from hiding your token, which prevents random logouts.",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
patches: [{
|
patches: [{
|
||||||
find: "setDevtoolsCallbacks",
|
find: "setDevtoolsCallbacks",
|
||||||
|
@ -16,9 +16,11 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { migratePluginSettings } from "@api/settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
migratePluginSettings("NoF1", "No F1");
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "NoF1",
|
name: "NoF1",
|
||||||
description: "Disables F1 help bind.",
|
description: "Disables F1 help bind.",
|
||||||
|
@ -16,9 +16,11 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { migratePluginSettings } from "@api/settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
migratePluginSettings("NoRPC", "No RPC");
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "NoRPC",
|
name: "NoRPC",
|
||||||
description: "Disables Discord's RPC server.",
|
description: "Disables Discord's RPC server.",
|
||||||
|
91
src/plugins/supportHelper.tsx
Normal file
91
src/plugins/supportHelper.tsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* 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 { DataStore } from "@api/index";
|
||||||
|
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
||||||
|
import { makeCodeblock } from "@utils/misc";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
import { isOutdated } from "@utils/updater";
|
||||||
|
import { Alerts, FluxDispatcher, Forms, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
|
import gitHash from "~git-hash";
|
||||||
|
import plugins from "~plugins";
|
||||||
|
|
||||||
|
import settings from "./settings";
|
||||||
|
|
||||||
|
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "SupportHelper",
|
||||||
|
required: true,
|
||||||
|
description: "Helps me provide support to you",
|
||||||
|
authors: [Devs.Ven],
|
||||||
|
|
||||||
|
commands: [{
|
||||||
|
name: "vencord-debug",
|
||||||
|
description: "Send Vencord Debug info",
|
||||||
|
predicate: ctx => ctx.channel.id === SUPPORT_CHANNEL_ID,
|
||||||
|
execute() {
|
||||||
|
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
|
||||||
|
|
||||||
|
const debugInfo = `
|
||||||
|
**Vencord Debug Info**
|
||||||
|
|
||||||
|
> Discord Branch: ${RELEASE_CHANNEL}
|
||||||
|
> Client: ${typeof DiscordNative === "undefined" ? window.armcord ? "Armcord" : `Web (${navigator.userAgent})` : `Desktop (Electron v${settings.electronVersion})`}
|
||||||
|
> Platform: ${window.navigator.platform}
|
||||||
|
> Vencord Version: ${gitHash}${settings.additionalInfo}
|
||||||
|
> Outdated: ${isOutdated}
|
||||||
|
> Enabled Plugins:
|
||||||
|
${makeCodeblock(Object.keys(plugins).filter(Vencord.Plugins.isPluginEnabled).join(", "))}
|
||||||
|
`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: debugInfo.trim()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
|
||||||
|
rememberDismiss() {
|
||||||
|
DataStore.set(REMEMBER_DISMISS_KEY, gitHash);
|
||||||
|
},
|
||||||
|
|
||||||
|
start() {
|
||||||
|
FluxDispatcher.subscribe("CHANNEL_SELECT", async ({ channelId }) => {
|
||||||
|
if (channelId !== SUPPORT_CHANNEL_ID) return;
|
||||||
|
|
||||||
|
const myId = BigInt(UserStore.getCurrentUser().id);
|
||||||
|
if (Object.values(Devs).some(d => d.id === myId)) return;
|
||||||
|
|
||||||
|
if (isOutdated && gitHash !== await DataStore.get(REMEMBER_DISMISS_KEY)) {
|
||||||
|
Alerts.show({
|
||||||
|
title: "Hold on!",
|
||||||
|
body: <div>
|
||||||
|
<Forms.FormText>You are using an outdated version of Vencord! Chances are, your issue is already fixed.</Forms.FormText>
|
||||||
|
<Forms.FormText>
|
||||||
|
Please first update using the Updater Page in Settings, or use the VencordInstaller (Update Vencord Button)
|
||||||
|
to do so, in case you can't access the Updater page.
|
||||||
|
</Forms.FormText>
|
||||||
|
</div>,
|
||||||
|
onCancel: this.rememberDismiss,
|
||||||
|
onConfirm: this.rememberDismiss
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
@ -105,7 +105,7 @@ export default definePlugin({
|
|||||||
}}>
|
}}>
|
||||||
{settings.store.showAvatars && <div style={{ marginTop: "4px" }}>
|
{settings.store.showAvatars && <div style={{ marginTop: "4px" }}>
|
||||||
<Avatar
|
<Avatar
|
||||||
size={Avatar.Sizes.SIZE_16}
|
size="SIZE_16"
|
||||||
src={user.getAvatarURL(guildId, 128)} />
|
src={user.getAvatarURL(guildId, 128)} />
|
||||||
</div>}
|
</div>}
|
||||||
{GuildMemberStore.getNick(guildId!, user.id) || !guildId && RelationshipStore.getNickname(user.id) || user.username}
|
{GuildMemberStore.getNick(guildId!, user.id) || !guildId && RelationshipStore.getNickname(user.id) || user.username}
|
||||||
|
@ -20,10 +20,11 @@ import { Settings } from "@api/settings";
|
|||||||
import { ErrorCard } from "@components/ErrorCard";
|
import { ErrorCard } from "@components/ErrorCard";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import Logger from "@utils/Logger";
|
import Logger from "@utils/Logger";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
import { wordsToTitle } from "@utils/text";
|
import { wordsToTitle } from "@utils/text";
|
||||||
import definePlugin, { OptionType, PluginOptionsItem } from "@utils/types";
|
import definePlugin, { OptionType, PluginOptionsItem } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Button, ChannelStore, FluxDispatcher, Forms, Margins, SelectedChannelStore, useMemo, UserStore } from "@webpack/common";
|
import { Button, ChannelStore, FluxDispatcher, Forms, SelectedChannelStore, useMemo, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
interface VoiceState {
|
interface VoiceState {
|
||||||
userId: string;
|
userId: string;
|
||||||
@ -304,7 +305,7 @@ export default definePlugin({
|
|||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
{hasEnglishVoices && (
|
{hasEnglishVoices && (
|
||||||
<>
|
<>
|
||||||
<Forms.FormTitle className={Margins.marginTop20} tag="h3">Play Example Sounds</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.top20} tag="h3">Play Example Sounds</Forms.FormTitle>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "grid",
|
display: "grid",
|
||||||
|
@ -20,10 +20,11 @@ import { addButton, removeButton } from "@api/MessagePopover";
|
|||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
import { copyWithToast } from "@utils/misc";
|
import { copyWithToast } from "@utils/misc";
|
||||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Button, ChannelStore, Forms, Margins, Parser, Text } from "@webpack/common";
|
import { Button, ChannelStore, Forms, Parser, Text } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
|
|
||||||
@ -98,7 +99,7 @@ function openViewRawModal(msg: Message) {
|
|||||||
<>
|
<>
|
||||||
<Forms.FormTitle tag="h5">Content</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Content</Forms.FormTitle>
|
||||||
<CodeBlock content={msg.content} lang="" />
|
<CodeBlock content={msg.content} lang="" />
|
||||||
<Forms.FormDivider className={Margins.marginBottom20} />
|
<Forms.FormDivider className={Margins.bottom20} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import gitRemote from "~git-remote";
|
|||||||
export const WEBPACK_CHUNK = "webpackChunkdiscord_app";
|
export const WEBPACK_CHUNK = "webpackChunkdiscord_app";
|
||||||
export const REACT_GLOBAL = "Vencord.Webpack.Common.React";
|
export const REACT_GLOBAL = "Vencord.Webpack.Common.React";
|
||||||
export const VENCORD_USER_AGENT = `Vencord/${gitHash}${gitRemote ? ` (https://github.com/${gitRemote})` : ""}`;
|
export const VENCORD_USER_AGENT = `Vencord/${gitHash}${gitRemote ? ` (https://github.com/${gitRemote})` : ""}`;
|
||||||
|
export const SUPPORT_CHANNEL_ID = "1026515880080842772";
|
||||||
|
|
||||||
// Add yourself here if you made a plugin
|
// Add yourself here if you made a plugin
|
||||||
export const Devs = /* #__PURE__*/ Object.freeze({
|
export const Devs = /* #__PURE__*/ Object.freeze({
|
||||||
@ -200,5 +201,9 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||||||
lewisakura: {
|
lewisakura: {
|
||||||
name: "lewisakura",
|
name: "lewisakura",
|
||||||
id: 96269247411400704n
|
id: 96269247411400704n
|
||||||
|
},
|
||||||
|
cloudburst: {
|
||||||
|
name: "cloudburst",
|
||||||
|
id: 892128204150685769n
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
25
src/utils/guards.ts
Normal file
25
src/utils/guards.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function isTruthy<T>(item: T): item is Exclude<T, 0 | "" | false | null | undefined> {
|
||||||
|
return Boolean(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isNonNullish<T>(item: T): item is Exclude<T, null | undefined> {
|
||||||
|
return item != null;
|
||||||
|
}
|
@ -117,6 +117,7 @@ const ModalAPI = mapMangledModuleLazy("onCloseRequest:null!=", {
|
|||||||
openModal: filters.byCode("onCloseRequest:null!="),
|
openModal: filters.byCode("onCloseRequest:null!="),
|
||||||
closeModal: filters.byCode("onCloseCallback&&"),
|
closeModal: filters.byCode("onCloseCallback&&"),
|
||||||
openModalLazy: m => m?.length === 1 && filters.byCode(".apply(this,arguments)")(m),
|
openModalLazy: m => m?.length === 1 && filters.byCode(".apply(this,arguments)")(m),
|
||||||
|
closeAllModals: filters.byCode(".value.key,")
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -142,3 +143,10 @@ export function openModal(render: RenderFunction, options?: ModalOptions, contex
|
|||||||
export function closeModal(modalKey: string, contextKey?: string): void {
|
export function closeModal(modalKey: string, contextKey?: string): void {
|
||||||
return ModalAPI.closeModal(modalKey, contextKey);
|
return ModalAPI.closeModal(modalKey, contextKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close all open modals
|
||||||
|
*/
|
||||||
|
export function closeAllModals(): void {
|
||||||
|
return ModalAPI.closeAllModals();
|
||||||
|
}
|
||||||
|
@ -75,6 +75,10 @@ export interface PluginDef {
|
|||||||
* Whether this plugin is required and forcefully enabled
|
* Whether this plugin is required and forcefully enabled
|
||||||
*/
|
*/
|
||||||
required?: boolean;
|
required?: boolean;
|
||||||
|
/**
|
||||||
|
* Whether this plugin should be enabled by default, but can be disabled
|
||||||
|
*/
|
||||||
|
enabledByDefault?: boolean;
|
||||||
/**
|
/**
|
||||||
* Set this if your plugin only works on Browser or Desktop, not both
|
* Set this if your plugin only works on Browser or Desktop, not both
|
||||||
*/
|
*/
|
||||||
@ -229,9 +233,12 @@ type PluginSettingType<O extends PluginSettingDef> = O extends PluginSettingStri
|
|||||||
O extends PluginSettingSliderDef ? number :
|
O extends PluginSettingSliderDef ? number :
|
||||||
O extends PluginSettingComponentDef ? any :
|
O extends PluginSettingComponentDef ? any :
|
||||||
never;
|
never;
|
||||||
|
type PluginSettingDefaultType<O extends PluginSettingDef> = O extends PluginSettingSelectDef ? (
|
||||||
|
O["options"] extends { default?: boolean; }[] ? O["options"][number]["value"] : undefined
|
||||||
|
) : O extends { default: infer T; } ? T : undefined;
|
||||||
|
|
||||||
type SettingsStore<D extends SettingsDefinition> = {
|
type SettingsStore<D extends SettingsDefinition> = {
|
||||||
[K in keyof D]: PluginSettingType<D[K]>;
|
[K in keyof D]: PluginSettingType<D[K]> | PluginSettingDefaultType<D[K]>;
|
||||||
};
|
};
|
||||||
|
|
||||||
/** An instance of defined plugin settings */
|
/** An instance of defined plugin settings */
|
||||||
|
@ -77,3 +77,25 @@ export async function rebuild() {
|
|||||||
return oldHashes["patcher.js"] !== newHashes["patcher.js"] ||
|
return oldHashes["patcher.js"] !== newHashes["patcher.js"] ||
|
||||||
oldHashes["preload.js"] !== newHashes["preload.js"];
|
oldHashes["preload.js"] !== newHashes["preload.js"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function maybePromptToUpdate(confirmMessage: string, checkForDev = false) {
|
||||||
|
if (IS_WEB) return;
|
||||||
|
if (checkForDev && IS_DEV) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const isOutdated = await checkForUpdates();
|
||||||
|
if (isOutdated) {
|
||||||
|
const wantsUpdate = confirm(confirmMessage);
|
||||||
|
if (wantsUpdate && isNewer) return alert("Your local copy has more recent commits. Please stash or reset them.");
|
||||||
|
if (wantsUpdate) {
|
||||||
|
await update();
|
||||||
|
const needFullRestart = await rebuild();
|
||||||
|
if (needFullRestart) DiscordNative.app.relaunch();
|
||||||
|
else location.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
UpdateLogger.error(err);
|
||||||
|
alert("That also failed :( Try updating or re-installing with the installer!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -53,8 +53,4 @@ export const Slider = waitForComponent<t.Slider>("Slider", filters.byCode("close
|
|||||||
export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);
|
export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]);
|
||||||
|
|
||||||
export const ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent") as Record<string, string>;
|
export const ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent") as Record<string, string>;
|
||||||
/**
|
|
||||||
* @deprecated Use @utils/margins instead
|
|
||||||
*/
|
|
||||||
export const Margins: t.Margins = findByPropsLazy("marginTop20");
|
|
||||||
export const ButtonLooks: t.ButtonLooks = findByPropsLazy("BLANK", "FILLED", "INVERTED");
|
export const ButtonLooks: t.ButtonLooks = findByPropsLazy("BLANK", "FILLED", "INVERTED");
|
||||||
|
3
src/webpack/common/types/components.d.ts
vendored
3
src/webpack/common/types/components.d.ts
vendored
@ -322,7 +322,4 @@ export type Flex = ComponentType<PropsWithChildren<any>> & {
|
|||||||
Direction: Record<"VERTICAL" | "HORIZONTAL" | "HORIZONTAL_REVERSE", string>;
|
Direction: Record<"VERTICAL" | "HORIZONTAL" | "HORIZONTAL_REVERSE", string>;
|
||||||
Justify: Record<"START" | "END" | "CENTER" | "BETWEEN" | "AROUND", string>;
|
Justify: Record<"START" | "END" | "CENTER" | "BETWEEN" | "AROUND", string>;
|
||||||
Wrap: Record<"NO_WRAP" | "WRAP" | "WRAP_REVERSE", string>;
|
Wrap: Record<"NO_WRAP" | "WRAP" | "WRAP_REVERSE", string>;
|
||||||
|
|
||||||
Content: ComponentType<PropsWithChildren<any>>;
|
|
||||||
Sidebar: ComponentType<PropsWithChildren<any>>;
|
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user