Compare commits

..

11 Commits

Author SHA1 Message Date
Vendicated
79b35d5797 initial custom plugin loading 2023-04-20 01:19:13 +02:00
V
62194674eb Delete highResAvatars.ts 2023-04-20 00:45:24 +02:00
V
04da98498f Restore src/Vencord.ts 2023-04-20 00:44:48 +02:00
Vendicated
6fa0fb017b more additions for external plugins 2023-04-20 00:41:02 +02:00
Vendicated
11ecc45b71 more fixes 2023-04-19 23:12:39 +02:00
Vendicated
82cd8d98f6 convert import aliases 2023-04-19 23:08:07 +02:00
Vendicated
c815f1c5f3 Run autofix to sort these imports! 2023-04-19 22:59:57 +02:00
Vendicated
e248f58d9f stuffs 3 2023-04-19 22:37:58 +02:00
Vendicated
3171b78a36 stuffs 2 2023-04-19 21:49:12 +02:00
Vendicated
525aa3af33 stuffs 2023-04-19 21:36:17 +02:00
Vendicated
b7299ea2cc npm types package 2023-04-19 20:18:42 +02:00
187 changed files with 2104 additions and 3633 deletions

View File

@ -62,7 +62,7 @@
"indent": ["error", 4, { "SwitchCase": 1 }],
"arrow-parens": ["error", "as-needed"],
"eol-last": ["error", "always"],
"@typescript-eslint/func-call-spacing": ["error", "never"],
"func-call-spacing": ["error", "never"],
"no-multi-spaces": "error",
"no-trailing-spaces": "error",
"no-whitespace-before-property": "error",

View File

