Compare commits
2 commits
main
...
mangled-co
Author | SHA1 | Date | |
---|---|---|---|
426c949ee4 | |||
a01ee40591 |
98
.eslintrc.json
Normal file
98
.eslintrc.json
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"ignorePatterns": ["dist", "browser", "packages/vencord-types"],
|
||||||
|
"plugins": [
|
||||||
|
"@typescript-eslint",
|
||||||
|
"simple-header",
|
||||||
|
"simple-import-sort",
|
||||||
|
"unused-imports",
|
||||||
|
"path-alias"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"import/resolver": {
|
||||||
|
"alias": {
|
||||||
|
"map": [
|
||||||
|
["@webpack", "./src/webpack"],
|
||||||
|
["@webpack/common", "./src/webpack/common"],
|
||||||
|
["@utils", "./src/utils"],
|
||||||
|
["@api", "./src/api"],
|
||||||
|
["@components", "./src/components"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
// Since it's only been a month and Vencord has already been stolen
|
||||||
|
// by random skids who rebranded it to "AlphaCord" and erased all license
|
||||||
|
// information
|
||||||
|
"simple-header/header": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"files": ["scripts/header-new.txt", "scripts/header-old.txt"],
|
||||||
|
"templates": { "author": [".*", "Vendicated and contributors"] }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"quotes": ["error", "double", { "avoidEscape": true }],
|
||||||
|
"jsx-quotes": ["error", "prefer-double"],
|
||||||
|
"no-mixed-spaces-and-tabs": "error",
|
||||||
|
"indent": ["error", 4, { "SwitchCase": 1 }],
|
||||||
|
"arrow-parens": ["error", "as-needed"],
|
||||||
|
"eol-last": ["error", "always"],
|
||||||
|
"@typescript-eslint/func-call-spacing": ["error", "never"],
|
||||||
|
"no-multi-spaces": "error",
|
||||||
|
"no-trailing-spaces": "error",
|
||||||
|
"no-whitespace-before-property": "error",
|
||||||
|
"semi": ["error", "always"],
|
||||||
|
"semi-style": ["error", "last"],
|
||||||
|
"space-in-parens": ["error", "never"],
|
||||||
|
"block-spacing": ["error", "always"],
|
||||||
|
"object-curly-spacing": ["error", "always"],
|
||||||
|
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
||||||
|
"spaced-comment": ["error", "always", { "markers": ["!"] }],
|
||||||
|
"yoda": "error",
|
||||||
|
"prefer-destructuring": ["error", {
|
||||||
|
"VariableDeclarator": { "array": false, "object": true },
|
||||||
|
"AssignmentExpression": { "array": false, "object": false }
|
||||||
|
}],
|
||||||
|
"operator-assignment": ["error", "always"],
|
||||||
|
"no-useless-computed-key": "error",
|
||||||
|
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
|
||||||
|
"no-invalid-regexp": "error",
|
||||||
|
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||||
|
"no-duplicate-imports": "error",
|
||||||
|
"no-extra-semi": "error",
|
||||||
|
"dot-notation": "error",
|
||||||
|
"no-useless-escape": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"extra": "i"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"no-fallthrough": "error",
|
||||||
|
"for-direction": "error",
|
||||||
|
"no-async-promise-executor": "error",
|
||||||
|
"no-cond-assign": "error",
|
||||||
|
"no-dupe-else-if": "error",
|
||||||
|
"no-duplicate-case": "error",
|
||||||
|
"no-irregular-whitespace": "error",
|
||||||
|
"no-loss-of-precision": "error",
|
||||||
|
"no-misleading-character-class": "error",
|
||||||
|
"no-prototype-builtins": "error",
|
||||||
|
"no-regex-spaces": "error",
|
||||||
|
"no-shadow-restricted-names": "error",
|
||||||
|
"no-unexpected-multiline": "error",
|
||||||
|
"no-unsafe-optional-chaining": "error",
|
||||||
|
"no-useless-backreference": "error",
|
||||||
|
"use-isnan": "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"prefer-spread": "error",
|
||||||
|
|
||||||
|
"simple-import-sort/imports": "error",
|
||||||
|
"simple-import-sort/exports": "error",
|
||||||
|
|
||||||
|
"unused-imports/no-unused-imports": "error",
|
||||||
|
|
||||||
|
"path-alias/no-relative": "error"
|
||||||
|
}
|
||||||
|
}
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -18,5 +18,7 @@ lerna-debug.log*
|
||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
src/userplugins
|
||||||
|
|
||||||
ExtensionCache/
|
ExtensionCache/
|
||||||
settings/
|
settings/
|
||||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,3 +0,0 @@
|
||||||
[submodule "src/userplugins/vc-message-logger-enhanced"]
|
|
||||||
path = src/userplugins/vc-message-logger-enhanced
|
|
||||||
url = https://github.com/Syncxv/vc-message-logger-enhanced.git
|
|
|
@ -1,11 +1,6 @@
|
||||||
{
|
{
|
||||||
"extends": "stylelint-config-standard",
|
"extends": "stylelint-config-standard",
|
||||||
"rules": {
|
"rules": {
|
||||||
"selector-class-pattern": [
|
"indentation": 4
|
||||||
"^[a-z][a-zA-Z0-9]*(-[a-z0-9][a-zA-Z0-9]*)*$",
|
|
||||||
{
|
|
||||||
"message": "Expected class selector to be kebab-case with camelCase segments"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -14,6 +14,8 @@
|
||||||
"typescript.preferences.quoteStyle": "double",
|
"typescript.preferences.quoteStyle": "double",
|
||||||
"javascript.preferences.quoteStyle": "double",
|
"javascript.preferences.quoteStyle": "double",
|
||||||
|
|
||||||
|
"eslint.experimental.useFlatConfig": false,
|
||||||
|
|
||||||
"gitlens.remotes": [
|
"gitlens.remotes": [
|
||||||
{
|
{
|
||||||
"domain": "codeberg.org",
|
"domain": "codeberg.org",
|
||||||
|
|
|
@ -16,6 +16,5 @@ DON'T
|
||||||
|
|
||||||
Repetitive violations of these guidelines might get your access to the repository restricted.
|
Repetitive violations of these guidelines might get your access to the repository restricted.
|
||||||
|
|
||||||
If you feel like a user is violating these guidelines or feel treated unfairly, please refrain from vigilantism
|
|
||||||
and instead report the issue to a moderator! The best way is joining our [official Discord community](https://vencord.dev/discord)
|
If you feel like a user is violating these guidelines or feel treated unfairly, please refrain from publicly challenging them and instead contact a Moderator on our Discord server or send an email to vendicated+conduct@riseup.net!
|
||||||
and opening a modmail ticket.
|
|
||||||
|
|
|
@ -1,55 +1,82 @@
|
||||||
# Contributing to Vencord
|
# Contribution Guide
|
||||||
|
|
||||||
Vencord is a community project and welcomes any kind of contribution from anyone!
|
First of all, thank you for contributing! :3
|
||||||
|
|
||||||
We have development documentation for new contributors, which can be found at <https://docs.vencord.dev>.
|
To ensure your contribution is robust, please follow the below guide!
|
||||||
|
|
||||||
All contributions should be made in accordance with our [Code of Conduct](./CODE_OF_CONDUCT.md).
|
For a friendly introduction to plugins, see [Megu's Plugin Guide!](docs/2_PLUGINS.md)
|
||||||
|
|
||||||
## How to contribute
|
## Style Guide
|
||||||
|
|
||||||
Contributions can be sent via pull requests. If you're new to Git, check [this guide](https://opensource.com/article/19/7/create-pull-request-github).
|
- This project has a very minimal .editorconfig. Make sure your editor supports this!
|
||||||
|
If you are using VSCode, it should automatically recommend you the extension; If not,
|
||||||
|
please install the Editorconfig extension
|
||||||
|
- Try to follow the formatting in the rest of the project and stay consistent
|
||||||
|
- Follow the file naming convention. File names should usually be camelCase, unless they export a Class
|
||||||
|
or React Component, in which case they should be PascalCase
|
||||||
|
|
||||||
Pull requests can be made either to the `main` or the `dev` branch. However, unless you're an advanced user, I recommend sticking to `main`. This is because the dev branch might contain unstable changes and be force pushed frequently, which could cause conflicts in your pull request.
|
## Contributing a Plugin
|
||||||
|
|
||||||
## Write a plugin
|
Because plugins modify code directly, incompatibilities are a problem.
|
||||||
|
|
||||||
Writing a plugin is the primary way to contribute.
|
Thus, 3rd party plugins are not supported, instead all plugins are part of Vencord itself.
|
||||||
|
This way we can ensure compatibility and high quality patches.
|
||||||
|
|
||||||
Before starting your plugin:
|
Follow the below guide to make your first plugin!
|
||||||
- Check existing pull requests to see if someone is already working on a similar plugin
|
|
||||||
- Check our [plugin requests tracker](https://github.com/Vencord/plugin-requests/issues) to see if there is an existing request, or if the same idea has been rejected
|
|
||||||
- If there isn't an existing request, [open one](https://github.com/Vencord/plugin-requests/issues/new?assignees=&labels=&projects=&template=request.yml) yourself
|
|
||||||
and include that you'd like to work on this yourself. Then wait for feedback to see if the idea even has any chance of being accepted. Or maybe others have some ideas to improve it!
|
|
||||||
- Familarise yourself with our plugin rules below to ensure your plugin is not banned
|
|
||||||
|
|
||||||
### Plugin Rules
|
### Finding the right module to patch
|
||||||
|
|
||||||
- No simple slash command plugins like `/cat`. Instead, make a [user installable Discord bot](https://discord.com/developers/docs/change-log#userinstallable-apps-preview)
|
If the thing you want to patch is an action performed when interacting with a part of the UI, use React DevTools.
|
||||||
- No simple text replace plugins like Let me Google that for you. The TextReplace plugin can do this
|
They come preinstalled and can be found as the "Components" tab in DevTools.
|
||||||
- No raw DOM manipulation. Use proper patches and React
|
Use the Selector (top left) to select the UI Element. Now you can see all callbacks, props or jump to the source
|
||||||
- No FakeDeafen or FakeMute
|
directly.
|
||||||
- No StereoMic
|
|
||||||
- No plugins that simply hide or redesign ui elements. This can be done with CSS
|
|
||||||
- No selfbots or API spam (animated status, message pruner, auto reply, nitro snipers, etc)
|
|
||||||
- No untrusted third party APIs. Popular services like Google or GitHub are fine, but absolutely no self hosted ones
|
|
||||||
- No plugins that require the user to enter their own API key
|
|
||||||
- Do not introduce new dependencies unless absolutely necessary and warranted
|
|
||||||
|
|
||||||
## Improve Vencord itself
|
If it is anything else, or you're too lazy to use React DevTools, hit `CTRL + Shift + F` while in DevTools and
|
||||||
|
enter a search term, for example "getUser" to search all source files.
|
||||||
|
Look at the results until you find something promising. Set a breakpoint and trigger the execution of that part of Code to inspect arguments, locals, etc...
|
||||||
|
|
||||||
If you have any ideas on how to improve Vencord itself, or want to propose a new plugin API, feel free to open a feature request so we can discuss.
|
### Writing a robust patch
|
||||||
|
|
||||||
Or if you notice any bugs or typos, feel free to fix them!
|
##### "find"
|
||||||
|
|
||||||
## Contribute to our Documentation
|
First you need to find a good `find` value. This should be a string that is unique to your module.
|
||||||
|
If you want to patch the `getUser` function, usually a good first try is `getUser:` or `function getUser()`,
|
||||||
|
depending on how the module is structured. Again, make sure this string is unique to your module and is not
|
||||||
|
found in any other module. To verify this, search for it in all bundles (CTRL + Shift + F)
|
||||||
|
|
||||||
The source code of our documentation is available at <https://github.com/Vencord/Docs>
|
##### "match"
|
||||||
|
|
||||||
If you see anything outdated, incorrect or lacking, please fix it!
|
This is the regex that will operate on the module found with "find". Just like in find, you should make sure
|
||||||
If you think a new page should be added, feel free to suggest it via an issue and we can discuss.
|
this only matches exactly the part you want to patch and no other parts in the file.
|
||||||
|
|
||||||
## Help out users in our Discord community
|
The easiest way to write and test your regex is the following:
|
||||||
|
|
||||||
We have an open support channel in our [Discord community](https://vencord.dev/discord).
|
- Get the ID of the module you want to patch. To do this, go to it in the sources tab and scroll up until you
|
||||||
Helping out users there is always appreciated! The more, the merrier.
|
see something like `447887: (e,t,n)=>{` (Obviously the number will differ).
|
||||||
|
- Now paste the following into the console: `Vencord.Webpack.wreq.m[447887].toString()` (Changing the number to your ID)
|
||||||
|
- Now either test regexes on this string in the console or use a tool like https://regex101.com
|
||||||
|
|
||||||
|
Also pay attention to the following:
|
||||||
|
|
||||||
|
- Never hardcode variable or parameter names or any other minified names. They will change in the future. The only Exception to this rule
|
||||||
|
are the react props parameter which seems to always be `e`, but even then only rely on this if it is necessary.
|
||||||
|
Instead, use one of the following approaches where applicable:
|
||||||
|
- Match 1 or 2 of any character: `.{1,2}`, for example to match the variable name in `var a=b`, `var (.{1,2})=`
|
||||||
|
- Match any but a guaranteed terminating character: `[^;]+`, for example to match the entire assigned value in `var a=b||c||func();`,
|
||||||
|
`var .{1,2}=([^;]+);`
|
||||||
|
- If you don't care about that part, just match a bunch of chars: `.{0,50}`, for example to extract the variable "b" in `createElement("div",{a:"foo",c:"bar"},b)`, `createElement\("div".{0,30},(.{1,2})\),`. Note the `.{0,30}`, this is essentially the same as `.+`, but safer as you can't end up accidently eating thousands of characters
|
||||||
|
- Additionally, as you might have noticed, all of the above approaches use regex groups (`(...)`) to capture the variable name. You can then use those groups in your replacement to access those variables dynamically
|
||||||
|
|
||||||
|
#### "replace"
|
||||||
|
|
||||||
|
This is the replacement for the match. This is the second argument to [String.replace](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace), so refer to those docs for info.
|
||||||
|
|
||||||
|
Never hardcode minified variable or parameter names here. Instead, use capture groups in your regex to capture the variable names
|
||||||
|
and use those in your replacement
|
||||||
|
|
||||||
|
Make sure your replacement does not introduce any whitespace. While this might seem weird, random whitespace may mess up other patches.
|
||||||
|
This includes spaces, tabs and especially newlines
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
And that's it! Now open a Pull Request with your Plugin
|
||||||
|
|
|
@ -2,22 +2,23 @@ if (typeof browser === "undefined") {
|
||||||
var browser = chrome;
|
var browser = chrome;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const script = document.createElement("script");
|
||||||
|
script.src = browser.runtime.getURL("dist/Vencord.js");
|
||||||
|
script.id = "vencord-script";
|
||||||
|
Object.assign(script.dataset, {
|
||||||
|
extensionBaseUrl: browser.runtime.getURL(""),
|
||||||
|
version: browser.runtime.getManifest().version
|
||||||
|
});
|
||||||
|
|
||||||
const style = document.createElement("link");
|
const style = document.createElement("link");
|
||||||
style.type = "text/css";
|
style.type = "text/css";
|
||||||
style.rel = "stylesheet";
|
style.rel = "stylesheet";
|
||||||
style.href = browser.runtime.getURL("dist/Vencord.css");
|
style.href = browser.runtime.getURL("dist/Vencord.css");
|
||||||
|
|
||||||
|
document.documentElement.append(script);
|
||||||
|
|
||||||
document.addEventListener(
|
document.addEventListener(
|
||||||
"DOMContentLoaded",
|
"DOMContentLoaded",
|
||||||
() => {
|
() => document.documentElement.append(style),
|
||||||
document.documentElement.append(style);
|
|
||||||
window.postMessage({
|
|
||||||
type: "vencord:meta",
|
|
||||||
meta: {
|
|
||||||
EXTENSION_VERSION: browser.runtime.getManifest().version,
|
|
||||||
EXTENSION_BASE_URL: browser.runtime.getURL(""),
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{ once: true }
|
{ once: true }
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"minimum_chrome_version": "111",
|
"minimum_chrome_version": "91",
|
||||||
|
|
||||||
"name": "Vencord Web",
|
"name": "Vencord Web",
|
||||||
"description": "The cutest Discord mod now in your browser",
|
"description": "The cutest Discord mod now in your browser",
|
||||||
|
@ -22,15 +22,7 @@
|
||||||
"run_at": "document_start",
|
"run_at": "document_start",
|
||||||
"matches": ["*://*.discord.com/*"],
|
"matches": ["*://*.discord.com/*"],
|
||||||
"js": ["content.js"],
|
"js": ["content.js"],
|
||||||
"all_frames": true,
|
"all_frames": true
|
||||||
"world": "ISOLATED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"run_at": "document_start",
|
|
||||||
"matches": ["*://*.discord.com/*"],
|
|
||||||
"js": ["dist/Vencord.js"],
|
|
||||||
"all_frames": true,
|
|
||||||
"world": "MAIN"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -22,15 +22,7 @@
|
||||||
"run_at": "document_start",
|
"run_at": "document_start",
|
||||||
"matches": ["*://*.discord.com/*"],
|
"matches": ["*://*.discord.com/*"],
|
||||||
"js": ["content.js"],
|
"js": ["content.js"],
|
||||||
"all_frames": true,
|
"all_frames": true
|
||||||
"world": "ISOLATED"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"run_at": "document_start",
|
|
||||||
"matches": ["*://*.discord.com/*"],
|
|
||||||
"js": ["dist/Vencord.js"],
|
|
||||||
"all_frames": true,
|
|
||||||
"world": "MAIN"
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -43,7 +35,7 @@
|
||||||
"browser_specific_settings": {
|
"browser_specific_settings": {
|
||||||
"gecko": {
|
"gecko": {
|
||||||
"id": "vencord-firefox@vendicated.dev",
|
"id": "vencord-firefox@vendicated.dev",
|
||||||
"strict_min_version": "128.0"
|
"strict_min_version": "91.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
// @author Vendicated (https://github.com/Vendicated)
|
// @author Vendicated (https://github.com/Vendicated)
|
||||||
// @namespace https://github.com/Vendicated/Vencord
|
// @namespace https://github.com/Vendicated/Vencord
|
||||||
// @supportURL https://github.com/Vendicated/Vencord
|
// @supportURL https://github.com/Vendicated/Vencord
|
||||||
// @icon https://raw.githubusercontent.com/Vendicated/Vencord/refs/heads/main/browser/icon.png
|
|
||||||
// @license GPL-3.0
|
// @license GPL-3.0
|
||||||
// @match *://*.discord.com/*
|
// @match *://*.discord.com/*
|
||||||
// @grant GM_xmlhttpRequest
|
// @grant GM_xmlhttpRequest
|
||||||
|
|
97
docs/1_INSTALLING.md
Normal file
97
docs/1_INSTALLING.md
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
> [!WARNING]
|
||||||
|
> These instructions are only for advanced users. If you're not a Developer, you should use our [graphical installer](https://github.com/Vendicated/VencordInstaller#usage) instead.
|
||||||
|
> No support will be provided for installing in this fashion. If you cannot figure it out, you should just stick to a regular install.
|
||||||
|
|
||||||
|
# Installation Guide
|
||||||
|
|
||||||
|
Welcome to Megu's Installation Guide! In this file, you will learn about how to download, install, and uninstall Vencord!
|
||||||
|
|
||||||
|
## Sections
|
||||||
|
|
||||||
|
- [Installation Guide](#installation-guide)
|
||||||
|
- [Sections](#sections)
|
||||||
|
- [Dependencies](#dependencies)
|
||||||
|
- [Installing Vencord](#installing-vencord)
|
||||||
|
- [Updating Vencord](#updating-vencord)
|
||||||
|
- [Uninstalling Vencord](#uninstalling-vencord)
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Install Git from https://git-scm.com/download
|
||||||
|
- Install Node.JS LTS from here: https://nodejs.dev/en/
|
||||||
|
|
||||||
|
## Installing Vencord
|
||||||
|
|
||||||
|
Install `pnpm`:
|
||||||
|
|
||||||
|
> :exclamation: This next command may need to be run as admin/root depending on your system, and you may need to close and reopen your terminal for pnpm to be in your PATH.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm i -g pnpm
|
||||||
|
```
|
||||||
|
|
||||||
|
> :exclamation: **IMPORTANT** Make sure you aren't using an admin/root terminal from here onwards. It **will** mess up your Discord/Vencord instance and you **will** most likely have to reinstall.
|
||||||
|
|
||||||
|
Clone Vencord:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git clone https://github.com/Vendicated/Vencord
|
||||||
|
cd Vencord
|
||||||
|
```
|
||||||
|
|
||||||
|
Install dependencies:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
```
|
||||||
|
|
||||||
|
Build Vencord:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
Inject vencord into your client:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pnpm inject
|
||||||
|
```
|
||||||
|
|
||||||
|
Then fully close Discord from your taskbar or task manager, and restart it. Vencord should be injected - you can check this by looking for the Vencord section in Discord settings.
|
||||||
|
|
||||||
|
## Updating Vencord
|
||||||
|
|
||||||
|
If you're using Discord already, go into the `Updater` tab in settings.
|
||||||
|
|
||||||
|
Sometimes it may be necessary to manually update if the GUI updater fails.
|
||||||
|
|
||||||
|
To pull latest changes:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
If this fails, you likely need to reset your local changes to vencord to resolve merge errors:
|
||||||
|
|
||||||
|
> :exclamation: This command will remove any local changes you've made to vencord. Make sure you back up if you made any code changes you don't want to lose!
|
||||||
|
|
||||||
|
```shell
|
||||||
|
git reset --hard
|
||||||
|
git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
and then to build the changes:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
Then just refresh your client
|
||||||
|
|
||||||
|
## Uninstalling Vencord
|
||||||
|
|
||||||
|
Simply run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pnpm uninject
|
||||||
|
```
|
111
docs/2_PLUGINS.md
Normal file
111
docs/2_PLUGINS.md
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
# Plugins Guide
|
||||||
|
|
||||||
|
Welcome to Megu's Plugin Guide! In this file, you will learn about how to write your own plugin!
|
||||||
|
|
||||||
|
You don't need to run `pnpm build` every time you make a change. Instead, use `pnpm watch` - this will auto-compile Vencord whenever you make a change. If using code patches (recommended), you will need to CTRL+R to load the changes.
|
||||||
|
|
||||||
|
## Plugin Entrypoint
|
||||||
|
|
||||||
|
> If it doesn't already exist, create a folder called `userplugins` in the `src` directory of this repo.
|
||||||
|
|
||||||
|
1. Create a folder in `src/userplugins/` with the name of your plugin. For example, `src/userplugins/epicPlugin/` - All of your plugin files will go here.
|
||||||
|
|
||||||
|
2. Create a file in that folder called `index.ts`
|
||||||
|
|
||||||
|
3. In `index.ts`, copy-paste the following template code:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "Epic Plugin",
|
||||||
|
description: "This plugin is absolutely epic",
|
||||||
|
authors: [
|
||||||
|
{
|
||||||
|
id: 12345n,
|
||||||
|
name: "Your Name",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
patches: [],
|
||||||
|
// Delete these two below if you are only using code patches
|
||||||
|
start() {},
|
||||||
|
stop() {},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Change the name, description, and authors to your own information.
|
||||||
|
|
||||||
|
Replace `12345n` with your user ID ending in `n` (e.g., `545581357812678656n`). If you don't want to share your Discord account, use `0n` instead!
|
||||||
|
|
||||||
|
## How Plugins Work In Vencord
|
||||||
|
|
||||||
|
Vencord uses a different way of making mods than you're used to.
|
||||||
|
Instead of monkeypatching webpack, we directly modify the code before Discord loads it.
|
||||||
|
|
||||||
|
This is _significantly_ more efficient than monkeypatching webpack, and is surprisingly easy, but it may be confusing at first.
|
||||||
|
|
||||||
|
## Making your patch
|
||||||
|
|
||||||
|
For an in-depth guide into patching code, see [CONTRIBUTING.md](../CONTRIBUTING.md)
|
||||||
|
|
||||||
|
in the `index.ts` file we made earlier, you'll see a `patches` array.
|
||||||
|
|
||||||
|
> You'll see examples of how patches are used in all the existing plugins, and it'll be easier to understand by looking at those examples, so do that first, and then return here!
|
||||||
|
|
||||||
|
> For a good example of a plugin using code patches AND runtime patching, check `src/plugins/unindent.ts`, which uses code patches to run custom runtime code.
|
||||||
|
|
||||||
|
One of the patches in the `isStaff` plugin, looks like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
match: /(\w+)\.isStaff=function\(\){return\s*!1};/,
|
||||||
|
replace: "$1.isStaff=function(){return true};",
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
The above regex matches the string in discord that will look something like:
|
||||||
|
|
||||||
|
```js
|
||||||
|
abc.isStaff = function () {
|
||||||
|
return !1;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Remember that Discord code is minified, so there won't be any newlines, and there will only be spaces where necessary. So the source code looks something like:
|
||||||
|
|
||||||
|
```
|
||||||
|
abc.isStaff=function(){return!1;}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can find these snippets by opening the devtools (`ctrl+shift+i`) and pressing `ctrl+shift+f`, searching for what you're looking to modify in there, and beautifying the file to make it more readable.
|
||||||
|
|
||||||
|
In the `match` regex in the example shown above, you'll notice at the start there is a `(\w+)`.
|
||||||
|
Anything in the brackets will be accessible in the `replace` string using `$<number>`. e.g., the first pair of brackets will be `$1`, the second will be `$2`, etc.
|
||||||
|
|
||||||
|
The replacement string we used is:
|
||||||
|
|
||||||
|
```
|
||||||
|
"$1.isStaff=function(){return true;};"
|
||||||
|
```
|
||||||
|
|
||||||
|
Which, using the above example, would replace the code with:
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> In this example, `$1` becomes `abc`
|
||||||
|
|
||||||
|
```js
|
||||||
|
abc.isStaff = function () {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The match value _can_ be a string, rather than regex, however usually regex will be better suited, as it can work with unknown values, whereas strings must be exact matches.
|
||||||
|
|
||||||
|
Once you've made your plugin, make sure you run `pnpm test` and make sure your code is nice and clean!
|
||||||
|
|
||||||
|
If you want to publish your plugin into the Vencord repo, move your plugin from `src/userplugins` into the `src/plugins` folder and open a PR!
|
||||||
|
|
||||||
|
> **Warning**
|
||||||
|
> Make sure you've read [CONTRIBUTING.md](../CONTRIBUTING.md) before opening a PR
|
||||||
|
|
||||||
|
If you need more help, ask in the support channel in our [Discord Server](https://discord.gg/D9uwnFnqmd).
|
|
@ -1,126 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
// @ts-check
|
|
||||||
|
|
||||||
import stylistic from "@stylistic/eslint-plugin";
|
|
||||||
import pathAlias from "eslint-plugin-path-alias";
|
|
||||||
import header from "eslint-plugin-simple-header";
|
|
||||||
import simpleImportSort from "eslint-plugin-simple-import-sort";
|
|
||||||
import unusedImports from "eslint-plugin-unused-imports";
|
|
||||||
import tseslint from "typescript-eslint";
|
|
||||||
|
|
||||||
export default tseslint.config(
|
|
||||||
{ ignores: ["dist", "browser", "packages/vencord-types"] },
|
|
||||||
{
|
|
||||||
files: ["src/**/*.{tsx,ts,mts,mjs,js,jsx}", "eslint.config.mjs"],
|
|
||||||
plugins: {
|
|
||||||
"simple-header": header,
|
|
||||||
"@stylistic": stylistic,
|
|
||||||
"@typescript-eslint": tseslint.plugin,
|
|
||||||
"simple-import-sort": simpleImportSort,
|
|
||||||
"unused-imports": unusedImports,
|
|
||||||
"path-alias": pathAlias,
|
|
||||||
},
|
|
||||||
settings: {
|
|
||||||
"import/resolver": {
|
|
||||||
map: [
|
|
||||||
["@webpack", "./src/webpack"],
|
|
||||||
["@webpack/common", "./src/webpack/common"],
|
|
||||||
["@utils", "./src/utils"],
|
|
||||||
["@api", "./src/api"],
|
|
||||||
["@components", "./src/components"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
languageOptions: {
|
|
||||||
parser: tseslint.parser,
|
|
||||||
parserOptions: {
|
|
||||||
project: ["./tsconfig.json"],
|
|
||||||
tsconfigRootDir: import.meta.dirname
|
|
||||||
}
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
/*
|
|
||||||
* Since it's only been a month and Vencord has already been stolen
|
|
||||||
* by random skids who rebranded it to "AlphaCord" and erased all license
|
|
||||||
* information
|
|
||||||
*/
|
|
||||||
"simple-header/header": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"files": ["scripts/header-new.txt", "scripts/header-old.txt"],
|
|
||||||
"templates": { "author": [".*", "Vendicated and contributors"] }
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
// Style Rules
|
|
||||||
"@stylistic/jsx-quotes": ["error", "prefer-double"],
|
|
||||||
"@stylistic/quotes": ["error", "double", { "avoidEscape": true }],
|
|
||||||
"@stylistic/no-mixed-spaces-and-tabs": "error",
|
|
||||||
"@stylistic/arrow-parens": ["error", "as-needed"],
|
|
||||||
"@stylistic/eol-last": ["error", "always"],
|
|
||||||
"@stylistic/no-multi-spaces": "error",
|
|
||||||
"@stylistic/no-trailing-spaces": "error",
|
|
||||||
"@stylistic/no-whitespace-before-property": "error",
|
|
||||||
"@stylistic/semi": ["error", "always"],
|
|
||||||
"@stylistic/semi-style": ["error", "last"],
|
|
||||||
"@stylistic/space-in-parens": ["error", "never"],
|
|
||||||
"@stylistic/block-spacing": ["error", "always"],
|
|
||||||
"@stylistic/object-curly-spacing": ["error", "always"],
|
|
||||||
"@stylistic/spaced-comment": ["error", "always", { "markers": ["!"] }],
|
|
||||||
"@stylistic/no-extra-semi": "error",
|
|
||||||
|
|
||||||
// TS Rules
|
|
||||||
"@stylistic/func-call-spacing": ["error", "never"],
|
|
||||||
|
|
||||||
// ESLint Rules
|
|
||||||
"yoda": "error",
|
|
||||||
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
|
||||||
"prefer-destructuring": ["error", {
|
|
||||||
"VariableDeclarator": { "array": false, "object": true },
|
|
||||||
"AssignmentExpression": { "array": false, "object": false }
|
|
||||||
}],
|
|
||||||
"operator-assignment": ["error", "always"],
|
|
||||||
"no-useless-computed-key": "error",
|
|
||||||
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
|
|
||||||
"no-invalid-regexp": "error",
|
|
||||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
|
||||||
"no-duplicate-imports": "error",
|
|
||||||
"dot-notation": "error",
|
|
||||||
"no-useless-escape": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
"extra": "i"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"no-fallthrough": "error",
|
|
||||||
"for-direction": "error",
|
|
||||||
"no-async-promise-executor": "error",
|
|
||||||
"no-cond-assign": "error",
|
|
||||||
"no-dupe-else-if": "error",
|
|
||||||
"no-duplicate-case": "error",
|
|
||||||
"no-irregular-whitespace": "error",
|
|
||||||
"no-loss-of-precision": "error",
|
|
||||||
"no-misleading-character-class": "error",
|
|
||||||
"no-prototype-builtins": "error",
|
|
||||||
"no-regex-spaces": "error",
|
|
||||||
"no-shadow-restricted-names": "error",
|
|
||||||
"no-unexpected-multiline": "error",
|
|
||||||
"no-unsafe-optional-chaining": "error",
|
|
||||||
"no-useless-backreference": "error",
|
|
||||||
"use-isnan": "error",
|
|
||||||
"prefer-const": "error",
|
|
||||||
"prefer-spread": "error",
|
|
||||||
|
|
||||||
// Plugin Rules
|
|
||||||
"simple-import-sort/imports": "error",
|
|
||||||
"simple-import-sort/exports": "error",
|
|
||||||
"unused-imports/no-unused-imports": "error",
|
|
||||||
"path-alias/no-relative": "error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
70
package.json
70
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.10.7",
|
"version": "1.9.0",
|
||||||
"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": {
|
||||||
|
@ -13,6 +13,9 @@
|
||||||
},
|
},
|
||||||
"license": "GPL-3.0-or-later",
|
"license": "GPL-3.0-or-later",
|
||||||
"author": "Vendicated",
|
"author": "Vendicated",
|
||||||
|
"directories": {
|
||||||
|
"doc": "docs"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs",
|
"build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs",
|
||||||
"buildStandalone": "pnpm build --standalone",
|
"buildStandalone": "pnpm build --standalone",
|
||||||
|
@ -21,13 +24,12 @@
|
||||||
"buildReporter": "pnpm buildWebStandalone --reporter --skip-extension",
|
"buildReporter": "pnpm buildWebStandalone --reporter --skip-extension",
|
||||||
"buildReporterDesktop": "pnpm build --reporter",
|
"buildReporterDesktop": "pnpm build --reporter",
|
||||||
"watch": "pnpm build --watch",
|
"watch": "pnpm build --watch",
|
||||||
"dev": "pnpm watch",
|
|
||||||
"watchWeb": "pnpm buildWeb --watch",
|
"watchWeb": "pnpm buildWeb --watch",
|
||||||
"generatePluginJson": "tsx scripts/generatePluginList.ts",
|
"generatePluginJson": "tsx scripts/generatePluginList.ts",
|
||||||
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
|
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
|
||||||
"inject": "node scripts/runInstaller.mjs",
|
"inject": "node scripts/runInstaller.mjs",
|
||||||
"uninject": "node scripts/runInstaller.mjs",
|
"uninject": "node scripts/runInstaller.mjs",
|
||||||
"lint": "eslint",
|
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern src/userplugins",
|
||||||
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
||||||
"lint:fix": "pnpm lint --fix",
|
"lint:fix": "pnpm lint --fix",
|
||||||
"test": "pnpm buildStandalone && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
|
"test": "pnpm buildStandalone && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
|
||||||
|
@ -35,55 +37,53 @@
|
||||||
"testTsc": "tsc --noEmit"
|
"testTsc": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@intrnl/xxhash64": "^0.1.2",
|
"@sapphi-red/web-noise-suppressor": "0.3.3",
|
||||||
"@sapphi-red/web-noise-suppressor": "0.3.5",
|
|
||||||
"@vap/core": "0.0.12",
|
"@vap/core": "0.0.12",
|
||||||
"@vap/shiki": "0.10.5",
|
"@vap/shiki": "0.10.5",
|
||||||
"fflate": "^0.8.2",
|
"eslint-plugin-simple-header": "^1.0.2",
|
||||||
|
"fflate": "^0.7.4",
|
||||||
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
||||||
"monaco-editor": "^0.50.0",
|
"monaco-editor": "^0.43.0",
|
||||||
"nanoid": "^5.0.7",
|
"nanoid": "^4.0.2",
|
||||||
"virtual-merge": "^1.0.1"
|
"virtual-merge": "^1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/eslint-plugin": "^2.6.1",
|
"@types/chrome": "^0.0.246",
|
||||||
"@types/chrome": "^0.0.269",
|
"@types/diff": "^5.0.3",
|
||||||
"@types/diff": "^5.2.1",
|
"@types/lodash": "^4.14.194",
|
||||||
"@types/lodash": "^4.17.7",
|
"@types/node": "^18.16.3",
|
||||||
"@types/node": "^22.0.3",
|
"@types/react": "^18.2.0",
|
||||||
"@types/react": "^18.3.3",
|
"@types/react-dom": "^18.2.1",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/yazl": "^2.4.2",
|
||||||
"@types/yazl": "^2.4.5",
|
"@typescript-eslint/eslint-plugin": "^5.59.1",
|
||||||
"diff": "^5.2.0",
|
"@typescript-eslint/parser": "^5.59.1",
|
||||||
|
"diff": "^5.1.0",
|
||||||
"discord-types": "^1.3.26",
|
"discord-types": "^1.3.26",
|
||||||
"esbuild": "^0.15.18",
|
"esbuild": "^0.15.18",
|
||||||
"eslint": "^9.8.0",
|
"eslint": "^8.46.0",
|
||||||
"eslint-import-resolver-alias": "^1.1.2",
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
"eslint-plugin-path-alias": "2.1.0",
|
"eslint-plugin-path-alias": "^1.0.0",
|
||||||
"eslint-plugin-simple-header": "^1.1.1",
|
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
"eslint-plugin-unused-imports": "^4.0.1",
|
"highlight.js": "10.6.0",
|
||||||
"highlight.js": "10.7.3",
|
|
||||||
"html-minifier-terser": "^7.2.0",
|
"html-minifier-terser": "^7.2.0",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.29.4",
|
||||||
"puppeteer-core": "^22.15.0",
|
"puppeteer-core": "^19.11.1",
|
||||||
"standalone-electron-types": "^1.0.0",
|
"standalone-electron-types": "^1.0.0",
|
||||||
"stylelint": "^16.8.1",
|
"stylelint": "^15.6.0",
|
||||||
"stylelint-config-standard": "^36.0.1",
|
"stylelint-config-standard": "^33.0.0",
|
||||||
"ts-patch": "^3.2.1",
|
"ts-patch": "^3.1.2",
|
||||||
"ts-pattern": "^5.3.1",
|
"tsx": "^3.12.7",
|
||||||
"tsx": "^4.16.5",
|
"type-fest": "^3.9.0",
|
||||||
"type-fest": "^4.23.0",
|
"typescript": "^5.4.5",
|
||||||
"typescript": "^5.5.4",
|
|
||||||
"typescript-eslint": "^8.0.0",
|
|
||||||
"typescript-transform-paths": "^3.4.7",
|
"typescript-transform-paths": "^3.4.7",
|
||||||
"zip-local": "^0.3.5"
|
"zip-local": "^0.3.5"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.1.0",
|
"packageManager": "pnpm@9.1.0",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"eslint@9.8.0": "patches/eslint@9.8.0.patch",
|
"eslint-plugin-path-alias@1.0.0": "patches/eslint-plugin-path-alias@1.0.0.patch",
|
||||||
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
|
"eslint@8.46.0": "patches/eslint@8.46.0.patch"
|
||||||
},
|
},
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
"ignoreMissing": [
|
"ignoreMissing": [
|
||||||
|
|
13
patches/eslint-plugin-path-alias@1.0.0.patch
Normal file
13
patches/eslint-plugin-path-alias@1.0.0.patch
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
diff --git a/lib/rules/no-relative.js b/lib/rules/no-relative.js
|
||||||
|
index 71594c83f1f4f733ffcc6047d7f7084348335dbe..d8623d87c89499c442171db3272cba07c9efabbe 100644
|
||||||
|
--- a/lib/rules/no-relative.js
|
||||||
|
+++ b/lib/rules/no-relative.js
|
||||||
|
@@ -41,7 +41,7 @@ module.exports = {
|
||||||
|
ImportDeclaration(node) {
|
||||||
|
const importPath = node.source.value;
|
||||||
|
|
||||||
|
- if (!/^(\.?\.\/)/.test(importPath)) {
|
||||||
|
+ if (!/^(\.\.\/)/.test(importPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
diff --git a/dist/index.js b/dist/index.js
|
|
||||||
index 67de6fb139070fd0e49beca65e3b63c531202e16..aa2883c8126e4952a42872ee920f59547a066430 100644
|
|
||||||
--- a/dist/index.js
|
|
||||||
+++ b/dist/index.js
|
|
||||||
@@ -1 +1 @@
|
|
||||||
-var C=Object.create;var f=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var $=(e,t)=>{for(var r in t)f(e,r,{get:t[r],enumerable:!0})},y=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of U(t))!F.call(e,s)&&s!==r&&f(e,s,{get:()=>t[s],enumerable:!(i=I(t,s))||i.enumerable});return e};var b=(e,t,r)=>(r=e!=null?C(S(e)):{},y(t||!e||!e.__esModule?f(r,"default",{value:e,enumerable:!0}):r,e)),D=e=>y(f({},"__esModule",{value:!0}),e);var N={};$(N,{default:()=>J});module.exports=D(N);var h="eslint-plugin-path-alias",v="2.0.0";var l=require("path"),M=b(require("nanomatch"));function j(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}var R=require("get-tsconfig"),a=require("path"),w=b(require("find-pkg")),O=require("fs");function P(e){if(e.options[0]?.paths)return z(e);let t=e.getFilename?.()??e.filename,r=(0,R.getTsconfig)(t);if(r?.config?.compilerOptions?.paths)return q(r);let i=w.default.sync((0,a.dirname)(t));if(!i)return;let s=JSON.parse((0,O.readFileSync)(i).toString());if(s?.imports)return L(s,i)}function L(e,t){let r=new Map,i=e.imports??{},s=(0,a.dirname)(t);return Object.entries(i).forEach(([o,n])=>{if(!n||typeof n!="string")return;let p=(0,a.resolve)(s,n);r.set(o,[p])}),r}function q(e){let t=new Map,r=e?.config?.compilerOptions?.paths??{},i=(0,a.dirname)(e.path);return e.config.compilerOptions?.baseUrl&&(i=(0,a.resolve)((0,a.dirname)(e.path),e.config.compilerOptions.baseUrl)),Object.entries(r).forEach(([s,o])=>{s=s.replace(/\/\*$/,""),o=o.map(n=>(0,a.resolve)(i,n.replace(/\/\*$/,""))),t.set(s,o)}),t}function z(e){let t=new Map,r=e.options[0]?.paths??{};return Object.entries(r).forEach(([i,s])=>{if(!s||typeof s!="string")return;if(s.startsWith("/")){t.set(i,[s]);return}let o=e.getCwd?.()??e.cwd,n=(0,a.resolve)(o,s);t.set(i,[n])}),t}var T={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:j("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let t=e.options[0]?.exceptions,r=e.getFilename?.()??e.filename,i=P(e);return i?.size?{ImportExpression(s){if(s.source.type!=="Literal"||typeof s.source.value!="string")return;let o=s.source.raw,n=s.source.value;if(!/^(\.?\.\/)/.test(n))return;let p=(0,l.resolve)((0,l.dirname)(r),n);if(A(p,t))return;let c=k(p,i);c&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:c},fix(m){let g=E(p,c,i.get(c)),d=o.replace(n,g);return m.replaceText(s.source,d)}})},ImportDeclaration(s){if(typeof s.source.value!="string")return;let o=s.source.value;if(!/^(\.?\.\/)/.test(o))return;let n=(0,l.resolve)((0,l.dirname)(r),o),p=A(n,t),u=k(n,i);p||u&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:u},fix(c){let m=s.source.raw,g=E(n,u,i.get(u)),d=m.replace(o,g);return c.replaceText(s.source,d)}})}}:{}}};function k(e,t){return Array.from(t.keys()).find(r=>t.get(r).some(s=>e.indexOf(s)===0))}function A(e,t){if(!t)return!1;let r=(0,l.basename)(e);return(0,M.default)(r,t).includes(r)}function E(e,t,r){for(let i of r)if(e.indexOf(i)===0)return e.replace(i,t)}var J={name:h,version:v,meta:{name:h,version:v},rules:{"no-relative":T}};
|
|
||||||
+var C=Object.create;var f=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var S=Object.getPrototypeOf,F=Object.prototype.hasOwnProperty;var $=(e,t)=>{for(var r in t)f(e,r,{get:t[r],enumerable:!0})},y=(e,t,r,i)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of U(t))!F.call(e,s)&&s!==r&&f(e,s,{get:()=>t[s],enumerable:!(i=I(t,s))||i.enumerable});return e};var b=(e,t,r)=>(r=e!=null?C(S(e)):{},y(t||!e||!e.__esModule?f(r,"default",{value:e,enumerable:!0}):r,e)),D=e=>y(f({},"__esModule",{value:!0}),e);var N={};$(N,{default:()=>J});module.exports=D(N);var h="eslint-plugin-path-alias",v="2.0.0";var l=require("path"),M=b(require("nanomatch"));function j(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}var R=require("get-tsconfig"),a=require("path"),w=b(require("find-pkg")),O=require("fs");function P(e){if(e.options[0]?.paths)return z(e);let t=e.getFilename?.()??e.filename,r=(0,R.getTsconfig)(t);if(r?.config?.compilerOptions?.paths)return q(r);let i=w.default.sync((0,a.dirname)(t));if(!i)return;let s=JSON.parse((0,O.readFileSync)(i).toString());if(s?.imports)return L(s,i)}function L(e,t){let r=new Map,i=e.imports??{},s=(0,a.dirname)(t);return Object.entries(i).forEach(([o,n])=>{if(!n||typeof n!="string")return;let p=(0,a.resolve)(s,n);r.set(o,[p])}),r}function q(e){let t=new Map,r=e?.config?.compilerOptions?.paths??{},i=(0,a.dirname)(e.path);return e.config.compilerOptions?.baseUrl&&(i=(0,a.resolve)((0,a.dirname)(e.path),e.config.compilerOptions.baseUrl)),Object.entries(r).forEach(([s,o])=>{s=s.replace(/\/\*$/,""),o=o.map(n=>(0,a.resolve)(i,n.replace(/\/\*$/,""))),t.set(s,o)}),t}function z(e){let t=new Map,r=e.options[0]?.paths??{};return Object.entries(r).forEach(([i,s])=>{if(!s||typeof s!="string")return;if(s.startsWith("/")){t.set(i,[s]);return}let o=e.getCwd?.()??e.cwd,n=(0,a.resolve)(o,s);t.set(i,[n])}),t}var T={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:j("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let t=e.options[0]?.exceptions,r=e.getFilename?.()??e.filename,i=P(e);return i?.size?{ImportExpression(s){if(s.source.type!=="Literal"||typeof s.source.value!="string")return;let o=s.source.raw,n=s.source.value;if(!/^(\.\.\/)/.test(n))return;let p=(0,l.resolve)((0,l.dirname)(r),n);if(A(p,t))return;let c=k(p,i);c&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:c},fix(m){let g=E(p,c,i.get(c)),d=o.replace(n,g);return m.replaceText(s.source,d)}})},ImportDeclaration(s){if(typeof s.source.value!="string")return;let o=s.source.value;if(!/^(\.\.\/)/.test(o))return;let n=(0,l.resolve)((0,l.dirname)(r),o),p=A(n,t),u=k(n,i);p||u&&e.report({node:s,messageId:"shouldUseAlias",data:{alias:u},fix(c){let m=s.source.raw,g=E(n,u,i.get(u)),d=m.replace(o,g);return c.replaceText(s.source,d)}})}}:{}}};function k(e,t){return Array.from(t.keys()).find(r=>t.get(r).some(s=>e.indexOf(s)===0))}function A(e,t){if(!t)return!1;let r=(0,l.basename)(e);return(0,M.default)(r,t).includes(r)}function E(e,t,r){for(let i of r)if(e.indexOf(i)===0)return e.replace(i,t)}var J={name:h,version:v,meta:{name:h,version:v},rules:{"no-relative":T}};
|
|
||||||
diff --git a/dist/index.mjs b/dist/index.mjs
|
|
||||||
index 96de18e06d4cc413e11af038cd760e4804c32e59..27e8c4e3e2c942400cc3982e52159904ca6eedfa 100644
|
|
||||||
--- a/dist/index.mjs
|
|
||||||
+++ b/dist/index.mjs
|
|
||||||
@@ -1 +1 @@
|
|
||||||
-var d="eslint-plugin-path-alias",h="2.0.0";import{dirname as x,resolve as j,basename as I}from"path";import U from"nanomatch";function y(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}import{getTsconfig as k}from"get-tsconfig";import{resolve as c,dirname as u}from"path";import A from"find-pkg";import{readFileSync as E}from"fs";function b(e){if(e.options[0]?.paths)return C(e);let s=e.getFilename?.()??e.filename,i=k(s);if(i?.config?.compilerOptions?.paths)return T(i);let r=A.sync(u(s));if(!r)return;let t=JSON.parse(E(r).toString());if(t?.imports)return M(t,r)}function M(e,s){let i=new Map,r=e.imports??{},t=u(s);return Object.entries(r).forEach(([o,n])=>{if(!n||typeof n!="string")return;let a=c(t,n);i.set(o,[a])}),i}function T(e){let s=new Map,i=e?.config?.compilerOptions?.paths??{},r=u(e.path);return e.config.compilerOptions?.baseUrl&&(r=c(u(e.path),e.config.compilerOptions.baseUrl)),Object.entries(i).forEach(([t,o])=>{t=t.replace(/\/\*$/,""),o=o.map(n=>c(r,n.replace(/\/\*$/,""))),s.set(t,o)}),s}function C(e){let s=new Map,i=e.options[0]?.paths??{};return Object.entries(i).forEach(([r,t])=>{if(!t||typeof t!="string")return;if(t.startsWith("/")){s.set(r,[t]);return}let o=e.getCwd?.()??e.cwd,n=c(o,t);s.set(r,[n])}),s}var P={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:y("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let s=e.options[0]?.exceptions,i=e.getFilename?.()??e.filename,r=b(e);return r?.size?{ImportExpression(t){if(t.source.type!=="Literal"||typeof t.source.value!="string")return;let o=t.source.raw,n=t.source.value;if(!/^(\.?\.\/)/.test(n))return;let a=j(x(i),n);if(w(a,s))return;let l=R(a,r);l&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:l},fix(f){let m=O(a,l,r.get(l)),g=o.replace(n,m);return f.replaceText(t.source,g)}})},ImportDeclaration(t){if(typeof t.source.value!="string")return;let o=t.source.value;if(!/^(\.?\.\/)/.test(o))return;let n=j(x(i),o),a=w(n,s),p=R(n,r);a||p&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:p},fix(l){let f=t.source.raw,m=O(n,p,r.get(p)),g=f.replace(o,m);return l.replaceText(t.source,g)}})}}:{}}};function R(e,s){return Array.from(s.keys()).find(i=>s.get(i).some(t=>e.indexOf(t)===0))}function w(e,s){if(!s)return!1;let i=I(e);return U(i,s).includes(i)}function O(e,s,i){for(let r of i)if(e.indexOf(r)===0)return e.replace(r,s)}var Q={name:d,version:h,meta:{name:d,version:h},rules:{"no-relative":P}};export{Q as default};
|
|
||||||
+var d="eslint-plugin-path-alias",h="2.0.0";import{dirname as x,resolve as j,basename as I}from"path";import U from"nanomatch";function y(e){return`https://github/com/msfragala/eslint-plugin-path-alias/blob/master/docs/rules/${e}.md`}import{getTsconfig as k}from"get-tsconfig";import{resolve as c,dirname as u}from"path";import A from"find-pkg";import{readFileSync as E}from"fs";function b(e){if(e.options[0]?.paths)return C(e);let s=e.getFilename?.()??e.filename,i=k(s);if(i?.config?.compilerOptions?.paths)return T(i);let r=A.sync(u(s));if(!r)return;let t=JSON.parse(E(r).toString());if(t?.imports)return M(t,r)}function M(e,s){let i=new Map,r=e.imports??{},t=u(s);return Object.entries(r).forEach(([o,n])=>{if(!n||typeof n!="string")return;let a=c(t,n);i.set(o,[a])}),i}function T(e){let s=new Map,i=e?.config?.compilerOptions?.paths??{},r=u(e.path);return e.config.compilerOptions?.baseUrl&&(r=c(u(e.path),e.config.compilerOptions.baseUrl)),Object.entries(i).forEach(([t,o])=>{t=t.replace(/\/\*$/,""),o=o.map(n=>c(r,n.replace(/\/\*$/,""))),s.set(t,o)}),s}function C(e){let s=new Map,i=e.options[0]?.paths??{};return Object.entries(i).forEach(([r,t])=>{if(!t||typeof t!="string")return;if(t.startsWith("/")){s.set(r,[t]);return}let o=e.getCwd?.()??e.cwd,n=c(o,t);s.set(r,[n])}),s}var P={meta:{type:"suggestion",docs:{description:"Ensure imports use path aliases whenever possible vs. relative paths",url:y("no-relative")},fixable:"code",schema:[{type:"object",properties:{exceptions:{type:"array",items:{type:"string"}},paths:{type:"object"}},additionalProperties:!1}],messages:{shouldUseAlias:"Import should use path alias instead of relative path"}},create(e){let s=e.options[0]?.exceptions,i=e.getFilename?.()??e.filename,r=b(e);return r?.size?{ImportExpression(t){if(t.source.type!=="Literal"||typeof t.source.value!="string")return;let o=t.source.raw,n=t.source.value;if(!/^(\.\.\/)/.test(n))return;let a=j(x(i),n);if(w(a,s))return;let l=R(a,r);l&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:l},fix(f){let m=O(a,l,r.get(l)),g=o.replace(n,m);return f.replaceText(t.source,g)}})},ImportDeclaration(t){if(typeof t.source.value!="string")return;let o=t.source.value;if(!/^(\.\.\/)/.test(o))return;let n=j(x(i),o),a=w(n,s),p=R(n,r);a||p&&e.report({node:t,messageId:"shouldUseAlias",data:{alias:p},fix(l){let f=t.source.raw,m=O(n,p,r.get(p)),g=f.replace(o,m);return l.replaceText(t.source,g)}})}}:{}}};function R(e,s){return Array.from(s.keys()).find(i=>s.get(i).some(t=>e.indexOf(t)===0))}function w(e,s){if(!s)return!1;let i=I(e);return U(i,s).includes(i)}function O(e,s,i){for(let r of i)if(e.indexOf(r)===0)return e.replace(r,s)}var Q={name:d,version:h,meta:{name:d,version:h},rules:{"no-relative":P}};export{Q as default};
|
|
2612
pnpm-lock.yaml
2612
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
@ -21,7 +21,7 @@ import esbuild from "esbuild";
|
||||||
import { readdir } from "fs/promises";
|
import { readdir } from "fs/promises";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch } from "./common.mjs";
|
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, VERSION, watch } from "./common.mjs";
|
||||||
|
|
||||||
const defines = {
|
const defines = {
|
||||||
IS_STANDALONE,
|
IS_STANDALONE,
|
||||||
|
@ -76,20 +76,22 @@ const globNativesPlugin = {
|
||||||
for (const dir of pluginDirs) {
|
for (const dir of pluginDirs) {
|
||||||
const dirPath = join("src", dir);
|
const dirPath = join("src", dir);
|
||||||
if (!await exists(dirPath)) continue;
|
if (!await exists(dirPath)) continue;
|
||||||
const plugins = await readdir(dirPath, { withFileTypes: true });
|
const plugins = await readdir(dirPath);
|
||||||
for (const file of plugins) {
|
for (const p of plugins) {
|
||||||
const fileName = file.name;
|
const nativePath = join(dirPath, p, "native.ts");
|
||||||
const nativePath = join(dirPath, fileName, "native.ts");
|
const indexNativePath = join(dirPath, p, "native/index.ts");
|
||||||
const indexNativePath = join(dirPath, fileName, "native/index.ts");
|
|
||||||
|
|
||||||
if (!(await exists(nativePath)) && !(await exists(indexNativePath)))
|
if (!(await exists(nativePath)) && !(await exists(indexNativePath)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const pluginName = await resolvePluginName(dirPath, file);
|
const nameParts = p.split(".");
|
||||||
|
const namePartsWithoutTarget = nameParts.length === 1 ? nameParts : nameParts.slice(0, -1);
|
||||||
|
// pluginName.thing.desktop -> PluginName.thing
|
||||||
|
const cleanPluginName = p[0].toUpperCase() + namePartsWithoutTarget.join(".").slice(1);
|
||||||
|
|
||||||
const mod = `p${i}`;
|
const mod = `p${i}`;
|
||||||
code += `import * as ${mod} from "./${dir}/${fileName}/native";\n`;
|
code += `import * as ${mod} from "./${dir}/${p}/native";\n`;
|
||||||
natives += `${JSON.stringify(pluginName)}:${mod},\n`;
|
natives += `${JSON.stringify(cleanPluginName)}:${mod},\n`;
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +133,7 @@ await Promise.all([
|
||||||
sourcemap,
|
sourcemap,
|
||||||
plugins: [
|
plugins: [
|
||||||
globPlugins("discordDesktop"),
|
globPlugins("discordDesktop"),
|
||||||
...commonRendererPlugins
|
...commonOpts.plugins
|
||||||
],
|
],
|
||||||
define: {
|
define: {
|
||||||
...defines,
|
...defines,
|
||||||
|
@ -180,7 +182,7 @@ await Promise.all([
|
||||||
sourcemap,
|
sourcemap,
|
||||||
plugins: [
|
plugins: [
|
||||||
globPlugins("vencordDesktop"),
|
globPlugins("vencordDesktop"),
|
||||||
...commonRendererPlugins
|
...commonOpts.plugins
|
||||||
],
|
],
|
||||||
define: {
|
define: {
|
||||||
...defines,
|
...defines,
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import Zip from "zip-local";
|
import Zip from "zip-local";
|
||||||
|
|
||||||
import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION, commonRendererPlugins } from "./common.mjs";
|
import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION } from "./common.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {esbuild.BuildOptions}
|
* @type {esbuild.BuildOptions}
|
||||||
|
@ -36,7 +36,7 @@ const commonOptions = {
|
||||||
external: ["~plugins", "~git-hash", "/assets/*"],
|
external: ["~plugins", "~git-hash", "/assets/*"],
|
||||||
plugins: [
|
plugins: [
|
||||||
globPlugins("web"),
|
globPlugins("web"),
|
||||||
...commonRendererPlugins
|
...commonOpts.plugins,
|
||||||
],
|
],
|
||||||
target: ["esnext"],
|
target: ["esnext"],
|
||||||
define: {
|
define: {
|
||||||
|
@ -116,12 +116,7 @@ await Promise.all(
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
]
|
]
|
||||||
).catch(err => {
|
);
|
||||||
console.error("Build failed");
|
|
||||||
console.error(err.message);
|
|
||||||
if (!commonOpts.watch)
|
|
||||||
process.exit(1);
|
|
||||||
});;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {(dir: string) => Promise<string[]>}
|
* @type {(dir: string) => Promise<string[]>}
|
||||||
|
|
|
@ -28,7 +28,6 @@ import { join, relative } from "path";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
|
|
||||||
import { getPluginTarget } from "../utils.mjs";
|
import { getPluginTarget } from "../utils.mjs";
|
||||||
import { builtinModules } from "module";
|
|
||||||
|
|
||||||
/** @type {import("../../package.json")} */
|
/** @type {import("../../package.json")} */
|
||||||
const PackageJSON = JSON.parse(readFileSync("package.json"));
|
const PackageJSON = JSON.parse(readFileSync("package.json"));
|
||||||
|
@ -54,32 +53,6 @@ export const banner = {
|
||||||
`.trim()
|
`.trim()
|
||||||
};
|
};
|
||||||
|
|
||||||
const PluginDefinitionNameMatcher = /definePlugin\(\{\s*(["'])?name\1:\s*(["'`])(.+?)\2/;
|
|
||||||
/**
|
|
||||||
* @param {string} base
|
|
||||||
* @param {import("fs").Dirent} dirent
|
|
||||||
*/
|
|
||||||
export async function resolvePluginName(base, dirent) {
|
|
||||||
const fullPath = join(base, dirent.name);
|
|
||||||
const content = dirent.isFile()
|
|
||||||
? await readFile(fullPath, "utf-8")
|
|
||||||
: await (async () => {
|
|
||||||
for (const file of ["index.ts", "index.tsx"]) {
|
|
||||||
try {
|
|
||||||
return await readFile(join(fullPath, file), "utf-8");
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error(`Invalid plugin ${fullPath}: could not resolve entry point`);
|
|
||||||
})();
|
|
||||||
|
|
||||||
return PluginDefinitionNameMatcher.exec(content)?.[3]
|
|
||||||
?? (() => {
|
|
||||||
throw new Error(`Invalid plugin ${fullPath}: must contain definePlugin call with simple string name property as first property`);
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function exists(path) {
|
export async function exists(path) {
|
||||||
return await access(path, FsConstants.F_OK)
|
return await access(path, FsConstants.F_OK)
|
||||||
.then(() => true)
|
.then(() => true)
|
||||||
|
@ -115,16 +88,14 @@ export const globPlugins = kind => ({
|
||||||
build.onLoad({ filter, namespace: "import-plugins" }, async () => {
|
build.onLoad({ filter, namespace: "import-plugins" }, async () => {
|
||||||
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
|
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
|
||||||
let code = "";
|
let code = "";
|
||||||
let pluginsCode = "\n";
|
let plugins = "\n";
|
||||||
let metaCode = "\n";
|
let meta = "\n";
|
||||||
let excludedCode = "\n";
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (const dir of pluginDirs) {
|
for (const dir of pluginDirs) {
|
||||||
const userPlugin = dir === "userplugins";
|
const userPlugin = dir === "userplugins";
|
||||||
|
|
||||||
const fullDir = `./src/${dir}`;
|
if (!await exists(`./src/${dir}`)) continue;
|
||||||
if (!await exists(fullDir)) continue;
|
const files = await readdir(`./src/${dir}`, { withFileTypes: true });
|
||||||
const files = await readdir(fullDir, { withFileTypes: true });
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const fileName = file.name;
|
const fileName = file.name;
|
||||||
if (fileName.startsWith("_") || fileName.startsWith(".")) continue;
|
if (fileName.startsWith("_") || fileName.startsWith(".")) continue;
|
||||||
|
@ -133,30 +104,23 @@ export const globPlugins = kind => ({
|
||||||
const target = getPluginTarget(fileName);
|
const target = getPluginTarget(fileName);
|
||||||
|
|
||||||
if (target && !IS_REPORTER) {
|
if (target && !IS_REPORTER) {
|
||||||
const excluded =
|
if (target === "dev" && !watch) continue;
|
||||||
(target === "dev" && !IS_DEV) ||
|
if (target === "web" && kind === "discordDesktop") continue;
|
||||||
(target === "web" && kind === "discordDesktop") ||
|
if (target === "desktop" && kind === "web") continue;
|
||||||
(target === "desktop" && kind === "web") ||
|
if (target === "discordDesktop" && kind !== "discordDesktop") continue;
|
||||||
(target === "discordDesktop" && kind !== "discordDesktop") ||
|
if (target === "vencordDesktop" && kind !== "vencordDesktop") continue;
|
||||||
(target === "vencordDesktop" && kind !== "vencordDesktop");
|
|
||||||
|
|
||||||
if (excluded) {
|
|
||||||
const name = await resolvePluginName(fullDir, file);
|
|
||||||
excludedCode += `${JSON.stringify(name)}:${JSON.stringify(target)},\n`;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const folderName = `src/${dir}/${fileName}`.replace(/^src\/plugins\//, "");
|
const folderName = `src/${dir}/${fileName}`.replace(/^src\/plugins\//, "");
|
||||||
|
|
||||||
const mod = `p${i}`;
|
const mod = `p${i}`;
|
||||||
code += `import ${mod} from "./${dir}/${fileName.replace(/\.tsx?$/, "")}";\n`;
|
code += `import ${mod} from "./${dir}/${fileName.replace(/\.tsx?$/, "")}";\n`;
|
||||||
pluginsCode += `[${mod}.name]:${mod},\n`;
|
plugins += `[${mod}.name]:${mod},\n`;
|
||||||
metaCode += `[${mod}.name]:${JSON.stringify({ folderName, userPlugin })},\n`; // TODO: add excluded plugins to display in the UI?
|
meta += `[${mod}.name]:${JSON.stringify({ folderName, userPlugin })},\n`; // TODO: add excluded plugins to display in the UI?
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
code += `export default {${pluginsCode}};export const PluginMeta={${metaCode}};export const ExcludedPlugins={${excludedCode}};`;
|
code += `export default {${plugins}};export const PluginMeta={${meta}};`;
|
||||||
return {
|
return {
|
||||||
contents: code,
|
contents: code,
|
||||||
resolveDir: "./src"
|
resolveDir: "./src"
|
||||||
|
@ -293,18 +257,6 @@ export const stylePlugin = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {(filter: RegExp, message: string) => import("esbuild").Plugin}
|
|
||||||
*/
|
|
||||||
export const banImportPlugin = (filter, message) => ({
|
|
||||||
name: "ban-imports",
|
|
||||||
setup: build => {
|
|
||||||
build.onResolve({ filter }, () => {
|
|
||||||
return { errors: [{ text: message }] };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import("esbuild").BuildOptions}
|
* @type {import("esbuild").BuildOptions}
|
||||||
*/
|
*/
|
||||||
|
@ -324,16 +276,3 @@ export const commonOpts = {
|
||||||
// Work around https://github.com/evanw/esbuild/issues/2460
|
// Work around https://github.com/evanw/esbuild/issues/2460
|
||||||
tsconfig: "./scripts/build/tsconfig.esbuild.json"
|
tsconfig: "./scripts/build/tsconfig.esbuild.json"
|
||||||
};
|
};
|
||||||
|
|
||||||
const escapedBuiltinModules = builtinModules
|
|
||||||
.map(m => m.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"))
|
|
||||||
.join("|");
|
|
||||||
const builtinModuleRegex = new RegExp(`^(node:)?(${escapedBuiltinModules})$`);
|
|
||||||
|
|
||||||
export const commonRendererPlugins = [
|
|
||||||
banImportPlugin(builtinModuleRegex, "Cannot import node inbuilt modules in browser code. You need to use a native.ts file"),
|
|
||||||
banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"),
|
|
||||||
banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"),
|
|
||||||
banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"),
|
|
||||||
...commonOpts.plugins
|
|
||||||
];
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ interface PluginData {
|
||||||
hasCommands: boolean;
|
hasCommands: boolean;
|
||||||
required: boolean;
|
required: boolean;
|
||||||
enabledByDefault: boolean;
|
enabledByDefault: boolean;
|
||||||
target: "discordDesktop" | "vencordDesktop" | "desktop" | "web" | "dev";
|
target: "discordDesktop" | "vencordDesktop" | "web" | "dev";
|
||||||
filePath: string;
|
filePath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ for (const variable of ["DISCORD_TOKEN", "CHROMIUM_BIN"]) {
|
||||||
const CANARY = process.env.USE_CANARY === "true";
|
const CANARY = process.env.USE_CANARY === "true";
|
||||||
|
|
||||||
const browser = await pup.launch({
|
const browser = await pup.launch({
|
||||||
headless: true,
|
headless: "new",
|
||||||
executablePath: process.env.CHROMIUM_BIN
|
executablePath: process.env.CHROMIUM_BIN
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -136,6 +136,7 @@ async function printReport() {
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
description: "Here's the latest Vencord Report!",
|
description: "Here's the latest Vencord Report!",
|
||||||
username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""),
|
username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""),
|
||||||
|
avatar_url: "https://cdn.discordapp.com/avatars/1017176847865352332/c312b6b44179ae6817de7e4b09e9c6af.webp?size=512",
|
||||||
embeds: [
|
embeds: [
|
||||||
{
|
{
|
||||||
title: "Bad Patches",
|
title: "Bad Patches",
|
||||||
|
@ -225,7 +226,7 @@ page.on("console", async e => {
|
||||||
plugin,
|
plugin,
|
||||||
type,
|
type,
|
||||||
id,
|
id,
|
||||||
match: regex.replace(/\(\?:\[A-Za-z_\$\]\[\\w\$\]\*\)/g, "\\i"),
|
match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"),
|
||||||
error: await maybeGetError(e.args()[3])
|
error: await maybeGetError(e.args()[3])
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -289,8 +290,6 @@ page.on("console", async e => {
|
||||||
|
|
||||||
page.on("error", e => console.error("[Error]", e.message));
|
page.on("error", e => console.error("[Error]", e.message));
|
||||||
page.on("pageerror", e => {
|
page.on("pageerror", e => {
|
||||||
if (e.message.includes("Sentry successfully disabled")) return;
|
|
||||||
|
|
||||||
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) {
|
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) {
|
||||||
console.error("[Page Error]", e.message);
|
console.error("[Page Error]", e.message);
|
||||||
report.otherErrors.push(e.message);
|
report.otherErrors.push(e.message);
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
git remote add upstream https://github.com/Vendicated/Vencord.git
|
|
||||||
git remote set-url --pull upstream DISABLED
|
|
|
@ -44,11 +44,6 @@ export interface ProfileBadge {
|
||||||
position?: BadgePosition;
|
position?: BadgePosition;
|
||||||
/** The badge name to display, Discord uses this. Required for component badges */
|
/** The badge name to display, Discord uses this. Required for component badges */
|
||||||
key?: string;
|
key?: string;
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows dynamically returning multiple badges
|
|
||||||
*/
|
|
||||||
getBadges?(userInfo: BadgeUserArgs): ProfileBadge[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Badges = new Set<ProfileBadge>();
|
const Badges = new Set<ProfileBadge>();
|
||||||
|
@ -78,16 +73,9 @@ export function _getBadges(args: BadgeUserArgs) {
|
||||||
const badges = [] as ProfileBadge[];
|
const badges = [] as ProfileBadge[];
|
||||||
for (const badge of Badges) {
|
for (const badge of Badges) {
|
||||||
if (!badge.shouldShow || badge.shouldShow(args)) {
|
if (!badge.shouldShow || badge.shouldShow(args)) {
|
||||||
const b = badge.getBadges
|
|
||||||
? badge.getBadges(args).map(b => {
|
|
||||||
b.component &&= ErrorBoundary.wrap(b.component, { noop: true });
|
|
||||||
return b;
|
|
||||||
})
|
|
||||||
: [{ ...badge, ...args }];
|
|
||||||
|
|
||||||
badge.position === BadgePosition.START
|
badge.position === BadgePosition.START
|
||||||
? badges.unshift(...b)
|
? badges.unshift({ ...badge, ...args })
|
||||||
: badges.push(...b);
|
: badges.push({ ...badge, ...args });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId);
|
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId);
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Logger } from "@utils/Logger";
|
|
||||||
import { makeCodeblock } from "@utils/text";
|
import { makeCodeblock } from "@utils/text";
|
||||||
|
|
||||||
import { sendBotMessage } from "./commandHelpers";
|
import { sendBotMessage } from "./commandHelpers";
|
||||||
|
@ -47,10 +46,10 @@ export let RequiredMessageOption: Option = ReqPlaceholder;
|
||||||
export const _init = function (cmds: Command[]) {
|
export const _init = function (cmds: Command[]) {
|
||||||
try {
|
try {
|
||||||
BUILT_IN = cmds;
|
BUILT_IN = cmds;
|
||||||
OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0];
|
OptionalMessageOption = cmds.find(c => c.name === "shrug")!.options![0];
|
||||||
RequiredMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "me")!.options![0];
|
RequiredMessageOption = cmds.find(c => c.name === "me")!.options![0];
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
new Logger("CommandsAPI").error("Failed to load CommandsApi", e, " - cmds is", cmds);
|
console.error("Failed to load CommandsApi");
|
||||||
}
|
}
|
||||||
return cmds;
|
return cmds;
|
||||||
} as never;
|
} as never;
|
||||||
|
@ -139,8 +138,6 @@ export function registerCommand<C extends Command>(command: C, plugin: string) {
|
||||||
throw new Error(`Command '${command.name}' already exists.`);
|
throw new Error(`Command '${command.name}' already exists.`);
|
||||||
|
|
||||||
command.isVencordCommand = true;
|
command.isVencordCommand = true;
|
||||||
command.untranslatedName ??= command.name;
|
|
||||||
command.untranslatedDescription ??= command.description;
|
|
||||||
command.id ??= `-${BUILT_IN.length + 1}`;
|
command.id ??= `-${BUILT_IN.length + 1}`;
|
||||||
command.applicationId ??= "-1"; // BUILT_IN;
|
command.applicationId ??= "-1"; // BUILT_IN;
|
||||||
command.type ??= ApplicationCommandType.CHAT_INPUT;
|
command.type ??= ApplicationCommandType.CHAT_INPUT;
|
||||||
|
|
|
@ -93,10 +93,8 @@ export interface Command {
|
||||||
isVencordCommand?: boolean;
|
isVencordCommand?: boolean;
|
||||||
|
|
||||||
name: string;
|
name: string;
|
||||||
untranslatedName?: string;
|
|
||||||
displayName?: string;
|
displayName?: string;
|
||||||
description: string;
|
description: string;
|
||||||
untranslatedDescription?: string;
|
|
||||||
displayDescription?: string;
|
displayDescription?: string;
|
||||||
|
|
||||||
options?: Option[];
|
options?: Option[];
|
||||||
|
|
|
@ -90,20 +90,19 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba
|
||||||
* A helper function for finding the children array of a group nested inside a context menu based on the id(s) of its children
|
* A helper function for finding the children array of a group nested inside a context menu based on the id(s) of its children
|
||||||
* @param id The id of the child. If an array is specified, all ids will be tried
|
* @param id The id of the child. If an array is specified, all ids will be tried
|
||||||
* @param children The context menu children
|
* @param children The context menu children
|
||||||
* @param matchSubstring Whether to check if the id is a substring of the child id
|
|
||||||
*/
|
*/
|
||||||
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null | undefined>, matchSubstring = false): Array<ReactElement | null | undefined> | null {
|
export function findGroupChildrenByChildId(id: string | string[], children: Array<ReactElement | null>): Array<ReactElement | null> | null {
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
if (child == null) continue;
|
if (child == null) continue;
|
||||||
|
|
||||||
if (Array.isArray(child)) {
|
if (Array.isArray(child)) {
|
||||||
const found = findGroupChildrenByChildId(id, child, matchSubstring);
|
const found = findGroupChildrenByChildId(id, child);
|
||||||
if (found !== null) return found;
|
if (found !== null) return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(Array.isArray(id) && id.some(id => matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id))
|
(Array.isArray(id) && id.some(id => child.props?.id === id))
|
||||||
|| (matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id)
|
|| child.props?.id === id
|
||||||
) return children;
|
) return children;
|
||||||
|
|
||||||
let nextChildren = child.props?.children;
|
let nextChildren = child.props?.children;
|
||||||
|
@ -113,7 +112,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
|
||||||
child.props.children = nextChildren;
|
child.props.children = nextChildren;
|
||||||
}
|
}
|
||||||
|
|
||||||
const found = findGroupChildrenByChildId(id, nextChildren, matchSubstring);
|
const found = findGroupChildrenByChildId(id, nextChildren);
|
||||||
if (found !== null) return found;
|
if (found !== null) return found;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,7 +121,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContextMenuProps {
|
interface ContextMenuProps {
|
||||||
contextMenuApiArguments?: Array<any>;
|
contextMenuAPIArguments?: Array<any>;
|
||||||
navId: string;
|
navId: string;
|
||||||
children: Array<ReactElement | null>;
|
children: Array<ReactElement | null>;
|
||||||
"aria-label": string;
|
"aria-label": string;
|
||||||
|
@ -136,7 +135,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
children: cloneMenuChildren(props.children),
|
children: cloneMenuChildren(props.children),
|
||||||
};
|
};
|
||||||
|
|
||||||
props.contextMenuApiArguments ??= [];
|
props.contextMenuAPIArguments ??= [];
|
||||||
const contextMenuPatches = navPatches.get(props.navId);
|
const contextMenuPatches = navPatches.get(props.navId);
|
||||||
|
|
||||||
if (!Array.isArray(props.children)) props.children = [props.children];
|
if (!Array.isArray(props.children)) props.children = [props.children];
|
||||||
|
@ -144,7 +143,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
if (contextMenuPatches) {
|
if (contextMenuPatches) {
|
||||||
for (const patch of contextMenuPatches) {
|
for (const patch of contextMenuPatches) {
|
||||||
try {
|
try {
|
||||||
patch(props.children, ...props.contextMenuApiArguments);
|
patch(props.children, ...props.contextMenuAPIArguments);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
||||||
}
|
}
|
||||||
|
@ -153,7 +152,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
|
|
||||||
for (const patch of globalPatches) {
|
for (const patch of globalPatches) {
|
||||||
try {
|
try {
|
||||||
patch(props.navId, props.children, ...props.contextMenuApiArguments);
|
patch(props.navId, props.children, ...props.contextMenuAPIArguments);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error("Global patch errored,", err);
|
ContextMenuLogger.error("Global patch errored,", err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,17 +16,16 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Channel, Message } from "discord-types/general";
|
import { Channel, Message } from "discord-types/general";
|
||||||
import type { ComponentType, MouseEventHandler } from "react";
|
import type { MouseEventHandler } from "react";
|
||||||
|
|
||||||
const logger = new Logger("MessagePopover");
|
const logger = new Logger("MessagePopover");
|
||||||
|
|
||||||
export interface ButtonItem {
|
export interface ButtonItem {
|
||||||
key?: string,
|
key?: string,
|
||||||
label: string,
|
label: string,
|
||||||
icon: ComponentType<any>,
|
icon: React.ComponentType<any>,
|
||||||
message: Message,
|
message: Message,
|
||||||
channel: Channel,
|
channel: Channel,
|
||||||
onClick?: MouseEventHandler<HTMLButtonElement>,
|
onClick?: MouseEventHandler<HTMLButtonElement>,
|
||||||
|
@ -49,26 +48,22 @@ export function removeButton(identifier: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function _buildPopoverElements(
|
export function _buildPopoverElements(
|
||||||
Component: React.ComponentType<ButtonItem>,
|
msg: Message,
|
||||||
message: Message
|
makeButton: (item: ButtonItem) => React.ComponentType
|
||||||
) {
|
) {
|
||||||
const items: React.ReactNode[] = [];
|
const items = [] as React.ComponentType[];
|
||||||
|
|
||||||
for (const [identifier, getItem] of buttons.entries()) {
|
for (const [identifier, getItem] of buttons.entries()) {
|
||||||
try {
|
try {
|
||||||
const item = getItem(message);
|
const item = getItem(msg);
|
||||||
if (item) {
|
if (item) {
|
||||||
item.key ??= identifier;
|
item.key ??= identifier;
|
||||||
items.push(
|
items.push(makeButton(item));
|
||||||
<ErrorBoundary noop>
|
|
||||||
<Component {...item} />
|
|
||||||
</ErrorBoundary>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`[${identifier}]`, err);
|
logger.error(`[${identifier}]`, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{items}</>;
|
return items;
|
||||||
}
|
}
|
|
@ -19,8 +19,6 @@
|
||||||
import * as DataStore from "@api/DataStore";
|
import * as DataStore from "@api/DataStore";
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Flex } from "@components/Flex";
|
|
||||||
import { openNotificationSettingsModal } from "@components/VencordSettings/NotificationSettings";
|
|
||||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
||||||
|
@ -172,14 +170,8 @@ function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
|
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<Flex>
|
|
||||||
<Button onClick={openNotificationSettingsModal}>
|
|
||||||
Notification Settings
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
disabled={log.length === 0}
|
disabled={log.length === 0}
|
||||||
color={Button.Colors.RED}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
Alerts.show({
|
Alerts.show({
|
||||||
title: "Are you sure?",
|
title: "Are you sure?",
|
||||||
|
@ -196,7 +188,6 @@ function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void
|
||||||
>
|
>
|
||||||
Clear Notification Log
|
Clear Notification Log
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
|
||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
</ModalRoot>
|
</ModalRoot>
|
||||||
);
|
);
|
||||||
|
|
|
@ -129,7 +129,7 @@ export const SettingsStore = new SettingsStoreClass(settings, {
|
||||||
|
|
||||||
if (path === "plugins" && key in plugins)
|
if (path === "plugins" && key in plugins)
|
||||||
return target[key] = {
|
return target[key] = {
|
||||||
enabled: IS_REPORTER || plugins[key].required || plugins[key].enabledByDefault || false
|
enabled: IS_REPORTER ?? plugins[key].required ?? plugins[key].enabledByDefault ?? false
|
||||||
};
|
};
|
||||||
|
|
||||||
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
|
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
|
||||||
|
@ -230,10 +230,6 @@ export function definePluginSettings<
|
||||||
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
|
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
|
||||||
return Settings.plugins[definedSettings.pluginName] as any;
|
return Settings.plugins[definedSettings.pluginName] as any;
|
||||||
},
|
},
|
||||||
get plain() {
|
|
||||||
if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized");
|
|
||||||
return PlainSettings.plugins[definedSettings.pluginName] as any;
|
|
||||||
},
|
|
||||||
use: settings => useSettings(
|
use: settings => useSettings(
|
||||||
settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[]
|
settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings<Settings>[]
|
||||||
).plugins[definedSettings.pluginName] as any,
|
).plugins[definedSettings.pluginName] as any,
|
||||||
|
|
|
@ -20,6 +20,8 @@ import { proxyLazy } from "@utils/lazy";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { findModuleId, proxyLazyWebpack, wreq } from "@webpack";
|
import { findModuleId, proxyLazyWebpack, wreq } from "@webpack";
|
||||||
|
|
||||||
|
import { Settings } from "./Settings";
|
||||||
|
|
||||||
interface UserSettingDefinition<T> {
|
interface UserSettingDefinition<T> {
|
||||||
/**
|
/**
|
||||||
* Get the setting value
|
* Get the setting value
|
||||||
|
@ -39,31 +41,31 @@ interface UserSettingDefinition<T> {
|
||||||
* Stateful React hook for this setting value
|
* Stateful React hook for this setting value
|
||||||
*/
|
*/
|
||||||
useSetting(): T;
|
useSetting(): T;
|
||||||
userSettingsAPIGroup: string;
|
userSettingDefinitionsAPIGroup: string;
|
||||||
userSettingsAPIName: string;
|
userSettingDefinitionsAPIName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UserSettings: Record<PropertyKey, UserSettingDefinition<any>> | undefined = proxyLazyWebpack(() => {
|
export const UserSettingsDefinitions: Record<PropertyKey, UserSettingDefinition<any>> | undefined = proxyLazyWebpack(() => {
|
||||||
const modId = findModuleId('"textAndImages","renderSpoilers"');
|
const modId = findModuleId('"textAndImages","renderSpoilers"');
|
||||||
if (modId == null) return new Logger("UserSettingsAPI ").error("Didn't find settings module.");
|
if (modId == null) return new Logger("UserSettingDefinitionsAPI").error("Didn't find settings definitions module.");
|
||||||
|
|
||||||
return wreq(modId as any);
|
return wreq(modId as any);
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the setting with the given setting group and name.
|
* Get the definition for a setting.
|
||||||
*
|
*
|
||||||
* @param group The setting group
|
* @param group The setting group
|
||||||
* @param name The name of the setting
|
* @param name The name of the setting
|
||||||
*/
|
*/
|
||||||
export function getUserSetting<T = any>(group: string, name: string): UserSettingDefinition<T> | undefined {
|
export function getUserSettingDefinition<T = any>(group: string, name: string): UserSettingDefinition<T> | undefined {
|
||||||
if (!Vencord.Plugins.isPluginEnabled("UserSettingsAPI")) throw new Error("Cannot use UserSettingsAPI without setting as dependency.");
|
if (!Settings.plugins.UserSettingDefinitionsAPI.enabled) throw new Error("Cannot use UserSettingDefinitionsAPI without setting as dependency.");
|
||||||
|
|
||||||
for (const key in UserSettings) {
|
for (const key in UserSettingsDefinitions) {
|
||||||
const userSetting = UserSettings[key];
|
const userSettingDefinition = UserSettingsDefinitions[key];
|
||||||
|
|
||||||
if (userSetting.userSettingsAPIGroup === group && userSetting.userSettingsAPIName === name) {
|
if (userSettingDefinition.userSettingDefinitionsAPIGroup === group && userSettingDefinition.userSettingDefinitionsAPIName === name) {
|
||||||
return userSetting;
|
return userSettingDefinition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,11 +73,11 @@ export function getUserSetting<T = any>(group: string, name: string): UserSettin
|
||||||
/**
|
/**
|
||||||
* {@link getUserSettingDefinition}, lazy.
|
* {@link getUserSettingDefinition}, lazy.
|
||||||
*
|
*
|
||||||
* Get the setting with the given setting group and name.
|
* Get the definition for a setting.
|
||||||
*
|
*
|
||||||
* @param group The setting group
|
* @param group The setting group
|
||||||
* @param name The name of the setting
|
* @param name The name of the setting
|
||||||
*/
|
*/
|
||||||
export function getUserSettingLazy<T = any>(group: string, name: string) {
|
export function getUserSettingDefinitionLazy<T = any>(group: string, name: string) {
|
||||||
return proxyLazy(() => getUserSetting<T>(group, name));
|
return proxyLazy(() => getUserSettingDefinition<T>(group, name));
|
||||||
}
|
}
|
|
@ -32,7 +32,7 @@ import * as $Notifications from "./Notifications";
|
||||||
import * as $ServerList from "./ServerList";
|
import * as $ServerList from "./ServerList";
|
||||||
import * as $Settings from "./Settings";
|
import * as $Settings from "./Settings";
|
||||||
import * as $Styles from "./Styles";
|
import * as $Styles from "./Styles";
|
||||||
import * as $UserSettings from "./UserSettings";
|
import * as $UserSettingDefinitions from "./UserSettingDefinitions";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An API allowing you to listen to Message Clicks or run your own logic
|
* An API allowing you to listen to Message Clicks or run your own logic
|
||||||
|
@ -119,6 +119,6 @@ export const ChatButtons = $ChatButtons;
|
||||||
export const MessageUpdater = $MessageUpdater;
|
export const MessageUpdater = $MessageUpdater;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An API allowing you to get an user setting
|
* An API allowing you to get the definition for an user setting
|
||||||
*/
|
*/
|
||||||
export const UserSettings = $UserSettings;
|
export const UserSettingDefinitions = $UserSettingDefinitions;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
.vc-expandableheader-center-flex {
|
.vc-expandableheader-center-flex {
|
||||||
display: flex;
|
display: flex;
|
||||||
place-items: center;
|
justify-items: center;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-expandableheader-btn {
|
.vc-expandableheader-btn {
|
||||||
|
|
|
@ -31,20 +31,10 @@ export interface ExpandableHeaderProps {
|
||||||
headerText: string;
|
headerText: string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
buttons?: React.ReactNode[];
|
buttons?: React.ReactNode[];
|
||||||
forceOpen?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ExpandableHeader({
|
export function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipText, defaultState = false, onDropDownClick, headerText }: ExpandableHeaderProps) {
|
||||||
children,
|
const [showContent, setShowContent] = useState(defaultState);
|
||||||
onMoreClick,
|
|
||||||
buttons,
|
|
||||||
moreTooltipText,
|
|
||||||
onDropDownClick,
|
|
||||||
headerText,
|
|
||||||
defaultState = false,
|
|
||||||
forceOpen = false,
|
|
||||||
}: ExpandableHeaderProps) {
|
|
||||||
const [showContent, setShowContent] = useState(defaultState || forceOpen);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -100,7 +90,6 @@ export function ExpandableHeader({
|
||||||
setShowContent(v => !v);
|
setShowContent(v => !v);
|
||||||
onDropDownClick?.(showContent);
|
onDropDownClick?.(showContent);
|
||||||
}}
|
}}
|
||||||
disabled={forceOpen}
|
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
width="24"
|
width="24"
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { CSSProperties } from "react";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
columns: number;
|
|
||||||
gap?: string;
|
|
||||||
inline?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Grid(props: Props & JSX.IntrinsicElements["div"]) {
|
|
||||||
const style: CSSProperties = {
|
|
||||||
display: props.inline ? "inline-grid" : "grid",
|
|
||||||
gridTemplateColumns: `repeat(${props.columns}, 1fr)`,
|
|
||||||
gap: props.gap,
|
|
||||||
...props.style
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div {...props} style={style}>
|
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -18,16 +18,19 @@
|
||||||
|
|
||||||
import "./iconStyles.css";
|
import "./iconStyles.css";
|
||||||
|
|
||||||
import { getIntlMessage, getTheme, Theme } from "@utils/discord";
|
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import type { PropsWithChildren } from "react";
|
import { i18n } from "@webpack/common";
|
||||||
|
import type { PropsWithChildren, SVGProps } from "react";
|
||||||
|
|
||||||
interface BaseIconProps extends IconProps {
|
interface BaseIconProps extends IconProps {
|
||||||
viewBox: string;
|
viewBox: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type IconProps = JSX.IntrinsicElements["svg"];
|
interface IconProps extends SVGProps<SVGSVGElement> {
|
||||||
type ImageProps = JSX.IntrinsicElements["img"];
|
className?: string;
|
||||||
|
height?: string | number;
|
||||||
|
width?: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
|
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
|
||||||
return (
|
return (
|
||||||
|
@ -64,7 +67,8 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Discord's copy icon, as seen in the user panel popout on the right of the username and in large code blocks
|
* Discord's copy icon, as seen in the user popout right of the username when clicking
|
||||||
|
* your own username in the bottom left user panel
|
||||||
*/
|
*/
|
||||||
export function CopyIcon(props: IconProps) {
|
export function CopyIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
|
@ -74,9 +78,8 @@ export function CopyIcon(props: IconProps) {
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<g fill="currentColor">
|
<g fill="currentColor">
|
||||||
<path d="M3 16a1 1 0 0 1-1-1v-5a8 8 0 0 1 8-8h5a1 1 0 0 1 1 1v.5a.5.5 0 0 1-.5.5H10a6 6 0 0 0-6 6v5.5a.5.5 0 0 1-.5.5H3Z" />
|
<path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1z" />
|
||||||
<path d="M6 18a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4v-4h-3a5 5 0 0 1-5-5V6h-4a4 4 0 0 0-4 4v8Z" />
|
<path d="M15 5H8c-1.1 0-1.99.9-1.99 2L6 21c0 1.1.89 2 1.99 2H19c1.1 0 2-.9 2-2V11l-6-6zM8 21V7h6v5h5v9H8z" />
|
||||||
<path d="M21.73 12a3 3 0 0 0-.6-.88l-4.25-4.24a3 3 0 0 0-.88-.61V9a3 3 0 0 0 3 3h2.73Z" />
|
|
||||||
</g>
|
</g>
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
|
@ -132,7 +135,7 @@ export function InfoIcon(props: IconProps) {
|
||||||
export function OwnerCrownIcon(props: IconProps) {
|
export function OwnerCrownIcon(props: IconProps) {
|
||||||
return (
|
return (
|
||||||
<Icon
|
<Icon
|
||||||
aria-label={getIntlMessage("GUILD_OWNER")}
|
aria-label={i18n.Messages.GUILD_OWNER}
|
||||||
{...props}
|
{...props}
|
||||||
className={classes(props.className, "vc-owner-crown-icon")}
|
className={classes(props.className, "vc-owner-crown-icon")}
|
||||||
role="img"
|
role="img"
|
||||||
|
@ -287,142 +290,3 @@ export function NoEntrySignIcon(props: IconProps) {
|
||||||
</Icon>
|
</Icon>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SafetyIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<Icon
|
|
||||||
{...props}
|
|
||||||
className={classes(props.className, "vc-safety-icon")}
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
fillRule="evenodd"
|
|
||||||
clipRule="evenodd"
|
|
||||||
d="M4.27 5.22A2.66 2.66 0 0 0 3 7.5v2.3c0 5.6 3.3 10.68 8.42 12.95.37.17.79.17 1.16 0A14.18 14.18 0 0 0 21 9.78V7.5c0-.93-.48-1.78-1.27-2.27l-6.17-3.76a3 3 0 0 0-3.12 0L4.27 5.22ZM6 7.68l6-3.66V12H6.22C6.08 11.28 6 10.54 6 9.78v-2.1Zm6 12.01V12h5.78A11.19 11.19 0 0 1 12 19.7Z"
|
|
||||||
/>
|
|
||||||
</Icon>
|
|
||||||
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function NotesIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<Icon
|
|
||||||
{...props}
|
|
||||||
className={classes(props.className, "vc-notes-icon")}
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M8 3C7.44771 3 7 3.44772 7 4V5C7 5.55228 7.44772 6 8 6H16C16.5523 6 17 5.55228 17 5V4C17 3.44772 16.5523 3 16 3H15.1245C14.7288 3 14.3535 2.82424 14.1002 2.52025L13.3668 1.64018C13.0288 1.23454 12.528 1 12 1C11.472 1 10.9712 1.23454 10.6332 1.64018L9.8998 2.52025C9.64647 2.82424 9.27121 3 8.8755 3H8Z"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
fillRule="evenodd"
|
|
||||||
clipRule="evenodd"
|
|
||||||
fill="currentColor"
|
|
||||||
d="M19 4.49996V4.99996C19 6.65681 17.6569 7.99996 16 7.99996H8C6.34315 7.99996 5 6.65681 5 4.99996V4.49996C5 4.22382 4.77446 3.99559 4.50209 4.04109C3.08221 4.27826 2 5.51273 2 6.99996V19C2 20.6568 3.34315 22 5 22H19C20.6569 22 22 20.6568 22 19V6.99996C22 5.51273 20.9178 4.27826 19.4979 4.04109C19.2255 3.99559 19 4.22382 19 4.49996ZM8 12C7.44772 12 7 12.4477 7 13C7 13.5522 7.44772 14 8 14H16C16.5523 14 17 13.5522 17 13C17 12.4477 16.5523 12 16 12H8ZM7 17C7 16.4477 7.44772 16 8 16H13C13.5523 16 14 16.4477 14 17C14 17.5522 13.5523 18 13 18H8C7.44772 18 7 17.5522 7 17Z"
|
|
||||||
/>
|
|
||||||
</Icon>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function FolderIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<Icon
|
|
||||||
{...props}
|
|
||||||
className={classes(props.className, "vc-folder-icon")}
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M2 5a3 3 0 0 1 3-3h3.93a2 2 0 0 1 1.66.9L12 5h7a3 3 0 0 1 3 3v11a3 3 0 0 1-3 3H5a3 3 0 0 1-3-3V5Z"
|
|
||||||
/>
|
|
||||||
</Icon>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function LogIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<Icon
|
|
||||||
{...props}
|
|
||||||
className={classes(props.className, "vc-log-icon")}
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
fillRule="evenodd"
|
|
||||||
clipRule="evenodd"
|
|
||||||
d="M3.11 8H6v10.82c0 .86.37 1.68 1 2.27.46.43 1.02.71 1.63.84A1 1 0 0 0 9 22h10a4 4 0 0 0 4-4v-1a2 2 0 0 0-2-2h-1V5a3 3 0 0 0-3-3H4.67c-.87 0-1.7.32-2.34.9-.63.6-1 1.42-1 2.28 0 .71.3 1.35.52 1.75a5.35 5.35 0 0 0 .48.7l.01.01h.01L3.11 7l-.76.65a1 1 0 0 0 .76.35Zm1.56-4c-.38 0-.72.14-.97.37-.24.23-.37.52-.37.81a1.69 1.69 0 0 0 .3.82H6v-.83c0-.29-.13-.58-.37-.8C5.4 4.14 5.04 4 4.67 4Zm5 13a3.58 3.58 0 0 1 0 3H19a2 2 0 0 0 2-2v-1H9.66ZM3.86 6.35ZM11 8a1 1 0 1 0 0 2h5a1 1 0 1 0 0-2h-5Zm-1 5a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2h-5a1 1 0 0 1-1-1Z"
|
|
||||||
/>
|
|
||||||
</Icon>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function RestartIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<Icon
|
|
||||||
{...props}
|
|
||||||
className={classes(props.className, "vc-restart-icon")}
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M4 12a8 8 0 0 1 14.93-4H15a1 1 0 1 0 0 2h6a1 1 0 0 0 1-1V3a1 1 0 1 0-2 0v3a9.98 9.98 0 0 0-18 6 10 10 0 0 0 16.29 7.78 1 1 0 0 0-1.26-1.56A8 8 0 0 1 4 12Z"
|
|
||||||
/>
|
|
||||||
</Icon>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PaintbrushIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<Icon
|
|
||||||
{...props}
|
|
||||||
className={classes(props.className, "vc-paintbrush-icon")}
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
fillRule="evenodd"
|
|
||||||
clipRule="evenodd"
|
|
||||||
d="M15.35 7.24C15.9 6.67 16 5.8 16 5a3 3 0 1 1 3 3c-.8 0-1.67.09-2.24.65a1.5 1.5 0 0 0 0 2.11l1.12 1.12a3 3 0 0 1 0 4.24l-5 5a3 3 0 0 1-4.25 0l-5.76-5.75a3 3 0 0 1 0-4.24l4.04-4.04.97-.97a3 3 0 0 1 4.24 0l1.12 1.12c.58.58 1.52.58 2.1 0ZM6.9 9.9 4.3 12.54a1 1 0 0 0 0 1.42l2.17 2.17.83-.84a1 1 0 0 1 1.42 1.42l-.84.83.59.59 1.83-1.84a1 1 0 0 1 1.42 1.42l-1.84 1.83.17.17a1 1 0 0 0 1.42 0l2.63-2.62L6.9 9.9Z"
|
|
||||||
/>
|
|
||||||
</Icon>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function PencilIcon(props: IconProps) {
|
|
||||||
return (
|
|
||||||
<Icon
|
|
||||||
{...props}
|
|
||||||
className={classes(props.className, "vc-pencil-icon")}
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="m13.96 5.46 4.58 4.58a1 1 0 0 0 1.42 0l1.38-1.38a2 2 0 0 0 0-2.82l-3.18-3.18a2 2 0 0 0-2.82 0l-1.38 1.38a1 1 0 0 0 0 1.42ZM2.11 20.16l.73-4.22a3 3 0 0 1 .83-1.61l7.87-7.87a1 1 0 0 1 1.42 0l4.58 4.58a1 1 0 0 1 0 1.42l-7.87 7.87a3 3 0 0 1-1.6.83l-4.23.73a1.5 1.5 0 0 1-1.73-1.73Z"
|
|
||||||
/>
|
|
||||||
</Icon>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
|
||||||
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
|
||||||
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
|
||||||
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
|
||||||
|
|
||||||
export function GithubIcon(props: ImageProps) {
|
|
||||||
const src = getTheme() === Theme.Light
|
|
||||||
? GithubIconLight
|
|
||||||
: GithubIconDark;
|
|
||||||
|
|
||||||
return <img {...props} src={src} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function WebsiteIcon(props: ImageProps) {
|
|
||||||
const src = getTheme() === Theme.Light
|
|
||||||
? WebsiteIconLight
|
|
||||||
: WebsiteIconDark;
|
|
||||||
|
|
||||||
return <img {...props} src={src} />;
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,16 +6,22 @@
|
||||||
|
|
||||||
import "./LinkIconButton.css";
|
import "./LinkIconButton.css";
|
||||||
|
|
||||||
|
import { getTheme, Theme } from "@utils/discord";
|
||||||
import { MaskedLink, Tooltip } from "@webpack/common";
|
import { MaskedLink, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
import { GithubIcon, WebsiteIcon } from "..";
|
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
||||||
|
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
||||||
|
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
||||||
|
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
||||||
|
|
||||||
export function GithubLinkIcon() {
|
export function GithubIcon() {
|
||||||
return <GithubIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
|
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
|
||||||
|
return <img src={src} aria-hidden className={"vc-settings-modal-link-icon"} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WebsiteLinkIcon() {
|
export function WebsiteIcon() {
|
||||||
return <WebsiteIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
|
const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
|
||||||
|
return <img src={src} aria-hidden className={"vc-settings-modal-link-icon"} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -35,5 +41,5 @@ function LinkIcon({ text, href, Icon }: Props & { Icon: React.ComponentType; })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const WebsiteButton = (props: Props) => <LinkIcon {...props} Icon={WebsiteLinkIcon} />;
|
export const WebsiteButton = (props: Props) => <LinkIcon {...props} Icon={WebsiteIcon} />;
|
||||||
export const GithubButton = (props: Props) => <LinkIcon {...props} Icon={GithubLinkIcon} />;
|
export const GithubButton = (props: Props) => <LinkIcon {...props} Icon={GithubIcon} />;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { gitRemote } from "@shared/vencordUserAgent";
|
||||||
import { proxyLazy } from "@utils/lazy";
|
import { proxyLazy } from "@utils/lazy";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes, isObjectEmpty } from "@utils/misc";
|
import { classes, isObjectEmpty } from "@utils/misc";
|
||||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
|
||||||
import { OptionType, Plugin } from "@utils/types";
|
import { OptionType, Plugin } from "@utils/types";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||||
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
||||||
|
@ -310,13 +310,3 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
</ModalRoot>
|
</ModalRoot>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function openPluginModal(plugin: Plugin, onRestartNeeded?: (pluginName: string) => void) {
|
|
||||||
openModal(modalProps => (
|
|
||||||
<PluginModal
|
|
||||||
{...modalProps}
|
|
||||||
plugin={plugin}
|
|
||||||
onRestartNeeded={() => onRestartNeeded?.(plugin.name)}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
|
||||||
import { OptionType, PluginOptionNumber } from "@utils/types";
|
import { OptionType, PluginOptionNumber } from "@utils/types";
|
||||||
import { Forms, React, TextInput } from "@webpack/common";
|
import { Forms, React, TextInput } from "@webpack/common";
|
||||||
|
|
||||||
|
@ -56,8 +54,7 @@ export function SettingNumericComponent({ option, pluginSettings, definedSetting
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
|
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
||||||
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
|
|
||||||
<TextInput
|
<TextInput
|
||||||
type="number"
|
type="number"
|
||||||
pattern="-?[0-9]+"
|
pattern="-?[0-9]+"
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
|
||||||
import { PluginOptionSelect } from "@utils/types";
|
import { PluginOptionSelect } from "@utils/types";
|
||||||
import { Forms, React, Select } from "@webpack/common";
|
import { Forms, React, Select } from "@webpack/common";
|
||||||
|
|
||||||
|
@ -46,8 +44,7 @@ export function SettingSelectComponent({ option, pluginSettings, definedSettings
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
|
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
||||||
<Forms.FormText className={Margins.bottom16} type="description">{option.description}</Forms.FormText>
|
|
||||||
<Select
|
<Select
|
||||||
isDisabled={option.disabled?.call(definedSettings) ?? false}
|
isDisabled={option.disabled?.call(definedSettings) ?? false}
|
||||||
options={option.options}
|
options={option.options}
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
|
||||||
import { PluginOptionSlider } from "@utils/types";
|
import { PluginOptionSlider } from "@utils/types";
|
||||||
import { Forms, React, Slider } from "@webpack/common";
|
import { Forms, React, Slider } from "@webpack/common";
|
||||||
|
|
||||||
|
@ -52,8 +50,7 @@ export function SettingSliderComponent({ option, pluginSettings, definedSettings
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
|
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
||||||
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
|
|
||||||
<Slider
|
<Slider
|
||||||
disabled={option.disabled?.call(definedSettings) ?? false}
|
disabled={option.disabled?.call(definedSettings) ?? false}
|
||||||
markers={option.markers}
|
markers={option.markers}
|
||||||
|
|
|
@ -16,8 +16,6 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { wordsFromCamel, wordsToTitle } from "@utils/text";
|
|
||||||
import { PluginOptionString } from "@utils/types";
|
import { PluginOptionString } from "@utils/types";
|
||||||
import { Forms, React, TextInput } from "@webpack/common";
|
import { Forms, React, TextInput } from "@webpack/common";
|
||||||
|
|
||||||
|
@ -43,8 +41,7 @@ export function SettingTextComponent({ option, pluginSettings, definedSettings,
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection>
|
<Forms.FormSection>
|
||||||
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
|
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
||||||
<Forms.FormText className={Margins.bottom20} type="description">{option.description}</Forms.FormText>
|
|
||||||
<TextInput
|
<TextInput
|
||||||
type="text"
|
type="text"
|
||||||
value={state}
|
value={state}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { showNotice } from "@api/Notices";
|
||||||
import { Settings, useSettings } from "@api/Settings";
|
import { Settings, useSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { CogWheel, InfoIcon } from "@components/Icons";
|
import { CogWheel, InfoIcon } from "@components/Icons";
|
||||||
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
import PluginModal from "@components/PluginSettings/PluginModal";
|
||||||
import { AddonCard } from "@components/VencordSettings/AddonCard";
|
import { AddonCard } from "@components/VencordSettings/AddonCard";
|
||||||
import { SettingsTab } from "@components/VencordSettings/shared";
|
import { SettingsTab } from "@components/VencordSettings/shared";
|
||||||
import { ChangeList } from "@utils/ChangeList";
|
import { ChangeList } from "@utils/ChangeList";
|
||||||
|
@ -31,12 +31,13 @@ import { proxyLazy } from "@utils/lazy";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes, isObjectEmpty } from "@utils/misc";
|
import { classes, isObjectEmpty } from "@utils/misc";
|
||||||
|
import { openModalLazy } from "@utils/modal";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Plugin } from "@utils/types";
|
import { Plugin } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
|
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
import Plugins, { ExcludedPlugins } from "~plugins";
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
// Avoid circular dependency
|
// Avoid circular dependency
|
||||||
const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("../../plugins"));
|
const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("../../plugins"));
|
||||||
|
@ -44,7 +45,7 @@ const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() =>
|
||||||
const cl = classNameFactory("vc-plugins-");
|
const cl = classNameFactory("vc-plugins-");
|
||||||
const logger = new Logger("PluginSettings", "#a6d189");
|
const logger = new Logger("PluginSettings", "#a6d189");
|
||||||
|
|
||||||
const InputStyles = findByPropsLazy("inputWrapper", "inputDefault", "error");
|
const InputStyles = findByPropsLazy("inputDefault", "inputWrapper");
|
||||||
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
|
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
|
||||||
|
|
||||||
|
|
||||||
|
@ -93,7 +94,15 @@ interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
|
||||||
export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
|
export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
|
||||||
const settings = Settings.plugins[plugin.name];
|
const settings = Settings.plugins[plugin.name];
|
||||||
|
|
||||||
const isEnabled = () => Vencord.Plugins.isPluginEnabled(plugin.name);
|
const isEnabled = () => settings.enabled ?? false;
|
||||||
|
|
||||||
|
function openModal() {
|
||||||
|
openModalLazy(async () => {
|
||||||
|
return modalProps => {
|
||||||
|
return <PluginModal {...modalProps} plugin={plugin} onRestartNeeded={() => onRestartNeeded(plugin.name)} />;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function toggleEnabled() {
|
function toggleEnabled() {
|
||||||
const wasEnabled = isEnabled();
|
const wasEnabled = isEnabled();
|
||||||
|
@ -151,11 +160,7 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
infoButton={
|
infoButton={
|
||||||
<button
|
<button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}>
|
||||||
role="switch"
|
|
||||||
onClick={() => openPluginModal(plugin, onRestartNeeded)}
|
|
||||||
className={classes(ButtonClasses.button, cl("info-button"))}
|
|
||||||
>
|
|
||||||
{plugin.options && !isObjectEmpty(plugin.options)
|
{plugin.options && !isObjectEmpty(plugin.options)
|
||||||
? <CogWheel />
|
? <CogWheel />
|
||||||
: <InfoIcon />}
|
: <InfoIcon />}
|
||||||
|
@ -172,37 +177,6 @@ const enum SearchStatus {
|
||||||
NEW
|
NEW
|
||||||
}
|
}
|
||||||
|
|
||||||
function ExcludedPluginsList({ search }: { search: string; }) {
|
|
||||||
const matchingExcludedPlugins = Object.entries(ExcludedPlugins)
|
|
||||||
.filter(([name]) => name.toLowerCase().includes(search));
|
|
||||||
|
|
||||||
const ExcludedReasons: Record<"web" | "discordDesktop" | "vencordDesktop" | "desktop" | "dev", string> = {
|
|
||||||
desktop: "Discord Desktop app or Vesktop",
|
|
||||||
discordDesktop: "Discord Desktop app",
|
|
||||||
vencordDesktop: "Vesktop app",
|
|
||||||
web: "Vesktop app and the Web version of Discord",
|
|
||||||
dev: "Developer version of Vencord"
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Text variant="text-md/normal" className={Margins.top16}>
|
|
||||||
{matchingExcludedPlugins.length
|
|
||||||
? <>
|
|
||||||
<Forms.FormText>Are you looking for:</Forms.FormText>
|
|
||||||
<ul>
|
|
||||||
{matchingExcludedPlugins.map(([name, reason]) => (
|
|
||||||
<li key={name}>
|
|
||||||
<b>{name}</b>: Only available on the {ExcludedReasons[reason]}
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</>
|
|
||||||
: "No plugins meet the search criteria."
|
|
||||||
}
|
|
||||||
</Text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PluginSettings() {
|
export default function PluginSettings() {
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const changes = React.useMemo(() => new ChangeList<string>(), []);
|
const changes = React.useMemo(() => new ChangeList<string>(), []);
|
||||||
|
@ -241,27 +215,26 @@ export default function PluginSettings() {
|
||||||
return o;
|
return o;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const sortedPlugins = useMemo(() => Object.values(Plugins)
|
const sortedPlugins = React.useMemo(() => Object.values(Plugins)
|
||||||
.sort((a, b) => a.name.localeCompare(b.name)), []);
|
.sort((a, b) => a.name.localeCompare(b.name)), []);
|
||||||
|
|
||||||
const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL });
|
const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL });
|
||||||
|
|
||||||
const search = searchValue.value.toLowerCase();
|
|
||||||
const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query }));
|
const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query }));
|
||||||
const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));
|
const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));
|
||||||
|
|
||||||
const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => {
|
const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => {
|
||||||
const { status } = searchValue;
|
const enabled = settings.plugins[plugin.name]?.enabled;
|
||||||
const enabled = Vencord.Plugins.isPluginEnabled(plugin.name);
|
if (enabled && searchValue.status === SearchStatus.DISABLED) return false;
|
||||||
if (enabled && status === SearchStatus.DISABLED) return false;
|
if (!enabled && searchValue.status === SearchStatus.ENABLED) return false;
|
||||||
if (!enabled && status === SearchStatus.ENABLED) return false;
|
if (searchValue.status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false;
|
||||||
if (status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false;
|
if (!searchValue.value.length) return true;
|
||||||
if (!search.length) return true;
|
|
||||||
|
|
||||||
|
const v = searchValue.value.toLowerCase();
|
||||||
return (
|
return (
|
||||||
plugin.name.toLowerCase().includes(search) ||
|
plugin.name.toLowerCase().includes(v) ||
|
||||||
plugin.description.toLowerCase().includes(search) ||
|
plugin.description.toLowerCase().includes(v) ||
|
||||||
plugin.tags?.some(t => t.toLowerCase().includes(search))
|
plugin.tags?.some(t => t.toLowerCase().includes(v))
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -282,20 +255,23 @@ export default function PluginSettings() {
|
||||||
return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
|
return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const plugins = [] as JSX.Element[];
|
type P = JSX.Element | JSX.Element[];
|
||||||
const requiredPlugins = [] as JSX.Element[];
|
let plugins: P, requiredPlugins: P;
|
||||||
|
if (sortedPlugins?.length) {
|
||||||
|
plugins = [];
|
||||||
|
requiredPlugins = [];
|
||||||
|
|
||||||
const showApi = searchValue.value.includes("API");
|
const showApi = searchValue.value === "API";
|
||||||
for (const p of sortedPlugins) {
|
for (const p of sortedPlugins) {
|
||||||
if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi))
|
if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!pluginFilter(p)) continue;
|
if (!pluginFilter(p)) continue;
|
||||||
|
|
||||||
const isRequired = p.required || p.isDependency || depMap[p.name]?.some(d => settings.plugins[d].enabled);
|
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
|
||||||
|
|
||||||
if (isRequired) {
|
if (isRequired) {
|
||||||
const tooltipText = p.required || !depMap[p.name]
|
const tooltipText = p.required
|
||||||
? "This plugin is required for Vencord to function."
|
? "This plugin is required for Vencord to function."
|
||||||
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
|
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
|
||||||
|
|
||||||
|
@ -308,7 +284,6 @@ export default function PluginSettings() {
|
||||||
onRestartNeeded={name => changes.handleChange(name)}
|
onRestartNeeded={name => changes.handleChange(name)}
|
||||||
disabled={true}
|
disabled={true}
|
||||||
plugin={p}
|
plugin={p}
|
||||||
key={p.name}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -324,6 +299,10 @@ export default function PluginSettings() {
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
plugins = requiredPlugins = <Text variant="text-md/normal">No plugins meet search criteria.</Text>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -334,8 +313,8 @@ export default function PluginSettings() {
|
||||||
Filters
|
Filters
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
|
|
||||||
<div className={classes(Margins.bottom20, cl("filter-controls"))}>
|
<div className={cl("filter-controls")}>
|
||||||
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} />
|
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} className={Margins.bottom20} />
|
||||||
<div className={InputStyles.inputWrapper}>
|
<div className={InputStyles.inputWrapper}>
|
||||||
<Select
|
<Select
|
||||||
options={[
|
options={[
|
||||||
|
@ -348,25 +327,15 @@ export default function PluginSettings() {
|
||||||
select={onStatusChange}
|
select={onStatusChange}
|
||||||
isSelected={v => v === searchValue.status}
|
isSelected={v => v === searchValue.status}
|
||||||
closeOnSelect={true}
|
closeOnSelect={true}
|
||||||
className={InputStyles.inputDefault}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle>
|
||||||
|
|
||||||
{plugins.length || requiredPlugins.length
|
|
||||||
? (
|
|
||||||
<div className={cl("grid")}>
|
<div className={cl("grid")}>
|
||||||
{plugins.length
|
{plugins}
|
||||||
? plugins
|
|
||||||
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
|
||||||
: <ExcludedPluginsList search={search} />
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
<Forms.FormDivider className={Margins.top20} />
|
<Forms.FormDivider className={Margins.top20} />
|
||||||
|
|
||||||
|
@ -374,10 +343,7 @@ export default function PluginSettings() {
|
||||||
Required Plugins
|
Required Plugins
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
<div className={cl("grid")}>
|
<div className={cl("grid")}>
|
||||||
{requiredPlugins.length
|
{requiredPlugins}
|
||||||
? requiredPlugins
|
|
||||||
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
|
|
||||||
}
|
|
||||||
</div>
|
</div>
|
||||||
</SettingsTab >
|
</SettingsTab >
|
||||||
);
|
);
|
||||||
|
|
|
@ -19,7 +19,6 @@
|
||||||
import { showNotification } from "@api/Notifications";
|
import { showNotification } from "@api/Notifications";
|
||||||
import { Settings, useSettings } from "@api/Settings";
|
import { Settings, useSettings } from "@api/Settings";
|
||||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
import { CheckedTextInput } from "@components/CheckedTextInput";
|
||||||
import { Grid } from "@components/Grid";
|
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
|
import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
|
@ -86,9 +85,7 @@ function SettingsSyncSection() {
|
||||||
size={Button.Sizes.SMALL}
|
size={Button.Sizes.SMALL}
|
||||||
disabled={!sectionEnabled}
|
disabled={!sectionEnabled}
|
||||||
onClick={() => putCloudSettings(true)}
|
onClick={() => putCloudSettings(true)}
|
||||||
>
|
>Sync to Cloud</Button>
|
||||||
Sync to Cloud
|
|
||||||
</Button>
|
|
||||||
<Tooltip text="This will overwrite your local settings with the ones on the cloud. Use wisely!">
|
<Tooltip text="This will overwrite your local settings with the ones on the cloud. Use wisely!">
|
||||||
{({ onMouseLeave, onMouseEnter }) => (
|
{({ onMouseLeave, onMouseEnter }) => (
|
||||||
<Button
|
<Button
|
||||||
|
@ -98,9 +95,7 @@ function SettingsSyncSection() {
|
||||||
color={Button.Colors.RED}
|
color={Button.Colors.RED}
|
||||||
disabled={!sectionEnabled}
|
disabled={!sectionEnabled}
|
||||||
onClick={() => getCloudSettings(true, true)}
|
onClick={() => getCloudSettings(true, true)}
|
||||||
>
|
>Sync from Cloud</Button>
|
||||||
Sync from Cloud
|
|
||||||
</Button>
|
|
||||||
)}
|
)}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Button
|
<Button
|
||||||
|
@ -108,9 +103,7 @@ function SettingsSyncSection() {
|
||||||
color={Button.Colors.RED}
|
color={Button.Colors.RED}
|
||||||
disabled={!sectionEnabled}
|
disabled={!sectionEnabled}
|
||||||
onClick={() => deleteCloudSettings()}
|
onClick={() => deleteCloudSettings()}
|
||||||
>
|
>Delete Cloud Settings</Button>
|
||||||
Delete Cloud Settings
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</Forms.FormSection>
|
</Forms.FormSection>
|
||||||
);
|
);
|
||||||
|
@ -131,12 +124,7 @@ function CloudTab() {
|
||||||
<Switch
|
<Switch
|
||||||
key="backend"
|
key="backend"
|
||||||
value={settings.cloud.authenticated}
|
value={settings.cloud.authenticated}
|
||||||
onChange={v => {
|
onChange={v => { v && authorizeCloud(); if (!v) settings.cloud.authenticated = v; }}
|
||||||
if (v)
|
|
||||||
authorizeCloud();
|
|
||||||
else
|
|
||||||
settings.cloud.authenticated = v;
|
|
||||||
}}
|
|
||||||
note="This will request authorization if you have not yet set up cloud integrations."
|
note="This will request authorization if you have not yet set up cloud integrations."
|
||||||
>
|
>
|
||||||
Enable Cloud Integrations
|
Enable Cloud Integrations
|
||||||
|
@ -148,27 +136,11 @@ function CloudTab() {
|
||||||
<CheckedTextInput
|
<CheckedTextInput
|
||||||
key="backendUrl"
|
key="backendUrl"
|
||||||
value={settings.cloud.url}
|
value={settings.cloud.url}
|
||||||
onChange={async v => {
|
onChange={v => { settings.cloud.url = v; settings.cloud.authenticated = false; deauthorizeCloud(); }}
|
||||||
settings.cloud.url = v;
|
|
||||||
settings.cloud.authenticated = false;
|
|
||||||
deauthorizeCloud();
|
|
||||||
}}
|
|
||||||
validate={validateUrl}
|
validate={validateUrl}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Grid columns={2} gap="1em" className={Margins.top8}>
|
|
||||||
<Button
|
|
||||||
size={Button.Sizes.MEDIUM}
|
|
||||||
disabled={!settings.cloud.authenticated}
|
|
||||||
onClick={async () => {
|
|
||||||
await deauthorizeCloud();
|
|
||||||
settings.cloud.authenticated = false;
|
|
||||||
await authorizeCloud();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Reauthorise
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
|
className={Margins.top8}
|
||||||
size={Button.Sizes.MEDIUM}
|
size={Button.Sizes.MEDIUM}
|
||||||
color={Button.Colors.RED}
|
color={Button.Colors.RED}
|
||||||
disabled={!settings.cloud.authenticated}
|
disabled={!settings.cloud.authenticated}
|
||||||
|
@ -180,11 +152,7 @@ function CloudTab() {
|
||||||
confirmColor: "vc-cloud-erase-data-danger-btn",
|
confirmColor: "vc-cloud-erase-data-danger-btn",
|
||||||
cancelText: "Nevermind"
|
cancelText: "Nevermind"
|
||||||
})}
|
})}
|
||||||
>
|
>Erase All Data</Button>
|
||||||
Erase All Data
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Forms.FormDivider className={Margins.top16} />
|
<Forms.FormDivider className={Margins.top16} />
|
||||||
</Forms.FormSection >
|
</Forms.FormSection >
|
||||||
<SettingsSyncSection />
|
<SettingsSyncSection />
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { useSettings } from "@api/Settings";
|
|
||||||
import { Margins } from "@utils/margins";
|
|
||||||
import { identity } from "@utils/misc";
|
|
||||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
|
||||||
import { Forms, Select, Slider, Text } from "@webpack/common";
|
|
||||||
|
|
||||||
import { ErrorCard } from "..";
|
|
||||||
|
|
||||||
export function NotificationSettings() {
|
|
||||||
const settings = useSettings().notifications;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div style={{ padding: "1em 0" }}>
|
|
||||||
<Forms.FormTitle tag="h5">Notification Style</Forms.FormTitle>
|
|
||||||
{settings.useNative !== "never" && Notification?.permission === "denied" && (
|
|
||||||
<ErrorCard style={{ padding: "1em" }} className={Margins.bottom8}>
|
|
||||||
<Forms.FormTitle tag="h5">Desktop Notification Permission denied</Forms.FormTitle>
|
|
||||||
<Forms.FormText>You have denied Notification Permissions. Thus, Desktop notifications will not work!</Forms.FormText>
|
|
||||||
</ErrorCard>
|
|
||||||
)}
|
|
||||||
<Forms.FormText className={Margins.bottom8}>
|
|
||||||
Some plugins may show you notifications. These come in two styles:
|
|
||||||
<ul>
|
|
||||||
<li><strong>Vencord Notifications</strong>: These are in-app notifications</li>
|
|
||||||
<li><strong>Desktop Notifications</strong>: Native Desktop notifications (like when you get a ping)</li>
|
|
||||||
</ul>
|
|
||||||
</Forms.FormText>
|
|
||||||
<Select
|
|
||||||
placeholder="Notification Style"
|
|
||||||
options={[
|
|
||||||
{ label: "Only use Desktop notifications when Discord is not focused", value: "not-focused", default: true },
|
|
||||||
{ label: "Always use Desktop notifications", value: "always" },
|
|
||||||
{ label: "Always use Vencord notifications", value: "never" },
|
|
||||||
] satisfies Array<{ value: typeof settings["useNative"]; } & Record<string, any>>}
|
|
||||||
closeOnSelect={true}
|
|
||||||
select={v => settings.useNative = v}
|
|
||||||
isSelected={v => v === settings.useNative}
|
|
||||||
serialize={identity}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Position</Forms.FormTitle>
|
|
||||||
<Select
|
|
||||||
isDisabled={settings.useNative === "always"}
|
|
||||||
placeholder="Notification Position"
|
|
||||||
options={[
|
|
||||||
{ label: "Bottom Right", value: "bottom-right", default: true },
|
|
||||||
{ label: "Top Right", value: "top-right" },
|
|
||||||
] satisfies Array<{ value: typeof settings["position"]; } & Record<string, any>>}
|
|
||||||
select={v => settings.position = v}
|
|
||||||
isSelected={v => v === settings.position}
|
|
||||||
serialize={identity}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Timeout</Forms.FormTitle>
|
|
||||||
<Forms.FormText className={Margins.bottom16}>Set to 0s to never automatically time out</Forms.FormText>
|
|
||||||
<Slider
|
|
||||||
disabled={settings.useNative === "always"}
|
|
||||||
markers={[0, 1000, 2500, 5000, 10_000, 20_000]}
|
|
||||||
minValue={0}
|
|
||||||
maxValue={20_000}
|
|
||||||
initialValue={settings.timeout}
|
|
||||||
onValueChange={v => settings.timeout = v}
|
|
||||||
onValueRender={v => (v / 1000).toFixed(2) + "s"}
|
|
||||||
onMarkerRender={v => (v / 1000) + "s"}
|
|
||||||
stickToMarkers={false}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Log Limit</Forms.FormTitle>
|
|
||||||
<Forms.FormText className={Margins.bottom16}>
|
|
||||||
The amount of notifications to save in the log until old ones are removed.
|
|
||||||
Set to <code>0</code> to disable Notification log and <code>∞</code> to never automatically remove old Notifications
|
|
||||||
</Forms.FormText>
|
|
||||||
<Slider
|
|
||||||
markers={[0, 25, 50, 75, 100, 200]}
|
|
||||||
minValue={0}
|
|
||||||
maxValue={200}
|
|
||||||
stickToMarkers={true}
|
|
||||||
initialValue={settings.logLimit}
|
|
||||||
onValueChange={v => settings.logLimit = v}
|
|
||||||
onValueRender={v => v === 200 ? "∞" : v}
|
|
||||||
onMarkerRender={v => v === 200 ? "∞" : v}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function openNotificationSettingsModal() {
|
|
||||||
openModal(props => (
|
|
||||||
<ModalRoot {...props} size={ModalSize.MEDIUM}>
|
|
||||||
<ModalHeader>
|
|
||||||
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>Notification Settings</Text>
|
|
||||||
<ModalCloseButton onClick={props.onClose} />
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalContent>
|
|
||||||
<NotificationSettings />
|
|
||||||
</ModalContent>
|
|
||||||
</ModalRoot>
|
|
||||||
));
|
|
||||||
}
|
|
|
@ -247,7 +247,7 @@ function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: Fu
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const parsed = (0, eval)(`([${fullPatch}][0])`) as Patch;
|
const parsed = (0, eval)(`(${fullPatch})`) as Patch;
|
||||||
|
|
||||||
if (!parsed.find) throw new Error("No 'find' field");
|
if (!parsed.find) throw new Error("No 'find' field");
|
||||||
if (!parsed.replacement) throw new Error("No 'replacement' field");
|
if (!parsed.replacement) throw new Error("No 'replacement' field");
|
||||||
|
@ -382,7 +382,6 @@ function PatchHelper() {
|
||||||
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
|
||||||
<CodeBlock lang="js" content={code} />
|
<CodeBlock lang="js" content={code} />
|
||||||
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
|
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
|
||||||
<Button className={Margins.top8} onClick={() => Clipboard.copy("```ts\n" + code + "\n```")}>Copy as Codeblock</Button>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</SettingsTab>
|
</SettingsTab>
|
||||||
|
|
|
@ -16,25 +16,24 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Settings, useSettings } from "@api/Settings";
|
import { useSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { DeleteIcon, FolderIcon, PaintbrushIcon, PencilIcon, PlusIcon, RestartIcon } from "@components/Icons";
|
import { DeleteIcon } from "@components/Icons";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
import PluginModal from "@components/PluginSettings/PluginModal";
|
||||||
import type { UserThemeHeader } from "@main/themes";
|
import type { UserThemeHeader } from "@main/themes";
|
||||||
import { openInviteModal } from "@utils/discord";
|
import { openInviteModal } from "@utils/discord";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
|
import { classes } from "@utils/misc";
|
||||||
|
import { openModal } from "@utils/modal";
|
||||||
import { showItemInFolder } from "@utils/native";
|
import { showItemInFolder } from "@utils/native";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { findLazy } from "@webpack";
|
import { findByPropsLazy, findLazy } from "@webpack";
|
||||||
import { Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
import { Button, Card, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
||||||
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
|
||||||
|
|
||||||
import { AddonCard } from "./AddonCard";
|
import { AddonCard } from "./AddonCard";
|
||||||
import { QuickAction, QuickActionCard } from "./quickActions";
|
|
||||||
import { SettingsTab, wrapTab } from "./shared";
|
import { SettingsTab, wrapTab } from "./shared";
|
||||||
|
|
||||||
type FileInput = ComponentType<{
|
type FileInput = ComponentType<{
|
||||||
|
@ -44,7 +43,9 @@ type FileInput = ComponentType<{
|
||||||
filters?: { name?: string; extensions: string[]; }[];
|
filters?: { name?: string; extensions: string[]; }[];
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
const InviteActions = findByPropsLazy("resolveInvite");
|
||||||
const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
|
const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
|
||||||
|
const TextAreaProps = findLazy(m => typeof m.textarea === "string");
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-theme-");
|
const cl = classNameFactory("vc-settings-theme-");
|
||||||
|
|
||||||
|
@ -77,16 +78,8 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
|
||||||
<Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle>
|
<Forms.FormTitle className={Margins.top20} tag="h5">Validator</Forms.FormTitle>
|
||||||
<Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText>
|
<Forms.FormText>This section will tell you whether your themes can successfully be loaded</Forms.FormText>
|
||||||
<div>
|
<div>
|
||||||
{themeLinks.map(rawLink => {
|
{themeLinks.map(link => (
|
||||||
const { label, link } = (() => {
|
<Card style={{
|
||||||
const match = /^@(light|dark) (.*)/.exec(rawLink);
|
|
||||||
if (!match) return { label: rawLink, link: rawLink };
|
|
||||||
|
|
||||||
const [, mode, link] = match;
|
|
||||||
return { label: `[${mode} mode only] ${link}`, link };
|
|
||||||
})();
|
|
||||||
|
|
||||||
return <Card style={{
|
|
||||||
padding: ".5em",
|
padding: ".5em",
|
||||||
marginBottom: ".5em",
|
marginBottom: ".5em",
|
||||||
marginTop: ".5em"
|
marginTop: ".5em"
|
||||||
|
@ -94,11 +87,11 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) {
|
||||||
<Forms.FormTitle tag="h5" style={{
|
<Forms.FormTitle tag="h5" style={{
|
||||||
overflowWrap: "break-word"
|
overflowWrap: "break-word"
|
||||||
}}>
|
}}>
|
||||||
{label}
|
{link}
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
<Validator link={link} />
|
<Validator link={link} />
|
||||||
</Card>;
|
</Card>
|
||||||
})}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
@ -220,13 +213,14 @@ function ThemesTab() {
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Forms.FormSection title="Local Themes">
|
<Forms.FormSection title="Local Themes">
|
||||||
<QuickActionCard>
|
<Card className="vc-settings-quick-actions-card">
|
||||||
<>
|
<>
|
||||||
{IS_WEB ?
|
{IS_WEB ?
|
||||||
(
|
(
|
||||||
<QuickAction
|
<Button
|
||||||
text={
|
size={Button.Sizes.SMALL}
|
||||||
<span style={{ position: "relative" }}>
|
disabled={themeDirPending}
|
||||||
|
>
|
||||||
Upload Theme
|
Upload Theme
|
||||||
<FileInput
|
<FileInput
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
|
@ -234,38 +228,45 @@ function ThemesTab() {
|
||||||
multiple={true}
|
multiple={true}
|
||||||
filters={[{ extensions: ["css"] }]}
|
filters={[{ extensions: ["css"] }]}
|
||||||
/>
|
/>
|
||||||
</span>
|
</Button>
|
||||||
}
|
|
||||||
Icon={PlusIcon}
|
|
||||||
/>
|
|
||||||
) : (
|
) : (
|
||||||
<QuickAction
|
<Button
|
||||||
text="Open Themes Folder"
|
onClick={() => showItemInFolder(themeDir!)}
|
||||||
action={() => showItemInFolder(themeDir!)}
|
size={Button.Sizes.SMALL}
|
||||||
disabled={themeDirPending}
|
disabled={themeDirPending}
|
||||||
Icon={FolderIcon}
|
>
|
||||||
/>
|
Open Themes Folder
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
<QuickAction
|
<Button
|
||||||
text="Load missing Themes"
|
onClick={refreshLocalThemes}
|
||||||
action={refreshLocalThemes}
|
size={Button.Sizes.SMALL}
|
||||||
Icon={RestartIcon}
|
>
|
||||||
/>
|
Load missing Themes
|
||||||
<QuickAction
|
</Button>
|
||||||
text="Edit QuickCSS"
|
<Button
|
||||||
action={() => VencordNative.quickCss.openEditor()}
|
onClick={() => VencordNative.quickCss.openEditor()}
|
||||||
Icon={PaintbrushIcon}
|
size={Button.Sizes.SMALL}
|
||||||
/>
|
>
|
||||||
|
Edit QuickCSS
|
||||||
|
</Button>
|
||||||
|
|
||||||
{Settings.plugins.ClientTheme.enabled && (
|
{Vencord.Settings.plugins.ClientTheme.enabled && (
|
||||||
<QuickAction
|
<Button
|
||||||
text="Edit ClientTheme"
|
onClick={() => openModal(modalProps => (
|
||||||
action={() => openPluginModal(Plugins.ClientTheme)}
|
<PluginModal
|
||||||
Icon={PencilIcon}
|
{...modalProps}
|
||||||
|
plugin={Vencord.Plugins.plugins.ClientTheme}
|
||||||
|
onRestartNeeded={() => { }}
|
||||||
/>
|
/>
|
||||||
|
))}
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
>
|
||||||
|
Edit ClientTheme
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
</QuickActionCard>
|
</Card>
|
||||||
|
|
||||||
<div className={cl("grid")}>
|
<div className={cl("grid")}>
|
||||||
{userThemes?.map(theme => (
|
{userThemes?.map(theme => (
|
||||||
|
@ -304,7 +305,6 @@ function ThemesTab() {
|
||||||
<Card className="vc-settings-card vc-text-selectable">
|
<Card className="vc-settings-card vc-text-selectable">
|
||||||
<Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle>
|
<Forms.FormTitle tag="h5">Paste links to css files here</Forms.FormTitle>
|
||||||
<Forms.FormText>One link per line</Forms.FormText>
|
<Forms.FormText>One link per line</Forms.FormText>
|
||||||
<Forms.FormText>You can prefix lines with @light or @dark to toggle them based on your Discord theme</Forms.FormText>
|
|
||||||
<Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText>
|
<Forms.FormText>Make sure to use direct links to files (raw or github.io)!</Forms.FormText>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
@ -312,7 +312,7 @@ function ThemesTab() {
|
||||||
<TextArea
|
<TextArea
|
||||||
value={themeText}
|
value={themeText}
|
||||||
onChange={setThemeText}
|
onChange={setThemeText}
|
||||||
className={"vc-settings-theme-links"}
|
className={classes(TextAreaProps.textarea, "vc-settings-theme-links")}
|
||||||
placeholder="Theme Links"
|
placeholder="Theme Links"
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
|
|
|
@ -17,20 +17,16 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
||||||
import { useSettings } from "@api/Settings";
|
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 { openPluginModal } from "@components/PluginSettings/PluginModal";
|
import { ErrorCard } from "@components/ErrorCard";
|
||||||
import { gitRemote } from "@shared/vencordUserAgent";
|
|
||||||
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";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Button, Card, Forms, React, Select, Switch } from "@webpack/common";
|
import { Button, Card, Forms, React, Select, Slider, Switch } from "@webpack/common";
|
||||||
|
|
||||||
import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "..";
|
|
||||||
import { openNotificationSettingsModal } from "./NotificationSettings";
|
|
||||||
import { QuickAction, QuickActionCard } from "./quickActions";
|
|
||||||
import { SettingsTab, wrapTab } from "./shared";
|
import { SettingsTab, wrapTab } from "./shared";
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-");
|
const cl = classNameFactory("vc-settings-");
|
||||||
|
@ -42,7 +38,6 @@ type KeysOfType<Object, Type> = {
|
||||||
[K in keyof Object]: Object[K] extends Type ? K : never;
|
[K in keyof Object]: Object[K] extends Type ? K : never;
|
||||||
}[keyof Object];
|
}[keyof Object];
|
||||||
|
|
||||||
|
|
||||||
function VencordSettings() {
|
function VencordSettings() {
|
||||||
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
|
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
|
||||||
fallbackValue: "Loading..."
|
fallbackValue: "Loading..."
|
||||||
|
@ -83,7 +78,7 @@ function VencordSettings() {
|
||||||
!IS_WEB && {
|
!IS_WEB && {
|
||||||
key: "transparent",
|
key: "transparent",
|
||||||
title: "Enable window transparency.",
|
title: "Enable window transparency.",
|
||||||
note: "You need a theme that supports transparency or this will do nothing. WILL STOP THE WINDOW FROM BEING RESIZABLE!! Requires a full restart"
|
note: "You need a theme that supports transparency or this will do nothing. Will stop the window from being resizable. Requires a full restart"
|
||||||
},
|
},
|
||||||
!IS_WEB && isWindows && {
|
!IS_WEB && isWindows && {
|
||||||
key: "winCtrlQ",
|
key: "winCtrlQ",
|
||||||
|
@ -101,53 +96,45 @@ function VencordSettings() {
|
||||||
<SettingsTab title="Vencord Settings">
|
<SettingsTab title="Vencord Settings">
|
||||||
<DonateCard image={donateImage} />
|
<DonateCard image={donateImage} />
|
||||||
<Forms.FormSection title="Quick Actions">
|
<Forms.FormSection title="Quick Actions">
|
||||||
<QuickActionCard>
|
<Card className={cl("quick-actions-card")}>
|
||||||
<QuickAction
|
<React.Fragment>
|
||||||
Icon={LogIcon}
|
|
||||||
text="Notification Log"
|
|
||||||
action={openNotificationLogModal}
|
|
||||||
/>
|
|
||||||
<QuickAction
|
|
||||||
Icon={PaintbrushIcon}
|
|
||||||
text="Edit QuickCSS"
|
|
||||||
action={() => VencordNative.quickCss.openEditor()}
|
|
||||||
/>
|
|
||||||
{!IS_WEB && (
|
{!IS_WEB && (
|
||||||
<QuickAction
|
<Button
|
||||||
Icon={RestartIcon}
|
onClick={relaunch}
|
||||||
text="Relaunch Discord"
|
size={Button.Sizes.SMALL}>
|
||||||
action={relaunch}
|
Restart Client
|
||||||
/>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={() => VencordNative.quickCss.openEditor()}
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
disabled={settingsDir === "Loading..."}>
|
||||||
|
Open QuickCSS File
|
||||||
|
</Button>
|
||||||
{!IS_WEB && (
|
{!IS_WEB && (
|
||||||
<QuickAction
|
<Button
|
||||||
Icon={FolderIcon}
|
onClick={() => showItemInFolder(settingsDir)}
|
||||||
text="Open Settings Folder"
|
size={Button.Sizes.SMALL}
|
||||||
action={() => showItemInFolder(settingsDir)}
|
disabled={settingsDirPending}>
|
||||||
/>
|
Open Settings Folder
|
||||||
|
</Button>
|
||||||
)}
|
)}
|
||||||
<QuickAction
|
<Button
|
||||||
Icon={GithubIcon}
|
onClick={() => VencordNative.native.openExternal("https://github.com/Vendicated/Vencord")}
|
||||||
text="View Source Code"
|
size={Button.Sizes.SMALL}
|
||||||
action={() => VencordNative.native.openExternal("https://github.com/" + gitRemote)}
|
disabled={settingsDirPending}>
|
||||||
/>
|
Open in GitHub
|
||||||
</QuickActionCard>
|
</Button>
|
||||||
|
</React.Fragment>
|
||||||
|
</Card>
|
||||||
</Forms.FormSection>
|
</Forms.FormSection>
|
||||||
|
|
||||||
<Forms.FormDivider />
|
<Forms.FormDivider />
|
||||||
|
|
||||||
<Forms.FormSection className={Margins.top16} title="Settings" tag="h5">
|
<Forms.FormSection className={Margins.top16} title="Settings" tag="h5">
|
||||||
<Forms.FormText className={Margins.bottom20} style={{ color: "var(--text-muted)" }}>
|
<Forms.FormText className={Margins.bottom20}>
|
||||||
Hint: You can change the position of this settings section in the
|
Hint: You can change the position of this settings section in the settings of the "Settings" plugin!
|
||||||
{" "}<Button
|
|
||||||
look={Button.Looks.BLANK}
|
|
||||||
style={{ color: "var(--text-link)", display: "inline-block" }}
|
|
||||||
onClick={() => openPluginModal(Vencord.Plugins.plugins.Settings)}
|
|
||||||
>
|
|
||||||
settings of the Settings plugin
|
|
||||||
</Button>!
|
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
|
|
||||||
{Switches.map(s => s && (
|
{Switches.map(s => s && (
|
||||||
<Switch
|
<Switch
|
||||||
key={s.key}
|
key={s.key}
|
||||||
|
@ -225,20 +212,94 @@ function VencordSettings() {
|
||||||
serialize={identity} />
|
serialize={identity} />
|
||||||
</>}
|
</>}
|
||||||
|
|
||||||
<Forms.FormSection className={Margins.top16} title="Vencord Notifications" tag="h5">
|
{typeof Notification !== "undefined" && <NotificationSection settings={settings.notifications} />}
|
||||||
<Flex>
|
|
||||||
<Button onClick={openNotificationSettingsModal}>
|
|
||||||
Notification Settings
|
|
||||||
</Button>
|
|
||||||
<Button onClick={openNotificationLogModal}>
|
|
||||||
View Notification Log
|
|
||||||
</Button>
|
|
||||||
</Flex>
|
|
||||||
</Forms.FormSection>
|
|
||||||
</SettingsTab>
|
</SettingsTab>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function NotificationSection({ settings }: { settings: typeof Settings["notifications"]; }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Forms.FormTitle tag="h5">Notification Style</Forms.FormTitle>
|
||||||
|
{settings.useNative !== "never" && Notification?.permission === "denied" && (
|
||||||
|
<ErrorCard style={{ padding: "1em" }} className={Margins.bottom8}>
|
||||||
|
<Forms.FormTitle tag="h5">Desktop Notification Permission denied</Forms.FormTitle>
|
||||||
|
<Forms.FormText>You have denied Notification Permissions. Thus, Desktop notifications will not work!</Forms.FormText>
|
||||||
|
</ErrorCard>
|
||||||
|
)}
|
||||||
|
<Forms.FormText className={Margins.bottom8}>
|
||||||
|
Some plugins may show you notifications. These come in two styles:
|
||||||
|
<ul>
|
||||||
|
<li><strong>Vencord Notifications</strong>: These are in-app notifications</li>
|
||||||
|
<li><strong>Desktop Notifications</strong>: Native Desktop notifications (like when you get a ping)</li>
|
||||||
|
</ul>
|
||||||
|
</Forms.FormText>
|
||||||
|
<Select
|
||||||
|
placeholder="Notification Style"
|
||||||
|
options={[
|
||||||
|
{ label: "Only use Desktop notifications when Discord is not focused", value: "not-focused", default: true },
|
||||||
|
{ label: "Always use Desktop notifications", value: "always" },
|
||||||
|
{ label: "Always use Vencord notifications", value: "never" },
|
||||||
|
] satisfies Array<{ value: typeof settings["useNative"]; } & Record<string, any>>}
|
||||||
|
closeOnSelect={true}
|
||||||
|
select={v => settings.useNative = v}
|
||||||
|
isSelected={v => v === settings.useNative}
|
||||||
|
serialize={identity}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Position</Forms.FormTitle>
|
||||||
|
<Select
|
||||||
|
isDisabled={settings.useNative === "always"}
|
||||||
|
placeholder="Notification Position"
|
||||||
|
options={[
|
||||||
|
{ label: "Bottom Right", value: "bottom-right", default: true },
|
||||||
|
{ label: "Top Right", value: "top-right" },
|
||||||
|
] satisfies Array<{ value: typeof settings["position"]; } & Record<string, any>>}
|
||||||
|
select={v => settings.position = v}
|
||||||
|
isSelected={v => v === settings.position}
|
||||||
|
serialize={identity}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Timeout</Forms.FormTitle>
|
||||||
|
<Forms.FormText className={Margins.bottom16}>Set to 0s to never automatically time out</Forms.FormText>
|
||||||
|
<Slider
|
||||||
|
disabled={settings.useNative === "always"}
|
||||||
|
markers={[0, 1000, 2500, 5000, 10_000, 20_000]}
|
||||||
|
minValue={0}
|
||||||
|
maxValue={20_000}
|
||||||
|
initialValue={settings.timeout}
|
||||||
|
onValueChange={v => settings.timeout = v}
|
||||||
|
onValueRender={v => (v / 1000).toFixed(2) + "s"}
|
||||||
|
onMarkerRender={v => (v / 1000) + "s"}
|
||||||
|
stickToMarkers={false}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Log Limit</Forms.FormTitle>
|
||||||
|
<Forms.FormText className={Margins.bottom16}>
|
||||||
|
The amount of notifications to save in the log until old ones are removed.
|
||||||
|
Set to <code>0</code> to disable Notification log and <code>∞</code> to never automatically remove old Notifications
|
||||||
|
</Forms.FormText>
|
||||||
|
<Slider
|
||||||
|
markers={[0, 25, 50, 75, 100, 200]}
|
||||||
|
minValue={0}
|
||||||
|
maxValue={200}
|
||||||
|
stickToMarkers={true}
|
||||||
|
initialValue={settings.logLimit}
|
||||||
|
onValueChange={v => settings.logLimit = v}
|
||||||
|
onValueRender={v => v === 200 ? "∞" : v}
|
||||||
|
onMarkerRender={v => v === 200 ? "∞" : v}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={openNotificationLogModal}
|
||||||
|
disabled={settings.logLimit === 0}
|
||||||
|
>
|
||||||
|
Open Notification Log
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface DonateCardProps {
|
interface DonateCardProps {
|
||||||
image: string;
|
image: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
.vc-settings-quickActions-card {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(200px, max-content));
|
|
||||||
gap: 0.5em;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0.5em 0;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-settings-quickActions-pill {
|
|
||||||
all: unset;
|
|
||||||
background: var(--background-secondary);
|
|
||||||
color: var(--header-secondary);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5em;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: 9999px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-settings-quickActions-pill:hover {
|
|
||||||
background: var(--background-secondary-alt);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-settings-quickActions-pill:focus-visible {
|
|
||||||
outline: 2px solid var(--focus-primary);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-settings-quickActions-img {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import "./quickActions.css";
|
|
||||||
|
|
||||||
import { classNameFactory } from "@api/Styles";
|
|
||||||
import { Card } from "@webpack/common";
|
|
||||||
import type { ComponentType, PropsWithChildren, ReactNode } from "react";
|
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-quickActions-");
|
|
||||||
|
|
||||||
export interface QuickActionProps {
|
|
||||||
Icon: ComponentType<{ className?: string; }>;
|
|
||||||
text: ReactNode;
|
|
||||||
action?: () => void;
|
|
||||||
disabled?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function QuickAction(props: QuickActionProps) {
|
|
||||||
const { Icon, action, text, disabled } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button className={cl("pill")} onClick={action} disabled={disabled}>
|
|
||||||
<Icon className={cl("img")} />
|
|
||||||
{text}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function QuickActionCard(props: PropsWithChildren) {
|
|
||||||
return (
|
|
||||||
<Card className={cl("card")}>
|
|
||||||
{props.children}
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -10,6 +10,17 @@
|
||||||
margin-bottom: -2px;
|
margin-bottom: -2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-settings-quick-actions-card {
|
||||||
|
padding: 1em;
|
||||||
|
display: flex;
|
||||||
|
gap: 1em;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.vc-settings-donate {
|
.vc-settings-donate {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@ -33,20 +44,6 @@
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
border: 1px solid var(--background-modifier-accent);
|
border: 1px solid var(--background-modifier-accent);
|
||||||
max-height: unset;
|
max-height: unset;
|
||||||
background-color: transparent;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-size: 12px;
|
|
||||||
line-height: 14px;
|
|
||||||
resize: none;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-settings-theme-links::placeholder {
|
|
||||||
color: var(--header-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vc-settings-theme-links:focus {
|
|
||||||
background-color: var(--background-tertiary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-cloud-settings-sync-grid {
|
.vc-cloud-settings-sync-grid {
|
||||||
|
|
|
@ -15,9 +15,9 @@ export async function loadLazyChunks() {
|
||||||
try {
|
try {
|
||||||
LazyChunkLoaderLogger.log("Loading all chunks...");
|
LazyChunkLoaderLogger.log("Loading all chunks...");
|
||||||
|
|
||||||
const validChunks = new Set<number>();
|
const validChunks = new Set<string>();
|
||||||
const invalidChunks = new Set<number>();
|
const invalidChunks = new Set<string>();
|
||||||
const deferredRequires = new Set<number>();
|
const deferredRequires = new Set<string>();
|
||||||
|
|
||||||
let chunksSearchingResolve: (value: void | PromiseLike<void>) => void;
|
let chunksSearchingResolve: (value: void | PromiseLike<void>) => void;
|
||||||
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
|
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
|
||||||
|
@ -27,19 +27,16 @@ export async function loadLazyChunks() {
|
||||||
|
|
||||||
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
|
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
|
||||||
|
|
||||||
const foundCssDebuggingLoad = false;
|
|
||||||
|
|
||||||
async function searchAndLoadLazyChunks(factoryCode: string) {
|
async function searchAndLoadLazyChunks(factoryCode: string) {
|
||||||
// Workaround to avoid loading the CSS debugging chunk which turns the app pink
|
|
||||||
const hasCssDebuggingLoad = foundCssDebuggingLoad ? false : factoryCode.includes(".cssDebuggingEnabled&&");
|
|
||||||
|
|
||||||
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
|
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
|
||||||
const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>();
|
const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
|
||||||
|
|
||||||
const shouldForceDefer = false;
|
// Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
|
||||||
|
// the chunk containing the component
|
||||||
|
const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
|
||||||
|
|
||||||
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
|
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
|
||||||
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => Number(m[1])) : [];
|
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
|
||||||
|
|
||||||
if (chunkIds.length === 0) {
|
if (chunkIds.length === 0) {
|
||||||
return;
|
return;
|
||||||
|
@ -48,16 +45,6 @@ export async function loadLazyChunks() {
|
||||||
let invalidChunkGroup = false;
|
let invalidChunkGroup = false;
|
||||||
|
|
||||||
for (const id of chunkIds) {
|
for (const id of chunkIds) {
|
||||||
if (hasCssDebuggingLoad) {
|
|
||||||
if (chunkIds.length > 1) {
|
|
||||||
throw new Error("Found multiple chunks in factory that loads the CSS debugging chunk");
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidChunks.add(id);
|
|
||||||
invalidChunkGroup = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
|
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
|
||||||
|
|
||||||
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
||||||
|
@ -74,7 +61,7 @@ export async function loadLazyChunks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!invalidChunkGroup) {
|
if (!invalidChunkGroup) {
|
||||||
validChunkGroups.add([chunkIds, Number(entryPoint)]);
|
validChunkGroups.add([chunkIds, entryPoint]);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -144,14 +131,14 @@ export async function loadLazyChunks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// All chunks Discord has mapped to asset files, even if they are not used anymore
|
// All chunks Discord has mapped to asset files, even if they are not used anymore
|
||||||
const allChunks = [] as number[];
|
const allChunks = [] as string[];
|
||||||
|
|
||||||
// Matches "id" or id:
|
// Matches "id" or id:
|
||||||
for (const currentMatch of wreq!.u.toString().matchAll(/(?:"([\deE]+?)"(?![,}]))|(?:([\deE]+?):)/g)) {
|
for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
|
||||||
const id = currentMatch[1] ?? currentMatch[2];
|
const id = currentMatch[1] ?? currentMatch[2];
|
||||||
if (id == null) continue;
|
if (id == null) continue;
|
||||||
|
|
||||||
allChunks.push(Number(id));
|
allChunks.push(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
||||||
|
|
|
@ -39,8 +39,9 @@ async function runReporter() {
|
||||||
}
|
}
|
||||||
if (searchType === "waitForStore") method = "findStore";
|
if (searchType === "waitForStore") method = "findStore";
|
||||||
|
|
||||||
let result: any;
|
|
||||||
try {
|
try {
|
||||||
|
let result: any;
|
||||||
|
|
||||||
if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||||
const [factory] = args;
|
const [factory] = args;
|
||||||
result = factory();
|
result = factory();
|
||||||
|
@ -49,26 +50,16 @@ async function runReporter() {
|
||||||
|
|
||||||
result = await Webpack.extractAndLoadChunks(code, matcher);
|
result = await Webpack.extractAndLoadChunks(code, matcher);
|
||||||
if (result === false) result = null;
|
if (result === false) result = null;
|
||||||
} else if (method === "mapMangledModule") {
|
|
||||||
const [code, mapper] = args;
|
|
||||||
|
|
||||||
result = Webpack.mapMangledModule(code, mapper);
|
|
||||||
if (Object.keys(result).length !== Object.keys(mapper).length) throw new Error("Webpack Find Fail");
|
|
||||||
} else {
|
} else {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
result = Webpack[method](...args);
|
result = Webpack[method](...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw "a rock at ben shapiro";
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let logMessage = searchType;
|
let logMessage = searchType;
|
||||||
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
||||||
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
|
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
|
||||||
else if (method === "mapMangledModule") {
|
|
||||||
const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null);
|
|
||||||
|
|
||||||
logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`;
|
|
||||||
}
|
|
||||||
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
||||||
|
|
||||||
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
<title>Vencord QuickCSS Editor</title>
|
<title>Vencord QuickCSS Editor</title>
|
||||||
<link
|
<link
|
||||||
rel="stylesheet"
|
rel="stylesheet"
|
||||||
href="https://cdn.jsdelivr.net/npm/monaco-editor@0.50.0/min/vs/editor/editor.main.css"
|
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.40.0/min/vs/editor/editor.main.min.css"
|
||||||
integrity="sha256-tiJPQ2O04z/pZ/AwdyIghrOMzewf+PIvEl1YKbQvsZk="
|
integrity="sha512-MOoQ02h80hklccfLrXFYkCzG+WVjORflOp9Zp8dltiaRP+35LYnO4LKOklR64oMGfGgJDLO8WJpkM1o5gZXYZQ=="
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
/>
|
/>
|
||||||
|
@ -29,8 +29,8 @@
|
||||||
<body>
|
<body>
|
||||||
<div id="container"></div>
|
<div id="container"></div>
|
||||||
<script
|
<script
|
||||||
src="https://cdn.jsdelivr.net/npm/monaco-editor@0.50.0/min/vs/loader.js"
|
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.40.0/min/vs/loader.min.js"
|
||||||
integrity="sha256-KcU48TGr84r7unF7J5IgBo95aeVrEbrGe04S7TcFUjs="
|
integrity="sha512-QzMpXeCPciAHP4wbYlV2PYgrQcaEkDQUjzkPU4xnjyVSD9T36/udamxtNBqb4qK4/bMQMPZ8ayrBe9hrGdBFjQ=="
|
||||||
crossorigin="anonymous"
|
crossorigin="anonymous"
|
||||||
referrerpolicy="no-referrer"
|
referrerpolicy="no-referrer"
|
||||||
></script>
|
></script>
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
<script>
|
<script>
|
||||||
require.config({
|
require.config({
|
||||||
paths: {
|
paths: {
|
||||||
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.50.0/min/vs",
|
vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.40.0/min/vs",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { onceDefined } from "@shared/onceDefined";
|
import { onceDefined } from "@shared/onceDefined";
|
||||||
import electron, { app, BrowserWindowConstructorOptions, Menu, nativeTheme } from "electron";
|
import electron, { app, BrowserWindowConstructorOptions, Menu } from "electron";
|
||||||
import { dirname, join } from "path";
|
import { dirname, join } from "path";
|
||||||
|
|
||||||
import { initIpc } from "./ipcMain";
|
import { initIpc } from "./ipcMain";
|
||||||
|
@ -100,19 +100,6 @@ if (!IS_VANILLA) {
|
||||||
|
|
||||||
super(options);
|
super(options);
|
||||||
initIpc(this);
|
initIpc(this);
|
||||||
|
|
||||||
// Workaround for https://github.com/electron/electron/issues/43367. Vesktop also has its own workaround
|
|
||||||
// @TODO: Remove this when the issue is fixed
|
|
||||||
if (IS_DISCORD_DESKTOP) {
|
|
||||||
this.webContents.on("devtools-opened", () => {
|
|
||||||
if (!nativeTheme.shouldUseDarkColors) return;
|
|
||||||
|
|
||||||
nativeTheme.themeSource = "light";
|
|
||||||
setTimeout(() => {
|
|
||||||
nativeTheme.themeSource = "dark";
|
|
||||||
}, 100);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else super(options);
|
} else super(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { get } from "@main/utils/simpleGet";
|
|
||||||
import { IpcEvents } from "@shared/IpcEvents";
|
import { IpcEvents } from "@shared/IpcEvents";
|
||||||
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
|
import { VENCORD_USER_AGENT } from "@shared/vencordUserAgent";
|
||||||
import { ipcMain } from "electron";
|
import { ipcMain } from "electron";
|
||||||
|
@ -26,6 +25,7 @@ import { join } from "path";
|
||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
import gitRemote from "~git-remote";
|
import gitRemote from "~git-remote";
|
||||||
|
|
||||||
|
import { get } from "../utils/simpleGet";
|
||||||
import { serializeErrors, VENCORD_FILES } from "./common";
|
import { serializeErrors, VENCORD_FILES } from "./common";
|
||||||
|
|
||||||
const API_BASE = `https://api.github.com/repos/${gitRemote}`;
|
const API_BASE = `https://api.github.com/repos/${gitRemote}`;
|
||||||
|
|
|
@ -35,8 +35,7 @@ export const ALLOWED_PROTOCOLS = [
|
||||||
"steam:",
|
"steam:",
|
||||||
"spotify:",
|
"spotify:",
|
||||||
"com.epicgames.launcher:",
|
"com.epicgames.launcher:",
|
||||||
"tidal:",
|
"tidal:"
|
||||||
"itunes:",
|
|
||||||
];
|
];
|
||||||
|
|
||||||
export const IS_VANILLA = /* @__PURE__ */ process.argv.includes("--vanilla");
|
export const IS_VANILLA = /* @__PURE__ */ process.argv.includes("--vanilla");
|
||||||
|
|
2
src/modules.d.ts
vendored
2
src/modules.d.ts
vendored
|
@ -16,6 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line spaced-comment
|
||||||
/// <reference types="standalone-electron-types"/>
|
/// <reference types="standalone-electron-types"/>
|
||||||
|
|
||||||
declare module "~plugins" {
|
declare module "~plugins" {
|
||||||
|
@ -25,7 +26,6 @@ declare module "~plugins" {
|
||||||
folderName: string;
|
folderName: string;
|
||||||
userPlugin: boolean;
|
userPlugin: boolean;
|
||||||
}>;
|
}>;
|
||||||
export const ExcludedPlugins: Record<string, "web" | "discordDesktop" | "vencordDesktop" | "desktop" | "dev">;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare module "~pluginNatives" {
|
declare module "~pluginNatives" {
|
||||||
|
|
3
src/plugins/_api/badges/fixBadgeOverflow.css
Normal file
3
src/plugins/_api/badges/fixBadgeOverflow.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[class*="profileBadges"] {
|
||||||
|
flex: none;
|
||||||
|
}
|
|
@ -16,6 +16,8 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./fixBadgeOverflow.css";
|
||||||
|
|
||||||
import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
|
import { _getBadges, BadgePosition, BadgeUserArgs, ProfileBadge } from "@api/Badges";
|
||||||
import DonateButton from "@components/DonateButton";
|
import DonateButton from "@components/DonateButton";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
@ -60,8 +62,36 @@ export default definePlugin({
|
||||||
authors: [Devs.Megu, Devs.Ven, Devs.TheSun],
|
authors: [Devs.Megu, Devs.Ven, Devs.TheSun],
|
||||||
required: true,
|
required: true,
|
||||||
patches: [
|
patches: [
|
||||||
|
/* Patch the badge list component on user profiles */
|
||||||
{
|
{
|
||||||
find: ".FULL_SIZE]:26",
|
find: 'id:"premium",',
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /&&(\i)\.push\(\{id:"premium".+?\}\);/,
|
||||||
|
replace: "$&$1.unshift(...$self.getBadges(arguments[0]));",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// alt: "", aria-hidden: false, src: originalSrc
|
||||||
|
match: /alt:" ","aria-hidden":!0,src:(?=(\i)\.src)/,
|
||||||
|
// ...badge.props, ..., src: badge.image ?? ...
|
||||||
|
replace: "...$1.props,$& $1.image??"
|
||||||
|
},
|
||||||
|
// replace their component with ours if applicable
|
||||||
|
{
|
||||||
|
match: /(?<=text:(\i)\.description,spacing:12,.{0,50})children:/,
|
||||||
|
replace: "children:$1.component ? () => $self.renderBadgeComponent($1) :"
|
||||||
|
},
|
||||||
|
// conditionally override their onClick with badge.onClick if it exists
|
||||||
|
{
|
||||||
|
match: /href:(\i)\.link/,
|
||||||
|
replace: "...($1.onClick && { onClick: vcE => $1.onClick(vcE, $1) }),$&"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
|
/* new profiles */
|
||||||
|
{
|
||||||
|
find: ".PANEL]:14",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=(\i)=\(0,\i\.\i\)\(\i\);)return 0===\i.length\?/,
|
match: /(?<=(\i)=\(0,\i\.\i\)\(\i\);)return 0===\i.length\?/,
|
||||||
replace: "$1.unshift(...$self.getBadges(arguments[0].displayProfile));$&"
|
replace: "$1.unshift(...$self.getBadges(arguments[0].displayProfile));$&"
|
||||||
|
@ -77,7 +107,7 @@ export default definePlugin({
|
||||||
replace: "...$1.props,$& $1.image??"
|
replace: "...$1.props,$& $1.image??"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=text:(\i)\.description,.{0,200})children:/,
|
match: /(?<=text:(\i)\.description,.{0,50})children:/,
|
||||||
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
|
replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :"
|
||||||
},
|
},
|
||||||
// conditionally override their onClick with badge.onClick if it exists
|
// conditionally override their onClick with badge.onClick if it exists
|
||||||
|
@ -106,8 +136,6 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
getBadges(props: { userId: string; user?: User; guildId: string; }) {
|
getBadges(props: { userId: string; user?: User; guildId: string; }) {
|
||||||
if (!props) return [];
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
props.userId ??= props.user?.id!;
|
props.userId ??= props.user?.id!;
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,38 @@
|
||||||
|
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
import { filters, waitFor, waitForSubscriptions } from "@webpack";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The last var name which the ContextMenu module was WebpackRequire'd and assigned to
|
||||||
|
*/
|
||||||
|
let lastVarName = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key exporting the ContextMenu module "Menu"
|
||||||
|
*/
|
||||||
|
let exportKey: PropertyKey = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of the module exporting the ContextMenu module "Menu"
|
||||||
|
*/
|
||||||
|
let modId: PropertyKey = "";
|
||||||
|
|
||||||
|
let mangledCallback: (...args: any[]) => any;
|
||||||
|
waitFor(filters.byCode("Menu API only allows Items and groups of Items as children."), mangledCallback = (_, modInfo) => {
|
||||||
|
exportKey = modInfo.exportKey;
|
||||||
|
modId = modInfo.id;
|
||||||
|
|
||||||
|
waitForSubscriptions.delete(nonMangledCallback);
|
||||||
|
});
|
||||||
|
|
||||||
|
let nonMangledCallback: (...args: any[]) => any;
|
||||||
|
waitFor(filters.byProps("Menu", "MenuItem"), nonMangledCallback = (_, modInfo) => {
|
||||||
|
exportKey = "Menu";
|
||||||
|
modId = modInfo.id;
|
||||||
|
|
||||||
|
waitForSubscriptions.delete(mangledCallback);
|
||||||
|
});
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "ContextMenuAPI",
|
name: "ContextMenuAPI",
|
||||||
|
@ -34,12 +66,26 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".Menu,{",
|
find: "navId:",
|
||||||
all: true,
|
all: true,
|
||||||
replacement: {
|
noWarn: true,
|
||||||
match: /Menu,{(?<=\.jsxs?\)\(\i\.Menu,{)/g,
|
replacement: [
|
||||||
replace: "$&contextMenuApiArguments:typeof arguments!=='undefined'?arguments:[],"
|
{
|
||||||
|
get match() {
|
||||||
|
return RegExp(`${String(modId)}(?<=(\\i)=.+?)`);
|
||||||
|
},
|
||||||
|
replace: (id, varName) => {
|
||||||
|
lastVarName = varName;
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
get match() {
|
||||||
|
return RegExp(`${String(exportKey)},{(?<=${lastVarName}\\.${String(exportKey)},{)`, "g");
|
||||||
|
},
|
||||||
|
replace: "$&contextMenuAPIArguments:typeof arguments!=='undefined'?arguments:[],"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 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: "DynamicImageModalAPI",
|
|
||||||
authors: [Devs.sadan, Devs.Nuckyz],
|
|
||||||
description: "Allows you to omit either width or height when opening an image modal",
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: "SCALE_DOWN:",
|
|
||||||
replacement: {
|
|
||||||
match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/,
|
|
||||||
replace: (_, width, height) => `!((null == ${width} || 0 === ${width}) && (null == ${height} || 0 === ${height}))`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
|
@ -31,7 +31,7 @@ export default definePlugin({
|
||||||
match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/,
|
match: /let\{[^}]*lostPermissionTooltipText:\i[^}]*\}=(\i),/,
|
||||||
replace: "$&vencordProps=$1,"
|
replace: "$&vencordProps=$1,"
|
||||||
}, {
|
}, {
|
||||||
match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
|
match: /\.Messages\.GUILD_OWNER(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
|
||||||
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
|
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default definePlugin({
|
||||||
authors: [Devs.Cyn],
|
authors: [Devs.Cyn],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "#{intl::REMOVE_ATTACHMENT_BODY}",
|
find: ".Messages.REMOVE_ATTACHMENT_BODY",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=.container\)?,children:)(\[.+?\])/,
|
match: /(?<=.container\)?,children:)(\[.+?\])/,
|
||||||
replace: "Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)",
|
replace: "Vencord.Api.MessageAccessories._modifyAccessories($1,this.props)",
|
||||||
|
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: '"Message Username"',
|
find: '"Message Username"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/,
|
match: /\.Messages\.GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE.+?}\),\i(?=\])/,
|
||||||
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
|
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default definePlugin({
|
||||||
authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
|
authors: [Devs.Arjix, Devs.hunt, Devs.Ven],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "#{intl::EDIT_TEXTAREA_HELP}",
|
find: ".Messages.EDIT_TEXTAREA_HELP",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/,
|
match: /(?<=,channel:\i\}\)\.then\().+?(?=return \i\.content!==this\.props\.message\.content&&\i\((.+?)\))/,
|
||||||
replace: (match, args) => "" +
|
replace: (match, args) => "" +
|
||||||
|
|
|
@ -24,10 +24,15 @@ export default definePlugin({
|
||||||
description: "API to add buttons to message popovers.",
|
description: "API to add buttons to message popovers.",
|
||||||
authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz],
|
authors: [Devs.KingFish, Devs.Ven, Devs.Nuckyz],
|
||||||
patches: [{
|
patches: [{
|
||||||
find: "#{intl::MESSAGE_UTILITIES_A11Y_LABEL}",
|
find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.jsx\)\((\i\.\i),\{label:\i\.\i\.string\(\i\.\i#{intl::MESSAGE_ACTION_REPLY}.{0,200}?"reply-self".{0,50}?\}\):null(?=,.+?message:(\i))/,
|
// foo && !bar ? createElement(reactionStuffs)... createElement(blah,...makeElement(reply-other))
|
||||||
replace: "$&,Vencord.Api.MessagePopover._buildPopoverElements($1,$2)"
|
match: /\i&&!\i\?\(0,\i\.jsxs?\)\(.{0,200}renderEmojiPicker:.{0,500}\?(\i)\(\{key:"reply-other"/,
|
||||||
|
replace: (m, makeElement) => {
|
||||||
|
const msg = m.match(/message:(.{1,3}),/)?.[1];
|
||||||
|
if (!msg) throw new Error("Could not find message variable");
|
||||||
|
return `...Vencord.Api.MessagePopover._buildPopoverElements(${msg},${makeElement}),${m}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,16 +25,16 @@ export default definePlugin({
|
||||||
description: "Api required for plugins that modify the server list",
|
description: "Api required for plugins that modify the server list",
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "#{intl::DISCODO_DISABLED}",
|
find: "Messages.DISCODO_DISABLED",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=#{intl::DISCODO_DISABLED}.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
|
match: /(?<=Messages\.DISCODO_DISABLED.+?return)(\(.{0,75}?tutorialContainer.+?}\))(?=}function)/,
|
||||||
replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
|
replace: "[$1].concat(Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.Above))"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "#{intl::SERVERS}),children",
|
find: "Messages.SERVERS,children",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=#{intl::SERVERS}\),children:)\i\.map\(\i\)/,
|
match: /(?<=Messages\.SERVERS,children:).+?default:return null\}\}\)/,
|
||||||
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
|
replace: "Vencord.Api.ServerList.renderAll(Vencord.Api.ServerList.ServerListRenderPosition.In).concat($&)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,8 @@ import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "UserSettingsAPI",
|
name: "UserSettingDefinitionsAPI",
|
||||||
description: "Patches Discord's UserSettings to expose their group and name.",
|
description: "Patches Discord's UserSettingDefinitions to expose their group and name.",
|
||||||
authors: [Devs.Nuckyz],
|
authors: [Devs.Nuckyz],
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
|
@ -31,17 +31,17 @@ export default definePlugin({
|
||||||
// Main setting definition
|
// Main setting definition
|
||||||
{
|
{
|
||||||
match: /(?<=INFREQUENT_USER_ACTION.{0,20},)useSetting:/,
|
match: /(?<=INFREQUENT_USER_ACTION.{0,20},)useSetting:/,
|
||||||
replace: "userSettingsAPIGroup:arguments[0],userSettingsAPIName:arguments[1],$&"
|
replace: "userSettingDefinitionsAPIGroup:arguments[0],userSettingDefinitionsAPIName:arguments[1],$&"
|
||||||
},
|
},
|
||||||
// Selective wrapper
|
// Selective wrapper
|
||||||
{
|
{
|
||||||
match: /updateSetting:.{0,100}SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE/,
|
match: /updateSetting:.{0,100}SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE/,
|
||||||
replace: "userSettingsAPIGroup:arguments[0].userSettingsAPIGroup,userSettingsAPIName:arguments[0].userSettingsAPIName,$&"
|
replace: "userSettingDefinitionsAPIGroup:arguments[0].userSettingDefinitionsAPIGroup,userSettingDefinitionsAPIName:arguments[0].userSettingDefinitionsAPIName,$&"
|
||||||
},
|
},
|
||||||
// Override wrapper
|
// Override wrapper
|
||||||
{
|
{
|
||||||
match: /updateSetting:.{0,60}USER_SETTINGS_OVERRIDE_CLEAR/,
|
match: /updateSetting:.{0,60}USER_SETTINGS_OVERRIDE_CLEAR/,
|
||||||
replace: "userSettingsAPIGroup:arguments[0].userSettingsAPIGroup,userSettingsAPIName:arguments[0].userSettingsAPIName,$&"
|
replace: "userSettingDefinitionsAPIGroup:arguments[0].userSettingDefinitionsAPIGroup,userSettingDefinitionsAPIName:arguments[0].userSettingDefinitionsAPIName,$&"
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
|
@ -18,8 +18,7 @@
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
disableAnalytics: {
|
disableAnalytics: {
|
||||||
|
@ -48,7 +47,14 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".METRICS",
|
find: "window.DiscordSentry=",
|
||||||
|
replacement: {
|
||||||
|
match: /^.+$/,
|
||||||
|
replace: "()=>{}",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".METRICS,",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /this\._intervalId=/,
|
match: /this\._intervalId=/,
|
||||||
|
@ -59,67 +65,14 @@ export default definePlugin({
|
||||||
replace: "$&return;"
|
replace: "$&return;"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".installedLogHooks)",
|
||||||
|
replacement: {
|
||||||
|
// if getDebugLogging() returns false, the hooks don't get installed.
|
||||||
|
match: "getDebugLogging(){",
|
||||||
|
replace: "getDebugLogging(){return false;"
|
||||||
}
|
}
|
||||||
],
|
},
|
||||||
|
]
|
||||||
startAt: StartAt.Init,
|
|
||||||
start() {
|
|
||||||
// Sentry is initialized in its own WebpackInstance.
|
|
||||||
// It has everything it needs preloaded, so, it doesn't include any chunk loading functionality.
|
|
||||||
// Because of that, its WebpackInstance doesnt export wreq.m or wreq.c
|
|
||||||
|
|
||||||
// To circuvent this and disable Sentry we are gonna hook when wreq.g of its WebpackInstance is set.
|
|
||||||
// When that happens we are gonna forcefully throw an error and abort everything.
|
|
||||||
Object.defineProperty(Function.prototype, "g", {
|
|
||||||
configurable: true,
|
|
||||||
|
|
||||||
set(v: any) {
|
|
||||||
Object.defineProperty(this, "g", {
|
|
||||||
value: v,
|
|
||||||
configurable: true,
|
|
||||||
enumerable: true,
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ensure this is most likely the Sentry WebpackInstance.
|
|
||||||
// Function.g is a very generic property and is not uncommon for another WebpackInstance (or even a React component: <g></g>) to include it
|
|
||||||
const { stack } = new Error();
|
|
||||||
if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || !String(this).includes("exports:{}") || this.c != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const assetPath = stack?.match(/\/assets\/.+?\.js/)?.[0];
|
|
||||||
if (!assetPath) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const srcRequest = new XMLHttpRequest();
|
|
||||||
srcRequest.open("GET", assetPath, false);
|
|
||||||
srcRequest.send();
|
|
||||||
|
|
||||||
// Final condition to see if this is the Sentry WebpackInstance
|
|
||||||
if (!srcRequest.responseText.includes("window.DiscordSentry=")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
new Logger("NoTrack", "#8caaee").info("Disabling Sentry by erroring its WebpackInstance");
|
|
||||||
|
|
||||||
Reflect.deleteProperty(Function.prototype, "g");
|
|
||||||
Reflect.deleteProperty(window, "DiscordSentry");
|
|
||||||
|
|
||||||
throw new Error("Sentry successfully disabled");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.defineProperty(window, "DiscordSentry", {
|
|
||||||
configurable: true,
|
|
||||||
|
|
||||||
set() {
|
|
||||||
new Logger("NoTrack", "#8caaee").error("Failed to disable Sentry. Falling back to deleting window.DiscordSentry");
|
|
||||||
|
|
||||||
Reflect.deleteProperty(Function.prototype, "g");
|
|
||||||
Reflect.deleteProperty(window, "DiscordSentry");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -25,9 +25,8 @@ import ThemesTab from "@components/VencordSettings/ThemesTab";
|
||||||
import UpdaterTab from "@components/VencordSettings/UpdaterTab";
|
import UpdaterTab from "@components/VencordSettings/UpdaterTab";
|
||||||
import VencordTab from "@components/VencordSettings/VencordTab";
|
import VencordTab from "@components/VencordSettings/VencordTab";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getIntlMessage } from "@utils/discord";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { React } from "@webpack/common";
|
import { i18n, React } from "@webpack/common";
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
|
|
||||||
|
@ -58,20 +57,20 @@ export default definePlugin({
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".SEARCH_NO_RESULTS&&0===",
|
find: "Messages.ACTIVITY_SETTINGS",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
|
match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/,
|
||||||
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
|
match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,30}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
|
||||||
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
|
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
|
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
|
match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/,
|
||||||
replace: "$2.open($1);return;"
|
replace: "$2.open($1);return;"
|
||||||
|
@ -149,18 +148,13 @@ export default definePlugin({
|
||||||
|
|
||||||
if (!header) return;
|
if (!header) return;
|
||||||
|
|
||||||
try {
|
|
||||||
const names = {
|
const names = {
|
||||||
top: getIntlMessage("USER_SETTINGS"),
|
top: i18n.Messages.USER_SETTINGS,
|
||||||
aboveNitro: getIntlMessage("BILLING_SETTINGS"),
|
aboveNitro: i18n.Messages.BILLING_SETTINGS,
|
||||||
belowNitro: getIntlMessage("APP_SETTINGS"),
|
belowNitro: i18n.Messages.APP_SETTINGS,
|
||||||
aboveActivity: getIntlMessage("ACTIVITY_SETTINGS")
|
aboveActivity: i18n.Messages.ACTIVITY_SETTINGS
|
||||||
};
|
};
|
||||||
|
|
||||||
return header === names[settingsLocation];
|
return header === names[settingsLocation];
|
||||||
} catch {
|
|
||||||
return firstChild === "PREMIUM";
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
patchedSettings: new WeakSet(),
|
patchedSettings: new WeakSet(),
|
||||||
|
@ -203,7 +197,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
get electronVersion() {
|
get electronVersion() {
|
||||||
return VencordNative.native.getVersions().electron || window.legcord?.electron || null;
|
return VencordNative.native.getVersions().electron || window.armcord?.electron || null;
|
||||||
},
|
},
|
||||||
|
|
||||||
get chromiumVersion() {
|
get chromiumVersion() {
|
||||||
|
|
|
@ -16,34 +16,24 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addAccessory } from "@api/MessageAccessories";
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
||||||
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
||||||
import { sendMessage } from "@utils/discord";
|
|
||||||
import { Logger } from "@utils/Logger";
|
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { isPluginDev, tryOrElse } from "@utils/misc";
|
import { isPluginDev } from "@utils/misc";
|
||||||
import { relaunch } from "@utils/native";
|
import { relaunch } from "@utils/native";
|
||||||
import { onlyOnce } from "@utils/onlyOnce";
|
|
||||||
import { makeCodeblock } from "@utils/text";
|
import { makeCodeblock } from "@utils/text";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { checkForUpdates, isOutdated, update } from "@utils/updater";
|
import { isOutdated, update } from "@utils/updater";
|
||||||
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
|
import { Alerts, Card, ChannelStore, Forms, GuildMemberStore, NavigationRouter, Parser, RelationshipStore, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import gitHash from "~git-hash";
|
import gitHash from "~git-hash";
|
||||||
import plugins, { PluginMeta } from "~plugins";
|
import plugins from "~plugins";
|
||||||
|
|
||||||
import SettingsPlugin from "./settings";
|
import settings from "./settings";
|
||||||
|
|
||||||
const VENCORD_GUILD_ID = "1015060230222131221";
|
const VENCORD_GUILD_ID = "1015060230222131221";
|
||||||
const VENBOT_USER_ID = "1017176847865352332";
|
|
||||||
const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920";
|
|
||||||
const CodeBlockRe = /```js\n(.+?)```/s;
|
|
||||||
|
|
||||||
const AllowedChannelIds = [
|
const AllowedChannelIds = [
|
||||||
SUPPORT_CHANNEL_ID,
|
SUPPORT_CHANNEL_ID,
|
||||||
|
@ -57,118 +47,67 @@ const TrustedRolesIds = [
|
||||||
"1042507929485586532", // donor
|
"1042507929485586532", // donor
|
||||||
];
|
];
|
||||||
|
|
||||||
const AsyncFunction = async function () { }.constructor;
|
export default definePlugin({
|
||||||
|
name: "SupportHelper",
|
||||||
|
required: true,
|
||||||
|
description: "Helps us provide support to you",
|
||||||
|
authors: [Devs.Ven],
|
||||||
|
dependencies: ["CommandsAPI"],
|
||||||
|
|
||||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
patches: [{
|
||||||
|
find: ".BEGINNING_DM.format",
|
||||||
async function forceUpdate() {
|
replacement: {
|
||||||
const outdated = await checkForUpdates();
|
match: /BEGINNING_DM\.format\(\{.+?\}\),(?=.{0,100}userId:(\i\.getRecipientId\(\)))/,
|
||||||
if (outdated) {
|
replace: "$& $self.ContributorDmWarningCard({ userId: $1 }),"
|
||||||
await update();
|
|
||||||
relaunch();
|
|
||||||
}
|
}
|
||||||
|
}],
|
||||||
|
|
||||||
return outdated;
|
commands: [{
|
||||||
}
|
name: "vencord-debug",
|
||||||
|
description: "Send Vencord Debug info",
|
||||||
async function generateDebugInfoMessage() {
|
predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id),
|
||||||
|
async execute() {
|
||||||
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
|
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
|
||||||
|
|
||||||
const client = (() => {
|
const client = (() => {
|
||||||
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
|
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
|
||||||
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
|
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
|
||||||
if ("legcord" in window) return `Legcord v${window.legcord.version}`;
|
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
|
||||||
|
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
|
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
|
||||||
return `${name} (${navigator.userAgent})`;
|
return `${name} (${navigator.userAgent})`;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const isApiPlugin = (plugin: string) => plugin.endsWith("API") || plugins[plugin].required;
|
||||||
|
|
||||||
|
const enabledPlugins = Object.keys(plugins).filter(p => Vencord.Plugins.isPluginEnabled(p) && !isApiPlugin(p));
|
||||||
|
|
||||||
const info = {
|
const info = {
|
||||||
Vencord:
|
Vencord:
|
||||||
`v${VERSION} • [${gitHash}](<https://github.com/Vendicated/Vencord/commit/${gitHash}>)` +
|
`v${VERSION} • [${gitHash}](<https://github.com/Vendicated/Vencord/commit/${gitHash}>)` +
|
||||||
`${SettingsPlugin.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`,
|
`${settings.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`,
|
||||||
Client: `${RELEASE_CHANNEL} ~ ${client}`,
|
Client: `${RELEASE_CHANNEL} ~ ${client}`,
|
||||||
Platform: window.navigator.platform
|
Platform: window.navigator.platform
|
||||||
};
|
};
|
||||||
|
|
||||||
if (IS_DISCORD_DESKTOP) {
|
if (IS_DISCORD_DESKTOP) {
|
||||||
info["Last Crash Reason"] = (await tryOrElse(() => DiscordNative.processUtils.getLastCrash(), undefined))?.rendererCrashReason ?? "N/A";
|
info["Last Crash Reason"] = (await DiscordNative.processUtils.getLastCrash())?.rendererCrashReason ?? "N/A";
|
||||||
}
|
}
|
||||||
|
|
||||||
const commonIssues = {
|
const debugInfo = `
|
||||||
"NoRPC enabled": Vencord.Plugins.isPluginEnabled("NoRPC"),
|
>>> ${Object.entries(info).map(([k, v]) => `**${k}**: ${v}`).join("\n")}
|
||||||
"Activity Sharing disabled": tryOrElse(() => !ShowCurrentGame.getSetting(), false),
|
|
||||||
"Vencord DevBuild": !IS_STANDALONE,
|
Enabled Plugins (${enabledPlugins.length}):
|
||||||
"Has UserPlugins": Object.values(PluginMeta).some(m => m.userPlugin),
|
${makeCodeblock(enabledPlugins.join(", "))}
|
||||||
"More than two weeks out of date": BUILD_TIMESTAMP < Date.now() - 12096e5,
|
`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: debugInfo.trim().replaceAll("```\n", "```")
|
||||||
};
|
};
|
||||||
|
|
||||||
let content = `>>> ${Object.entries(info).map(([k, v]) => `**${k}**: ${v}`).join("\n")}`;
|
|
||||||
content += "\n" + Object.entries(commonIssues)
|
|
||||||
.filter(([, v]) => v).map(([k]) => `⚠️ ${k}`)
|
|
||||||
.join("\n");
|
|
||||||
|
|
||||||
return content.trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
function generatePluginList() {
|
|
||||||
const isApiPlugin = (plugin: string) => plugin.endsWith("API") || plugins[plugin].required;
|
|
||||||
|
|
||||||
const enabledPlugins = Object.keys(plugins)
|
|
||||||
.filter(p => Vencord.Plugins.isPluginEnabled(p) && !isApiPlugin(p));
|
|
||||||
|
|
||||||
const enabledStockPlugins = enabledPlugins.filter(p => !PluginMeta[p].userPlugin);
|
|
||||||
const enabledUserPlugins = enabledPlugins.filter(p => PluginMeta[p].userPlugin);
|
|
||||||
|
|
||||||
|
|
||||||
let content = `**Enabled Plugins (${enabledStockPlugins.length}):**\n${makeCodeblock(enabledStockPlugins.join(", "))}`;
|
|
||||||
|
|
||||||
if (enabledUserPlugins.length) {
|
|
||||||
content += `**Enabled UserPlugins (${enabledUserPlugins.length}):**\n${makeCodeblock(enabledUserPlugins.join(", "))}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkForUpdatesOnce = onlyOnce(checkForUpdates);
|
|
||||||
|
|
||||||
const settings = definePluginSettings({}).withPrivateSettings<{
|
|
||||||
dismissedDevBuildWarning?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "SupportHelper",
|
|
||||||
required: true,
|
|
||||||
description: "Helps us provide support to you",
|
|
||||||
authors: [Devs.Ven],
|
|
||||||
dependencies: ["UserSettingsAPI", "MessageAccessoriesAPI"],
|
|
||||||
|
|
||||||
settings,
|
|
||||||
|
|
||||||
patches: [{
|
|
||||||
find: "#{intl::BEGINNING_DM}",
|
|
||||||
replacement: {
|
|
||||||
match: /#{intl::BEGINNING_DM},{.+?}\),(?=.{0,300}(\i)\.isMultiUserDM)/,
|
|
||||||
replace: "$& $self.renderContributorDmWarningCard({ channel: $1 }),"
|
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
commands: [
|
|
||||||
{
|
|
||||||
name: "vencord-debug",
|
|
||||||
description: "Send Vencord debug info",
|
|
||||||
predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id),
|
|
||||||
execute: async () => ({ content: await generateDebugInfoMessage() })
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "vencord-plugins",
|
|
||||||
description: "Send Vencord plugin list",
|
|
||||||
predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id),
|
|
||||||
execute: () => ({ content: generatePluginList() })
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
flux: {
|
flux: {
|
||||||
async CHANNEL_SELECT({ channelId }) {
|
async CHANNEL_SELECT({ channelId }) {
|
||||||
if (channelId !== SUPPORT_CHANNEL_ID) return;
|
if (channelId !== SUPPORT_CHANNEL_ID) return;
|
||||||
|
@ -176,9 +115,6 @@ export default definePlugin({
|
||||||
const selfId = UserStore.getCurrentUser()?.id;
|
const selfId = UserStore.getCurrentUser()?.id;
|
||||||
if (!selfId || isPluginDev(selfId)) return;
|
if (!selfId || isPluginDev(selfId)) return;
|
||||||
|
|
||||||
if (!IS_UPDATER_DISABLED) {
|
|
||||||
await checkForUpdatesOnce().catch(() => { });
|
|
||||||
|
|
||||||
if (isOutdated) {
|
if (isOutdated) {
|
||||||
return Alerts.show({
|
return Alerts.show({
|
||||||
title: "Hold on!",
|
title: "Hold on!",
|
||||||
|
@ -191,11 +127,13 @@ export default definePlugin({
|
||||||
onCancel: () => openUpdaterModal!(),
|
onCancel: () => openUpdaterModal!(),
|
||||||
cancelText: "View Updates",
|
cancelText: "View Updates",
|
||||||
confirmText: "Update & Restart Now",
|
confirmText: "Update & Restart Now",
|
||||||
onConfirm: forceUpdate,
|
async onConfirm() {
|
||||||
|
await update();
|
||||||
|
relaunch();
|
||||||
|
},
|
||||||
secondaryConfirmText: "I know what I'm doing or I can't update"
|
secondaryConfirmText: "I know what I'm doing or I can't update"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore outdated type
|
// @ts-ignore outdated type
|
||||||
const roles = GuildMemberStore.getSelfMember(VENCORD_GUILD_ID)?.roles;
|
const roles = GuildMemberStore.getSelfMember(VENCORD_GUILD_ID)?.roles;
|
||||||
|
@ -210,35 +148,31 @@ export default definePlugin({
|
||||||
Please either switch to an <Link href="https://vencord.dev/download">officially supported version of Vencord</Link>, or
|
Please either switch to an <Link href="https://vencord.dev/download">officially supported version of Vencord</Link>, or
|
||||||
contact your package maintainer for support instead.
|
contact your package maintainer for support instead.
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</div>
|
</div>,
|
||||||
|
onCloseCallback: () => setTimeout(() => NavigationRouter.back(), 50)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IS_STANDALONE && !settings.store.dismissedDevBuildWarning) {
|
const repo = await VencordNative.updater.getRepo();
|
||||||
|
if (repo.ok && !repo.value.includes("Vendicated/Vencord")) {
|
||||||
return Alerts.show({
|
return Alerts.show({
|
||||||
title: "Hold on!",
|
title: "Hold on!",
|
||||||
body: <div>
|
body: <div>
|
||||||
<Forms.FormText>You are using a custom build of Vencord, which we do not provide support for!</Forms.FormText>
|
<Forms.FormText>You are using a fork of Vencord, which we do not provide support for!</Forms.FormText>
|
||||||
|
|
||||||
<Forms.FormText className={Margins.top8}>
|
<Forms.FormText className={Margins.top8}>
|
||||||
We only provide support for <Link href="https://vencord.dev/download">official builds</Link>.
|
Please either switch to an <Link href="https://vencord.dev/download">officially supported version of Vencord</Link>, or
|
||||||
Either <Link href="https://vencord.dev/download">switch to an official build</Link> or figure your issue out yourself.
|
contact your package maintainer for support instead.
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
|
|
||||||
<Text variant="text-md/bold" className={Margins.top8}>You will be banned from receiving support if you ignore this rule.</Text>
|
|
||||||
</div>,
|
</div>,
|
||||||
confirmText: "Understood",
|
onCloseCallback: () => setTimeout(() => NavigationRouter.back(), 50)
|
||||||
secondaryConfirmText: "Don't show again",
|
|
||||||
onConfirmSecondary: () => settings.store.dismissedDevBuildWarning = true
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
|
ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => {
|
||||||
const userId = channel.getRecipientId();
|
|
||||||
if (!isPluginDev(userId)) return null;
|
if (!isPluginDev(userId)) return null;
|
||||||
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
if (RelationshipStore.isFriend(userId)) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
||||||
|
@ -248,86 +182,5 @@ export default definePlugin({
|
||||||
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}, { noop: true }),
|
}, { noop: true })
|
||||||
|
|
||||||
start() {
|
|
||||||
addAccessory("vencord-debug", props => {
|
|
||||||
const buttons = [] as JSX.Element[];
|
|
||||||
|
|
||||||
const shouldAddUpdateButton =
|
|
||||||
!IS_UPDATER_DISABLED
|
|
||||||
&& (
|
|
||||||
(props.channel.id === KNOWN_ISSUES_CHANNEL_ID) ||
|
|
||||||
(props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID)
|
|
||||||
)
|
|
||||||
&& props.message.content?.includes("update");
|
|
||||||
|
|
||||||
if (shouldAddUpdateButton) {
|
|
||||||
buttons.push(
|
|
||||||
<Button
|
|
||||||
key="vc-update"
|
|
||||||
color={Button.Colors.GREEN}
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
if (await forceUpdate())
|
|
||||||
showToast("Success! Restarting...", Toasts.Type.SUCCESS);
|
|
||||||
else
|
|
||||||
showToast("Already up to date!", Toasts.Type.MESSAGE);
|
|
||||||
} catch (e) {
|
|
||||||
new Logger(this.name).error("Error while updating:", e);
|
|
||||||
showToast("Failed to update :(", Toasts.Type.FAILURE);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Update Now
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.channel.id === SUPPORT_CHANNEL_ID) {
|
|
||||||
if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) {
|
|
||||||
buttons.push(
|
|
||||||
<Button
|
|
||||||
key="vc-dbg"
|
|
||||||
onClick={async () => sendMessage(props.channel.id, { content: await generateDebugInfoMessage() })}
|
|
||||||
>
|
|
||||||
Run /vencord-debug
|
|
||||||
</Button>,
|
|
||||||
<Button
|
|
||||||
key="vc-plg-list"
|
|
||||||
onClick={async () => sendMessage(props.channel.id, { content: generatePluginList() })}
|
|
||||||
>
|
|
||||||
Run /vencord-plugins
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.message.author.id === VENBOT_USER_ID) {
|
|
||||||
const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || "");
|
|
||||||
if (match) {
|
|
||||||
buttons.push(
|
|
||||||
<Button
|
|
||||||
key="vc-run-snippet"
|
|
||||||
onClick={async () => {
|
|
||||||
try {
|
|
||||||
await AsyncFunction(match[1])();
|
|
||||||
showToast("Success!", Toasts.Type.SUCCESS);
|
|
||||||
} catch (e) {
|
|
||||||
new Logger(this.name).error("Error while running snippet:", e);
|
|
||||||
showToast("Failed to run snippet :(", Toasts.Type.FAILURE);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Run Snippet
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buttons.length
|
|
||||||
? <Flex>{buttons}</Flex>
|
|
||||||
: null;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
# AccountPanelServerProfile
|
|
||||||
|
|
||||||
Right click your account panel in the bottom left to view your profile in the current server
|
|
||||||
|
|
||||||
![](https://github.com/user-attachments/assets/3228497d-488f-479c-93d2-a32ccdb08f0f)
|
|
||||||
|
|
||||||
![](https://github.com/user-attachments/assets/6fc45363-d95f-4810-812f-2f9fb28b41b5)
|
|
|
@ -1,134 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import { getCurrentChannel } from "@utils/discord";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
|
||||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
|
||||||
import { ContextMenuApi, Menu, useEffect, useRef } from "@webpack/common";
|
|
||||||
import { User } from "discord-types/general";
|
|
||||||
|
|
||||||
interface UserProfileProps {
|
|
||||||
popoutProps: Record<string, any>;
|
|
||||||
currentUser: User;
|
|
||||||
originalPopout: () => React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cannot be undefined");
|
|
||||||
const styles = findByPropsLazy("accountProfilePopoutWrapper");
|
|
||||||
|
|
||||||
let openAlternatePopout = false;
|
|
||||||
let accountPanelRef: React.MutableRefObject<Record<PropertyKey, any> | null> = { current: null };
|
|
||||||
|
|
||||||
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
|
|
||||||
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Menu.Menu
|
|
||||||
navId="vc-ap-server-profile"
|
|
||||||
onClose={ContextMenuApi.closeContextMenu}
|
|
||||||
>
|
|
||||||
<Menu.MenuItem
|
|
||||||
id="vc-ap-view-alternate-popout"
|
|
||||||
label={prioritizeServerProfile ? "View Account Profile" : "View Server Profile"}
|
|
||||||
disabled={getCurrentChannel()?.getGuildId() == null}
|
|
||||||
action={e => {
|
|
||||||
openAlternatePopout = true;
|
|
||||||
accountPanelRef.current?.props.onMouseDown();
|
|
||||||
accountPanelRef.current?.props.onClick(e);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Menu.MenuCheckboxItem
|
|
||||||
id="vc-ap-prioritize-server-profile"
|
|
||||||
label="Prioritize Server Profile"
|
|
||||||
checked={prioritizeServerProfile}
|
|
||||||
action={() => settings.store.prioritizeServerProfile = !prioritizeServerProfile}
|
|
||||||
/>
|
|
||||||
</Menu.Menu>
|
|
||||||
);
|
|
||||||
}, { noop: true });
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
|
||||||
prioritizeServerProfile: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Prioritize Server Profile when left clicking your account panel",
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "AccountPanelServerProfile",
|
|
||||||
description: "Right click your account panel in the bottom left to view your profile in the current server",
|
|
||||||
authors: [Devs.Nuckyz, Devs.relitrix],
|
|
||||||
settings,
|
|
||||||
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
|
|
||||||
group: true,
|
|
||||||
replacement: [
|
|
||||||
{
|
|
||||||
match: /(?<=\.SIZE_32\)}\);)/,
|
|
||||||
replace: "$self.useAccountPanelRef();"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/,
|
|
||||||
replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalPopout:()=>{${originalPopout}}})`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /\.AVATAR,children:.+?(?=renderPopout:)/,
|
|
||||||
replace: "$&onRequestClose:$self.onPopoutClose,"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /(?<=.avatarWrapper,)/,
|
|
||||||
replace: "ref:$self.accountPanelRef,onContextMenu:$self.openAccountPanelContextMenu,"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
|
|
||||||
get accountPanelRef() {
|
|
||||||
return accountPanelRef;
|
|
||||||
},
|
|
||||||
|
|
||||||
useAccountPanelRef() {
|
|
||||||
useEffect(() => () => {
|
|
||||||
accountPanelRef.current = null;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (accountPanelRef = useRef(null));
|
|
||||||
},
|
|
||||||
|
|
||||||
openAccountPanelContextMenu(event: React.UIEvent) {
|
|
||||||
ContextMenuApi.openContextMenu(event, AccountPanelContextMenu);
|
|
||||||
},
|
|
||||||
|
|
||||||
onPopoutClose() {
|
|
||||||
openAlternatePopout = false;
|
|
||||||
},
|
|
||||||
|
|
||||||
UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalPopout }: UserProfileProps) => {
|
|
||||||
if (
|
|
||||||
(settings.store.prioritizeServerProfile && openAlternatePopout) ||
|
|
||||||
(!settings.store.prioritizeServerProfile && !openAlternatePopout)
|
|
||||||
) {
|
|
||||||
return originalPopout();
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentChannel = getCurrentChannel();
|
|
||||||
if (currentChannel?.getGuildId() == null) {
|
|
||||||
return originalPopout();
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.accountProfilePopoutWrapper}>
|
|
||||||
<UserProfile {...popoutProps} userId={currentUser.id} guildId={currentChannel.getGuildId()} channelId={currentChannel.id} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}, { noop: true })
|
|
||||||
});
|
|
|
@ -41,7 +41,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Status emojis
|
// Status emojis
|
||||||
find: "#{intl::GUILD_OWNER}",
|
find: ".Messages.GUILD_OWNER,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=\.activityEmoji,.+?animate:)\i/,
|
match: /(?<=\.activityEmoji,.+?animate:)\i/,
|
||||||
replace: "!0"
|
replace: "!0"
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
# Always Expand Roles
|
|
||||||
|
|
||||||
Always expands the role list in profile popouts
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a modification for Discord's desktop app
|
|
||||||
* Copyright (c) 2023 Vendicated and contributors
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { migratePluginSettings } from "@api/Settings";
|
|
||||||
import { Devs } from "@utils/constants";
|
|
||||||
import definePlugin from "@utils/types";
|
|
||||||
|
|
||||||
migratePluginSettings("AlwaysExpandRoles", "ShowAllRoles");
|
|
||||||
export default definePlugin({
|
|
||||||
name: "AlwaysExpandRoles",
|
|
||||||
description: "Always expands the role list in profile popouts",
|
|
||||||
authors: [Devs.surgedevs],
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: 'action:"EXPAND_ROLES"',
|
|
||||||
replacement: [
|
|
||||||
{
|
|
||||||
match: /(roles:\i(?=.+?(\i)\(!0\)[,;]\i\({action:"EXPAND_ROLES"}\)).+?\[\i,\2\]=\i\.useState\()!1\)/,
|
|
||||||
replace: (_, rest, setExpandedRoles) => `${rest}!0)`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Fix not calculating non-expanded roles because the above patch makes the default "expanded",
|
|
||||||
// which makes the collapse button never show up and calculation never occur
|
|
||||||
match: /(?<=useLayoutEffect\(\(\)=>{if\()\i/,
|
|
||||||
replace: isExpanded => "false"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
|
|
@ -71,7 +71,7 @@ export default definePlugin({
|
||||||
description: "Anonymise uploaded file names",
|
description: "Anonymise uploaded file names",
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "instantBatchUpload:",
|
find: "instantBatchUpload:function",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /uploadFiles:(\i),/,
|
match: /uploadFiles:(\i),/,
|
||||||
replace:
|
replace:
|
||||||
|
@ -86,9 +86,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "#{intl::ATTACHMENT_UTILITIES_SPOILER}",
|
find: ".Messages.ATTACHMENT_UTILITIES_SPOILER",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}#{intl::ATTACHMENT_UTILITIES_SPOILER})/,
|
match: /(?<=children:\[)(?=.{10,80}tooltip:.{0,100}\i\.\i\.Messages\.ATTACHMENT_UTILITIES_SPOILER)/,
|
||||||
replace: "arguments[0].canEdit!==false?$self.renderIcon(arguments[0]):null,"
|
replace: "arguments[0].canEdit!==false?$self.renderIcon(arguments[0]):null,"
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
|
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
|
||||||
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
|
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
|
||||||
|
|
||||||
const Native = VencordNative.pluginHelpers.AppleMusicRichPresence as PluginNative<typeof import("./native")>;
|
const Native = VencordNative.pluginHelpers.AppleMusic as PluginNative<typeof import("./native")>;
|
||||||
|
|
||||||
interface ActivityAssets {
|
interface ActivityAssets {
|
||||||
large_image?: string;
|
large_image?: string;
|
||||||
|
@ -24,7 +24,7 @@ interface ActivityButton {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Activity {
|
interface Activity {
|
||||||
state?: string;
|
state: string;
|
||||||
details?: string;
|
details?: string;
|
||||||
timestamps?: {
|
timestamps?: {
|
||||||
start?: number;
|
start?: number;
|
||||||
|
@ -52,8 +52,8 @@ const enum ActivityFlag {
|
||||||
|
|
||||||
export interface TrackData {
|
export interface TrackData {
|
||||||
name: string;
|
name: string;
|
||||||
album?: string;
|
album: string;
|
||||||
artist?: string;
|
artist: string;
|
||||||
|
|
||||||
appleMusicLink?: string;
|
appleMusicLink?: string;
|
||||||
songLink?: string;
|
songLink?: string;
|
||||||
|
@ -61,8 +61,8 @@ export interface TrackData {
|
||||||
albumArtwork?: string;
|
albumArtwork?: string;
|
||||||
artistArtwork?: string;
|
artistArtwork?: string;
|
||||||
|
|
||||||
playerPosition?: number;
|
playerPosition: number;
|
||||||
duration?: number;
|
duration: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const enum AssetImageType {
|
const enum AssetImageType {
|
||||||
|
@ -120,7 +120,7 @@ const settings = definePluginSettings({
|
||||||
stateString: {
|
stateString: {
|
||||||
type: OptionType.STRING,
|
type: OptionType.STRING,
|
||||||
description: "Activity state format string",
|
description: "Activity state format string",
|
||||||
default: "{artist} · {album}"
|
default: "{artist}"
|
||||||
},
|
},
|
||||||
largeImageType: {
|
largeImageType: {
|
||||||
type: OptionType.SELECT,
|
type: OptionType.SELECT,
|
||||||
|
@ -155,8 +155,8 @@ const settings = definePluginSettings({
|
||||||
function customFormat(formatStr: string, data: TrackData) {
|
function customFormat(formatStr: string, data: TrackData) {
|
||||||
return formatStr
|
return formatStr
|
||||||
.replaceAll("{name}", data.name)
|
.replaceAll("{name}", data.name)
|
||||||
.replaceAll("{album}", data.album ?? "")
|
.replaceAll("{album}", data.album)
|
||||||
.replaceAll("{artist}", data.artist ?? "");
|
.replaceAll("{artist}", data.artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImageAsset(type: AssetImageType, data: TrackData) {
|
function getImageAsset(type: AssetImageType, data: TrackData) {
|
||||||
|
@ -212,16 +212,14 @@ export default definePlugin({
|
||||||
|
|
||||||
const assets: ActivityAssets = {};
|
const assets: ActivityAssets = {};
|
||||||
|
|
||||||
const isRadio = Number.isNaN(trackData.duration) && (trackData.playerPosition === 0);
|
|
||||||
|
|
||||||
if (settings.store.largeImageType !== AssetImageType.Disabled) {
|
if (settings.store.largeImageType !== AssetImageType.Disabled) {
|
||||||
assets.large_image = largeImageAsset;
|
assets.large_image = largeImageAsset;
|
||||||
if (!isRadio) assets.large_text = customFormat(settings.store.largeTextString, trackData);
|
assets.large_text = customFormat(settings.store.largeTextString, trackData);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (settings.store.smallImageType !== AssetImageType.Disabled) {
|
if (settings.store.smallImageType !== AssetImageType.Disabled) {
|
||||||
assets.small_image = smallImageAsset;
|
assets.small_image = smallImageAsset;
|
||||||
if (!isRadio) assets.small_text = customFormat(settings.store.smallTextString, trackData);
|
assets.small_text = customFormat(settings.store.smallTextString, trackData);
|
||||||
}
|
}
|
||||||
|
|
||||||
const buttons: ActivityButton[] = [];
|
const buttons: ActivityButton[] = [];
|
||||||
|
@ -245,17 +243,17 @@ export default definePlugin({
|
||||||
|
|
||||||
name: customFormat(settings.store.nameString, trackData),
|
name: customFormat(settings.store.nameString, trackData),
|
||||||
details: customFormat(settings.store.detailsString, trackData),
|
details: customFormat(settings.store.detailsString, trackData),
|
||||||
state: isRadio ? undefined : customFormat(settings.store.stateString, trackData),
|
state: customFormat(settings.store.stateString, trackData),
|
||||||
|
|
||||||
timestamps: (trackData.playerPosition && trackData.duration && settings.store.enableTimestamps) ? {
|
timestamps: (settings.store.enableTimestamps ? {
|
||||||
start: Date.now() - (trackData.playerPosition * 1000),
|
start: Date.now() - (trackData.playerPosition * 1000),
|
||||||
end: Date.now() - (trackData.playerPosition * 1000) + (trackData.duration * 1000),
|
end: Date.now() - (trackData.playerPosition * 1000) + (trackData.duration * 1000),
|
||||||
} : undefined,
|
} : undefined),
|
||||||
|
|
||||||
assets,
|
assets,
|
||||||
|
|
||||||
buttons: !isRadio && buttons.length ? buttons.map(v => v.label) : undefined,
|
buttons: buttons.length ? buttons.map(v => v.label) : undefined,
|
||||||
metadata: !isRadio && buttons.length ? { button_urls: buttons.map(v => v.url) } : undefined,
|
metadata: { button_urls: buttons.map(v => v.url) || undefined, },
|
||||||
|
|
||||||
type: settings.store.activityType,
|
type: settings.store.activityType,
|
||||||
flags: ActivityFlag.INSTANCE,
|
flags: ActivityFlag.INSTANCE,
|
||||||
|
|
|
@ -11,11 +11,37 @@ import type { TrackData } from ".";
|
||||||
|
|
||||||
const exec = promisify(execFile);
|
const exec = promisify(execFile);
|
||||||
|
|
||||||
|
// function exec(file: string, args: string[] = []) {
|
||||||
|
// return new Promise<{ code: number | null, stdout: string | null, stderr: string | null; }>((resolve, reject) => {
|
||||||
|
// const process = spawn(file, args, { stdio: [null, "pipe", "pipe"] });
|
||||||
|
|
||||||
|
// let stdout: string | null = null;
|
||||||
|
// process.stdout.on("data", (chunk: string) => { stdout ??= ""; stdout += chunk; });
|
||||||
|
// let stderr: string | null = null;
|
||||||
|
// process.stderr.on("data", (chunk: string) => { stdout ??= ""; stderr += chunk; });
|
||||||
|
|
||||||
|
// process.on("exit", code => { resolve({ code, stdout, stderr }); });
|
||||||
|
// process.on("error", err => reject(err));
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
async function applescript(cmds: string[]) {
|
async function applescript(cmds: string[]) {
|
||||||
const { stdout } = await exec("osascript", cmds.map(c => ["-e", c]).flat());
|
const { stdout } = await exec("osascript", cmds.map(c => ["-e", c]).flat());
|
||||||
return stdout;
|
return stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeSearchUrl(type: string, query: string) {
|
||||||
|
const url = new URL("https://tools.applemediaservices.com/api/apple-media/music/US/search.json");
|
||||||
|
url.searchParams.set("types", type);
|
||||||
|
url.searchParams.set("limit", "1");
|
||||||
|
url.searchParams.set("term", query);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestOptions: RequestInit = {
|
||||||
|
headers: { "user-agent": "Mozilla/5.0 (Windows NT 10.0; rv:125.0) Gecko/20100101 Firefox/125.0" },
|
||||||
|
};
|
||||||
|
|
||||||
interface RemoteData {
|
interface RemoteData {
|
||||||
appleMusicLink?: string,
|
appleMusicLink?: string,
|
||||||
songLink?: string,
|
songLink?: string,
|
||||||
|
@ -25,24 +51,6 @@ interface RemoteData {
|
||||||
|
|
||||||
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null;
|
let cachedRemoteData: { id: string, data: RemoteData; } | { id: string, failures: number; } | null = null;
|
||||||
|
|
||||||
const APPLE_MUSIC_BUNDLE_REGEX = /<script type="module" crossorigin src="([a-zA-Z0-9.\-/]+)"><\/script>/;
|
|
||||||
const APPLE_MUSIC_TOKEN_REGEX = /\w+="([A-Za-z0-9-_]*\.[A-Za-z0-9-_]*\.[A-Za-z0-9-_]*)",\w+="x-apple-jingle-correlation-key"/;
|
|
||||||
|
|
||||||
let cachedToken: string | undefined = undefined;
|
|
||||||
|
|
||||||
const getToken = async () => {
|
|
||||||
if (cachedToken) return cachedToken;
|
|
||||||
|
|
||||||
const html = await fetch("https://music.apple.com/").then(r => r.text());
|
|
||||||
const bundleUrl = new URL(html.match(APPLE_MUSIC_BUNDLE_REGEX)![1], "https://music.apple.com/");
|
|
||||||
|
|
||||||
const bundle = await fetch(bundleUrl).then(r => r.text());
|
|
||||||
const token = bundle.match(APPLE_MUSIC_TOKEN_REGEX)![1];
|
|
||||||
|
|
||||||
cachedToken = token;
|
|
||||||
return token;
|
|
||||||
};
|
|
||||||
|
|
||||||
async function fetchRemoteData({ id, name, artist, album }: { id: string, name: string, artist: string, album: string; }) {
|
async function fetchRemoteData({ id, name, artist, album }: { id: string, name: string, artist: string, album: string; }) {
|
||||||
if (id === cachedRemoteData?.id) {
|
if (id === cachedRemoteData?.id) {
|
||||||
if ("data" in cachedRemoteData) return cachedRemoteData.data;
|
if ("data" in cachedRemoteData) return cachedRemoteData.data;
|
||||||
|
@ -50,39 +58,21 @@ async function fetchRemoteData({ id, name, artist, album }: { id: string, name:
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const dataUrl = new URL("https://amp-api-edge.music.apple.com/v1/catalog/us/search");
|
const [songData, artistData] = await Promise.all([
|
||||||
dataUrl.searchParams.set("platform", "web");
|
fetch(makeSearchUrl("songs", artist + " " + album + " " + name), requestOptions).then(r => r.json()),
|
||||||
dataUrl.searchParams.set("l", "en-US");
|
fetch(makeSearchUrl("artists", artist.split(/ *[,&] */)[0]), requestOptions).then(r => r.json())
|
||||||
dataUrl.searchParams.set("limit", "1");
|
]);
|
||||||
dataUrl.searchParams.set("with", "serverBubbles");
|
|
||||||
dataUrl.searchParams.set("types", "songs");
|
|
||||||
dataUrl.searchParams.set("term", `${name} ${artist} ${album}`);
|
|
||||||
dataUrl.searchParams.set("include[songs]", "artists");
|
|
||||||
|
|
||||||
const token = await getToken();
|
const appleMusicLink = songData?.songs?.data[0]?.attributes.url;
|
||||||
|
const songLink = songData?.songs?.data[0]?.id ? `https://song.link/i/${songData?.songs?.data[0]?.id}` : undefined;
|
||||||
|
|
||||||
const songData = await fetch(dataUrl, {
|
const albumArtwork = songData?.songs?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512");
|
||||||
headers: {
|
const artistArtwork = artistData?.artists?.data[0]?.attributes.artwork.url.replace("{w}", "512").replace("{h}", "512");
|
||||||
"accept": "*/*",
|
|
||||||
"accept-language": "en-US,en;q=0.9",
|
|
||||||
"authorization": `Bearer ${token}`,
|
|
||||||
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36",
|
|
||||||
"origin": "https://music.apple.com",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(data => data.results.song.data[0]);
|
|
||||||
|
|
||||||
cachedRemoteData = {
|
cachedRemoteData = {
|
||||||
id,
|
id,
|
||||||
data: {
|
data: { appleMusicLink, songLink, albumArtwork, artistArtwork }
|
||||||
appleMusicLink: songData.attributes.url,
|
|
||||||
songLink: `https://song.link/i/${songData.id}`,
|
|
||||||
albumArtwork: songData.attributes.artwork.url.replace("{w}x{h}", "512x512"),
|
|
||||||
artistArtwork: songData.relationships.artists.data[0].attributes.artwork.url.replace("{w}x{h}", "512x512"),
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return cachedRemoteData.data;
|
return cachedRemoteData.data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[AppleMusicRichPresence] Failed to fetch remote data:", e);
|
console.error("[AppleMusicRichPresence] Failed to fetch remote data:", e);
|
||||||
|
|
|
@ -73,8 +73,8 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
// Legcord 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 ("legcord" in window) return;
|
if ("armcord" in window) return;
|
||||||
|
|
||||||
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
|
||||||
|
|
5
src/plugins/automodContext/README.md
Normal file
5
src/plugins/automodContext/README.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# AutomodContext
|
||||||
|
|
||||||
|
Allows you to jump to the messages surrounding an automod hit
|
||||||
|
|
||||||
|
![Visualization](https://github.com/Vendicated/Vencord/assets/61953774/d13740c8-2062-4553-b975-82fd3d6cc08b)
|
73
src/plugins/automodContext/index.tsx
Normal file
73
src/plugins/automodContext/index.tsx
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2024 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
import { Button, ChannelStore, Text } from "@webpack/common";
|
||||||
|
|
||||||
|
const { selectChannel } = findByPropsLazy("selectChannel", "selectVoiceChannel");
|
||||||
|
|
||||||
|
function jumpToMessage(channelId: string, messageId: string) {
|
||||||
|
const guildId = ChannelStore.getChannel(channelId)?.guild_id;
|
||||||
|
|
||||||
|
selectChannel({
|
||||||
|
guildId,
|
||||||
|
channelId,
|
||||||
|
messageId,
|
||||||
|
jumpType: "INSTANT"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function findChannelId(message: any): string | null {
|
||||||
|
const { embeds: [embed] } = message;
|
||||||
|
const channelField = embed.fields.find(({ rawName }) => rawName === "channel_id");
|
||||||
|
|
||||||
|
if (!channelField) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return channelField.rawValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "AutomodContext",
|
||||||
|
description: "Allows you to jump to the messages surrounding an automod hit.",
|
||||||
|
authors: [Devs.JohnyTheCarrot],
|
||||||
|
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: ".Messages.GUILD_AUTOMOD_REPORT_ISSUES",
|
||||||
|
replacement: {
|
||||||
|
match: /\.Messages\.ACTIONS.+?}\)(?=,(\(0.{0,40}\.dot.*?}\)),)/,
|
||||||
|
replace: (m, dot) => `${m},${dot},$self.renderJumpButton({message:arguments[0].message})`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
renderJumpButton: ErrorBoundary.wrap(({ message }: { message: any; }) => {
|
||||||
|
const channelId = findChannelId(message);
|
||||||
|
|
||||||
|
if (!channelId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
style={{ padding: "2px 8px" }}
|
||||||
|
look={Button.Looks.LINK}
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
color={Button.Colors.LINK}
|
||||||
|
onClick={() => jumpToMessage(channelId, message.id)}
|
||||||
|
>
|
||||||
|
<Text color="text-link" variant="text-xs/normal">
|
||||||
|
Jump to Surrounding
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
}, { noop: true })
|
||||||
|
});
|
|
@ -16,34 +16,28 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { 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";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
export default definePlugin({
|
||||||
|
name: "BANger",
|
||||||
|
description: "Replaces the GIF in the ban dialogue with a custom one.",
|
||||||
|
authors: [Devs.Xinto, Devs.Glitch],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "BAN_CONFIRM_TITLE.",
|
||||||
|
replacement: {
|
||||||
|
match: /src:\i\("?\d+"?\)/g,
|
||||||
|
replace: "src: Vencord.Settings.plugins.BANger.source"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
options: {
|
||||||
source: {
|
source: {
|
||||||
description: "Source to replace ban GIF with (Video or Gif)",
|
description: "Source to replace ban GIF with (Video or Gif)",
|
||||||
type: OptionType.STRING,
|
type: OptionType.STRING,
|
||||||
default: "https://i.imgur.com/wp5q52C.mp4",
|
default: "https://i.imgur.com/wp5q52C.mp4",
|
||||||
restartNeeded: true,
|
restartNeeded: true,
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "BANger",
|
|
||||||
description: "Replaces the GIF in the ban dialogue with a custom one.",
|
|
||||||
authors: [Devs.Xinto, Devs.Glitch],
|
|
||||||
settings,
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: "#{intl::BAN_CONFIRM_TITLE}",
|
|
||||||
replacement: {
|
|
||||||
match: /src:\i\("?\d+"?\)/g,
|
|
||||||
replace: "src:$self.source"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
get source() {
|
|
||||||
return settings.store.source;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
# Better Folders
|
|
||||||
|
|
||||||
Better Folders offers a variety of options to improve your folder experience
|
|
||||||
|
|
||||||
Always show the folder icon, regardless of if the folder is open or not
|
|
||||||
|
|
||||||
Only have one folder open at a time
|
|
||||||
|
|
||||||
Open folders in a sidebar:
|
|
||||||
|
|
||||||
![A folder open in a separate sidebar](https://github.com/user-attachments/assets/432d3146-8091-4bae-9c1e-c19046c72947)
|
|
|
@ -18,10 +18,9 @@
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getIntlMessage } from "@utils/discord";
|
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
|
import { findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
|
||||||
import { FluxDispatcher, useMemo } from "@webpack/common";
|
import { FluxDispatcher, i18n, useMemo } from "@webpack/common";
|
||||||
|
|
||||||
import FolderSideBar from "./FolderSideBar";
|
import FolderSideBar from "./FolderSideBar";
|
||||||
|
|
||||||
|
@ -31,9 +30,9 @@ enum FolderIconDisplay {
|
||||||
MoreThanOneFolderExpanded
|
MoreThanOneFolderExpanded
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
|
||||||
const SortedGuildStore = findStoreLazy("SortedGuildStore");
|
|
||||||
const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
|
const GuildsTree = findLazy(m => m.prototype?.moveNextTo);
|
||||||
|
const SortedGuildStore = findStoreLazy("SortedGuildStore");
|
||||||
|
export const ExpandedGuildFolderStore = findStoreLazy("ExpandedGuildFolderStore");
|
||||||
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
|
const FolderUtils = findByPropsLazy("move", "toggleGuildFolderExpand");
|
||||||
|
|
||||||
let lastGuildId = null as string | null;
|
let lastGuildId = null as string | null;
|
||||||
|
@ -119,22 +118,22 @@ export default definePlugin({
|
||||||
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
|
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
|
||||||
{
|
{
|
||||||
match: /\[(\i)\]=(\(0,\i\.\i\).{0,40}getGuildsTree\(\).+?}\))(?=,)/,
|
match: /\[(\i)\]=(\(0,\i\.\i\).{0,40}getGuildsTree\(\).+?}\))(?=,)/,
|
||||||
replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0]?.isBetterFolders,betterFoldersOriginalTree,arguments[0]?.betterFoldersExpandedIds)`
|
replace: (_, originalTreeVar, rest) => `[betterFoldersOriginalTree]=${rest},${originalTreeVar}=$self.getGuildTree(!!arguments[0].isBetterFolders,betterFoldersOriginalTree,arguments[0].betterFoldersExpandedIds)`
|
||||||
},
|
},
|
||||||
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
|
// If we are rendering the Better Folders sidebar, we filter out everything but the servers and folders from the GuildsBar Guild List children
|
||||||
{
|
{
|
||||||
match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/,
|
match: /lastTargetNode:\i\[\i\.length-1\].+?Fragment.+?\]}\)\]/,
|
||||||
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0]?.isBetterFolders))"
|
replace: "$&.filter($self.makeGuildsBarGuildListFilter(!!arguments[0].isBetterFolders))"
|
||||||
},
|
},
|
||||||
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
|
// If we are rendering the Better Folders sidebar, we filter out everything but the scroller for the guild list from the GuildsBar Tree children
|
||||||
{
|
{
|
||||||
match: /unreadMentionsIndicatorBottom,.+?}\)\]/,
|
match: /unreadMentionsIndicatorBottom,.+?}\)\]/,
|
||||||
replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0]?.isBetterFolders))"
|
replace: "$&.filter($self.makeGuildsBarTreeFilter(!!arguments[0].isBetterFolders))"
|
||||||
},
|
},
|
||||||
// Export the isBetterFolders variable to the folders component
|
// Export the isBetterFolders variable to the folders component
|
||||||
{
|
{
|
||||||
match: /switch\(\i\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,/,
|
match: /(?<=\.Messages\.SERVERS.+?switch\((\i)\.type\){case \i\.\i\.FOLDER:.+?folderNode:\i,)/,
|
||||||
replace: '$&isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,'
|
replace: 'isBetterFolders:typeof isBetterFolders!=="undefined"?isBetterFolders:false,'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -168,31 +167,31 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
predicate: () => settings.store.keepIcons,
|
predicate: () => settings.store.keepIcons,
|
||||||
match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i),.+?;)(?=let)/,
|
match: /(?<=let{folderNode:\i,setNodeRef:\i,.+?expanded:(\i),.+?;)(?=let)/,
|
||||||
replace: (_, isExpanded) => `${isExpanded}=!!arguments[0]?.isBetterFolders&&${isExpanded};`
|
replace: (_, isExpanded) => `${isExpanded}=!!arguments[0].isBetterFolders&&${isExpanded};`
|
||||||
},
|
},
|
||||||
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
|
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
|
||||||
{
|
{
|
||||||
predicate: () => !settings.store.keepIcons,
|
predicate: () => !settings.store.keepIcons,
|
||||||
match: /(?<=#{intl::SERVER_FOLDER_PLACEHOLDER}.+?useTransition\)\()/,
|
match: /(?<=\.Messages\.SERVER_FOLDER_PLACEHOLDER.+?useTransition\)\()/,
|
||||||
replace: "$self.shouldShowTransition(arguments[0])&&"
|
replace: "!!arguments[0].isBetterFolders&&"
|
||||||
},
|
},
|
||||||
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
|
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
|
||||||
{
|
{
|
||||||
predicate: () => !settings.store.keepIcons,
|
predicate: () => !settings.store.keepIcons,
|
||||||
match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/,
|
match: /expandedFolderBackground,.+?,(?=\i\(\(\i,\i,\i\)=>{let{key.{0,45}ul)(?<=selected:\i,expanded:(\i),.+?)/,
|
||||||
replace: (m, isExpanded) => `${m}$self.shouldRenderContents(arguments[0],${isExpanded})?null:`
|
replace: (m, isExpanded) => `${m}!arguments[0].isBetterFolders&&${isExpanded}?null:`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
|
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
|
||||||
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||||
match: /(?<=\.wrapper,children:\[)/,
|
match: /(?<=\.wrapper,children:\[)/,
|
||||||
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&"
|
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)&&"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar
|
// Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar
|
||||||
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||||
match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/,
|
match: /(?<=\.expandedFolderBackground.+?}\),)(?=\i,)/,
|
||||||
replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)?null:"
|
replace: "!$self.shouldShowFolderIconAndBackground(!!arguments[0].isBetterFolders,arguments[0].betterFoldersExpandedIds)?null:"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -201,12 +200,12 @@ export default definePlugin({
|
||||||
predicate: () => settings.store.sidebar,
|
predicate: () => settings.store.sidebar,
|
||||||
replacement: {
|
replacement: {
|
||||||
// Render the Better Folders sidebar
|
// Render the Better Folders sidebar
|
||||||
match: /(container.{0,50}({className:\i\.guilds,themeOverride:\i})\))/,
|
match: /(?<=({className:\i\.guilds,themeOverride:\i})\))/,
|
||||||
replace: "$1,$self.FolderSideBar({...$2})"
|
replace: ",$self.FolderSideBar($1)"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "#{intl::DISCODO_DISABLED}",
|
find: ".Messages.DISCODO_DISABLED",
|
||||||
predicate: () => settings.store.closeAllHomeButton,
|
predicate: () => settings.store.closeAllHomeButton,
|
||||||
replacement: {
|
replacement: {
|
||||||
// Close all folders when clicking the home button
|
// Close all folders when clicking the home button
|
||||||
|
@ -250,10 +249,6 @@ export default definePlugin({
|
||||||
dispatchingFoldersClose = false;
|
dispatchingFoldersClose = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
|
||||||
|
|
||||||
LOGOUT() {
|
|
||||||
closeFolders();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -277,11 +272,7 @@ export default definePlugin({
|
||||||
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
|
makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
|
||||||
return child => {
|
return child => {
|
||||||
if (isBetterFolders) {
|
if (isBetterFolders) {
|
||||||
try {
|
return child?.props?.["aria-label"] === i18n.Messages.SERVERS;
|
||||||
return child?.props?.["aria-label"] === getIntlMessage("SERVERS");
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
@ -311,20 +302,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
shouldShowTransition(props: any) {
|
FolderSideBar: guildsBarProps => <FolderSideBar {...guildsBarProps} />,
|
||||||
// Pending guilds
|
|
||||||
if (props?.folderNode?.id === 1) return true;
|
|
||||||
|
|
||||||
return !!props?.isBetterFolders;
|
closeFolders
|
||||||
},
|
|
||||||
|
|
||||||
shouldRenderContents(props: any, isExpanded: boolean) {
|
|
||||||
// Pending guilds
|
|
||||||
if (props?.folderNode?.id === 1) return false;
|
|
||||||
|
|
||||||
return !props?.isBetterFolders && isExpanded;
|
|
||||||
},
|
|
||||||
|
|
||||||
FolderSideBar,
|
|
||||||
closeFolders,
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,9 +34,9 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "#{intl::GIF}",
|
find: ".Messages.GIF,",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /alt:(\i)=(\i\.\i\.string\(\i\.\i#{intl::GIF}\))(?=,[^}]*\}=(\i))/,
|
match: /alt:(\i)=(\i\.\i\.Messages\.GIF)(?=,[^}]*\}=(\i))/,
|
||||||
replace:
|
replace:
|
||||||
// rename prop so we can always use default value
|
// rename prop so we can always use default value
|
||||||
"alt_$$:$1=$self.altify($3)||$2",
|
"alt_$$:$1=$self.altify($3)||$2",
|
||||||
|
|
|
@ -16,31 +16,19 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings, Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { canonicalizeMatch } from "@utils/patches";
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const UserPopoutSectionCssClasses = findByPropsLazy("section", "lastSection");
|
||||||
hide: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Hide notes",
|
|
||||||
default: false,
|
|
||||||
restartNeeded: true
|
|
||||||
},
|
|
||||||
noSpellCheck: {
|
|
||||||
type: OptionType.BOOLEAN,
|
|
||||||
description: "Disable spellcheck in notes",
|
|
||||||
disabled: () => Settings.plugins.BetterNotesBox.hide,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "BetterNotesBox",
|
name: "BetterNotesBox",
|
||||||
description: "Hide notes or disable spellcheck (Configure in settings!!)",
|
description: "Hide notes or disable spellcheck (Configure in settings!!)",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
settings,
|
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
|
@ -48,7 +36,7 @@ export default definePlugin({
|
||||||
all: true,
|
all: true,
|
||||||
// Some modules match the find but the replacement is returned untouched
|
// Some modules match the find but the replacement is returned untouched
|
||||||
noWarn: true,
|
noWarn: true,
|
||||||
predicate: () => settings.store.hide,
|
predicate: () => Vencord.Settings.plugins.BetterNotesBox.hide,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /hideNote:.+?(?=([,}].*?\)))/g,
|
match: /hideNote:.+?(?=([,}].*?\)))/g,
|
||||||
replace: (m, rest) => {
|
replace: (m, rest) => {
|
||||||
|
@ -63,15 +51,40 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "#{intl::NOTE_PLACEHOLDER}",
|
find: "Messages.NOTE_PLACEHOLDER",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /#{intl::NOTE_PLACEHOLDER}\),/,
|
match: /\.NOTE_PLACEHOLDER,/,
|
||||||
replace: "$&spellCheck:!$self.noSpellCheck,"
|
replace: "$&spellCheck:!Vencord.Settings.plugins.BetterNotesBox.noSpellCheck,"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".popularApplicationCommandIds,",
|
||||||
|
replacement: {
|
||||||
|
match: /lastSection:(!?\i)}\),/,
|
||||||
|
replace: "$&$self.patchPadding({lastSection:$1}),"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
get noSpellCheck() {
|
options: {
|
||||||
return settings.store.noSpellCheck;
|
hide: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Hide notes",
|
||||||
|
default: false,
|
||||||
|
restartNeeded: true
|
||||||
|
},
|
||||||
|
noSpellCheck: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Disable spellcheck in notes",
|
||||||
|
disabled: () => Settings.plugins.BetterNotesBox.hide,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
patchPadding: ErrorBoundary.wrap(({ lastSection }) => {
|
||||||
|
if (!lastSection) return null;
|
||||||
|
return (
|
||||||
|
<div className={UserPopoutSectionCssClasses.lastSection} ></div>
|
||||||
|
);
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingDefinitionLazy } from "@api/UserSettingDefinitions";
|
||||||
import { ImageIcon } from "@components/Icons";
|
import { ImageIcon } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getCurrentGuild, openImageModal } from "@utils/discord";
|
import { getCurrentGuild, openImageModal } from "@utils/discord";
|
||||||
|
@ -15,7 +15,7 @@ import { Clipboard, GuildStore, Menu, PermissionStore } from "@webpack/common";
|
||||||
|
|
||||||
const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild");
|
const GuildSettingsActions = findByPropsLazy("open", "selectRole", "updateGuild");
|
||||||
|
|
||||||
const DeveloperMode = getUserSettingLazy("appearance", "developerMode")!;
|
const DeveloperMode = getUserSettingDefinitionLazy("appearance", "developerMode")!;
|
||||||
|
|
||||||
function PencilIcon() {
|
function PencilIcon() {
|
||||||
return (
|
return (
|
||||||
|
@ -65,7 +65,7 @@ export default definePlugin({
|
||||||
name: "BetterRoleContext",
|
name: "BetterRoleContext",
|
||||||
description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile",
|
description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile",
|
||||||
authors: [Devs.Ven, Devs.goodbee],
|
authors: [Devs.Ven, Devs.goodbee],
|
||||||
dependencies: ["UserSettingsAPI"],
|
dependencies: ["UserSettingDefinitionsAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
@ -99,11 +99,7 @@ export default definePlugin({
|
||||||
id="vc-view-role-icon"
|
id="vc-view-role-icon"
|
||||||
label="View Role Icon"
|
label="View Role Icon"
|
||||||
action={() => {
|
action={() => {
|
||||||
openImageModal({
|
openImageModal(`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`);
|
||||||
url: `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`,
|
|
||||||
height: 128,
|
|
||||||
width: 128
|
|
||||||
});
|
|
||||||
}}
|
}}
|
||||||
icon={ImageIcon}
|
icon={ImageIcon}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -47,7 +47,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
find: "#{intl::ADD_ROLE_A11Y_LABEL}",
|
find: ".ADD_ROLE_A11Y_LABEL",
|
||||||
all: true,
|
all: true,
|
||||||
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
|
||||||
noWarn: true,
|
noWarn: true,
|
||||||
|
|
|
@ -60,7 +60,7 @@ export default definePlugin({
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "#{intl::AUTH_SESSIONS_SESSION_LOG_OUT}",
|
find: "Messages.AUTH_SESSIONS_SESSION_LOG_OUT",
|
||||||
replacement: [
|
replacement: [
|
||||||
// Replace children with a single label with state
|
// Replace children with a single label with state
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,69 +0,0 @@
|
||||||
/*
|
|
||||||
* Vencord, a Discord client mod
|
|
||||||
* Copyright (c) 2024 Vendicated and contributors
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
|
||||||
import { getIntlMessage } from "@utils/discord";
|
|
||||||
import { isObjectEmpty } from "@utils/misc";
|
|
||||||
import { Alerts, Menu, useMemo, useState } from "@webpack/common";
|
|
||||||
|
|
||||||
import Plugins from "~plugins";
|
|
||||||
|
|
||||||
function onRestartNeeded() {
|
|
||||||
Alerts.show({
|
|
||||||
title: "Restart required",
|
|
||||||
body: <p>You have changed settings that require a restart.</p>,
|
|
||||||
confirmText: "Restart now",
|
|
||||||
cancelText: "Later!",
|
|
||||||
onConfirm: () => location.reload()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function PluginsSubmenu() {
|
|
||||||
const sortedPlugins = useMemo(() => Object.values(Plugins)
|
|
||||||
.sort((a, b) => a.name.localeCompare(b.name)), []);
|
|
||||||
const [query, setQuery] = useState("");
|
|
||||||
|
|
||||||
const search = query.toLowerCase();
|
|
||||||
const include = (p: typeof Plugins[keyof typeof Plugins]) => (
|
|
||||||
Vencord.Plugins.isPluginEnabled(p.name)
|
|
||||||
&& p.options && !isObjectEmpty(p.options)
|
|
||||||
&& (
|
|
||||||
p.name.toLowerCase().includes(search)
|
|
||||||
|| p.description.toLowerCase().includes(search)
|
|
||||||
|| p.tags?.some(t => t.toLowerCase().includes(search))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const plugins = sortedPlugins.filter(include);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Menu.MenuControlItem
|
|
||||||
id="vc-plugins-search"
|
|
||||||
control={(props, ref) => (
|
|
||||||
<Menu.MenuSearchControl
|
|
||||||
{...props}
|
|
||||||
query={query}
|
|
||||||
onChange={setQuery}
|
|
||||||
ref={ref}
|
|
||||||
placeholder={getIntlMessage("SEARCH")}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!!plugins.length && <Menu.MenuSeparator />}
|
|
||||||
|
|
||||||
{plugins.map(p => (
|
|
||||||
<Menu.MenuItem
|
|
||||||
key={p.name}
|
|
||||||
id={p.name}
|
|
||||||
label={p.name}
|
|
||||||
action={() => openPluginModal(p, onRestartNeeded)}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -7,15 +7,12 @@
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { getIntlMessage } from "@utils/discord";
|
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { waitFor } from "@webpack";
|
import { waitFor } from "@webpack";
|
||||||
import { ComponentDispatch, FocusLock, Menu, useEffect, useRef } from "@webpack/common";
|
import { ComponentDispatch, FocusLock, i18n, Menu, useEffect, useRef } from "@webpack/common";
|
||||||
import type { HTMLAttributes, ReactElement } from "react";
|
import type { HTMLAttributes, ReactElement } from "react";
|
||||||
|
|
||||||
import PluginsSubmenu from "./PluginsSubmenu";
|
|
||||||
|
|
||||||
type SettingsEntry = { section: string, label: string; };
|
type SettingsEntry = { section: string, label: string; };
|
||||||
|
|
||||||
const cl = classNameFactory("");
|
const cl = classNameFactory("");
|
||||||
|
@ -112,7 +109,7 @@ export default definePlugin({
|
||||||
predicate: () => settings.store.disableFade
|
predicate: () => settings.store.disableFade
|
||||||
},
|
},
|
||||||
{ // Load menu TOC eagerly
|
{ // Load menu TOC eagerly
|
||||||
find: "#{intl::USER_SETTINGS_WITH_BUILD_OVERRIDE}",
|
find: "Messages.USER_SETTINGS_WITH_BUILD_OVERRIDE.format",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
|
match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
|
||||||
replace: "$&(async ()=>$2)(),"
|
replace: "$&(async ()=>$2)(),"
|
||||||
|
@ -120,22 +117,14 @@ export default definePlugin({
|
||||||
predicate: () => settings.store.eagerLoad
|
predicate: () => settings.store.eagerLoad
|
||||||
},
|
},
|
||||||
{ // Settings cog context menu
|
{ // Settings cog context menu
|
||||||
find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}",
|
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
||||||
replacement: [
|
replacement: {
|
||||||
{
|
|
||||||
match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
|
match: /(EXPERIMENTS:.+?)(\(0,\i.\i\)\(\))(?=\.filter\(\i=>\{let\{section:\i\}=)/,
|
||||||
replace: "$1$self.wrapMenu($2)"
|
replace: "$1$self.wrapMenu($2)"
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /case \i\.\i\.DEVELOPER_OPTIONS:return \i;/,
|
|
||||||
replace: "$&case 'VencordPlugins':return $self.PluginsSubmenu();"
|
|
||||||
}
|
}
|
||||||
]
|
}
|
||||||
},
|
|
||||||
],
|
],
|
||||||
|
|
||||||
PluginsSubmenu,
|
|
||||||
|
|
||||||
// This is the very outer layer of the entire ui, so we can't wrap this in an ErrorBoundary
|
// This is the very outer layer of the entire ui, so we can't wrap this in an ErrorBoundary
|
||||||
// without possibly also catching unrelated errors of children.
|
// without possibly also catching unrelated errors of children.
|
||||||
//
|
//
|
||||||
|
@ -160,7 +149,7 @@ export default definePlugin({
|
||||||
if (item.section === "HEADER") {
|
if (item.section === "HEADER") {
|
||||||
items.push({ label: item.label, items: [] });
|
items.push({ label: item.label, items: [] });
|
||||||
} else if (item.section === "DIVIDER") {
|
} else if (item.section === "DIVIDER") {
|
||||||
items.push({ label: getIntlMessage("OTHER_OPTIONS"), items: [] });
|
items.push({ label: i18n.Messages.OTHER_OPTIONS, items: [] });
|
||||||
} else {
|
} else {
|
||||||
items.at(-1)!.items.push(item);
|
items.at(-1)!.items.push(item);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,9 +25,11 @@ export default definePlugin({
|
||||||
description: "Upload with a single click, open menu with right click",
|
description: "Upload with a single click, open menu with right click",
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: '"ChannelAttachButton"',
|
find: "Messages.CHAT_ATTACH_UPLOAD_OR_INVITE",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),\.\.\.(\i),/,
|
// Discord merges multiple props here with Object.assign()
|
||||||
|
// This patch passes a third object to it with which we override onClick and onContextMenu
|
||||||
|
match: /CHAT_ATTACH_UPLOAD_OR_INVITE,onDoubleClick:(.+?:void 0),\.\.\.(\i),/,
|
||||||
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
|
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -57,11 +57,7 @@ export const handleViewPreview = async ({ guildId, channelId, ownerId }: Applica
|
||||||
const previewUrl = await ApplicationStreamPreviewStore.getPreviewURL(guildId, channelId, ownerId);
|
const previewUrl = await ApplicationStreamPreviewStore.getPreviewURL(guildId, channelId, ownerId);
|
||||||
if (!previewUrl) return;
|
if (!previewUrl) return;
|
||||||
|
|
||||||
openImageModal({
|
openImageModal(previewUrl);
|
||||||
url: previewUrl,
|
|
||||||
height: 720,
|
|
||||||
width: 1280
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => {
|
export const addViewStreamContext: NavContextMenuPatchCallback = (children, { userId }: { userId: string | bigint; }) => {
|
||||||
|
|
|
@ -45,8 +45,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".embedWrapper,embed",
|
find: ".embedWrapper,embed",
|
||||||
replacement: [{
|
replacement: [{
|
||||||
match: /\.container/,
|
match: /\.embedWrapper(?=.+?channel_id:(\i)\.id)/g,
|
||||||
replace: "$&+(this.props.channel.nsfw? ' vc-nsfw-img': '')"
|
replace: "$&+($1.nsfw?' vc-nsfw-img':'')"
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -155,5 +155,4 @@ export const defaultRules = [
|
||||||
"igshid",
|
"igshid",
|
||||||
"igsh",
|
"igsh",
|
||||||
"share_id@reddit.com",
|
"share_id@reddit.com",
|
||||||
"si@soundcloud.com",
|
|
||||||
];
|
];
|
||||||
|
|
|
@ -12,9 +12,9 @@ import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||||
import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common";
|
import { Button, Forms, useStateFromStores } from "@webpack/common";
|
||||||
|
|
||||||
const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
|
const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
|
||||||
|
|
||||||
const colorPresets = [
|
const colorPresets = [
|
||||||
"#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D",
|
"#1E1514", "#172019", "#13171B", "#1C1C28", "#402D2D",
|
||||||
|
@ -30,12 +30,13 @@ function onPickColor(color: number) {
|
||||||
updateColorVars(hexColor);
|
updateColorVars(hexColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveClientTheme = findByCodeLazy('type:"UNSYNCED_USER_SETTINGS_UPDATE', '"system"===');
|
const saveClientTheme = findByCodeLazy('type:"UNSYNCED_USER_SETTINGS_UPDATE",settings:{useSystemTheme:"system"===');
|
||||||
|
|
||||||
function setTheme(theme: string) {
|
function setTheme(theme: string) {
|
||||||
saveClientTheme({ theme });
|
saveClientTheme({ theme });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ThemeStore = findStoreLazy("ThemeStore");
|
||||||
const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore");
|
const NitroThemeStore = findStoreLazy("ClientThemesBackgroundStore");
|
||||||
|
|
||||||
function ThemeSettings() {
|
function ThemeSettings() {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue