Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
4aa7a052d0 | ||
|
f088f17a0a | ||
|
a55c758b0e | ||
|
f092f434fe | ||
|
2e6dfaa879 | ||
|
96dc2e12d0 | ||
|
d931790ed0 | ||
|
6b26c12bfa | ||
|
5bb08bdb64 | ||
|
405be7ef13 | ||
|
a7e2fb48ba | ||
|
ae80749dd8 | ||
|
8c47b7080d | ||
|
8378638ee4 | ||
|
7c563471f6 | ||
|
29382d2781 | ||
|
6226672ee8 | ||
|
5b5ee82f27 | ||
|
62f74f5917 | ||
|
265c7a18a7 |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Clean up obsolete files
|
- name: Clean up obsolete files
|
||||||
run: |
|
run: |
|
||||||
rm -rf dist/extension* Vencord.user.css
|
rm -rf dist/extension* Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
|
||||||
|
|
||||||
- name: Get some values needed for the release
|
- name: Get some values needed for the release
|
||||||
id: release_values
|
id: release_values
|
||||||
|
2
.github/workflows/reportBrokenPlugins.yml
vendored
2
.github/workflows/reportBrokenPlugins.yml
vendored
@ -50,7 +50,7 @@ jobs:
|
|||||||
export CHROMIUM_BIN=$(which chromium-browser)
|
export CHROMIUM_BIN=$(which chromium-browser)
|
||||||
export USE_CANARY=true
|
export USE_CANARY=true
|
||||||
|
|
||||||
esbuild test/generateReport.ts > dist/report.mjs
|
esbuild scripts/generateReport.ts > dist/report.mjs
|
||||||
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
|
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
|
||||||
env:
|
env:
|
||||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||||
|
@ -6,7 +6,7 @@ The cutest Discord client mod
|
|||||||
|
|
||||||
- Super easy to install (Download Installer, open, click install button, done)
|
- Super easy to install (Download Installer, open, click install button, done)
|
||||||
- 100+ 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, GameActivityToggle, Experiments, NoTrack, MessageLogger, QuickReply, Free Emotes/Stickers, CustomCommands, ShowHiddenChannels, PronounDB
|
||||||
- Fairly lightweight despite the many inbuilt plugins
|
- 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!)
|
- Works on any Discord branch: Stable, Canary or PTB all work (though for the best experience I recommend stable!)
|
||||||
|
BIN
browser/icon.png
BIN
browser/icon.png
Binary file not shown.
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 1.1 KiB |
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.1.4",
|
"version": "1.1.5",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
@ -34,12 +34,12 @@
|
|||||||
"@vap/core": "0.0.12",
|
"@vap/core": "0.0.12",
|
||||||
"@vap/shiki": "0.10.3",
|
"@vap/shiki": "0.10.3",
|
||||||
"fflate": "^0.7.4",
|
"fflate": "^0.7.4",
|
||||||
"nanoid": "^4.0.2"
|
"nanoid": "^4.0.2",
|
||||||
|
"virtual-merge": "^1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/diff": "^5.0.2",
|
"@types/diff": "^5.0.2",
|
||||||
"@types/lodash": "^4.14.191",
|
"@types/lodash": "^4.14.191",
|
||||||
"@types/nanoid": "^3.0.0",
|
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
"@types/react": "^18.0.27",
|
"@types/react": "^18.0.27",
|
||||||
"@types/react-dom": "^18.0.10",
|
"@types/react-dom": "^18.0.10",
|
||||||
|
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@ -11,7 +11,6 @@ patchedDependencies:
|
|||||||
specifiers:
|
specifiers:
|
||||||
'@types/diff': ^5.0.2
|
'@types/diff': ^5.0.2
|
||||||
'@types/lodash': ^4.14.191
|
'@types/lodash': ^4.14.191
|
||||||
'@types/nanoid': ^3.0.0
|
|
||||||
'@types/node': ^18.11.18
|
'@types/node': ^18.11.18
|
||||||
'@types/react': ^18.0.27
|
'@types/react': ^18.0.27
|
||||||
'@types/react-dom': ^18.0.10
|
'@types/react-dom': ^18.0.10
|
||||||
@ -40,17 +39,18 @@ specifiers:
|
|||||||
tsx: ^3.12.6
|
tsx: ^3.12.6
|
||||||
type-fest: ^3.5.3
|
type-fest: ^3.5.3
|
||||||
typescript: ^4.9.4
|
typescript: ^4.9.4
|
||||||
|
virtual-merge: ^1.0.1
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vap/core': 0.0.12
|
'@vap/core': 0.0.12
|
||||||
'@vap/shiki': 0.10.3
|
'@vap/shiki': 0.10.3
|
||||||
fflate: 0.7.4
|
fflate: 0.7.4
|
||||||
nanoid: 4.0.2
|
nanoid: 4.0.2
|
||||||
|
virtual-merge: 1.0.1
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/diff': 5.0.2
|
'@types/diff': 5.0.2
|
||||||
'@types/lodash': 4.14.191
|
'@types/lodash': 4.14.191
|
||||||
'@types/nanoid': 3.0.0
|
|
||||||
'@types/node': 18.11.18
|
'@types/node': 18.11.18
|
||||||
'@types/react': 18.0.27
|
'@types/react': 18.0.27
|
||||||
'@types/react-dom': 18.0.10
|
'@types/react-dom': 18.0.10
|
||||||
@ -421,13 +421,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
|
resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/nanoid/3.0.0:
|
|
||||||
resolution: {integrity: sha512-UXitWSmXCwhDmAKe7D3hNQtQaHeHt5L8LO1CB8GF8jlYVzOv5cBWDNqiJ+oPEWrWei3i3dkZtHY/bUtd0R/uOQ==}
|
|
||||||
deprecated: This is a stub types definition. nanoid provides its own type definitions, so you do not need this installed.
|
|
||||||
dependencies:
|
|
||||||
nanoid: 4.0.2
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@types/node/18.11.18:
|
/@types/node/18.11.18:
|
||||||
resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==}
|
resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -2260,6 +2253,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==}
|
resolution: {integrity: sha512-7ZtY5KTCNheRGfEFxnedV5zFiORN1+Y1N6zvPTnHQd8ENUvfaDBeuJDZb2bN/oXwXxu3qkTXDzy57W5vAmDTBw==}
|
||||||
engines: {node: ^14 || ^16 || >=18}
|
engines: {node: ^14 || ^16 || >=18}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/nanomatch/1.2.13:
|
/nanomatch/1.2.13:
|
||||||
resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
|
resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
|
||||||
@ -3139,6 +3133,10 @@ packages:
|
|||||||
spdx-expression-parse: 3.0.1
|
spdx-expression-parse: 3.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/virtual-merge/1.0.1:
|
||||||
|
resolution: {integrity: sha512-h7rzV6n5fZJbDu2lP4iu+IOtsZ00uqECFUxFePK1uY0pz/S5B7FNDJpmdDVfyGL7poyJECEHfTaIpJaknNkU0Q==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/vscode-oniguruma/1.7.0:
|
/vscode-oniguruma/1.7.0:
|
||||||
resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==}
|
resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
@ -48,6 +48,7 @@ const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.j
|
|||||||
const sourcemap = watch ? "inline" : "external";
|
const sourcemap = watch ? "inline" : "external";
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
// common preload
|
||||||
esbuild.build({
|
esbuild.build({
|
||||||
...nodeCommonOpts,
|
...nodeCommonOpts,
|
||||||
entryPoints: ["src/preload.ts"],
|
entryPoints: ["src/preload.ts"],
|
||||||
@ -55,12 +56,19 @@ await Promise.all([
|
|||||||
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("preload") },
|
footer: { js: "//# sourceURL=VencordPreload\n" + sourceMapFooter("preload") },
|
||||||
sourcemap,
|
sourcemap,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
// Discord Desktop main & renderer
|
||||||
esbuild.build({
|
esbuild.build({
|
||||||
...nodeCommonOpts,
|
...nodeCommonOpts,
|
||||||
entryPoints: ["src/patcher.ts"],
|
entryPoints: ["src/main/index.ts"],
|
||||||
outfile: "dist/patcher.js",
|
outfile: "dist/patcher.js",
|
||||||
footer: { js: "//# sourceURL=VencordPatcher\n" + sourceMapFooter("patcher") },
|
footer: { js: "//# sourceURL=VencordPatcher\n" + sourceMapFooter("patcher") },
|
||||||
sourcemap,
|
sourcemap,
|
||||||
|
define: {
|
||||||
|
...defines,
|
||||||
|
IS_DISCORD_DESKTOP: true,
|
||||||
|
IS_VENCORD_DESKTOP: false
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
esbuild.build({
|
esbuild.build({
|
||||||
...commonOpts,
|
...commonOpts,
|
||||||
@ -77,7 +85,43 @@ await Promise.all([
|
|||||||
],
|
],
|
||||||
define: {
|
define: {
|
||||||
...defines,
|
...defines,
|
||||||
IS_WEB: false
|
IS_WEB: false,
|
||||||
|
IS_DISCORD_DESKTOP: true,
|
||||||
|
IS_VENCORD_DESKTOP: false
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
// Vencord Desktop main & renderer
|
||||||
|
esbuild.build({
|
||||||
|
...nodeCommonOpts,
|
||||||
|
entryPoints: ["src/main/index.ts"],
|
||||||
|
outfile: "dist/vencordDesktopMain.js",
|
||||||
|
footer: { js: "//# sourceURL=VencordDesktopMain\n" + sourceMapFooter("vencordDesktopMain") },
|
||||||
|
sourcemap,
|
||||||
|
define: {
|
||||||
|
...defines,
|
||||||
|
IS_DISCORD_DESKTOP: false,
|
||||||
|
IS_VENCORD_DESKTOP: true
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
esbuild.build({
|
||||||
|
...commonOpts,
|
||||||
|
entryPoints: ["src/Vencord.ts"],
|
||||||
|
outfile: "dist/vencordDesktopRenderer.js",
|
||||||
|
format: "iife",
|
||||||
|
target: ["esnext"],
|
||||||
|
footer: { js: "//# sourceURL=VencordDesktopRenderer\n" + sourceMapFooter("vencordDesktopRenderer") },
|
||||||
|
globalName: "Vencord",
|
||||||
|
sourcemap,
|
||||||
|
plugins: [
|
||||||
|
globPlugins,
|
||||||
|
...commonOpts.plugins
|
||||||
|
],
|
||||||
|
define: {
|
||||||
|
...defines,
|
||||||
|
IS_WEB: false,
|
||||||
|
IS_DISCORD_DESKTOP: false,
|
||||||
|
IS_VENCORD_DESKTOP: true
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
]).catch(err => {
|
]).catch(err => {
|
||||||
|
@ -45,7 +45,9 @@ const commonOptions = {
|
|||||||
define: {
|
define: {
|
||||||
IS_WEB: "true",
|
IS_WEB: "true",
|
||||||
IS_STANDALONE: "true",
|
IS_STANDALONE: "true",
|
||||||
IS_DEV: JSON.stringify(watch)
|
IS_DEV: JSON.stringify(watch),
|
||||||
|
IS_DISCORD_DESKTOP: "false",
|
||||||
|
IS_VENCORD_DESKTOP: "false"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ import "./webpack/patchWebpack";
|
|||||||
import { showNotification } from "./api/Notifications";
|
import { showNotification } from "./api/Notifications";
|
||||||
import { PlainSettings, Settings } from "./api/settings";
|
import { PlainSettings, Settings } from "./api/settings";
|
||||||
import { patches, PMLogger, startAllPlugins } from "./plugins";
|
import { patches, PMLogger, startAllPlugins } from "./plugins";
|
||||||
import { checkForUpdates, rebuild, update, UpdateLogger } from "./utils/updater";
|
import { checkForUpdates, rebuild, update,UpdateLogger } from "./utils/updater";
|
||||||
import { onceReady } from "./webpack";
|
import { onceReady } from "./webpack";
|
||||||
import { SettingsRouter } from "./webpack/common";
|
import { SettingsRouter } from "./webpack/common";
|
||||||
|
|
||||||
@ -56,8 +56,12 @@ async function init() {
|
|||||||
permanent: true,
|
permanent: true,
|
||||||
noPersist: true,
|
noPersist: true,
|
||||||
onClick() {
|
onClick() {
|
||||||
if (needsFullRestart)
|
if (needsFullRestart) {
|
||||||
window.DiscordNative.app.relaunch();
|
if (IS_DISCORD_DESKTOP)
|
||||||
|
window.DiscordNative.app.relaunch();
|
||||||
|
else
|
||||||
|
window.VencordDesktop.app.relaunch();
|
||||||
|
}
|
||||||
else
|
else
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
@ -96,7 +100,7 @@ async function init() {
|
|||||||
|
|
||||||
init();
|
init();
|
||||||
|
|
||||||
if (!IS_WEB && Settings.winNativeTitleBar && navigator.platform.toLowerCase().startsWith("win")) {
|
if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && navigator.platform.toLowerCase().startsWith("win")) {
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
document.head.append(Object.assign(document.createElement("style"), {
|
document.head.append(Object.assign(document.createElement("style"), {
|
||||||
id: "vencord-native-titlebar-style",
|
id: "vencord-native-titlebar-style",
|
||||||
|
69
src/api/SettingsStore.ts
Normal file
69
src/api/SettingsStore.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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 Logger from "@utils/Logger";
|
||||||
|
import { proxyLazy } from "@utils/proxyLazy";
|
||||||
|
import { findModuleId, wreq } from "@webpack";
|
||||||
|
|
||||||
|
import { Settings } from "./settings";
|
||||||
|
|
||||||
|
interface Setting<T> {
|
||||||
|
/**
|
||||||
|
* Get the setting value
|
||||||
|
*/
|
||||||
|
getSetting(): T;
|
||||||
|
/**
|
||||||
|
* Update the setting value
|
||||||
|
* @param value The new value
|
||||||
|
*/
|
||||||
|
updateSetting(value: T | ((old: T) => T)): Promise<void>;
|
||||||
|
/**
|
||||||
|
* React hook for automatically updating components when the setting is updated
|
||||||
|
*/
|
||||||
|
useSetting(): T;
|
||||||
|
settingsStoreApiGroup: string;
|
||||||
|
settingsStoreApiName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingsStores: Array<Setting<any>> | undefined = proxyLazy(() => {
|
||||||
|
const modId = findModuleId('"textAndImages","renderSpoilers"');
|
||||||
|
if (modId == null) return new Logger("SettingsStoreAPI").error("Didn't find stores module.");
|
||||||
|
|
||||||
|
const mod = wreq(modId);
|
||||||
|
if (mod == null) return;
|
||||||
|
|
||||||
|
return Object.values(mod).filter((s: any) => s?.settingsStoreApiGroup) as any;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the store for a setting
|
||||||
|
* @param group The setting group
|
||||||
|
* @param name The name of the setting
|
||||||
|
*/
|
||||||
|
export function getSettingStore<T = any>(group: string, name: string): Setting<T> | undefined {
|
||||||
|
if (!Settings.plugins.SettingsStoreAPI.enabled) throw new Error("Cannot use SettingsStoreAPI without setting as dependency.");
|
||||||
|
|
||||||
|
return SettingsStores?.find(s => s?.settingsStoreApiGroup === group && s?.settingsStoreApiName === name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getSettingStore but lazy
|
||||||
|
*/
|
||||||
|
export function getSettingStoreLazy<T = any>(group: string, name: string) {
|
||||||
|
return proxyLazy(() => getSettingStore<T>(group, name));
|
||||||
|
}
|
@ -28,6 +28,7 @@ import * as $MessagePopover from "./MessagePopover";
|
|||||||
import * as $Notices from "./Notices";
|
import * as $Notices from "./Notices";
|
||||||
import * as $Notifications from "./Notifications";
|
import * as $Notifications from "./Notifications";
|
||||||
import * as $ServerList from "./ServerList";
|
import * as $ServerList from "./ServerList";
|
||||||
|
import * as $SettingsStore from "./SettingsStore";
|
||||||
import * as $Styles from "./Styles";
|
import * as $Styles from "./Styles";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,6 +86,10 @@ export const MessageDecorations = $MessageDecorations;
|
|||||||
* An API allowing you to add components to member list users, in both DM's and servers
|
* An API allowing you to add components to member list users, in both DM's and servers
|
||||||
*/
|
*/
|
||||||
export const MemberListDecorators = $MemberListDecorators;
|
export const MemberListDecorators = $MemberListDecorators;
|
||||||
|
/**
|
||||||
|
* An API allowing you to read, manipulate and automatically update components based on Discord settings
|
||||||
|
*/
|
||||||
|
export const SettingsStore = $SettingsStore;
|
||||||
/**
|
/**
|
||||||
* An API allowing you to dynamically load styles
|
* An API allowing you to dynamically load styles
|
||||||
* a
|
* a
|
||||||
|
@ -46,6 +46,7 @@ const cl = classNameFactory("vc-plugins-");
|
|||||||
const logger = new Logger("PluginSettings", "#a6d189");
|
const logger = new Logger("PluginSettings", "#a6d189");
|
||||||
|
|
||||||
const InputStyles = findByPropsLazy("inputDefault", "inputWrapper");
|
const InputStyles = findByPropsLazy("inputDefault", "inputWrapper");
|
||||||
|
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
|
||||||
|
|
||||||
const CogWheel = LazyComponent(() => findByCode("18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069"));
|
const CogWheel = LazyComponent(() => findByCode("18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069"));
|
||||||
const InfoIcon = LazyComponent(() => findByCode("4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16"));
|
const InfoIcon = LazyComponent(() => findByCode("4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16"));
|
||||||
@ -154,7 +155,7 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
|
|||||||
<Text variant="text-md/bold" className={cl("name")}>
|
<Text variant="text-md/bold" className={cl("name")}>
|
||||||
{plugin.name}{isNew && <Badge text="NEW" color="#ED4245" />}
|
{plugin.name}{isNew && <Badge text="NEW" color="#ED4245" />}
|
||||||
</Text>
|
</Text>
|
||||||
<button role="switch" onClick={() => openModal()} className={classes("button-12Fmur", cl("info-button"))}>
|
<button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}>
|
||||||
{plugin.options
|
{plugin.options
|
||||||
? <CogWheel />
|
? <CogWheel />
|
||||||
: <InfoIcon width="24" height="24" />}
|
: <InfoIcon width="24" height="24" />}
|
||||||
|
@ -24,6 +24,7 @@ import { handleComponentFailed } from "@components/handleComponentFailed";
|
|||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes, useAwaiter } from "@utils/misc";
|
import { classes, useAwaiter } from "@utils/misc";
|
||||||
|
import { relaunch } from "@utils/native";
|
||||||
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, Parser, React, Switch, Toasts } from "@webpack/common";
|
import { Alerts, Button, Card, Forms, Parser, React, Switch, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
@ -133,7 +134,7 @@ function Updatable(props: CommonProps) {
|
|||||||
cancelText: "Not now!",
|
cancelText: "Not now!",
|
||||||
onConfirm() {
|
onConfirm() {
|
||||||
if (needFullRestart)
|
if (needFullRestart)
|
||||||
window.DiscordNative.app.relaunch();
|
relaunch();
|
||||||
else
|
else
|
||||||
location.reload();
|
location.reload();
|
||||||
r();
|
r();
|
||||||
|
@ -26,6 +26,7 @@ import { ErrorCard } from "@components/ErrorCard";
|
|||||||
import IpcEvents from "@utils/IpcEvents";
|
import IpcEvents from "@utils/IpcEvents";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { identity, useAwaiter } from "@utils/misc";
|
import { identity, useAwaiter } from "@utils/misc";
|
||||||
|
import { relaunch } from "@utils/native";
|
||||||
import { Button, Card, Forms, React, Select, Slider, Switch } from "@webpack/common";
|
import { Button, Card, Forms, React, Select, Slider, Switch } from "@webpack/common";
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-");
|
const cl = classNameFactory("vc-settings-");
|
||||||
@ -100,7 +101,7 @@ function VencordSettings() {
|
|||||||
) : (
|
) : (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => window.DiscordNative.app.relaunch()}
|
onClick={relaunch}
|
||||||
size={Button.Sizes.SMALL}>
|
size={Button.Sizes.SMALL}>
|
||||||
Restart Client
|
Restart Client
|
||||||
</Button>
|
</Button>
|
||||||
@ -111,6 +112,7 @@ function VencordSettings() {
|
|||||||
Open QuickCSS File
|
Open QuickCSS File
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
// FIXME: Vencord Desktop support
|
||||||
onClick={() => window.DiscordNative.fileManager.showItemInFolder(settingsDir)}
|
onClick={() => window.DiscordNative.fileManager.showItemInFolder(settingsDir)}
|
||||||
size={Button.Sizes.SMALL}
|
size={Button.Sizes.SMALL}
|
||||||
disabled={settingsDirPending}>
|
disabled={settingsDirPending}>
|
||||||
|
@ -21,8 +21,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 { handleComponentFailed } from "@components/handleComponentFailed";
|
||||||
import { findByCodeLazy } from "@webpack";
|
import { Forms, SettingsRouter, TabBar, Text } from "@webpack/common";
|
||||||
import { Forms, SettingsRouter, Text } from "@webpack/common";
|
|
||||||
|
|
||||||
import BackupRestoreTab from "./BackupRestoreTab";
|
import BackupRestoreTab from "./BackupRestoreTab";
|
||||||
import PluginsTab from "./PluginsTab";
|
import PluginsTab from "./PluginsTab";
|
||||||
@ -32,8 +31,6 @@ import VencordSettings from "./VencordTab";
|
|||||||
|
|
||||||
const cl = classNameFactory("vc-settings-");
|
const cl = classNameFactory("vc-settings-");
|
||||||
|
|
||||||
const TabBar = findByCodeLazy('[role="tab"][aria-disabled="false"]');
|
|
||||||
|
|
||||||
interface SettingsProps {
|
interface SettingsProps {
|
||||||
tab: string;
|
tab: string;
|
||||||
}
|
}
|
||||||
|
3
src/globals.d.ts
vendored
3
src/globals.d.ts
vendored
@ -35,6 +35,8 @@ declare global {
|
|||||||
export var IS_WEB: boolean;
|
export var IS_WEB: boolean;
|
||||||
export var IS_DEV: boolean;
|
export var IS_DEV: boolean;
|
||||||
export var IS_STANDALONE: boolean;
|
export var IS_STANDALONE: boolean;
|
||||||
|
export var IS_DISCORD_DESKTOP: boolean;
|
||||||
|
export var IS_VENCORD_DESKTOP: boolean;
|
||||||
|
|
||||||
export var VencordNative: typeof import("./VencordNative").default;
|
export var VencordNative: typeof import("./VencordNative").default;
|
||||||
export var Vencord: typeof import("./Vencord");
|
export var Vencord: typeof import("./Vencord");
|
||||||
@ -54,6 +56,7 @@ declare global {
|
|||||||
* If you really must use it, mark your plugin as Desktop App only by naming it Foo.desktop.ts(x)
|
* If you really must use it, mark your plugin as Desktop App only by naming it Foo.desktop.ts(x)
|
||||||
*/
|
*/
|
||||||
export var DiscordNative: any;
|
export var DiscordNative: any;
|
||||||
|
export var VencordDesktop: any;
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
webpackChunkdiscord_app: {
|
webpackChunkdiscord_app: {
|
||||||
|
109
src/main/index.ts
Normal file
109
src/main/index.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { app, protocol, session } from "electron";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
import { getSettings } from "./ipcMain";
|
||||||
|
import { IS_VANILLA } from "./utils/constants";
|
||||||
|
import { installExt } from "./utils/extensions";
|
||||||
|
|
||||||
|
if (IS_VENCORD_DESKTOP || !IS_VANILLA) {
|
||||||
|
app.whenReady().then(() => {
|
||||||
|
// Source Maps! Maybe there's a better way but since the renderer is executed
|
||||||
|
// from a string I don't think any other form of sourcemaps would work
|
||||||
|
protocol.registerFileProtocol("vencord", ({ url: unsafeUrl }, cb) => {
|
||||||
|
let url = unsafeUrl.slice("vencord://".length);
|
||||||
|
if (url.endsWith("/")) url = url.slice(0, -1);
|
||||||
|
switch (url) {
|
||||||
|
case "renderer.js.map":
|
||||||
|
case "preload.js.map":
|
||||||
|
case "patcher.js.map": // doubt
|
||||||
|
case "main.js.map":
|
||||||
|
cb(join(__dirname, url));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cb({ statusCode: 403 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (getSettings().enableReactDevtools)
|
||||||
|
installExt("fmkadmapgofadopljbjfkapdkoienihi")
|
||||||
|
.then(() => console.info("[Vencord] Installed React Developer Tools"))
|
||||||
|
.catch(err => console.error("[Vencord] Failed to install React Developer Tools", err));
|
||||||
|
} catch { }
|
||||||
|
|
||||||
|
|
||||||
|
// Remove CSP
|
||||||
|
type PolicyResult = Record<string, string[]>;
|
||||||
|
|
||||||
|
const parsePolicy = (policy: string): PolicyResult => {
|
||||||
|
const result: PolicyResult = {};
|
||||||
|
policy.split(";").forEach(directive => {
|
||||||
|
const [directiveKey, ...directiveValue] = directive.trim().split(/\s+/g);
|
||||||
|
if (directiveKey && !Object.prototype.hasOwnProperty.call(result, directiveKey)) {
|
||||||
|
result[directiveKey] = directiveValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
const stringifyPolicy = (policy: PolicyResult): string =>
|
||||||
|
Object.entries(policy)
|
||||||
|
.filter(([, values]) => values?.length)
|
||||||
|
.map(directive => directive.flat().join(" "))
|
||||||
|
.join("; ");
|
||||||
|
|
||||||
|
function patchCsp(headers: Record<string, string[]>, header: string) {
|
||||||
|
if (header in headers) {
|
||||||
|
const csp = parsePolicy(headers[header][0]);
|
||||||
|
|
||||||
|
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
|
||||||
|
csp[directive] = ["*", "blob:", "data:", "'unsafe-inline'"];
|
||||||
|
}
|
||||||
|
// TODO: Restrict this to only imported packages with fixed version.
|
||||||
|
// Perhaps auto generate with esbuild
|
||||||
|
csp["script-src"] ??= [];
|
||||||
|
csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com");
|
||||||
|
headers[header] = [stringifyPolicy(csp)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => {
|
||||||
|
if (responseHeaders) {
|
||||||
|
if (resourceType === "mainFrame")
|
||||||
|
patchCsp(responseHeaders, "content-security-policy");
|
||||||
|
|
||||||
|
// Fix hosts that don't properly set the css content type, such as
|
||||||
|
// raw.githubusercontent.com
|
||||||
|
if (resourceType === "stylesheet")
|
||||||
|
responseHeaders["content-type"] = ["text/css"];
|
||||||
|
}
|
||||||
|
cb({ cancel: false, responseHeaders });
|
||||||
|
});
|
||||||
|
|
||||||
|
// assign a noop to onHeadersReceived to prevent other mods from adding their own incompatible ones.
|
||||||
|
// For instance, OpenAsar adds their own that doesn't fix content-type for stylesheets which makes it
|
||||||
|
// impossible to load css from github raw despite our fix above
|
||||||
|
session.defaultSession.webRequest.onHeadersReceived = () => { };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_DISCORD_DESKTOP) {
|
||||||
|
require("./patcher");
|
||||||
|
}
|
@ -28,7 +28,7 @@ import { join } from "path";
|
|||||||
|
|
||||||
import monacoHtml from "~fileContent/../components/monacoWin.html;base64";
|
import monacoHtml from "~fileContent/../components/monacoWin.html;base64";
|
||||||
|
|
||||||
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE } from "./constants";
|
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE } from "./utils/constants";
|
||||||
|
|
||||||
mkdirSync(SETTINGS_DIR, { recursive: true });
|
mkdirSync(SETTINGS_DIR, { recursive: true });
|
||||||
|
|
||||||
@ -44,6 +44,14 @@ export function readSettings() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSettings(): typeof import("@api/settings").Settings {
|
||||||
|
try {
|
||||||
|
return JSON.parse(readSettings());
|
||||||
|
} catch {
|
||||||
|
return {} as any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.OPEN_QUICKCSS, () => shell.openPath(QUICKCSS_PATH));
|
ipcMain.handle(IpcEvents.OPEN_QUICKCSS, () => shell.openPath(QUICKCSS_PATH));
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.OPEN_EXTERNAL, (_, url) => {
|
ipcMain.handle(IpcEvents.OPEN_EXTERNAL, (_, url) => {
|
@ -17,12 +17,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { onceDefined } from "@utils/onceDefined";
|
import { onceDefined } from "@utils/onceDefined";
|
||||||
import electron, { app, BrowserWindowConstructorOptions, Menu, protocol, session } from "electron";
|
import electron, { app, BrowserWindowConstructorOptions, Menu } from "electron";
|
||||||
import { dirname, join } from "path";
|
import { dirname, join } from "path";
|
||||||
|
|
||||||
import { initIpc } from "./ipcMain";
|
import { getSettings, initIpc } from "./ipcMain";
|
||||||
import { installExt } from "./ipcMain/extensions";
|
import { IS_VANILLA } from "./utils/constants";
|
||||||
import { readSettings } from "./ipcMain/index";
|
|
||||||
|
|
||||||
console.log("[Vencord] Starting up...");
|
console.log("[Vencord] Starting up...");
|
||||||
|
|
||||||
@ -41,11 +40,8 @@ require.main!.filename = join(asarPath, discordPkg.main);
|
|||||||
// @ts-ignore Untyped method? Dies from cringe
|
// @ts-ignore Untyped method? Dies from cringe
|
||||||
app.setAppPath(asarPath);
|
app.setAppPath(asarPath);
|
||||||
|
|
||||||
if (!process.argv.includes("--vanilla")) {
|
if (!IS_VANILLA) {
|
||||||
let settings: typeof import("@api/settings").Settings = {} as any;
|
const settings = getSettings();
|
||||||
try {
|
|
||||||
settings = JSON.parse(readSettings());
|
|
||||||
} catch { }
|
|
||||||
|
|
||||||
// Repatch after host updates on Windows
|
// Repatch after host updates on Windows
|
||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
@ -116,84 +112,6 @@ if (!process.argv.includes("--vanilla")) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
process.env.DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
process.env.DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
|
||||||
// Source Maps! Maybe there's a better way but since the renderer is executed
|
|
||||||
// from a string I don't think any other form of sourcemaps would work
|
|
||||||
protocol.registerFileProtocol("vencord", ({ url: unsafeUrl }, cb) => {
|
|
||||||
let url = unsafeUrl.slice("vencord://".length);
|
|
||||||
if (url.endsWith("/")) url = url.slice(0, -1);
|
|
||||||
switch (url) {
|
|
||||||
case "renderer.js.map":
|
|
||||||
case "preload.js.map":
|
|
||||||
case "patcher.js.map": // doubt
|
|
||||||
cb(join(__dirname, url));
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
cb({ statusCode: 403 });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (settings?.enableReactDevtools)
|
|
||||||
installExt("fmkadmapgofadopljbjfkapdkoienihi")
|
|
||||||
.then(() => console.info("[Vencord] Installed React Developer Tools"))
|
|
||||||
.catch(err => console.error("[Vencord] Failed to install React Developer Tools", err));
|
|
||||||
} catch { }
|
|
||||||
|
|
||||||
|
|
||||||
// Remove CSP
|
|
||||||
type PolicyResult = Record<string, string[]>;
|
|
||||||
|
|
||||||
const parsePolicy = (policy: string): PolicyResult => {
|
|
||||||
const result: PolicyResult = {};
|
|
||||||
policy.split(";").forEach(directive => {
|
|
||||||
const [directiveKey, ...directiveValue] = directive.trim().split(/\s+/g);
|
|
||||||
if (directiveKey && !Object.prototype.hasOwnProperty.call(result, directiveKey)) {
|
|
||||||
result[directiveKey] = directiveValue;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
const stringifyPolicy = (policy: PolicyResult): string =>
|
|
||||||
Object.entries(policy)
|
|
||||||
.filter(([, values]) => values?.length)
|
|
||||||
.map(directive => directive.flat().join(" "))
|
|
||||||
.join("; ");
|
|
||||||
|
|
||||||
function patchCsp(headers: Record<string, string[]>, header: string) {
|
|
||||||
if (header in headers) {
|
|
||||||
const csp = parsePolicy(headers[header][0]);
|
|
||||||
|
|
||||||
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
|
|
||||||
csp[directive] = ["*", "blob:", "data:", "'unsafe-inline'"];
|
|
||||||
}
|
|
||||||
// TODO: Restrict this to only imported packages with fixed version.
|
|
||||||
// Perhaps auto generate with esbuild
|
|
||||||
csp["script-src"] ??= [];
|
|
||||||
csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com");
|
|
||||||
headers[header] = [stringifyPolicy(csp)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => {
|
|
||||||
if (responseHeaders) {
|
|
||||||
if (resourceType === "mainFrame")
|
|
||||||
patchCsp(responseHeaders, "content-security-policy");
|
|
||||||
|
|
||||||
// Fix hosts that don't properly set the css content type, such as
|
|
||||||
// raw.githubusercontent.com
|
|
||||||
if (resourceType === "stylesheet")
|
|
||||||
responseHeaders["content-type"] = ["text/css"];
|
|
||||||
}
|
|
||||||
cb({ cancel: false, responseHeaders });
|
|
||||||
});
|
|
||||||
|
|
||||||
// assign a noop to onHeadersReceived to prevent other mods from adding their own incompatible ones.
|
|
||||||
// For instance, OpenAsar adds their own that doesn't fix content-type for stylesheets which makes it
|
|
||||||
// impossible to load css from github raw despite our fix above
|
|
||||||
session.defaultSession.webRequest.onHeadersReceived = () => { };
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
console.log("[Vencord] Running in vanilla mode. Not loading Vencord");
|
console.log("[Vencord] Running in vanilla mode. Not loading Vencord");
|
||||||
}
|
}
|
@ -25,7 +25,7 @@ import { join } from "path";
|
|||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
import gitRemote from "~git-remote";
|
import gitRemote from "~git-remote";
|
||||||
|
|
||||||
import { get } from "../simpleGet";
|
import { get } from "../utils/simpleGet";
|
||||||
import { calculateHashes, serializeErrors } from "./common";
|
import { calculateHashes, serializeErrors } from "./common";
|
||||||
|
|
||||||
const API_BASE = `https://api.github.com/repos/${gitRemote}`;
|
const API_BASE = `https://api.github.com/repos/${gitRemote}`;
|
||||||
@ -57,6 +57,13 @@ async function calculateGitChanges() {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FILES_TO_DOWNLOAD = [
|
||||||
|
IS_DISCORD_DESKTOP ? "patcher.js" : "vencordDesktopMain.js",
|
||||||
|
"preload.js",
|
||||||
|
IS_DISCORD_DESKTOP ? "renderer.js" : "vencordDesktopRenderer.js",
|
||||||
|
"renderer.css"
|
||||||
|
];
|
||||||
|
|
||||||
async function fetchUpdates() {
|
async function fetchUpdates() {
|
||||||
const release = await githubGet("/releases/latest");
|
const release = await githubGet("/releases/latest");
|
||||||
|
|
||||||
@ -66,7 +73,7 @@ async function fetchUpdates() {
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
data.assets.forEach(({ name, browser_download_url }) => {
|
data.assets.forEach(({ name, browser_download_url }) => {
|
||||||
if (["patcher.js", "preload.js", "renderer.js", "renderer.css"].some(s => name.startsWith(s))) {
|
if (FILES_TO_DOWNLOAD.some(s => name.startsWith(s))) {
|
||||||
PendingUpdates.push([name, browser_download_url]);
|
PendingUpdates.push([name, browser_download_url]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -75,8 +82,17 @@ async function fetchUpdates() {
|
|||||||
|
|
||||||
async function applyUpdates() {
|
async function applyUpdates() {
|
||||||
await Promise.all(PendingUpdates.map(
|
await Promise.all(PendingUpdates.map(
|
||||||
async ([name, data]) => writeFile(join(__dirname, name), await get(data)))
|
async ([name, data]) => writeFile(
|
||||||
);
|
join(
|
||||||
|
__dirname,
|
||||||
|
IS_VENCORD_DESKTOP
|
||||||
|
// vencordDesktopRenderer.js -> renderer.js
|
||||||
|
? name.replace(/vencordDesktop(\w)/, (_, c) => c.toLowerCase())
|
||||||
|
: name
|
||||||
|
),
|
||||||
|
await get(data)
|
||||||
|
)
|
||||||
|
));
|
||||||
PendingUpdates = [];
|
PendingUpdates = [];
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
@ -33,3 +33,5 @@ export const ALLOWED_PROTOCOLS = [
|
|||||||
"steam:",
|
"steam:",
|
||||||
"spotify:"
|
"spotify:"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
export const IS_VANILLA = /* @__PURE__ */ process.argv.includes("--vanilla");
|
@ -29,7 +29,7 @@ import { closeModal, Modals, openModal } from "@utils/modal";
|
|||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Forms } from "@webpack/common";
|
import { Forms } from "@webpack/common";
|
||||||
|
|
||||||
const CONTRIBUTOR_BADGE = "https://media.discordapp.net/stickers/1026517526106087454.webp";
|
const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/attachments/1033680203433660458/1092089947126780035/favicon.png";
|
||||||
|
|
||||||
/** List of vencord contributor IDs */
|
/** List of vencord contributor IDs */
|
||||||
const contributorIds: string[] = Object.values(Devs).map(d => d.id.toString());
|
const contributorIds: string[] = Object.values(Devs).map(d => d.id.toString());
|
||||||
|
@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { migratePluginSettings } from "@api/settings";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
|
|
||||||
// duplicate values have multiple branches with different types. Just include all to be safe
|
|
||||||
const nameMap = {
|
|
||||||
radio: "MenuRadioItem",
|
|
||||||
separator: "MenuSeparator",
|
|
||||||
checkbox: "MenuCheckboxItem",
|
|
||||||
groupstart: "MenuGroup",
|
|
||||||
|
|
||||||
control: "MenuControlItem",
|
|
||||||
compositecontrol: "MenuControlItem",
|
|
||||||
|
|
||||||
item: "MenuItem",
|
|
||||||
customitem: "MenuItem",
|
|
||||||
};
|
|
||||||
|
|
||||||
migratePluginSettings("MenuItemDeobfuscatorAPI", "MenuItemDeobfuscatorApi");
|
|
||||||
export default definePlugin({
|
|
||||||
name: "MenuItemDeobfuscatorAPI",
|
|
||||||
description: "Deobfuscates Discord's Menu Item module",
|
|
||||||
authors: [Devs.Ven],
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: '"Menu API',
|
|
||||||
replacement: {
|
|
||||||
match: /function.{0,80}type===(\i)\).{0,50}navigable:.+?Menu API/s,
|
|
||||||
replace: (m, mod) => {
|
|
||||||
let nicenNames = "";
|
|
||||||
const redefines = [] as string[];
|
|
||||||
// if (t.type === m.MenuItem)
|
|
||||||
const typeCheckRe = /\(.{1,3}\.type===(.{1,5})\)/g;
|
|
||||||
// push({type:"item"})
|
|
||||||
const pushTypeRe = /type:"(\w+)"/g;
|
|
||||||
|
|
||||||
let typeMatch: RegExpExecArray | null;
|
|
||||||
// for each if (t.type === ...)
|
|
||||||
while ((typeMatch = typeCheckRe.exec(m)) !== null) {
|
|
||||||
// extract the current menu item
|
|
||||||
const item = typeMatch[1];
|
|
||||||
// Set the starting index of the second regex to that of the first to start
|
|
||||||
// matching from after the if
|
|
||||||
pushTypeRe.lastIndex = typeCheckRe.lastIndex;
|
|
||||||
// extract the first type: "..."
|
|
||||||
const type = pushTypeRe.exec(m)?.[1];
|
|
||||||
if (type && type in nameMap) {
|
|
||||||
const name = nameMap[type];
|
|
||||||
nicenNames += `Object.defineProperty(${item},"name",{value:"${name}"});`;
|
|
||||||
redefines.push(`${name}:${item}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (redefines.length < 6) {
|
|
||||||
console.warn("[ApiMenuItemDeobfuscator] Expected to at least remap 6 items, only remapped", redefines.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge all our redefines with the actual module
|
|
||||||
return `${nicenNames}Object.assign(${mod},{${redefines.join(",")}});${m}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
|
|
38
src/plugins/apiSettingsStore.ts
Normal file
38
src/plugins/apiSettingsStore.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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 { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "SettingsStoreAPI",
|
||||||
|
description: "Patches Discord's SettingsStores to expose their group and name",
|
||||||
|
authors: [Devs.Nuckyz],
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: '"textAndImages","renderSpoilers"',
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /(?<=INFREQUENT_USER_ACTION.{0,20}),useSetting:function/,
|
||||||
|
replace: ",settingsStoreApiGroup:arguments[0],settingsStoreApiName:arguments[1]$&"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
84
src/plugins/betterFolders/FolderSideBar.tsx
Normal file
84
src/plugins/betterFolders/FolderSideBar.tsx
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
/*
|
||||||
|
* 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 { Settings } from "@api/settings";
|
||||||
|
import { classNameFactory } from "@api/Styles";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||||
|
import { i18n, React, useStateFromStores } from "@webpack/common";
|
||||||
|
|
||||||
|
const cl = classNameFactory("vc-bf-");
|
||||||
|
const classes = findByPropsLazy("sidebar", "guilds");
|
||||||
|
|
||||||
|
const Animations = findByPropsLazy("a", "animated", "useTransition");
|
||||||
|
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
|
||||||
|
const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
||||||
|
|
||||||
|
function Guilds(props: {
|
||||||
|
className: string;
|
||||||
|
bfGuildFolders: any[];
|
||||||
|
}) {
|
||||||
|
// @ts-expect-error
|
||||||
|
const res = Vencord.Plugins.plugins.BetterFolders.Guilds(props);
|
||||||
|
|
||||||
|
const scrollerProps = res.props.children?.props?.children?.[1]?.props;
|
||||||
|
if (scrollerProps?.children) {
|
||||||
|
const servers = scrollerProps.children.find(c => c?.props?.["aria-label"] === i18n.Messages.SERVERS);
|
||||||
|
if (servers) scrollerProps.children = servers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorBoundary.wrap(() => {
|
||||||
|
const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders());
|
||||||
|
const fullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext());
|
||||||
|
|
||||||
|
const guilds = document.querySelector(`.${classes.guilds}`);
|
||||||
|
|
||||||
|
const visible = !!expandedFolders.size;
|
||||||
|
const className = cl("folder-sidebar", { fullscreen });
|
||||||
|
|
||||||
|
const Sidebar = (
|
||||||
|
<Guilds
|
||||||
|
className={classes.guilds}
|
||||||
|
bfGuildFolders={Array.from(expandedFolders)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!guilds || !Settings.plugins.BetterFolders.sidebarAnim)
|
||||||
|
return visible
|
||||||
|
? <div className={className}>{Sidebar}</div>
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Animations.Transition
|
||||||
|
items={visible}
|
||||||
|
from={{ width: 0 }}
|
||||||
|
enter={{ width: guilds.getBoundingClientRect().width }}
|
||||||
|
leave={{ width: 0 }}
|
||||||
|
config={{ duration: 200 }}
|
||||||
|
>
|
||||||
|
{(style, show) => show && (
|
||||||
|
<Animations.animated.div style={style} className={className}>
|
||||||
|
{Sidebar}
|
||||||
|
</Animations.animated.div>
|
||||||
|
)}
|
||||||
|
</Animations.Transition>
|
||||||
|
);
|
||||||
|
}, { noop: true });
|
17
src/plugins/betterFolders/betterFolders.css
Normal file
17
src/plugins/betterFolders/betterFolders.css
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
.vc-bf-folder-sidebar [class*="wrapper-"] > [class*="listItem-"]:first-of-type,
|
||||||
|
.vc-bf-folder-sidebar [class*="unreadMentionsIndicator"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bf-folder-sidebar [class*="expandedFolderBackground-"] {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bf-folder-sidebar {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-bf-fullscreen {
|
||||||
|
width: 0 !important;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
177
src/plugins/betterFolders/index.ts
Normal file
177
src/plugins/betterFolders/index.ts
Normal file
@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* 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 "./betterFolders.css";
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/settings";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
|
||||||
|
import { FluxDispatcher } from "@webpack/common";
|
||||||
|
|
||||||
|
import FolderSideBar from "./FolderSideBar";
|
||||||
|
|
||||||
|
const GuildsTree = findLazy(m => m.prototype?.convertToFolder);
|
||||||
|
const GuildFolderStore = findStoreLazy("SortedGuildStore");
|
||||||
|
const ExpandedFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
||||||
|
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
sidebar: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Display servers from folder on dedicated sidebar",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
sidebarAnim: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Animate opening the folder sidebar",
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
closeAllFolders: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Close all folders when selecting a server not in a folder",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
closeAllHomeButton: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Close all folders when clicking on the home button",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
closeOthers: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Close other folders when opening a folder",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
forceOpen: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Force a folder to open when switching to a server of that folder",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "BetterFolders",
|
||||||
|
description: "Shows server folders on dedicated sidebar and adds folder related improvements",
|
||||||
|
authors: [Devs.juby],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: '("guildsnav")',
|
||||||
|
predicate: () => settings.store.sidebar,
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /(\i)\(\){return \i\(\(0,\i\.jsx\)\("div",{className:\i\(\)\.guildSeparator}\)\)}/,
|
||||||
|
replace: "$&$self.Separator=$1;"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Folder component patch
|
||||||
|
{
|
||||||
|
match: /\i\(\(function\(\i,\i,\i\){var \i=\i\.key;return.+\(\i\)},\i\)}\)\)/,
|
||||||
|
replace: "arguments[0].bfHideServers?null:$&"
|
||||||
|
},
|
||||||
|
|
||||||
|
// BEGIN Guilds component patch
|
||||||
|
{
|
||||||
|
match: /(\i)\.themeOverride,(.{15,25}\(function\(\){var \i=)(\i\.\i\.getGuildsTree\(\))/,
|
||||||
|
replace: "$1.themeOverride,bfPatch=$1.bfGuildFolders,$2bfPatch?$self.getGuildsTree(bfPatch,$3):$3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /return(\(0,\i\.jsx\))(\(\i,{)(folderNode:\i,setNodeRef:\i\.setNodeRef,draggable:!0,.+},\i\.id\));case/,
|
||||||
|
replace: "var bfHideServers=typeof bfPatch==='undefined',folder=$1$2bfHideServers,$3;return !bfHideServers&&arguments[1]?[$1($self.Separator,{}),folder]:folder;case"
|
||||||
|
},
|
||||||
|
// END
|
||||||
|
|
||||||
|
{
|
||||||
|
match: /\("guildsnav"\);return\(0,\i\.jsx\)\(.{1,6},{navigator:\i,children:\(0,\i\.jsx\)\(/,
|
||||||
|
replace: "$&$self.Guilds="
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "APPLICATION_LIBRARY,render",
|
||||||
|
predicate: () => settings.store.sidebar,
|
||||||
|
replacement: {
|
||||||
|
match: /(\(0,\i\.jsx\))\(\i\..,{className:\i\(\)\.guilds,themeOverride:\i}\)/,
|
||||||
|
replace: "$&,$1($self.FolderSideBar,{})"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: '("guildsnav")',
|
||||||
|
predicate: () => settings.store.closeAllHomeButton,
|
||||||
|
replacement: {
|
||||||
|
match: ",onClick:function(){if(!__OVERLAY__){",
|
||||||
|
replace: "$&$self.closeFolders();"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
settings,
|
||||||
|
|
||||||
|
start() {
|
||||||
|
const getGuildFolder = (id: string) => GuildFolderStore.guildFolders.find(f => f.guildIds.includes(id));
|
||||||
|
|
||||||
|
FluxDispatcher.subscribe("CHANNEL_SELECT", this.onSwitch = data => {
|
||||||
|
if (!settings.store.closeAllFolders && !settings.store.forceOpen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (this.lastGuildId !== data.guildId) {
|
||||||
|
this.lastGuildId = data.guildId;
|
||||||
|
|
||||||
|
const guildFolder = getGuildFolder(data.guildId);
|
||||||
|
if (guildFolder?.folderId) {
|
||||||
|
if (settings.store.forceOpen && !ExpandedFolderStore.isFolderExpanded(guildFolder.folderId))
|
||||||
|
FolderUtils.toggleGuildFolderExpand(guildFolder.folderId);
|
||||||
|
} else if (settings.store.closeAllFolders)
|
||||||
|
this.closeFolders();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
FluxDispatcher.subscribe("TOGGLE_GUILD_FOLDER_EXPAND", this.onToggleFolder = e => {
|
||||||
|
if (settings.store.closeOthers && !this.dispatching)
|
||||||
|
FluxDispatcher.wait(() => {
|
||||||
|
const expandedFolders = ExpandedFolderStore.getExpandedFolders();
|
||||||
|
if (expandedFolders.size > 1) {
|
||||||
|
this.dispatching = true;
|
||||||
|
|
||||||
|
for (const id of expandedFolders) if (id !== e.folderId)
|
||||||
|
FolderUtils.toggleGuildFolderExpand(id);
|
||||||
|
|
||||||
|
this.dispatching = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
FluxDispatcher.unsubscribe("CHANNEL_SELECT", this.onSwitch);
|
||||||
|
FluxDispatcher.unsubscribe("TOGGLE_GUILD_FOLDER_EXPAND", this.onToggleFolder);
|
||||||
|
},
|
||||||
|
|
||||||
|
FolderSideBar,
|
||||||
|
|
||||||
|
getGuildsTree(folders, oldTree) {
|
||||||
|
const tree = new GuildsTree();
|
||||||
|
tree.root.children = oldTree.root.children.filter(e => folders.includes(e.id));
|
||||||
|
tree.nodes = folders.map(id => oldTree.nodes[id]);
|
||||||
|
return tree;
|
||||||
|
},
|
||||||
|
|
||||||
|
closeFolders() {
|
||||||
|
for (const id of ExpandedFolderStore.getExpandedFolders())
|
||||||
|
FolderUtils.toggleGuildFolderExpand(id);
|
||||||
|
},
|
||||||
|
});
|
@ -44,7 +44,7 @@ export default definePlugin({
|
|||||||
match: /"(?:username|dot)"===\i(?!\.\i)/g,
|
match: /"(?:username|dot)"===\i(?!\.\i)/g,
|
||||||
replace: "true",
|
replace: "true",
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
options: {
|
options: {
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { relaunch } from "@utils/native";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import * as Webpack from "@webpack";
|
import * as Webpack from "@webpack";
|
||||||
import { extract, filters, findAll, search } from "@webpack";
|
import { extract, filters, findAll, search } from "@webpack";
|
||||||
@ -71,13 +72,14 @@ export default definePlugin({
|
|||||||
findAllByProps: (...props: string[]) => findAll(filters.byProps(...props)),
|
findAllByProps: (...props: string[]) => findAll(filters.byProps(...props)),
|
||||||
findByCode: newFindWrapper(filters.byCode),
|
findByCode: newFindWrapper(filters.byCode),
|
||||||
findAllByCode: (code: string) => findAll(filters.byCode(code)),
|
findAllByCode: (code: string) => findAll(filters.byCode(code)),
|
||||||
|
findStore: newFindWrapper(filters.byStoreName),
|
||||||
PluginsApi: Vencord.Plugins,
|
PluginsApi: Vencord.Plugins,
|
||||||
plugins: Vencord.Plugins.plugins,
|
plugins: Vencord.Plugins.plugins,
|
||||||
React,
|
React,
|
||||||
Settings: Vencord.Settings,
|
Settings: Vencord.Settings,
|
||||||
Api: Vencord.Api,
|
Api: Vencord.Api,
|
||||||
reload: () => location.reload(),
|
reload: () => location.reload(),
|
||||||
restart: IS_WEB ? WEB_ONLY("restart") : window.DiscordNative.app.relaunch
|
restart: IS_WEB ? WEB_ONLY("restart") : relaunch
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { ApplicationCommandOptionType, sendBotMessage } from "@api/Commands";
|
|
||||||
import { findOption } from "@api/Commands/commandHelpers";
|
|
||||||
import { ApplicationCommandInputType } from "@api/Commands/types";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
import { findByCode, findByProps } from "@webpack";
|
|
||||||
|
|
||||||
const DRAFT_TYPE = 0;
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "CorruptMp4s",
|
|
||||||
description: "Create corrupt mp4s with extremely high or negative duration",
|
|
||||||
authors: [Devs.Ven],
|
|
||||||
dependencies: ["CommandsAPI"],
|
|
||||||
commands: [{
|
|
||||||
name: "corrupt",
|
|
||||||
description: "Create a corrupt mp4 with extremely high or negative duration",
|
|
||||||
inputType: ApplicationCommandInputType.BUILT_IN,
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: "mp4",
|
|
||||||
description: "the video to corrupt",
|
|
||||||
type: ApplicationCommandOptionType.ATTACHMENT,
|
|
||||||
required: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "kind",
|
|
||||||
description: "the kind of corruption",
|
|
||||||
type: ApplicationCommandOptionType.STRING,
|
|
||||||
choices: [
|
|
||||||
{
|
|
||||||
name: "infinite",
|
|
||||||
value: "infinite",
|
|
||||||
label: "Very high duration"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "negative",
|
|
||||||
value: "negative",
|
|
||||||
label: "Negative duration"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
execute: async (args, ctx) => {
|
|
||||||
const UploadStore = findByProps("getUploads");
|
|
||||||
const upload = UploadStore.getUploads(ctx.channel.id, DRAFT_TYPE)[0];
|
|
||||||
|
|
||||||
const video = upload?.item?.file as File | undefined;
|
|
||||||
|
|
||||||
if (video?.type !== "video/mp4")
|
|
||||||
return void sendBotMessage(ctx.channel.id, {
|
|
||||||
content: "Please upload a mp4 file"
|
|
||||||
});
|
|
||||||
|
|
||||||
const corruption = findOption<string>(args, "kind", "infinite");
|
|
||||||
|
|
||||||
const buf = new Uint8Array(await video.arrayBuffer());
|
|
||||||
let found = false;
|
|
||||||
|
|
||||||
// adapted from https://github.com/GeopJr/exorcism/blob/c9a12d77ccbcb49c987b385eafae250906efc297/src/App.svelte#L41-L48
|
|
||||||
for (let i = 0; i < buf.length; i++) {
|
|
||||||
if (buf[i] === 0x6d && buf[i + 1] === 0x76 && buf[i + 2] === 0x68 && buf[i + 3] === 0x64) {
|
|
||||||
let start = i + 18;
|
|
||||||
buf[start++] = 0x00;
|
|
||||||
buf[start++] = 0x01;
|
|
||||||
buf[start++] = corruption === "negative" ? 0xff : 0x7f;
|
|
||||||
buf[start++] = 0xff;
|
|
||||||
buf[start++] = 0xff;
|
|
||||||
buf[start++] = corruption === "negative" ? 0xf0 : 0xff;
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
return void sendBotMessage(ctx.channel.id, {
|
|
||||||
content: "Could not find signature. Is this even a mp4?"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const newName = video.name.replace(/\.mp4$/i, ".corrupt.mp4");
|
|
||||||
const promptToUpload = findByCode("UPLOAD_FILE_LIMIT_ERROR");
|
|
||||||
const file = new File([buf], newName, { type: "video/mp4" });
|
|
||||||
setTimeout(() => promptToUpload([file], ctx.channel, DRAFT_TYPE), 10);
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
|
@ -161,7 +161,11 @@ function initWs(isManual = false) {
|
|||||||
return reply("Expected exactly one 'find' matches, found " + keys.length);
|
return reply("Expected exactly one 'find' matches, found " + keys.length);
|
||||||
|
|
||||||
const mod = candidates[keys[0]];
|
const mod = candidates[keys[0]];
|
||||||
let src = String(mod.original ?? mod);
|
let src = String(mod.original ?? mod).replaceAll("\n", "");
|
||||||
|
|
||||||
|
if (src.startsWith("function(")) {
|
||||||
|
src = "0," + src;
|
||||||
|
}
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
|
@ -238,7 +238,7 @@ export default definePlugin({
|
|||||||
name: "EmoteCloner",
|
name: "EmoteCloner",
|
||||||
description: "Adds a Clone context menu item to emotes to clone them your own server",
|
description: "Adds a Clone context menu item to emotes to clone them your own server",
|
||||||
authors: [Devs.Ven, Devs.Nuckyz],
|
authors: [Devs.Ven, Devs.Nuckyz],
|
||||||
dependencies: ["MenuItemDeobfuscatorAPI", "ContextMenuAPI"],
|
dependencies: ["ContextMenuAPI"],
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
addContextMenuPatch("message", messageContextMenuPatch);
|
addContextMenuPatch("message", messageContextMenuPatch);
|
||||||
|
145
src/plugins/fakeProfileThemes.tsx
Normal file
145
src/plugins/fakeProfileThemes.tsx
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// This plugin is a port from Alyxia's Vendetta plugin
|
||||||
|
import { definePluginSettings } from "@api/settings";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { Margins } from "@utils/margins";
|
||||||
|
import { copyWithToast } from "@utils/misc";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { Button, Forms } from "@webpack/common";
|
||||||
|
import { User } from "discord-types/general";
|
||||||
|
import virtualMerge from "virtual-merge";
|
||||||
|
|
||||||
|
interface UserProfile extends User {
|
||||||
|
themeColors?: Array<number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Colors {
|
||||||
|
primary: number;
|
||||||
|
accent: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
function encode(primary: number, accent: number): string {
|
||||||
|
const message = `[#${primary.toString(16).padStart(6, "0")},#${accent.toString(16).padStart(6, "0")}]`;
|
||||||
|
const padding = "";
|
||||||
|
const encoded = Array.from(message)
|
||||||
|
.map(x => x.codePointAt(0))
|
||||||
|
.filter(x => x! >= 0x20 && x! <= 0x7f)
|
||||||
|
.map(x => String.fromCodePoint(x! + 0xe0000))
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return (padding || "") + " " + encoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Courtesy of Cynthia.
|
||||||
|
function decode(bio: string): Array<number> | null {
|
||||||
|
if (bio == null) return null;
|
||||||
|
|
||||||
|
const colorString = bio.match(
|
||||||
|
/\u{e005b}\u{e0023}([\u{e0061}-\u{e0066}\u{e0041}-\u{e0046}\u{e0030}-\u{e0039}]+?)\u{e002c}\u{e0023}([\u{e0061}-\u{e0066}\u{e0041}-\u{e0046}\u{e0030}-\u{e0039}]+?)\u{e005d}/u,
|
||||||
|
);
|
||||||
|
if (colorString != null) {
|
||||||
|
const parsed = [...colorString[0]]
|
||||||
|
.map(x => String.fromCodePoint(x.codePointAt(0)! - 0xe0000))
|
||||||
|
.join("");
|
||||||
|
const colors = parsed
|
||||||
|
.substring(1, parsed.length - 1)
|
||||||
|
.split(",")
|
||||||
|
.map(x => parseInt(x.replace("#", "0x"), 16));
|
||||||
|
|
||||||
|
return colors;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
nitroFirst: {
|
||||||
|
description: "Default color source if both are present",
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
options: [
|
||||||
|
{ label: "Nitro colors", value: true, default: true },
|
||||||
|
{ label: "Fake colors", value: false },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "FakeProfileThemes",
|
||||||
|
description: "Allows profile theming by hiding the colors in your bio thanks to invisible 3y3 encoding.",
|
||||||
|
authors: [Devs.Alyxia, Devs.Remty],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "getUserProfile=",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<=getUserProfile=function\(\i\){return )(\i\[\i\])/,
|
||||||
|
replace: "$self.colorDecodeHook($1)"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
find: ".USER_SETTINGS_PROFILE_THEME_ACCENT",
|
||||||
|
replacement: {
|
||||||
|
match: /RESET_PROFILE_THEME}\)(?<=},color:(\i).+?},color:(\i).+?)/,
|
||||||
|
replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
settingsAboutComponent: () => (
|
||||||
|
<Forms.FormSection>
|
||||||
|
<Forms.FormTitle tag="h3">Usage</Forms.FormTitle>
|
||||||
|
<Forms.FormText>
|
||||||
|
After enabling this plugin, you will see custom colors in the profiles of other people using compatible plugins. <br />
|
||||||
|
To set your own colors:
|
||||||
|
<ul>
|
||||||
|
<li>• go to your profile settings</li>
|
||||||
|
<li>• choose your own colors in the Nitro preview</li>
|
||||||
|
<li>• click the "Copy 3y3" button</li>
|
||||||
|
<li>• paste the invisible text anywhere in your bio</li>
|
||||||
|
</ul><br />
|
||||||
|
<b>Please note:</b> if you are using a theme which hides nitro upsells, you should disable it temporarily to set colors.
|
||||||
|
</Forms.FormText>
|
||||||
|
</Forms.FormSection>),
|
||||||
|
settings,
|
||||||
|
colorDecodeHook(user: UserProfile) {
|
||||||
|
if (user) {
|
||||||
|
// don't replace colors if already set with nitro
|
||||||
|
if (settings.store.nitroFirst && user.themeColors) return user;
|
||||||
|
const colors = decode(user.bio);
|
||||||
|
if (colors) {
|
||||||
|
return virtualMerge(user, {
|
||||||
|
premiumType: 2,
|
||||||
|
themeColors: colors
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
},
|
||||||
|
addCopy3y3Button: ErrorBoundary.wrap(function ({ primary, accent }: Colors) {
|
||||||
|
return <Button
|
||||||
|
onClick={() => {
|
||||||
|
const colorString = encode(primary, accent);
|
||||||
|
copyWithToast(colorString);
|
||||||
|
}}
|
||||||
|
color={Button.Colors.PRIMARY}
|
||||||
|
size={Button.Sizes.XLARGE}
|
||||||
|
className={Margins.left16}
|
||||||
|
>Copy 3y3
|
||||||
|
</Button >;
|
||||||
|
}, { noop: true }),
|
||||||
|
});
|
85
src/plugins/gameActivityToggle/index.tsx
Normal file
85
src/plugins/gameActivityToggle/index.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
/*
|
||||||
|
* 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 { getSettingStoreLazy } from "@api/SettingsStore";
|
||||||
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
import { findByCodeLazy } from "@webpack";
|
||||||
|
|
||||||
|
import style from "./style.css?managed";
|
||||||
|
|
||||||
|
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame");
|
||||||
|
const Button = findByCodeLazy("Button.Sizes.NONE,disabled:");
|
||||||
|
|
||||||
|
function makeIcon(showCurrentGame?: boolean) {
|
||||||
|
return function () {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 96 960 960"
|
||||||
|
>
|
||||||
|
<path fill="currentColor" d="M182 856q-51 0-79-35.5T82 734l42-300q9-60 53.5-99T282 296h396q60 0 104.5 39t53.5 99l42 300q7 51-21 86.5T778 856q-21 0-39-7.5T706 826l-90-90H344l-90 90q-15 15-33 22.5t-39 7.5Zm498-240q17 0 28.5-11.5T720 576q0-17-11.5-28.5T680 536q-17 0-28.5 11.5T640 576q0 17 11.5 28.5T680 616Zm-80-120q17 0 28.5-11.5T640 456q0-17-11.5-28.5T600 416q-17 0-28.5 11.5T560 456q0 17 11.5 28.5T600 496ZM310 616h60v-70h70v-60h-70v-70h-60v70h-70v60h70v70Z" />
|
||||||
|
{!showCurrentGame && <line x1="920" y1="280" x2="40" y2="880" stroke="var(--status-danger)" stroke-width="80" />}
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function GameActivityToggleButton() {
|
||||||
|
const showCurrentGame = ShowCurrentGame?.useSetting();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
tooltipText="Toggle Game Activity"
|
||||||
|
icon={makeIcon(showCurrentGame)}
|
||||||
|
role="switch"
|
||||||
|
aria-checked={!showCurrentGame}
|
||||||
|
onClick={() => ShowCurrentGame?.updateSetting(old => !old)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "GameActivityToggle",
|
||||||
|
description: "Adds a button next to the mic and deafen button to toggle game activity.",
|
||||||
|
authors: [Devs.Nuckyz],
|
||||||
|
dependencies: ["SettingsStoreAPI"],
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: ".Messages.ACCOUNT_SPEAKING_WHILE_MUTED",
|
||||||
|
replacement: {
|
||||||
|
match: /this\.renderNameZone\(\).+?children:\[/,
|
||||||
|
replace: "$&$self.GameActivityToggleButton(),"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
GameActivityToggleButton: ErrorBoundary.wrap(GameActivityToggleButton, { noop: true }),
|
||||||
|
|
||||||
|
start() {
|
||||||
|
enableStyle(style);
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
disableStyle(style);
|
||||||
|
}
|
||||||
|
});
|
3
src/plugins/gameActivityToggle/style.css
Normal file
3
src/plugins/gameActivityToggle/style.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[class*="withTagAsButton"] {
|
||||||
|
min-width: 88px;
|
||||||
|
}
|
@ -76,7 +76,7 @@ export default definePlugin({
|
|||||||
name: "MessageLogger",
|
name: "MessageLogger",
|
||||||
description: "Temporarily logs deleted and edited messages.",
|
description: "Temporarily logs deleted and edited messages.",
|
||||||
authors: [Devs.rushii, Devs.Ven],
|
authors: [Devs.rushii, Devs.Ven],
|
||||||
dependencies: ["ContextMenuAPI", "MenuItemDeobfuscatorAPI"],
|
dependencies: ["ContextMenuAPI"],
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
addDeleteStyle();
|
addDeleteStyle();
|
||||||
@ -209,6 +209,11 @@ export default definePlugin({
|
|||||||
" m" +
|
" m" +
|
||||||
")" +
|
")" +
|
||||||
".update($3"
|
".update($3"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// fix up key (edit last message) attempting to edit a deleted message
|
||||||
|
match: /(?<=getLastEditableMessage=.{0,200}\.find\(\(function\((\i)\)\{)return/,
|
||||||
|
replace: "return !$1.deleted &&"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -111,7 +111,7 @@ function jumpIfOffScreen(channelId: string, messageId: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getNextMessage(isUp: boolean, isReply: boolean) {
|
function getNextMessage(isUp: boolean, isReply: boolean) {
|
||||||
let messages: Message[] = MessageStore.getMessages(SelectedChannelStore.getChannelId())._array;
|
let messages: Array<Message & { deleted?: boolean; }> = MessageStore.getMessages(SelectedChannelStore.getChannelId())._array;
|
||||||
if (!isReply) { // we are editing so only include own
|
if (!isReply) { // we are editing so only include own
|
||||||
const meId = UserStore.getCurrentUser().id;
|
const meId = UserStore.getCurrentUser().id;
|
||||||
messages = messages.filter(m => m.author.id === meId);
|
messages = messages.filter(m => m.author.id === meId);
|
||||||
@ -121,11 +121,18 @@ function getNextMessage(isUp: boolean, isReply: boolean) {
|
|||||||
? Math.min(messages.length - 1, i + 1)
|
? Math.min(messages.length - 1, i + 1)
|
||||||
: Math.max(-1, i - 1);
|
: Math.max(-1, i - 1);
|
||||||
|
|
||||||
|
const findNextNonDeleted = (i: number) => {
|
||||||
|
do {
|
||||||
|
i = mutate(i);
|
||||||
|
} while (i !== -1 && messages[messages.length - i - 1]?.deleted === true);
|
||||||
|
return i;
|
||||||
|
};
|
||||||
|
|
||||||
let i: number;
|
let i: number;
|
||||||
if (isReply)
|
if (isReply)
|
||||||
replyIdx = i = mutate(replyIdx);
|
replyIdx = i = findNextNonDeleted(replyIdx);
|
||||||
else
|
else
|
||||||
editIdx = i = mutate(editIdx);
|
editIdx = i = findNextNonDeleted(editIdx);
|
||||||
|
|
||||||
return i === - 1 ? undefined : messages[messages.length - i - 1];
|
return i === - 1 ? undefined : messages[messages.length - i - 1];
|
||||||
}
|
}
|
||||||
|
@ -62,10 +62,10 @@ export default definePlugin({
|
|||||||
renderReadAllButton: () => <ReadAllButton />,
|
renderReadAllButton: () => <ReadAllButton />,
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
addServerListElement(ServerListRenderPosition.In, this.renderReadAllButton);
|
addServerListElement(ServerListRenderPosition.Above, this.renderReadAllButton);
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
removeServerListElement(ServerListRenderPosition.In, this.renderReadAllButton);
|
removeServerListElement(ServerListRenderPosition.Above, this.renderReadAllButton);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -76,7 +76,7 @@ export default definePlugin({
|
|||||||
name: "ReverseImageSearch",
|
name: "ReverseImageSearch",
|
||||||
description: "Adds ImageSearch to image context menus",
|
description: "Adds ImageSearch to image context menus",
|
||||||
authors: [Devs.Ven, Devs.Nuckyz],
|
authors: [Devs.Ven, Devs.Nuckyz],
|
||||||
dependencies: ["MenuItemDeobfuscatorAPI", "ContextMenuAPI"],
|
dependencies: ["ContextMenuAPI"],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL",
|
find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL",
|
||||||
|
@ -168,6 +168,7 @@ export default definePlugin({
|
|||||||
get additionalInfo() {
|
get additionalInfo() {
|
||||||
if (IS_DEV) return " (Dev)";
|
if (IS_DEV) return " (Dev)";
|
||||||
if (IS_WEB) return " (Web)";
|
if (IS_WEB) return " (Web)";
|
||||||
|
if (IS_VENCORD_DESKTOP) return " (Vencord Desktop)";
|
||||||
if (IS_STANDALONE) return " (Standalone)";
|
if (IS_STANDALONE) return " (Standalone)";
|
||||||
return "";
|
return "";
|
||||||
},
|
},
|
||||||
|
@ -22,8 +22,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
|||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { debounce } from "@utils/debounce";
|
import { debounce } from "@utils/debounce";
|
||||||
import { classes, copyWithToast, LazyComponent } from "@utils/misc";
|
import { classes, copyWithToast } from "@utils/misc";
|
||||||
import { filters, find } from "@webpack";
|
|
||||||
import { ContextMenu, FluxDispatcher, Forms, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common";
|
import { ContextMenu, FluxDispatcher, Forms, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common";
|
||||||
|
|
||||||
import { SpotifyStore, Track } from "./SpotifyStore";
|
import { SpotifyStore, Track } from "./SpotifyStore";
|
||||||
@ -79,7 +78,7 @@ function CopyContextMenu({ name, path }: { name: string; path: string; }) {
|
|||||||
const openId = `spotify-open-${name}`;
|
const openId = `spotify-open-${name}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu.ContextMenu
|
<Menu.Menu
|
||||||
navId={`spotify-${name}-menu`}
|
navId={`spotify-${name}-menu`}
|
||||||
onClose={() => FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })}
|
onClose={() => FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })}
|
||||||
aria-label={`Spotify ${name} Menu`}
|
aria-label={`Spotify ${name} Menu`}
|
||||||
@ -96,7 +95,7 @@ function CopyContextMenu({ name, path }: { name: string; path: string; }) {
|
|||||||
label={`Open ${name} in Spotify`}
|
label={`Open ${name} in Spotify`}
|
||||||
action={() => SpotifyStore.openExternal(path)}
|
action={() => SpotifyStore.openExternal(path)}
|
||||||
/>
|
/>
|
||||||
</Menu.ContextMenu>
|
</Menu.Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,11 +153,6 @@ const seek = debounce((v: number) => {
|
|||||||
SpotifyStore.seek(v);
|
SpotifyStore.seek(v);
|
||||||
});
|
});
|
||||||
|
|
||||||
const Slider = LazyComponent(() => {
|
|
||||||
const filter = filters.byCode("sliderContainer");
|
|
||||||
return find(m => m.render && filter(m.render));
|
|
||||||
});
|
|
||||||
|
|
||||||
function SeekBar() {
|
function SeekBar() {
|
||||||
const { duration } = SpotifyStore.track!;
|
const { duration } = SpotifyStore.track!;
|
||||||
|
|
||||||
@ -190,7 +184,7 @@ function SeekBar() {
|
|||||||
>
|
>
|
||||||
{msToHuman(position)}
|
{msToHuman(position)}
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<Slider
|
<Menu.MenuSliderControl
|
||||||
minValue={0}
|
minValue={0}
|
||||||
maxValue={duration}
|
maxValue={duration}
|
||||||
value={position}
|
value={position}
|
||||||
@ -217,7 +211,7 @@ function AlbumContextMenu({ track }: { track: Track; }) {
|
|||||||
const volume = useStateFromStores([SpotifyStore], () => SpotifyStore.volume);
|
const volume = useStateFromStores([SpotifyStore], () => SpotifyStore.volume);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Menu.ContextMenu
|
<Menu.Menu
|
||||||
navId="spotify-album-menu"
|
navId="spotify-album-menu"
|
||||||
onClose={() => FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })}
|
onClose={() => FluxDispatcher.dispatch({ type: "CONTEXT_MENU_CLOSE" })}
|
||||||
aria-label="Spotify Album Menu"
|
aria-label="Spotify Album Menu"
|
||||||
@ -240,7 +234,7 @@ function AlbumContextMenu({ track }: { track: Track; }) {
|
|||||||
key="spotify-volume"
|
key="spotify-volume"
|
||||||
label="Volume"
|
label="Volume"
|
||||||
control={(props, ref) => (
|
control={(props, ref) => (
|
||||||
<Slider
|
<Menu.MenuSliderControl
|
||||||
{...props}
|
{...props}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
value={volume}
|
value={volume}
|
||||||
@ -250,7 +244,7 @@ function AlbumContextMenu({ track }: { track: Track; }) {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Menu.ContextMenu>
|
</Menu.Menu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,10 +366,10 @@ export function Player() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorBoundary fallback={() => (
|
<ErrorBoundary fallback={() => (
|
||||||
<>
|
<div className="vc-spotify-fallback">
|
||||||
<Forms.FormText>Failed to render Spotify Modal :(</Forms.FormText>
|
<p>Failed to render Spotify Modal :(</p>
|
||||||
<Forms.FormText>Check the console for errors</Forms.FormText>
|
<p >Check the console for errors</p>
|
||||||
</>
|
</div>
|
||||||
)}>
|
)}>
|
||||||
<div id={cl("player")}>
|
<div id={cl("player")}>
|
||||||
<Info track={track} />
|
<Info track={track} />
|
||||||
|
@ -39,7 +39,6 @@ export default definePlugin({
|
|||||||
name: "SpotifyControls",
|
name: "SpotifyControls",
|
||||||
description: "Spotify Controls",
|
description: "Spotify Controls",
|
||||||
authors: [Devs.Ven, Devs.afn, Devs.KraXen72],
|
authors: [Devs.Ven, Devs.afn, Devs.KraXen72],
|
||||||
dependencies: ["MenuItemDeobfuscatorAPI"],
|
|
||||||
options: {
|
options: {
|
||||||
hoverControls: {
|
hoverControls: {
|
||||||
description: "Show controls on hover",
|
description: "Show controls on hover",
|
||||||
|
@ -191,3 +191,8 @@
|
|||||||
.vc-spotify-time-right {
|
.vc-spotify-time-right {
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-spotify-fallback {
|
||||||
|
padding: 0.5em;
|
||||||
|
color: var(--text-normal);
|
||||||
|
}
|
||||||
|
@ -33,7 +33,7 @@ const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
|
|||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "SupportHelper",
|
name: "SupportHelper",
|
||||||
required: true,
|
required: true,
|
||||||
description: "Helps me provide support to you",
|
description: "Helps us provide support to you",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
|
|
||||||
commands: [{
|
commands: [{
|
||||||
|
@ -35,8 +35,6 @@ export default definePlugin({
|
|||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
description: "Makes Avatars/Banners in user profiles clickable, and adds Guild Context Menu Entries to View Banner/Icon.",
|
description: "Makes Avatars/Banners in user profiles clickable, and adds Guild Context Menu Entries to View Banner/Icon.",
|
||||||
|
|
||||||
dependencies: ["MenuItemDeobfuscatorAPI"],
|
|
||||||
|
|
||||||
openImage(url: string) {
|
openImage(url: string) {
|
||||||
const u = new URL(url);
|
const u = new URL(url);
|
||||||
u.searchParams.set("size", "512");
|
u.searchParams.set("size", "512");
|
||||||
|
@ -54,7 +54,9 @@ if (location.protocol !== "data:") {
|
|||||||
document.getElementById("vencord-css-core")!.textContent = readFileSync(rendererCss, "utf-8");
|
document.getElementById("vencord-css-core")!.textContent = readFileSync(rendererCss, "utf-8");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
require(process.env.DISCORD_PRELOAD!);
|
|
||||||
|
if (process.env.DISCORD_PRELOAD)
|
||||||
|
require(process.env.DISCORD_PRELOAD);
|
||||||
} else {
|
} else {
|
||||||
// Monaco Popout
|
// Monaco Popout
|
||||||
contextBridge.exposeInMainWorld("setCss", debounce(s => VencordNative.ipc.invoke(IpcEvents.SET_QUICK_CSS, s)));
|
contextBridge.exposeInMainWorld("setCss", debounce(s => VencordNative.ipc.invoke(IpcEvents.SET_QUICK_CSS, s)));
|
||||||
|
@ -226,6 +226,18 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||||||
name: "TheKodeToad",
|
name: "TheKodeToad",
|
||||||
id: 706152404072267788n
|
id: 706152404072267788n
|
||||||
},
|
},
|
||||||
|
juby: {
|
||||||
|
name: "Juby210",
|
||||||
|
id: 324622488644616195n
|
||||||
|
},
|
||||||
|
Alyxia: {
|
||||||
|
name: "Alyxia Sother",
|
||||||
|
id: 952185386350829688n
|
||||||
|
},
|
||||||
|
Remty: {
|
||||||
|
name: "Remty",
|
||||||
|
id: 335055032204656642n
|
||||||
|
},
|
||||||
skyevg: {
|
skyevg: {
|
||||||
name: "skyevg",
|
name: "skyevg",
|
||||||
id: 1090310844283363348n
|
id: 1090310844283363348n
|
||||||
|
24
src/utils/native.ts
Normal file
24
src/utils/native.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* 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 relaunch() {
|
||||||
|
if (IS_DISCORD_DESKTOP)
|
||||||
|
window.DiscordNative.app.relaunch();
|
||||||
|
else
|
||||||
|
window.VencordDesktop.app.relaunch();
|
||||||
|
}
|
@ -47,7 +47,9 @@ export async function downloadSettingsBackup() {
|
|||||||
const backup = await exportSettings();
|
const backup = await exportSettings();
|
||||||
const data = new TextEncoder().encode(backup);
|
const data = new TextEncoder().encode(backup);
|
||||||
|
|
||||||
if (IS_WEB) {
|
if (IS_DISCORD_DESKTOP) {
|
||||||
|
DiscordNative.fileManager.saveWithDialog(data, filename);
|
||||||
|
} else {
|
||||||
const file = new File([data], filename, { type: "application/json" });
|
const file = new File([data], filename, { type: "application/json" });
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
a.href = URL.createObjectURL(file);
|
a.href = URL.createObjectURL(file);
|
||||||
@ -59,8 +61,6 @@ export async function downloadSettingsBackup() {
|
|||||||
URL.revokeObjectURL(a.href);
|
URL.revokeObjectURL(a.href);
|
||||||
document.body.removeChild(a);
|
document.body.removeChild(a);
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
DiscordNative.fileManager.saveWithDialog(data, filename);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +77,24 @@ const toastFailure = (err: any) => Toasts.show({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export async function uploadSettingsBackup(showToast = true): Promise<void> {
|
export async function uploadSettingsBackup(showToast = true): Promise<void> {
|
||||||
if (IS_WEB) {
|
if (IS_DISCORD_DESKTOP) {
|
||||||
|
const [file] = await DiscordNative.fileManager.openFiles({
|
||||||
|
filters: [
|
||||||
|
{ name: "Vencord Settings Backup", extensions: ["json"] },
|
||||||
|
{ name: "all", extensions: ["*"] }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (file) {
|
||||||
|
try {
|
||||||
|
await importSettings(new TextDecoder().decode(file.data));
|
||||||
|
if (showToast) toastSuccess();
|
||||||
|
} catch (err) {
|
||||||
|
new Logger("SettingsSync").error(err);
|
||||||
|
if (showToast) toastFailure(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
const input = document.createElement("input");
|
const input = document.createElement("input");
|
||||||
input.type = "file";
|
input.type = "file";
|
||||||
input.style.display = "none";
|
input.style.display = "none";
|
||||||
@ -102,22 +119,5 @@ export async function uploadSettingsBackup(showToast = true): Promise<void> {
|
|||||||
document.body.appendChild(input);
|
document.body.appendChild(input);
|
||||||
input.click();
|
input.click();
|
||||||
setImmediate(() => document.body.removeChild(input));
|
setImmediate(() => document.body.removeChild(input));
|
||||||
} else {
|
|
||||||
const [file] = await DiscordNative.fileManager.openFiles({
|
|
||||||
filters: [
|
|
||||||
{ name: "Vencord Settings Backup", extensions: ["json"] },
|
|
||||||
{ name: "all", extensions: ["*"] }
|
|
||||||
]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (file) {
|
|
||||||
try {
|
|
||||||
await importSettings(new TextDecoder().decode(file.data));
|
|
||||||
if (showToast) toastSuccess();
|
|
||||||
} catch (err) {
|
|
||||||
new Logger("SettingsSync").error(err);
|
|
||||||
if (showToast) toastFailure(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import gitHash from "~git-hash";
|
|||||||
|
|
||||||
import IpcEvents from "./IpcEvents";
|
import IpcEvents from "./IpcEvents";
|
||||||
import Logger from "./Logger";
|
import Logger from "./Logger";
|
||||||
|
import { relaunch } from "./native";
|
||||||
import { IpcRes } from "./types";
|
import { IpcRes } from "./types";
|
||||||
|
|
||||||
export const UpdateLogger = /* #__PURE__*/ new Logger("Updater", "white");
|
export const UpdateLogger = /* #__PURE__*/ new Logger("Updater", "white");
|
||||||
@ -90,8 +91,10 @@ export async function maybePromptToUpdate(confirmMessage: string, checkForDev =
|
|||||||
if (wantsUpdate) {
|
if (wantsUpdate) {
|
||||||
await update();
|
await update();
|
||||||
const needFullRestart = await rebuild();
|
const needFullRestart = await rebuild();
|
||||||
if (needFullRestart) DiscordNative.app.relaunch();
|
if (needFullRestart)
|
||||||
else location.reload();
|
relaunch();
|
||||||
|
else
|
||||||
|
location.reload();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -17,40 +17,37 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// eslint-disable-next-line path-alias/no-relative
|
// eslint-disable-next-line path-alias/no-relative
|
||||||
import { filters, findByPropsLazy } from "../webpack";
|
import { filters, findByPropsLazy, waitFor } from "@webpack";
|
||||||
|
|
||||||
import { waitForComponent } from "./internal";
|
import { waitForComponent } from "./internal";
|
||||||
import * as t from "./types/components";
|
import * as t from "./types/components";
|
||||||
|
|
||||||
export const Forms = {
|
export let Forms = {} as {
|
||||||
FormTitle: waitForComponent<t.FormTitle>("FormTitle", filters.byCode("errorSeparator")),
|
FormTitle: t.FormTitle,
|
||||||
FormSection: waitForComponent<t.FormSection>("FormSection", filters.byCode("titleClassName", "sectionTitle")),
|
FormSection: t.FormSection,
|
||||||
FormDivider: waitForComponent<t.FormDivider>("FormDivider", m => {
|
FormDivider: t.FormDivider,
|
||||||
if (typeof m !== "function") return false;
|
FormText: t.FormText,
|
||||||
const s = m.toString();
|
|
||||||
return s.length < 200 && s.includes(".divider");
|
|
||||||
}),
|
|
||||||
FormText: waitForComponent<t.FormText>("FormText", m => m.Types?.INPUT_PLACEHOLDER),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Card = waitForComponent<t.Card>("Card", m => m.Types?.PRIMARY && m.defaultProps);
|
export let Card: t.Card;
|
||||||
export const Button = waitForComponent<t.Button>("Button", ["Hovers", "Looks", "Sizes"]);
|
export let Button: t.Button;
|
||||||
export const Switch = waitForComponent<t.Switch>("Switch", filters.byCode("tooltipNote", "ringTarget"));
|
export let Switch: t.Switch;
|
||||||
export const Tooltip = waitForComponent<t.Tooltip>("Tooltip", filters.byCode("shouldShowTooltip:!1", "clickableOnMobile||"));
|
export let Tooltip: t.Tooltip;
|
||||||
|
export let TextInput: t.TextInput;
|
||||||
|
export let TextArea: t.TextArea;
|
||||||
|
export let Text: t.Text;
|
||||||
|
export let Select: t.Select;
|
||||||
|
export let SearchableSelect: t.SearchableSelect;
|
||||||
|
export let Slider: t.Slider;
|
||||||
|
export let ButtonLooks: t.ButtonLooks;
|
||||||
|
export let TabBar: any;
|
||||||
|
|
||||||
export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format"));
|
export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format"));
|
||||||
export const TextInput = waitForComponent<t.TextInput>("TextInput", ["defaultProps", "Sizes", "contextType"]);
|
|
||||||
export const TextArea = waitForComponent<t.TextArea>("TextArea", filters.byCode("handleSetRef", "textArea"));
|
|
||||||
export const Text = waitForComponent<t.Text>("Text", m => {
|
|
||||||
if (typeof m !== "function") return false;
|
|
||||||
const s = m.toString();
|
|
||||||
return (s.length < 1500 && s.includes("data-text-variant") && s.includes("always-white"));
|
|
||||||
});
|
|
||||||
export const Select = waitForComponent<t.Select>("Select", filters.byCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems"));
|
|
||||||
const searchableSelectFilter = filters.byCode("autoFocus", ".Messages.SELECT");
|
|
||||||
export const SearchableSelect = waitForComponent<t.SearchableSelect>("SearchableSelect", m =>
|
|
||||||
m.render && searchableSelectFilter(m.render)
|
|
||||||
);
|
|
||||||
export const Slider = waitForComponent<t.Slider>("Slider", filters.byCode("closestMarkerIndex", "stickToMarkers"));
|
|
||||||
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>;
|
||||||
export const ButtonLooks: t.ButtonLooks = findByPropsLazy("BLANK", "FILLED", "INVERTED");
|
|
||||||
|
waitFor("FormItem", m => {
|
||||||
|
({ Card, Button, FormSwitch: Switch, Tooltip, TextInput, TextArea, Text, Select, SearchableSelect, Slider, ButtonLooks, TabBar } = m);
|
||||||
|
Forms = m;
|
||||||
|
});
|
||||||
|
@ -16,32 +16,14 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { proxyLazy } from "@utils/proxyLazy";
|
|
||||||
|
|
||||||
// eslint-disable-next-line path-alias/no-relative
|
// eslint-disable-next-line path-alias/no-relative
|
||||||
import { filters, mapMangledModule, mapMangledModuleLazy } from "../webpack";
|
import { filters, mapMangledModuleLazy, waitFor } from "../webpack";
|
||||||
import type * as t from "./types/menu";
|
import type * as t from "./types/menu";
|
||||||
|
|
||||||
export const Menu: t.Menu = proxyLazy(() => {
|
export let Menu = {} as t.Menu;
|
||||||
const hasDeobfuscator = Vencord.Settings.plugins.MenuItemDeobfuscatorAPI.enabled;
|
|
||||||
const menuItems = ["MenuSeparator", "MenuGroup", "MenuItem", "MenuCheckboxItem", "MenuRadioItem", "MenuControlItem"];
|
|
||||||
|
|
||||||
const map = mapMangledModule("♫ ⊂(。◕‿‿◕。⊂) ♪", {
|
waitFor("MenuItem", m => Menu = m);
|
||||||
ContextMenu: filters.byCode("getContainerProps"),
|
|
||||||
...Object.fromEntries((hasDeobfuscator ? menuItems : []).map(s => [s, (m: any) => m.name === s]))
|
|
||||||
}) as t.Menu;
|
|
||||||
|
|
||||||
if (!hasDeobfuscator) {
|
|
||||||
for (const m of menuItems)
|
|
||||||
Object.defineProperty(map, m, {
|
|
||||||
get() {
|
|
||||||
throw new Error("MenuItemDeobfuscator must be enabled to use this.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return map;
|
|
||||||
});
|
|
||||||
|
|
||||||
export const ContextMenu: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN"', {
|
export const ContextMenu: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN"', {
|
||||||
open: filters.byCode("stopPropagation"),
|
open: filters.byCode("stopPropagation"),
|
||||||
|
10
src/webpack/common/types/menu.d.ts
vendored
10
src/webpack/common/types/menu.d.ts
vendored
@ -21,7 +21,7 @@ import type { ComponentType, CSSProperties, PropsWithChildren, UIEvent } from "r
|
|||||||
type RC<C> = ComponentType<PropsWithChildren<C & Record<string, any>>>;
|
type RC<C> = ComponentType<PropsWithChildren<C & Record<string, any>>>;
|
||||||
|
|
||||||
export interface Menu {
|
export interface Menu {
|
||||||
ContextMenu: RC<{
|
Menu: RC<{
|
||||||
navId: string;
|
navId: string;
|
||||||
onClose(): void;
|
onClose(): void;
|
||||||
className?: string;
|
className?: string;
|
||||||
@ -49,19 +49,21 @@ export interface Menu {
|
|||||||
id: string;
|
id: string;
|
||||||
interactive?: boolean;
|
interactive?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
// TODO: Type me
|
||||||
|
MenuSliderControl: RC<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ContextMenuApi {
|
export interface ContextMenuApi {
|
||||||
close(): void;
|
close(): void;
|
||||||
open(
|
open(
|
||||||
event: UIEvent,
|
event: UIEvent,
|
||||||
render?: Menu["ContextMenu"],
|
render?: Menu["Menu"],
|
||||||
options?: { enableSpellCheck?: boolean; },
|
options?: { enableSpellCheck?: boolean; },
|
||||||
renderLazy?: () => Promise<Menu["ContextMenu"]>
|
renderLazy?: () => Promise<Menu["Menu"]>
|
||||||
): void;
|
): void;
|
||||||
openLazy(
|
openLazy(
|
||||||
event: UIEvent,
|
event: UIEvent,
|
||||||
renderLazy?: () => Promise<Menu["ContextMenu"]>,
|
renderLazy?: () => Promise<Menu["Menu"]>,
|
||||||
options?: { enableSpellCheck?: boolean; }
|
options?: { enableSpellCheck?: boolean; }
|
||||||
): void;
|
): void;
|
||||||
}
|
}
|
||||||
|
1
src/webpack/common/types/utils.d.ts
vendored
1
src/webpack/common/types/utils.d.ts
vendored
@ -30,6 +30,7 @@ export interface FluxDispatcher {
|
|||||||
isDispatching(): boolean;
|
isDispatching(): boolean;
|
||||||
subscribe(event: FluxEvents, callback: (data: any) => void): void;
|
subscribe(event: FluxEvents, callback: (data: any) => void): void;
|
||||||
unsubscribe(event: FluxEvents, callback: (data: any) => void): void;
|
unsubscribe(event: FluxEvents, callback: (data: any) => void): void;
|
||||||
|
wait(callback: () => void): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Parser = Record<
|
export type Parser = Record<
|
||||||
|
@ -67,7 +67,7 @@ export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {
|
|||||||
instance.pop();
|
instance.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IS_DEV && !IS_WEB) {
|
if (IS_DEV && IS_DISCORD_DESKTOP) {
|
||||||
var devToolsOpen = false;
|
var devToolsOpen = false;
|
||||||
// At this point in time, DiscordNative has not been exposed yet, so setImmediate is needed
|
// At this point in time, DiscordNative has not been exposed yet, so setImmediate is needed
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -109,6 +109,8 @@ export const find = traceFunction("find", function find(filter: FilterFn, getDef
|
|||||||
if (!isWaitFor) {
|
if (!isWaitFor) {
|
||||||
const err = new Error("Didn't find module matching this filter");
|
const err = new Error("Didn't find module matching this filter");
|
||||||
if (IS_DEV) {
|
if (IS_DEV) {
|
||||||
|
logger.error(err);
|
||||||
|
logger.error(filter);
|
||||||
if (!devToolsOpen)
|
if (!devToolsOpen)
|
||||||
// Strict behaviour in DevBuilds to fail early and make sure the issue is found
|
// Strict behaviour in DevBuilds to fail early and make sure the issue is found
|
||||||
throw err;
|
throw err;
|
||||||
|
Reference in New Issue
Block a user