Compare commits
74 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
544edce9f9 | ||
|
e4485165d0 | ||
|
fada76ec81 | ||
|
f659c46031 | ||
|
ae1dc4eab0 | ||
|
fe60a72b80 | ||
|
5a0b2ee3f5 | ||
|
6c1b8b0d8a | ||
|
b2a1410a96 | ||
|
c25c95eecd | ||
|
d94418f42f | ||
|
da1a8cdd67 | ||
|
5d7ede34d8 | ||
|
cd61354998 | ||
|
a452945ac8 | ||
|
b577660800 | ||
|
4f57c7eded | ||
|
e3e5da10a9 | ||
|
998ce72f3b | ||
|
188d12d1a3 | ||
|
a522eab40d | ||
|
c2721f158f | ||
|
61cd7b4d99 | ||
|
926af0d1cd | ||
|
dcaf4aec97 | ||
|
5a97adb435 | ||
|
925d709335 | ||
|
1a36dbbc9b | ||
|
b59db2f8c2 | ||
|
d81302f64c | ||
|
390987e4a9 | ||
|
377cf60055 | ||
|
34ac718705 | ||
|
e4659ed7c3 | ||
|
c33d59b45d | ||
|
ac1b67ccbd | ||
|
c0f2c97458 | ||
|
664dd0a992 | ||
|
f66e35b658 | ||
|
df214e1e93 | ||
|
47a39a062e | ||
|
9e63da6d78 | ||
|
79295683ee | ||
|
5eb9dd04df | ||
|
03b5dc9c27 | ||
|
726a1b5d96 | ||
|
581fe252a4 | ||
|
30b2e88e77 | ||
|
1f38a8eeab | ||
|
6db9721c06 | ||
|
9891791fa7 | ||
|
8dd5eeead2 | ||
|
abf8667a5d | ||
|
e33ac900bc | ||
|
8a026060c7 | ||
|
c8b77bb187 | ||
|
88b06191b9 | ||
|
62277770a8 | ||
|
4facc3cad7 | ||
|
837d1fc083 | ||
|
608a67c9ae | ||
|
f32d25b641 | ||
|
4c7a2ba340 | ||
|
da7f0cfff6 | ||
|
ae6584da7c | ||
|
c6b1b9463c | ||
|
376aaf39ce | ||
|
5454a41243 | ||
|
044f64e446 | ||
|
0b7fca864a | ||
|
30ac256070 | ||
|
d0e2a32471 | ||
|
ec026ca34c | ||
|
4baaa9bd91 |
30
.github/ISSUE_TEMPLATE/blank.yml
vendored
Normal file
30
.github/ISSUE_TEMPLATE/blank.yml
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
name: Blank Issue
|
||||||
|
description: Create a blank issue. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL.
|
||||||
|
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
# READ THIS BEFORE OPENING AN ISSUE
|
||||||
|
|
||||||
|
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
|
||||||
|
|
||||||
|
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
|
||||||
|
- you are filing a security related report
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: content
|
||||||
|
attributes:
|
||||||
|
label: Content
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: checkboxes
|
||||||
|
id: agreement-check
|
||||||
|
attributes:
|
||||||
|
label: Request Agreement
|
||||||
|
options:
|
||||||
|
- label: I have read the requirements for opening an issue above
|
||||||
|
required: true
|
16
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
16
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@ -1,9 +1,21 @@
|
|||||||
name: Bug/Crash Report
|
name: Bug/Crash Report
|
||||||
description: Create a bug or crash report for Vencord
|
description: Create a bug or crash report for Vencord. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL.
|
||||||
labels: [bug]
|
labels: [bug]
|
||||||
title: "[Bug] <title>"
|
title: "[Bug] <title>"
|
||||||
|
|
||||||
body:
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
# READ THIS BEFORE OPENING AN ISSUE
|
||||||
|
|
||||||
|
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
|
||||||
|
|
||||||
|
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
|
||||||
|
- you are filing a security related report
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: discord
|
id: discord
|
||||||
attributes:
|
attributes:
|
||||||
@ -64,3 +76,5 @@ body:
|
|||||||
options:
|
options:
|
||||||
- label: I am using Discord Stable or tried on Stable and this bug happens there as well
|
- label: I am using Discord Stable or tried on Stable and this bug happens there as well
|
||||||
required: true
|
required: true
|
||||||
|
- label: I have read the requirements for opening an issue above
|
||||||
|
required: true
|
||||||
|
2
.github/ISSUE_TEMPLATE/config.yml
vendored
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
blank_issues_enabled: true
|
blank_issues_enabled: false
|
||||||
contact_links:
|
contact_links:
|
||||||
- name: Vencord Support Server
|
- name: Vencord Support Server
|
||||||
url: https://discord.gg/D9uwnFnqmd
|
url: https://discord.gg/D9uwnFnqmd
|
||||||
|
32
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
32
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@ -1,32 +0,0 @@
|
|||||||
name: Feature Request
|
|
||||||
description: Create a feature request for Vencord. To request new plugins, please use the Discussions tab
|
|
||||||
labels: [enhancement]
|
|
||||||
title: "[Feature Request] <title>"
|
|
||||||
|
|
||||||
body:
|
|
||||||
- type: input
|
|
||||||
id: discord
|
|
||||||
attributes:
|
|
||||||
label: Discord Account
|
|
||||||
description: Who on Discord is making this request? Not required but encouraged for easier follow-up
|
|
||||||
placeholder: username#0000
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
|
||||||
id: feature-basic-description
|
|
||||||
attributes:
|
|
||||||
label: What is it that you'd like to see?
|
|
||||||
description: Describe the feature you want added as detailed as possible
|
|
||||||
placeholder: I think ... would be a cool feature to add. This would be awesome, thanks!
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
|
|
||||||
- type: checkboxes
|
|
||||||
id: agreement-check
|
|
||||||
attributes:
|
|
||||||
label: Request Agreement
|
|
||||||
description: DO NOT USE THIS TEMPLATE FOR PLUGIN REQUESTS!!! For plugin requests, **use discussions**
|
|
||||||
options:
|
|
||||||
- label: This is not a plugin request
|
|
||||||
required: true
|
|
18
.github/workflows/publish.yml
vendored
18
.github/workflows/publish.yml
vendored
@ -36,26 +36,10 @@ jobs:
|
|||||||
|
|
||||||
- name: Publish extension
|
- name: Publish extension
|
||||||
run: |
|
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
|
cd dist/chromium-unpacked
|
||||||
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish || EXIT_CODE=$?
|
pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish
|
||||||
|
|
||||||
# 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:
|
env:
|
||||||
# Chrome
|
|
||||||
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
|
||||||
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
|
CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
|
||||||
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
|
CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
|
||||||
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
|
||||||
|
|
||||||
# Firefox
|
|
||||||
WEB_EXT_API_KEY: ${{ secrets.WEBEXT_USER }}
|
|
||||||
WEB_EXT_API_SECRET: ${{ secrets.WEBEXT_SECRET }}
|
|
||||||
|
24
README.md
24
README.md
@ -22,29 +22,7 @@ The cutest Discord client mod
|
|||||||
|
|
||||||
## Installing / Uninstalling
|
## Installing / Uninstalling
|
||||||
|
|
||||||
Click the below button to install Vencord to the Discord Desktop app
|
Visit https://vencord.dev/download
|
||||||
|
|
||||||
[![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
|
## Join our Support/Community Server
|
||||||
|
|
||||||
|
@ -48,7 +48,8 @@ window.VencordNative = {
|
|||||||
getThemesList: () => DataStore.entries(themeStore).then(entries =>
|
getThemesList: () => DataStore.entries(themeStore).then(entries =>
|
||||||
entries.map(([name, css]) => getThemeInfo(css, name.toString()))
|
entries.map(([name, css]) => getThemeInfo(css, name.toString()))
|
||||||
),
|
),
|
||||||
getThemeData: (fileName: string) => DataStore.get(fileName, themeStore)
|
getThemeData: (fileName: string) => DataStore.get(fileName, themeStore),
|
||||||
|
getSystemValues: async () => ({}),
|
||||||
},
|
},
|
||||||
|
|
||||||
native: {
|
native: {
|
||||||
|
32
browser/background.js
Normal file
32
browser/background.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
/**
|
||||||
|
* @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,7 +26,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
"web_accessible_resources": ["dist/*", "third-party/*"],
|
"background": {
|
||||||
|
"scripts": ["background.js"]
|
||||||
|
},
|
||||||
|
|
||||||
|
"web_accessible_resources": ["dist/Vencord.js", "dist/Vencord.css"],
|
||||||
|
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.5.2",
|
"version": "1.5.8",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
@ -94,7 +94,7 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"overwriteDest": true
|
"overwriteDest": true
|
||||||
},
|
},
|
||||||
"sourceDir": "./dist/extension-v2-unpacked"
|
"sourceDir": "./dist/firefox-unpacked"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18",
|
"node": ">=18",
|
||||||
|
@ -43,7 +43,7 @@ const nodeCommonOpts = {
|
|||||||
format: "cjs",
|
format: "cjs",
|
||||||
platform: "node",
|
platform: "node",
|
||||||
target: ["esnext"],
|
target: ["esnext"],
|
||||||
external: ["electron", ...commonOpts.external],
|
external: ["electron", "original-fs", ...commonOpts.external],
|
||||||
define: defines,
|
define: defines,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -145,11 +145,11 @@ async function loadDir(dir, basePath = "") {
|
|||||||
/**
|
/**
|
||||||
* @type {(target: string, files: string[]) => Promise<void>}
|
* @type {(target: string, files: string[]) => Promise<void>}
|
||||||
*/
|
*/
|
||||||
async function buildExtension(target, files, noMonaco = false) {
|
async function buildExtension(target, files) {
|
||||||
const entries = {
|
const entries = {
|
||||||
"dist/Vencord.js": await readFile("dist/extension.js"),
|
"dist/Vencord.js": await readFile("dist/extension.js"),
|
||||||
"dist/Vencord.css": await readFile("dist/extension.css"),
|
"dist/Vencord.css": await readFile("dist/extension.css"),
|
||||||
...(noMonaco ? {} : await loadDir("dist/monaco")),
|
...await loadDir("dist/monaco"),
|
||||||
...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file =>
|
...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}`)]
|
[`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)]
|
||||||
))),
|
))),
|
||||||
@ -195,8 +195,11 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
appendCssRuntime,
|
appendCssRuntime,
|
||||||
buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
|
buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
|
||||||
buildExtension("firefox-unpacked", ["content.js", "manifestv2.json", "icon.png"], true),
|
buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension.zip");
|
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension-chrome.zip");
|
||||||
console.info("Packed Chromium Extension written to dist/extension.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");
|
||||||
|
@ -61,6 +61,13 @@ const report = {
|
|||||||
otherErrors: [] as string[]
|
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) {
|
function toCodeBlock(s: string) {
|
||||||
s = s.replace(/```/g, "`\u200B`\u200B`");
|
s = s.replace(/```/g, "`\u200B`\u200B`");
|
||||||
return "```" + s + " ```";
|
return "```" + s + " ```";
|
||||||
@ -86,6 +93,8 @@ async function printReport() {
|
|||||||
console.log(` - Error: ${toCodeBlock(p.error)}`);
|
console.log(` - Error: ${toCodeBlock(p.error)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
report.otherErrors = report.otherErrors.filter(e => !IGNORED_DISCORD_ERRORS.some(regex => e.match(regex)));
|
||||||
|
|
||||||
console.log("## Discord Errors");
|
console.log("## Discord Errors");
|
||||||
report.otherErrors.forEach(e => {
|
report.otherErrors.forEach(e => {
|
||||||
console.log(`- ${toCodeBlock(e)}`);
|
console.log(`- ${toCodeBlock(e)}`);
|
||||||
|
@ -23,7 +23,8 @@ export default {
|
|||||||
deleteTheme: (fileName: string) => invoke<void>(IpcEvents.DELETE_THEME, fileName),
|
deleteTheme: (fileName: string) => invoke<void>(IpcEvents.DELETE_THEME, fileName),
|
||||||
getThemesDir: () => invoke<string>(IpcEvents.GET_THEMES_DIR),
|
getThemesDir: () => invoke<string>(IpcEvents.GET_THEMES_DIR),
|
||||||
getThemesList: () => invoke<Array<UserThemeHeader>>(IpcEvents.GET_THEMES_LIST),
|
getThemesList: () => invoke<Array<UserThemeHeader>>(IpcEvents.GET_THEMES_LIST),
|
||||||
getThemeData: (fileName: string) => invoke<string | undefined>(IpcEvents.GET_THEME_DATA, fileName)
|
getThemeData: (fileName: string) => invoke<string | undefined>(IpcEvents.GET_THEME_DATA, fileName),
|
||||||
|
getSystemValues: () => invoke<Record<string, string>>(IpcEvents.GET_THEME_SYSTEM_VALUES),
|
||||||
},
|
},
|
||||||
|
|
||||||
updater: {
|
updater: {
|
||||||
|
@ -20,7 +20,6 @@ import { Channel, User } from "discord-types/general/index.js";
|
|||||||
|
|
||||||
interface DecoratorProps {
|
interface DecoratorProps {
|
||||||
activities: any[];
|
activities: any[];
|
||||||
canUseAvatarDecorations: boolean;
|
|
||||||
channel: Channel;
|
channel: Channel;
|
||||||
/**
|
/**
|
||||||
* Only for DM members
|
* Only for DM members
|
||||||
@ -52,9 +51,9 @@ export function removeDecorator(identifier: string) {
|
|||||||
decorators.delete(identifier);
|
decorators.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function __addDecoratorsToList(props: DecoratorProps): (JSX.Element | null)[] {
|
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
|
||||||
const isInGuild = !!(props.guildId);
|
const isInGuild = !!(props.guildId);
|
||||||
return [...decorators.values()].map(decoratorObj => {
|
return Array.from(decorators.values(), decoratorObj => {
|
||||||
const { decorator, onlyIn } = decoratorObj;
|
const { decorator, onlyIn } = decoratorObj;
|
||||||
// this can most likely be done cleaner
|
// this can most likely be done cleaner
|
||||||
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
|
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
|
||||||
|
@ -237,7 +237,8 @@ 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 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 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) {
|
export function addSettingsListener(path: string, onUpdate: (newValue: any, path: string) => void) {
|
||||||
((onUpdate as SubscriptionCallback)._paths ??= []).push(path);
|
if (path)
|
||||||
|
((onUpdate as SubscriptionCallback)._paths ??= []).push(path);
|
||||||
subscriptions.add(onUpdate);
|
subscriptions.add(onUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,3 +221,37 @@ export function CogWheel(props: IconProps) {
|
|||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ReplyIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "vc-reply-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M10 8.26667V4L3 11.4667L10 18.9333V14.56C15 14.56 18.5 16.2667 21 20C20 14.6667 17 9.33333 10 8.26667Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DeleteIcon(props: IconProps) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
{...props}
|
||||||
|
className={classes(props.className, "vc-delete-icon")}
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M15 3.999V2H9V3.999H3V5.999H21V3.999H15Z"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
fill="currentColor"
|
||||||
|
d="M5 6.99902V18.999C5 20.101 5.897 20.999 7 20.999H17C18.103 20.999 19 20.101 19 18.999V6.99902H5ZM11 17H9V11H11V17ZM15 17H13V11H15V17Z"
|
||||||
|
/>
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -238,7 +238,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||||||
<Button
|
<Button
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
size={Button.Sizes.SMALL}
|
size={Button.Sizes.SMALL}
|
||||||
color={Button.Colors.WHITE}
|
color={Button.Colors.PRIMARY}
|
||||||
look={Button.Looks.LINK}
|
look={Button.Looks.LINK}
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
text-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-author-modal-name::before {
|
.vc-author-modal-name::before {
|
||||||
|
@ -18,15 +18,14 @@
|
|||||||
|
|
||||||
import { useSettings } from "@api/Settings";
|
import { useSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
|
import { DeleteIcon } from "@components/Icons";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { IsFirefox } from "@utils/constants";
|
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { showItemInFolder } from "@utils/native";
|
import { showItemInFolder } from "@utils/native";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack";
|
import { findByPropsLazy, findLazy } from "@webpack";
|
||||||
import { Button, Card, FluxDispatcher, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
import { Button, Card, FluxDispatcher, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
||||||
import { UserThemeHeader } from "main/themes";
|
import { UserThemeHeader } from "main/themes";
|
||||||
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
||||||
@ -42,8 +41,7 @@ type FileInput = ComponentType<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
const InviteActions = findByPropsLazy("resolveInvite");
|
const InviteActions = findByPropsLazy("resolveInvite");
|
||||||
const TrashIcon = findByCodeLazy("M5 6.99902V18.999C5 20.101 5.897 20.999");
|
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 TextAreaProps = findLazy(m => typeof m.textarea === "string");
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-theme-");
|
const cl = classNameFactory("vc-settings-theme-");
|
||||||
@ -114,7 +112,7 @@ function ThemeCard({ theme, enabled, onChange, onDelete }: ThemeCardProps) {
|
|||||||
infoButton={
|
infoButton={
|
||||||
IS_WEB && (
|
IS_WEB && (
|
||||||
<div style={{ cursor: "pointer", color: "var(--status-danger" }} onClick={onDelete}>
|
<div style={{ cursor: "pointer", color: "var(--status-danger" }} onClick={onDelete}>
|
||||||
<TrashIcon />
|
<DeleteIcon />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -251,14 +249,12 @@ function ThemesTab() {
|
|||||||
>
|
>
|
||||||
Load missing Themes
|
Load missing Themes
|
||||||
</Button>
|
</Button>
|
||||||
{!IsFirefox && (
|
<Button
|
||||||
<Button
|
onClick={() => VencordNative.quickCss.openEditor()}
|
||||||
onClick={() => VencordNative.quickCss.openEditor()}
|
size={Button.Sizes.SMALL}
|
||||||
size={Button.Sizes.SMALL}
|
>
|
||||||
>
|
Edit QuickCSS
|
||||||
Edit QuickCSS
|
</Button>
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@ -320,15 +316,6 @@ function ThemesTab() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsTab title="Themes">
|
<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
|
<TabBar
|
||||||
type="top"
|
type="top"
|
||||||
look="brand"
|
look="brand"
|
||||||
|
@ -21,7 +21,6 @@ import { Settings, useSettings } from "@api/Settings";
|
|||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import DonateButton from "@components/DonateButton";
|
import DonateButton from "@components/DonateButton";
|
||||||
import { ErrorCard } from "@components/ErrorCard";
|
import { ErrorCard } from "@components/ErrorCard";
|
||||||
import { IsFirefox } from "@utils/constants";
|
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { identity } from "@utils/misc";
|
import { identity } from "@utils/misc";
|
||||||
import { relaunch, showItemInFolder } from "@utils/native";
|
import { relaunch, showItemInFolder } from "@utils/native";
|
||||||
@ -110,14 +109,12 @@ function VencordSettings() {
|
|||||||
Restart Client
|
Restart Client
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!IsFirefox && (
|
<Button
|
||||||
<Button
|
onClick={() => VencordNative.quickCss.openEditor()}
|
||||||
onClick={() => VencordNative.quickCss.openEditor()}
|
size={Button.Sizes.SMALL}
|
||||||
size={Button.Sizes.SMALL}
|
disabled={settingsDir === "Loading..."}>
|
||||||
disabled={settingsDir === "Loading..."}>
|
Open QuickCSS File
|
||||||
Open QuickCSS File
|
</Button>
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{!IS_WEB && (
|
{!IS_WEB && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => showItemInFolder(settingsDir)}
|
onClick={() => showItemInFolder(settingsDir)}
|
||||||
|
@ -62,6 +62,10 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
|||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
|
|
||||||
|
const findHeader = (headers: Record<string, string[]>, headerName: Lowercase<string>) => {
|
||||||
|
return Object.keys(headers).find(h => h.toLowerCase() === headerName);
|
||||||
|
};
|
||||||
|
|
||||||
// Remove CSP
|
// Remove CSP
|
||||||
type PolicyResult = Record<string, string[]>;
|
type PolicyResult = Record<string, string[]>;
|
||||||
|
|
||||||
@ -73,6 +77,7 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
|||||||
result[directiveKey] = directiveValue;
|
result[directiveKey] = directiveValue;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
const stringifyPolicy = (policy: PolicyResult): string =>
|
const stringifyPolicy = (policy: PolicyResult): string =>
|
||||||
@ -81,31 +86,39 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
|||||||
.map(directive => directive.flat().join(" "))
|
.map(directive => directive.flat().join(" "))
|
||||||
.join("; ");
|
.join("; ");
|
||||||
|
|
||||||
function patchCsp(headers: Record<string, string[]>, header: string) {
|
const patchCsp = (headers: Record<string, string[]>) => {
|
||||||
if (header in headers) {
|
const header = findHeader(headers, "content-security-policy");
|
||||||
|
|
||||||
|
if (header) {
|
||||||
const csp = parsePolicy(headers[header][0]);
|
const csp = parsePolicy(headers[header][0]);
|
||||||
|
|
||||||
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
|
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
|
||||||
csp[directive] = ["*", "blob:", "data:", "vencord:", "'unsafe-inline'"];
|
csp[directive] ??= [];
|
||||||
|
csp[directive].push("*", "blob:", "data:", "vencord:", "'unsafe-inline'");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Restrict this to only imported packages with fixed version.
|
// TODO: Restrict this to only imported packages with fixed version.
|
||||||
// Perhaps auto generate with esbuild
|
// Perhaps auto generate with esbuild
|
||||||
csp["script-src"] ??= [];
|
csp["script-src"] ??= [];
|
||||||
csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com");
|
csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com");
|
||||||
headers[header] = [stringifyPolicy(csp)];
|
headers[header] = [stringifyPolicy(csp)];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => {
|
session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => {
|
||||||
if (responseHeaders) {
|
if (responseHeaders) {
|
||||||
if (resourceType === "mainFrame")
|
if (resourceType === "mainFrame")
|
||||||
patchCsp(responseHeaders, "content-security-policy");
|
patchCsp(responseHeaders);
|
||||||
|
|
||||||
// Fix hosts that don't properly set the css content type, such as
|
// Fix hosts that don't properly set the css content type, such as
|
||||||
// raw.githubusercontent.com
|
// raw.githubusercontent.com
|
||||||
if (resourceType === "stylesheet")
|
if (resourceType === "stylesheet") {
|
||||||
responseHeaders["content-type"] = ["text/css"];
|
const header = findHeader(responseHeaders, "content-type");
|
||||||
|
if (header)
|
||||||
|
responseHeaders[header] = ["text/css"];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cb({ cancel: false, responseHeaders });
|
cb({ cancel: false, responseHeaders });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ import "./ipcPlugins";
|
|||||||
import { debounce } from "@utils/debounce";
|
import { debounce } from "@utils/debounce";
|
||||||
import { IpcEvents } from "@utils/IpcEvents";
|
import { IpcEvents } from "@utils/IpcEvents";
|
||||||
import { Queue } from "@utils/Queue";
|
import { Queue } from "@utils/Queue";
|
||||||
import { BrowserWindow, ipcMain, shell } from "electron";
|
import { BrowserWindow, ipcMain, shell, systemPreferences } from "electron";
|
||||||
import { mkdirSync, readFileSync, watch } from "fs";
|
import { mkdirSync, readFileSync, watch } from "fs";
|
||||||
import { open, readdir, readFile, writeFile } from "fs/promises";
|
import { open, readdir, readFile, writeFile } from "fs/promises";
|
||||||
import { join, normalize } from "path";
|
import { join, normalize } from "path";
|
||||||
@ -112,6 +112,10 @@ ipcMain.handle(IpcEvents.SET_QUICK_CSS, (_, css) =>
|
|||||||
ipcMain.handle(IpcEvents.GET_THEMES_DIR, () => THEMES_DIR);
|
ipcMain.handle(IpcEvents.GET_THEMES_DIR, () => THEMES_DIR);
|
||||||
ipcMain.handle(IpcEvents.GET_THEMES_LIST, () => listThemes());
|
ipcMain.handle(IpcEvents.GET_THEMES_LIST, () => listThemes());
|
||||||
ipcMain.handle(IpcEvents.GET_THEME_DATA, (_, fileName) => getThemeData(fileName));
|
ipcMain.handle(IpcEvents.GET_THEME_DATA, (_, fileName) => getThemeData(fileName));
|
||||||
|
ipcMain.handle(IpcEvents.GET_THEME_SYSTEM_VALUES, () => ({
|
||||||
|
// win & mac only
|
||||||
|
"os-accent-color": `#${systemPreferences.getAccentColor?.() || ""}`
|
||||||
|
}));
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR);
|
ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR);
|
||||||
ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = readSettings());
|
ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = readSettings());
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "fs";
|
import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "original-fs";
|
||||||
import { basename, dirname, join } from "path";
|
import { basename, dirname, join } from "path";
|
||||||
|
|
||||||
function isNewer($new: string, old: string) {
|
function isNewer($new: string, old: string) {
|
||||||
|
@ -26,7 +26,7 @@ export default definePlugin({
|
|||||||
patches: [
|
patches: [
|
||||||
// obtain BUILT_IN_COMMANDS instance
|
// obtain BUILT_IN_COMMANDS instance
|
||||||
{
|
{
|
||||||
find: '"giphy","tenor"',
|
find: ',"tenor"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
// Matches BUILT_IN_COMMANDS. This is not exported so this is
|
// Matches BUILT_IN_COMMANDS. This is not exported so this is
|
||||||
@ -34,7 +34,7 @@ export default definePlugin({
|
|||||||
// patch simpler
|
// patch simpler
|
||||||
|
|
||||||
// textCommands = builtInCommands.filter(...)
|
// textCommands = builtInCommands.filter(...)
|
||||||
match: /(?<=\w=)(\w)(\.filter\(.{0,30}giphy)/,
|
match: /(?<=\w=)(\w)(\.filter\(.{0,60}tenor)/,
|
||||||
replace: "Vencord.Api.Commands._init($1)$2",
|
replace: "Vencord.Api.Commands._init($1)$2",
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
@ -22,21 +22,28 @@ import definePlugin from "@utils/types";
|
|||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MemberListDecoratorsAPI",
|
name: "MemberListDecoratorsAPI",
|
||||||
description: "API to add decorators to member list (both in servers and DMs)",
|
description: "API to add decorators to member list (both in servers and DMs)",
|
||||||
authors: [Devs.TheSun],
|
authors: [Devs.TheSun, Devs.Ven],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "lostPermissionTooltipText,",
|
find: "lostPermissionTooltipText,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /Fragment,{children:\[(.{30,80})\]/,
|
match: /decorators:.{0,100}?children:\[(?<=(\i)\.lostPermissionTooltipText.+?)/,
|
||||||
replace: "Fragment,{children:Vencord.Api.MemberListDecorators.__addDecoratorsToList(this.props).concat($1)"
|
replace: "$&...Vencord.Api.MemberListDecorators.__getDecorators($1),"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "PrivateChannel.renderAvatar",
|
find: "PrivateChannel.renderAvatar",
|
||||||
replacement: {
|
replacement: [
|
||||||
match: /(subText:(.{1,2})\.renderSubtitle\(\).{1,50}decorators):(.{30,100}:null)/,
|
// props are shadowed by nested props so we have to do this
|
||||||
replace: "$1:Vencord.Api.MemberListDecorators.__addDecoratorsToList($2.props).concat($3)"
|
{
|
||||||
}
|
match: /\i=(\i)\.applicationStream,/,
|
||||||
|
replace: "$&vencordProps=$1,"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/,
|
||||||
|
replace: "decorators:[...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)), $1?$2:null]"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
@ -27,7 +27,7 @@ export default definePlugin({
|
|||||||
{
|
{
|
||||||
find: ".withMentionPrefix",
|
find: ".withMentionPrefix",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(.roleDot.{10,50}{children:.{1,2})}\)/,
|
match: /(currentUserIsPremium:.{10,50}{children:.{1,2})}\)/,
|
||||||
replace: "$1.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))})"
|
replace: "$1.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))})"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,9 @@ export default definePlugin({
|
|||||||
addContextMenuPatch("user-settings-cog", children => () => {
|
addContextMenuPatch("user-settings-cog", children => () => {
|
||||||
const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any;
|
const section = children.find(c => Array.isArray(c) && c.some(it => it?.props?.id === "VencordSettings")) as any;
|
||||||
section?.forEach(c => {
|
section?.forEach(c => {
|
||||||
if (c?.props?.id?.startsWith("Vencord")) {
|
const id = c?.props?.id;
|
||||||
c.props.action = () => SettingsRouter.open(c.props.id);
|
if (id?.startsWith("Vencord") || id?.startsWith("Vesktop")) {
|
||||||
|
c.props.action = () => SettingsRouter.open(id);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { DataStore } from "@api/index";
|
import { DataStore } from "@api/index";
|
||||||
import { Devs, IsFirefox, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
||||||
import { isPluginDev } from "@utils/misc";
|
import { isPluginDev } from "@utils/misc";
|
||||||
import { makeCodeblock } from "@utils/text";
|
import { makeCodeblock } from "@utils/text";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
@ -30,7 +30,6 @@ import plugins from "~plugins";
|
|||||||
import settings from "./settings";
|
import settings from "./settings";
|
||||||
|
|
||||||
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
|
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
|
||||||
const FIREFOX_DISMISS_KEY = "Vencord-Firefox-Warning-Dismiss";
|
|
||||||
|
|
||||||
const AllowedChannelIds = [
|
const AllowedChannelIds = [
|
||||||
SUPPORT_CHANNEL_ID,
|
SUPPORT_CHANNEL_ID,
|
||||||
@ -116,22 +115,6 @@ ${makeCodeblock(enabledPlugins.join(", ") + "\n\n" + enabledApiPlugins.join(", "
|
|||||||
onConfirm: rememberDismiss
|
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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -58,6 +58,26 @@ 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() {
|
async start() {
|
||||||
// ArmCord comes with its own arRPC implementation, so this plugin just confuses users
|
// ArmCord comes with its own arRPC implementation, so this plugin just confuses users
|
||||||
if ("armcord" in window) return;
|
if ("armcord" in window) return;
|
||||||
@ -65,22 +85,7 @@ export default definePlugin({
|
|||||||
if (ws) ws.close();
|
if (ws) ws.close();
|
||||||
ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket
|
ws = new WebSocket("ws://127.0.0.1:1337"); // try to open WebSocket
|
||||||
|
|
||||||
ws.onmessage = async e => { // on message, set status to data
|
ws.onmessage = this.handleEvent;
|
||||||
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
|
const connectionSuccessful = await new Promise(res => setTimeout(() => res(ws.readyState === WebSocket.OPEN), 1000)); // check if open after 1s
|
||||||
if (!connectionSuccessful) {
|
if (!connectionSuccessful) {
|
@ -19,6 +19,9 @@
|
|||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
|
||||||
|
const UserPopoutSectionCssClasses = findByPropsLazy("section", "lastSection");
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "BetterNotesBox",
|
name: "BetterNotesBox",
|
||||||
@ -34,12 +37,20 @@ export default definePlugin({
|
|||||||
match: /hideNote:.+?(?=[,}])/g,
|
match: /hideNote:.+?(?=[,}])/g,
|
||||||
replace: "hideNote:true",
|
replace: "hideNote:true",
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
find: "Messages.NOTE_PLACEHOLDER",
|
find: "Messages.NOTE_PLACEHOLDER",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.NOTE_PLACEHOLDER,/,
|
match: /\.NOTE_PLACEHOLDER,/,
|
||||||
replace: "$&spellCheck:!Vencord.Settings.plugins.BetterNotesBox.noSpellCheck,"
|
replace: "$&spellCheck:!Vencord.Settings.plugins.BetterNotesBox.noSpellCheck,"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".Messages.NOTE}",
|
||||||
|
replacement: {
|
||||||
|
match: /(\i)\.hideNote\?null/,
|
||||||
|
replace: "$1.hideNote?$self.patchPadding($1)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
@ -56,5 +67,12 @@ export default definePlugin({
|
|||||||
disabled: () => Settings.plugins.BetterNotesBox.hide,
|
disabled: () => Settings.plugins.BetterNotesBox.hide,
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
patchPadding(e: any) {
|
||||||
|
if (!e.lastSection) return;
|
||||||
|
return (
|
||||||
|
<div className={UserPopoutSectionCssClasses.lastSection}></div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
@ -48,6 +48,7 @@ export default definePlugin({
|
|||||||
{
|
{
|
||||||
find: ".ADD_ROLE_A11Y_LABEL",
|
find: ".ADD_ROLE_A11Y_LABEL",
|
||||||
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
||||||
|
noWarn: true,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /"dot"===\i/,
|
match: /"dot"===\i/,
|
||||||
replace: "true"
|
replace: "true"
|
||||||
@ -56,6 +57,7 @@ export default definePlugin({
|
|||||||
{
|
{
|
||||||
find: ".roleVerifiedIcon",
|
find: ".roleVerifiedIcon",
|
||||||
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
||||||
|
noWarn: true,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /"dot"===\i/,
|
match: /"dot"===\i/,
|
||||||
replace: "true"
|
replace: "true"
|
@ -24,11 +24,9 @@ import { Margins } from "@utils/margins";
|
|||||||
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
|
import { ModalContent, ModalHeader, ModalRoot, openModalLazy } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findByCodeLazy, findStoreLazy } from "@webpack";
|
import { findByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
|
import { EmojiStore, FluxDispatcher, Forms, GuildStore, Menu, PermissionsBits, PermissionStore, React, RestAPI, Toasts, Tooltip, UserStore } from "@webpack/common";
|
||||||
import { Promisable } from "type-fest";
|
import { Promisable } from "type-fest";
|
||||||
|
|
||||||
const MANAGE_EMOJIS_AND_STICKERS = 1n << 30n;
|
|
||||||
|
|
||||||
const StickersStore = findStoreLazy("StickersStore");
|
const StickersStore = findStoreLazy("StickersStore");
|
||||||
const uploadEmoji = findByCodeLazy('"EMOJI_UPLOAD_START"', "GUILD_EMOJIS(");
|
const uploadEmoji = findByCodeLazy('"EMOJI_UPLOAD_START"', "GUILD_EMOJIS(");
|
||||||
|
|
||||||
@ -120,7 +118,7 @@ function getGuildCandidates(data: Data) {
|
|||||||
|
|
||||||
return Object.values(GuildStore.getGuilds()).filter(g => {
|
return Object.values(GuildStore.getGuilds()).filter(g => {
|
||||||
const canCreate = g.ownerId === meId ||
|
const canCreate = g.ownerId === meId ||
|
||||||
BigInt(PermissionStore.getGuildPermissions({ id: g.id }) & MANAGE_EMOJIS_AND_STICKERS) === MANAGE_EMOJIS_AND_STICKERS;
|
(PermissionStore.getGuildPermissions({ id: g.id }) & PermissionsBits.CREATE_GUILD_EXPRESSIONS) === PermissionsBits.CREATE_GUILD_EXPRESSIONS;
|
||||||
if (!canCreate) return false;
|
if (!canCreate) return false;
|
||||||
|
|
||||||
if (data.t === "Sticker") return true;
|
if (data.t === "Sticker") return true;
|
@ -33,12 +33,6 @@ const settings = definePluginSettings({
|
|||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
default: false,
|
default: false,
|
||||||
restartNeeded: true
|
restartNeeded: true
|
||||||
},
|
|
||||||
forceStagingBanner: {
|
|
||||||
description: "Whether to force Staging banner under user area.",
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
default: false,
|
|
||||||
restartNeeded: true
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -83,14 +77,6 @@ export default definePlugin({
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
find: ".Messages.DEV_NOTICE_STAGING",
|
|
||||||
predicate: () => settings.store.forceStagingBanner,
|
|
||||||
replacement: {
|
|
||||||
match: /"staging"===window\.GLOBAL_ENV\.RELEASE_CHANNEL/,
|
|
||||||
replace: "true"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
find: 'H1,title:"Experiments"',
|
find: 'H1,title:"Experiments"',
|
||||||
replacement: {
|
replacement: {
|
@ -222,8 +222,7 @@ export default definePlugin({
|
|||||||
predicate: () => settings.store.enableStreamQualityBypass,
|
predicate: () => settings.store.enableStreamQualityBypass,
|
||||||
replacement: [
|
replacement: [
|
||||||
"canUseHighVideoUploadQuality",
|
"canUseHighVideoUploadQuality",
|
||||||
// TODO: Remove the last two when they get removed from stable
|
"canStreamQuality",
|
||||||
"(?:canStreamQuality|canStreamHighQuality|canStreamMidQuality)",
|
|
||||||
].map(func => {
|
].map(func => {
|
||||||
return {
|
return {
|
||||||
match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"),
|
match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"),
|
||||||
@ -296,7 +295,7 @@ export default definePlugin({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
predicate: () => settings.store.transformStickers,
|
predicate: () => settings.store.transformStickers,
|
||||||
match: /renderAttachments=function\(\i\){var (\i)=\i.attachments.+?;/,
|
match: /renderAttachments=function\(\i\){var \i=this,(\i)=\i.attachments.+?;/,
|
||||||
replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});`
|
replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -330,6 +329,20 @@ export default definePlugin({
|
|||||||
match: /(?<=\.Messages\.EMOJI_POPOUT_ADDED_PACK_DESCRIPTION.+?return ).{0,1200}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?(?=}\()/,
|
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)`
|
replace: reactNode => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!arguments[0]?.fakeNitroNode?.fake)`
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "canUsePremiumAppIcons:function",
|
||||||
|
replacement: {
|
||||||
|
match: /canUsePremiumAppIcons:function\(\i\){/,
|
||||||
|
replace: "$&return true;"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "location:\"AppIconHome\"",
|
||||||
|
replacement: {
|
||||||
|
match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/,
|
||||||
|
replace: "true"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
Puts your favorite emoji first in the emoji autocomplete.
|
Puts your favorite emoji first in the emoji autocomplete.
|
||||||
|
|
||||||
![FavEmojis](https://i.imgur.com/mEFCoZG.png)
|
![a screenshot of the favourite emojis section](https://github.com/Vendicated/Vencord/assets/45497981/419c8c16-1afc-46e0-9cc2-20b9c3489711)
|
||||||
![Example](https://i.imgur.com/wY3Tc43.png)
|
![a comparison of the emoji picker before and after enabling this plugin](https://github.com/Vendicated/Vencord/assets/45497981/4f57626d-cfc6-4155-a47c-2eac191231bb)
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
Adds a search bar to favorite gifs.
|
Adds a search bar to favorite gifs.
|
||||||
|
|
||||||
![Screenshot](https://i.imgur.com/Bcgb7PD.png)
|
![Screenshot](https://github.com/Vendicated/Vencord/assets/45497981/19552adc-d921-4153-976e-e9361dc8fdaf)
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { GuildStore } from "@webpack/common";
|
import { GuildStore } from "@webpack/common";
|
||||||
|
import { Channel, User } from "discord-types/general";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "ForceOwnerCrown",
|
name: "ForceOwnerCrown",
|
||||||
@ -27,32 +28,22 @@ export default definePlugin({
|
|||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
// This is the logic where it decides whether to render the owner crown or not
|
// This is the logic where it decides whether to render the owner crown or not
|
||||||
find: ".renderOwner=",
|
find: ".MULTIPLE_AVATAR",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /isOwner;return null!=(\w+)?&&/g,
|
match: /(\i)=(\i)\.isOwner,/,
|
||||||
replace: "isOwner;if($self.isGuildOwner(this.props)){$1=true;}return null!=$1&&"
|
replace: "$1=$self.isGuildOwner($2),"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
],
|
|
||||||
isGuildOwner(props) {
|
|
||||||
// Check if channel is a Group DM, if so return false
|
|
||||||
if (props?.channel?.type === 3) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
isGuildOwner(props: { user: User, channel: Channel, guildId?: string; }) {
|
||||||
|
if (!props?.user?.id) return false;
|
||||||
|
if (props.channel?.type === 3 /* GROUP_DM */)
|
||||||
|
return false;
|
||||||
|
|
||||||
// guild id is in props twice, fallback if the first is undefined
|
// guild id is in props twice, fallback if the first is undefined
|
||||||
const guildId = props?.guildId ?? props?.channel?.guild_id;
|
const guildId = props.guildId ?? props.channel?.guild_id;
|
||||||
const userId = props?.user?.id;
|
const userId = props.user.id;
|
||||||
|
|
||||||
if (guildId && userId) {
|
return GuildStore.getGuild(guildId)?.ownerId === 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;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
@ -1,233 +0,0 @@
|
|||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2022 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as DataStore from "@api/DataStore";
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import { useForceUpdater } from "@utils/react";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
|
||||||
import { Tooltip } from "webpack/common";
|
|
||||||
|
|
||||||
const enum ActivitiesTypes {
|
|
||||||
Game,
|
|
||||||
Embedded
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IgnoredActivity {
|
|
||||||
id: string;
|
|
||||||
type: ActivitiesTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
const RegisteredGamesClasses = findByPropsLazy("overlayToggleIconOff", "overlayToggleIconOn");
|
|
||||||
const TryItOutClasses = findByPropsLazy("tryItOutBadge", "tryItOutBadgeIcon");
|
|
||||||
const BaseShapeRoundClasses = findByPropsLazy("baseShapeRound", "baseShapeRoundLeft", "baseShapeRoundRight");
|
|
||||||
const RunningGameStore = findStoreLazy("RunningGameStore");
|
|
||||||
|
|
||||||
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="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)}
|
|
||||||
>
|
|
||||||
{
|
|
||||||
ignoredActivitiesCache.has(activity.id)
|
|
||||||
? <ToggleIconOff />
|
|
||||||
: <ToggleIconOn forceWhite={forceWhite} />
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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<HTMLDivElement, MouseEvent>, activity: IgnoredActivity, forceUpdateComponent: () => void) {
|
|
||||||
e.stopPropagation();
|
|
||||||
if (ignoredActivitiesCache.has(activity.id)) ignoredActivitiesCache.delete(activity.id);
|
|
||||||
else ignoredActivitiesCache.set(activity.id, activity);
|
|
||||||
forceUpdateComponent();
|
|
||||||
saveCacheToDatastore();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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: ".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: /(?<=\(\)\.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: '.displayName="LocalActivityStore"',
|
|
||||||
replacement: {
|
|
||||||
match: /LISTENING.+?\)\);(?<=(\i)\.push.+?)/,
|
|
||||||
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
async start() {
|
|
||||||
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 });
|
|
||||||
}
|
|
||||||
|
|
||||||
await saveCacheToDatastore();
|
|
||||||
} else ignoredActivitiesCache = ignoredActivitiesData;
|
|
||||||
|
|
||||||
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)) {
|
|
||||||
/** 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) {
|
|
||||||
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 !ignoredActivitiesCache.has(exePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
174
src/plugins/ignoreActivities/index.tsx
Normal file
174
src/plugins/ignoreActivities/index.tsx
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as DataStore from "@api/DataStore";
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { getSettingStoreLazy } from "@api/SettingsStore";
|
||||||
|
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 { Tooltip } from "webpack/common";
|
||||||
|
|
||||||
|
const enum ActivitiesTypes {
|
||||||
|
Game,
|
||||||
|
Embedded
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IgnoredActivity {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: ActivitiesTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RunningGameStore = findStoreLazy("RunningGameStore");
|
||||||
|
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame");
|
||||||
|
|
||||||
|
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
|
||||||
|
const forceUpdate = useForceUpdater();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip text={tooltipText}>
|
||||||
|
{tooltipProps => (
|
||||||
|
<button
|
||||||
|
{...tooltipProps}
|
||||||
|
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>
|
||||||
|
)}
|
||||||
|
</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 handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity, forceUpdateButton: () => 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
|
||||||
|
ShowCurrentGame?.updateSetting(old => old);
|
||||||
|
forceUpdateButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
const settings = definePluginSettings({}).withPrivateSettings<{
|
||||||
|
ignoredActivities: IgnoredActivity[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
function getIgnoredActivities() {
|
||||||
|
return settings.store.ignoredActivities ??= [];
|
||||||
|
}
|
||||||
|
|
||||||
|
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.",
|
||||||
|
|
||||||
|
dependencies: ["SettingsStoreAPI"],
|
||||||
|
settings,
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: '.displayName="LocalActivityStore"',
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /LISTENING.+?\)\);(?<=(\i)\.push.+?)/,
|
||||||
|
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY",
|
||||||
|
replacement: {
|
||||||
|
match: /\(\)\.removeGame.+?null(?<=(\i)\?\i=\i\.\i\.Messages\.SETTINGS_GAMES_NOW_PLAYING_STATE.+?=(\i)\.overlay.+?)/,
|
||||||
|
replace: (m, nowPlaying, props) => `${m},$self.renderToggleGameActivityButton(${props},${nowPlaying})`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".Messages.EMBEDDED_ACTIVITIES_DEVELOPER_SHELF_SUBTITLE",
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /(?<=\(\)\.activityTitleText.+?children:(\i)\.name.*?}\),)/,
|
||||||
|
replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /(?<=\(\)\.activityCardDetails.+?children:(\i\.application)\.name.*?}\),)/,
|
||||||
|
replace: (_, props) => `$self.renderToggleActivityButton(${props}),`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getIgnoredActivities().length !== 0) {
|
||||||
|
const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[];
|
||||||
|
|
||||||
|
for (const [index, ignoredActivity] of getIgnoredActivities().entries()) {
|
||||||
|
if (ignoredActivity.type !== ActivitiesTypes.Game) continue;
|
||||||
|
|
||||||
|
if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
|
||||||
|
getIgnoredActivities().splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
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);
|
||||||
|
else {
|
||||||
|
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
||||||
|
if (exePath) return !getIgnoredActivities().some(activity => activity.id === 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
@ -2,5 +2,5 @@
|
|||||||
|
|
||||||
Lets you zoom in to images and gifs. Use scroll wheel to zoom in and shift + scroll wheel to increase lens radius / size
|
Lets you zoom in to images and gifs. Use scroll wheel to zoom in and shift + scroll wheel to increase lens radius / size
|
||||||
|
|
||||||
![Example](https://i.imgur.com/VJdo4aq.png)
|
![the plugin in action](https://github.com/Vendicated/Vencord/assets/45497981/408cd77d-c5f4-40bc-8de2-f977a31b3e5f)
|
||||||
![ContextMenu](https://i.imgur.com/0oaRM2s.png)
|
![the context menu options offered by the plugin](https://github.com/Vendicated/Vencord/assets/45497981/3bede636-f1ce-493f-af46-788b920cb81c)
|
||||||
|
@ -165,7 +165,7 @@ export default definePlugin({
|
|||||||
{
|
{
|
||||||
find: '"renderLinkComponent","maxWidth"',
|
find: '"renderLinkComponent","maxWidth"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(return\(.{1,100}\(\)\.wrapper.{1,100})(src)/,
|
match: /(return\(.{1,100}\(\)\.wrapper.{1,200})(src)/,
|
||||||
replace: `$1id: '${ELEMENT_ID}',$2`
|
replace: `$1id: '${ELEMENT_ID}',$2`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -174,8 +174,8 @@ export default definePlugin({
|
|||||||
find: "handleImageLoad=",
|
find: "handleImageLoad=",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(render=function\(\){.{1,500}limitResponsiveWidth.{1,600})onMouseEnter:/,
|
match: /showThumbhashPlaceholder:/,
|
||||||
replace: "$1...$self.makeProps(this),onMouseEnter:"
|
replace: "...$self.makeProps(this),$&"
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -189,7 +189,6 @@ export default definePlugin({
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
find: ".carouselModal,",
|
find: ".carouselModal,",
|
||||||
replacement: {
|
replacement: {
|
||||||
|
5
src/plugins/messageClickActions/README.md
Normal file
5
src/plugins/messageClickActions/README.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
# MessageClickActions
|
||||||
|
|
||||||
|
Allows you to double click to edit/reply to a message or delete it if you hold the backspace key
|
||||||
|
|
||||||
|
![](https://github.com/Vendicated/Vencord/assets/55940580/6885aca2-4021-4910-b636-bb40f877a816)
|
@ -21,18 +21,17 @@ import { definePluginSettings, Settings } from "@api/Settings";
|
|||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { FluxDispatcher, PermissionStore, UserStore } from "@webpack/common";
|
import { FluxDispatcher, PermissionsBits, PermissionStore, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
let isDeletePressed = false;
|
let isDeletePressed = false;
|
||||||
const keydown = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed = true);
|
const keydown = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed = true);
|
||||||
const keyup = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed = false);
|
const keyup = (e: KeyboardEvent) => e.key === "Backspace" && (isDeletePressed = false);
|
||||||
|
|
||||||
const MANAGE_CHANNELS = 1n << 4n;
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
enableDeleteOnClick: {
|
enableDeleteOnClick: {
|
||||||
type: OptionType.BOOLEAN,
|
type: OptionType.BOOLEAN,
|
||||||
description: "Enable delete on click",
|
description: "Enable delete on click while holding backspace",
|
||||||
default: true
|
default: true
|
||||||
},
|
},
|
||||||
enableDoubleClickToEdit: {
|
enableDoubleClickToEdit: {
|
||||||
@ -72,6 +71,7 @@ export default definePlugin({
|
|||||||
if (!isDeletePressed) {
|
if (!isDeletePressed) {
|
||||||
if (event.detail < 2) return;
|
if (event.detail < 2) return;
|
||||||
if (settings.store.requireModifier && !event.ctrlKey && !event.shiftKey) return;
|
if (settings.store.requireModifier && !event.ctrlKey && !event.shiftKey) return;
|
||||||
|
if (channel.guild_id && !PermissionStore.can(PermissionsBits.SEND_MESSAGES, channel)) return;
|
||||||
|
|
||||||
if (isMe) {
|
if (isMe) {
|
||||||
if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id)) return;
|
if (!settings.store.enableDoubleClickToEdit || EditStore.isEditing(channel.id, msg.id)) return;
|
||||||
@ -89,7 +89,7 @@ export default definePlugin({
|
|||||||
showMentionToggle: channel.guild_id !== null
|
showMentionToggle: channel.guild_id !== null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(MANAGE_CHANNELS, channel))) {
|
} else if (settings.store.enableDeleteOnClick && (isMe || PermissionStore.can(PermissionsBits.MANAGE_MESSAGES, channel))) {
|
||||||
if (msg.deleted) {
|
if (msg.deleted) {
|
||||||
FluxDispatcher.dispatch({
|
FluxDispatcher.dispatch({
|
||||||
type: "MESSAGE_DELETE",
|
type: "MESSAGE_DELETE",
|
@ -53,8 +53,6 @@ interface TagSettings {
|
|||||||
[k: string]: TagSetting;
|
[k: string]: TagSetting;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CLYDE_ID = "1081004946872352958";
|
|
||||||
|
|
||||||
// PermissionStore.computePermissions is not the same function and doesn't work here
|
// PermissionStore.computePermissions is not the same function and doesn't work here
|
||||||
const PermissionUtil = findByPropsLazy("computePermissions", "canEveryoneRole") as {
|
const PermissionUtil = findByPropsLazy("computePermissions", "canEveryoneRole") as {
|
||||||
computePermissions({ ...args }): bigint;
|
computePermissions({ ...args }): bigint;
|
||||||
@ -215,7 +213,7 @@ export default definePlugin({
|
|||||||
},
|
},
|
||||||
// add HTML data attributes (for easier theming)
|
// add HTML data attributes (for easier theming)
|
||||||
{
|
{
|
||||||
match: /children:\[(?=\i,\(0,\i\.jsx\)\("span",{className:\i\(\)\.botText,children:(\i)}\)\])/,
|
match: /children:\[(?=\i\?null:\i,\i,\(0,\i\.jsx\)\("span",{className:\i\(\)\.botText,children:(\i)}\)\])/,
|
||||||
replace: "'data-tag':$1.toLowerCase(),children:["
|
replace: "'data-tag':$1.toLowerCase(),children:["
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -230,10 +228,10 @@ export default definePlugin({
|
|||||||
},
|
},
|
||||||
// in the member list
|
// in the member list
|
||||||
{
|
{
|
||||||
find: ".renderBot=function(){",
|
find: ".Messages.GUILD_OWNER,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.BOT;return null!=(\i)&&.{0,10}\?(.{0,50})\.botTag,type:\i/,
|
match: /(?<type>\i)=\(null==.{0,50}\.BOT,null!=(?<user>\i)&&\i\.bot/,
|
||||||
replace: ".BOT;var type=$self.getTag({...this.props,origType:$1.bot?0:null,location:'not-chat'});return type!==null?$2.botTag,type"
|
replace: "$<type> = $self.getTag({user: $<user>, channel: arguments[0].channel, origType: $<user>.bot ? 0 : null, location: 'not-chat' }), typeof $<type> === 'number'"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// pass channel id down props to be used in profiles
|
// pass channel id down props to be used in profiles
|
||||||
@ -253,7 +251,7 @@ export default definePlugin({
|
|||||||
},
|
},
|
||||||
// in profiles
|
// in profiles
|
||||||
{
|
{
|
||||||
find: ",botType:",
|
find: "showStreamerModeTooltip:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /,botType:(\i\((\i)\)),/g,
|
match: /,botType:(\i\((\i)\)),/g,
|
||||||
replace: ",botType:$self.getTag({user:$2,channelId:arguments[0].moreTags_channelId,origType:$1,location:'not-chat'}),"
|
replace: ",botType:$self.getTag({user:$2,channelId:arguments[0].moreTags_channelId,origType:$1,location:'not-chat'}),"
|
||||||
@ -341,15 +339,17 @@ export default definePlugin({
|
|||||||
message, user, channelId, origType, location, channel
|
message, user, channelId, origType, location, channel
|
||||||
}: {
|
}: {
|
||||||
message?: Message,
|
message?: Message,
|
||||||
user: User,
|
user: User & { isClyde(): boolean; },
|
||||||
channel?: Channel & { isForumPost(): boolean; },
|
channel?: Channel & { isForumPost(): boolean; },
|
||||||
channelId?: string;
|
channelId?: string;
|
||||||
origType?: number;
|
origType?: number;
|
||||||
location: "chat" | "not-chat";
|
location: "chat" | "not-chat";
|
||||||
}): number | null {
|
}): number | null {
|
||||||
|
if (!user)
|
||||||
|
return null;
|
||||||
if (location === "chat" && user.id === "1")
|
if (location === "chat" && user.id === "1")
|
||||||
return Tag.Types.OFFICIAL;
|
return Tag.Types.OFFICIAL;
|
||||||
if (user.id === CLYDE_ID)
|
if (user.isClyde())
|
||||||
return Tag.Types.AI;
|
return Tag.Types.AI;
|
||||||
|
|
||||||
let type = typeof origType === "number" ? origType : null;
|
let type = typeof origType === "number" ? origType : null;
|
41
src/plugins/noMosaic/index.ts
Normal file
41
src/plugins/noMosaic/index.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { disableStyle, enableStyle } from "@api/Styles";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
import style from "./styles.css?managed";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "NoMosaic",
|
||||||
|
authors: [Devs.AutumnVN],
|
||||||
|
description: "Removes Discord new image mosaic",
|
||||||
|
tags: ["image", "mosaic", "media"],
|
||||||
|
patches: [{
|
||||||
|
find: "Media Mosaic",
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /mediaLayoutType:\i\.\i\.MOSAIC/,
|
||||||
|
replace: 'mediaLayoutType:"RESPONSIVE"',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /\i===\i\.\i\.MOSAIC/,
|
||||||
|
replace: "true",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /null!==\(\i=\i\.get\(\i\)\)&&void 0!==\i\?\i:"INVALID"/,
|
||||||
|
replace: '"INVALID"',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
start() {
|
||||||
|
enableStyle(style);
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
disableStyle(style);
|
||||||
|
}
|
||||||
|
});
|
3
src/plugins/noMosaic/styles.css
Normal file
3
src/plugins/noMosaic/styles.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[class^="nonMediaAttachmentsContainer-"] [class*="messageAttachment-"] {
|
||||||
|
position: relative;
|
||||||
|
}
|
@ -84,8 +84,8 @@ export default definePlugin({
|
|||||||
find: "showProgressBadge:",
|
find: "showProgressBadge:",
|
||||||
predicate: () => settings.store.hidePremiumOffersCount,
|
predicate: () => settings.store.hidePremiumOffersCount,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\(function\(\){return \i\.\i\.getUnacknowledgedOffers\(\i\)\.length}\)/,
|
match: /=\i\.unviewedTrialCount\+\i\.unviewedDiscountCount/,
|
||||||
replace: "(function(){return 0})"
|
replace: "=0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
21
src/plugins/noTypingAnimation/index.ts
Normal file
21
src/plugins/noTypingAnimation/index.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
7
src/plugins/onePingPerDM/README.md
Normal file
7
src/plugins/onePingPerDM/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# OnePingPerDM
|
||||||
|
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
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
- Prevents ping audio spam in DMs
|
||||||
|
- Be able to distinguish more than one ping as multiple users
|
||||||
|
- Be less annoyed while gaming
|
71
src/plugins/onePingPerDM/index.ts
Normal file
71
src/plugins/onePingPerDM/index.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* 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 { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { ChannelStore, ReadStateStore, UserStore } from "@webpack/common";
|
||||||
|
import { MessageJSON } 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: /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:)/,
|
||||||
|
replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1"
|
||||||
|
}]
|
||||||
|
}],
|
||||||
|
isPrivateChannelRead(message: MessageJSON) {
|
||||||
|
const channelType = ChannelStore.getChannel(message.channel_id)?.type;
|
||||||
|
if (channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
(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;
|
||||||
|
}
|
||||||
|
return ReadStateStore.getOldestUnreadMessageId(message.channel_id) === message.id;
|
||||||
|
},
|
||||||
|
});
|
@ -19,10 +19,7 @@
|
|||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findStoreLazy } from "@webpack";
|
import { FluxDispatcher } from "@webpack/common";
|
||||||
import { GenericStore } from "@webpack/common";
|
|
||||||
|
|
||||||
const PoggerModeSettingsStore: GenericStore = findStoreLazy("PoggermodeSettingsStore");
|
|
||||||
|
|
||||||
const enum Intensity {
|
const enum Intensity {
|
||||||
Normal,
|
Normal,
|
||||||
@ -61,9 +58,12 @@ export default definePlugin({
|
|||||||
});
|
});
|
||||||
|
|
||||||
function setPoggerState(state: boolean) {
|
function setPoggerState(state: boolean) {
|
||||||
Object.assign(PoggerModeSettingsStore.__getLocalVars().state, {
|
FluxDispatcher.dispatch({
|
||||||
enabled: state,
|
type: "POGGERMODE_SETTINGS_UPDATE",
|
||||||
settingsVisible: state
|
settings: {
|
||||||
|
enabled: state,
|
||||||
|
settingsVisible: state
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,5 +101,8 @@ function setSettings(intensity: Intensity) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(PoggerModeSettingsStore.__getLocalVars().state, state);
|
FluxDispatcher.dispatch({
|
||||||
|
type: "POGGERMODE_SETTINGS_UPDATE",
|
||||||
|
settings: state
|
||||||
|
});
|
||||||
}
|
}
|
9
src/plugins/permissionFreeWill/README.md
Normal file
9
src/plugins/permissionFreeWill/README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# PermissionFreeWill
|
||||||
|
|
||||||
|
Removes the client-side restrictions that prevent editing channel permissions, such as permission lockouts ("Pretty sure
|
||||||
|
you don't want to do this") and onboarding requirements ("Making this change will make your server incompatible [...]")
|
||||||
|
|
||||||
|
## Warning
|
||||||
|
|
||||||
|
This plugin will let you create permissions in servers that **WILL** lock you out of channels until an administrator
|
||||||
|
can resolve it for you. Please be careful with the overwrites you are making and check carefully.
|
56
src/plugins/permissionFreeWill/index.ts
Normal file
56
src/plugins/permissionFreeWill/index.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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 { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
lockout: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
description: 'Bypass the permission lockout prevention ("Pretty sure you don\'t want to do this")',
|
||||||
|
restartNeeded: true
|
||||||
|
},
|
||||||
|
onboarding: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
description: 'Bypass the onboarding requirements ("Making this change will make your server incompatible [...]")',
|
||||||
|
restartNeeded: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "PermissionFreeWill",
|
||||||
|
description: "Disables the client-side restrictions for channel permission management.",
|
||||||
|
authors: [Devs.lewisakura],
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
// Permission lockout, just set the check to true
|
||||||
|
{
|
||||||
|
find: "Messages.SELF_DENY_PERMISSION_BODY",
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /case"DENY":.{0,50}if\((?=\i\.\i\.can)/,
|
||||||
|
replace: "$&true||"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
predicate: () => settings.store.lockout
|
||||||
|
},
|
||||||
|
// Onboarding, same thing but we need to prevent the check
|
||||||
|
{
|
||||||
|
find: "Messages.ONBOARDING_CHANNEL_THRESHOLD_WARNING",
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /case 1:if\((?=!\i\.sent.{20,30}Messages\.CANNOT_CHANGE_CHANNEL_PERMS)/,
|
||||||
|
replace: "$&false&&"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
predicate: () => settings.store.onboarding
|
||||||
|
}
|
||||||
|
],
|
||||||
|
settings
|
||||||
|
});
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user