@ -2,14 +2,10 @@
The cutest Discord client mod
![](https://user-images.githubusercontent.com/45497981/235015332-0453d3eb-1da6-4601-963e-ef5e454123a1.png)
*A screenshot of Vencord featuring the [ClearVision-v6 theme](https://github.com/ClearVision/ClearVision-v6) (Vencord does not come with it pre-installed, it is only an example)*
## Features
- Super easy to install (Download Installer, open, click install button, done)
- 100+ plugins built in: [See a list](https://vencord.dev/plugins)
- 100+ plugins built in: [See a list](https://gist.github.com/Vendicated/8696cde7b92548064a3ae92ead84d033)
- Some highlights: SpotifyControls, GameActivityToggle, Experiments, NoTrack, MessageLogger, QuickReply, Free Emotes/Stickers, CustomCommands, ShowHiddenChannels, PronounDB
- Fairly lightweight despite the many inbuilt plugins
- Excellent Browser Support: Run Vencord in your Browser via extension or UserScript
@ -17,14 +13,10 @@ The cutest Discord client mod
- Custom CSS and Themes: Inbuilt css editor with support to import any css files (including BetterDiscord themes)
- Privacy friendly, blocks Discord analytics & crash reporting out of the box and has no telemetry
- Maintained very actively, broken plugins are usually fixed within 12 hours
- Settings sync: Keep your plugins and their settings synchronised between devices / apps (optional)
## Installing / Uninstalling
Click the below button to install Vencord to the Discord Desktop app
[![Download and run the Installer](https://img.shields.io/github/v/release/Vencord/Installer?label=Download%20Vencord%20Installer&style=for-the-badge)](https://github.com/Vencord/Installer#vencord-installer)
[![Download and run the Installer ](https://img.shields.io/github/v/release/Vencord/Installer?label=Download%20Vencord%20Installer&style=for-the-badge)](https://github.com/Vencord/Installer#usage)
## Installing on Browser
@ -32,30 +24,25 @@ Click the below button to install Vencord to the Discord Desktop app
Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that the CSS Editor, Themes loaded from remote sources and co. will not work in the UserScript. Use the extension if you need any of those
## Installing our Desktop App
## Building from Source
As an alternative to the Discord Desktop app, Vencord also has its own standalone Desktop app that is snappier and lighter than Discord's official Desktop app. It is currently in beta and we have yet to implement some features like screensharing, but you can try the beta nonetheless
See the docs folder
[![Download Vencord Desktop](https://img.shields.io/github/v/release/Vencord/Desktop?label=Download%20Vencord%20Desktop&style=for-the-badge)](https://github.com/Vencord/Desktop#vencord-desktop)
## Contributing
## Join our Support/Community Server
See [CONTRIBUTING.md](CONTRIBUTING.md) and [Megu's Plugin Guide!](docs/2_PLUGINS.md)
[![Vencord Discord Server](https://invidget.switchblade.xyz/D9uwnFnqmd?theme=dark)](https://discord.gg/D9uwnFnqmd)
[contribute]: CONTRIBUTING.md
[contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute]
## Join
[join]: https://discord.gg/D9uwnFnqmd
[join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join]
## Disclaimer
Discord is trademark of Discord Inc. and solely mentioned for the sake of descriptivity.
Mention of it does not imply any affiliation with or endorsement by Discord Inc.
<details>
<summary>Using Vencord violates Discord's terms of service</summary>
Client modifications are against Discords Terms of Service.
However, Discord is pretty indifferent about them and there are no known cases of users getting banned for using client mods! So you should generally be fine as long as you dont use any plugins that implement abusive behaviour. But no worries, all inbuilt plugins are safe to use!
Regardless, if your account is very important to you and it getting disabled would be a disaster for you, you should probably not use any client mods (not exclusive to Vencord), just to be safe
Additionally, make sure not to post screenshots with Vencord in a server where you might get banned for it
</details>

View File

@ -59,9 +59,7 @@ async function checkCors(url, method) {
const origin = headers["access-control-allow-origin"];
if (origin !== "*" && origin !== window.location.origin) return false;
const methods = headers["access-control-allow-methods"]?.toLowerCase()
.split(",")
.map(s => s.trim());
const methods = headers["access-control-allow-methods"]?.toLowerCase().split(/,\s/g);
if (methods && !methods.includes(method.toLowerCase())) return false;
return true;

View File

@ -16,70 +16,51 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/// <reference path="../src/modules.d.ts" />
/// <reference path="../src/globals.d.ts" />
import monacoHtml from "~fileContent/../src/components/monacoWin.html";
import * as DataStore from "../src/api/DataStore";
import { debounce } from "../src/utils";
import { getTheme, Theme } from "../src/utils/discord";
import IpcEvents from "../src/utils/IpcEvents";
// Discord deletes this so need to store in variable
const { localStorage } = window;
// listeners for ipc.on
const cssListeners = new Set<(css: string) => void>();
const NOOP = () => { };
const NOOP_ASYNC = async () => { };
const listeners = {} as Record<string, Set<Function>>;
const setCssDebounced = debounce((css: string) => VencordNative.quickCss.set(css));
const handlers = {
[IpcEvents.GET_REPO]: () => "https://github.com/Vendicated/Vencord", // shrug
[IpcEvents.GET_SETTINGS_DIR]: () => "LocalStorage",
[IpcEvents.GET_QUICK_CSS]: () => DataStore.get("VencordQuickCss").then(s => s ?? ""),
[IpcEvents.SET_QUICK_CSS]: (css: string) => {
DataStore.set("VencordQuickCss", css);
listeners[IpcEvents.QUICK_CSS_UPDATE]?.forEach(l => l(null, css));
},
[IpcEvents.GET_SETTINGS]: () => localStorage.getItem("VencordSettings") || "{}",
[IpcEvents.SET_SETTINGS]: (s: string) => localStorage.setItem("VencordSettings", s),
[IpcEvents.GET_UPDATES]: () => ({ ok: true, value: [] }),
[IpcEvents.OPEN_EXTERNAL]: (url: string) => open(url, "_blank"),
};
function onEvent(event: string, ...args: any[]) {
const handler = handlers[event];
if (!handler) throw new Error(`Event ${event} not implemented.`);
return handler(...args);
}
// probably should make this less cursed at some point
window.VencordNative = {
native: {
getVersions: () => ({}),
openExternal: async (url) => void open(url, "_blank")
},
updater: {
getRepo: async () => ({ ok: true, value: "https://github.com/Vendicated/Vencord" }),
getUpdates: async () => ({ ok: true, value: [] }),
update: async () => ({ ok: true, value: false }),
rebuild: async () => ({ ok: true, value: true }),
},
quickCss: {
get: () => DataStore.get("VencordQuickCss").then(s => s ?? ""),
set: async (css: string) => {
await DataStore.set("VencordQuickCss", css);
cssListeners.forEach(l => l(css));
getVersions: () => ({}),
ipc: {
send: (event: string, ...args: any[]) => void onEvent(event, ...args),
sendSync: onEvent,
on(event: string, listener: () => {}) {
(listeners[event] ??= new Set()).add(listener);
},
addChangeListener(cb) {
cssListeners.add(cb);
},
openFile: NOOP_ASYNC,
async openEditor() {
const features = `popup,width=${Math.min(window.innerWidth, 1000)},height=${Math.min(window.innerHeight, 1000)}`;
const win = open("about:blank", "VencordQuickCss", features);
if (!win) {
alert("Failed to open QuickCSS popup. Make sure to allow popups!");
return;
}
win.setCss = setCssDebounced;
win.getCurrentCss = () => VencordNative.quickCss.get();
win.getTheme = () =>
getTheme() === Theme.Light
? "vs-light"
: "vs-dark";
win.document.write(monacoHtml);
off(event: string, listener: () => {}) {
return listeners[event]?.delete(listener);
},
invoke: (event: string, ...args: any[]) => Promise.resolve(onEvent(event, ...args))
},
settings: {
get: () => localStorage.getItem("VencordSettings") || "{}",
set: async (s: string) => localStorage.setItem("VencordSettings", s),
getSettingsDir: async () => "LocalStorage"
}
};

View File

@ -16,7 +16,7 @@ chrome.webRequest.onHeadersReceived.addListener(
// In main frame requests, the CSP needs to be removed to enable fetching of custom css
// as desired by the user
removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-security-policy");
} else if (type === "stylesheet" && url.startsWith("https://raw.githubusercontent.com/")) {
} else if (type === "stylesheet" && url.startsWith("https://raw.githubusercontent.com")) {
// Most users will load css from GitHub, but GitHub doesn't set the correct content type,
// so we fix it here
removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-type");

View File

@ -26,6 +26,10 @@ export default definePlugin({
name: "Your Name",
},
],
// Delete `patches` if you are not using code patches, as it will make
// your plugin require restarts, and your stop() method will not be
// invoked at all. The presence of the key in the object alone is
// enough to trigger this behavior, even if the value is an empty array.
patches: [],
// Delete these two below if you are only using code patches
start() {},

View File

@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
"version": "1.2.1",
"version": "1.1.9",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
@ -28,24 +28,25 @@
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
"testTsc": "tsc --noEmit",
"uninject": "node scripts/runInstaller.mjs",
"watch": "node scripts/build/build.mjs --watch"
"watch": "node scripts/build/build.mjs --watch",
"buildTypes": "ttsc --emitDeclarationOnly --declaration --outDir packages/vencord-types"
},
"dependencies": {
"@vap/core": "0.0.12",
"@vap/shiki": "0.10.5",
"@vap/shiki": "0.10.3",
"fflate": "^0.7.4",
"nanoid": "^4.0.2",
"virtual-merge": "^1.0.1"
},
"devDependencies": {
"@types/diff": "^5.0.3",
"@types/lodash": "^4.14.194",
"@types/node": "^18.16.3",
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.1",
"@types/diff": "^5.0.2",
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.18",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"@types/yazl": "^2.4.2",
"@typescript-eslint/eslint-plugin": "^5.59.1",
"@typescript-eslint/parser": "^5.59.1",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.49.0",
"diff": "^5.1.0",
"discord-types": "^1.3.26",
"esbuild": "^0.15.18",
@ -53,17 +54,19 @@
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-header": "^3.1.1",
"eslint-plugin-path-alias": "^1.0.0",
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-simple-import-sort": "^8.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"highlight.js": "10.6.0",
"moment": "^2.29.4",
"puppeteer-core": "^19.11.1",
"puppeteer-core": "^19.6.0",
"standalone-electron-types": "^1.0.0",
"stylelint": "^15.6.0",
"stylelint-config-standard": "^33.0.0",
"tsx": "^3.12.7",
"type-fest": "^3.9.0",
"typescript": "^5.0.4"
"stylelint": "^14.16.1",
"stylelint-config-standard": "^29.0.0",
"tsx": "^3.12.6",
"ttypescript": "^1.5.15",
"type-fest": "^3.5.3",
"typescript": "^4.9.4",
"typescript-transform-paths": "^3.4.6"
},
"packageManager": "pnpm@8.1.1",
"pnpm": {

7
packages/vencord-types/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
*
!.*ignore
!package.json
!README.md
!prepare.ts
!index.d.ts
!globals.d.ts

View File

@ -0,0 +1,3 @@
node_modules
prepare.ts
.gitignore

View File

@ -0,0 +1,11 @@
# Vencord Types
Typings for Vencord's api, published to npm
```sh
npm i @vencord/types
yarn add @vencord/types
pnpm add @vencord/types
```

View File

@ -1,6 +1,6 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
* 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
@ -16,13 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export const enum UserType {
Banned = -1,
Normal = 0,
Admin = 1
declare global {
export var VencordNative: typeof import("./VencordNative").default;
export var Vencord: typeof import("./Vencord");
}
export interface ReviewDBUser {
lastReviewID: number,
type: UserType;
}
export { };

5
packages/vencord-types/index.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/* eslint-disable */
/// <reference path="Vencord.d.ts" />
/// <reference path="globals.d.ts" />
/// <reference path="modules.d.ts" />

View File

@ -0,0 +1,26 @@
{
"name": "@vencord/types",
"private": false,
"version": "0.1.3",
"description": "",
"types": "index.d.ts",
"scripts": {
"prepublishOnly": "tsx ./prepare.ts",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "Vencord",
"license": "GPL-3.0",
"devDependencies": {
"tsx": "^3.12.6"
},
"dependencies": {
"@types/lodash": "^4.14.191",
"@types/node": "^18.11.18",
"@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10",
"discord-types": "^1.3.26",
"standalone-electron-types": "^1.0.0",
"type-fest": "^3.5.3"
}
}

View File

@ -0,0 +1,44 @@
/*
* 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 { cpSync, readdirSync, rmSync } from "fs";
import { join } from "path";
const SRC = join(__dirname, "..", "..", "src");
for (const file of ["preload.d.ts", "userplugins", "main", "debug"]) {
rmSync(join(__dirname, file), { recursive: true, force: true });
}
function copyDtsFiles(from: string, to: string) {
for (const file of readdirSync(from, { withFileTypes: true })) {
// bad
if (from === SRC && file.name === "globals.d.ts") continue;
const fullFrom = join(from, file.name);
const fullTo = join(to, file.name);
if (file.isDirectory()) {
copyDtsFiles(fullFrom, fullTo);
} else if (file.name.endsWith(".d.ts")) {
cpSync(fullFrom, fullTo);
}
}
}
copyDtsFiles(SRC, __dirname);

1022
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

2
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,2 @@
packages:
- packages/*

View File

@ -76,7 +76,7 @@ await Promise.all([
outfile: "dist/renderer.js",
format: "iife",
target: ["esnext"],
footer: { js: "//# sourceURL=VencordRenderer\n" + sourceMapFooter("renderer") },
footer: { js: "Vencord.Plugins.loadExternalPlugins();\n//# sourceURL=VencordRenderer\n" + sourceMapFooter("renderer") },
globalName: "Vencord",
sourcemap,
plugins: [
@ -110,7 +110,7 @@ await Promise.all([
outfile: "dist/vencordDesktopRenderer.js",
format: "iife",
target: ["esnext"],
footer: { js: "//# sourceURL=VencordDesktopRenderer\n" + sourceMapFooter("vencordDesktopRenderer") },
footer: { js: "Vencord.Plugins.loadExternalPlugins();\n//# sourceURL=VencordDesktopRenderer\n" + sourceMapFooter("vencordDesktopRenderer") },
globalName: "Vencord",
sourcemap,
plugins: [

View File

@ -130,7 +130,7 @@ async function printReport() {
},
{
title: "Discord Errors",
description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n")) : "None",
description: toCodeBlock(report.otherErrors.join("\n")),
color: report.otherErrors.length ? 0xff0000 : 0x00ff00
}
]
@ -194,10 +194,9 @@ page.on("console", async e => {
return a.toString();
}
})
).then(a => a.join(" ").trim());
).then(a => a.join(" "));
if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of")) {
if (!text.startsWith("Failed to load resource: the server responded with a status of")) {
console.error("Got unexpected error", text);
report.otherErrors.push(text);
}
@ -234,7 +233,7 @@ function runTime(token: string) {
// Needs native server to run
if (p.name === "WebRichPresence (arRPC)") return;
Vencord.Settings.plugins[p.name].enabled = true;
p.required = true;
p.patches?.forEach(patch => {
patch.plugin = p.name;
delete patch.predicate;
@ -268,7 +267,7 @@ function runTime(token: string) {
if (!isWasm)
await wreq.e(id as any);
await new Promise(r => setTimeout(r, 150));
await new Promise(r => setTimeout(r, 500));
}
console.error("[PUP_DEBUG]", "Finished loading chunks!");

View File

@ -28,12 +28,12 @@ import "./utils/quickCss";
import "./webpack/patchWebpack";
import { showNotification } from "./api/Notifications";
import { PlainSettings, Settings } from "./api/Settings";
import { PlainSettings, Settings } from "./api/settings";
import { patches, PMLogger, startAllPlugins } from "./plugins";
import { localStorage } from "./utils/localStorage";
import { relaunch } from "./utils/native";
import { getCloudSettings, putCloudSettings } from "./utils/settingsSync";
import { checkForUpdates, update, UpdateLogger } from "./utils/updater";
import { checkForUpdates, rebuild, update, UpdateLogger } from "./utils/updater";
import { onceReady } from "./webpack";
import { SettingsRouter } from "./webpack/common";
@ -76,6 +76,7 @@ async function init() {
if (Settings.autoUpdate) {
await update();
await rebuild();
if (Settings.autoUpdateNotification)
setTimeout(() => showNotification({
title: "Vencord has been updated!",

View File

@ -16,46 +16,34 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { IpcEvents } from "@utils/IpcEvents";
import { IpcRes } from "@utils/types";
import { ipcRenderer } from "electron";
import IPC_EVENTS from "@utils/IpcEvents";
import { IpcRenderer, ipcRenderer } from "electron";
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
return ipcRenderer.invoke(event, ...args) as Promise<T>;
function assertEventAllowed(event: string) {
if (!(event in IPC_EVENTS)) throw new Error(`Event ${event} not allowed.`);
}
export function sendSync<T = any>(event: IpcEvents, ...args: any[]) {
return ipcRenderer.sendSync(event, ...args) as T;
}
export default {
updater: {
getUpdates: () => invoke<IpcRes<Record<"hash" | "author" | "message", string>[]>>(IpcEvents.GET_UPDATES),
update: () => invoke<IpcRes<boolean>>(IpcEvents.UPDATE),
rebuild: () => invoke<IpcRes<boolean>>(IpcEvents.BUILD),
getRepo: () => invoke<IpcRes<string>>(IpcEvents.GET_REPO),
},
settings: {
get: () => sendSync<string>(IpcEvents.GET_SETTINGS),
set: (settings: string) => invoke<void>(IpcEvents.SET_SETTINGS, settings),
getSettingsDir: () => invoke<string>(IpcEvents.GET_SETTINGS_DIR),
},
quickCss: {
get: () => invoke<string>(IpcEvents.GET_QUICK_CSS),
set: (css: string) => invoke<void>(IpcEvents.SET_QUICK_CSS, css),
addChangeListener(cb: (newCss: string) => void) {
ipcRenderer.on(IpcEvents.QUICK_CSS_UPDATE, (_, css) => cb(css));
getVersions: () => process.versions,
ipc: {
send(event: string, ...args: any[]) {
assertEventAllowed(event);
ipcRenderer.send(event, ...args);
},
openFile: () => invoke<void>(IpcEvents.OPEN_QUICKCSS),
openEditor: () => invoke<void>(IpcEvents.OPEN_MONACO_EDITOR),
},
native: {
getVersions: () => process.versions as Partial<NodeJS.ProcessVersions>,
openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url)
},
sendSync<T = any>(event: string, ...args: any[]): T {
assertEventAllowed(event);
return ipcRenderer.sendSync(event, ...args);
},
on(event: string, listener: Parameters<IpcRenderer["on"]>[1]) {
assertEventAllowed(event);
ipcRenderer.on(event, listener);
},
off(event: string, listener: Parameters<IpcRenderer["off"]>[1]) {
assertEventAllowed(event);
ipcRenderer.off(event, listener);
},
invoke<T = any>(event: string, ...args: any[]): Promise<T> {
assertEventAllowed(event);
return ipcRenderer.invoke(event, ...args);
}
}
};

View File

@ -79,8 +79,8 @@ export function _getBadges(args: BadgeUserArgs) {
: badges.push({ ...badge, ...args });
}
}
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/apiBadges").default).getDonorBadges(args.user.id);
if (donorBadges) badges.unshift(...donorBadges);
const donorBadge = (Plugins.BadgeAPI as any).getDonorBadge(args.user.id);
if (donorBadge) badges.unshift(donorBadge);
return badges;
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { makeCodeblock } from "@utils/text";
import { makeCodeblock } from "@utils/misc";
import { sendBotMessage } from "./commandHelpers";
import { ApplicationCommandInputType, ApplicationCommandOptionType, ApplicationCommandType, Argument, Command, CommandContext, Option } from "./types";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
import type { ReactElement } from "react";
type ContextMenuPatchCallbackReturn = (() => void) | void;

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
import { MessageStore } from "@webpack/common";
import type { Channel, Message } from "discord-types/general";
import type { Promisable } from "type-fest";
@ -36,50 +36,10 @@ export interface Emoji {
export interface MessageObject {
content: string,
validNonShortcutEmojis: Emoji[];
invalidEmojis: any[];
tts: boolean;
}
export interface Upload {
classification: string;
currentSize: number;
description: string | null;
filename: string;
id: string;
isImage: boolean;
isVideo: boolean;
item: {
file: File;
platform: number;
};
loaded: number;
mimeType: string;
preCompressionSize: number;
responseUrl: string;
sensitive: boolean;
showLargeMessageDialog: boolean;
spoiler: boolean;
status: "NOT_STARTED" | "STARTED" | "UPLOADING" | "ERROR" | "COMPLETED" | "CANCELLED";
uniqueId: string;
uploadedFilename: string;
}
export interface MessageReplyOptions {
messageReference: Message["messageReference"];
allowedMentions?: {
parse: Array<string>;
repliedUser: boolean;
};
}
export interface MessageExtra {
stickers?: string[];
uploads?: Upload[];
replyOptions: MessageReplyOptions;
content: string;
channel: Channel;
type?: any;
openWarningPopout: (props: any) => any;
stickerIds?: string[];
}
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
@ -88,8 +48,7 @@ export type EditListener = (channelId: string, messageId: string, messageObj: Me
const sendListeners = new Set<SendListener>();
const editListeners = new Set<EditListener>();
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
extra.replyOptions = replyOptions;
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra) {
for (const listener of sendListeners) {
try {
const result = await listener(channelId, messageObj, extra);

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
import { Channel, Message } from "discord-types/general";
import type { MouseEventHandler } from "react";

View File

@ -18,7 +18,7 @@
import "./styles.css";
import { useSettings } from "@api/Settings";
import { useSettings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { classes } from "@utils/misc";
import { React, useEffect, useMemo, useState, useStateFromStores, WindowStore } from "@webpack/common";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/Settings";
import { Settings } from "@api/settings";
import { Queue } from "@utils/Queue";
import { ReactDOM } from "@webpack/common";
import type { ReactNode } from "react";

View File

@ -17,10 +17,10 @@
*/
import * as DataStore from "@api/DataStore";
import { Settings } from "@api/Settings";
import { Settings } from "@api/settings";
import { classNameFactory } from "@api/Styles";
import { useAwaiter } from "@utils/misc";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { useAwaiter } from "@utils/react";
import { Alerts, Button, Forms, moment, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
import { nanoid } from "nanoid";
import type { DispatchWithoutAction } from "react";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
const logger = new Logger("ServerListAPI");

View File

@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
import { proxyLazy } from "@utils/proxyLazy";
import { findModuleId, wreq } from "@webpack";
import { Settings } from "./Settings";
import { Settings } from "./settings";
interface Setting<T> {
/**

View File

@ -28,6 +28,7 @@ import * as $MessagePopover from "./MessagePopover";
import * as $Notices from "./Notices";
import * as $Notifications from "./Notifications";
import * as $ServerList from "./ServerList";
import * as $Settings from "./settings";
import * as $SettingsStore from "./SettingsStore";
import * as $Styles from "./Styles";
@ -104,3 +105,9 @@ export const Notifications = $Notifications;
* An api allowing you to patch and add/remove items to/from context menus
*/
export const ContextMenu = $ContextMenu;
/**
* Settings lol
*/
export const Settings = $Settings;
export const settings = $Settings;

View File

@ -17,8 +17,9 @@
*/
import { debounce } from "@utils/debounce";
import IpcEvents from "@utils/IpcEvents";
import { localStorage } from "@utils/localStorage";
import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
import { mergeDefaults } from "@utils/misc";
import { putCloudSettings } from "@utils/settingsSync";
import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types";
@ -93,7 +94,7 @@ const DefaultSettings: Settings = {
};
try {
var settings = JSON.parse(VencordNative.settings.get()) as Settings;
var settings = JSON.parse(VencordNative.ipc.sendSync(IpcEvents.GET_SETTINGS)) as Settings;
mergeDefaults(settings, DefaultSettings);
} catch (err) {
var settings = mergeDefaults({} as Settings, DefaultSettings);
@ -172,7 +173,7 @@ function makeProxy(settings: any, root = settings, path = ""): Settings {
PlainSettings.cloud.settingsSyncVersion = Date.now();
localStorage.Vencord_settingsDirty = true;
saveSettingsOnFrequentAction();
VencordNative.settings.set(JSON.stringify(root, null, 4));
VencordNative.ipc.invoke(IpcEvents.SET_SETTINGS, JSON.stringify(root, null, 4));
return true;
}
});
@ -248,7 +249,10 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
logger.info(`Migrating settings from old name ${oldName} to ${name}`);
plugins[name] = plugins[oldName];
delete plugins[oldName];
VencordNative.settings.set(JSON.stringify(settings, null, 4));
VencordNative.ipc.invoke(
IpcEvents.SET_SETTINGS,
JSON.stringify(settings, null, 4)
);
break;
}
}

View File

@ -16,6 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import IpcEvents from "@utils/IpcEvents";
import { Button } from "@webpack/common";
import { Heart } from "./Heart";
@ -26,7 +27,9 @@ export default function DonateButton(props: any) {
{...props}
look={Button.Looks.LINK}
color={Button.Colors.TRANSPARENT}
onClick={() => VencordNative.native.openExternal("https://github.com/sponsors/Vendicated")}
onClick={() =>
VencordNative.ipc.invoke(IpcEvents.OPEN_EXTERNAL, "https://github.com/sponsors/Vendicated")
}
>
<Heart />
Donate

View File

@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
import { Margins } from "@utils/margins";
import { LazyComponent } from "@utils/react";
import { LazyComponent } from "@utils/misc";
import { React } from "@webpack/common";
import { ErrorCard } from "./ErrorCard";

View File

@ -1,83 +0,0 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { classes } from "@utils/misc";
import type { PropsWithChildren } from "react";
interface BaseIconProps extends IconProps {
viewBox: string;
}
interface IconProps {
className?: string;
height?: number;
width?: number;
}
function Icon({ height = 24, width = 24, className, children, viewBox }: PropsWithChildren<BaseIconProps>) {
return (
<svg
className={classes(className, "vc-icon")}
aria-hidden="true"
role="img"
width={width}
height={height}
viewBox={viewBox}
>
{children}
</svg>
);
}
/**
* Discord's link icon, as seen in the Message context menu "Copy Message Link" option
*/
export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
return (
<Icon
height={height}
width={width}
className={classes(className, "vc-link-icon")}
viewBox="0 0 24 24"
>
<g fill="none" fill-rule="evenodd">
<path fill="currentColor" d="M10.59 13.41c.41.39.41 1.03 0 1.42-.39.39-1.03.39-1.42 0a5.003 5.003 0 0 1 0-7.07l3.54-3.54a5.003 5.003 0 0 1 7.07 0 5.003 5.003 0 0 1 0 7.07l-1.49 1.49c.01-.82-.12-1.64-.4-2.42l.47-.48a2.982 2.982 0 0 0 0-4.24 2.982 2.982 0 0 0-4.24 0l-3.53 3.53a2.982 2.982 0 0 0 0 4.24zm2.82-4.24c.39-.39 1.03-.39 1.42 0a5.003 5.003 0 0 1 0 7.07l-3.54 3.54a5.003 5.003 0 0 1-7.07 0 5.003 5.003 0 0 1 0-7.07l1.49-1.49c-.01.82.12 1.64.4 2.43l-.47.47a2.982 2.982 0 0 0 0 4.24 2.982 2.982 0 0 0 4.24 0l3.53-3.53a2.982 2.982 0 0 0 0-4.24.973.973 0 0 1 0-1.42z" />
<rect width={width} height={height} />
</g>
</Icon>
);
}
/**
* Discord's copy icon, as seen in the user popout right of the username when clicking
* your own username in the bottom left user panel
*/
export function CopyIcon(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-copy-icon")}
viewBox="0 0 24 24"
>
<g fill="currentColor">
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1z" />
<path d="M15 5H8c-1.1 0-1.99.9-1.99 2L6 21c0 1.1.89 2 1.99 2H19c1.1 0 2-.9 2-2V11l-6-6zM8 21V7h6v5h5v9H8z" />
</g>
</Icon>
);
}

51
src/components/Monaco.ts Normal file
View File

@ -0,0 +1,51 @@
/*
* 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 { debounce } from "@utils/debounce";
import IpcEvents from "@utils/IpcEvents";
import { Queue } from "@utils/Queue";
import { find } from "@webpack";
import monacoHtml from "~fileContent/monacoWin.html";
const queue = new Queue();
const setCss = debounce((css: string) => {
queue.push(() => VencordNative.ipc.invoke(IpcEvents.SET_QUICK_CSS, css));
});
export async function launchMonacoEditor() {
const features = `popup,width=${Math.min(window.innerWidth, 1000)},height=${Math.min(window.innerHeight, 1000)}`;
const win = open("about:blank", "VencordQuickCss", features);
if (!win) {
alert("Failed to open QuickCSS popup. Make sure to allow popups!");
return;
}
win.setCss = setCss;
win.getCurrentCss = () => VencordNative.ipc.invoke(IpcEvents.GET_QUICK_CSS);
win.getTheme = () =>
find(m =>
m.ProtoClass?.typeName.endsWith("PreloadedUserSettings")
)?.getCurrentValue()?.appearance?.theme === 2
? "vs-light"
: "vs-dark";
win.document.write(monacoHtml);
window.__VENCORD_MONACO_WIN__ = new WeakRef(win);
}

View File

@ -18,8 +18,8 @@
import { debounce } from "@utils/debounce";
import { Margins } from "@utils/margins";
import { makeCodeblock } from "@utils/misc";
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
import { makeCodeblock } from "@utils/text";
import { ReplaceFn } from "@utils/types";
import { search } from "@webpack";
import { Button, Clipboard, Forms, Parser, React, Switch, Text, TextInput } from "@webpack/common";

View File

@ -17,14 +17,13 @@
*/
import { generateId } from "@api/Commands";
import { useSettings } from "@api/Settings";
import { useSettings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { proxyLazy } from "@utils/lazy";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { classes, LazyComponent } from "@utils/misc";
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
import { LazyComponent } from "@utils/react";
import { proxyLazy } from "@utils/proxyLazy";
import { OptionType, Plugin } from "@utils/types";
import { findByCode, findByPropsLazy } from "@webpack";
import { Button, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
@ -129,8 +128,6 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
} else {
const options = Object.entries(plugin.options).map(([key, setting]) => {
if (setting.hidden) return null;
function onChange(newValue: any) {
setTempSettings(s => ({ ...s, [key]: newValue }));
}
@ -153,7 +150,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
);
});
return <Flex flexDirection="column" style={{ gap: 12, marginBottom: 16 }}>{options}</Flex>;
return <Flex flexDirection="column" style={{ gap: 12 }}>{options}</Flex>;
}
}
@ -183,7 +180,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text>
<ModalCloseButton onClick={onClose} />
</ModalHeader>
<ModalContent>
<ModalContent style={{ marginBottom: 8, marginTop: 8 }}>
<Forms.FormSection>
<Forms.FormTitle tag="h3">About {plugin.name}</Forms.FormTitle>
<Forms.FormText>{plugin.description}</Forms.FormText>

View File

@ -20,7 +20,7 @@ import "./styles.css";
import * as DataStore from "@api/DataStore";
import { showNotice } from "@api/Notices";
import { useSettings } from "@api/Settings";
import { useSettings } from "@api/settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
@ -29,12 +29,10 @@ import { Badge } from "@components/PluginSettings/components";
import PluginModal from "@components/PluginSettings/PluginModal";
import { Switch } from "@components/Switch";
import { ChangeList } from "@utils/ChangeList";
import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { classes, LazyComponent, useAwaiter } from "@utils/misc";
import { openModalLazy } from "@utils/modal";
import { onlyOnce } from "@utils/onlyOnce";
import { LazyComponent, useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types";
import { findByCode, findByPropsLazy } from "@webpack";
import { Alerts, Button, Card, Forms, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
@ -127,7 +125,7 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe
}
// if the plugin has patches, dont use stopPlugin/startPlugin. Wait for restart to apply changes.
if (plugin.patches?.length) {
if (plugin.patches) {
settings.enabled = !wasEnabled;
onRestartNeeded(plugin.name);
return;
@ -260,9 +258,6 @@ export default ErrorBoundary.wrap(function PluginSettings() {
requiredPlugins = [];
for (const p of sortedPlugins) {
if (!p.options && p.name.endsWith("API") && searchValue.value !== "API")
continue;
if (!pluginFilter(p)) continue;
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
@ -346,7 +341,7 @@ export default ErrorBoundary.wrap(function PluginSettings() {
);
}, {
message: "Failed to render the Plugin Settings. If this persists, try using the installer to reinstall!",
onError: onlyOnce(handleComponentFailed),
onError: handleComponentFailed,
});
function makeDependencyList(deps: string[]) {

View File

@ -17,7 +17,7 @@
*/
import { showNotification } from "@api/Notifications";
import { Settings, useSettings } from "@api/Settings";
import { Settings, useSettings } from "@api/settings";
import { CheckedTextInput } from "@components/CheckedTextInput";
import ErrorBoundary from "@components/ErrorBoundary";
import { Link } from "@components/Link";

View File

@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { useSettings } from "@api/Settings";
import { useSettings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Link } from "@components/Link";
import { Margins } from "@utils/margins";
import { useAwaiter } from "@utils/react";
import { useAwaiter } from "@utils/misc";
import { findLazy } from "@webpack";
import { Card, Forms, React, TextArea } from "@webpack/common";
@ -93,7 +93,7 @@ export default ErrorBoundary.wrap(function () {
<Card className="vc-settings-card vc-text-selectable">
<Forms.FormTitle tag="h5">Paste links to .theme.css files here</Forms.FormTitle>
<Forms.FormText>One link per line</Forms.FormText>
<Forms.FormText><strong>Make sure to use the raw links or github.io links!</strong></Forms.FormText>
<Forms.FormText>Make sure to use the raw links or github.io links!</Forms.FormText>
<Forms.FormDivider className={Margins.top8 + " " + Margins.bottom8} />
<Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle>
<div style={{ marginBottom: ".5em" }}>

View File

@ -16,18 +16,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { useSettings } from "@api/Settings";
import { useSettings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex";
import { handleComponentFailed } from "@components/handleComponentFailed";
import { Link } from "@components/Link";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { classes, useAwaiter } from "@utils/misc";
import { relaunch } from "@utils/native";
import { onlyOnce } from "@utils/onlyOnce";
import { useAwaiter } from "@utils/react";
import { changes, checkForUpdates, getRepo, isNewer, 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 gitHash from "~git-hash";
@ -127,6 +125,7 @@ function Updatable(props: CommonProps) {
onClick={withDispatcher(setIsUpdating, async () => {
if (await update()) {
setUpdates([]);
await rebuild();
await new Promise<void>(r => {
Alerts.show({
title: "Update Success!",
@ -252,5 +251,5 @@ function Updater() {
export default IS_WEB ? null : ErrorBoundary.wrap(Updater, {
message: "Failed to render the Updater. If this persists, try using the installer to reinstall!",
onError: onlyOnce(handleComponentFailed),
onError: handleComponentFailed,
});

View File

@ -18,15 +18,15 @@
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
import { Settings, useSettings } from "@api/Settings";
import { Settings, useSettings } from "@api/settings";
import { classNameFactory } from "@api/Styles";
import DonateButton from "@components/DonateButton";
import ErrorBoundary from "@components/ErrorBoundary";
import { ErrorCard } from "@components/ErrorCard";
import IpcEvents from "@utils/IpcEvents";
import { Margins } from "@utils/margins";
import { identity } from "@utils/misc";
import { identity, useAwaiter } from "@utils/misc";
import { relaunch, showItemInFolder } from "@utils/native";
import { useAwaiter } from "@utils/react";
import { Button, Card, Forms, React, Select, Slider, Switch } from "@webpack/common";
const cl = classNameFactory("vc-settings-");
@ -39,7 +39,7 @@ type KeysOfType<Object, Type> = {
}[keyof Object];
function VencordSettings() {
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
const [settingsDir, , settingsDirPending] = useAwaiter(() => VencordNative.ipc.invoke<string>(IpcEvents.GET_SETTINGS_DIR), {
fallbackValue: "Loading..."
});
const settings = useSettings();
@ -101,35 +101,40 @@ function VencordSettings() {
<DonateCard image={donateImage} />
<Forms.FormSection title="Quick Actions">
<Card className={cl("quick-actions-card")}>
<React.Fragment>
{!IS_WEB && (
{IS_WEB ? (
<Button
onClick={() => require("../Monaco").launchMonacoEditor()}
size={Button.Sizes.SMALL}
disabled={settingsDir === "Loading..."}>
Open QuickCSS File
</Button>
) : (
<React.Fragment>
<Button
onClick={relaunch}
size={Button.Sizes.SMALL}>
Restart Client
</Button>
)}
<Button
onClick={() => VencordNative.quickCss.openEditor()}
size={Button.Sizes.SMALL}
disabled={settingsDir === "Loading..."}>
Open QuickCSS File
</Button>
{!IS_WEB && (
<Button
onClick={() => VencordNative.ipc.invoke(IpcEvents.OPEN_MONACO_EDITOR)}
size={Button.Sizes.SMALL}
disabled={settingsDir === "Loading..."}>
Open QuickCSS File
</Button>
<Button
onClick={() => showItemInFolder(settingsDir)}
size={Button.Sizes.SMALL}
disabled={settingsDirPending}>
Open Settings Folder
</Button>
)}
<Button
onClick={() => VencordNative.native.openExternal("https://github.com/Vendicated/Vencord")}
size={Button.Sizes.SMALL}
disabled={settingsDirPending}>
Open in GitHub
</Button>
</React.Fragment>
<Button
onClick={() => VencordNative.ipc.invoke(IpcEvents.OPEN_EXTERNAL, "https://github.com/Vendicated/Vencord")}
size={Button.Sizes.SMALL}
disabled={settingsDirPending}>
Open in GitHub
</Button>
</React.Fragment>
)}
</Card>
</Forms.FormSection>

View File

@ -22,7 +22,6 @@ import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { handleComponentFailed } from "@components/handleComponentFailed";
import { isMobile } from "@utils/misc";
import { onlyOnce } from "@utils/onlyOnce";
import { Forms, SettingsRouter, TabBar, Text } from "@webpack/common";
import BackupRestoreTab from "./BackupRestoreTab";
@ -87,10 +86,8 @@ function Settings(props: SettingsProps) {
</Forms.FormSection >;
}
const onError = onlyOnce(handleComponentFailed);
export default function (props: SettingsProps) {
return <ErrorBoundary onError={onError}>
return <ErrorBoundary onError={handleComponentFailed}>
<Settings tab={props.tab} />
</ErrorBoundary>;
}

View File

@ -15,7 +15,7 @@
display: flex;
gap: 1em;
align-items: center;
justify-content: space-evenly;
justify-content: space-between;
flex-grow: 1;
flex-flow: row wrap;
margin-bottom: 1em;
@ -59,7 +59,7 @@
}
.vc-text-selectable,
.vc-text-selectable :not(a, button, a *, button *) {
.vc-text-selectable :not(a, button) {
/* make text selectable, silly discord makes the entirety of settings not selectable */
user-select: text;

View File

@ -16,6 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
export { default as ErrorBoundary } from "./ErrorBoundary";
export * from "./ErrorCard";
export * from "./Flex";
export * from "./Heart";
export * from "./Link";
export { default as PatchHelper } from "./PatchHelper";
export { default as PluginSettings } from "./PluginSettings";
export * from "./Switch";
export { default as VencordSettings } from "./VencordSettings";

View File

@ -1,66 +1,52 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Vencord QuickCSS Editor</title>
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.37.1/min/vs/editor/editor.main.min.css"
integrity="sha512-wB3xfL98hWg1bpkVYSyL0js/Jx9s7FsDg9aYO6nOMSJTgPuk/PFqxXQJKKSUjteZjeYrfgo9NFBOA1r9HwDuZw=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
<style>
html,
body,
#container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.37.1/min/vs/loader.min.js"
integrity="sha512-A+6SvPGkIN9Rf0mUXmW4xh7rDvALXf/f0VtOUiHlDUSPknu2kcfz1KzLpOJyL2pO+nZS13hhIjLqVgiQExLJrw=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<head>
<meta charset="utf-8">
<title>Vencord QuickCSS Editor</title>
<link rel="stylesheet" data-name="vs/editor/editor.main"
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.min.css">
<style>
html,
body,
#container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<script>
require.config({
paths: {
vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.37.1/min/vs",
},
});
<body>
<div id="container"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.min.js"></script>
require(["vs/editor/editor.main"], () => {
getCurrentCss().then((css) => {
var editor = monaco.editor.create(
document.getElementById("container"),
{
value: css,
language: "css",
theme: getTheme(),
}
);
editor.onDidChangeModelContent(() =>
setCss(editor.getValue())
);
window.addEventListener("resize", () => {
// make monaco re-layout
editor.layout();
});
<script>
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs' } });
require(["vs/editor/editor.main"], () => {
getCurrentCss().then(css => {
var editor = monaco.editor.create(document.getElementById('container'), {
value: css,
language: 'css',
theme: getTheme(),
});
editor.onDidChangeModelContent(() =>
setCss(editor.getValue())
);
window.addEventListener("resize", () => {
// make monaco re-layout
editor.layout();
});
});
</script>
</body>
});
</script>
</body>
</html>

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
if (IS_DEV) {
var traces = {} as Record<string, [number, any[]]>;

View File

@ -19,16 +19,16 @@
import "./updater";
import { debounce } from "@utils/debounce";
import { IpcEvents } from "@utils/IpcEvents";
import IpcEvents from "@utils/IpcEvents";
import { Queue } from "@utils/Queue";
import { BrowserWindow, ipcMain, shell } from "electron";
import { mkdirSync, readFileSync, watch } from "fs";
import { mkdirSync, readdirSync, readFileSync, watch } from "fs";
import { open, readFile, writeFile } from "fs/promises";
import { join } from "path";
import monacoHtml from "~fileContent/../components/monacoWin.html;base64";
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE } from "./utils/constants";
import { ALLOWED_PROTOCOLS, PLUGINS_DIR, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE } from "./utils/constants";
mkdirSync(SETTINGS_DIR, { recursive: true });
@ -44,7 +44,7 @@ export function readSettings() {
}
}
export function getSettings(): typeof import("@api/Settings").Settings {
export function getSettings(): typeof import("@api/settings").Settings {
try {
return JSON.parse(readSettings());
} catch {
@ -105,3 +105,15 @@ ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
});
await win.loadURL(`data:text/html;base64,${monacoHtml}`);
});
ipcMain.on(IpcEvents.GET_PLUGINS, e => {
try {
const files = readdirSync(PLUGINS_DIR).filter(f => f.endsWith(".js"));
console.log(files);
e.returnValue = files.map(f => [f, readFileSync(join(PLUGINS_DIR, f), "utf-8")]);
} catch (err) {
console.error(err);
e.returnValue = [];
}
});

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { IpcEvents } from "@utils/IpcEvents";
import IpcEvents from "@utils/IpcEvents";
import { execFile as cpExecFile } from "child_process";
import { ipcMain } from "electron";
import { join } from "path";

View File

@ -17,7 +17,7 @@
*/
import { VENCORD_USER_AGENT } from "@utils/constants";
import { IpcEvents } from "@utils/IpcEvents";
import IpcEvents from "@utils/IpcEvents";
import { ipcMain } from "electron";
import { writeFile } from "fs/promises";
import { join } from "path";

View File

@ -25,6 +25,7 @@ export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR ?? (
: join(app.getPath("userData"), "..", "Vencord")
);
export const SETTINGS_DIR = join(DATA_DIR, "settings");
export const PLUGINS_DIR = join(DATA_DIR, "plugins");
export const QUICKCSS_PATH = join(SETTINGS_DIR, "quickCss.css");
export const SETTINGS_FILE = join(SETTINGS_DIR, "settings.json");
export const ALLOWED_PROTOCOLS = [

2
src/modules.d.ts vendored
View File

@ -20,7 +20,7 @@
/// <reference types="standalone-electron-types"/>
declare module "~plugins" {
const plugins: Record<string, import("@utils/types").Plugin>;
const plugins: Record<string, import("./utils/types").Plugin>;
export default plugins;
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/Settings";
import { Settings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";

View File

@ -22,11 +22,12 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Heart } from "@components/Heart";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import IpcEvents from "@utils/IpcEvents";
import Logger from "@utils/Logger";
import { Margins } from "@utils/margins";
import { closeModal, Modals, openModal } from "@utils/modal";
import definePlugin from "@utils/types";
import { Forms, Toasts } from "@webpack/common";
import { Forms } from "@webpack/common";
const CONTRIBUTOR_BADGE = "https://cdn.discordapp.com/attachments/1033680203433660458/1092089947126780035/favicon.png";
@ -47,29 +48,7 @@ const ContributorBadge: ProfileBadge = {
link: "https://github.com/Vendicated/Vencord"
};
let DonorBadges = {} as Record<string, Pick<ProfileBadge, "image" | "description">[]>;
async function loadBadges(noCache = false) {
DonorBadges = {};
const init = {} as RequestInit;
if (noCache)
init.cache = "no-cache";
const badges = await fetch("https://gist.githubusercontent.com/Vendicated/51a3dd775f6920429ec6e9b735ca7f01/raw/badges.csv", init)
.then(r => r.text());
const lines = badges.trim().split("\n");
if (lines.shift() !== "id,tooltip,image") {
new Logger("BadgeAPI").error("Invalid badges.csv file!");
return;
}
for (const line of lines) {
const [id, description, image] = line.split(",");
(DonorBadges[id] ??= []).push({ image, description });
}
}
const DonorBadges = {} as Record<string, Pick<ProfileBadge, "image" | "description">>;
export default definePlugin({
name: "BadgeAPI",
@ -103,93 +82,93 @@ export default definePlugin({
}
],
toolboxActions: {
async "Refetch Badges"() {
await loadBadges(true);
Toasts.show({
id: Toasts.genId(),
message: "Successfully refetched badges!",
type: Toasts.Type.SUCCESS
});
}
},
async start() {
Vencord.Api.Badges.addBadge(ContributorBadge);
await loadBadges();
},
renderBadgeComponent: ErrorBoundary.wrap((badge: ProfileBadge & BadgeUserArgs) => {
const Component = badge.component!;
return <Component {...badge} />;
}, { noop: true }),
async start() {
Vencord.Api.Badges.addBadge(ContributorBadge);
const badges = await fetch("https://gist.githubusercontent.com/Vendicated/51a3dd775f6920429ec6e9b735ca7f01/raw/badges.csv").then(r => r.text());
const lines = badges.trim().split("\n");
if (lines.shift() !== "id,tooltip,image") {
new Logger("BadgeAPI").error("Invalid badges.csv file!");
return;
}
for (const line of lines) {
const [id, description, image] = line.split(",");
DonorBadges[id] = { image, description };
}
},
getDonorBadges(userId: string) {
return DonorBadges[userId]?.map(badge => ({
...badge,
position: BadgePosition.START,
props: {
style: {
borderRadius: "50%",
transform: "scale(0.9)" // The image is a bit too big compared to default badges
}
},
onClick() {
const modalKey = openModal(props => (
<ErrorBoundary noop onError={() => {
closeModal(modalKey);
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
}}>
<Modals.ModalRoot {...props}>
<Modals.ModalHeader>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<Forms.FormTitle
tag="h2"
style={{
width: "100%",
textAlign: "center",
margin: 0
}}
>
<Heart />
Vencord Donor
</Forms.FormTitle>
</Flex>
</Modals.ModalHeader>
<Modals.ModalContent>
<Flex>
<img
role="presentation"
src="https://cdn.discordapp.com/emojis/1026533070955872337.png"
alt=""
style={{ margin: "auto" }}
/>
<img
role="presentation"
src="https://cdn.discordapp.com/emojis/1026533090627174460.png"
alt=""
style={{ margin: "auto" }}
/>
</Flex>
<div style={{ padding: "1em" }}>
<Forms.FormText>
This Badge is a special perk for Vencord Donors
</Forms.FormText>
<Forms.FormText className={Margins.top20}>
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
</Forms.FormText>
</div>
</Modals.ModalContent>
<Modals.ModalFooter>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<DonateButton />
</Flex>
</Modals.ModalFooter>
</Modals.ModalRoot>
</ErrorBoundary>
));
},
}));
getDonorBadge(userId: string) {
const badge = DonorBadges[userId];
if (badge) {
return {
...badge,
position: BadgePosition.START,
props: {
style: {
borderRadius: "50%",
transform: "scale(0.9)" // The image is a bit too big compared to default badges
}
},
onClick() {
const modalKey = openModal(props => (
<ErrorBoundary noop onError={() => {
closeModal(modalKey);
VencordNative.ipc.invoke(IpcEvents.OPEN_EXTERNAL, "https://github.com/sponsors/Vendicated");
}}>
<Modals.ModalRoot {...props}>
<Modals.ModalHeader>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<Forms.FormTitle
tag="h2"
style={{
width: "100%",
textAlign: "center",
margin: 0
}}
>
<Heart />
Vencord Donor
</Forms.FormTitle>
</Flex>
</Modals.ModalHeader>
<Modals.ModalContent>
<Flex>
<img
role="presentation"
src="https://cdn.discordapp.com/emojis/1026533070955872337.png"
alt=""
style={{ margin: "auto" }}
/>
<img
role="presentation"
src="https://cdn.discordapp.com/emojis/1026533090627174460.png"
alt=""
style={{ margin: "auto" }}
/>
</Flex>
<div style={{ padding: "1em" }}>
<Forms.FormText>
This Badge is a special perk for Vencord Donors
</Forms.FormText>
<Forms.FormText className={Margins.top20}>
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
</Forms.FormText>
</div>
</Modals.ModalContent>
<Modals.ModalFooter>
<Flex style={{ width: "100%", justifyContent: "center" }}>
<DonateButton />
</Flex>
</Modals.ModalFooter>
</Modals.ModalRoot>
</ErrorBoundary>
));
},
};
}
}
});

View File

@ -22,29 +22,17 @@ import definePlugin from "@utils/types";
export default definePlugin({
name: "MessageEventsAPI",
description: "Api required by anything using message events.",
authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
authors: [Devs.Arjix, Devs.hunt],
patches: [
{
find: '"MessageActionCreators"',
replacement: {
// editMessage: function (...) {
match: /\beditMessage:(function\(.+?\))\{/,
// editMessage: async function (...) { await handlePreEdit(...); ...
replacement: [{
match: /_sendMessage:(function\([^)]+\)){/,
replace: "_sendMessage:async $1{if(await Vencord.Api.MessageEvents._handlePreSend(...arguments))return;"
}, {
match: /\beditMessage:(function\([^)]+\)){/,
replace: "editMessage:async $1{await Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
}
},
{
find: ".handleSendMessage=",
replacement: {
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
match: /(props\.chatInputType.+?\.then\(\()(function.+?var (\i)=\i\.\i\.parse\((\i),.+?var (\i)=\i\.\i\.getSendMessageOptionsForReply\(\i\);)(?<=\)\(({.+?})\)\.then.+?)/,
// props.chatInputType...then((async function(isMessageValid)... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); if(await Vencord.api...) return { shoudClear:true, shouldRefocus:true };
replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" +
`${rest1}async ${rest2}` +
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
"return{shoudClear:true,shouldRefocus:true};"
}
}]
},
{
find: '("interactionUsernameProfile',

View File

@ -32,7 +32,7 @@ export default definePlugin({
}
},
{
find: "Messages.SERVERS,children",
find: "Messages.SERVERS",
replacement: {
match: /(Messages\.SERVERS,children:)(.+?default:return null\}\}\)\))/,
replace: "$1Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($2)"

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/Settings";
import { Settings } from "@api/settings";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { findByPropsLazy, findStoreLazy } from "@webpack";

View File

@ -18,7 +18,7 @@
import "./betterFolders.css";
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";

View File

@ -16,8 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/Settings";
import { Settings } from "@api/settings";
import { Devs } from "@utils/constants";
import { makeLazy } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
export default definePlugin({
@ -29,7 +30,7 @@ export default definePlugin({
{
find: "hideNote:",
all: true,
predicate: () => Vencord.Settings.plugins.BetterNotesBox.hide,
predicate: makeLazy(() => Vencord.Settings.plugins.BetterNotesBox.hide),
replacement: {
match: /hideNote:.+?(?=[,}])/g,
replace: "hideNote:true",

View File

@ -16,11 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/Settings";
import { migratePluginSettings, Settings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { Clipboard, Toasts } from "@webpack/common";
migratePluginSettings("BetterRoleDot", "ClickableRoleDot");
export default definePlugin({
name: "BetterRoleDot",
authors: [Devs.Ven],
@ -29,10 +30,10 @@ export default definePlugin({
patches: [
{
find: ".dotBorderBase",
find: "M0 4C0 1.79086 1.79086 0 4 0H16C18.2091 0 20 1.79086 20 4V16C20 18.2091 18.2091 20 16 20H4C1.79086 20 0 18.2091 0 16V4Z",
replacement: {
match: /,viewBox:"0 0 20 20"/,
replace: "$&,onClick:()=>$self.copyToClipBoard(arguments[0].color),style:{cursor:'pointer'}",
match: /viewBox:"0 0 20 20"/,
replace: "$&,onClick:()=>$self.copyToClipBoard(e.color),style:{cursor:'pointer'}",
},
},
{

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/Settings";
import { Settings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/Settings";
import { Settings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";

View File

@ -23,6 +23,7 @@ import {
removePreEditListener,
removePreSendListener
} from "@api/MessageEvents";
import { migratePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
@ -32,6 +33,7 @@ import { defaultRules } from "./defaultRules";
const reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
const reHasRegExpChar = RegExp(reRegExpChar.source);
migratePluginSettings("ClearURLs", "clearURLs");
export default definePlugin({
name: "ClearURLs",
description: "Removes tracking garbage from URLs",

View File

@ -17,9 +17,9 @@
*/
import { showNotification } from "@api/Notifications";
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
import { closeAllModals } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import { maybePromptToUpdate } from "@utils/updater";

View File

@ -16,11 +16,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/settings";
import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
import { isTruthy } from "@utils/guards";
import { useAwaiter } from "@utils/react";
import { useAwaiter } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
import {

View File

@ -16,13 +16,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { showNotification } from "@api/Notifications";
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
import definePlugin, { OptionType } from "@utils/types";
import { filters, findAll, search } from "@webpack";
import { Menu } from "@webpack/common";
const PORT = 8485;
const NAV_ID = "dev-companion-reconnect";
@ -236,25 +238,33 @@ function initWs(isManual = false) {
});
}
const contextMenuPatch: NavContextMenuPatchCallback = children => () => {
children.unshift(
<Menu.MenuItem
id={NAV_ID}
label="Reconnect Dev Companion"
action={() => {
socket?.close(1000, "Reconnecting");
initWs(true);
}}
/>
);
};
export default definePlugin({
name: "DevCompanion",
description: "Dev Companion Plugin",
authors: [Devs.Ven],
settings,
toolboxActions: {
"Reconnect"() {
socket?.close(1000, "Reconnecting");
initWs(true);
}
},
start() {
initWs();
addContextMenuPatch("user-settings-cog", contextMenuPatch);
},
stop() {
socket?.close(1000, "Plugin Stopped");
socket = void 0;
removeContextMenuPatch("user-settings-cog", contextMenuPatch);
}
});

View File

@ -19,7 +19,7 @@
import { addContextMenuPatch, findGroupChildrenByChildId, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { CheckedTextInput } from "@components/CheckedTextInput";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
import { Margins } from "@utils/margins";
import { ModalContent, ModalHeader, ModalRoot, openModal } from "@utils/modal";
import definePlugin from "@utils/types";

View File

@ -16,11 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { ErrorCard } from "@components/ErrorCard";
import { definePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Forms, React } from "@webpack/common";
@ -90,13 +87,6 @@ export default definePlugin({
match: /"staging"===window\.GLOBAL_ENV\.RELEASE_CHANNEL/,
replace: "true"
}
},
{
find: 'H1,title:"Experiments"',
replacement: {
match: 'title:"Experiments",children:[',
replace: "$&$self.WarningCard(),"
}
}
],
@ -119,19 +109,5 @@ export default definePlugin({
</Forms.FormText>
</React.Fragment>
);
},
WarningCard: ErrorBoundary.wrap(() => (
<ErrorCard id="vc-experiments-warning-card" className={Margins.bottom16}>
<Forms.FormTitle tag="h2">Hold on!!</Forms.FormTitle>
<Forms.FormText>
Experiments are unreleased Discord features. They might not work, or even break your client or get your account disabled.
</Forms.FormText>
<Forms.FormText className={Margins.top8}>
Only use experiments if you know what you're doing. Vencord is not responsible for any damage caused by enabling experiments.
</Forms.FormText>
</ErrorCard>
), { noop: true })
}
});

View File

@ -17,11 +17,11 @@
*/
import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
import { definePluginSettings, Settings } from "@api/Settings";
import { definePluginSettings, migratePluginSettings, Settings } from "@api/settings";
import { Devs } from "@utils/constants";
import { ApngBlendOp, ApngDisposeOp, getGifEncoder, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord";
import { proxyLazy } from "@utils/lazy";
import { proxyLazy } from "@utils/proxyLazy";
import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { ChannelStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common";
@ -149,6 +149,8 @@ const settings = definePluginSettings({
}
});
migratePluginSettings("FakeNitro", "NitroBypass");
export default definePlugin({
name: "FakeNitro",
authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.obscurity, Devs.captain, Devs.Nuckyz, Devs.AutumnVN],
@ -641,7 +643,7 @@ export default definePlugin({
if (!settings.enableStickerBypass)
break stickerBypass;
const sticker = StickerStore.getStickerById(extra.stickers?.[0]!);
const sticker = StickerStore.getStickerById(extra?.stickerIds?.[0]!);
if (!sticker)
break stickerBypass;
@ -663,7 +665,7 @@ export default definePlugin({
link = `https://distok.top/stickers/${packId}/${sticker.id}.gif`;
}
extra.stickers!.length = 0;
delete extra.stickerIds;
messageObj.content += " " + link + `&name=${encodeURIComponent(sticker.name)}`;
}
}

View File

@ -17,7 +17,7 @@
*/
// This plugin is a port from Alyxia's Vendetta plugin
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";

View File

@ -17,7 +17,7 @@
*/
import { ApplicationCommandOptionType } from "@api/Commands";
import { Settings } from "@api/Settings";
import { Settings } from "@api/settings";
import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";

View File

@ -1,6 +1,6 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors
* Copyright (c) 2022 Samu
*
* 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
@ -16,22 +16,27 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { addPreSendListener, MessageObject, removePreSendListener } from "@api/MessageEvents";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
export default definePlugin({
name: "ShowAllMessageButtons",
description: "Always show all message buttons no matter if you are holding the shift key or not.",
authors: [Devs.Nuckyz],
const re = /https?:\/\/twitter\.com(?=\/\w+?\/status\/)/g;
patches: [
{
find: ".Messages.MESSAGE_UTILITIES_A11Y_LABEL",
replacement: {
// isExpanded: V, (?<=,V = shiftKeyDown && !H...;)
match: /isExpanded:(\i),(?<=,\1=\i&&(!.+);.+?)/,
replace: "isExpanded:$2,"
}
}
]
export default definePlugin({
name: "FxTwitter",
description: "Uses FxTwitter to improve embeds from twitter on send",
authors: [Devs.Samu],
dependencies: ["MessageEventsAPI"],
addPrefix(msg: MessageObject) {
msg.content = msg.content.replace(re, "https://fxtwitter.com");
},
start() {
this.preSend = addPreSendListener((_, msg) => this.addPrefix(msg));
},
stop() {
removePreSendListener(this.preSend);
}
});

View File

@ -19,7 +19,7 @@
import * as DataStore from "@api/DataStore";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { useForceUpdater } from "@utils/react";
import { useForceUpdater } from "@utils/misc";
import definePlugin from "@utils/types";
import { findByPropsLazy, findStoreLazy } from "@webpack";
import { Tooltip } from "webpack/common";

View File

@ -17,7 +17,7 @@
*/
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/settings";
import { disableStyle, enableStyle } from "@api/Styles";
import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants";

View File

@ -17,11 +17,10 @@
*/
import { registerCommand, unregisterCommand } from "@api/Commands";
import { Settings } from "@api/Settings";
import { Logger } from "@utils/Logger";
import { Settings } from "@api/settings";
import IpcEvents from "@utils/IpcEvents";
import Logger from "@utils/Logger";
import { Patch, Plugin } from "@utils/types";
import { FluxDispatcher } from "@webpack/common";
import { FluxEvents } from "@webpack/types";
import Plugins from "~plugins";
@ -65,7 +64,7 @@ for (const p of pluginsValues) if (settings[p.name]?.enabled) {
});
}
for (const p of pluginsValues) {
function initPlugin(p: Plugin) {
if (p.settings) {
p.settings.pluginName = p.name;
p.options ??= {};
@ -84,6 +83,9 @@ for (const p of pluginsValues) {
}
}
}
for (const p of pluginsValues) {
initPlugin(p);
}
export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins() {
for (const name in Plugins)
@ -99,9 +101,9 @@ export function startDependenciesRecursive(p: Plugin) {
if (!Settings.plugins[dep].enabled) {
startDependenciesRecursive(Plugins[dep]);
// If the plugin has patches, don't start the plugin, just enable it.
Settings.plugins[dep].enabled = true;
if (Plugins[dep].patches) {
logger.warn(`Enabling dependency ${dep} requires restart.`);
Settings.plugins[dep].enabled = true;
restartNeeded = true;
return;
}
@ -113,64 +115,56 @@ export function startDependenciesRecursive(p: Plugin) {
}
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
const { name, commands, flux } = p;
if (p.start) {
logger.info("Starting plugin", name);
logger.info("Starting plugin", p.name);
if (p.started) {
logger.warn(`${name} already started`);
logger.warn(`${p.name} already started`);
return false;
}
try {
p.start();
p.started = true;
} catch (e) {
logger.error(`Failed to start ${name}\n`, e);
logger.error(`Failed to start ${p.name}\n`, e);
return false;
}
}
if (commands?.length) {
logger.info("Registering commands of plugin", name);
for (const cmd of commands) {
if (p.commands?.length) {
logger.info("Registering commands of plugin", p.name);
for (const cmd of p.commands) {
try {
registerCommand(cmd, name);
registerCommand(cmd, p.name);
} catch (e) {
logger.error(`Failed to register command ${cmd.name}\n`, e);
return false;
}
}
}
if (flux) {
for (const event in flux) {
FluxDispatcher.subscribe(event as FluxEvents, flux[event]);
}
}
return true;
}, p => `startPlugin ${p.name}`);
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
const { name, commands, flux } = p;
if (p.stop) {
logger.info("Stopping plugin", name);
logger.info("Stopping plugin", p.name);
if (!p.started) {
logger.warn(`${name} already stopped`);
logger.warn(`${p.name} already stopped`);
return false;
}
try {
p.stop();
p.started = false;
} catch (e) {
logger.error(`Failed to stop ${name}\n`, e);
logger.error(`Failed to stop ${p.name}\n`, e);
return false;
}
}
if (commands?.length) {
logger.info("Unregistering commands of plugin", name);
for (const cmd of commands) {
if (p.commands?.length) {
logger.info("Unregistering commands of plugin", p.name);
for (const cmd of p.commands) {
try {
unregisterCommand(cmd.name);
} catch (e) {
@ -180,11 +174,20 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu
}
}
if (flux) {
for (const event in flux) {
FluxDispatcher.unsubscribe(event as FluxEvents, flux[event]);
}
}
return true;
}, p => `stopPlugin ${p.name}`);
export function loadExternalPlugins() {
for (const [name, src] of VencordNative.ipc.sendSync(IpcEvents.GET_PLUGINS)) {
try {
const p = Function(src)().default;
if (!p.name || Object.prototype.hasOwnProperty.call(Plugins, p.name))
throw new Error("Invalid plugin or name conflicts with existing plugin");
Plugins[p.name] = p;
initPlugin(p);
} catch (err) {
logger.error(`Failed to load plugin ${name}`, err);
}
}
}

View File

@ -17,7 +17,7 @@
*/
import { addButton, removeButton } from "@api/MessagePopover";
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { getStegCloak } from "@utils/dependencies";
@ -85,7 +85,7 @@ function ChatBarIcon() {
onMouseLeave={onMouseLeave}
innerClassName={ButtonWrapperClasses.button}
onClick={() => buildEncModal()}
style={{ padding: "0 2px", scale: "0.9" }}
style={{ padding: "0 4px" }}
>
<div className={ButtonWrapperClasses.buttonWrapper}>
<svg

View File

@ -19,7 +19,7 @@
import * as DataStore from "@api/DataStore";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { ChannelStore, NavigationRouter, SelectedChannelStore, SelectedGuildStore } from "@webpack/common";
import { ChannelStore, FluxDispatcher, NavigationRouter, SelectedChannelStore, SelectedGuildStore } from "@webpack/common";
export interface LogoutEvent {
type: "LOGOUT";
@ -37,54 +37,63 @@ interface PreviousChannel {
channelId: string | null;
}
let isSwitchingAccount = false;
let previousCache: PreviousChannel | undefined;
function attemptToNavigateToChannel(guildId: string | null, channelId: string) {
if (!ChannelStore.hasChannel(channelId)) return;
NavigationRouter.transitionTo(`/channels/${guildId ?? "@me"}/${channelId}`);
}
export default definePlugin({
name: "KeepCurrentChannel",
description: "Attempt to navigate to the channel you were in before switching accounts or loading Discord.",
authors: [Devs.Nuckyz],
flux: {
LOGOUT(e: LogoutEvent) {
({ isSwitchingAccount } = e);
},
isSwitchingAccount: false,
previousCache: {} as PreviousChannel,
CONNECTION_OPEN() {
if (!isSwitchingAccount) return;
isSwitchingAccount = false;
attemptToNavigateToChannel(guildId: string | null, channelId: string) {
if (!ChannelStore.hasChannel(channelId)) return;
NavigationRouter.transitionTo(`/channels/${guildId ?? "@me"}/${channelId}`);
},
if (previousCache?.channelId)
attemptToNavigateToChannel(previousCache.guildId, previousCache.channelId);
},
onLogout(e: LogoutEvent) {
this.isSwitchingAccount = e.isSwitchingAccount;
},
async CHANNEL_SELECT({ guildId, channelId }: ChannelSelectEvent) {
if (isSwitchingAccount) return;
onConnectionOpen() {
if (!this.isSwitchingAccount) return;
this.isSwitchingAccount = false;
previousCache = {
guildId,
channelId
};
await DataStore.set("KeepCurrentChannel_previousData", previousCache);
}
if (this.previousCache.channelId) this.attemptToNavigateToChannel(this.previousCache.guildId, this.previousCache.channelId);
},
async onChannelSelect({ guildId, channelId }: ChannelSelectEvent) {
if (this.isSwitchingAccount) return;
this.previousCache = {
guildId,
channelId
};
await DataStore.set("KeepCurrentChannel_previousData", this.previousCache);
},
async start() {
previousCache = await DataStore.get<PreviousChannel>("KeepCurrentChannel_previousData");
if (!previousCache) {
previousCache = {
const previousData = await DataStore.get<PreviousChannel>("KeepCurrentChannel_previousData");
if (previousData) {
this.previousCache = previousData;
if (this.previousCache.channelId) this.attemptToNavigateToChannel(this.previousCache.guildId, this.previousCache.channelId);
} else {
this.previousCache = {
guildId: SelectedGuildStore.getGuildId(),
channelId: SelectedChannelStore.getChannelId() ?? null
};
await DataStore.set("KeepCurrentChannel_previousData", previousCache);
} else if (previousCache.channelId) {
attemptToNavigateToChannel(previousCache.guildId, previousCache.channelId);
await DataStore.set("KeepCurrentChannel_previousData", this.previousCache);
}
FluxDispatcher.subscribe("LOGOUT", this.onLogout.bind(this));
FluxDispatcher.subscribe("CONNECTION_OPEN", this.onConnectionOpen.bind(this));
FluxDispatcher.subscribe("CHANNEL_SELECT", this.onChannelSelect.bind(this));
},
stop() {
FluxDispatcher.unsubscribe("LOGOUT", this.onLogout);
FluxDispatcher.unsubscribe("CONNECTION_OPEN", this.onConnectionOpen);
FluxDispatcher.unsubscribe("CHANNEL_SELECT", this.onChannelSelect);
}
});

View File

@ -16,10 +16,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/settings";
import { Link } from "@components/Link";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
import { FluxDispatcher, Forms } from "@webpack/common";

View File

@ -20,34 +20,31 @@ import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Devs } from "@utils/constants";
import { getCurrentChannel } from "@utils/discord";
import { useForceUpdater } from "@utils/misc";
import definePlugin from "@utils/types";
import { findStoreLazy } from "@webpack";
import { SelectedChannelStore, Tooltip, useStateFromStores } from "@webpack/common";
import { FluxStore } from "@webpack/types";
import { FluxDispatcher, Tooltip } from "@webpack/common";
const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore") as FluxStore & { getMemberCount(guildId: string): number | null; };
const ChannelMemberStore = findStoreLazy("ChannelMemberStore") as FluxStore & {
getProps(guildId: string, channelId: string): { groups: { count: number; id: string; }[]; };
};
const counts = {} as Record<string, [number, number]>;
let forceUpdate: () => void;
const GuildMemberCountStore = findStoreLazy("GuildMemberCountStore");
function MemberCount() {
const { id: channelId, guild_id: guildId } = useStateFromStores([SelectedChannelStore], () => getCurrentChannel());
const { groups } = useStateFromStores(
[ChannelMemberStore],
() => ChannelMemberStore.getProps(guildId, channelId)
);
const total = useStateFromStores(
[GuildMemberCountStore],
() => GuildMemberCountStore.getMemberCount(guildId)
);
const guildId = getCurrentChannel().guild_id;
const c = counts[guildId];
if (total == null)
return null;
forceUpdate = useForceUpdater();
const online =
(groups.length === 1 && groups[0].id === "unknown")
? 0
: groups.reduce((count, curr) => count + (curr.id === "offline" ? 0 : curr.count), 0);
if (!c) return null;
let total = c[0].toLocaleString();
if (total === "0" && c[1] > 0) {
const approx = GuildMemberCountStore.getMemberCount(guildId);
total = approx ? approx.toLocaleString() : "Loading...";
}
const online = c[1].toLocaleString();
return (
<Flex id="vc-membercount" style={{
@ -58,7 +55,7 @@ function MemberCount() {
alignContent: "center",
gap: 0
}}>
<Tooltip text={`${online} Online in this Channel`} position="bottom">
<Tooltip text={`${online} Online`} position="bottom">
{props => (
<div {...props}>
<span
@ -75,7 +72,7 @@ function MemberCount() {
</div>
)}
</Tooltip>
<Tooltip text={`${total} Total Server Members`} position="bottom">
<Tooltip text={`${total} Total Members`} position="bottom">
{props => (
<div {...props}>
<span
@ -105,10 +102,37 @@ export default definePlugin({
patches: [{
find: ".isSidebarVisible,",
replacement: {
match: /(var (\i)=\i\.className.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
match: /(var (.)=.\.className.+?children):\[(.\.useMemo[^}]+"aria-multiselectable")/,
replace: "$1:[$2.startsWith('members')?$self.render():null,$3"
}
}],
render: ErrorBoundary.wrap(MemberCount, { noop: true })
onGuildMemberListUpdate({ guildId, groups, memberCount, id }) {
// eeeeeh - sometimes it has really wrong counts??? like 10 times less than actual
// but if we only listen to everyone updates, sometimes we never get the count?
// this seems to work but isn't optional
if (id !== "everyone" && counts[guildId]) return;
let count = 0;
for (const group of groups) {
if (group.id !== "offline")
count += group.count;
}
counts[guildId] = [memberCount, count];
forceUpdate?.();
},
start() {
FluxDispatcher.subscribe("GUILD_MEMBER_LIST_UPDATE", this.onGuildMemberListUpdate);
},
stop() {
FluxDispatcher.unsubscribe("GUILD_MEMBER_LIST_UPDATE", this.onGuildMemberListUpdate);
},
render: () => (
<ErrorBoundary noop>
<MemberCount />
</ErrorBoundary>
)
});

View File

@ -17,6 +17,7 @@
*/
import { addClickListener, removeClickListener } from "@api/MessageEvents";
import { migratePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
@ -28,6 +29,8 @@ const keyup = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed =
const MANAGE_CHANNELS = 1n << 4n;
migratePluginSettings("MessageClickActions", "MessageQuickActions");
export default definePlugin({
name: "MessageClickActions",
description: "Hold Backspace and click to delete, double click to edit",

View File

@ -17,12 +17,11 @@
*/
import { addAccessory } from "@api/MessageAccessories";
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants.js";
import { classes } from "@utils/misc";
import { classes, LazyComponent } from "@utils/misc";
import { Queue } from "@utils/Queue";
import { LazyComponent } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
import { find, findByCode, findByPropsLazy } from "@webpack";
import {
@ -52,7 +51,7 @@ const SearchResultClasses = findByPropsLazy("message", "searchResult");
let AutoModEmbed: React.ComponentType<any> = () => null;
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
const tenorRegex = /https:\/\/(?:www.)?tenor\.com/;
interface Attachment {
height: number;

View File

@ -19,11 +19,11 @@
import "./messageLogger.css";
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
import { Settings } from "@api/Settings";
import { Settings } from "@api/settings";
import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import Logger from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { FluxDispatcher, i18n, Menu, moment, Parser, Timestamp, UserStore } from "@webpack/common";
@ -51,28 +51,11 @@ const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) =
if (!deleted && !editHistory?.length) return;
toggle: {
if (!deleted) break toggle;
const domElement = document.getElementById(`chat-messages-${channel_id}-${id}`);
if (!domElement) break toggle;
children.push((
<Menu.MenuItem
id={TOGGLE_DELETE_STYLE_ID}
key={TOGGLE_DELETE_STYLE_ID}
label="Toggle Deleted Highlight"
action={() => domElement.classList.toggle("messagelogger-deleted")}
/>
));
}
children.push((
<Menu.MenuItem
id={REMOVE_HISTORY_ID}
key={REMOVE_HISTORY_ID}
label="Remove Message History"
color="danger"
action={() => {
if (deleted) {
FluxDispatcher.dispatch({
@ -87,6 +70,20 @@ const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) =
}}
/>
));
if (!deleted) return;
const domElement = document.getElementById(`chat-messages-${channel_id}-${id}`);
if (!domElement) return;
children.push((
<Menu.MenuItem
id={TOGGLE_DELETE_STYLE_ID}
key={TOGGLE_DELETE_STYLE_ID}
label="Toggle Deleted Highlight"
action={() => domElement.classList.toggle("messagelogger-deleted")}
/>
));
};
export default definePlugin({

View File

@ -18,7 +18,7 @@
import { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, registerCommand, sendBotMessage, unregisterCommand } from "@api/Commands";
import * as DataStore from "@api/DataStore";
import { Settings } from "@api/Settings";
import { Settings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";

View File

@ -17,9 +17,11 @@
*/
import { findOption, OptionalMessageOption } from "@api/Commands";
import { migratePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
migratePluginSettings("MoreKaomoji", "moarKaomojis");
export default definePlugin({
name: "MoreKaomoji",
description: "Adds more Kaomoji to discord. ヽ(´▽`)/",

View File

@ -16,9 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import { proxyLazy } from "@utils/lazy.js";
import { proxyLazy } from "@utils/proxyLazy.js";
import definePlugin, { OptionType } from "@utils/types";
import { find, findByPropsLazy } from "@webpack";
import { ChannelStore, GuildStore } from "@webpack/common";

View File

@ -16,12 +16,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/settings";
import { makeRange } from "@components/PluginSettings/components/SettingSliderComponent";
import { Devs } from "@utils/constants";
import { sleep } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types";
import { SelectedChannelStore, UserStore } from "@webpack/common";
import { FluxDispatcher, SelectedChannelStore, UserStore } from "@webpack/common";
import { Message, ReactionEmoji } from "discord-types/general";
interface IMessageCreate {
@ -80,40 +80,50 @@ export default definePlugin({
description: "🗿🗿🗿🗿🗿🗿🗿🗿",
settings,
flux: {
async MESSAGE_CREATE({ optimistic, type, message, channelId }: IMessageCreate) {
if (optimistic || type !== "MESSAGE_CREATE") return;
if (message.state === "SENDING") return;
if (settings.store.ignoreBots && message.author?.bot) return;
if (!message.content) return;
if (channelId !== SelectedChannelStore.getChannelId()) return;
async onMessage(e: IMessageCreate) {
if (e.optimistic || e.type !== "MESSAGE_CREATE") return;
if (e.message.state === "SENDING") return;
if (settings.store.ignoreBots && e.message.author?.bot) return;
if (!e.message.content) return;
if (e.channelId !== SelectedChannelStore.getChannelId()) return;
const moyaiCount = getMoyaiCount(message.content);
for (let i = 0; i < moyaiCount; i++) {
boom();
await sleep(300);
}
},
MESSAGE_REACTION_ADD({ optimistic, type, channelId, userId, emoji }: IReactionAdd) {
if (optimistic || type !== "MESSAGE_REACTION_ADD") return;
if (settings.store.ignoreBots && UserStore.getUser(userId)?.bot) return;
if (channelId !== SelectedChannelStore.getChannelId()) return;
const name = emoji.name.toLowerCase();
if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return;
boom();
},
VOICE_CHANNEL_EFFECT_SEND({ emoji }: IVoiceChannelEffectSendEvent) {
if (!emoji?.name) return;
const name = emoji.name.toLowerCase();
if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return;
const moyaiCount = getMoyaiCount(e.message.content);
for (let i = 0; i < moyaiCount; i++) {
boom();
await sleep(300);
}
},
onReaction(e: IReactionAdd) {
if (e.optimistic || e.type !== "MESSAGE_REACTION_ADD") return;
if (settings.store.ignoreBots && UserStore.getUser(e.userId)?.bot) return;
if (e.channelId !== SelectedChannelStore.getChannelId()) return;
const name = e.emoji.name.toLowerCase();
if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return;
boom();
},
onVoiceChannelEffect(e: IVoiceChannelEffectSendEvent) {
if (!e.emoji?.name) return;
const name = e.emoji.name.toLowerCase();
if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return;
boom();
},
start() {
FluxDispatcher.subscribe("MESSAGE_CREATE", this.onMessage);
FluxDispatcher.subscribe("MESSAGE_REACTION_ADD", this.onReaction);
FluxDispatcher.subscribe("VOICE_CHANNEL_EFFECT_SEND", this.onVoiceChannelEffect);
},
stop() {
FluxDispatcher.unsubscribe("MESSAGE_CREATE", this.onMessage);
FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD", this.onReaction);
FluxDispatcher.unsubscribe("VOICE_CHANNEL_EFFECT_SEND", this.onVoiceChannelEffect);
}
});

View File

@ -16,10 +16,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
import { ModalContent, ModalFooter, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import definePlugin, { OptionType } from "@utils/types";
import definePlugin from "@utils/types";
import { findByProps, findStoreLazy } from "@webpack";
import { Button, Text } from "@webpack/common";
@ -50,28 +49,10 @@ function NoDMNotificationsModal({ modalProps }: { modalProps: ModalProps; }) {
);
}
const settings = definePluginSettings({
guild: {
description: "Mute Guild",
type: OptionType.BOOLEAN,
default: true
},
everyone: {
description: "Suppress @everyone and @here",
type: OptionType.BOOLEAN,
default: true
},
role: {
description: "Suppress All Role @mentions",
type: OptionType.BOOLEAN,
default: true
},
});
export default definePlugin({
name: "MuteNewGuild",
description: "Mutes newly joined guilds",
authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince],
authors: [Devs.Glitch, Devs.Nuckyz],
patches: [
{
find: ",acceptInvite:function",
@ -81,17 +62,10 @@ export default definePlugin({
}
}
],
settings,
handleMute(guildId: string | null) {
if (guildId === "@me" || guildId === "null" || guildId == null) return;
findByProps("updateGuildNotificationSettings").updateGuildNotificationSettings(guildId,
{
muted: settings.store.guild,
suppress_everyone: settings.store.everyone,
suppress_roles: settings.store.role
}
);
findByProps("updateGuildNotificationSettings").updateGuildNotificationSettings(guildId, { muted: true, suppress_everyone: true, suppress_roles: true });
},
start() {

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/Settings";
import { Settings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";

View File

@ -0,0 +1,59 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Samu and Linnea Gräf
*
* 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 { addPreSendListener, MessageObject, removePreSendListener } from "@api/MessageEvents";
import { Settings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
export default definePlugin({
name: "NoCanaryMessageLinks",
description: "Allows you to change/remove the subdomain of discord message and channel links",
authors: [Devs.Samu, Devs.nea],
options: {
linkPrefix: {
description: "The subdomain for your discord message links",
type: OptionType.STRING,
default: "",
restartNeeded: false,
},
alwaysUseDiscordHost: {
description: "Always use discord.com host (replace discordapp.com)",
type: OptionType.BOOLEAN,
default: false,
restartNeeded: false,
},
},
dependencies: ["MessageEventsAPI"],
removeBetas(msg: MessageObject) {
const settings = Settings.plugins.NoCanaryMessageLinks;
msg.content = msg.content.replace(
/https:\/\/(?:canary\.|ptb\.)?(discord(?:app)?\.com)(\/channels\/(?:\d{17,20}|@me)\/\d{17,20}(?:\/\d{17,20})?)/g,
(_, host, path) => "https://" + (settings.linkPrefix ? settings.linkPrefix + "." : "") + (settings.alwaysUseDiscordHost ? "discord.com" : host) + path
);
},
start() {
this.preSend = addPreSendListener((_, msg) => this.removeBetas(msg));
},
stop() {
removePreSendListener(this.preSend);
}
});

View File

@ -16,9 +16,12 @@
* 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";
migratePluginSettings("NoDevtoolsWarning", "STFU");
export default definePlugin({
name: "NoDevtoolsWarning",
description: "Disables the 'HOLD UP' banner in the console. As a side effect, also prevents Discord from hiding your token, which prevents random logouts.",

View File

@ -16,9 +16,11 @@
* 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";
migratePluginSettings("NoF1", "No F1");
export default definePlugin({
name: "NoF1",
description: "Disables F1 help bind.",

View File

@ -16,9 +16,11 @@
* 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";
migratePluginSettings("NoRPC", "No RPC");
export default definePlugin({
name: "NoRPC",
description: "Disables Discord's RPC server.",

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings } from "@api/Settings";
import { definePluginSettings } from "@api/settings";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
import type { Message } from "discord-types/general";

View File

@ -19,7 +19,7 @@
import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, CommandContext, findOption, sendBotMessage } from "@api/Commands";
import { Devs } from "@utils/constants";
import { getGifEncoder } from "@utils/dependencies";
import { makeLazy } from "@utils/lazy";
import { makeLazy } from "@utils/misc";
import definePlugin from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack";

View File

@ -21,7 +21,7 @@ import definePlugin from "@utils/types";
import { Channel } from "discord-types/general";
import { addContextMenus, removeContextMenus } from "./contextMenus";
import { getPinAt, isPinned, settings, snapshotArray, sortedSnapshot, usePinnedDms } from "./settings";
import { getPinAt, isPinned, settings, snapshotArray, usePinnedDms } from "./settings";
export default definePlugin({
name: "PinDMs",
@ -44,7 +44,7 @@ export default definePlugin({
},
isPinned,
getSnapshot: sortedSnapshot,
getSnapshot: () => snapshotArray,
getScrollOffset(channelId: string, rowHeight: number, padding: number, preRenderedChildren: number, originalOffset: number) {
if (!isPinned(channelId))

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { definePluginSettings, Settings, useSettings } from "@api/Settings";
import { definePluginSettings, Settings, useSettings } from "@api/settings";
import { OptionType } from "@utils/types";
import { findStoreLazy } from "@webpack";
@ -71,7 +71,7 @@ export function togglePin(id: string) {
save([...snapshot]);
}
export function sortedSnapshot() {
function sortedSnapshot() {
requireSnapshot();
if (settings.store.pinOrder === PinOrder.LastMessage)
return PrivateChannelSortStore.getPrivateChannelIds().filter(isPinned);

View File

@ -19,7 +19,7 @@
import { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges";
import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
import { addDecoration, removeDecoration } from "@api/MessageDecorations";
import { Settings } from "@api/Settings";
import { Settings } from "@api/settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import definePlugin, { OptionType } from "@utils/types";
@ -30,7 +30,7 @@ import { User } from "discord-types/general";
const SessionsStore = findStoreLazy("SessionsStore");
function Icon(path: string, viewBox = "0 0 24 24") {
return ({ color, tooltip, wantMargin }: { color: string; tooltip: string; wantMargin: boolean; }) => (
return ({ color, tooltip }: { color: string; tooltip: string; }) => (
<Tooltip text={tooltip} >
{(tooltipProps: any) => (
<svg
@ -39,12 +39,6 @@ function Icon(path: string, viewBox = "0 0 24 24") {
width="20"
viewBox={viewBox}
fill={color}
style={{
marginLeft: wantMargin ? 4 : 0,
verticalAlign: "top",
position: "relative",
top: wantMargin ? 1 : 0,
}}
>
<path d={path} />
</svg>
@ -63,16 +57,16 @@ type Platform = keyof typeof Icons;
const getStatusColor = findByCodeLazy(".TWITCH", ".STREAMING", ".INVISIBLE");
const PlatformIcon = ({ platform, status, wantMargin }: { platform: Platform, status: string; wantMargin: boolean; }) => {
const PlatformIcon = ({ platform, status }: { platform: Platform, status: string; }) => {
const tooltip = platform[0].toUpperCase() + platform.slice(1);
const Icon = Icons[platform] ?? Icons.desktop;
return <Icon color={`var(--${getStatusColor(status)}`} tooltip={tooltip} wantMargin={wantMargin} />;
return <Icon color={`var(--${getStatusColor(status)}`} tooltip={tooltip} />;
};
const getStatus = (id: string): Record<Platform, string> => PresenceStore.getState()?.clientStatuses?.[id];
const PlatformIndicator = ({ user, wantMargin = true }: { user: User; wantMargin?: boolean; }) => {
const PlatformIndicator = ({ user, inline = false, marginLeft = "4px" }: { user: User; inline?: boolean; marginLeft?: string; }) => {
if (!user || user.bot) return null;
if (user.id === UserStore.getCurrentUser().id) {
@ -105,21 +99,29 @@ const PlatformIndicator = ({ user, wantMargin = true }: { user: User; wantMargin
key={platform}
platform={platform as Platform}
status={status}
wantMargin={wantMargin}
/>
));
if (!icons.length) return null;
return (
<span className="vc-platform-indicator">
<div
className="vc-platform-indicator"
style={{
marginLeft,
gap: "4px",
display: inline ? "inline-flex" : "flex",
alignItems: "center",
transform: inline ? "translateY(4px)" : undefined
}}
>
{icons}
</span>
</div>
);
};
const badge: ProfileBadge = {
component: p => <PlatformIndicator {...p} wantMargin={false} />,
component: p => <PlatformIndicator {...p} marginLeft="" />,
position: BadgePosition.START,
shouldShow: userInfo => !!Object.keys(getStatus(userInfo.user.id) ?? {}).length,
key: "indicator"
@ -144,7 +146,7 @@ const indicatorLocations = {
description: "Inside messages",
onEnable: () => addDecoration("platform-indicator", props =>
<ErrorBoundary noop>
<PlatformIndicator user={props.message?.author} />
<PlatformIndicator user={props.message?.author} inline />
</ErrorBoundary>
),
onDisable: () => removeDecoration("platform-indicator")

View File

@ -16,59 +16,66 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/settings";
import { classes } from "@utils/misc";
import { findByPropsLazy } from "@webpack";
import { UserStore } from "@webpack/common";
import { Message } from "discord-types/general";
import { useFormattedPronouns } from "../pronoundbUtils";
import { settings } from "../settings";
import { awaitAndFormatPronouns } from "../pronoundbUtils";
const styles: Record<string, string> = findByPropsLazy("timestampInline");
function shouldShow(message: Message): boolean {
if (!settings.store.showInMessages)
// Respect showInMessages
if (!Settings.plugins.PronounDB.showInMessages)
return false;
// Don't bother fetching bot or system users
if (message.author.bot || message.author.system)
return false;
if (!settings.store.showSelf && message.author.id === UserStore.getCurrentUser().id)
// Respect showSelf options
if (!Settings.plugins.PronounDB.showSelf && message.author.id === UserStore.getCurrentUser().id)
return false;
return true;
}
export function PronounsChatComponentWrapper({ message }: { message: Message; }) {
return shouldShow(message)
? <PronounsChatComponent message={message} />
: null;
if (!shouldShow(message))
return null;
return <PronounsChatComponent message={message} />;
}
export function CompactPronounsChatComponentWrapper({ message }: { message: Message; }) {
return shouldShow(message)
? <CompactPronounsChatComponent message={message} />
: null;
if (!shouldShow(message))
return null;
return <CompactPronounsChatComponent message={message} />;
}
function PronounsChatComponent({ message }: { message: Message; }) {
const result = useFormattedPronouns(message.author.id);
return result
? (
const result = awaitAndFormatPronouns(message.author.id);
if (result != null) {
return (
<span
className={classes(styles.timestampInline, styles.timestamp)}
> {result}</span>
)
: null;
);
}
return null;
}
export function CompactPronounsChatComponent({ message }: { message: Message; }) {
const result = useFormattedPronouns(message.author.id);
return result
? (
const result = awaitAndFormatPronouns(message.author.id);
if (result != null) {
return (
<span
className={classes(styles.timestampInline, styles.timestamp, "vc-pronoundb-compact")}
> {result}</span>
)
: null;
);
}
return null;
}

View File

@ -0,0 +1,61 @@
/*
* Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import { Settings } from "@api/settings";
import { UserStore } from "@webpack/common";
import { awaitAndFormatPronouns } from "../pronoundbUtils";
import { UserProfilePronounsProps, UserProfileProps } from "../types";
export default function PronounsProfileWrapper(PronounsComponent: React.ElementType<UserProfilePronounsProps>, props: UserProfilePronounsProps, profileProps: UserProfileProps) {
const user = UserStore.getUser(profileProps.userId) ?? {};
// Respect showInProfile
if (!Settings.plugins.PronounDB.showInProfile)
return null;
// Don't bother fetching bot or system users
if (user.bot || user.system)
return null;
// Respect showSelf options
if (!Settings.plugins.PronounDB.showSelf && user.id === UserStore.getCurrentUser().id)
return null;
return <ProfilePronouns
userId={profileProps.userId}
Component={PronounsComponent}
leProps={props}
/>;
}
function ProfilePronouns(
{ userId, Component, leProps }: {
userId: string;
Component: React.ElementType<UserProfilePronounsProps>;
leProps: UserProfilePronounsProps;
}
) {
const result = awaitAndFormatPronouns(userId);
// If the promise completed, the result was not "unspecified", and there is a mapping for the code, then render
if (result != null) {
// First child is the header, second is a div with the actual text
leProps.currentPronouns ||= result;
return <Component {...leProps} />;
}
return null;
}

Some files were not shown because too many files have changed in this diff Show More