Compare commits
1 Commits
main
...
feat/permi
Author | SHA1 | Date | |
---|---|---|---|
|
64c7581286 |
@ -51,10 +51,7 @@
|
||||
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
||||
"spaced-comment": ["error", "always", { "markers": ["!"] }],
|
||||
"yoda": "error",
|
||||
"prefer-destructuring": ["error", {
|
||||
"VariableDeclarator": { "array": false, "object": true },
|
||||
"AssignmentExpression": { "array": false, "object": false }
|
||||
}],
|
||||
"prefer-destructuring": ["error", { "object": true, "array": false }],
|
||||
"operator-assignment": ["error", "always"],
|
||||
"no-useless-computed-key": "error",
|
||||
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
|
||||
|
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -14,8 +14,7 @@ body:
|
||||
DO NOT USE THIS FORM, unless
|
||||
- you are a vencord contributor
|
||||
- you were given explicit permission to use this form by a moderator in our support server
|
||||
|
||||
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
|
||||
- you are filing a security related report
|
||||
|
||||
- type: input
|
||||
id: discord
|
||||
|
18
.github/workflows/publish.yml
vendored
18
.github/workflows/publish.yml
vendored
@ -36,10 +36,26 @@ jobs:
|
||||
|
||||
- name: Publish extension
|
||||
run: |
|
||||
# Do not fail so that even if chrome fails, firefox gets a shot. But also store exit code to fail workflow later
|
||||
EXIT_CODE=0
|
||||
|
||||
# Chrome
|
||||
cd dist/chromium-unpacked
|
||||
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish
|
||||
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish || EXIT_CODE=$?
|
||||
|
||||
# Firefox
|
||||
cd ../firefox-unpacked
|
||||
npm i -g web-ext@7.4.0 web-ext-submit@7.4.0
|
||||
web-ext-submit || EXIT_CODE=$?
|
||||
|
||||
exit $EXIT_CODE
|
||||
env:
|
||||
# Chrome
|
||||
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
||||
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
|
||||
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||
|
||||
# Firefox
|
||||
WEB_EXT_API_KEY: ${{ secrets.WEBEXT_USER }}
|
||||
WEB_EXT_API_SECRET: ${{ secrets.WEBEXT_SECRET }}
|
||||
|
32
README.md
32
README.md
@ -22,20 +22,34 @@ The cutest Discord client mod
|
||||
|
||||
## Installing / Uninstalling
|
||||
|
||||
Visit https://vencord.dev/download
|
||||
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)
|
||||
|
||||
## Installing on Browser
|
||||
|
||||
[![Get it on the Firefox Webstore](https://blog.mozilla.org/addons/files/2015/11/get-the-addon.png)](https://addons.mozilla.org/en-GB/firefox/addon/vencord-web/) [![Get it on the Chrome Webstore](https://storage.googleapis.com/web-dev-uploads/image/WlD8wC6g8khYWPJUsQceQkhXSlv1/UV4C4ybeBTsZt43U4xis.png)](https://chrome.google.com/webstore/detail/vencord-web/cbghhgpcnddeihccjmnadmkaejncjndb)
|
||||
|
||||
Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that 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
|
||||
|
||||
<details>
|
||||
<summary>Alternative Downloads</summary>
|
||||
|
||||
## Vencord Desktop
|
||||
|
||||
> **Warning**
|
||||
> This is an alternative app. It currently doesn't support keybinds and possibly some more features. If you just want to install to the normal Discord Desktop app, scroll up
|
||||
|
||||
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
|
||||
|
||||
[![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)
|
||||
|
||||
</details>
|
||||
|
||||
## Join our Support/Community Server
|
||||
|
||||
https://discord.gg/D9uwnFnqmd
|
||||
|
||||
## Sponsors
|
||||
|
||||
| **Thanks a lot to all Vencord [sponsors](https://github.com/sponsors/Vendicated)!!** |
|
||||
|:--:|
|
||||
| [![](https://meow.vendicated.dev/sponsors.png)](https://github.com/sponsors/Vendicated) |
|
||||
| *generated using [github-sponsor-graph](https://github.com/Vendicated/github-sponsor-graph)* |
|
||||
|
||||
|
||||
## Star History
|
||||
|
||||
<a href="https://star-history.com/#Vendicated/Vencord&Timeline">
|
||||
|
@ -1,32 +0,0 @@
|
||||
/**
|
||||
* @template T
|
||||
* @param {T[]} arr
|
||||
* @param {(v: T) => boolean} predicate
|
||||
*/
|
||||
function removeFirst(arr, predicate) {
|
||||
const idx = arr.findIndex(predicate);
|
||||
if (idx !== -1) arr.splice(idx, 1);
|
||||
}
|
||||
|
||||
chrome.webRequest.onHeadersReceived.addListener(
|
||||
({ responseHeaders, type, url }) => {
|
||||
if (!responseHeaders) return;
|
||||
|
||||
if (type === "main_frame") {
|
||||
// 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/")) {
|
||||
// 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");
|
||||
responseHeaders.push({
|
||||
name: "Content-Type",
|
||||
value: "text/css"
|
||||
});
|
||||
}
|
||||
return { responseHeaders };
|
||||
},
|
||||
{ urls: ["https://raw.githubusercontent.com/*", "*://*.discord.com/*"], types: ["main_frame", "stylesheet"] },
|
||||
["blocking", "responseHeaders"]
|
||||
);
|
@ -26,11 +26,7 @@
|
||||
}
|
||||
],
|
||||
|
||||
"background": {
|
||||
"scripts": ["background.js"]
|
||||
},
|
||||
|
||||
"web_accessible_resources": ["dist/Vencord.js", "dist/Vencord.css"],
|
||||
"web_accessible_resources": ["dist/*", "third-party/*"],
|
||||
|
||||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "vencord",
|
||||
"private": "true",
|
||||
"version": "1.6.3",
|
||||
"version": "1.5.4",
|
||||
"description": "The cutest Discord client mod",
|
||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||
"bugs": {
|
||||
@ -70,7 +70,7 @@
|
||||
"typescript": "^5.0.4",
|
||||
"zip-local": "^0.3.5"
|
||||
},
|
||||
"packageManager": "pnpm@8.10.2",
|
||||
"packageManager": "pnpm@8.1.1",
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",
|
||||
@ -94,7 +94,7 @@
|
||||
"build": {
|
||||
"overwriteDest": true
|
||||
},
|
||||
"sourceDir": "./dist/firefox-unpacked"
|
||||
"sourceDir": "./dist/extension-v2-unpacked"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18",
|
||||
|
@ -18,10 +18,8 @@
|
||||
*/
|
||||
|
||||
import esbuild from "esbuild";
|
||||
import { readdir } from "fs/promises";
|
||||
import { join } from "path";
|
||||
|
||||
import { BUILD_TIMESTAMP, commonOpts, existsAsync, globPlugins, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs";
|
||||
import { BUILD_TIMESTAMP, commonOpts, globPlugins, isStandalone, updaterDisabled, VERSION, watch } from "./common.mjs";
|
||||
|
||||
const defines = {
|
||||
IS_STANDALONE: isStandalone,
|
||||
@ -45,59 +43,13 @@ const nodeCommonOpts = {
|
||||
format: "cjs",
|
||||
platform: "node",
|
||||
target: ["esnext"],
|
||||
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external],
|
||||
external: ["electron", ...commonOpts.external],
|
||||
define: defines,
|
||||
};
|
||||
|
||||
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
|
||||
const sourcemap = watch ? "inline" : "external";
|
||||
|
||||
/**
|
||||
* @type {import("esbuild").Plugin}
|
||||
*/
|
||||
const globNativesPlugin = {
|
||||
name: "glob-natives-plugin",
|
||||
setup: build => {
|
||||
const filter = /^~pluginNatives$/;
|
||||
build.onResolve({ filter }, args => {
|
||||
return {
|
||||
namespace: "import-natives",
|
||||
path: args.path
|
||||
};
|
||||
});
|
||||
|
||||
build.onLoad({ filter, namespace: "import-natives" }, async () => {
|
||||
const pluginDirs = ["plugins", "userplugins"];
|
||||
let code = "";
|
||||
let natives = "\n";
|
||||
let i = 0;
|
||||
for (const dir of pluginDirs) {
|
||||
const dirPath = join("src", dir);
|
||||
if (!await existsAsync(dirPath)) continue;
|
||||
const plugins = await readdir(dirPath);
|
||||
for (const p of plugins) {
|
||||
if (!await existsAsync(join(dirPath, p, "native.ts"))) continue;
|
||||
|
||||
const nameParts = p.split(".");
|
||||
const namePartsWithoutTarget = nameParts.length === 1 ? nameParts : nameParts.slice(0, -1);
|
||||
// pluginName.thing.desktop -> PluginName.thing
|
||||
const cleanPluginName = p[0].toUpperCase() + namePartsWithoutTarget.join(".").slice(1);
|
||||
|
||||
const mod = `p${i}`;
|
||||
code += `import * as ${mod} from "./${dir}/${p}/native";\n`;
|
||||
natives += `${JSON.stringify(cleanPluginName)}:${mod},\n`;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
code += `export default {${natives}};`;
|
||||
return {
|
||||
contents: code,
|
||||
resolveDir: "./src"
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
// Discord Desktop main & renderer & preload
|
||||
esbuild.build({
|
||||
@ -110,11 +62,7 @@ await Promise.all([
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: true,
|
||||
IS_VESKTOP: false
|
||||
},
|
||||
plugins: [
|
||||
...nodeCommonOpts.plugins,
|
||||
globNativesPlugin
|
||||
]
|
||||
}
|
||||
}),
|
||||
esbuild.build({
|
||||
...commonOpts,
|
||||
@ -159,11 +107,7 @@ await Promise.all([
|
||||
...defines,
|
||||
IS_DISCORD_DESKTOP: false,
|
||||
IS_VESKTOP: true
|
||||
},
|
||||
plugins: [
|
||||
...nodeCommonOpts.plugins,
|
||||
globNativesPlugin
|
||||
]
|
||||
}
|
||||
}),
|
||||
esbuild.build({
|
||||
...commonOpts,
|
||||
|
@ -145,11 +145,11 @@ async function loadDir(dir, basePath = "") {
|
||||
/**
|
||||
* @type {(target: string, files: string[]) => Promise<void>}
|
||||
*/
|
||||
async function buildExtension(target, files) {
|
||||
async function buildExtension(target, files, noMonaco = false) {
|
||||
const entries = {
|
||||
"dist/Vencord.js": await readFile("dist/extension.js"),
|
||||
"dist/Vencord.css": await readFile("dist/extension.css"),
|
||||
...await loadDir("dist/monaco"),
|
||||
...(noMonaco ? {} : await loadDir("dist/monaco")),
|
||||
...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file =>
|
||||
[`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)]
|
||||
))),
|
||||
@ -195,11 +195,8 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content
|
||||
await Promise.all([
|
||||
appendCssRuntime,
|
||||
buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
|
||||
buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
|
||||
buildExtension("firefox-unpacked", ["content.js", "manifestv2.json", "icon.png"], true),
|
||||
]);
|
||||
|
||||
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip");
|
||||
console.info("Packed Chromium Extension written to dist/extension-chrome.zip");
|
||||
|
||||
Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
|
||||
console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
|
||||
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension.zip");
|
||||
console.info("Packed Chromium Extension written to dist/extension.zip");
|
||||
|
@ -20,8 +20,8 @@ import "../suppressExperimentalWarnings.js";
|
||||
import "../checkNodeVersion.js";
|
||||
|
||||
import { exec, execSync } from "child_process";
|
||||
import { constants as FsConstants, readFileSync } from "fs";
|
||||
import { access, readdir, readFile } from "fs/promises";
|
||||
import { existsSync, readFileSync } from "fs";
|
||||
import { readdir, readFile } from "fs/promises";
|
||||
import { join, relative } from "path";
|
||||
import { promisify } from "util";
|
||||
|
||||
@ -47,12 +47,6 @@ export const banner = {
|
||||
|
||||
const isWeb = process.argv.slice(0, 2).some(f => f.endsWith("buildWeb.mjs"));
|
||||
|
||||
export function existsAsync(path) {
|
||||
return access(path, FsConstants.F_OK)
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
// https://github.com/evanw/esbuild/issues/619#issuecomment-751995294
|
||||
/**
|
||||
* @type {import("esbuild").Plugin}
|
||||
@ -85,7 +79,7 @@ export const globPlugins = kind => ({
|
||||
let plugins = "\n";
|
||||
let i = 0;
|
||||
for (const dir of pluginDirs) {
|
||||
if (!await existsAsync(`./src/${dir}`)) continue;
|
||||
if (!existsSync(`./src/${dir}`)) continue;
|
||||
const files = await readdir(`./src/${dir}`);
|
||||
for (const file of files) {
|
||||
if (file.startsWith("_") || file.startsWith(".")) continue;
|
||||
|
@ -61,13 +61,6 @@ const report = {
|
||||
otherErrors: [] as string[]
|
||||
};
|
||||
|
||||
const IGNORED_DISCORD_ERRORS = [
|
||||
"KeybindStore: Looking for callback action",
|
||||
"Unable to process domain list delta: Client revision number is null",
|
||||
"Downloading the full bad domains file",
|
||||
/\[GatewaySocket\].{0,110}Cannot access '/
|
||||
] as Array<string | RegExp>;
|
||||
|
||||
function toCodeBlock(s: string) {
|
||||
s = s.replace(/```/g, "`\u200B`\u200B`");
|
||||
return "```" + s + " ```";
|
||||
@ -93,8 +86,6 @@ async function printReport() {
|
||||
console.log(` - Error: ${toCodeBlock(p.error)}`);
|
||||
});
|
||||
|
||||
report.otherErrors = report.otherErrors.filter(e => !IGNORED_DISCORD_ERRORS.some(regex => e.match(regex)));
|
||||
|
||||
console.log("## Discord Errors");
|
||||
report.otherErrors.forEach(e => {
|
||||
console.log(`- ${toCodeBlock(e)}`);
|
||||
@ -268,7 +259,7 @@ function runTime(token: string) {
|
||||
const { wreq } = Vencord.Webpack;
|
||||
|
||||
console.error("[PUP_DEBUG]", "Loading all chunks...");
|
||||
const ids = Function("return" + wreq.u.toString().match(/(?<=\()\{.+?\}/s)![0])();
|
||||
const ids = Function("return" + wreq.u.toString().match(/\{.+\}/s)![0])();
|
||||
for (const id in ids) {
|
||||
const isWasm = await fetch(wreq.p + wreq.u(id))
|
||||
.then(r => r.text())
|
||||
@ -289,7 +280,7 @@ function runTime(token: string) {
|
||||
setTimeout(() => console.log("PUPPETEER_TEST_DONE_SIGNAL"), 1000);
|
||||
}, 1000));
|
||||
} catch (e) {
|
||||
console.error("[PUP_DEBUG]", "A fatal error occurred");
|
||||
console.error("[PUP_DEBUG]", "A fatal error occured");
|
||||
console.error("[PUP_DEBUG]", e);
|
||||
process.exit(1);
|
||||
}
|
||||
|
@ -136,7 +136,7 @@ if (IS_DISCORD_DESKTOP && Settings.winNativeTitleBar && navigator.platform.toLow
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.head.append(Object.assign(document.createElement("style"), {
|
||||
id: "vencord-native-titlebar-style",
|
||||
textContent: "[class*=titleBar]{display: none!important}"
|
||||
textContent: "[class*=titleBar-]{display: none!important}"
|
||||
}));
|
||||
}, { once: true });
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
import { IpcEvents } from "@utils/IpcEvents";
|
||||
import { IpcRes } from "@utils/types";
|
||||
import { ipcRenderer } from "electron";
|
||||
import { PluginIpcMappings } from "main/ipcPlugins";
|
||||
import type { UserThemeHeader } from "main/themes";
|
||||
|
||||
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
|
||||
@ -18,16 +17,6 @@ export function sendSync<T = any>(event: IpcEvents, ...args: any[]) {
|
||||
return ipcRenderer.sendSync(event, ...args) as T;
|
||||
}
|
||||
|
||||
const PluginHelpers = {} as Record<string, Record<string, (...args: any[]) => Promise<any>>>;
|
||||
const pluginIpcMap = sendSync<PluginIpcMappings>(IpcEvents.GET_PLUGIN_IPC_METHOD_MAP);
|
||||
|
||||
for (const [plugin, methods] of Object.entries(pluginIpcMap)) {
|
||||
const map = PluginHelpers[plugin] = {};
|
||||
for (const [methodName, method] of Object.entries(methods)) {
|
||||
map[methodName] = (...args: any[]) => invoke(method as IpcEvents, ...args);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
themes: {
|
||||
uploadTheme: (fileName: string, fileData: string) => invoke<void>(IpcEvents.UPLOAD_THEME, fileName, fileData),
|
||||
@ -72,5 +61,12 @@ export default {
|
||||
openExternal: (url: string) => invoke<void>(IpcEvents.OPEN_EXTERNAL, url)
|
||||
},
|
||||
|
||||
pluginHelpers: PluginHelpers
|
||||
pluginHelpers: {
|
||||
OpenInApp: {
|
||||
resolveRedirect: (url: string) => invoke<string>(IpcEvents.OPEN_IN_APP__RESOLVE_REDIRECT, url),
|
||||
},
|
||||
VoiceMessages: {
|
||||
readRecording: (path: string) => invoke<Uint8Array | null>(IpcEvents.VOICE_MESSAGES_READ_RECORDING, path),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -17,14 +17,14 @@
|
||||
*/
|
||||
|
||||
import { mergeDefaults } from "@utils/misc";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||
import { SnowflakeUtils } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
import type { PartialDeep } from "type-fest";
|
||||
|
||||
import { Argument } from "./types";
|
||||
|
||||
const MessageCreator = findByPropsLazy("createBotMessage");
|
||||
const createBotMessage = findByCodeLazy('username:"Clyde"');
|
||||
const MessageSender = findByPropsLazy("receiveMessage");
|
||||
|
||||
export function generateId() {
|
||||
@ -38,7 +38,7 @@ export function generateId() {
|
||||
* @returns {Message}
|
||||
*/
|
||||
export function sendBotMessage(channelId: string, message: PartialDeep<Message>): Message {
|
||||
const botMessage = MessageCreator.createBotMessage({ channelId, content: "", embeds: [] });
|
||||
const botMessage = createBotMessage({ channelId, content: "", embeds: [] });
|
||||
|
||||
MessageSender.receiveMessage(channelId, mergeDefaults(message, botMessage));
|
||||
|
||||
|
@ -69,7 +69,7 @@ export function addGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback)
|
||||
* Remove a context menu patch
|
||||
* @param navId The navId(s) for the context menu(s) to remove the patch
|
||||
* @param patch The patch to be removed
|
||||
* @returns Whether the patch was successfully removed from the context menu(s)
|
||||
* @returns Wheter the patch was sucessfully removed from the context menu(s)
|
||||
*/
|
||||
export function removeContextMenuPatch<T extends string | Array<string>>(navId: T, patch: NavContextMenuPatchCallback): T extends string ? boolean : Array<boolean> {
|
||||
const navIds = Array.isArray(navId) ? navId : [navId as string];
|
||||
@ -82,7 +82,7 @@ export function removeContextMenuPatch<T extends string | Array<string>>(navId:
|
||||
/**
|
||||
* Remove a global context menu patch
|
||||
* @param patch The patch to be removed
|
||||
* @returns Whether the patch was successfully removed
|
||||
* @returns Wheter the patch was sucessfully removed
|
||||
*/
|
||||
export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallback): boolean {
|
||||
return globalPatches.delete(patch);
|
||||
|
@ -20,6 +20,7 @@ import { Channel, User } from "discord-types/general/index.js";
|
||||
|
||||
interface DecoratorProps {
|
||||
activities: any[];
|
||||
canUseAvatarDecorations: boolean;
|
||||
channel: Channel;
|
||||
/**
|
||||
* Only for DM members
|
||||
@ -51,9 +52,9 @@ export function removeDecorator(identifier: string) {
|
||||
decorators.delete(identifier);
|
||||
}
|
||||
|
||||
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
|
||||
export function __addDecoratorsToList(props: DecoratorProps): (JSX.Element | null)[] {
|
||||
const isInGuild = !!(props.guildId);
|
||||
return Array.from(decorators.values(), decoratorObj => {
|
||||
return [...decorators.values()].map(decoratorObj => {
|
||||
const { decorator, onlyIn } = decoratorObj;
|
||||
// this can most likely be done cleaner
|
||||
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
|
||||
|
@ -237,7 +237,6 @@ type ResolvePropDeep<T, P> = P extends "" ? T :
|
||||
export function addSettingsListener<Path extends keyof Settings>(path: Path, onUpdate: (newValue: Settings[Path], path: Path) => void): void;
|
||||
export function addSettingsListener<Path extends string>(path: Path, onUpdate: (newValue: Path extends "" ? any : ResolvePropDeep<Settings, Path>, path: Path extends "" ? string : Path) => void): void;
|
||||
export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void) {
|
||||
if (path)
|
||||
((onUpdate as SubscriptionCallback)._paths ??= []).push(path);
|
||||
subscriptions.add(onUpdate);
|
||||
}
|
||||
|
69
src/api/SettingsStore.ts
Normal file
69
src/api/SettingsStore.ts
Normal file
@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { findModuleId, wreq } from "@webpack";
|
||||
|
||||
import { Settings } from "./Settings";
|
||||
|
||||
interface Setting<T> {
|
||||
/**
|
||||
* Get the setting value
|
||||
*/
|
||||
getSetting(): T;
|
||||
/**
|
||||
* Update the setting value
|
||||
* @param value The new value
|
||||
*/
|
||||
updateSetting(value: T | ((old: T) => T)): Promise<void>;
|
||||
/**
|
||||
* React hook for automatically updating components when the setting is updated
|
||||
*/
|
||||
useSetting(): T;
|
||||
settingsStoreApiGroup: string;
|
||||
settingsStoreApiName: string;
|
||||
}
|
||||
|
||||
const SettingsStores: Array<Setting<any>> | undefined = proxyLazy(() => {
|
||||
const modId = findModuleId('"textAndImages","renderSpoilers"');
|
||||
if (modId == null) return new Logger("SettingsStoreAPI").error("Didn't find stores module.");
|
||||
|
||||
const mod = wreq(modId);
|
||||
if (mod == null) return;
|
||||
|
||||
return Object.values(mod).filter((s: any) => s?.settingsStoreApiGroup) as any;
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the store for a setting
|
||||
* @param group The setting group
|
||||
* @param name The name of the setting
|
||||
*/
|
||||
export function getSettingStore<T = any>(group: string, name: string): Setting<T> | undefined {
|
||||
if (!Settings.plugins.SettingsStoreAPI.enabled) throw new Error("Cannot use SettingsStoreAPI without setting as dependency.");
|
||||
|
||||
return SettingsStores?.find(s => s?.settingsStoreApiGroup === group && s?.settingsStoreApiName === name);
|
||||
}
|
||||
|
||||
/**
|
||||
* getSettingStore but lazy
|
||||
*/
|
||||
export function getSettingStoreLazy<T = any>(group: string, name: string) {
|
||||
return proxyLazy(() => getSettingStore<T>(group, name));
|
||||
}
|
@ -29,6 +29,7 @@ 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";
|
||||
|
||||
/**
|
||||
@ -90,6 +91,10 @@ export const MemberListDecorators = $MemberListDecorators;
|
||||
* An API allowing you to persist data
|
||||
*/
|
||||
export const Settings = $Settings;
|
||||
/**
|
||||
* An API allowing you to read, manipulate and automatically update components based on Discord settings
|
||||
*/
|
||||
export const SettingsStore = $SettingsStore;
|
||||
/**
|
||||
* An API allowing you to dynamically load styles
|
||||
* a
|
||||
|
@ -94,7 +94,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||
(async () => {
|
||||
for (const user of plugin.authors.slice(0, 6)) {
|
||||
const author = user.id
|
||||
? await UserUtils.getUser(`${user.id}`)
|
||||
? await UserUtils.fetchUser(`${user.id}`)
|
||||
.catch(() => makeDummyUser({ username: user.name }))
|
||||
: makeDummyUser({ username: user.name });
|
||||
|
||||
|
@ -17,7 +17,6 @@
|
||||
font-size: 20px;
|
||||
height: 20px;
|
||||
position: relative;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.vc-author-modal-name::before {
|
||||
|
@ -34,7 +34,7 @@ import { openModalLazy } from "@utils/modal";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import { Plugin } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
|
||||
import { Alerts, Button, Card, Forms, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
|
||||
|
||||
import Plugins from "~plugins";
|
||||
|
||||
@ -251,7 +251,7 @@ export default function PluginSettings() {
|
||||
}
|
||||
DataStore.set("Vencord_existingPlugins", existingTimestamps);
|
||||
|
||||
return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
|
||||
return window._.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
|
||||
}));
|
||||
|
||||
type P = JSX.Element | JSX.Element[];
|
||||
|
@ -18,14 +18,16 @@
|
||||
|
||||
import { useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { ErrorCard } from "@components/ErrorCard";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { DeleteIcon } from "@components/Icons";
|
||||
import { Link } from "@components/Link";
|
||||
import { IsFirefox } from "@utils/constants";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes } from "@utils/misc";
|
||||
import { showItemInFolder } from "@utils/native";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import { findByPropsLazy, findLazy } from "@webpack";
|
||||
import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack";
|
||||
import { Button, Card, FluxDispatcher, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
||||
import { UserThemeHeader } from "main/themes";
|
||||
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
||||
@ -41,7 +43,7 @@ type FileInput = ComponentType<{
|
||||
}>;
|
||||
|
||||
const InviteActions = findByPropsLazy("resolveInvite");
|
||||
const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
|
||||
const FileInput: FileInput = findByCodeLazy("activateUploadDialogue=");
|
||||
const TextAreaProps = findLazy(m => typeof m.textarea === "string");
|
||||
|
||||
const cl = classNameFactory("vc-settings-theme-");
|
||||
@ -249,12 +251,14 @@ function ThemesTab() {
|
||||
>
|
||||
Load missing Themes
|
||||
</Button>
|
||||
{!IsFirefox && (
|
||||
<Button
|
||||
onClick={() => VencordNative.quickCss.openEditor()}
|
||||
size={Button.Sizes.SMALL}
|
||||
>
|
||||
Edit QuickCSS
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
</Card>
|
||||
|
||||
@ -316,6 +320,15 @@ function ThemesTab() {
|
||||
|
||||
return (
|
||||
<SettingsTab title="Themes">
|
||||
{IsFirefox && (
|
||||
<ErrorCard>
|
||||
<Forms.FormTitle tag="h5">Warning</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
You are using Firefox. Expect the vast majority of themes to not work.
|
||||
If this is a problem, use a chromium browser or Discord Desktop / Vesktop.
|
||||
</Forms.FormText>
|
||||
</ErrorCard>
|
||||
)}
|
||||
<TabBar
|
||||
type="top"
|
||||
look="brand"
|
||||
|
@ -46,7 +46,7 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
|
||||
if (code === "ENOENT")
|
||||
var err = `Command \`${path}\` not found.\nPlease install it and try again`;
|
||||
else {
|
||||
var err = `An error occurred while running \`${cmd}\`:\n`;
|
||||
var err = `An error occured while running \`${cmd}\`:\n`;
|
||||
err += stderr || `Code \`${code}\`. See the console for more info`;
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,7 @@ import { Settings, useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import DonateButton from "@components/DonateButton";
|
||||
import { ErrorCard } from "@components/ErrorCard";
|
||||
import { IsFirefox } from "@utils/constants";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { identity } from "@utils/misc";
|
||||
import { relaunch, showItemInFolder } from "@utils/native";
|
||||
@ -109,12 +110,14 @@ function VencordSettings() {
|
||||
Restart Client
|
||||
</Button>
|
||||
)}
|
||||
{!IsFirefox && (
|
||||
<Button
|
||||
onClick={() => VencordNative.quickCss.openEditor()}
|
||||
size={Button.Sizes.SMALL}
|
||||
disabled={settingsDir === "Loading..."}>
|
||||
Open QuickCSS File
|
||||
</Button>
|
||||
)}
|
||||
{!IS_WEB && (
|
||||
<Button
|
||||
onClick={() => showItemInFolder(settingsDir)}
|
||||
|
@ -62,10 +62,6 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
||||
} catch { }
|
||||
|
||||
|
||||
const findHeader = (headers: Record<string, string[]>, headerName: Lowercase<string>) => {
|
||||
return Object.keys(headers).find(h => h.toLowerCase() === headerName);
|
||||
};
|
||||
|
||||
// Remove CSP
|
||||
type PolicyResult = Record<string, string[]>;
|
||||
|
||||
@ -77,7 +73,6 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
||||
result[directiveKey] = directiveValue;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
const stringifyPolicy = (policy: PolicyResult): string =>
|
||||
@ -86,39 +81,31 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
||||
.map(directive => directive.flat().join(" "))
|
||||
.join("; ");
|
||||
|
||||
const patchCsp = (headers: Record<string, string[]>) => {
|
||||
const header = findHeader(headers, "content-security-policy");
|
||||
|
||||
if (header) {
|
||||
function patchCsp(headers: Record<string, string[]>, header: string) {
|
||||
if (header in headers) {
|
||||
const csp = parsePolicy(headers[header][0]);
|
||||
|
||||
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
|
||||
csp[directive] ??= [];
|
||||
csp[directive].push("*", "blob:", "data:", "vencord:", "'unsafe-inline'");
|
||||
csp[directive] = ["*", "blob:", "data:", "vencord:", "'unsafe-inline'"];
|
||||
}
|
||||
|
||||
// TODO: Restrict this to only imported packages with fixed version.
|
||||
// Perhaps auto generate with esbuild
|
||||
csp["script-src"] ??= [];
|
||||
csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com");
|
||||
headers[header] = [stringifyPolicy(csp)];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => {
|
||||
if (responseHeaders) {
|
||||
if (resourceType === "mainFrame")
|
||||
patchCsp(responseHeaders);
|
||||
patchCsp(responseHeaders, "content-security-policy");
|
||||
|
||||
// Fix hosts that don't properly set the css content type, such as
|
||||
// raw.githubusercontent.com
|
||||
if (resourceType === "stylesheet") {
|
||||
const header = findHeader(responseHeaders, "content-type");
|
||||
if (header)
|
||||
responseHeaders[header] = ["text/css"];
|
||||
if (resourceType === "stylesheet")
|
||||
responseHeaders["content-type"] = ["text/css"];
|
||||
}
|
||||
}
|
||||
|
||||
cb({ cancel: false, responseHeaders });
|
||||
});
|
||||
|
||||
|
@ -17,26 +17,73 @@
|
||||
*/
|
||||
|
||||
import { IpcEvents } from "@utils/IpcEvents";
|
||||
import { ipcMain } from "electron";
|
||||
import { app, ipcMain } from "electron";
|
||||
import { readFile } from "fs/promises";
|
||||
import { request } from "https";
|
||||
import { basename, normalize } from "path";
|
||||
|
||||
import PluginNatives from "~pluginNatives";
|
||||
import { getSettings } from "./ipcMain";
|
||||
|
||||
const PluginIpcMappings = {} as Record<string, Record<string, string>>;
|
||||
export type PluginIpcMappings = typeof PluginIpcMappings;
|
||||
// FixSpotifyEmbeds
|
||||
app.on("browser-window-created", (_, win) => {
|
||||
win.webContents.on("frame-created", (_, { frame }) => {
|
||||
frame.once("dom-ready", () => {
|
||||
if (frame.url.startsWith("https://open.spotify.com/embed/")) {
|
||||
const settings = getSettings().plugins?.FixSpotifyEmbeds;
|
||||
if (!settings?.enabled) return;
|
||||
|
||||
for (const [plugin, methods] of Object.entries(PluginNatives)) {
|
||||
const entries = Object.entries(methods);
|
||||
if (!entries.length) continue;
|
||||
|
||||
const mappings = PluginIpcMappings[plugin] = {};
|
||||
|
||||
for (const [methodName, method] of entries) {
|
||||
const key = `VencordPluginNative_${plugin}_${methodName}`;
|
||||
ipcMain.handle(key, method);
|
||||
mappings[methodName] = key;
|
||||
frame.executeJavaScript(`
|
||||
const original = Audio.prototype.play;
|
||||
Audio.prototype.play = function() {
|
||||
this.volume = ${(settings.volume / 100) || 0.1};
|
||||
return original.apply(this, arguments);
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// #region OpenInApp
|
||||
// These links don't support CORS, so this has to be native
|
||||
const validRedirectUrls = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
|
||||
|
||||
function getRedirect(url: string) {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const req = request(new URL(url), { method: "HEAD" }, res => {
|
||||
resolve(
|
||||
res.headers.location
|
||||
? getRedirect(res.headers.location)
|
||||
: url
|
||||
);
|
||||
});
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
ipcMain.on(IpcEvents.GET_PLUGIN_IPC_METHOD_MAP, e => {
|
||||
e.returnValue = PluginIpcMappings;
|
||||
ipcMain.handle(IpcEvents.OPEN_IN_APP__RESOLVE_REDIRECT, async (_, url: string) => {
|
||||
if (!validRedirectUrls.test(url)) return url;
|
||||
|
||||
return getRedirect(url);
|
||||
});
|
||||
// #endregion
|
||||
|
||||
|
||||
// #region VoiceMessages
|
||||
ipcMain.handle(IpcEvents.VOICE_MESSAGES_READ_RECORDING, async (_, filePath: string) => {
|
||||
filePath = normalize(filePath);
|
||||
const filename = basename(filePath);
|
||||
const discordBaseDirWithTrailingSlash = normalize(app.getPath("userData") + "/");
|
||||
console.log(filename, discordBaseDirWithTrailingSlash, filePath);
|
||||
if (filename !== "recording.ogg" || !filePath.startsWith(discordBaseDirWithTrailingSlash)) return null;
|
||||
|
||||
try {
|
||||
const buf = await readFile(filePath);
|
||||
return new Uint8Array(buf.buffer);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// #endregion
|
||||
|
@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "original-fs";
|
||||
import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "fs";
|
||||
import { basename, dirname, join } from "path";
|
||||
|
||||
function isNewer($new: string, old: string) {
|
||||
|
@ -49,9 +49,7 @@ async function getRepo() {
|
||||
async function calculateGitChanges() {
|
||||
await git("fetch");
|
||||
|
||||
const branch = await git("branch", "--show-current");
|
||||
|
||||
const res = await git("log", `HEAD...origin/${branch.stdout.trim()}`, "--pretty=format:%an/%h/%s");
|
||||
const res = await git("log", "HEAD...origin/main", "--pretty=format:%an/%h/%s");
|
||||
|
||||
const commits = res.stdout.trim();
|
||||
return commits ? commits.split("\n").map(line => {
|
||||
|
5
src/modules.d.ts
vendored
5
src/modules.d.ts
vendored
@ -24,11 +24,6 @@ declare module "~plugins" {
|
||||
export default plugins;
|
||||
}
|
||||
|
||||
declare module "~pluginNatives" {
|
||||
const pluginNatives: Record<string, Record<string, (event: Electron.IpcMainInvokeEvent, ...args: unknown[]) => unknown>>;
|
||||
export default pluginNatives;
|
||||
}
|
||||
|
||||
declare module "~git-hash" {
|
||||
const hash: string;
|
||||
export default hash;
|
||||
|
@ -85,19 +85,17 @@ export default definePlugin({
|
||||
},
|
||||
{
|
||||
// alt: "", aria-hidden: false, src: originalSrc
|
||||
match: /alt:" ","aria-hidden":!0,src:(?=(\i)\.src)/,
|
||||
match: /alt:" ","aria-hidden":!0,src:(?=(\i)\.src)/g,
|
||||
// ...badge.props, ..., src: badge.image ?? ...
|
||||
replace: "...$1.props,$& $1.image??"
|
||||
},
|
||||
// replace their component with ours if applicable
|
||||
{
|
||||
match: /(?<=text:(\i)\.description,spacing:12,)children:/,
|
||||
replace: "children:$1.component ? () => $self.renderBadgeComponent($1) :"
|
||||
match: /children:function(?<=(\i)\.(?:tooltip|description),spacing:\d.+?)/g,
|
||||
replace: "children:$1.component ? () => $self.renderBadgeComponent($1) : function"
|
||||
},
|
||||
// conditionally override their onClick with badge.onClick if it exists
|
||||
{
|
||||
match: /href:(\i)\.link/,
|
||||
replace: "...($1.onClick && { onClick: $1.onClick }),$&"
|
||||
match: /onClick:function(?=.{0,200}href:(\i)\.link)/,
|
||||
replace: "onClick:$1.onClick??function"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ export default definePlugin({
|
||||
patches: [
|
||||
// obtain BUILT_IN_COMMANDS instance
|
||||
{
|
||||
find: ',"tenor"',
|
||||
find: '"giphy","tenor"',
|
||||
replacement: [
|
||||
{
|
||||
// Matches BUILT_IN_COMMANDS. This is not exported so this is
|
||||
@ -34,7 +34,7 @@ export default definePlugin({
|
||||
// patch simpler
|
||||
|
||||
// textCommands = builtInCommands.filter(...)
|
||||
match: /(?<=\w=)(\w)(\.filter\(.{0,60}tenor)/,
|
||||
match: /(?<=\w=)(\w)(\.filter\(.{0,30}giphy)/,
|
||||
replace: "Vencord.Api.Commands._init($1)$2",
|
||||
}
|
||||
],
|
||||
@ -44,8 +44,8 @@ export default definePlugin({
|
||||
find: "Unexpected value for option",
|
||||
replacement: {
|
||||
// return [2, cmd.execute(args, ctx)]
|
||||
match: /,(\i)\.execute\((\i),(\i)\)/,
|
||||
replace: (_, cmd, args, ctx) => `,Vencord.Api.Commands._handleCommand(${cmd}, ${args}, ${ctx})`
|
||||
match: /,(.{1,2})\.execute\((.{1,2}),(.{1,2})\)]/,
|
||||
replace: (_, cmd, args, ctx) => `,Vencord.Api.Commands._handleCommand(${cmd}, ${args}, ${ctx})]`
|
||||
}
|
||||
},
|
||||
// Show plugin name instead of "Built-In"
|
||||
|
@ -29,8 +29,8 @@ export default definePlugin({
|
||||
{
|
||||
find: "♫ (つ。◕‿‿◕。)つ ♪",
|
||||
replacement: {
|
||||
match: /let{navId:/,
|
||||
replace: "Vencord.Api.ContextMenu._patchContextMenu(arguments[0]);$&"
|
||||
match: /(?<=function \i\((\i)\){)(?=var \i,\i=\i\.navId)/,
|
||||
replace: (_, props) => `Vencord.Api.ContextMenu._patchContextMenu(${props});`
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -22,25 +22,20 @@ import definePlugin from "@utils/types";
|
||||
export default definePlugin({
|
||||
name: "MemberListDecoratorsAPI",
|
||||
description: "API to add decorators to member list (both in servers and DMs)",
|
||||
authors: [Devs.TheSun, Devs.Ven],
|
||||
authors: [Devs.TheSun],
|
||||
patches: [
|
||||
{
|
||||
find: ".lostPermission)",
|
||||
replacement: [
|
||||
{
|
||||
match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/,
|
||||
replace: "$&vencordProps=$1,"
|
||||
}, {
|
||||
match: /decorators:.{0,100}?children:\[/,
|
||||
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
|
||||
find: "lostPermissionTooltipText,",
|
||||
replacement: {
|
||||
match: /Fragment,{children:\[(.{30,80})\]/,
|
||||
replace: "Fragment,{children:Vencord.Api.MemberListDecorators.__addDecoratorsToList(this.props).concat($1)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "PrivateChannel.renderAvatar",
|
||||
replacement: {
|
||||
match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/,
|
||||
replace: "decorators:[...Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]), $1?$2:null]"
|
||||
match: /(subText:(.{1,2})\.renderSubtitle\(\).{1,50}decorators):(.{30,100}:null)/,
|
||||
replace: "$1:Vencord.Api.MemberListDecorators.__addDecoratorsToList($2.props).concat($3)"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -27,8 +27,9 @@ export default definePlugin({
|
||||
{
|
||||
find: ".Messages.REMOVE_ATTACHMENT_BODY",
|
||||
replacement: {
|
||||
match: /(?<=.container\)?,children:)(\[.+?\])/,
|
||||
replace: "Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)",
|
||||
match: /(.container\)?,children:)(\[[^\]]+\])(}\)\};return)/,
|
||||
replace: (_, pre, accessories, post) =>
|
||||
`${pre}Vencord.Api.MessageAccessories._modifyAccessories(${accessories},this.props)${post}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -25,10 +25,10 @@ export default definePlugin({
|
||||
authors: [Devs.TheSun],
|
||||
patches: [
|
||||
{
|
||||
find: '"Message Username"',
|
||||
find: ".withMentionPrefix",
|
||||
replacement: {
|
||||
match: /\.Messages\.GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE.+?}\),\i(?=\])/,
|
||||
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
|
||||
match: /(.roleDot.{10,50}{children:.{1,2})}\)/,
|
||||
replace: "$1.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))})"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -27,8 +27,10 @@ export default definePlugin({
|
||||
{
|
||||
find: '"MessageActionCreators"',
|
||||
replacement: {
|
||||
match: /async editMessage\(.+?\)\{/,
|
||||
replace: "$&await Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
|
||||
// editMessage: function (...) {
|
||||
match: /\beditMessage:(function\(.+?\))\{/,
|
||||
// editMessage: async function (...) { await handlePreEdit(...); ...
|
||||
replace: "editMessage:async $1{await Vencord.Api.MessageEvents._handlePreEdit(...arguments);"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -36,7 +38,7 @@ export default definePlugin({
|
||||
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: /(type:this\.props\.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptionsForReply\(\i\);)(?<=\)\(({.+?})\)\.then.+?)/,
|
||||
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}` +
|
||||
@ -47,10 +49,10 @@ export default definePlugin({
|
||||
{
|
||||
find: '("interactionUsernameProfile',
|
||||
replacement: {
|
||||
match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
|
||||
match: /var \i=(\i)\.id,\i=(\i)\.id;return \i\.useCallback\(\(?function\((\i)\){/,
|
||||
replace: (m, message, channel, event) =>
|
||||
// the message param is shadowed by the event param, so need to alias them
|
||||
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg, vcChan, ${event});`
|
||||
`var _msg=${message},_chan=${channel};${m}Vencord.Api.MessageEvents._handleClick(_msg, _chan, ${event});`
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -29,12 +29,12 @@ export default definePlugin({
|
||||
find: 'displayName="NoticeStore"',
|
||||
replacement: [
|
||||
{
|
||||
match: /\i=null;(?=.{0,80}getPremiumSubscription\(\))/g,
|
||||
replace: "if(Vencord.Api.Notices.currentNotice)return false;$&"
|
||||
match: /(?=;\i=null;.{0,70}getPremiumSubscription)/g,
|
||||
replace: ";if(Vencord.Api.Notices.currentNotice)return false"
|
||||
},
|
||||
{
|
||||
match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/,
|
||||
replace: "if($1.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&"
|
||||
match: /(?<=,NOTICE_DISMISS:function\(\i\){)(?=if\(null==(\i)\))/,
|
||||
replace: (_, notice) => `if(${notice}.id=="VencordNotice")return(${notice}=null,Vencord.Api.Notices.nextNotice(),true);`
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -27,15 +27,15 @@ export default definePlugin({
|
||||
{
|
||||
find: "Messages.DISCODO_DISABLED",
|
||||
replacement: {
|
||||
match: /(?<=Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
|
||||
replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
|
||||
match: /(Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
|
||||
replace: "$1[$2].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "Messages.SERVERS,children",
|
||||
replacement: {
|
||||
match: /(?<=Messages\.SERVERS,children:).+?default:return null\}\}\)/,
|
||||
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
|
||||
match: /(Messages\.SERVERS,children:)(.+?default:return null\}\}\)\))/,
|
||||
replace: "$1Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($2)"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
38
src/plugins/_api/settingsStore.ts
Normal file
38
src/plugins/_api/settingsStore.ts
Normal file
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2022 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "SettingsStoreAPI",
|
||||
description: "Patches Discord's SettingsStores to expose their group and name",
|
||||
authors: [Devs.Nuckyz],
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: '"textAndImages","renderSpoilers"',
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=INFREQUENT_USER_ACTION.{0,20}),useSetting:function/,
|
||||
replace: ",settingsStoreApiGroup:arguments[0],settingsStoreApiName:arguments[1]$&"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
});
|
@ -26,7 +26,7 @@ export default definePlugin({
|
||||
required: true,
|
||||
patches: [
|
||||
{
|
||||
find: "AnalyticsActionHandlers.handle",
|
||||
find: "TRACKING_URL:",
|
||||
replacement: {
|
||||
match: /^.+$/,
|
||||
replace: "()=>{}",
|
||||
@ -43,21 +43,20 @@ export default definePlugin({
|
||||
find: ".METRICS,",
|
||||
replacement: [
|
||||
{
|
||||
match: /this\._intervalId=/,
|
||||
replace: "this._intervalId=undefined&&"
|
||||
match: /this\._intervalId.+?12e4\)/,
|
||||
replace: ""
|
||||
},
|
||||
{
|
||||
match: /(increment\(\i\){)/,
|
||||
replace: "$1return;"
|
||||
match: /(?<=increment=function\(\i\){)/,
|
||||
replace: "return;"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: ".installedLogHooks)",
|
||||
replacement: {
|
||||
// if getDebugLogging() returns false, the hooks don't get installed.
|
||||
match: "getDebugLogging(){",
|
||||
replace: "getDebugLogging(){return false;"
|
||||
match: /if\(\i\.getDebugLogging\(\)&&!\i\.installedLogHooks\)/,
|
||||
replace: "if(false)"
|
||||
}
|
||||
},
|
||||
]
|
||||
|
@ -39,9 +39,8 @@ export default definePlugin({
|
||||
addContextMenuPatch("user-settings-cog", children => () => {
|
||||
const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any;
|
||||
section?.forEach(c => {
|
||||
const id = c?.props?.id;
|
||||
if (id?.startsWith("Vencord") || id?.startsWith("Vesktop")) {
|
||||
c.props.action = () => SettingsRouter.open(id);
|
||||
if (c?.props?.id?.startsWith("Vencord")) {
|
||||
c.props.action = () => SettingsRouter.open(c.props.id);
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -63,26 +62,26 @@ export default definePlugin({
|
||||
replacement: {
|
||||
get match() {
|
||||
switch (Settings.plugins.Settings.settingsLocation) {
|
||||
case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/;
|
||||
case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/;
|
||||
case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/;
|
||||
case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/;
|
||||
case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/;
|
||||
case "top": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/;
|
||||
case "aboveNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/;
|
||||
case "belowNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/;
|
||||
case "belowActivity": return /(?<=\{section:(\i)\.ID\.DIVIDER},)\{section:"changelog"/;
|
||||
case "bottom": return /\{section:(\i)\.ID\.CUSTOM,\s*element:.+?}/;
|
||||
case "aboveActivity":
|
||||
default:
|
||||
return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS\}/;
|
||||
return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS\}/;
|
||||
}
|
||||
},
|
||||
replace: "...$self.makeSettingsCategories($1),$&"
|
||||
}
|
||||
}],
|
||||
|
||||
customSections: [] as ((SectionTypes: Record<string, unknown>) => any)[],
|
||||
customSections: [] as ((ID: Record<string, unknown>) => any)[],
|
||||
|
||||
makeSettingsCategories(SectionTypes: Record<string, unknown>) {
|
||||
makeSettingsCategories({ ID }: { ID: Record<string, unknown>; }) {
|
||||
return [
|
||||
{
|
||||
section: SectionTypes.HEADER,
|
||||
section: ID.HEADER,
|
||||
label: "Vencord",
|
||||
className: "vc-settings-header"
|
||||
},
|
||||
@ -128,9 +127,9 @@ export default definePlugin({
|
||||
element: require("@components/VencordSettings/PatchHelperTab").default,
|
||||
className: "vc-patch-helper"
|
||||
},
|
||||
...this.customSections.map(func => func(SectionTypes)),
|
||||
...this.customSections.map(func => func(ID)),
|
||||
{
|
||||
section: SectionTypes.DIVIDER
|
||||
section: ID.DIVIDER
|
||||
}
|
||||
].filter(Boolean);
|
||||
},
|
||||
|
@ -17,7 +17,7 @@
|
||||
*/
|
||||
|
||||
import { DataStore } from "@api/index";
|
||||
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
||||
import { Devs, IsFirefox, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
||||
import { isPluginDev } from "@utils/misc";
|
||||
import { makeCodeblock } from "@utils/text";
|
||||
import definePlugin from "@utils/types";
|
||||
@ -30,6 +30,7 @@ import plugins from "~plugins";
|
||||
import settings from "./settings";
|
||||
|
||||
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
|
||||
const FIREFOX_DISMISS_KEY = "Vencord-Firefox-Warning-Dismiss";
|
||||
|
||||
const AllowedChannelIds = [
|
||||
SUPPORT_CHANNEL_ID,
|
||||
@ -115,6 +116,22 @@ ${makeCodeblock(enabledPlugins.join(", ") + "\n\n" + enabledApiPlugins.join(", "
|
||||
onConfirm: rememberDismiss
|
||||
});
|
||||
}
|
||||
|
||||
if (IsFirefox) {
|
||||
const rememberDismiss = () => DataStore.set(FIREFOX_DISMISS_KEY, true);
|
||||
|
||||
Alerts.show({
|
||||
title: "Hold on!",
|
||||
body: <div>
|
||||
<Forms.FormText>You are using Firefox.</Forms.FormText>
|
||||
<Forms.FormText>Due to Firefox's stupid extension guidelines, most themes and many plugins will not function correctly.</Forms.FormText>
|
||||
<Forms.FormText>Do not report bugs. Do not ask for help with broken plugins.</Forms.FormText>
|
||||
<Forms.FormText>Instead, use a chromium browser, Discord Desktop, or Vesktop.</Forms.FormText>
|
||||
</div>,
|
||||
onCancel: rememberDismiss,
|
||||
onConfirm: rememberDismiss
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -21,30 +21,16 @@ import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "AlwaysAnimate",
|
||||
description: "Animates anything that can be animated",
|
||||
description: "Animates anything that can be animated, besides status emojis.",
|
||||
authors: [Devs.FieryFlames],
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "canAnimate:",
|
||||
find: ".canAnimate",
|
||||
all: true,
|
||||
// Some modules match the find but the replacement is returned untouched
|
||||
noWarn: true,
|
||||
replacement: {
|
||||
match: /canAnimate:.+?(?=([,}].*?\)))/g,
|
||||
replace: (m, rest) => {
|
||||
const destructuringMatch = rest.match(/}=.+/);
|
||||
if (destructuringMatch == null) return "canAnimate:!0";
|
||||
return m;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
// Status emojis
|
||||
find: ".Messages.GUILD_OWNER,",
|
||||
replacement: {
|
||||
match: /(?<=\.activityEmoji,.+?animate:)\i/,
|
||||
replace: "!0"
|
||||
match: /\.canAnimate\b/g,
|
||||
replace: ".canAnimate || true"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -27,15 +27,15 @@ export default definePlugin({
|
||||
{
|
||||
find: ".displayName=\"MaskedLinkStore\"",
|
||||
replacement: {
|
||||
match: /(?<=isTrustedDomain\(\i\){)return \i\(\i\)/,
|
||||
replace: "return true"
|
||||
match: /\.isTrustedDomain=function\(.\){return.+?};/,
|
||||
replace: ".isTrustedDomain=function(){return true};"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "isSuspiciousDownload:",
|
||||
find: '"7z","ade","adp"',
|
||||
replacement: {
|
||||
match: /function \i\(\i\){(?=.{0,60}\.parse\(\i\))/,
|
||||
replace: "$&return null;"
|
||||
match: /JSON\.parse\('\[.+?'\)/,
|
||||
replace: "[]"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -20,19 +20,26 @@ import { popNotice, showNotice } from "@api/Notices";
|
||||
import { Link } from "@components/Link";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
|
||||
import { filters, findByCodeLazy, mapMangledModuleLazy } from "@webpack";
|
||||
import { FluxDispatcher, Forms, Toasts } from "@webpack/common";
|
||||
|
||||
const RpcUtils = findByPropsLazy("fetchApplicationsRPC", "getRemoteIconURL");
|
||||
const assetManager = mapMangledModuleLazy(
|
||||
"getAssetImage: size must === [number, number] for Twitch",
|
||||
{
|
||||
getAsset: filters.byCode("apply("),
|
||||
}
|
||||
);
|
||||
|
||||
const lookupRpcApp = findByCodeLazy(".APPLICATION_RPC(");
|
||||
|
||||
async function lookupAsset(applicationId: string, key: string): Promise<string> {
|
||||
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
|
||||
return (await assetManager.getAsset(applicationId, [key, undefined]))[0];
|
||||
}
|
||||
|
||||
const apps: any = {};
|
||||
async function lookupApp(applicationId: string): Promise<string> {
|
||||
const socket: any = {};
|
||||
await RpcUtils.fetchApplicationsRPC(socket, applicationId);
|
||||
await lookupRpcApp(socket, applicationId);
|
||||
return socket.application;
|
||||
}
|
||||
|
||||
@ -51,26 +58,6 @@ export default definePlugin({
|
||||
</>
|
||||
),
|
||||
|
||||
async handleEvent(e: MessageEvent<any>) {
|
||||
const data = JSON.parse(e.data);
|
||||
|
||||
const { activity } = data;
|
||||
const assets = activity?.assets;
|
||||
|
||||
if (assets?.large_image) assets.large_image = await lookupAsset(activity.application_id, assets.large_image);
|
||||
if (assets?.small_image) assets.small_image = await lookupAsset(activity.application_id, assets.small_image);
|
||||
|
||||
if (activity) {
|
||||
const appId = activity.application_id;
|
||||
apps[appId] ||= await lookupApp(appId);
|
||||
|
||||
const app = apps[appId];
|
||||
activity.name ||= app.name;
|
||||
}
|
||||
|
||||
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...data });
|
||||
},
|
||||
|
||||
async start() {
|
||||
// ArmCord comes with its own arRPC implementation, so this plugin just confuses users
|
||||
if ("armcord" in window) return;
|
||||
@ -78,7 +65,22 @@ export default definePlugin({
|
||||
if (ws) ws.close();
|
||||
ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket
|
||||
|
||||
ws.onmessage = this.handleEvent;
|
||||
ws.onmessage = async e => { // on message, set status to data
|
||||
const data = JSON.parse(e.data);
|
||||
|
||||
if (data.activity?.assets?.large_image) data.activity.assets.large_image = await lookupAsset(data.activity.application_id, data.activity.assets.large_image);
|
||||
if (data.activity?.assets?.small_image) data.activity.assets.small_image = await lookupAsset(data.activity.application_id, data.activity.assets.small_image);
|
||||
|
||||
if (data.activity) {
|
||||
const appId = data.activity.application_id;
|
||||
apps[appId] ||= await lookupApp(appId);
|
||||
|
||||
const app = apps[appId];
|
||||
data.activity.name ||= app.name;
|
||||
}
|
||||
|
||||
FluxDispatcher.dispatch({ type: "LOCAL_ACTIVITY_UPDATE", ...data });
|
||||
};
|
||||
|
||||
const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 1000)); // check if open after 1s
|
||||
if (!connectionSuccessful) {
|
||||
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||
{
|
||||
find: "BAN_CONFIRM_TITLE.",
|
||||
replacement: {
|
||||
match: /src:\i\("\d+"\)/g,
|
||||
match: /src:\w\(\d+\)/g,
|
||||
replace: "src: Vencord.Settings.plugins.BANger.source"
|
||||
}
|
||||
}
|
||||
|
@ -16,44 +16,56 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { Settings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import { find, findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { useStateFromStores } from "@webpack/common";
|
||||
import type { CSSProperties } from "react";
|
||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { i18n, React, useStateFromStores } from "@webpack/common";
|
||||
|
||||
import { ExpandedGuildFolderStore, settings } from ".";
|
||||
const cl = classNameFactory("vc-bf-");
|
||||
const classes = findByPropsLazy("sidebar", "guilds");
|
||||
|
||||
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
|
||||
const Animations = findByPropsLazy("a", "animated", "useTransition");
|
||||
const GuildsBar = LazyComponent(() => find(m => m.type?.toString().includes('("guildsnav")')));
|
||||
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
|
||||
const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
||||
|
||||
export default ErrorBoundary.wrap(guildsBarProps => {
|
||||
function Guilds(props: {
|
||||
className: string;
|
||||
bfGuildFolders: any[];
|
||||
}) {
|
||||
// @ts-expect-error
|
||||
const res = Vencord.Plugins.plugins.BetterFolders.Guilds(props);
|
||||
|
||||
// TODO: Make this better
|
||||
const scrollerProps = res.props.children?.props?.children?.props?.children?.[1]?.props;
|
||||
if (scrollerProps?.children) {
|
||||
const servers = scrollerProps.children.find(c => c?.props?.["aria-label"] === i18n.Messages.SERVERS);
|
||||
if (servers) scrollerProps.children = servers;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
export default ErrorBoundary.wrap(() => {
|
||||
const expandedFolders = useStateFromStores([ExpandedGuildFolderStore], () => ExpandedGuildFolderStore.getExpandedFolders());
|
||||
const isFullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext());
|
||||
const fullscreen = useStateFromStores([ChannelRTCStore], () => ChannelRTCStore.isFullscreenInContext());
|
||||
|
||||
const guilds = document.querySelector(`.${classes.guilds}`);
|
||||
|
||||
const visible = !!expandedFolders.size;
|
||||
const className = cl("folder-sidebar", { fullscreen });
|
||||
|
||||
const Sidebar = (
|
||||
<GuildsBar
|
||||
{...guildsBarProps}
|
||||
isBetterFolders={true}
|
||||
betterFoldersExpandedIds={expandedFolders}
|
||||
<Guilds
|
||||
className={classes.guilds}
|
||||
bfGuildFolders={Array.from(expandedFolders)}
|
||||
/>
|
||||
);
|
||||
|
||||
const visible = !!expandedFolders.size;
|
||||
const guilds = document.querySelector(guildsBarProps.className.split(" ").map(c => `.${c}`).join(""));
|
||||
|
||||
// We need to display none if we are in fullscreen. Yes this seems horrible doing with css, but it's literally how Discord does it.
|
||||
// Also display flex otherwise to fix scrolling
|
||||
const barStyle = {
|
||||
display: isFullscreen ? "none" : "flex",
|
||||
} as CSSProperties;
|
||||
|
||||
if (!guilds || !settings.store.sidebarAnim) {
|
||||
if (!guilds || !Settings.plugins.BetterFolders.sidebarAnim)
|
||||
return visible
|
||||
? <div style={barStyle}>{Sidebar}</div>
|
||||
? <div className={className}>{Sidebar}</div>
|
||||
: null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Animations.Transition
|
||||
@ -63,13 +75,11 @@ export default ErrorBoundary.wrap(guildsBarProps => {
|
||||
leave={{ width: 0 }}
|
||||
config={{ duration: 200 }}
|
||||
>
|
||||
{(animationStyle, show) =>
|
||||
show && (
|
||||
<Animations.animated.div style={{ ...animationStyle, ...barStyle }}>
|
||||
{(style, show) => show && (
|
||||
<Animations.animated.div style={style} className={className}>
|
||||
{Sidebar}
|
||||
</Animations.animated.div>
|
||||
)
|
||||
}
|
||||
)}
|
||||
</Animations.Transition>
|
||||
);
|
||||
}, { noop: true });
|
||||
|
17
src/plugins/betterFolders/betterFolders.css
Normal file
17
src/plugins/betterFolders/betterFolders.css
Normal file
@ -0,0 +1,17 @@
|
||||
.vc-bf-folder-sidebar [class*="wrapper-"] > [class*="listItem-"]:first-of-type,
|
||||
.vc-bf-folder-sidebar [class*="unreadMentionsIndicator"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.vc-bf-folder-sidebar [class*="expandedFolderBackground-"] {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.vc-bf-folder-sidebar {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.vc-bf-fullscreen {
|
||||
width: 0 !important;
|
||||
visibility: hidden;
|
||||
}
|
177
src/plugins/betterFolders/index.ts
Normal file
177
src/plugins/betterFolders/index.ts
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "./betterFolders.css";
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
|
||||
import { FluxDispatcher } from "@webpack/common";
|
||||
|
||||
import FolderSideBar from "./FolderSideBar";
|
||||
|
||||
const GuildsTree = findLazy(m => m.prototype?.convertToFolder);
|
||||
const GuildFolderStore = findStoreLazy("SortedGuildStore");
|
||||
const ExpandedFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
||||
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
|
||||
|
||||
const settings = definePluginSettings({
|
||||
sidebar: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Display servers from folder on dedicated sidebar",
|
||||
default: true,
|
||||
},
|
||||
sidebarAnim: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Animate opening the folder sidebar",
|
||||
default: true,
|
||||
},
|
||||
closeAllFolders: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Close all folders when selecting a server not in a folder",
|
||||
default: false,
|
||||
},
|
||||
closeAllHomeButton: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Close all folders when clicking on the home button",
|
||||
default: false,
|
||||
},
|
||||
closeOthers: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Close other folders when opening a folder",
|
||||
default: false,
|
||||
},
|
||||
forceOpen: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Force a folder to open when switching to a server of that folder",
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "BetterFolders",
|
||||
description: "Shows server folders on dedicated sidebar and adds folder related improvements",
|
||||
authors: [Devs.juby, Devs.AutumnVN],
|
||||
patches: [
|
||||
{
|
||||
find: '("guildsnav")',
|
||||
predicate: () => settings.store.sidebar,
|
||||
replacement: [
|
||||
{
|
||||
match: /(\i)\(\){return \i\(\(0,\i\.jsx\)\("div",{className:\i\(\)\.guildSeparator}\)\)}/,
|
||||
replace: "$&$self.Separator=$1;"
|
||||
},
|
||||
|
||||
// Folder component patch
|
||||
{
|
||||
match: /\i\(\(function\(\i,\i,\i\){var \i=\i\.key;return.+\(\i\)},\i\)}\)\)/,
|
||||
replace: "arguments[0].bfHideServers?null:$&"
|
||||
},
|
||||
|
||||
// BEGIN Guilds component patch
|
||||
{
|
||||
match: /(\i)\.themeOverride,(.{15,25}\(function\(\){var \i=)(\i\.\i\.getGuildsTree\(\))/,
|
||||
replace: "$1.themeOverride,bfPatch=$1.bfGuildFolders,$2bfPatch?$self.getGuildsTree(bfPatch,$3):$3"
|
||||
},
|
||||
{
|
||||
match: /return(\(0,\i\.jsx\))(\(\i,{)(folderNode:\i,setNodeRef:\i\.setNodeRef,draggable:!0,.+},\i\.id\));case/,
|
||||
replace: "var bfHideServers=typeof bfPatch==='undefined',folder=$1$2bfHideServers,$3;return !bfHideServers&&arguments[1]?[$1($self.Separator,{}),folder]:folder;case"
|
||||
},
|
||||
// END
|
||||
|
||||
{
|
||||
match: /\("guildsnav"\);return\(0,\i\.jsx\)\(.{1,6},{navigator:\i,children:\(0,\i\.jsx\)\(/,
|
||||
replace: "$&$self.Guilds="
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "APPLICATION_LIBRARY,render",
|
||||
predicate: () => settings.store.sidebar,
|
||||
replacement: {
|
||||
match: /(\(0,\i\.jsx\))\(\i\..,{className:\i\(\)\.guilds,themeOverride:\i}\)/,
|
||||
replace: "$&,$1($self.FolderSideBar,{})"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: '("guildsnav")',
|
||||
predicate: () => settings.store.closeAllHomeButton,
|
||||
replacement: {
|
||||
match: ",onClick:function(){if(!__OVERLAY__){",
|
||||
replace: "$&$self.closeFolders();"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
settings,
|
||||
|
||||
start() {
|
||||
const getGuildFolder = (id: string) => GuildFolderStore.getGuildFolders().find(f => f.guildIds.includes(id));
|
||||
|
||||
FluxDispatcher.subscribe("CHANNEL_SELECT", this.onSwitch = data => {
|
||||
if (!settings.store.closeAllFolders && !settings.store.forceOpen)
|
||||
return;
|
||||
|
||||
if (this.lastGuildId !== data.guildId) {
|
||||
this.lastGuildId = data.guildId;
|
||||
|
||||
const guildFolder = getGuildFolder(data.guildId);
|
||||
if (guildFolder?.folderId) {
|
||||
if (settings.store.forceOpen && !ExpandedFolderStore.isFolderExpanded(guildFolder.folderId))
|
||||
FolderUtils.toggleGuildFolderExpand(guildFolder.folderId);
|
||||
} else if (settings.store.closeAllFolders)
|
||||
this.closeFolders();
|
||||
}
|
||||
});
|
||||
|
||||
FluxDispatcher.subscribe("TOGGLE_GUILD_FOLDER_EXPAND", this.onToggleFolder = e => {
|
||||
if (settings.store.closeOthers && !this.dispatching)
|
||||
FluxDispatcher.wait(() => {
|
||||
const expandedFolders = ExpandedFolderStore.getExpandedFolders();
|
||||
if (expandedFolders.size > 1) {
|
||||
this.dispatching = true;
|
||||
|
||||
for (const id of expandedFolders) if (id !== e.folderId)
|
||||
FolderUtils.toggleGuildFolderExpand(id);
|
||||
|
||||
this.dispatching = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
stop() {
|
||||
FluxDispatcher.unsubscribe("CHANNEL_SELECT", this.onSwitch);
|
||||
FluxDispatcher.unsubscribe("TOGGLE_GUILD_FOLDER_EXPAND", this.onToggleFolder);
|
||||
},
|
||||
|
||||
FolderSideBar,
|
||||
|
||||
getGuildsTree(folders, oldTree) {
|
||||
const tree = new GuildsTree();
|
||||
tree.root.children = oldTree.root.children.filter(e => folders.includes(e.id));
|
||||
tree.nodes = folders.map(id => oldTree.nodes[id]);
|
||||
return tree;
|
||||
},
|
||||
|
||||
closeFolders() {
|
||||
for (const id of ExpandedFolderStore.getExpandedFolders())
|
||||
FolderUtils.toggleGuildFolderExpand(id);
|
||||
},
|
||||
});
|
@ -1,307 +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 { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByProps, findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { FluxDispatcher, i18n } from "@webpack/common";
|
||||
|
||||
import FolderSideBar from "./FolderSideBar";
|
||||
|
||||
enum FolderIconDisplay {
|
||||
Never,
|
||||
Always,
|
||||
MoreThanOneFolderExpanded
|
||||
}
|
||||
|
||||
const GuildsTree = proxyLazy(() => findByProps("GuildsTree").GuildsTree);
|
||||
const SortedGuildStore = findStoreLazy("SortedGuildStore");
|
||||
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
||||
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
|
||||
|
||||
let lastGuildId = null as string | null;
|
||||
let dispatchingFoldersClose = false;
|
||||
|
||||
function getGuildFolder(id: string) {
|
||||
return SortedGuildStore.getGuildFolders().find(folder => folder.guildIds.includes(id));
|
||||
}
|
||||
|
||||
function closeFolders() {
|
||||
for (const id of ExpandedGuildFolderStore.getExpandedFolders())
|
||||
FolderUtils.toggleGuildFolderExpand(id);
|
||||
}
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
sidebar: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Display servers from folder on dedicated sidebar",
|
||||
restartNeeded: true,
|
||||
default: true
|
||||
},
|
||||
sidebarAnim: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Animate opening the folder sidebar",
|
||||
default: true
|
||||
},
|
||||
closeAllFolders: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Close all folders when selecting a server not in a folder",
|
||||
default: false
|
||||
},
|
||||
closeAllHomeButton: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Close all folders when clicking on the home button",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
closeOthers: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Close other folders when opening a folder",
|
||||
default: false
|
||||
},
|
||||
forceOpen: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Force a folder to open when switching to a server of that folder",
|
||||
default: false
|
||||
},
|
||||
keepIcons: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Keep showing guild icons in the primary guild bar folder when it's open in the BetterFolders sidebar",
|
||||
restartNeeded: true,
|
||||
default: false
|
||||
},
|
||||
showFolderIcon: {
|
||||
type: OptionType.SELECT,
|
||||
description: "Show the folder icon above the folder guilds in the BetterFolders sidebar",
|
||||
options: [
|
||||
{ label: "Never", value: FolderIconDisplay.Never },
|
||||
{ label: "Always", value: FolderIconDisplay.Always, default: true },
|
||||
{ label: "When more than one folder is expanded", value: FolderIconDisplay.MoreThanOneFolderExpanded }
|
||||
],
|
||||
restartNeeded: true
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "BetterFolders",
|
||||
description: "Shows server folders on dedicated sidebar and adds folder related improvements",
|
||||
authors: [Devs.juby, Devs.AutumnVN, Devs.Nuckyz],
|
||||
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: '("guildsnav")',
|
||||
predicate: () => settings.store.sidebar,
|
||||
replacement: [
|
||||
// Create the isBetterFolders variable in the GuildsBar component
|
||||
{
|
||||
match: /(?<=let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?)(?=}=\i,)/,
|
||||
replace: ",isBetterFolders"
|
||||
},
|
||||
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
|
||||
{
|
||||
match: /(useStateFromStoresArray\).{0,25}let \i)=(\i\.\i.getGuildsTree\(\))/,
|
||||
replace: (_, rest, guildsTree) => `${rest}=$self.getGuildTree(!!arguments[0].isBetterFolders,${guildsTree},arguments[0].betterFoldersExpandedIds)`
|
||||
},
|
||||
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
|
||||
{
|
||||
match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/,
|
||||
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0].isBetterFolders))"
|
||||
},
|
||||
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
|
||||
{
|
||||
match: /unreadMentionsIndicatorBottom,barClassName.+?}\)\]/,
|
||||
replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0].isBetterFolders))"
|
||||
},
|
||||
// Export the isBetterFolders variable to the folders component
|
||||
{
|
||||
match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,)/,
|
||||
replace: 'isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
// This is the parent folder component
|
||||
find: ".MAX_GUILD_FOLDER_NAME_LENGTH,",
|
||||
predicate: () => settings.store.sidebar && settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||
replacement: [
|
||||
{
|
||||
// Modify the expanded state to instead return the list of expanded folders
|
||||
match: /(useStateFromStores\).{0,20}=>)(\i\.\i)\.isFolderExpanded\(\i\)/,
|
||||
replace: (_, rest, ExpandedGuildFolderStore) => `${rest}${ExpandedGuildFolderStore}.getExpandedFolders()`,
|
||||
},
|
||||
{
|
||||
// Modify the expanded prop to use the boolean if the above patch fails, or check if the folder is expanded from the list if it succeeds
|
||||
// Also export the list of expanded folders to the child folder component if the patch above succeeds, else export undefined
|
||||
match: /(?<=folderNode:(\i),expanded:)\i(?=,)/,
|
||||
replace: (isExpandedOrExpandedIds, folderNote) => ""
|
||||
+ `typeof ${isExpandedOrExpandedIds}==="boolean"?${isExpandedOrExpandedIds}:${isExpandedOrExpandedIds}.has(${folderNote}.id),`
|
||||
+ `betterFoldersExpandedIds:${isExpandedOrExpandedIds} instanceof Set?${isExpandedOrExpandedIds}:void 0`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: ".FOLDER_ITEM_GUILD_ICON_MARGIN);",
|
||||
predicate: () => settings.store.sidebar,
|
||||
replacement: [
|
||||
// We use arguments[0] to access the isBetterFolders variable in this nested folder component (the parent exports all the props so we don't have to patch it)
|
||||
|
||||
// If we are rendering the normal GuildsBar sidebar, we make Discord think the folder is always collapsed to show better icons (the mini guild icons) and avoid transitions
|
||||
{
|
||||
predicate: () => settings.store.keepIcons,
|
||||
match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i),.+?;)(?=let)/,
|
||||
replace: (_, isExpanded) => `${isExpanded}=!!arguments[0].isBetterFolders&&${isExpanded};`
|
||||
},
|
||||
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
|
||||
{
|
||||
predicate: () => !settings.store.keepIcons,
|
||||
match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/,
|
||||
replace: "!!arguments[0].isBetterFolders&&"
|
||||
},
|
||||
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
|
||||
{
|
||||
predicate: () => !settings.store.keepIcons,
|
||||
match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/,
|
||||
replace: (m, isExpanded) => `${m}!arguments[0].isBetterFolders&&${isExpanded}?null:`
|
||||
},
|
||||
{
|
||||
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
|
||||
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||
match: /(?<=\.wrapper,children:\[)/,
|
||||
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)&&"
|
||||
},
|
||||
{
|
||||
// Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar
|
||||
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||
match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/,
|
||||
replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)?null:"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "APPLICATION_LIBRARY,render",
|
||||
predicate: () => settings.store.sidebar,
|
||||
replacement: {
|
||||
// Render the Better Folders sidebar
|
||||
match: /(?<=({className:\i\.guilds,themeOverride:\i})\))/,
|
||||
replace: ",$self.FolderSideBar($1)"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".Messages.DISCODO_DISABLED",
|
||||
predicate: () => settings.store.closeAllHomeButton,
|
||||
replacement: {
|
||||
// Close all folders when clicking the home button
|
||||
match: /(?<=onClick:\(\)=>{)(?=.{0,200}"discodo")/,
|
||||
replace: "$self.closeFolders();"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
flux: {
|
||||
CHANNEL_SELECT(data) {
|
||||
if (!settings.store.closeAllFolders && !settings.store.forceOpen)
|
||||
return;
|
||||
|
||||
if (lastGuildId !== data.guildId) {
|
||||
lastGuildId = data.guildId;
|
||||
const guildFolder = getGuildFolder(data.guildId);
|
||||
|
||||
if (guildFolder?.folderId) {
|
||||
if (settings.store.forceOpen && !ExpandedGuildFolderStore.isFolderExpanded(guildFolder.folderId)) {
|
||||
FolderUtils.toggleGuildFolderExpand(guildFolder.folderId);
|
||||
}
|
||||
} else if (settings.store.closeAllFolders) {
|
||||
closeFolders();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
TOGGLE_GUILD_FOLDER_EXPAND(data) {
|
||||
if (settings.store.closeOthers && !dispatchingFoldersClose) {
|
||||
dispatchingFoldersClose = true;
|
||||
|
||||
FluxDispatcher.wait(() => {
|
||||
const expandedFolders = ExpandedGuildFolderStore.getExpandedFolders();
|
||||
|
||||
if (expandedFolders.size > 1) {
|
||||
for (const id of expandedFolders) if (id !== data.folderId)
|
||||
FolderUtils.toggleGuildFolderExpand(id);
|
||||
}
|
||||
|
||||
dispatchingFoldersClose = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getGuildTree(isBetterFolders: boolean, oldTree: any, expandedFolderIds?: Set<any>) {
|
||||
if (!isBetterFolders || expandedFolderIds == null) return oldTree;
|
||||
|
||||
const newTree = new GuildsTree();
|
||||
// Children is every folder and guild which is not in a folder, this filters out only the expanded folders
|
||||
newTree.root.children = oldTree.root.children.filter(guildOrFolder => expandedFolderIds.has(guildOrFolder.id));
|
||||
// Nodes is every folder and guild, even if it's in a folder, this filters out only the expanded folders and guilds inside them
|
||||
newTree.nodes = Object.fromEntries(
|
||||
Object.entries(oldTree.nodes)
|
||||
.filter(([_, guildOrFolder]: any[]) => expandedFolderIds.has(guildOrFolder.id) || expandedFolderIds.has(guildOrFolder.parentId))
|
||||
);
|
||||
|
||||
return newTree;
|
||||
},
|
||||
|
||||
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
|
||||
return child => {
|
||||
if (isBetterFolders) {
|
||||
return child?.props?.["aria-label"] === i18n.Messages.SERVERS;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
},
|
||||
|
||||
makeGuildsBarTreeFilter(isBetterFolders: boolean) {
|
||||
return child => {
|
||||
if (isBetterFolders) {
|
||||
return "onScroll" in child.props;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
},
|
||||
|
||||
shouldShowFolderIconAndBackground(isBetterFolders: boolean, expandedFolderIds?: Set<any>) {
|
||||
if (!isBetterFolders) return true;
|
||||
|
||||
switch (settings.store.showFolderIcon) {
|
||||
case FolderIconDisplay.Never:
|
||||
return false;
|
||||
case FolderIconDisplay.Always:
|
||||
return true;
|
||||
case FolderIconDisplay.MoreThanOneFolderExpanded:
|
||||
return (expandedFolderIds?.size ?? 0) > 1;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
FolderSideBar: guildsBarProps => <FolderSideBar {...guildsBarProps} />,
|
||||
|
||||
closeFolders
|
||||
});
|
@ -34,18 +34,16 @@ export default definePlugin({
|
||||
},
|
||||
},
|
||||
{
|
||||
find: ".Messages.GIF,",
|
||||
find: ".embedGallerySide",
|
||||
replacement: {
|
||||
match: /alt:(\i)=(\i\.default\.Messages\.GIF)(?=,[^}]*\}=(\i))/,
|
||||
match: /(?<==(.{1,3})\.alt.{0,20})\?.{0,5}\.Messages\.GIF/,
|
||||
replace:
|
||||
// rename prop so we can always use default value
|
||||
"alt_$$:$1=$self.altify($3)||$2",
|
||||
"?($1.alt='GIF',$self.altify($1))",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
altify(props: any) {
|
||||
props.alt ??= "GIF";
|
||||
if (props.alt !== "GIF") return props.alt;
|
||||
|
||||
let url: string = props.original || props.src;
|
||||
|
@ -19,9 +19,6 @@
|
||||
import { Settings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
|
||||
const UserPopoutSectionCssClasses = findByPropsLazy("section", "lastSection");
|
||||
|
||||
export default definePlugin({
|
||||
name: "BetterNotesBox",
|
||||
@ -32,31 +29,17 @@ export default definePlugin({
|
||||
{
|
||||
find: "hideNote:",
|
||||
all: true,
|
||||
// Some modules match the find but the replacement is returned untouched
|
||||
noWarn: true,
|
||||
predicate: () => Vencord.Settings.plugins.BetterNotesBox.hide,
|
||||
replacement: {
|
||||
match: /hideNote:.+?(?=([,}].*?\)))/g,
|
||||
replace: (m, rest) => {
|
||||
const destructuringMatch = rest.match(/}=.+/);
|
||||
if (destructuringMatch == null) return "hideNote:!0";
|
||||
return m;
|
||||
match: /hideNote:.+?(?=[,}])/g,
|
||||
replace: "hideNote:true",
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
}, {
|
||||
find: "Messages.NOTE_PLACEHOLDER",
|
||||
replacement: {
|
||||
match: /\.NOTE_PLACEHOLDER,/,
|
||||
replace: "$&spellCheck:!Vencord.Settings.plugins.BetterNotesBox.noSpellCheck,"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".Messages.NOTE}",
|
||||
replacement: {
|
||||
match: /(?<=return \i\?)null(?=:\(0,\i\.jsxs)/,
|
||||
replace: "$self.patchPadding(arguments[0])"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
@ -73,12 +56,5 @@ export default definePlugin({
|
||||
disabled: () => Settings.plugins.BetterNotesBox.hide,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
|
||||
patchPadding(e: any) {
|
||||
if (!e.lastSection) return;
|
||||
return (
|
||||
<div className={UserPopoutSectionCssClasses.lastSection}></div>
|
||||
);
|
||||
}
|
||||
});
|
@ -38,7 +38,6 @@ export default definePlugin({
|
||||
{
|
||||
find: '"dot"===',
|
||||
all: true,
|
||||
noWarn: true,
|
||||
predicate: () => Settings.plugins.BetterRoleDot.bothStyles,
|
||||
replacement: {
|
||||
match: /"(?:username|dot)"===\i(?!\.\i)/g,
|
||||
@ -49,7 +48,6 @@ export default definePlugin({
|
||||
{
|
||||
find: ".ADD_ROLE_A11Y_LABEL",
|
||||
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
||||
noWarn: true,
|
||||
replacement: {
|
||||
match: /"dot"===\i/,
|
||||
replace: "true"
|
||||
@ -58,7 +56,6 @@ export default definePlugin({
|
||||
{
|
||||
find: ".roleVerifiedIcon",
|
||||
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
||||
noWarn: true,
|
||||
replacement: {
|
||||
match: /"dot"===\i/,
|
||||
replace: "true"
|
||||
|
@ -29,8 +29,9 @@ export default definePlugin({
|
||||
replacement: {
|
||||
// Discord merges multiple props here with Object.assign()
|
||||
// This patch passes a third object to it with which we override onClick and onContextMenu
|
||||
match: /CHAT_ATTACH_UPLOAD_OR_INVITE,onDoubleClick:(.+?:void 0),\.\.\.(\i),/,
|
||||
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
|
||||
match: /CHAT_ATTACH_UPLOAD_OR_INVITE,onDoubleClick:(.+?:void 0)\},(.{1,3})\)/,
|
||||
replace: (m, onDblClick, otherProps) =>
|
||||
`${m.slice(0, -1)},{onClick:${onDblClick},onContextMenu:${otherProps}.onClick})`,
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -45,8 +45,11 @@ export default definePlugin({
|
||||
{
|
||||
find: ".embedWrapper,embed",
|
||||
replacement: [{
|
||||
match: /\.embedWrapper/g,
|
||||
replace: "$&+(this.props.channel.nsfw?' vc-nsfw-img':'')"
|
||||
match: /(\.renderEmbed=.+?(.)=.\.props)(.+?\.embedWrapper)/g,
|
||||
replace: "$1,vcProps=$2$3+(vcProps.channel.nsfw?' vc-nsfw-img':'')"
|
||||
}, {
|
||||
match: /(\.renderAttachments=.+?(.)=this\.props)(.+?\.embedWrapper)/g,
|
||||
replace: "$1,vcProps=$2$3+(vcProps.nsfw?' vc-nsfw-img':'')"
|
||||
}]
|
||||
}
|
||||
],
|
||||
|
@ -73,9 +73,9 @@ export default definePlugin({
|
||||
},
|
||||
|
||||
patches: [{
|
||||
find: "renderConnectionStatus(){",
|
||||
find: ".renderConnectionStatus=",
|
||||
replacement: {
|
||||
match: /(?<=renderConnectionStatus\(\)\{.+\.channel,children:)\i/,
|
||||
match: /(?<=renderConnectionStatus=.+\.channel,children:)\w/,
|
||||
replace: "[$&, $self.renderTimer(this.props.channel.id)]"
|
||||
}
|
||||
}],
|
||||
|
@ -34,9 +34,8 @@ export default definePlugin({
|
||||
{
|
||||
find: ".AVATAR_STATUS_MOBILE_16;",
|
||||
replacement: {
|
||||
match: /(?<=fromIsMobile:\i=!0,.+?)status:(\i)/,
|
||||
// Rename field to force it to always use "online"
|
||||
replace: 'status_$:$1="online"'
|
||||
match: /(\.fromIsMobile,.+?)\i.status/,
|
||||
replace: (_, rest) => `${rest}"online"`
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -22,16 +22,23 @@ import { Devs } from "@utils/constants";
|
||||
import { isTruthy } from "@utils/guards";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||
import { ApplicationAssetUtils, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
|
||||
import { filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
|
||||
import { FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common";
|
||||
|
||||
const ActivityComponent = findByCodeLazy("onOpenGameProfile");
|
||||
const ActivityClassName = findByPropsLazy("activity", "buttonColor");
|
||||
const Colors = findByPropsLazy("profileColors");
|
||||
|
||||
const assetManager = mapMangledModuleLazy(
|
||||
"getAssetImage: size must === [number, number] for Twitch",
|
||||
{
|
||||
getAsset: filters.byCode("apply("),
|
||||
}
|
||||
);
|
||||
|
||||
async function getApplicationAsset(key: string): Promise<string> {
|
||||
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
|
||||
return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0];
|
||||
return (await assetManager.getAsset(settings.store.appID, [key, undefined]))[0];
|
||||
}
|
||||
|
||||
interface ActivityAssets {
|
||||
@ -396,7 +403,7 @@ export default definePlugin({
|
||||
return (
|
||||
<>
|
||||
<Forms.FormText>
|
||||
Go to <Link href="https://discord.com/developers/applications">Discord Developer Portal</Link> to create an application and
|
||||
Go to <Link href="https://discord.com/developers/applications">Discord Deverloper Portal</Link> to create an application and
|
||||
get the application ID.
|
||||
</Forms.FormText>
|
||||
<Forms.FormText>
|
||||
|
@ -63,7 +63,7 @@ async function embedDidMount(this: Component<Props>) {
|
||||
embed.rawTitle = titles[0].title;
|
||||
}
|
||||
|
||||
if (thumbnails[0]?.votes >= 0 && thumbnails[0].timestamp) {
|
||||
if (thumbnails[0]?.votes >= 0) {
|
||||
embed.dearrow.oldThumb = embed.thumbnail.proxyURL;
|
||||
embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`;
|
||||
}
|
||||
@ -147,8 +147,8 @@ export default definePlugin({
|
||||
replacement: [
|
||||
// patch componentDidMount to replace embed thumbnail and title
|
||||
{
|
||||
match: /render\(\)\{let\{embed:/,
|
||||
replace: "componentDidMount=$self.embedDidMount;$&"
|
||||
match: /(\i).render=function.{0,50}\i\.embed/,
|
||||
replace: "$1.componentDidMount=$self.embedDidMount,$&"
|
||||
},
|
||||
|
||||
// add dearrow button
|
||||
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||
{
|
||||
find: ".Messages.BOT_CALL_IDLE_DISCONNECT",
|
||||
replacement: {
|
||||
match: /(?<=function \i\(\){)(?=.{1,120}\.Messages\.BOT_CALL_IDLE_DISCONNECT)/,
|
||||
match: /(?<=function \i\(\){)(?=.{1,100}\.Messages\.BOT_CALL_IDLE_DISCONNECT)/,
|
||||
replace: "return;"
|
||||
}
|
||||
}
|
||||
|
@ -23,12 +23,12 @@ import { Logger } from "@utils/Logger";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { findByCodeLazy, findStoreLazy } from "@webpack";
|
||||
import { EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
|
||||
import { Promisable } from "type-fest";
|
||||
|
||||
const StickersStore = findStoreLazy("StickersStore");
|
||||
const EmojiManager = findByPropsLazy("fetchEmoji", "uploadEmoji", "deleteEmoji");
|
||||
const uploadEmoji = findByCodeLazy('"EMOJI_UPLOAD_START"', "GUILD_EMOJIS(");
|
||||
|
||||
interface Sticker {
|
||||
t: "Sticker";
|
||||
@ -106,7 +106,7 @@ async function cloneEmoji(guildId: string, emoji: Emoji) {
|
||||
reader.readAsDataURL(data);
|
||||
});
|
||||
|
||||
return EmojiManager.uploadEmoji({
|
||||
return uploadEmoji({
|
||||
guildId,
|
||||
name: emoji.name.split("~")[0],
|
||||
image: dataUrl
|
||||
@ -155,15 +155,10 @@ async function doClone(guildId: string, data: Sticker | Emoji) {
|
||||
type: Toasts.Type.SUCCESS,
|
||||
id: Toasts.genId()
|
||||
});
|
||||
} catch (e: any) {
|
||||
let message = "Something went wrong (check console!)";
|
||||
try {
|
||||
message = JSON.parse(e.text).message;
|
||||
} catch { }
|
||||
|
||||
} catch (e) {
|
||||
new Logger("EmoteCloner").error("Failed to clone", data.name, "to", guildId, e);
|
||||
Toasts.show({
|
||||
message: "Failed to clone: " + message,
|
||||
message: "Oopsie something went wrong :( Check console!!!",
|
||||
type: Toasts.Type.FAILURE,
|
||||
id: Toasts.genId()
|
||||
});
|
||||
|
@ -33,6 +33,12 @@ const settings = definePluginSettings({
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false,
|
||||
restartNeeded: true
|
||||
},
|
||||
forceStagingBanner: {
|
||||
description: "Whether to force Staging banner under user area.",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: false,
|
||||
restartNeeded: true
|
||||
}
|
||||
});
|
||||
|
||||
@ -52,7 +58,7 @@ export default definePlugin({
|
||||
{
|
||||
find: "Object.defineProperties(this,{isDeveloper",
|
||||
replacement: {
|
||||
match: /(?<={isDeveloper:\{[^}]+?,get:\(\)=>)\i/,
|
||||
match: /(?<={isDeveloper:\{[^}]+?,get:function\(\)\{return )\w/,
|
||||
replace: "true"
|
||||
}
|
||||
},
|
||||
@ -64,26 +70,25 @@ export default definePlugin({
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".isStaff=()",
|
||||
find: ".isStaff=function(){",
|
||||
predicate: () => settings.store.enableIsStaff,
|
||||
replacement: [
|
||||
{
|
||||
match: /=>*?(\i)\.hasFlag\((\i\.\i)\.STAFF\)}/,
|
||||
replace: (_, user, flags) => `=>Vencord.Webpack.Common.UserStore.getCurrentUser()?.id===${user}.id||${user}.hasFlag(${flags}.STAFF)}`
|
||||
match: /return\s*?(\i)\.hasFlag\((\i\.\i)\.STAFF\)}/,
|
||||
replace: (_, user, flags) => `return Vencord.Webpack.Common.UserStore.getCurrentUser()?.id===${user}.id||${user}.hasFlag(${flags}.STAFF)}`
|
||||
},
|
||||
{
|
||||
match: /hasFreePremium\(\){return this.isStaff\(\)\s*?\|\|/,
|
||||
replace: "hasFreePremium(){return ",
|
||||
match: /hasFreePremium=function\(\){return this.isStaff\(\)\s*?\|\|/,
|
||||
replace: "hasFreePremium=function(){return ",
|
||||
}
|
||||
]
|
||||
},
|
||||
// Fix search history being disabled / broken with isStaff
|
||||
{
|
||||
find: '("showNewSearch")',
|
||||
predicate: () => settings.store.enableIsStaff,
|
||||
find: ".Messages.DEV_NOTICE_STAGING",
|
||||
predicate: () => settings.store.forceStagingBanner,
|
||||
replacement: {
|
||||
match: /(?<=showNewSearch"\);return)\s?/,
|
||||
replace: "!1&&"
|
||||
match: /"staging"===window\.GLOBAL_ENV\.RELEASE_CHANNEL/,
|
||||
replace: "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -24,33 +24,35 @@ import { getCurrentGuild } from "@utils/discord";
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { ChannelStore, EmojiStore, FluxDispatcher, lodash, Parser, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
|
||||
import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
|
||||
import { ChannelStore, EmojiStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common";
|
||||
import type { Message } from "discord-types/general";
|
||||
import { applyPalette, GIFEncoder, quantize } from "gifenc";
|
||||
import type { ReactElement, ReactNode } from "react";
|
||||
|
||||
const DRAFT_TYPE = 0;
|
||||
const promptToUpload = findByCodeLazy("UPLOAD_FILE_LIMIT_ERROR");
|
||||
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
|
||||
const PreloadedUserSettingsProtoHandler = findLazy(m => m.ProtoClass?.typeName === "discord_protos.discord_users.v1.PreloadedUserSettings");
|
||||
const ReaderFactory = findByPropsLazy("readerFactory");
|
||||
const StickerStore = findStoreLazy("StickersStore") as {
|
||||
getPremiumPacks(): StickerPack[];
|
||||
getAllGuildStickers(): Map<string, Sticker[]>;
|
||||
getStickerById(id: string): Sticker | undefined;
|
||||
};
|
||||
|
||||
const UserSettingsProtoStore = findStoreLazy("UserSettingsProtoStore");
|
||||
const ProtoUtils = findByPropsLazy("BINARY_READ_OPTIONS");
|
||||
function searchProtoClass(localName: string, parentProtoClass: any) {
|
||||
if (!parentProtoClass) return;
|
||||
|
||||
function searchProtoClassField(localName: string, protoClass: any) {
|
||||
const field = protoClass?.fields?.find((field: any) => field.localName === localName);
|
||||
const field = parentProtoClass.fields.find(field => field.localName === localName);
|
||||
if (!field) return;
|
||||
|
||||
const fieldGetter = Object.values(field).find(value => typeof value === "function") as any;
|
||||
return fieldGetter?.();
|
||||
const getter: any = Object.values(field).find(value => typeof value === "function");
|
||||
return getter?.();
|
||||
}
|
||||
|
||||
const PreloadedUserSettingsActionCreators = proxyLazy(() => UserSettingsActionCreators.PreloadedUserSettingsActionCreators);
|
||||
const AppearanceSettingsActionCreators = proxyLazy(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass));
|
||||
const ClientThemeSettingsActionsCreators = proxyLazy(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators));
|
||||
const AppearanceSettingsProto = proxyLazy(() => searchProtoClass("appearance", PreloadedUserSettingsProtoHandler.ProtoClass));
|
||||
const ClientThemeSettingsProto = proxyLazy(() => searchProtoClass("clientThemeSettings", AppearanceSettingsProto));
|
||||
|
||||
const USE_EXTERNAL_EMOJIS = 1n << 18n;
|
||||
const USE_EXTERNAL_STICKERS = 1n << 37n;
|
||||
@ -174,46 +176,39 @@ export default definePlugin({
|
||||
predicate: () => settings.store.enableEmojiBypass,
|
||||
replacement: [
|
||||
{
|
||||
// Create a variable for the intention of listing the emoji
|
||||
match: /(?<=,intention:(\i).+?;)/,
|
||||
replace: (_, intention) => `let fakeNitroIntention=${intention};`
|
||||
match: /(?<=(\i)=\i\.intention)/,
|
||||
replace: (_, intention) => `,fakeNitroIntention=${intention}`
|
||||
},
|
||||
{
|
||||
// Send the intention of listing the emoji to the nitro permission check functions
|
||||
match: /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g,
|
||||
replace: '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0'
|
||||
},
|
||||
{
|
||||
// Disallow the emoji if the intention doesn't allow it
|
||||
match: /(&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/,
|
||||
replace: (_, rest, canUseExternal) => `${rest}(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))`
|
||||
},
|
||||
{
|
||||
// Make the emoji always available if the intention allows it
|
||||
match: /if\(!\i\.available/,
|
||||
replace: m => `${m}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention))`
|
||||
}
|
||||
]
|
||||
},
|
||||
// Allow emojis and animated emojis to be sent everywhere
|
||||
{
|
||||
find: "canUseAnimatedEmojis:function",
|
||||
predicate: () => settings.store.enableEmojiBypass,
|
||||
replacement: {
|
||||
match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))(?=})/g,
|
||||
match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))/g,
|
||||
replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`
|
||||
}
|
||||
},
|
||||
// Allow stickers to be sent everywhere
|
||||
{
|
||||
find: "canUseCustomStickersEverywhere:function",
|
||||
find: "canUseStickersEverywhere:function",
|
||||
predicate: () => settings.store.enableStickerBypass,
|
||||
replacement: {
|
||||
match: /canUseCustomStickersEverywhere:function\(\i\){/,
|
||||
match: /canUseStickersEverywhere:function\(\i\){/,
|
||||
replace: "$&return true;"
|
||||
},
|
||||
},
|
||||
// Make stickers always available
|
||||
{
|
||||
find: "\"SENDABLE\"",
|
||||
predicate: () => settings.store.enableStickerBypass,
|
||||
@ -222,13 +217,13 @@ export default definePlugin({
|
||||
replace: "true?"
|
||||
}
|
||||
},
|
||||
// Allow streaming with high quality
|
||||
{
|
||||
find: "canUseHighVideoUploadQuality:function",
|
||||
predicate: () => settings.store.enableStreamQualityBypass,
|
||||
replacement: [
|
||||
"canUseHighVideoUploadQuality",
|
||||
"canStreamQuality",
|
||||
// TODO: Remove the last two when they get removed from stable
|
||||
"(?:canStreamQuality|canStreamHighQuality|canStreamMidQuality)",
|
||||
].map(func => {
|
||||
return {
|
||||
match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"),
|
||||
@ -236,16 +231,14 @@ export default definePlugin({
|
||||
};
|
||||
})
|
||||
},
|
||||
// Remove boost requirements to stream with high quality
|
||||
{
|
||||
find: "STREAM_FPS_OPTION.format",
|
||||
predicate: () => settings.store.enableStreamQualityBypass,
|
||||
replacement: {
|
||||
match: /guildPremiumTier:\i\.\i\.TIER_\d,?/g,
|
||||
match: /(userPremiumType|guildPremiumTier):.{0,10}TIER_\d,?/g,
|
||||
replace: ""
|
||||
}
|
||||
},
|
||||
// Allow client themes to be changeable
|
||||
{
|
||||
find: "canUseClientThemes:function",
|
||||
replacement: {
|
||||
@ -257,22 +250,19 @@ export default definePlugin({
|
||||
find: '.displayName="UserSettingsProtoStore"',
|
||||
replacement: [
|
||||
{
|
||||
// Overwrite incoming connection settings proto with our local settings
|
||||
match: /CONNECTION_OPEN:function\((\i)\){/,
|
||||
replace: (m, props) => `${m}$self.handleProtoChange(${props}.userSettingsProto,${props}.user);`
|
||||
},
|
||||
{
|
||||
// Overwrite non local proto changes with our local settings
|
||||
match: /let{settings:/,
|
||||
replace: "arguments[0].local||$self.handleProtoChange(arguments[0].settings.proto);$&"
|
||||
match: /=(\i)\.local;/,
|
||||
replace: (m, props) => `${m}${props}.local||$self.handleProtoChange(${props}.settings.proto);`
|
||||
}
|
||||
]
|
||||
},
|
||||
// Call our function to handle changing the gradient theme when selecting a new one
|
||||
{
|
||||
find: ",updateTheme(",
|
||||
find: "updateTheme:function",
|
||||
replacement: {
|
||||
match: /(function \i\(\i\){let{backgroundGradientPresetId:(\i).+?)(\i\.\i\.updateAsync.+?theme=(.+?),.+?},\i\))/,
|
||||
match: /(function \i\(\i\){var (\i)=\i\.backgroundGradientPresetId.+?)(\i\.\i\.updateAsync.+?theme=(.+?);.+?\),\i\))/,
|
||||
replace: (_, rest, backgroundGradientPresetId, originalCall, theme) => `${rest}$self.handleGradientThemeSelect(${backgroundGradientPresetId},${theme},()=>${originalCall});`
|
||||
}
|
||||
},
|
||||
@ -280,13 +270,11 @@ export default definePlugin({
|
||||
find: '["strong","em","u","text","inlineCode","s","spoiler"]',
|
||||
replacement: [
|
||||
{
|
||||
// Call our function to decide whether the emoji link should be kept or not
|
||||
predicate: () => settings.store.transformEmojis,
|
||||
match: /1!==(\i)\.length\|\|1!==\i\.length/,
|
||||
replace: (m, content) => `${m}||$self.shouldKeepEmojiLink(${content}[0])`
|
||||
},
|
||||
{
|
||||
// Patch the rendered message content to add fake nitro emojis or remove sticker links
|
||||
predicate: () => settings.store.transformEmojis || settings.store.transformStickers,
|
||||
match: /(?=return{hasSpoilerEmbeds:\i,content:(\i)})/,
|
||||
replace: (_, content) => `${content}=$self.patchFakeNitroEmojisOrRemoveStickersLinks(${content},arguments[2]?.formatInline);`
|
||||
@ -294,41 +282,36 @@ export default definePlugin({
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "renderEmbeds(",
|
||||
find: "renderEmbeds=function",
|
||||
replacement: [
|
||||
{
|
||||
// Call our function to decide whether the embed should be ignored or not
|
||||
predicate: () => settings.store.transformEmojis || settings.store.transformStickers,
|
||||
match: /(renderEmbeds\((\i)\){)(.+?embeds\.map\((\i)=>{)/,
|
||||
match: /(renderEmbeds=function\((\i)\){)(.+?embeds\.map\(\(function\((\i)\){)/,
|
||||
replace: (_, rest1, message, rest2, embed) => `${rest1}const fakeNitroMessage=${message};${rest2}if($self.shouldIgnoreEmbed(${embed},fakeNitroMessage))return null;`
|
||||
},
|
||||
{
|
||||
// Patch the stickers array to add fake nitro stickers
|
||||
predicate: () => settings.store.transformStickers,
|
||||
match: /(?<=renderStickersAccessories\((\i)\){let (\i)=\(0,\i\.\i\)\(\i\).+?;)/,
|
||||
replace: (_, message, stickers) => `${stickers}=$self.patchFakeNitroStickers(${stickers},${message});`
|
||||
match: /renderStickersAccessories=function\((\i)\){var (\i)=\(0,\i\.\i\)\(\i\),/,
|
||||
replace: (m, message, stickers) => `${m}${stickers}=$self.patchFakeNitroStickers(${stickers},${message}),`
|
||||
},
|
||||
{
|
||||
// Filter attachments to remove fake nitro stickers or emojis
|
||||
predicate: () => settings.store.transformStickers,
|
||||
match: /renderAttachments\(\i\){let{attachments:(\i).+?;/,
|
||||
match: /renderAttachments=function\(\i\){var (\i)=\i.attachments.+?;/,
|
||||
replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: ".Messages.STICKER_POPOUT_UNJOINED_PRIVATE_GUILD_DESCRIPTION.format",
|
||||
find: ".STICKER_IN_MESSAGE_HOVER,",
|
||||
predicate: () => settings.store.transformStickers,
|
||||
replacement: [
|
||||
{
|
||||
// Export the renderable sticker to be used in the fake nitro sticker notice
|
||||
match: /let{renderableSticker:(\i).{0,250}isGuildSticker.+?channel:\i,/,
|
||||
replace: (m, renderableSticker) => `${m}fakeNitroRenderableSticker:${renderableSticker},`
|
||||
match: /var (\i)=\i\.renderableSticker,.{0,50}closePopout.+?channel:\i,closePopout:\i,/,
|
||||
replace: (m, renderableSticker) => `${m}renderableSticker:${renderableSticker},`
|
||||
},
|
||||
{
|
||||
// Add the fake nitro sticker notice
|
||||
match: /(let \i,{sticker:\i,channel:\i,closePopout:\i.+?}=(\i).+?;)(.+?description:)(\i)(?=,sticker:\i)/,
|
||||
replace: (_, rest, props, rest2, reactNode) => `${rest}let{fakeNitroRenderableSticker}=${props};${rest2}$self.addFakeNotice(${FakeNoticeType.Sticker},${reactNode},!!fakeNitroRenderableSticker?.fake)`
|
||||
match: /(emojiSection.{0,50}description:)(\i)(?<=(\i)\.sticker,.+?)(?=,)/,
|
||||
replace: (_, rest, reactNode, props) => `${rest}$self.addFakeNotice(${FakeNoticeType.Sticker},${reactNode},!!${props}.renderableSticker?.fake)`
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -336,8 +319,7 @@ export default definePlugin({
|
||||
find: ".EMOJI_UPSELL_POPOUT_MORE_EMOJIS_OPENED,",
|
||||
predicate: () => settings.store.transformEmojis,
|
||||
replacement: {
|
||||
// Export the emoji node to be used in the fake nitro emoji notice
|
||||
match: /isDiscoverable:\i,shouldHideRoleSubscriptionCTA:\i,(?<={node:(\i),.+?)/,
|
||||
match: /isDiscoverable:\i,shouldHideRoleSubscriptionCTA:\i,(?<=(\i)=\i\.node.+?)/,
|
||||
replace: (m, node) => `${m}fakeNitroNode:${node},`
|
||||
}
|
||||
},
|
||||
@ -345,25 +327,8 @@ export default definePlugin({
|
||||
find: ".Messages.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION",
|
||||
predicate: () => settings.store.transformEmojis,
|
||||
replacement: {
|
||||
// Add the fake nitro emoji notice
|
||||
match: /(?<=isDiscoverable:\i,emojiComesFromCurrentGuild:\i,.+?}=(\i).+?;)(.+?return )(.{0,1000}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?)(?=},)/,
|
||||
replace: (_, props, rest, reactNode) => `let{fakeNitroNode}=${props};${rest}$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!fakeNitroNode?.fake)`
|
||||
}
|
||||
},
|
||||
// Allow using custom app icons
|
||||
{
|
||||
find: "canUsePremiumAppIcons:function",
|
||||
replacement: {
|
||||
match: /canUsePremiumAppIcons:function\(\i\){/,
|
||||
replace: "$&return true;"
|
||||
}
|
||||
},
|
||||
// Separate patch for allowing using custom app icons
|
||||
{
|
||||
find: "location:\"AppIconHome\"",
|
||||
replacement: {
|
||||
match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/,
|
||||
replace: "true"
|
||||
match: /(?<=\.Messages\.EMOJI_POPOUT_ADDED_PACK_DESCRIPTION.+?return ).{0,1200}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?(?=}\()/,
|
||||
replace: reactNode => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!arguments[0]?.fakeNitroNode?.fake)`
|
||||
}
|
||||
}
|
||||
],
|
||||
@ -381,30 +346,26 @@ export default definePlugin({
|
||||
},
|
||||
|
||||
handleProtoChange(proto: any, user: any) {
|
||||
if (proto == null || typeof proto === "string" || !UserSettingsProtoStore || !PreloadedUserSettingsActionCreators || !AppearanceSettingsActionCreators || !ClientThemeSettingsActionsCreators) return;
|
||||
if (proto == null || typeof proto === "string" || !UserSettingsProtoStore || (!proto.appearance && !AppearanceSettingsProto)) return;
|
||||
|
||||
const premiumType: number = user?.premium_type ?? UserStore?.getCurrentUser()?.premiumType ?? 0;
|
||||
|
||||
if (premiumType !== 2) {
|
||||
proto.appearance ??= AppearanceSettingsActionCreators.create();
|
||||
proto.appearance ??= AppearanceSettingsProto.create();
|
||||
|
||||
if (UserSettingsProtoStore.settings.appearance?.theme != null) {
|
||||
const appearanceSettingsDummy = AppearanceSettingsActionCreators.create({
|
||||
theme: UserSettingsProtoStore.settings.appearance.theme
|
||||
});
|
||||
|
||||
proto.appearance.theme = appearanceSettingsDummy.theme;
|
||||
proto.appearance.theme = UserSettingsProtoStore.settings.appearance.theme;
|
||||
}
|
||||
|
||||
if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null) {
|
||||
const clientThemeSettingsDummy = ClientThemeSettingsActionsCreators.create({
|
||||
if (UserSettingsProtoStore.settings.appearance?.clientThemeSettings?.backgroundGradientPresetId?.value != null && ClientThemeSettingsProto) {
|
||||
const clientThemeSettingsDummyProto = ClientThemeSettingsProto.create({
|
||||
backgroundGradientPresetId: {
|
||||
value: UserSettingsProtoStore.settings.appearance.clientThemeSettings.backgroundGradientPresetId.value
|
||||
}
|
||||
});
|
||||
|
||||
proto.appearance.clientThemeSettings ??= clientThemeSettingsDummy;
|
||||
proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummy.backgroundGradientPresetId;
|
||||
proto.appearance.clientThemeSettings ??= clientThemeSettingsDummyProto;
|
||||
proto.appearance.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.backgroundGradientPresetId;
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -413,26 +374,26 @@ export default definePlugin({
|
||||
const premiumType = UserStore?.getCurrentUser()?.premiumType ?? 0;
|
||||
if (premiumType === 2 || backgroundGradientPresetId == null) return original();
|
||||
|
||||
if (!PreloadedUserSettingsActionCreators || !AppearanceSettingsActionCreators || !ClientThemeSettingsActionsCreators || !ProtoUtils) return;
|
||||
if (!AppearanceSettingsProto || !ClientThemeSettingsProto || !ReaderFactory) return;
|
||||
|
||||
const currentAppearanceSettings = PreloadedUserSettingsActionCreators.getCurrentValue().appearance;
|
||||
const currentAppearanceProto = PreloadedUserSettingsProtoHandler.getCurrentValue().appearance;
|
||||
|
||||
const newAppearanceProto = currentAppearanceSettings != null
|
||||
? AppearanceSettingsActionCreators.fromBinary(AppearanceSettingsActionCreators.toBinary(currentAppearanceSettings), ProtoUtils.BINARY_READ_OPTIONS)
|
||||
: AppearanceSettingsActionCreators.create();
|
||||
const newAppearanceProto = currentAppearanceProto != null
|
||||
? AppearanceSettingsProto.fromBinary(AppearanceSettingsProto.toBinary(currentAppearanceProto), ReaderFactory)
|
||||
: AppearanceSettingsProto.create();
|
||||
|
||||
newAppearanceProto.theme = theme;
|
||||
|
||||
const clientThemeSettingsDummy = ClientThemeSettingsActionsCreators.create({
|
||||
const clientThemeSettingsDummyProto = ClientThemeSettingsProto.create({
|
||||
backgroundGradientPresetId: {
|
||||
value: backgroundGradientPresetId
|
||||
}
|
||||
});
|
||||
|
||||
newAppearanceProto.clientThemeSettings ??= clientThemeSettingsDummy;
|
||||
newAppearanceProto.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummy.backgroundGradientPresetId;
|
||||
newAppearanceProto.clientThemeSettings ??= clientThemeSettingsDummyProto;
|
||||
newAppearanceProto.clientThemeSettings.backgroundGradientPresetId = clientThemeSettingsDummyProto.backgroundGradientPresetId;
|
||||
|
||||
const proto = PreloadedUserSettingsActionCreators.ProtoClass.create();
|
||||
const proto = PreloadedUserSettingsProtoHandler.ProtoClass.create();
|
||||
proto.appearance = newAppearanceProto;
|
||||
|
||||
FluxDispatcher.dispatch({
|
||||
@ -558,7 +519,7 @@ export default definePlugin({
|
||||
};
|
||||
|
||||
try {
|
||||
return modifyChildren(lodash.cloneDeep(content));
|
||||
return modifyChildren(window._.cloneDeep(content));
|
||||
} catch (err) {
|
||||
new Logger("FakeNitro").error(err);
|
||||
return content;
|
||||
@ -755,7 +716,7 @@ export default definePlugin({
|
||||
gif.finish();
|
||||
|
||||
const file = new File([gif.bytesView()], `${stickerId}.gif`, { type: "image/gif" });
|
||||
UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), DRAFT_TYPE);
|
||||
promptToUpload([file], ChannelStore.getChannel(channelId), DRAFT_TYPE);
|
||||
},
|
||||
|
||||
start() {
|
||||
|
@ -87,15 +87,15 @@ export default definePlugin({
|
||||
authors: [Devs.Alyxia, Devs.Remty],
|
||||
patches: [
|
||||
{
|
||||
find: "UserProfileStore",
|
||||
find: "getUserProfile=",
|
||||
replacement: {
|
||||
match: /(?<=getUserProfile\(\i\){return )(\i\[\i\])/,
|
||||
match: /(?<=getUserProfile=function\(\i\){return )(\i\[\i\])/,
|
||||
replace: "$self.colorDecodeHook($1)"
|
||||
}
|
||||
}, {
|
||||
find: ".USER_SETTINGS_PROFILE_THEME_ACCENT",
|
||||
replacement: {
|
||||
match: /RESET_PROFILE_THEME}\)(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/,
|
||||
match: /RESET_PROFILE_THEME}\)(?<=},color:(\i).+?},color:(\i).+?)/,
|
||||
replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})"
|
||||
}
|
||||
}
|
||||
|
@ -39,27 +39,22 @@ export default definePlugin({
|
||||
description: "Puts your favorite emoji first in the emoji autocomplete.",
|
||||
patches: [
|
||||
{
|
||||
find: "renderResults({results:",
|
||||
find: ".activeCommandOption",
|
||||
replacement: [
|
||||
{
|
||||
// https://regex101.com/r/N7kpLM/1
|
||||
match: /let \i=.{1,100}renderResults\({results:(\i)\.query\.results,/,
|
||||
replace: "$self.sortEmojis($1);$&"
|
||||
},
|
||||
],
|
||||
// = someFunc(a.selectedIndex); ...trackEmojiSearch({ state: theState, isInPopoutExperimental: someBool })
|
||||
match: /=\i\(\i\.selectedIndex\);(?=.+?state:(\i),isInPopoutExperiment:\i)/,
|
||||
// self.sortEmojis(theState)
|
||||
replace: "$&$self.sortEmojis($1);"
|
||||
},
|
||||
|
||||
{
|
||||
find: "MAX_AUTOCOMPLETE_RESULTS+",
|
||||
replacement: [
|
||||
// set maxCount to Infinity so our sortEmojis callback gets the entire list, not just the first 10
|
||||
// and remove Discord's emojiResult slice, storing the endIndex on the array for us to use later
|
||||
{
|
||||
// https://regex101.com/r/x2mobQ/1
|
||||
// searchEmojis(...,maxCount: stuff) ... endEmojis = emojis.slice(0, maxCount - gifResults.length)
|
||||
match: /,maxCount:(\i)(.{1,500}\i)=(\i)\.slice\(0,(\i-\i\.length)\)/,
|
||||
match: /,maxCount:(\i)(.+?)=(\i)\.slice\(0,(\1-\i\.length)\)/,
|
||||
// ,maxCount:Infinity ... endEmojis = (emojis.sliceTo = n, emojis)
|
||||
replace: ",maxCount:Infinity$2=($3.sliceTo = $4, $3)"
|
||||
replace: ",maxCount:Infinity$2=($3.sliceTo=$4,$3)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ interface Instance {
|
||||
}
|
||||
|
||||
|
||||
const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchBarFullRow");
|
||||
const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchHeader", "searchInput");
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
searchOption: {
|
||||
@ -91,13 +91,13 @@ export default definePlugin({
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "renderHeaderContent()",
|
||||
find: "renderCategoryExtras",
|
||||
replacement: [
|
||||
{
|
||||
// https://regex101.com/r/07gpzP/1
|
||||
// https://regex101.com/r/4uHtTE/1
|
||||
// ($1 renderHeaderContent=function { ... switch (x) ... case FAVORITES:return) ($2) ($3 case default:return r.jsx(($<searchComp>), {...props}))
|
||||
match: /(renderHeaderContent\(\).{1,150}FAVORITES:return)(.{1,150});(case.{1,200}default:return\(0,\i\.jsx\)\((?<searchComp>\i\..{1,10}),)/,
|
||||
replace: "$1 this.state.resultType === 'Favorites' ? $self.renderSearchBar(this, $<searchComp>) : $2;$3"
|
||||
match: /(renderHeaderContent=function.{1,150}FAVORITES:return)(.{1,150};)(case.{1,200}default:return\(0,\i\.jsx\)\((?<searchComp>\i\.\i))/,
|
||||
replace: "$1 this.state.resultType === \"Favorites\" ? $self.renderSearchBar(this, $<searchComp>) : $2; $3"
|
||||
},
|
||||
{
|
||||
// to persist filtered favorites when component re-renders.
|
||||
|
@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
import { getSettings } from "main/ipcMain";
|
||||
|
||||
app.on("browser-window-created", (_, win) => {
|
||||
win.webContents.on("frame-created", (_, { frame }) => {
|
||||
frame.once("dom-ready", () => {
|
||||
if (frame.url.startsWith("https://open.spotify.com/embed/")) {
|
||||
const settings = getSettings().plugins?.FixSpotifyEmbeds;
|
||||
if (!settings?.enabled) return;
|
||||
|
||||
frame.executeJavaScript(`
|
||||
const original = Audio.prototype.play;
|
||||
Audio.prototype.play = function() {
|
||||
this.volume = ${(settings.volume / 100) || 0.1};
|
||||
return original.apply(this, arguments);
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
@ -19,7 +19,6 @@
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { GuildStore } from "@webpack/common";
|
||||
import { Channel, User } from "discord-types/general";
|
||||
|
||||
export default definePlugin({
|
||||
name: "ForceOwnerCrown",
|
||||
@ -27,22 +26,33 @@ export default definePlugin({
|
||||
authors: [Devs.D3SOX, Devs.Nickyux],
|
||||
patches: [
|
||||
{
|
||||
find: "AVATAR_DECORATION_PADDING:",
|
||||
// This is the logic where it decides whether to render the owner crown or not
|
||||
find: ".renderOwner=",
|
||||
replacement: {
|
||||
match: /,isOwner:(\i),/,
|
||||
replace: ",_isOwner:$1=$self.isGuildOwner(e),"
|
||||
}
|
||||
match: /isOwner;return null!=(\w+)?&&/g,
|
||||
replace: "isOwner;if($self.isGuildOwner(this.props)){$1=true;}return null!=$1&&"
|
||||
}
|
||||
},
|
||||
],
|
||||
isGuildOwner(props: { user: User, channel: Channel, isOwner: boolean, guildId?: string; }) {
|
||||
if (!props?.user?.id) return props.isOwner;
|
||||
if (props.channel?.type === 3 /* GROUP_DM */)
|
||||
return props.isOwner;
|
||||
isGuildOwner(props) {
|
||||
// Check if channel is a Group DM, if so return false
|
||||
if (props?.channel?.type === 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// guild id is in props twice, fallback if the first is undefined
|
||||
const guildId = props.guildId ?? props.channel?.guild_id;
|
||||
const userId = props.user.id;
|
||||
const guildId = props?.guildId ?? props?.channel?.guild_id;
|
||||
const userId = props?.user?.id;
|
||||
|
||||
return GuildStore.getGuild(guildId)?.ownerId === userId;
|
||||
if (guildId && userId) {
|
||||
const guild = GuildStore.getGuild(guildId);
|
||||
if (guild) {
|
||||
return guild.ownerId === userId;
|
||||
}
|
||||
console.error("[ForceOwnerCrown] failed to get guild", { guildId, guild, props });
|
||||
} else {
|
||||
console.error("[ForceOwnerCrown] no guildId or userId", { guildId, userId, props });
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
@ -16,15 +16,16 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { getSettingStoreLazy } from "@api/SettingsStore";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByCodeLazy } from "@webpack";
|
||||
import { StatusSettingsStores } from "@webpack/common";
|
||||
|
||||
import style from "./style.css?managed";
|
||||
|
||||
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame");
|
||||
const Button = findByCodeLazy("Button.Sizes.NONE,disabled:");
|
||||
|
||||
function makeIcon(showCurrentGame?: boolean) {
|
||||
@ -39,7 +40,7 @@ function makeIcon(showCurrentGame?: boolean) {
|
||||
{!showCurrentGame && <>
|
||||
<mask id="gameActivityMask" >
|
||||
<rect fill="white" x="0" y="0" width="24" height="24" />
|
||||
<path fill="black" d="M23.27 4.54 19.46.73 .73 19.46 4.54 23.27 23.27 4.54Z" />
|
||||
<path fill="black" d="M23.27 4.54 19.46.73 .73 19.46 4.54 23.27 23.27 4.54Z"/>
|
||||
</mask>
|
||||
<path fill="var(--status-danger)" d="M23 2.27 21.73 1 1 21.73 2.27 23 23 2.27Z" />
|
||||
</>}
|
||||
@ -49,7 +50,7 @@ function makeIcon(showCurrentGame?: boolean) {
|
||||
}
|
||||
|
||||
function GameActivityToggleButton() {
|
||||
const showCurrentGame = StatusSettingsStores.ShowCurrentGame.useSetting();
|
||||
const showCurrentGame = ShowCurrentGame?.useSetting();
|
||||
|
||||
return (
|
||||
<Button
|
||||
@ -57,7 +58,7 @@ function GameActivityToggleButton() {
|
||||
icon={makeIcon(showCurrentGame)}
|
||||
role="switch"
|
||||
aria-checked={!showCurrentGame}
|
||||
onClick={() => StatusSettingsStores.ShowCurrentGame.updateSetting(old => !old)}
|
||||
onClick={() => ShowCurrentGame?.updateSetting(old => !old)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -66,6 +67,7 @@ export default definePlugin({
|
||||
name: "GameActivityToggle",
|
||||
description: "Adds a button next to the mic and deafen button to toggle game activity.",
|
||||
authors: [Devs.Nuckyz, Devs.RuukuLada],
|
||||
dependencies: ["SettingsStoreAPI"],
|
||||
|
||||
patches: [
|
||||
{
|
||||
|
@ -1,3 +1,3 @@
|
||||
[class*="withTagAsButton"] {
|
||||
min-width: 88px !important;
|
||||
min-width: 88px;
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ export default definePlugin({
|
||||
patches: [{
|
||||
find: ".handleSelectGIF=",
|
||||
replacement: {
|
||||
match: /\.handleSelectGIF=(\i)=>\{/,
|
||||
replace: ".handleSelectGIF=$1=>{if (!this.props.className) return $self.handleSelect($1);"
|
||||
match: /\.handleSelectGIF=function.+?\{/,
|
||||
replace: ".handleSelectGIF=function(gif){return $self.handleSelect(gif);"
|
||||
}
|
||||
}],
|
||||
|
||||
|
@ -18,9 +18,8 @@
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByProps, findByPropsLazy } from "@webpack";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { ContextMenu, FluxDispatcher, Menu } from "@webpack/common";
|
||||
import { Channel, Message } from "discord-types/general";
|
||||
|
||||
@ -51,7 +50,6 @@ const settings = definePluginSettings({
|
||||
}>();
|
||||
|
||||
const MessageActions = findByPropsLazy("sendGreetMessage");
|
||||
const WELCOME_STICKERS = proxyLazy(() => findByProps("WELCOME_STICKERS")?.WELCOME_STICKERS);
|
||||
|
||||
function greet(channel: Channel, message: Message, stickers: string[]) {
|
||||
const options = MessageActions.getSendMessageOptionsForReply({
|
||||
@ -77,7 +75,7 @@ function greet(channel: Channel, message: Message, stickers: string[]) {
|
||||
}
|
||||
|
||||
|
||||
function GreetMenu({ channel, message }: { message: Message, channel: Channel; }) {
|
||||
function GreetMenu({ stickers, channel, message }: { stickers: Sticker[], message: Message, channel: Channel; }) {
|
||||
const s = settings.use(["greetMode", "multiGreetChoices"]);
|
||||
const { greetMode, multiGreetChoices = [] } = s;
|
||||
|
||||
@ -107,7 +105,7 @@ function GreetMenu({ channel, message }: { message: Message, channel: Channel; }
|
||||
<Menu.MenuGroup
|
||||
label="Greet Stickers"
|
||||
>
|
||||
{WELCOME_STICKERS.map(sticker => (
|
||||
{stickers.map(sticker => (
|
||||
<Menu.MenuItem
|
||||
key={sticker.id}
|
||||
id={"greet-" + sticker.id}
|
||||
@ -125,7 +123,7 @@ function GreetMenu({ channel, message }: { message: Message, channel: Channel; }
|
||||
label="Unholy Multi-Greet"
|
||||
id="unholy-multi-greet"
|
||||
>
|
||||
{WELCOME_STICKERS.map(sticker => {
|
||||
{stickers.map(sticker => {
|
||||
const checked = multiGreetChoices.some(s => s === sticker.id);
|
||||
|
||||
return (
|
||||
@ -170,20 +168,21 @@ export default definePlugin({
|
||||
{
|
||||
find: "Messages.WELCOME_CTA_LABEL",
|
||||
replacement: {
|
||||
match: /innerClassName:\i\.welcomeCTAButton,(?<={channel:\i,message:\i}=(\i).{0,400}?)/,
|
||||
replace: "$&onContextMenu:(vcEvent)=>$self.pickSticker(vcEvent, $1),"
|
||||
match: /innerClassName:\i\(\).welcomeCTAButton,(?<=%\i\.length;return (\i)\[\i\].+?)/,
|
||||
replace: "$&onContextMenu:(e)=>$self.pickSticker(e,$1,arguments[0]),"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
pickSticker(
|
||||
event: React.UIEvent,
|
||||
stickers: Sticker[],
|
||||
props: {
|
||||
channel: Channel,
|
||||
message: Message;
|
||||
}
|
||||
) {
|
||||
if (!(props.message as any).deleted)
|
||||
ContextMenu.open(event, () => <GreetMenu {...props} />);
|
||||
ContextMenu.open(event, () => <GreetMenu stickers={stickers} {...props} />);
|
||||
}
|
||||
});
|
||||
|
@ -25,10 +25,10 @@ export default definePlugin({
|
||||
authors: [Devs.botato, Devs.Animal],
|
||||
patches: [
|
||||
{
|
||||
find: "hasFlag:{writable",
|
||||
find: "),{hasFlag:",
|
||||
replacement: {
|
||||
match: /if\((\i)<=(?:1<<30|1073741824)\)return/,
|
||||
replace: "if($1===(1<<20))return false;$&",
|
||||
match: /(if\((.{1,2})<=1<<30\)return)/,
|
||||
replace: "if($2===(1<<20)){return false};$1",
|
||||
},
|
||||
},
|
||||
],
|
||||
|
@ -1,17 +1,28 @@
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
* 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 * as DataStore from "@api/DataStore";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { useForceUpdater } from "@utils/react";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { StatusSettingsStores, Tooltip } from "webpack/common";
|
||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { Tooltip } from "webpack/common";
|
||||
|
||||
const enum ActivitiesTypes {
|
||||
Game,
|
||||
@ -20,153 +31,203 @@ const enum ActivitiesTypes {
|
||||
|
||||
interface IgnoredActivity {
|
||||
id: string;
|
||||
name: string;
|
||||
type: ActivitiesTypes;
|
||||
}
|
||||
|
||||
const RegisteredGamesClasses = findByPropsLazy("overlayToggleIconOff", "overlayToggleIconOn");
|
||||
const TryItOutClasses = findByPropsLazy("tryItOutBadge", "tryItOutBadgeIcon");
|
||||
const BaseShapeRoundClasses = findByPropsLazy("baseShapeRound", "baseShapeRoundLeft", "baseShapeRoundRight");
|
||||
const RunningGameStore = findStoreLazy("RunningGameStore");
|
||||
|
||||
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
|
||||
function ToggleIconOff() {
|
||||
return (
|
||||
<svg
|
||||
className={RegisteredGamesClasses.overlayToggleIconOff}
|
||||
height="24"
|
||||
width="24"
|
||||
viewBox="0 2.2 32 26"
|
||||
aria-hidden={true}
|
||||
role="img"
|
||||
>
|
||||
<g
|
||||
fill="none"
|
||||
fillRule="evenodd"
|
||||
>
|
||||
<path
|
||||
className={RegisteredGamesClasses.fill}
|
||||
fill="currentColor"
|
||||
d="M 16 8 C 7.664063 8 1.25 15.34375 1.25 15.34375 L 0.65625 16 L 1.25 16.65625 C 1.25 16.65625 7.097656 23.324219 14.875 23.9375 C 15.246094 23.984375 15.617188 24 16 24 C 16.382813 24 16.753906 23.984375 17.125 23.9375 C 24.902344 23.324219 30.75 16.65625 30.75 16.65625 L 31.34375 16 L 30.75 15.34375 C 30.75 15.34375 24.335938 8 16 8 Z M 16 10 C 18.203125 10 20.234375 10.601563 22 11.40625 C 22.636719 12.460938 23 13.675781 23 15 C 23 18.613281 20.289063 21.582031 16.78125 21.96875 C 16.761719 21.972656 16.738281 21.964844 16.71875 21.96875 C 16.480469 21.980469 16.242188 22 16 22 C 15.734375 22 15.476563 21.984375 15.21875 21.96875 C 11.710938 21.582031 9 18.613281 9 15 C 9 13.695313 9.351563 12.480469 9.96875 11.4375 L 9.9375 11.4375 C 11.71875 10.617188 13.773438 10 16 10 Z M 16 12 C 14.34375 12 13 13.34375 13 15 C 13 16.65625 14.34375 18 16 18 C 17.65625 18 19 16.65625 19 15 C 19 13.34375 17.65625 12 16 12 Z M 7.25 12.9375 C 7.09375 13.609375 7 14.285156 7 15 C 7 16.753906 7.5 18.394531 8.375 19.78125 C 5.855469 18.324219 4.105469 16.585938 3.53125 16 C 4.011719 15.507813 5.351563 14.203125 7.25 12.9375 Z M 24.75 12.9375 C 26.648438 14.203125 27.988281 15.507813 28.46875 16 C 27.894531 16.585938 26.144531 18.324219 23.625 19.78125 C 24.5 18.394531 25 16.753906 25 15 C 25 14.285156 24.90625 13.601563 24.75 12.9375 Z"
|
||||
/>
|
||||
<rect
|
||||
className={RegisteredGamesClasses.fill}
|
||||
x="3"
|
||||
y="26"
|
||||
width="26"
|
||||
height="2"
|
||||
transform="rotate(-45 2 20)"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ToggleIconOn({ forceWhite }: { forceWhite?: boolean; }) {
|
||||
return (
|
||||
<svg
|
||||
className={RegisteredGamesClasses.overlayToggleIconOn}
|
||||
height="24"
|
||||
width="24"
|
||||
viewBox="0 2.2 32 26"
|
||||
>
|
||||
<path
|
||||
className={forceWhite ? "" : RegisteredGamesClasses.fill}
|
||||
fill={forceWhite ? "var(--white-500)" : ""}
|
||||
d="M 16 8 C 7.664063 8 1.25 15.34375 1.25 15.34375 L 0.65625 16 L 1.25 16.65625 C 1.25 16.65625 7.097656 23.324219 14.875 23.9375 C 15.246094 23.984375 15.617188 24 16 24 C 16.382813 24 16.753906 23.984375 17.125 23.9375 C 24.902344 23.324219 30.75 16.65625 30.75 16.65625 L 31.34375 16 L 30.75 15.34375 C 30.75 15.34375 24.335938 8 16 8 Z M 16 10 C 18.203125 10 20.234375 10.601563 22 11.40625 C 22.636719 12.460938 23 13.675781 23 15 C 23 18.613281 20.289063 21.582031 16.78125 21.96875 C 16.761719 21.972656 16.738281 21.964844 16.71875 21.96875 C 16.480469 21.980469 16.242188 22 16 22 C 15.734375 22 15.476563 21.984375 15.21875 21.96875 C 11.710938 21.582031 9 18.613281 9 15 C 9 13.695313 9.351563 12.480469 9.96875 11.4375 L 9.9375 11.4375 C 11.71875 10.617188 13.773438 10 16 10 Z M 16 12 C 14.34375 12 13 13.34375 13 15 C 13 16.65625 14.34375 18 16 18 C 17.65625 18 19 16.65625 19 15 C 19 13.34375 17.65625 12 16 12 Z M 7.25 12.9375 C 7.09375 13.609375 7 14.285156 7 15 C 7 16.753906 7.5 18.394531 8.375 19.78125 C 5.855469 18.324219 4.105469 16.585938 3.53125 16 C 4.011719 15.507813 5.351563 14.203125 7.25 12.9375 Z M 24.75 12.9375 C 26.648438 14.203125 27.988281 15.507813 28.46875 16 C 27.894531 16.585938 26.144531 18.324219 23.625 19.78125 C 24.5 18.394531 25 16.753906 25 15 C 25 14.285156 24.90625 13.601563 24.75 12.9375 Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function ToggleActivityComponent({ activity, forceWhite, forceLeftMargin }: { activity: IgnoredActivity; forceWhite?: boolean; forceLeftMargin?: boolean; }) {
|
||||
const forceUpdate = useForceUpdater();
|
||||
|
||||
return (
|
||||
<Tooltip text={tooltipText}>
|
||||
{tooltipProps => (
|
||||
<button
|
||||
{...tooltipProps}
|
||||
<Tooltip text="Toggle activity">
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<div
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
className={RegisteredGamesClasses.overlayToggleIcon}
|
||||
role="button"
|
||||
aria-label="Toggle activity"
|
||||
tabIndex={0}
|
||||
style={forceLeftMargin ? { marginLeft: "2px" } : undefined}
|
||||
onClick={e => handleActivityToggle(e, activity, forceUpdate)}
|
||||
style={{ all: "unset", cursor: "pointer", display: "flex", justifyContent: "center", alignItems: "center" }}
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 -960 960 960"
|
||||
>
|
||||
<path fill={fill} d={path} />
|
||||
</svg>
|
||||
</button>
|
||||
{
|
||||
ignoredActivitiesCache.has(activity.id)
|
||||
? <ToggleIconOff />
|
||||
: <ToggleIconOn forceWhite={forceWhite} />
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
const ToggleIconOn = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Disable Activity", "M480-320q75 0 127.5-52.5T660-500q0-75-52.5-127.5T480-680q-75 0-127.5 52.5T300-500q0 75 52.5 127.5T480-320Zm0-72q-45 0-76.5-31.5T372-500q0-45 31.5-76.5T480-608q45 0 76.5 31.5T588-500q0 45-31.5 76.5T480-392Zm0 192q-146 0-266-81.5T40-500q54-137 174-218.5T480-800q146 0 266 81.5T920-500q-54 137-174 218.5T480-200Zm0-300Zm0 220q113 0 207.5-59.5T832-500q-50-101-144.5-160.5T480-720q-113 0-207.5 59.5T128-500q50 101 144.5 160.5T480-280Z", fill);
|
||||
const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(activity, "Enable Activity", "m644-428-58-58q9-47-27-88t-93-32l-58-58q17-8 34.5-12t37.5-4q75 0 127.5 52.5T660-500q0 20-4 37.5T644-428Zm128 126-58-56q38-29 67.5-63.5T832-500q-50-101-143.5-160.5T480-720q-29 0-57 4t-55 12l-62-62q41-17 84-25.5t90-8.5q151 0 269 83.5T920-500q-23 59-60.5 109.5T772-302Zm20 246L624-222q-35 11-70.5 16.5T480-200q-151 0-269-83.5T40-500q21-53 53-98.5t73-81.5L56-792l56-56 736 736-56 56ZM222-624q-29 26-53 57t-41 67q50 101 143.5 160.5T480-280q20 0 39-2.5t39-5.5l-36-38q-11 3-21 4.5t-21 1.5q-75 0-127.5-52.5T300-500q0-11 1.5-21t4.5-21l-84-82Zm319 93Zm-151 75Z", fill);
|
||||
|
||||
function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
||||
if (getIgnoredActivities().some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
|
||||
return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)");
|
||||
function ToggleActivityComponentWithBackground({ activity }: { activity: IgnoredActivity; }) {
|
||||
return (
|
||||
<div
|
||||
className={`${TryItOutClasses.tryItOutBadge} ${BaseShapeRoundClasses.baseShapeRound}`}
|
||||
style={{ padding: "0px 2px", height: 28 }}
|
||||
>
|
||||
<ToggleActivityComponent activity={activity} forceWhite={true} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity, forceUpdateButton: () => void) {
|
||||
function handleActivityToggle(e: React.MouseEvent<HTMLDivElement, MouseEvent>, activity: IgnoredActivity, forceUpdateComponent: () => void) {
|
||||
e.stopPropagation();
|
||||
|
||||
const ignoredActivityIndex = getIgnoredActivities().findIndex(act => act.id === activity.id);
|
||||
if (ignoredActivityIndex === -1) settings.store.ignoredActivities = getIgnoredActivities().concat(activity);
|
||||
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
|
||||
|
||||
// Trigger activities recalculation
|
||||
StatusSettingsStores.ShowCurrentGame.updateSetting(old => old);
|
||||
forceUpdateButton();
|
||||
if (ignoredActivitiesCache.has(activity.id)) ignoredActivitiesCache.delete(activity.id);
|
||||
else ignoredActivitiesCache.set(activity.id, activity);
|
||||
forceUpdateComponent();
|
||||
saveCacheToDatastore();
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({}).withPrivateSettings<{
|
||||
ignoredActivities: IgnoredActivity[];
|
||||
}>();
|
||||
|
||||
function getIgnoredActivities() {
|
||||
return settings.store.ignoredActivities ??= [];
|
||||
async function saveCacheToDatastore() {
|
||||
await DataStore.set("IgnoreActivities_ignoredActivities", ignoredActivitiesCache);
|
||||
}
|
||||
|
||||
let ignoredActivitiesCache = new Map<IgnoredActivity["id"], IgnoredActivity>();
|
||||
|
||||
export default definePlugin({
|
||||
name: "IgnoreActivities",
|
||||
authors: [Devs.Nuckyz],
|
||||
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are ignored from the Registered Games and Activities tabs.",
|
||||
|
||||
settings,
|
||||
|
||||
description: "Ignore certain activities (like games and actual activities) from showing up on your status. You can configure which ones are ignored from the Registered Games and Activities tabs.",
|
||||
patches: [
|
||||
{
|
||||
find: '.displayName="LocalActivityStore"',
|
||||
find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY",
|
||||
replacement: {
|
||||
match: /!(\i)(\)return null;var \i=(\i)\.overlay.+?children:)(\[.{0,70}overlayStatusText.+?\])(?=}\)}\(\))/,
|
||||
replace: (_, platformCheck, restWithoutPlatformCheck, props, children) => "false"
|
||||
+ `${restWithoutPlatformCheck}`
|
||||
+ `(${platformCheck}?${children}:[])`
|
||||
+ `.concat(Vencord.Plugins.plugins.IgnoreActivities.renderToggleGameActivityButton(${props}))`
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".overlayBadge",
|
||||
replacement: [
|
||||
{
|
||||
match: /LISTENING.+?}\),(?<=(\i)\.push.+?)/,
|
||||
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored),`
|
||||
match: /(?<=\(\)\.badgeContainer,children:).{0,50}?name:(\i)\.name.+?null/,
|
||||
replace: (m, props) => `[${m},$self.renderToggleActivityButton(${props})]`
|
||||
},
|
||||
{
|
||||
match: /(?<=\(\)\.badgeContainer,children:).{0,50}?name:(\i\.application)\.name.+?null/,
|
||||
replace: (m, props) => `${m},$self.renderToggleActivityButton(${props})`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY",
|
||||
find: '.displayName="LocalActivityStore"',
|
||||
replacement: {
|
||||
match: /\.Messages\.SETTINGS_GAMES_TOGGLE_OVERLAY.+?}\(\),(?<={overlay:\i,.+?=(\i),.+?)(?=!(\i))/,
|
||||
replace: (m, props, nowPlaying) => `${m}$self.renderToggleGameActivityButton(${props},${nowPlaying}),`
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".activityTitleText,variant",
|
||||
replacement: {
|
||||
match: /(?<=\i\.activityTitleText.+?children:(\i)\.name.*?}\),)/,
|
||||
replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
|
||||
},
|
||||
},
|
||||
{
|
||||
find: ".activityCardDetails,children",
|
||||
replacement: {
|
||||
match: /(?<=\i\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),)/,
|
||||
replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
|
||||
match: /LISTENING.+?\)\);(?<=(\i)\.push.+?)/,
|
||||
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
async start() {
|
||||
const oldIgnoredActivitiesData = await DataStore.get<Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities");
|
||||
|
||||
if (oldIgnoredActivitiesData != null) {
|
||||
settings.store.ignoredActivities = Array.from(oldIgnoredActivitiesData.values())
|
||||
.map(activity => ({ ...activity, name: "Unknown Name" }));
|
||||
|
||||
DataStore.del("IgnoreActivities_ignoredActivities");
|
||||
const ignoredActivitiesData = await DataStore.get<string[] | Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities") ?? new Map<IgnoredActivity["id"], IgnoredActivity>();
|
||||
/** Migrate old data */
|
||||
if (Array.isArray(ignoredActivitiesData)) {
|
||||
for (const id of ignoredActivitiesData) {
|
||||
ignoredActivitiesCache.set(id, { id, type: ActivitiesTypes.Game });
|
||||
}
|
||||
|
||||
if (getIgnoredActivities().length !== 0) {
|
||||
const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[];
|
||||
await saveCacheToDatastore();
|
||||
} else ignoredActivitiesCache = ignoredActivitiesData;
|
||||
|
||||
for (const [index, ignoredActivity] of getIgnoredActivities().entries()) {
|
||||
if (ignoredActivitiesCache.size !== 0) {
|
||||
const gamesSeen: { id?: string; exePath: string; }[] = RunningGameStore.getGamesSeen();
|
||||
|
||||
for (const ignoredActivity of ignoredActivitiesCache.values()) {
|
||||
if (ignoredActivity.type !== ActivitiesTypes.Game) continue;
|
||||
|
||||
if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
|
||||
getIgnoredActivities().splice(index, 1);
|
||||
/** Custom added game which no longer exists */
|
||||
ignoredActivitiesCache.delete(ignoredActivity.id);
|
||||
}
|
||||
}
|
||||
|
||||
await saveCacheToDatastore();
|
||||
}
|
||||
},
|
||||
|
||||
renderToggleGameActivityButton(props: { id?: string; exePath: string; }) {
|
||||
return (
|
||||
<ErrorBoundary noop>
|
||||
<ToggleActivityComponent activity={{ id: props.id ?? props.exePath, type: ActivitiesTypes.Game }} forceLeftMargin={true} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
},
|
||||
|
||||
renderToggleActivityButton(props: { id: string; }) {
|
||||
return (
|
||||
<ErrorBoundary noop>
|
||||
<ToggleActivityComponentWithBackground activity={{ id: props.id, type: ActivitiesTypes.Embedded }} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
},
|
||||
|
||||
isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) {
|
||||
if (props.type === 0 || props.type === 3) {
|
||||
if (props.application_id != null) return !getIgnoredActivities().some(activity => activity.id === props.application_id);
|
||||
if (props.type === 0) {
|
||||
if (props.application_id !== undefined) return !ignoredActivitiesCache.has(props.application_id);
|
||||
else {
|
||||
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
||||
if (exePath) return !getIgnoredActivities().some(activity => activity.id === exePath);
|
||||
if (exePath) return !ignoredActivitiesCache.has(exePath);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
renderToggleGameActivityButton(props: { id?: string; name: string, exePath: string; }, nowPlaying: boolean) {
|
||||
return (
|
||||
<ErrorBoundary noop>
|
||||
<div style={{ marginLeft: 12, zIndex: 0 }}>
|
||||
{ToggleActivityComponent({ id: props.id ?? props.exePath, name: props.name, type: ActivitiesTypes.Game }, nowPlaying)}
|
||||
</div>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
},
|
||||
|
||||
renderToggleActivityButton(props: { id: string; name: string; }) {
|
||||
return (
|
||||
<ErrorBoundary noop>
|
||||
{ToggleActivityComponent({ id: props.id, name: props.name, type: ActivitiesTypes.Embedded })}
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
@ -37,6 +37,13 @@ export const settings = definePluginSettings({
|
||||
default: true,
|
||||
},
|
||||
|
||||
preventCarouselFromClosingOnClick: {
|
||||
type: OptionType.BOOLEAN,
|
||||
// Thanks chat gpt
|
||||
description: "Allow the image modal in the image slideshow thing / carousel to remain open when clicking on the image",
|
||||
default: true,
|
||||
},
|
||||
|
||||
invertScroll: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Invert scroll",
|
||||
@ -94,7 +101,7 @@ const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => {
|
||||
/>
|
||||
<Menu.MenuCheckboxItem
|
||||
id="vc-nearest-neighbour"
|
||||
label="Nearest Neighbour"
|
||||
label="Nearset Neighbour"
|
||||
checked={settings.store.nearestNeighbour}
|
||||
action={() => {
|
||||
settings.store.nearestNeighbour = !settings.store.nearestNeighbour;
|
||||
@ -156,14 +163,10 @@ export default definePlugin({
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "Messages.OPEN_IN_BROWSER",
|
||||
find: '"renderLinkComponent","maxWidth"',
|
||||
replacement: {
|
||||
// there are 2 image thingies. one for carosuel and one for the single image.
|
||||
// so thats why i added global flag.
|
||||
// also idk if this patch is good, should it be more specific?
|
||||
// https://regex101.com/r/xfvNvV/1
|
||||
match: /return.{1,200}\.wrapper.{1,200}src:\i,/g,
|
||||
replace: `$&id: '${ELEMENT_ID}',`
|
||||
match: /(return\(.{1,100}\(\)\.wrapper.{1,100})(src)/,
|
||||
replace: `$1id: '${ELEMENT_ID}',$2`
|
||||
}
|
||||
},
|
||||
|
||||
@ -171,26 +174,27 @@ export default definePlugin({
|
||||
find: "handleImageLoad=",
|
||||
replacement: [
|
||||
{
|
||||
match: /showThumbhashPlaceholder:\i,/,
|
||||
replace: "...$self.makeProps(this),$&"
|
||||
match: /(render=function\(\){.{1,500}limitResponsiveWidth.{1,600})onMouseEnter:/,
|
||||
replace: "$1...$self.makeProps(this),onMouseEnter:"
|
||||
},
|
||||
|
||||
{
|
||||
match: /componentDidMount\(\){/,
|
||||
match: /componentDidMount=function\(\){/,
|
||||
replace: "$&$self.renderMagnifier(this);",
|
||||
},
|
||||
|
||||
{
|
||||
match: /componentWillUnmount\(\){/,
|
||||
match: /componentWillUnmount=function\(\){/,
|
||||
replace: "$&$self.unMountMagnifier();"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
find: ".carouselModal",
|
||||
find: ".carouselModal,",
|
||||
replacement: {
|
||||
match: /(?<=\.carouselModal.{0,100}onClick:)\i,/,
|
||||
replace: "()=>{},"
|
||||
match: /onClick:(\i),/,
|
||||
replace: "onClick:$self.settings.store.preventCarouselFromClosingOnClick ? () => {} : $1,"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -15,17 +15,25 @@
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.vc-imgzoom-nearest-neighbor>img {
|
||||
image-rendering: pixelated;
|
||||
|
||||
/* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */
|
||||
.vc-imgzoom-nearest-neighbor > img {
|
||||
image-rendering: pixelated; /* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */
|
||||
}
|
||||
|
||||
/* make the carousel take up less space so we can click the backdrop and exit out of it */
|
||||
[class*="modalCarouselWrapper_"] {
|
||||
top: 0 !important;
|
||||
[class|="carouselModal"] {
|
||||
height: fit-content;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[class*="carouselModal_"] {
|
||||
height: 0 !important;
|
||||
[class*="modalCarouselWrapper"] {
|
||||
height: fit-content;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
[class|="wrapper"]:has(> #vc-imgzoom-magnify-modal) {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
@ -131,15 +131,15 @@ export default definePlugin({
|
||||
// Indicator
|
||||
find: ".Messages.MESSAGE_EDITED,",
|
||||
replacement: {
|
||||
match: /let\{className:\i,message:\i[^}]*\}=(\i)/,
|
||||
match: /var .,.,.=(.)\.className,.=.\.message,.=.\.children,.=.\.content,.=.\.onUpdate/gm,
|
||||
replace: "try {$1 && $self.INV_REGEX.test($1.message.content) ? $1.content.push($self.indicator()) : null } catch {};$&"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "ChannelTextAreaButtons",
|
||||
find: ".activeCommandOption",
|
||||
replacement: {
|
||||
match: /(\i)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||
replace: "$&,(()=>{try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}})()",
|
||||
match: /(.)\.push.{1,30}disabled:(\i),.{1,20}\},"gift"\)\)/,
|
||||
replace: "$&;try{$2||$1.push($self.chatBarIcon(arguments[0]))}catch{}",
|
||||
}
|
||||
},
|
||||
],
|
||||
|
@ -21,8 +21,8 @@ import { Link } from "@components/Link";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
|
||||
import { filters, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
|
||||
import { FluxDispatcher, Forms } from "@webpack/common";
|
||||
|
||||
interface ActivityAssets {
|
||||
large_image?: string;
|
||||
@ -86,9 +86,15 @@ const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f";
|
||||
const logger = new Logger("LastFMRichPresence");
|
||||
|
||||
const presenceStore = findByPropsLazy("getLocalPresence");
|
||||
const assetManager = mapMangledModuleLazy(
|
||||
"getAssetImage: size must === [number, number] for Twitch",
|
||||
{
|
||||
getAsset: filters.byCode("apply("),
|
||||
}
|
||||
);
|
||||
|
||||
async function getApplicationAsset(key: string): Promise<string> {
|
||||
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
|
||||
return (await assetManager.getAsset(applicationId, [key, undefined]))[0];
|
||||
}
|
||||
|
||||
function setActivity(activity: Activity | null) {
|
||||
|
@ -16,9 +16,8 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
// These are Xor encrypted to prevent you from spoiling yourself when you read the source code.
|
||||
// don't worry about it :P
|
||||
@ -61,35 +60,17 @@ const quotes = [
|
||||
"Wdn`khc'|f*eghl{%"
|
||||
];
|
||||
|
||||
const settings = definePluginSettings({
|
||||
replaceEvents: {
|
||||
description: "Replace Event Quotes too",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "LoadingQuotes",
|
||||
description: "Replace Discords loading quotes",
|
||||
authors: [Devs.Ven, Devs.KraXen72],
|
||||
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: ".LOADING_DID_YOU_KNOW}",
|
||||
replacement: [
|
||||
{
|
||||
match: /\._loadingText=function\(\)\{/,
|
||||
replace: "$&return $self.quote;",
|
||||
find: ".LOADING_DID_YOU_KNOW",
|
||||
replacement: {
|
||||
match: /\._loadingText=.+?random\(.+?;/s,
|
||||
replace: "._loadingText=$self.quote;",
|
||||
},
|
||||
{
|
||||
match: /\._eventLoadingText=function\(\)\{/,
|
||||
replace: "$&return $self.quote;",
|
||||
predicate: () => settings.store.replaceEvents
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
|
@ -105,10 +105,10 @@ export default definePlugin({
|
||||
authors: [Devs.Ven, Devs.Commandtechno],
|
||||
|
||||
patches: [{
|
||||
find: "{isSidebarVisible:",
|
||||
find: ".isSidebarVisible,",
|
||||
replacement: {
|
||||
match: /(?<=let\{className:(\i),.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
|
||||
replace: ":[$1?.startsWith('members')?$self.render():null,$2"
|
||||
match: /(var (\i)=\i\.className.+?children):\[(\i\.useMemo[^}]+"aria-multiselectable")/,
|
||||
replace: "$1:[$2?.startsWith('members')?$self.render():null,$3"
|
||||
}
|
||||
}],
|
||||
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
import { addAccessory } from "@api/MessageAccessories";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { getSettingStoreLazy } from "@api/SettingsStore";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants.js";
|
||||
import { classes } from "@utils/misc";
|
||||
@ -35,7 +36,6 @@ import {
|
||||
PermissionStore,
|
||||
RestAPI,
|
||||
Text,
|
||||
TextAndImagesSettingsStores,
|
||||
UserStore
|
||||
} from "@webpack/common";
|
||||
import { Channel, Guild, Message } from "discord-types/general";
|
||||
@ -46,11 +46,12 @@ const messageCache = new Map<string, {
|
||||
}>();
|
||||
|
||||
const Embed = LazyComponent(() => findByCode(".inlineMediaEmbed"));
|
||||
const AutoModEmbed = LazyComponent(() => findByCode(".withFooter]:", "childrenMessageContent:"));
|
||||
const ChannelMessage = LazyComponent(() => find(m => m.type?.toString()?.includes("renderSimpleAccessories)")));
|
||||
const ChannelMessage = LazyComponent(() => find(m => m.type?.toString()?.includes('["message","compact","className",')));
|
||||
|
||||
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\//;
|
||||
|
||||
@ -318,9 +319,10 @@ function ChannelMessageEmbedAccessory({ message, channel, guildID }: MessageEmbe
|
||||
/>;
|
||||
}
|
||||
|
||||
const compactModeEnabled = getSettingStoreLazy<boolean>("textAndImages", "messageDisplayCompact")!;
|
||||
|
||||
function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
||||
const { message, channel, guildID } = props;
|
||||
const compact = TextAndImagesSettingsStores.MessageDisplayCompact.useSetting();
|
||||
const isDM = guildID === "@me";
|
||||
const images = getImages(message);
|
||||
const { parse } = Parser;
|
||||
@ -336,7 +338,7 @@ function AutomodEmbedAccessory(props: MessageEmbedProps): JSX.Element | null {
|
||||
<span>{isDM ? " - Direct Message" : " - " + GuildStore.getGuild(channel.guild_id)?.name}</span>
|
||||
</Text>
|
||||
}
|
||||
compact={compact}
|
||||
compact={compactModeEnabled.getSetting()}
|
||||
content={
|
||||
<>
|
||||
{message.content || message.attachments.length <= images.length
|
||||
@ -363,7 +365,20 @@ export default definePlugin({
|
||||
name: "MessageLinkEmbeds",
|
||||
description: "Adds a preview to messages that link another message",
|
||||
authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev],
|
||||
dependencies: ["MessageAccessoriesAPI"],
|
||||
dependencies: ["MessageAccessoriesAPI", "SettingsStoreAPI"],
|
||||
patches: [
|
||||
{
|
||||
find: ".embedCard",
|
||||
replacement: [{
|
||||
match: /function (\i)\(\i\){var \i=\i\.message,\i=\i\.channel.{0,200}\.hideTimestamp/,
|
||||
replace: "$self.AutoModEmbed=$1;$&"
|
||||
}]
|
||||
}
|
||||
],
|
||||
|
||||
set AutoModEmbed(e: any) {
|
||||
AutoModEmbed = e;
|
||||
},
|
||||
|
||||
settings,
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
/* Message content highlighting */
|
||||
.messagelogger-deleted [class*="contents"] > :is(div, h1, h2, h3, p) {
|
||||
.messagelogger-deleted [class*="contents-"] > :is(div, h1, h2, h3, p) {
|
||||
color: #f04747 !important;
|
||||
}
|
||||
|
||||
/* Bot "thinking" text highlighting */
|
||||
.messagelogger-deleted [class*="colorStandard"] {
|
||||
.messagelogger-deleted [class*="colorStandard-"] {
|
||||
color: #f04747 !important;
|
||||
}
|
||||
|
||||
|
@ -210,7 +210,7 @@ export default definePlugin({
|
||||
ignoreGuilds.includes(ChannelStore.getChannel(message.channel_id)?.guild_id);
|
||||
},
|
||||
|
||||
// Based on canary 63b8f1b4f2025213c5cf62f0966625bee3d53136
|
||||
// Based on canary 9ab8626bcebceaea6da570b9c586172d02b9c996
|
||||
patches: [
|
||||
{
|
||||
// MessageStore
|
||||
@ -219,7 +219,7 @@ export default definePlugin({
|
||||
replacement: [
|
||||
{
|
||||
// Add deleted=true to all target messages in the MESSAGE_DELETE event
|
||||
match: /MESSAGE_DELETE:function\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?},/,
|
||||
match: /MESSAGE_DELETE:function\((\w)\){var .+?((?:\w{1,2}\.){2})getOrCreate.+?},/,
|
||||
replace:
|
||||
"MESSAGE_DELETE:function($1){" +
|
||||
" var cache = $2getOrCreate($1.channelId);" +
|
||||
@ -229,7 +229,7 @@ export default definePlugin({
|
||||
},
|
||||
{
|
||||
// Add deleted=true to all target messages in the MESSAGE_DELETE_BULK event
|
||||
match: /MESSAGE_DELETE_BULK:function\((\i)\){let.+?((?:\i\.){2})getOrCreate.+?},/,
|
||||
match: /MESSAGE_DELETE_BULK:function\((\w)\){var .+?((?:\w{1,2}\.){2})getOrCreate.+?},/,
|
||||
replace:
|
||||
"MESSAGE_DELETE_BULK:function($1){" +
|
||||
" var cache = $2getOrCreate($1.channelId);" +
|
||||
@ -239,7 +239,7 @@ export default definePlugin({
|
||||
},
|
||||
{
|
||||
// Add current cached content + new edit time to cached message's editHistory
|
||||
match: /(MESSAGE_UPDATE:function\((\i)\).+?)\.update\((\i)/,
|
||||
match: /(MESSAGE_UPDATE:function\((\w)\).+?)\.update\((\w)/,
|
||||
replace: "$1" +
|
||||
".update($3,m =>" +
|
||||
" (($2.message.flags & 64) === 64 || $self.shouldIgnore($2.message)) ? m :" +
|
||||
@ -251,8 +251,8 @@ export default definePlugin({
|
||||
},
|
||||
{
|
||||
// fix up key (edit last message) attempting to edit a deleted message
|
||||
match: /(?<=getLastEditableMessage\(\i\)\{.{0,200}\.find\((\i)=>)/,
|
||||
replace: "!$1.deleted &&"
|
||||
match: /(?<=getLastEditableMessage=.{0,200}\.find\(\(function\((\i)\)\{)return/,
|
||||
replace: "return !$1.deleted &&"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -260,13 +260,13 @@ export default definePlugin({
|
||||
{
|
||||
// Message domain model
|
||||
// Module 451
|
||||
find: "}addReaction(",
|
||||
find: "isFirstMessageInForumPost=function",
|
||||
replacement: [
|
||||
{
|
||||
match: /this\.customRenderedContent=(\i)\.customRenderedContent,/,
|
||||
replace: "this.customRenderedContent = $1.customRenderedContent," +
|
||||
"this.deleted = $1.deleted || false," +
|
||||
"this.editHistory = $1.editHistory || [],"
|
||||
match: /(\w)\.customRenderedContent=(\w)\.customRenderedContent;/,
|
||||
replace: "$1.customRenderedContent = $2.customRenderedContent;" +
|
||||
"$1.deleted = $2.deleted || false;" +
|
||||
"$1.editHistory = $2.editHistory || [];"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -283,7 +283,7 @@ export default definePlugin({
|
||||
// },
|
||||
{
|
||||
// Pass through editHistory & deleted & original attachments to the "edited message" transformer
|
||||
match: /interactionData:(\i)\.interactionData/,
|
||||
match: /interactionData:(\w)\.interactionData/,
|
||||
replace:
|
||||
"interactionData:$1.interactionData," +
|
||||
"deleted:$1.deleted," +
|
||||
@ -299,7 +299,7 @@ export default definePlugin({
|
||||
{
|
||||
// Construct new edited message and add editHistory & deleted (ref above)
|
||||
// Pass in custom data to attachment parser to mark attachments deleted as well
|
||||
match: /attachments:(\i)\((\i)\)/,
|
||||
match: /attachments:(\w{1,2})\((\w)\)/,
|
||||
replace:
|
||||
"attachments: $1((() => {" +
|
||||
" let old = arguments[1]?.attachments;" +
|
||||
@ -315,7 +315,7 @@ export default definePlugin({
|
||||
},
|
||||
{
|
||||
// Preserve deleted attribute on attachments
|
||||
match: /(\((\i)\){return null==\2\.attachments.+?)spoiler:/,
|
||||
match: /(\((\w)\){return null==\2\.attachments.+?)spoiler:/,
|
||||
replace:
|
||||
"$1deleted: arguments[0]?.deleted," +
|
||||
"spoiler:"
|
||||
@ -326,15 +326,15 @@ export default definePlugin({
|
||||
{
|
||||
// Attachment renderer
|
||||
// Module 96063
|
||||
find: ".removeAttachmentHoverButton",
|
||||
find: "().removeAttachmentHoverButton",
|
||||
replacement: [
|
||||
{
|
||||
match: /(className:\i,attachment:\i),/,
|
||||
replace: "$1,attachment: {deleted},"
|
||||
match: /((\w)\.className,\w=\2\.attachment),/,
|
||||
replace: "$1,deleted=$2.attachment?.deleted,"
|
||||
},
|
||||
{
|
||||
match: /\[\i\.obscured\]:.+?,/,
|
||||
replace: "$& 'messagelogger-deleted-attachment': deleted,"
|
||||
match: /\["className","attachment".+?className:/,
|
||||
replace: "$& (deleted ? 'messagelogger-deleted-attachment ' : '') +"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -360,7 +360,7 @@ export default definePlugin({
|
||||
{
|
||||
// Render editHistory in the deepest div for message content
|
||||
match: /(\)\("div",\{id:.+?children:\[)/,
|
||||
replace: "$1 (arguments[0].message.editHistory?.length > 0 ? arguments[0].message.editHistory.map(edit => $self.renderEdit(edit)) : null), "
|
||||
replace: "$1 (arguments[0].message.editHistory.length > 0 ? arguments[0].message.editHistory.map(edit => $self.renderEdit(edit)) : null), "
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -371,11 +371,11 @@ export default definePlugin({
|
||||
find: "displayName=\"ReferencedMessageStore\"",
|
||||
replacement: [
|
||||
{
|
||||
match: /MESSAGE_DELETE:function\((\i)\).+?},/,
|
||||
match: /MESSAGE_DELETE:function\((\w)\).+?},/,
|
||||
replace: "MESSAGE_DELETE:function($1){},"
|
||||
},
|
||||
{
|
||||
match: /MESSAGE_DELETE_BULK:function\((\i)\).+?},/,
|
||||
match: /MESSAGE_DELETE_BULK:function\((\w)\).+?},/,
|
||||
replace: "MESSAGE_DELETE_BULK:function($1){},"
|
||||
}
|
||||
]
|
||||
@ -384,7 +384,7 @@ export default definePlugin({
|
||||
{
|
||||
// Message context base menu
|
||||
// Module 600300
|
||||
find: "useMessageMenu:",
|
||||
find: "id:\"remove-reactions\"",
|
||||
replacement: [
|
||||
{
|
||||
// Remove the first section if message is deleted
|
||||
|
@ -22,7 +22,7 @@ import { Devs } from "@utils/constants";
|
||||
import { Margins } from "@utils/margins";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findLazy } from "@webpack";
|
||||
import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip, useState } from "@webpack/common";
|
||||
import { Card, ChannelStore, Forms, GuildStore, Switch, TextInput, Tooltip, useState } from "@webpack/common";
|
||||
import { RC } from "@webpack/types";
|
||||
import { Channel, Message, User } from "discord-types/general";
|
||||
|
||||
@ -53,11 +53,14 @@ interface TagSettings {
|
||||
[k: string]: TagSetting;
|
||||
}
|
||||
|
||||
const CLYDE_ID = "1081004946872352958";
|
||||
|
||||
// PermissionStore.computePermissions is not the same function and doesn't work here
|
||||
const PermissionUtil = findByPropsLazy("computePermissions", "canEveryoneRole") as {
|
||||
computePermissions({ ...args }): bigint;
|
||||
};
|
||||
|
||||
const Permissions = findByPropsLazy("SEND_MESSAGES", "VIEW_CREATOR_MONETIZATION_ANALYTICS") as Record<PermissionName, bigint>;
|
||||
const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; };
|
||||
|
||||
const isWebhook = (message: Message, user: User) => !!message?.webhookId && user.isNonUserBot();
|
||||
@ -187,14 +190,17 @@ export default definePlugin({
|
||||
patches: [
|
||||
// add tags to the tag list
|
||||
{
|
||||
find: "BotTagTypes:",
|
||||
replacement: {
|
||||
match: /\((\i)=\{\}\)\)\[(\i)\.BOT/,
|
||||
replace: "($1=$self.getTagTypes()))[$2.BOT"
|
||||
find: '.BOT=0]="BOT"',
|
||||
replacement: [
|
||||
// add tags to the exported tags list (Tag.Types)
|
||||
{
|
||||
match: /(\i)\[.\.BOT=0\]="BOT";/,
|
||||
replace: "$&$1=$self.addTagVariants($1);"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP,",
|
||||
find: ".DISCORD_SYSTEM_MESSAGE_BOT_TAG_TOOLTIP;",
|
||||
replacement: [
|
||||
// make the tag show the right text
|
||||
{
|
||||
@ -209,25 +215,25 @@ export default definePlugin({
|
||||
},
|
||||
// add HTML data attributes (for easier theming)
|
||||
{
|
||||
match: /.botText,children:(\i)}\)]/,
|
||||
replace: "$&,'data-tag':$1.toLowerCase()"
|
||||
match: /children:\[(?=\i,\(0,\i\.jsx\)\("span",{className:\i\(\)\.botText,children:(\i)}\)\])/,
|
||||
replace: "'data-tag':$1.toLowerCase(),children:["
|
||||
}
|
||||
],
|
||||
},
|
||||
// in messages
|
||||
{
|
||||
find: "renderSystemTag:",
|
||||
find: ".Types.ORIGINAL_POSTER",
|
||||
replacement: {
|
||||
match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)/,
|
||||
replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null"
|
||||
match: /return null==(\i)\?null:\(0,/,
|
||||
replace: "$1=$self.getTag({...arguments[0],origType:$1,location:'chat'});$&"
|
||||
}
|
||||
},
|
||||
// in the member list
|
||||
{
|
||||
find: ".Messages.GUILD_OWNER,",
|
||||
find: ".renderBot=function(){",
|
||||
replacement: {
|
||||
match: /(?<type>\i)=\(null==.{0,100}\.BOT;return null!=(?<user>\i)&&\i\.bot/,
|
||||
replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }); return typeof $<type> === 'number'"
|
||||
match: /\.BOT;return null!=(\i)&&.{0,10}\?(.{0,50})\.botTag,type:\i/,
|
||||
replace: ".BOT;var type=$self.getTag({...this.props,origType:$1.bot?0:null,location:'not-chat'});return type!==null?$2.botTag,type"
|
||||
}
|
||||
},
|
||||
// pass channel id down props to be used in profiles
|
||||
@ -247,18 +253,11 @@ export default definePlugin({
|
||||
},
|
||||
// in profiles
|
||||
{
|
||||
find: ",overrideDiscriminator:",
|
||||
replacement: [
|
||||
{
|
||||
// prevent channel id from getting ghosted
|
||||
// it's either this or extremely long lookbehind
|
||||
match: /user:\i,nick:\i,/,
|
||||
replace: "$&moreTags_channelId,"
|
||||
}, {
|
||||
find: ",botType:",
|
||||
replacement: {
|
||||
match: /,botType:(\i\((\i)\)),/g,
|
||||
replace: ",botType:$self.getTag({user:$2,channelId:moreTags_channelId,origType:$1,location:'not-chat'}),"
|
||||
replace: ",botType:$self.getTag({user:$2,channelId:arguments[0].moreTags_channelId,origType:$1,location:'not-chat'}),"
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
|
||||
@ -298,25 +297,24 @@ export default definePlugin({
|
||||
if (!guild) return [];
|
||||
|
||||
const permissions = PermissionUtil.computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
|
||||
return Object.entries(PermissionsBits)
|
||||
return Object.entries(Permissions)
|
||||
.map(([perm, permInt]) =>
|
||||
permissions & permInt ? perm : ""
|
||||
)
|
||||
.filter(Boolean);
|
||||
},
|
||||
|
||||
getTagTypes() {
|
||||
const obj = {};
|
||||
addTagVariants(tagConstant) {
|
||||
let i = 100;
|
||||
tags.forEach(({ name }) => {
|
||||
obj[name] = ++i;
|
||||
obj[i] = name;
|
||||
obj[`${name}-BOT`] = ++i;
|
||||
obj[i] = `${name}-BOT`;
|
||||
obj[`${name}-OP`] = ++i;
|
||||
obj[i] = `${name}-OP`;
|
||||
tagConstant[name] = ++i;
|
||||
tagConstant[i] = name;
|
||||
tagConstant[`${name}-BOT`] = ++i;
|
||||
tagConstant[i] = `${name}-BOT`;
|
||||
tagConstant[`${name}-OP`] = ++i;
|
||||
tagConstant[i] = `${name}-OP`;
|
||||
});
|
||||
return obj;
|
||||
return tagConstant;
|
||||
},
|
||||
|
||||
isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]),
|
||||
@ -343,17 +341,15 @@ export default definePlugin({
|
||||
message, user, channelId, origType, location, channel
|
||||
}: {
|
||||
message?: Message,
|
||||
user: User & { isClyde(): boolean; },
|
||||
user: User,
|
||||
channel?: Channel & { isForumPost(): boolean; },
|
||||
channelId?: string;
|
||||
origType?: number;
|
||||
location: "chat" | "not-chat";
|
||||
}): number | null {
|
||||
if (!user)
|
||||
return null;
|
||||
if (location === "chat" && user.id === "1")
|
||||
return Tag.Types.OFFICIAL;
|
||||
if (user.isClyde())
|
||||
if (user.id === CLYDE_ID)
|
||||
return Tag.Types.AI;
|
||||
|
||||
let type = typeof origType === "number" ? origType : null;
|
||||
@ -381,6 +377,7 @@ export default definePlugin({
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
});
|
||||
|
@ -45,16 +45,16 @@ export default definePlugin({
|
||||
authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince],
|
||||
patches: [
|
||||
{
|
||||
find: ",acceptInvite(",
|
||||
find: ",acceptInvite:function",
|
||||
replacement: {
|
||||
match: /INVITE_ACCEPT_SUCCESS.+?,(\i)=null!==.+?;/,
|
||||
match: /INVITE_ACCEPT_SUCCESS.+?;(\i)=null.+?;/,
|
||||
replace: (m, guildId) => `${m}$self.handleMute(${guildId});`
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "{joinGuild:",
|
||||
find: "{joinGuild:function",
|
||||
replacement: {
|
||||
match: /guildId:(\i),lurker:(\i).{0,20}}\)\);/,
|
||||
match: /guildId:(\w+),lurker:(\w+).{0,20}\)}\)\);/,
|
||||
replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.handleMute(${guildId});`
|
||||
}
|
||||
}
|
||||
|
@ -47,17 +47,16 @@ export default definePlugin({
|
||||
patches: [
|
||||
{
|
||||
find: ".Messages.USER_PROFILE_MODAL", // Note: the module is lazy-loaded
|
||||
replacement: {
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=\.MUTUAL_GUILDS\}\),)(?=(\i\.bot).{0,20}(\(0,\i\.jsx\)\(.{0,100}id:))/,
|
||||
replace: '($1||arguments[0].isCurrentUser)?null:$2"MUTUAL_GDMS",children:"Mutual Groups"}),'
|
||||
}
|
||||
replace: '$1?null:$2"MUTUAL_GDMS",children:"Mutual Groups"}),'
|
||||
},
|
||||
{
|
||||
find: ".UserProfileSections.USER_INFO_CONNECTIONS:",
|
||||
replacement: {
|
||||
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
|
||||
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs($1,$2);"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -29,11 +29,11 @@ export default definePlugin({
|
||||
authors: [Devs.rushii, Devs.Samu],
|
||||
patches: [
|
||||
{
|
||||
find: "Messages.BLOCKED_MESSAGES_HIDE",
|
||||
find: 'safety_prompt:"DMSpamExperiment",response:"show_redacted_messages"',
|
||||
replacement: [
|
||||
{
|
||||
match: /let\{[^}]*collapsedReason[^}]*\}/,
|
||||
replace: "return null;$&"
|
||||
match: /\.collapsedReason;return/,
|
||||
replace: ".collapsedReason;return null;return;"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -26,8 +26,8 @@ export default definePlugin({
|
||||
patches: [{
|
||||
find: "setDevtoolsCallbacks",
|
||||
replacement: {
|
||||
match: /if\(null!=\i&&"0.0.0"===\i\.remoteApp\.getVersion\(\)\)/,
|
||||
replace: "if(true)"
|
||||
match: /if\(.{0,10}\|\|"0.0.0"!==.{0,2}\.remoteApp\.getVersion\(\)\)/,
|
||||
replace: "if(false)"
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
import style from "./styles.css?managed";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
inlineVideo: {
|
||||
description: "Play videos without carousel modal",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
restartNeeded: true
|
||||
},
|
||||
mediaLayoutType: {
|
||||
description: "Choose media layout type",
|
||||
type: OptionType.SELECT,
|
||||
restartNeeded: true,
|
||||
options: [
|
||||
{ label: "STATIC, render loading image but image isn't resposive, no problem unless discord window width is too small", value: "STATIC", default: true },
|
||||
{ label: "RESPONSIVE, image is responsive but not render loading image, cause messages shift when loaded", value: "RESPONSIVE" },
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "NoMosaic",
|
||||
authors: [Devs.AutumnVN],
|
||||
description: "Removes Discord new image mosaic",
|
||||
tags: ["image", "mosaic", "media"],
|
||||
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: ".oneByTwoLayoutThreeGrid",
|
||||
replacement: [{
|
||||
match: /mediaLayoutType:\i\.\i\.MOSAIC/,
|
||||
replace: "mediaLayoutType:$self.mediaLayoutType()",
|
||||
},
|
||||
{
|
||||
match: /null!==\(\i=\i\.get\(\i\)\)&&void 0!==\i\?\i:"INVALID"/,
|
||||
replace: '"INVALID"',
|
||||
}]
|
||||
},
|
||||
{
|
||||
find: "renderAttachments(",
|
||||
predicate: () => settings.store.inlineVideo,
|
||||
replacement: {
|
||||
match: /url:(\i)\.url\}\);return /,
|
||||
replace: "$&$1.content_type?.startsWith('image/')&&"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "Messages.REMOVE_ATTACHMENT_TOOLTIP_TEXT",
|
||||
replacement: {
|
||||
match: /\i===\i\.\i\.MOSAIC/,
|
||||
replace: "true"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
mediaLayoutType() {
|
||||
return settings.store.mediaLayoutType;
|
||||
},
|
||||
|
||||
start() {
|
||||
enableStyle(style);
|
||||
},
|
||||
|
||||
stop() {
|
||||
disableStyle(style);
|
||||
}
|
||||
});
|
@ -1,3 +0,0 @@
|
||||
[class^="nonMediaAttachmentsContainer_"] [class*="messageAttachment_"] {
|
||||
position: relative;
|
||||
}
|
@ -55,18 +55,18 @@ export default definePlugin({
|
||||
// or by searching for "showProgressBadge:"
|
||||
patches: [
|
||||
{
|
||||
find: "getPendingCount(){",
|
||||
find: ".getPendingCount=",
|
||||
predicate: () => settings.store.hideFriendRequestsCount,
|
||||
replacement: {
|
||||
match: /(?<=getPendingCount\(\)\{)/,
|
||||
match: /(?<=\.getPendingCount=function\(\)\{)/,
|
||||
replace: "return 0;"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "getMessageRequestsCount(){",
|
||||
find: ".getMessageRequestsCount=",
|
||||
predicate: () => settings.store.hideMessageRequestsCount,
|
||||
replacement: {
|
||||
match: /(?<=getMessageRequestsCount\(\)\{)/,
|
||||
match: /(?<=\.getMessageRequestsCount=function\(\)\{)/,
|
||||
replace: "return 0;"
|
||||
}
|
||||
},
|
||||
@ -84,10 +84,8 @@ export default definePlugin({
|
||||
find: "showProgressBadge:",
|
||||
predicate: () => settings.store.hidePremiumOffersCount,
|
||||
replacement: {
|
||||
// The two groups inside the first group grab the minified names of the variables,
|
||||
// they are then referenced later to find unviewedTrialCount + unviewedDiscountCount.
|
||||
match: /(?<=\{unviewedTrialCount:(\i),unviewedDiscountCount:(\i)\}.{0,200}\i=)\1\+\2/,
|
||||
replace: "0"
|
||||
match: /\(function\(\){return \i\.\i\.getUnacknowledgedOffers\(\i\)\.length}\)/,
|
||||
replace: "(function(){return 0})"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -30,23 +30,23 @@ export default definePlugin({
|
||||
// = isPremiumAtLeast(user.premiumType, TIER_2)
|
||||
match: /=(?=\i\.\i\.isPremiumAtLeast\(null==(\i))/,
|
||||
// = user.banner && isPremiumAtLeast(user.premiumType, TIER_2)
|
||||
replace: "=(arguments[0]?.bannerSrc||$1?.banner)&&"
|
||||
replace: "=$1?.banner&&"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".avatarPositionPremiumNoBanner,default:",
|
||||
find: "().avatarPositionPremiumNoBanner,default:",
|
||||
replacement: {
|
||||
// premiumUserWithoutBanner: foo().avatarPositionPremiumNoBanner, default: foo().avatarPositionNormal
|
||||
match: /\.avatarPositionPremiumNoBanner(?=,default:\i\.(\i))/,
|
||||
match: /\.avatarPositionPremiumNoBanner(?=,default:\i\(\)\.(\i))/,
|
||||
// premiumUserWithoutBanner: foo().avatarPositionNormal...
|
||||
replace: ".$1"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "hasThemeColors(){",
|
||||
find: ".hasThemeColors=function(){",
|
||||
replacement: {
|
||||
match: /get canUsePremiumProfileCustomization\(\){return /,
|
||||
replace: "$&false &&"
|
||||
match: /(?<=key:"canUsePremiumProfileCustomization",get:function\(\){return)/,
|
||||
replace: " false;"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -25,11 +25,14 @@ export default definePlugin({
|
||||
authors: [Devs.Nuckyz],
|
||||
patches: [
|
||||
{
|
||||
find: '"ApplicationStreamPreviewUploadManager"',
|
||||
replacement: {
|
||||
match: /await \i\.\i\.(makeChunkedRequest\(|post\(\{url:)\i\.\i\.STREAM_PREVIEW.+?\}\)/g,
|
||||
replace: "0"
|
||||
}
|
||||
find: '("ApplicationStreamPreviewUploadManager")',
|
||||
replacement: [
|
||||
String.raw`\i\.\i\.makeChunkedRequest\(`,
|
||||
String.raw`\i\.\i\.post\({url:`
|
||||
].map(match => ({
|
||||
match: new RegExp(String.raw`(?=return\[(\d),${match}\i\.\i\.STREAM_PREVIEW.+?}\)\];)`),
|
||||
replace: (_, code) => `return[${code},Promise.resolve({body:"",status:204})];`
|
||||
}))
|
||||
}
|
||||
]
|
||||
});
|
||||
|
@ -25,15 +25,15 @@ export default definePlugin({
|
||||
authors: [Devs.rushii],
|
||||
patches: [
|
||||
{
|
||||
find: ",setSystemTrayApplications",
|
||||
find: "setSystemTrayApplications:function",
|
||||
replacement: [
|
||||
{
|
||||
match: /setBadge\(\i\).+?},/,
|
||||
replace: "setBadge(){},"
|
||||
match: /setBadge:function.+?},/,
|
||||
replace: "setBadge:function(){},"
|
||||
},
|
||||
{
|
||||
match: /setSystemTrayIcon\(\i\).+?},/,
|
||||
replace: "setSystemTrayIcon(){},"
|
||||
match: /setSystemTrayIcon:function.+?},/,
|
||||
replace: "setSystemTrayIcon:function(){},"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1,21 +0,0 @@
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "NoTypingAnimation",
|
||||
authors: [Devs.AutumnVN],
|
||||
description: "Disables the CPU-intensive typing dots animation",
|
||||
patches: [{
|
||||
find: "dotCycle",
|
||||
replacement: {
|
||||
match: /document.hasFocus\(\)/,
|
||||
replace: "false"
|
||||
}
|
||||
}]
|
||||
});
|
@ -33,7 +33,7 @@ export default definePlugin({
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "renderJumpButton()",
|
||||
find: "renderJumpButton=function()",
|
||||
replacement: {
|
||||
match: /if\(.{1,10}\)(.{1,10}\.show\({.{1,50}UNBLOCK_TO_JUMP_TITLE)/,
|
||||
replace: "if(false)$1"
|
||||
|
@ -4,65 +4,35 @@
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { ChannelStore, ReadStateStore, UserStore } from "@webpack/common";
|
||||
import { MessageJSON } from "discord-types/general";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ChannelStore, ReadStateStore } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
const enum ChannelType {
|
||||
DM = 1,
|
||||
GROUP_DM = 3
|
||||
}
|
||||
|
||||
const settings = definePluginSettings({
|
||||
channelToAffect: {
|
||||
type: OptionType.SELECT,
|
||||
description: "Select the type of DM for the plugin to affect",
|
||||
options: [
|
||||
{ label: "Both", value: "both_dms", default: true },
|
||||
{ label: "User DMs", value: "user_dm" },
|
||||
{ label: "Group DMs", value: "group_dm" },
|
||||
]
|
||||
},
|
||||
allowMentions: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Receive audio pings for @mentions",
|
||||
default: false,
|
||||
},
|
||||
allowEveryone: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Receive audio pings for @everyone and @here in group DMs",
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "OnePingPerDM",
|
||||
description: "If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit",
|
||||
authors: [Devs.ProffDea],
|
||||
settings,
|
||||
patches: [{
|
||||
find: ".getDesktopType()===",
|
||||
replacement: [{
|
||||
match: /(\i\.\i\.getDesktopType\(\)===\i\.\i\.NEVER)\)/,
|
||||
replace: "$&if(!$self.isPrivateChannelRead(arguments[0]?.message))return;else "
|
||||
match: /if\((\i\.\i\.getDesktopType\(\)===\i\.\i\.NEVER)\){/,
|
||||
replace: "if($1){if(!$self.isPrivateChannelRead(arguments[0]?.message))return;"
|
||||
},
|
||||
{
|
||||
match: /sound:(\i\?\i:void 0,volume:\i,onClick)/,
|
||||
match: /sound:(\i\?\i:void 0,volume:\i,onClick:)/,
|
||||
replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1"
|
||||
}]
|
||||
}],
|
||||
isPrivateChannelRead(message: MessageJSON) {
|
||||
isPrivateChannelRead(message: Message) {
|
||||
const channelType = ChannelStore.getChannel(message.channel_id)?.type;
|
||||
if (
|
||||
(channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) ||
|
||||
(channelType === ChannelType.DM && settings.store.channelToAffect === "group_dm") ||
|
||||
(channelType === ChannelType.GROUP_DM && settings.store.channelToAffect === "user_dm") ||
|
||||
(settings.store.allowMentions && message.mentions.some(m => m.id === UserStore.getCurrentUser().id)) ||
|
||||
(settings.store.allowEveryone && message.mention_everyone)
|
||||
) {
|
||||
return true;
|
||||
if (channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) {
|
||||
return false;
|
||||
}
|
||||
return ReadStateStore.getOldestUnreadMessageId(message.channel_id) === message.id;
|
||||
},
|
||||
|
@ -18,12 +18,12 @@
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType, PluginNative } from "@utils/types";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { showToast, Toasts } from "@webpack/common";
|
||||
import type { MouseEvent } from "react";
|
||||
|
||||
const ShortUrlMatcher = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
|
||||
const SpotifyMatcher = /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user|episode)\/(.+)(?:\?.+?)?$/;
|
||||
const SpotifyMatcher = /^https:\/\/open\.spotify\.com\/(track|album|artist|playlist|user)\/(.+)(?:\?.+?)?$/;
|
||||
const SteamMatcher = /^https:\/\/(steamcommunity\.com|(?:help|store)\.steampowered\.com)\/.+$/;
|
||||
const EpicMatcher = /^https:\/\/store\.epicgames\.com\/(.+)$/;
|
||||
|
||||
@ -45,8 +45,6 @@ const settings = definePluginSettings({
|
||||
}
|
||||
});
|
||||
|
||||
const Native = VencordNative.pluginHelpers.OpenInApp as PluginNative<typeof import("./native")>;
|
||||
|
||||
export default definePlugin({
|
||||
name: "OpenInApp",
|
||||
description: "Open Spotify, Steam and Epic Games URLs in their respective apps instead of your browser",
|
||||
@ -55,10 +53,10 @@ export default definePlugin({
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: "trackAnnouncementMessageLinkClicked({",
|
||||
find: '"MaskedLinkStore"',
|
||||
replacement: {
|
||||
match: /(?<=handleClick:function\(\)\{return (\i)\}.+?)function \1\(.+?\)\{/,
|
||||
replace: "async $& if(await $self.handleLink(...arguments)) return;"
|
||||
match: /return ((\i)\.apply\(this,arguments\))(?=\}function \i.{0,250}\.trusted)/,
|
||||
replace: "return $self.handleLink(...arguments).then(handled => handled||$1)"
|
||||
}
|
||||
},
|
||||
// Make Spotify profile activity links open in app on web
|
||||
@ -73,7 +71,7 @@ export default definePlugin({
|
||||
{
|
||||
find: ".CONNECTED_ACCOUNT_VIEWED,",
|
||||
replacement: {
|
||||
match: /(?<=href:\i,onClick:\i=>\{)(?=.{0,10}\i=(\i)\.type,.{0,100}CONNECTED_ACCOUNT_VIEWED)/,
|
||||
match: /(?<=href:\i,onClick:function\(\i\)\{)(?=\i=(\i)\.type,.{0,50}CONNECTED_ACCOUNT_VIEWED)/,
|
||||
replace: "$self.handleAccountView(arguments[0],$1.type,$1.id);"
|
||||
}
|
||||
}
|
||||
@ -86,7 +84,7 @@ export default definePlugin({
|
||||
if (!IS_WEB && ShortUrlMatcher.test(url)) {
|
||||
event?.preventDefault();
|
||||
// CORS jumpscare
|
||||
url = await Native.resolveRedirect(url);
|
||||
url = await VencordNative.pluginHelpers.OpenInApp.resolveRedirect(url);
|
||||
}
|
||||
|
||||
spotify: {
|
||||
|
@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { IpcMainInvokeEvent } from "electron";
|
||||
import { request } from "https";
|
||||
|
||||
// These links don't support CORS, so this has to be native
|
||||
const validRedirectUrls = /^https:\/\/(spotify\.link|s\.team)\/.+$/;
|
||||
|
||||
function getRedirect(url: string) {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const req = request(new URL(url), { method: "HEAD" }, res => {
|
||||
resolve(
|
||||
res.headers.location
|
||||
? getRedirect(res.headers.location)
|
||||
: url
|
||||
);
|
||||
});
|
||||
req.on("error", reject);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
export async function resolveRedirect(_: IpcMainInvokeEvent, url: string) {
|
||||
if (!validRedirectUrls.test(url)) return url;
|
||||
|
||||
return getRedirect(url);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user