Compare commits

..

12 Commits

Author SHA1 Message Date
Vendicated
8465140bc4 Bump to v1.0.9 2023-03-01 21:40:31 +01:00
Lewis Crichton
e6ccb751a0 Fix for latest Discord Update (#550)
Co-authored-by: Nuckyz <61953774+Nuckyz@users.noreply.github.com>
Co-authored-by: Vendicated <vendicated@riseup.net>
2023-03-01 21:35:08 +01:00
Marvin Witt
dfc7a15083 chore: extend description of NoDevtoolsWarning plugin (#545)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-01 18:32:58 +01:00
Vendicated
37003edae9 fix(Notifications): Correctly close errored notifications 2023-03-01 05:45:17 +01:00
Nuckyz
faa90eccd3 feat: Crash Handler (#531)
Co-authored-by: Ven <vendicated@riseup.net>
2023-03-01 05:26:13 +01:00
Cloudburst
c91b0df607 GMPolyfill: add header prop (#543) 2023-02-28 23:13:49 +01:00
Ven
f56d99e133 Update README.md 2023-02-28 22:38:02 +01:00
Ven
c690662802 Improve README 2023-02-28 22:37:09 +01:00
Vendicated
4918d699d5 Windows: Add Option to use native titlebar ~ Closes #537 2023-02-28 22:17:39 +01:00
Justice Almanzar
5ec517875e typings for defaultless settings (#512)
* typings for defaultless settings

* fix other silly typings

* type guard utils

---------

Co-authored-by: Ven <vendicated@riseup.net>
2023-02-28 06:12:35 +01:00
Vendicated
cf56ad985b oop oop oop 2023-02-28 02:43:58 +01:00
Vendicated
c09d1558f7 Add SupportHelper plugin 2023-02-28 02:40:45 +01:00
36 changed files with 408 additions and 101 deletions

View File

@ -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

View File

@ -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");

View File

@ -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",

View File

@ -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 });
}

View File

@ -89,4 +89,6 @@ export default ErrorBoundary.wrap(function NotificationComponent({
)} )}
</button> </button>
); );
}, {
onError: ({ props }) => props.onClose!()
}); });

View File

@ -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

View File

@ -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>
); );

View File

@ -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>
</> </>

View File

@ -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")}>

View File

@ -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>&mdash; Custom QuickCSS</li> <li>&mdash; Custom QuickCSS</li>

View File

@ -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">

View File

@ -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>

View File

@ -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",

View File

@ -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>;
} }

View File

@ -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 () => { "Uh Oh! Failed to render this Page." +
const wantsUpdate = confirm( " However, there is an update available that might fix it." +
"Uh Oh! Failed to render this Page." + " Would you like to update and restart now?"
" However, there is an update available that might fix it." + );
" 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!");
}
}
});
}
} }

View File

@ -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";

View File

@ -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>

View File

@ -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
View 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);
}
}
});

View File

@ -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({

View File

@ -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}

View File

@ -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 {

View File

@ -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",

View File

@ -16,11 +16,13 @@
* 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: "No F1", name: "NoF1",
description: "Disables F1 help bind.", description: "Disables F1 help bind.",
authors: [Devs.Cyn], authors: [Devs.Cyn],
patches: [ patches: [

View File

@ -16,11 +16,13 @@
* 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: "No RPC", name: "NoRPC",
description: "Disables Discord's RPC server.", description: "Disables Discord's RPC server.",
authors: [Devs.Cyn], authors: [Devs.Cyn],
target: "DESKTOP", target: "DESKTOP",

View 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
});
}
});
}
});

View File

@ -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}

View File

@ -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",

View File

@ -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} />
</> </>
)} )}

View File

@ -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
View 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;
}

View File

@ -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();
}

View File

@ -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 */

View File

@ -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!");
}
}

View File

@ -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");

View File

@ -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>>;
}; };