mirror of
https://github.com/Vendicated/Vencord
synced 2024-08-26 03:37:47 -04:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
8c5c4c78a5
102 changed files with 2042 additions and 1206 deletions
|
@ -16,5 +16,6 @@ DON'T
|
|||
|
||||
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 publicly challenging them and instead contact a Moderator on our Discord server or send an email to vendicated+conduct@riseup.net!
|
||||
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)
|
||||
and opening a modmail ticket.
|
||||
|
|
|
@ -1,82 +1,55 @@
|
|||
# Contribution Guide
|
||||
# Contributing to Vencord
|
||||
|
||||
First of all, thank you for contributing! :3
|
||||
Vencord is a community project and welcomes any kind of contribution from anyone!
|
||||
|
||||
To ensure your contribution is robust, please follow the below guide!
|
||||
We have development documentation for new contributors, which can be found at <https://docs.vencord.dev>.
|
||||
|
||||
For a friendly introduction to plugins, see [Megu's Plugin Guide!](docs/2_PLUGINS.md)
|
||||
All contributions should be made in accordance with our [Code of Conduct](./CODE_OF_CONDUCT.md).
|
||||
|
||||
## Style Guide
|
||||
## How to contribute
|
||||
|
||||
- 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
|
||||
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).
|
||||
|
||||
## Contributing a Plugin
|
||||
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.
|
||||
|
||||
Because plugins modify code directly, incompatibilities are a problem.
|
||||
## Write a plugin
|
||||
|
||||
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.
|
||||
Writing a plugin is the primary way to contribute.
|
||||
|
||||
Follow the below guide to make your first plugin!
|
||||
Before starting your 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
|
||||
|
||||
### Finding the right module to patch
|
||||
### Plugin Rules
|
||||
|
||||
If the thing you want to patch is an action performed when interacting with a part of the UI, use React DevTools.
|
||||
They come preinstalled and can be found as the "Components" tab in DevTools.
|
||||
Use the Selector (top left) to select the UI Element. Now you can see all callbacks, props or jump to the source
|
||||
directly.
|
||||
- No simple slash command plugins like `/cat`. Instead, make a [user installable Discord bot](https://discord.com/developers/docs/change-log#userinstallable-apps-preview)
|
||||
- No simple text replace plugins like Let me Google that for you. The TextReplace plugin can do this
|
||||
- No raw DOM manipulation. Use proper patches and React
|
||||
- No FakeDeafen or FakeMute
|
||||
- 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
|
||||
|
||||
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...
|
||||
## Improve Vencord itself
|
||||
|
||||
### Writing a robust patch
|
||||
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.
|
||||
|
||||
##### "find"
|
||||
Or if you notice any bugs or typos, feel free to fix them!
|
||||
|
||||
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)
|
||||
## Contribute to our Documentation
|
||||
|
||||
##### "match"
|
||||
The source code of our documentation is available at <https://github.com/Vencord/Docs>
|
||||
|
||||
This is the regex that will operate on the module found with "find". Just like in find, you should make sure
|
||||
this only matches exactly the part you want to patch and no other parts in the file.
|
||||
If you see anything outdated, incorrect or lacking, please fix it!
|
||||
If you think a new page should be added, feel free to suggest it via an issue and we can discuss.
|
||||
|
||||
The easiest way to write and test your regex is the following:
|
||||
## Help out users in our Discord community
|
||||
|
||||
- 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
|
||||
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
|
||||
We have an open support channel in our [Discord community](https://vencord.dev/discord).
|
||||
Helping out users there is always appreciated! The more, the merrier.
|
||||
|
|
|
@ -65,8 +65,3 @@ Regardless, if your account is very important to you and it getting disabled wou
|
|||
Additionally, make sure not to post screenshots with Vencord in a server where you might get banned for it
|
||||
|
||||
</details>
|
||||
|
||||
```math
|
||||
\ce{$\unicode[goombafont; color:red; z-index: -10; position: fixed; top: 0; left: 0; height: 100%; object-fit: cover; width: 100%; opacity: 1; background: url('https://github.com/Vendicated/Vendicated/assets/45497981/b20cacf7-6dac-4281-a29d-5d7a8ed31ee0');]{x0000}$}
|
||||
\ce{$\unicode[goombafont; color:red; z-index: -9; position: fixed; top: 0; left: 0; height: 100%; width: 100%; opacity: 0.9; background: var(--bgColor-default);]{x0000}$}
|
||||
```
|
||||
|
|
|
@ -2,23 +2,22 @@ if (typeof browser === "undefined") {
|
|||
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");
|
||||
style.type = "text/css";
|
||||
style.rel = "stylesheet";
|
||||
style.href = browser.runtime.getURL("dist/Vencord.css");
|
||||
|
||||
document.documentElement.append(script);
|
||||
|
||||
document.addEventListener(
|
||||
"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 }
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"manifest_version": 3,
|
||||
"minimum_chrome_version": "91",
|
||||
"minimum_chrome_version": "111",
|
||||
|
||||
"name": "Vencord Web",
|
||||
"description": "The cutest Discord mod now in your browser",
|
||||
|
@ -22,7 +22,15 @@
|
|||
"run_at": "document_start",
|
||||
"matches": ["*://*.discord.com/*"],
|
||||
"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,7 +22,15 @@
|
|||
"run_at": "document_start",
|
||||
"matches": ["*://*.discord.com/*"],
|
||||
"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"
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -35,7 +43,7 @@
|
|||
"browser_specific_settings": {
|
||||
"gecko": {
|
||||
"id": "vencord-firefox@vendicated.dev",
|
||||
"strict_min_version": "91.0"
|
||||
"strict_min_version": "128.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,97 +0,0 @@
|
|||
> [!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
|
||||
```
|
|
@ -1,111 +0,0 @@
|
|||
# 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,7 +1,7 @@
|
|||
{
|
||||
"name": "vencord",
|
||||
"private": "true",
|
||||
"version": "1.9.0",
|
||||
"version": "1.9.3",
|
||||
"description": "The cutest Discord client mod",
|
||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||
"bugs": {
|
||||
|
@ -13,9 +13,6 @@
|
|||
},
|
||||
"license": "GPL-3.0-or-later",
|
||||
"author": "Vendicated",
|
||||
"directories": {
|
||||
"doc": "docs"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs",
|
||||
"buildStandalone": "pnpm build --standalone",
|
||||
|
@ -43,7 +40,7 @@
|
|||
"eslint-plugin-simple-header": "^1.0.2",
|
||||
"fflate": "^0.7.4",
|
||||
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
||||
"monaco-editor": "^0.43.0",
|
||||
"monaco-editor": "^0.50.0",
|
||||
"nanoid": "^4.0.2",
|
||||
"virtual-merge": "^1.0.1"
|
||||
},
|
||||
|
|
|
@ -35,8 +35,8 @@ importers:
|
|||
specifier: github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3
|
||||
version: https://codeload.github.com/mattdesl/gifenc/tar.gz/64842fca317b112a8590f8fef2bf3825da8f6fe3
|
||||
monaco-editor:
|
||||
specifier: ^0.43.0
|
||||
version: 0.43.0
|
||||
specifier: ^0.50.0
|
||||
version: 0.50.0
|
||||
nanoid:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2
|
||||
|
@ -1531,6 +1531,10 @@ packages:
|
|||
is-core-module@2.13.1:
|
||||
resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
|
||||
|
||||
is-core-module@2.14.0:
|
||||
resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
is-data-descriptor@0.1.4:
|
||||
resolution: {integrity: sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -1791,8 +1795,8 @@ packages:
|
|||
moment@2.29.4:
|
||||
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
|
||||
|
||||
monaco-editor@0.43.0:
|
||||
resolution: {integrity: sha512-cnoqwQi/9fml2Szamv1XbSJieGJ1Dc8tENVMD26Kcfl7xGQWp7OBKMjlwKVGYFJ3/AXJjSOGvcqK7Ry/j9BM1Q==}
|
||||
monaco-editor@0.50.0:
|
||||
resolution: {integrity: sha512-8CclLCmrRRh+sul7C08BmPBP3P8wVWfBHomsTcndxg5NRCEPfu/mc2AGU8k37ajjDVXcXFc12ORAMUkmk+lkFA==}
|
||||
|
||||
ms@2.0.0:
|
||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||
|
@ -3463,7 +3467,7 @@ snapshots:
|
|||
eslint-import-resolver-node@0.3.9:
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
is-core-module: 2.13.1
|
||||
is-core-module: 2.14.0
|
||||
resolve: 1.22.8
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
@ -3490,7 +3494,7 @@ snapshots:
|
|||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.8.1(@typescript-eslint/parser@5.59.1(eslint@8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))(typescript@5.4.5))(eslint-import-resolver-node@0.3.9)(eslint@8.46.0(patch_hash=xm46kqcmdgzlmm4aifkfpxaho4))
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.13.1
|
||||
is-core-module: 2.14.0
|
||||
is-glob: 4.0.3
|
||||
minimatch: 3.1.2
|
||||
object.fromentries: 2.0.8
|
||||
|
@ -3934,6 +3938,10 @@ snapshots:
|
|||
dependencies:
|
||||
hasown: 2.0.2
|
||||
|
||||
is-core-module@2.14.0:
|
||||
dependencies:
|
||||
hasown: 2.0.2
|
||||
|
||||
is-data-descriptor@0.1.4:
|
||||
dependencies:
|
||||
kind-of: 3.2.2
|
||||
|
@ -4169,7 +4177,7 @@ snapshots:
|
|||
|
||||
moment@2.29.4: {}
|
||||
|
||||
monaco-editor@0.43.0: {}
|
||||
monaco-editor@0.50.0: {}
|
||||
|
||||
ms@2.0.0: {}
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ import esbuild from "esbuild";
|
|||
import { readdir } from "fs/promises";
|
||||
import { join } from "path";
|
||||
|
||||
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, VERSION, watch } from "./common.mjs";
|
||||
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, watch } from "./common.mjs";
|
||||
|
||||
const defines = {
|
||||
IS_STANDALONE,
|
||||
|
@ -76,22 +76,20 @@ const globNativesPlugin = {
|
|||
for (const dir of pluginDirs) {
|
||||
const dirPath = join("src", dir);
|
||||
if (!await exists(dirPath)) continue;
|
||||
const plugins = await readdir(dirPath);
|
||||
for (const p of plugins) {
|
||||
const nativePath = join(dirPath, p, "native.ts");
|
||||
const indexNativePath = join(dirPath, p, "native/index.ts");
|
||||
const plugins = await readdir(dirPath, { withFileTypes: true });
|
||||
for (const file of plugins) {
|
||||
const fileName = file.name;
|
||||
const nativePath = join(dirPath, fileName, "native.ts");
|
||||
const indexNativePath = join(dirPath, fileName, "native/index.ts");
|
||||
|
||||
if (!(await exists(nativePath)) && !(await exists(indexNativePath)))
|
||||
continue;
|
||||
|
||||
const nameParts = p.split(".");
|
||||
const namePartsWithoutTarget = nameParts.length === 1 ? nameParts : nameParts.slice(0, -1);
|
||||
// pluginName.thing.desktop -> PluginName.thing
|
||||
const cleanPluginName = p[0].toUpperCase() + namePartsWithoutTarget.join(".").slice(1);
|
||||
const pluginName = await resolvePluginName(dirPath, file);
|
||||
|
||||
const mod = `p${i}`;
|
||||
code += `import * as ${mod} from "./${dir}/${p}/native";\n`;
|
||||
natives += `${JSON.stringify(cleanPluginName)}:${mod},\n`;
|
||||
code += `import * as ${mod} from "./${dir}/${fileName}/native";\n`;
|
||||
natives += `${JSON.stringify(pluginName)}:${mod},\n`;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,32 @@ export const banner = {
|
|||
`.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) {
|
||||
return await access(path, FsConstants.F_OK)
|
||||
.then(() => true)
|
||||
|
@ -88,31 +114,48 @@ export const globPlugins = kind => ({
|
|||
build.onLoad({ filter, namespace: "import-plugins" }, async () => {
|
||||
const pluginDirs = ["plugins/_api", "plugins/_core", "plugins", "userplugins"];
|
||||
let code = "";
|
||||
let plugins = "\n";
|
||||
let pluginsCode = "\n";
|
||||
let metaCode = "\n";
|
||||
let excludedCode = "\n";
|
||||
let i = 0;
|
||||
for (const dir of pluginDirs) {
|
||||
if (!await exists(`./src/${dir}`)) continue;
|
||||
const files = await readdir(`./src/${dir}`);
|
||||
for (const file of files) {
|
||||
if (file.startsWith("_") || file.startsWith(".")) continue;
|
||||
if (file === "index.ts") continue;
|
||||
const userPlugin = dir === "userplugins";
|
||||
|
||||
const fullDir = `./src/${dir}`;
|
||||
if (!await exists(fullDir)) continue;
|
||||
const files = await readdir(fullDir, { withFileTypes: true });
|
||||
for (const file of files) {
|
||||
const fileName = file.name;
|
||||
if (fileName.startsWith("_") || fileName.startsWith(".")) continue;
|
||||
if (fileName === "index.ts") continue;
|
||||
|
||||
const target = getPluginTarget(fileName);
|
||||
|
||||
const target = getPluginTarget(file);
|
||||
if (target && !IS_REPORTER) {
|
||||
if (target === "dev" && !watch) continue;
|
||||
if (target === "web" && kind === "discordDesktop") continue;
|
||||
if (target === "desktop" && kind === "web") continue;
|
||||
if (target === "discordDesktop" && kind !== "discordDesktop") continue;
|
||||
if (target === "vencordDesktop" && kind !== "vencordDesktop") continue;
|
||||
const excluded =
|
||||
(target === "dev" && !IS_DEV) ||
|
||||
(target === "web" && kind === "discordDesktop") ||
|
||||
(target === "desktop" && kind === "web") ||
|
||||
(target === "discordDesktop" && kind !== "discordDesktop") ||
|
||||
(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 mod = `p${i}`;
|
||||
code += `import ${mod} from "./${dir}/${file.replace(/\.tsx?$/, "")}";\n`;
|
||||
plugins += `[${mod}.name]:${mod},\n`;
|
||||
code += `import ${mod} from "./${dir}/${fileName.replace(/\.tsx?$/, "")}";\n`;
|
||||
pluginsCode += `[${mod}.name]:${mod},\n`;
|
||||
metaCode += `[${mod}.name]:${JSON.stringify({ folderName, userPlugin })},\n`; // TODO: add excluded plugins to display in the UI?
|
||||
i++;
|
||||
}
|
||||
}
|
||||
code += `export default {${plugins}};`;
|
||||
code += `export default {${pluginsCode}};export const PluginMeta={${metaCode}};export const ExcludedPlugins={${excludedCode}};`;
|
||||
return {
|
||||
contents: code,
|
||||
resolveDir: "./src"
|
||||
|
|
|
@ -39,7 +39,7 @@ interface PluginData {
|
|||
hasCommands: boolean;
|
||||
required: boolean;
|
||||
enabledByDefault: boolean;
|
||||
target: "discordDesktop" | "vencordDesktop" | "web" | "dev";
|
||||
target: "discordDesktop" | "vencordDesktop" | "desktop" | "web" | "dev";
|
||||
filePath: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -136,7 +136,6 @@ async function printReport() {
|
|||
body: JSON.stringify({
|
||||
description: "Here's the latest Vencord Report!",
|
||||
username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""),
|
||||
avatar_url: "https://cdn.discordapp.com/avatars/1017176847865352332/c312b6b44179ae6817de7e4b09e9c6af.webp?size=512",
|
||||
embeds: [
|
||||
{
|
||||
title: "Bad Patches",
|
||||
|
@ -290,6 +289,8 @@ page.on("console", async e => {
|
|||
|
||||
page.on("error", e => console.error("[Error]", e.message));
|
||||
page.on("pageerror", e => {
|
||||
if (e.message.includes("Sentry successfully disabled")) return;
|
||||
|
||||
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) {
|
||||
console.error("[Page Error]", e.message);
|
||||
report.otherErrors.push(e.message);
|
||||
|
|
|
@ -44,6 +44,11 @@ export interface ProfileBadge {
|
|||
position?: BadgePosition;
|
||||
/** The badge name to display, Discord uses this. Required for component badges */
|
||||
key?: string;
|
||||
|
||||
/**
|
||||
* Allows dynamically returning multiple badges
|
||||
*/
|
||||
getBadges?(userInfo: BadgeUserArgs): ProfileBadge[];
|
||||
}
|
||||
|
||||
const Badges = new Set<ProfileBadge>();
|
||||
|
@ -73,9 +78,16 @@ export function _getBadges(args: BadgeUserArgs) {
|
|||
const badges = [] as ProfileBadge[];
|
||||
for (const badge of Badges) {
|
||||
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
|
||||
? badges.unshift({ ...badge, ...args })
|
||||
: badges.push({ ...badge, ...args });
|
||||
? badges.unshift(...b)
|
||||
: badges.push(...b);
|
||||
}
|
||||
}
|
||||
const donorBadges = (Plugins.BadgeAPI as unknown as typeof import("../plugins/_api/badges").default).getDonorBadges(args.userId);
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
import * as DataStore from "@api/DataStore";
|
||||
import { Settings } from "@api/Settings";
|
||||
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 { useAwaiter } from "@utils/react";
|
||||
import { Alerts, Button, Forms, React, Text, Timestamp, useEffect, useReducer, useState } from "@webpack/common";
|
||||
|
@ -170,24 +172,31 @@ function LogModal({ modalProps, close }: { modalProps: ModalProps; close(): void
|
|||
</ModalContent>
|
||||
|
||||
<ModalFooter>
|
||||
<Button
|
||||
disabled={log.length === 0}
|
||||
onClick={() => {
|
||||
Alerts.show({
|
||||
title: "Are you sure?",
|
||||
body: `This will permanently remove ${log.length} notification${log.length === 1 ? "" : "s"}. This action cannot be undone.`,
|
||||
async onConfirm() {
|
||||
await DataStore.set(KEY, []);
|
||||
signals.forEach(x => x());
|
||||
},
|
||||
confirmText: "Do it!",
|
||||
confirmColor: "vc-notification-log-danger-btn",
|
||||
cancelText: "Nevermind"
|
||||
});
|
||||
}}
|
||||
>
|
||||
Clear Notification Log
|
||||
</Button>
|
||||
<Flex>
|
||||
<Button onClick={openNotificationSettingsModal}>
|
||||
Notification Settings
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
disabled={log.length === 0}
|
||||
color={Button.Colors.RED}
|
||||
onClick={() => {
|
||||
Alerts.show({
|
||||
title: "Are you sure?",
|
||||
body: `This will permanently remove ${log.length} notification${log.length === 1 ? "" : "s"}. This action cannot be undone.`,
|
||||
async onConfirm() {
|
||||
await DataStore.set(KEY, []);
|
||||
signals.forEach(x => x());
|
||||
},
|
||||
confirmText: "Do it!",
|
||||
confirmColor: "vc-notification-log-danger-btn",
|
||||
cancelText: "Nevermind"
|
||||
});
|
||||
}}
|
||||
>
|
||||
Clear Notification Log
|
||||
</Button>
|
||||
</Flex>
|
||||
</ModalFooter>
|
||||
</ModalRoot>
|
||||
);
|
||||
|
|
|
@ -129,7 +129,7 @@ export const SettingsStore = new SettingsStoreClass(settings, {
|
|||
|
||||
if (path === "plugins" && key in plugins)
|
||||
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
|
||||
|
|
|
@ -1,69 +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 { proxyLazy } from "@utils/lazy";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { findModuleId, proxyLazyWebpack, wreq } from "@webpack";
|
||||
|
||||
import { Settings } from "./Settings";
|
||||
|
||||
interface Setting<T> {
|
||||
/**
|
||||
* Get the setting value
|
||||
*/
|
||||
getSetting(): T;
|
||||
/**
|
||||
* Update the setting value
|
||||
* @param value The new value
|
||||
*/
|
||||
updateSetting(value: T | ((old: T) => T)): Promise<void>;
|
||||
/**
|
||||
* React hook for automatically updating components when the setting is updated
|
||||
*/
|
||||
useSetting(): T;
|
||||
settingsStoreApiGroup: string;
|
||||
settingsStoreApiName: string;
|
||||
}
|
||||
|
||||
export const SettingsStores: Array<Setting<any>> | undefined = proxyLazyWebpack(() => {
|
||||
const modId = findModuleId('"textAndImages","renderSpoilers"') as any;
|
||||
if (modId == null) return new Logger("SettingsStoreAPI").error("Didn't find stores module.");
|
||||
|
||||
const mod = wreq(modId);
|
||||
if (mod == null) return;
|
||||
|
||||
return Object.values(mod).filter((s: any) => s?.settingsStoreApiGroup) as any;
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the store for a setting
|
||||
* @param group The setting group
|
||||
* @param name The name of the setting
|
||||
*/
|
||||
export function getSettingStore<T = any>(group: string, name: string): Setting<T> | undefined {
|
||||
if (!Settings.plugins.SettingsStoreAPI.enabled) throw new Error("Cannot use SettingsStoreAPI without setting as dependency.");
|
||||
|
||||
return SettingsStores?.find(s => s?.settingsStoreApiGroup === group && s?.settingsStoreApiName === name);
|
||||
}
|
||||
|
||||
/**
|
||||
* getSettingStore but lazy
|
||||
*/
|
||||
export function getSettingStoreLazy<T = any>(group: string, name: string) {
|
||||
return proxyLazy(() => getSettingStore<T>(group, name));
|
||||
}
|
81
src/api/UserSettings.ts
Normal file
81
src/api/UserSettings.ts
Normal file
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Vencord, a modification for Discord's desktop app
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { findModuleId, proxyLazyWebpack, wreq } from "@webpack";
|
||||
|
||||
interface UserSettingDefinition<T> {
|
||||
/**
|
||||
* Get the setting value
|
||||
*/
|
||||
getSetting(): T;
|
||||
/**
|
||||
* Update the setting value
|
||||
* @param value The new value
|
||||
*/
|
||||
updateSetting(value: T): Promise<void>;
|
||||
/**
|
||||
* Update the setting value
|
||||
* @param value A callback that accepts the old value as the first argument, and returns the new value
|
||||
*/
|
||||
updateSetting(value: (old: T) => T): Promise<void>;
|
||||
/**
|
||||
* Stateful React hook for this setting value
|
||||
*/
|
||||
useSetting(): T;
|
||||
userSettingsAPIGroup: string;
|
||||
userSettingsAPIName: string;
|
||||
}
|
||||
|
||||
export const UserSettings: Record<PropertyKey, UserSettingDefinition<any>> | undefined = proxyLazyWebpack(() => {
|
||||
const modId = findModuleId('"textAndImages","renderSpoilers"');
|
||||
if (modId == null) return new Logger("UserSettingsAPI ").error("Didn't find settings module.");
|
||||
|
||||
return wreq(modId as any);
|
||||
});
|
||||
|
||||
/**
|
||||
* Get the setting with the given setting group and name.
|
||||
*
|
||||
* @param group The setting group
|
||||
* @param name The name of the setting
|
||||
*/
|
||||
export function getUserSetting<T = any>(group: string, name: string): UserSettingDefinition<T> | undefined {
|
||||
if (!Vencord.Plugins.isPluginEnabled("UserSettingsAPI")) throw new Error("Cannot use UserSettingsAPI without setting as dependency.");
|
||||
|
||||
for (const key in UserSettings) {
|
||||
const userSetting = UserSettings[key];
|
||||
|
||||
if (userSetting.userSettingsAPIGroup === group && userSetting.userSettingsAPIName === name) {
|
||||
return userSetting;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link getUserSettingDefinition}, lazy.
|
||||
*
|
||||
* Get the setting with the given setting group and name.
|
||||
*
|
||||
* @param group The setting group
|
||||
* @param name The name of the setting
|
||||
*/
|
||||
export function getUserSettingLazy<T = any>(group: string, name: string) {
|
||||
return proxyLazy(() => getUserSetting<T>(group, name));
|
||||
}
|
|
@ -31,8 +31,8 @@ import * as $Notices from "./Notices";
|
|||
import * as $Notifications from "./Notifications";
|
||||
import * as $ServerList from "./ServerList";
|
||||
import * as $Settings from "./Settings";
|
||||
import * as $SettingsStores from "./SettingsStores";
|
||||
import * as $Styles from "./Styles";
|
||||
import * as $UserSettings from "./UserSettings";
|
||||
|
||||
/**
|
||||
* An API allowing you to listen to Message Clicks or run your own logic
|
||||
|
@ -118,4 +118,7 @@ export const ChatButtons = $ChatButtons;
|
|||
*/
|
||||
export const MessageUpdater = $MessageUpdater;
|
||||
|
||||
export const SettingsStores = $SettingsStores;
|
||||
/**
|
||||
* An API allowing you to get an user setting
|
||||
*/
|
||||
export const UserSettings = $UserSettings;
|
||||
|
|
|
@ -31,10 +31,20 @@ export interface ExpandableHeaderProps {
|
|||
headerText: string;
|
||||
children: React.ReactNode;
|
||||
buttons?: React.ReactNode[];
|
||||
forceOpen?: boolean;
|
||||
}
|
||||
|
||||
export function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipText, defaultState = false, onDropDownClick, headerText }: ExpandableHeaderProps) {
|
||||
const [showContent, setShowContent] = useState(defaultState);
|
||||
export function ExpandableHeader({
|
||||
children,
|
||||
onMoreClick,
|
||||
buttons,
|
||||
moreTooltipText,
|
||||
onDropDownClick,
|
||||
headerText,
|
||||
defaultState = false,
|
||||
forceOpen = false,
|
||||
}: ExpandableHeaderProps) {
|
||||
const [showContent, setShowContent] = useState(defaultState || forceOpen);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -90,6 +100,7 @@ export function ExpandableHeader({ children, onMoreClick, buttons, moreTooltipTe
|
|||
setShowContent(v => !v);
|
||||
onDropDownClick?.(showContent);
|
||||
}}
|
||||
disabled={forceOpen}
|
||||
>
|
||||
<svg
|
||||
width="24"
|
||||
|
|
28
src/components/Grid.tsx
Normal file
28
src/components/Grid.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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,19 +18,17 @@
|
|||
|
||||
import "./iconStyles.css";
|
||||
|
||||
import { getTheme, Theme } from "@utils/discord";
|
||||
import { classes } from "@utils/misc";
|
||||
import { i18n } from "@webpack/common";
|
||||
import type { PropsWithChildren, SVGProps } from "react";
|
||||
import type { PropsWithChildren } from "react";
|
||||
|
||||
interface BaseIconProps extends IconProps {
|
||||
viewBox: string;
|
||||
}
|
||||
|
||||
interface IconProps extends SVGProps<SVGSVGElement> {
|
||||
className?: string;
|
||||
height?: string | number;
|
||||
width?: string | number;
|
||||
}
|
||||
type IconProps = JSX.IntrinsicElements["svg"];
|
||||
type ImageProps = JSX.IntrinsicElements["img"];
|
||||
|
||||
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
|
||||
return (
|
||||
|
@ -290,3 +288,127 @@ export function NoEntrySignIcon(props: IconProps) {
|
|||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
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} />;
|
||||
}
|
||||
|
|
|
@ -11,20 +11,16 @@ import { classNameFactory } from "@api/Styles";
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Link } from "@components/Link";
|
||||
import { DevsById } from "@utils/constants";
|
||||
import { fetchUserProfile, getTheme, Theme } from "@utils/discord";
|
||||
import { pluralise } from "@utils/misc";
|
||||
import { fetchUserProfile } from "@utils/discord";
|
||||
import { classes, pluralise } from "@utils/misc";
|
||||
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
|
||||
import { Forms, MaskedLink, showToast, Tooltip, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
|
||||
import { Forms, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
|
||||
import Plugins from "~plugins";
|
||||
|
||||
import { PluginCard } from ".";
|
||||
|
||||
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
||||
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
||||
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
||||
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
||||
import { GithubButton, WebsiteButton } from "./LinkIconButton";
|
||||
|
||||
const cl = classNameFactory("vc-author-modal-");
|
||||
|
||||
|
@ -40,16 +36,6 @@ export function openContributorModal(user: User) {
|
|||
);
|
||||
}
|
||||
|
||||
function GithubIcon() {
|
||||
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
|
||||
return <img src={src} alt="GitHub" />;
|
||||
}
|
||||
|
||||
function WebsiteIcon() {
|
||||
const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
|
||||
return <img src={src} alt="Website" />;
|
||||
}
|
||||
|
||||
function ContributorModal({ user }: { user: User; }) {
|
||||
useSettings();
|
||||
|
||||
|
@ -86,24 +72,18 @@ function ContributorModal({ user }: { user: User; }) {
|
|||
/>
|
||||
<Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle>
|
||||
|
||||
<div className={cl("links")}>
|
||||
<div className={classes("vc-settings-modal-links", cl("links"))}>
|
||||
{website && (
|
||||
<Tooltip text={website}>
|
||||
{props => (
|
||||
<MaskedLink {...props} href={"https://" + website}>
|
||||
<WebsiteIcon />
|
||||
</MaskedLink>
|
||||
)}
|
||||
</Tooltip>
|
||||
<WebsiteButton
|
||||
text={website}
|
||||
href={`https://${website}`}
|
||||
/>
|
||||
)}
|
||||
{githubName && (
|
||||
<Tooltip text={githubName}>
|
||||
{props => (
|
||||
<MaskedLink {...props} href={`https://github.com/${githubName}`}>
|
||||
<GithubIcon />
|
||||
</MaskedLink>
|
||||
)}
|
||||
</Tooltip>
|
||||
<GithubButton
|
||||
text={githubName}
|
||||
href={`https://github.com/${githubName}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
|
12
src/components/PluginSettings/LinkIconButton.css
Normal file
12
src/components/PluginSettings/LinkIconButton.css
Normal file
|
@ -0,0 +1,12 @@
|
|||
.vc-settings-modal-link-icon {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border-radius: 50%;
|
||||
border: 4px solid var(--background-tertiary);
|
||||
box-sizing: border-box
|
||||
}
|
||||
|
||||
.vc-settings-modal-links {
|
||||
display: flex;
|
||||
gap: 0.2em;
|
||||
}
|
39
src/components/PluginSettings/LinkIconButton.tsx
Normal file
39
src/components/PluginSettings/LinkIconButton.tsx
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./LinkIconButton.css";
|
||||
|
||||
import { MaskedLink, Tooltip } from "@webpack/common";
|
||||
|
||||
import { GithubIcon, WebsiteIcon } from "..";
|
||||
|
||||
export function GithubLinkIcon() {
|
||||
return <GithubIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
|
||||
}
|
||||
|
||||
export function WebsiteLinkIcon() {
|
||||
return <WebsiteIcon aria-hidden className={"vc-settings-modal-link-icon"} />;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
text: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
function LinkIcon({ text, href, Icon }: Props & { Icon: React.ComponentType; }) {
|
||||
return (
|
||||
<Tooltip text={text}>
|
||||
{props => (
|
||||
<MaskedLink {...props} href={href}>
|
||||
<Icon />
|
||||
</MaskedLink>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export const WebsiteButton = (props: Props) => <LinkIcon {...props} Icon={WebsiteLinkIcon} />;
|
||||
export const GithubButton = (props: Props) => <LinkIcon {...props} Icon={GithubLinkIcon} />;
|
7
src/components/PluginSettings/PluginModal.css
Normal file
7
src/components/PluginSettings/PluginModal.css
Normal file
|
@ -0,0 +1,7 @@
|
|||
.vc-plugin-modal-info {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.vc-plugin-modal-description {
|
||||
flex-grow: 1;
|
||||
}
|
|
@ -16,20 +16,26 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import "./PluginModal.css";
|
||||
|
||||
import { generateId } from "@api/Commands";
|
||||
import { useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { gitRemote } from "@shared/vencordUserAgent";
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes, isObjectEmpty } from "@utils/misc";
|
||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
|
||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { OptionType, Plugin } from "@utils/types";
|
||||
import { findByPropsLazy, findComponentByCodeLazy } from "@webpack";
|
||||
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
import { Constructor } from "type-fest";
|
||||
|
||||
import { PluginMeta } from "~plugins";
|
||||
|
||||
import {
|
||||
ISettingElementProps,
|
||||
SettingBooleanComponent,
|
||||
|
@ -40,6 +46,9 @@ import {
|
|||
SettingTextComponent
|
||||
} from "./components";
|
||||
import { openContributorModal } from "./ContributorModal";
|
||||
import { GithubButton, WebsiteButton } from "./LinkIconButton";
|
||||
|
||||
const cl = classNameFactory("vc-plugin-modal-");
|
||||
|
||||
const UserSummaryItem = findComponentByCodeLazy("defaultRenderUser", "showDefaultAvatarsForNullUsers");
|
||||
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
||||
|
@ -180,16 +189,54 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
);
|
||||
}
|
||||
|
||||
/*
|
||||
function switchToPopout() {
|
||||
onClose();
|
||||
|
||||
const PopoutKey = `DISCORD_VENCORD_PLUGIN_SETTINGS_MODAL_${plugin.name}`;
|
||||
PopoutActions.open(
|
||||
PopoutKey,
|
||||
() => <PluginModal
|
||||
transitionState={transitionState}
|
||||
plugin={plugin}
|
||||
onRestartNeeded={onRestartNeeded}
|
||||
onClose={() => PopoutActions.close(PopoutKey)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
*/
|
||||
|
||||
const pluginMeta = PluginMeta[plugin.name];
|
||||
|
||||
return (
|
||||
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM} className="vc-text-selectable">
|
||||
<ModalHeader separator={false}>
|
||||
<Text variant="heading-lg/semibold" style={{ flexGrow: 1 }}>{plugin.name}</Text>
|
||||
|
||||
{/*
|
||||
<Button look={Button.Looks.BLANK} onClick={switchToPopout}>
|
||||
<OpenExternalIcon aria-label="Open in Popout" />
|
||||
</Button>
|
||||
*/}
|
||||
<ModalCloseButton onClick={onClose} />
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<Forms.FormSection>
|
||||
<Forms.FormTitle tag="h3">About {plugin.name}</Forms.FormTitle>
|
||||
<Forms.FormText>{plugin.description}</Forms.FormText>
|
||||
<Flex className={cl("info")}>
|
||||
<Forms.FormText className={cl("description")}>{plugin.description}</Forms.FormText>
|
||||
{!pluginMeta.userPlugin && (
|
||||
<div className="vc-settings-modal-links">
|
||||
<WebsiteButton
|
||||
text="View more info"
|
||||
href={`https://vencord.dev/plugins/${plugin.name}`}
|
||||
/>
|
||||
<GithubButton
|
||||
text="View source code"
|
||||
href={`https://github.com/${gitRemote}/tree/main/src/plugins/${pluginMeta.folderName}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Flex>
|
||||
<Forms.FormTitle tag="h3" style={{ marginTop: 8, marginBottom: 0 }}>Authors</Forms.FormTitle>
|
||||
<div style={{ width: "fit-content", marginBottom: 8 }}>
|
||||
<UserSummaryItem
|
||||
|
@ -263,3 +310,13 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
|||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
|
||||
export function openPluginModal(plugin: Plugin, onRestartNeeded?: (pluginName: string) => void) {
|
||||
openModal(modalProps => (
|
||||
<PluginModal
|
||||
{...modalProps}
|
||||
plugin={plugin}
|
||||
onRestartNeeded={() => onRestartNeeded?.(plugin.name)}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
|
|
@ -42,16 +42,6 @@
|
|||
|
||||
.vc-author-modal-links {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 0.2em;
|
||||
}
|
||||
|
||||
.vc-author-modal-links img {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border-radius: 50%;
|
||||
border: 4px solid var(--background-tertiary);
|
||||
box-sizing: border-box
|
||||
}
|
||||
|
||||
.vc-author-modal-plugins {
|
||||
|
|
|
@ -23,7 +23,7 @@ import { showNotice } from "@api/Notices";
|
|||
import { Settings, useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { CogWheel, InfoIcon } from "@components/Icons";
|
||||
import PluginModal from "@components/PluginSettings/PluginModal";
|
||||
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||
import { AddonCard } from "@components/VencordSettings/AddonCard";
|
||||
import { SettingsTab } from "@components/VencordSettings/shared";
|
||||
import { ChangeList } from "@utils/ChangeList";
|
||||
|
@ -31,13 +31,12 @@ import { proxyLazy } from "@utils/lazy";
|
|||
import { Logger } from "@utils/Logger";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { classes, isObjectEmpty } from "@utils/misc";
|
||||
import { openModalLazy } from "@utils/modal";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import { Plugin } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
|
||||
import { Alerts, Button, Card, Forms, lodash, Parser, React, Select, Text, TextInput, Toasts, Tooltip, useMemo } from "@webpack/common";
|
||||
|
||||
import Plugins from "~plugins";
|
||||
import Plugins, { ExcludedPlugins } from "~plugins";
|
||||
|
||||
// Avoid circular dependency
|
||||
const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() => require("../../plugins"));
|
||||
|
@ -45,7 +44,7 @@ const { startDependenciesRecursive, startPlugin, stopPlugin } = proxyLazy(() =>
|
|||
const cl = classNameFactory("vc-plugins-");
|
||||
const logger = new Logger("PluginSettings", "#a6d189");
|
||||
|
||||
const InputStyles = findByPropsLazy("inputDefault", "inputWrapper");
|
||||
const InputStyles = findByPropsLazy("inputWrapper", "inputDefault", "error");
|
||||
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
|
||||
|
||||
|
||||
|
@ -96,14 +95,6 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
|
|||
|
||||
const isEnabled = () => settings.enabled ?? false;
|
||||
|
||||
function openModal() {
|
||||
openModalLazy(async () => {
|
||||
return modalProps => {
|
||||
return <PluginModal {...modalProps} plugin={plugin} onRestartNeeded={() => onRestartNeeded(plugin.name)} />;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function toggleEnabled() {
|
||||
const wasEnabled = isEnabled();
|
||||
|
||||
|
@ -160,7 +151,11 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
|
|||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
infoButton={
|
||||
<button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}>
|
||||
<button
|
||||
role="switch"
|
||||
onClick={() => openPluginModal(plugin, onRestartNeeded)}
|
||||
className={classes(ButtonClasses.button, cl("info-button"))}
|
||||
>
|
||||
{plugin.options && !isObjectEmpty(plugin.options)
|
||||
? <CogWheel />
|
||||
: <InfoIcon />}
|
||||
|
@ -177,6 +172,37 @@ const enum SearchStatus {
|
|||
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() {
|
||||
const settings = useSettings();
|
||||
const changes = React.useMemo(() => new ChangeList<string>(), []);
|
||||
|
@ -215,26 +241,27 @@ export default function PluginSettings() {
|
|||
return o;
|
||||
}, []);
|
||||
|
||||
const sortedPlugins = React.useMemo(() => Object.values(Plugins)
|
||||
const sortedPlugins = useMemo(() => Object.values(Plugins)
|
||||
.sort((a, b) => a.name.localeCompare(b.name)), []);
|
||||
|
||||
const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL });
|
||||
|
||||
const search = searchValue.value.toLowerCase();
|
||||
const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query }));
|
||||
const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status }));
|
||||
|
||||
const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => {
|
||||
const enabled = settings.plugins[plugin.name]?.enabled;
|
||||
if (enabled && searchValue.status === SearchStatus.DISABLED) return false;
|
||||
if (!enabled && searchValue.status === SearchStatus.ENABLED) return false;
|
||||
if (searchValue.status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false;
|
||||
if (!searchValue.value.length) return true;
|
||||
const { status } = searchValue;
|
||||
const enabled = Vencord.Plugins.isPluginEnabled(plugin.name);
|
||||
if (enabled && status === SearchStatus.DISABLED) return false;
|
||||
if (!enabled && status === SearchStatus.ENABLED) return false;
|
||||
if (status === SearchStatus.NEW && !newPlugins?.includes(plugin.name)) return false;
|
||||
if (!search.length) return true;
|
||||
|
||||
const v = searchValue.value.toLowerCase();
|
||||
return (
|
||||
plugin.name.toLowerCase().includes(v) ||
|
||||
plugin.description.toLowerCase().includes(v) ||
|
||||
plugin.tags?.some(t => t.toLowerCase().includes(v))
|
||||
plugin.name.toLowerCase().includes(search) ||
|
||||
plugin.description.toLowerCase().includes(search) ||
|
||||
plugin.tags?.some(t => t.toLowerCase().includes(search))
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -255,54 +282,48 @@ export default function PluginSettings() {
|
|||
return lodash.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins;
|
||||
}));
|
||||
|
||||
type P = JSX.Element | JSX.Element[];
|
||||
let plugins: P, requiredPlugins: P;
|
||||
if (sortedPlugins?.length) {
|
||||
plugins = [];
|
||||
requiredPlugins = [];
|
||||
const plugins = [] as JSX.Element[];
|
||||
const requiredPlugins = [] as JSX.Element[];
|
||||
|
||||
const showApi = searchValue.value === "API";
|
||||
for (const p of sortedPlugins) {
|
||||
if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi))
|
||||
continue;
|
||||
const showApi = searchValue.value.includes("API");
|
||||
for (const p of sortedPlugins) {
|
||||
if (p.hidden || (!p.options && p.name.endsWith("API") && !showApi))
|
||||
continue;
|
||||
|
||||
if (!pluginFilter(p)) continue;
|
||||
if (!pluginFilter(p)) continue;
|
||||
|
||||
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
|
||||
const isRequired = p.required || depMap[p.name]?.some(d => settings.plugins[d].enabled);
|
||||
|
||||
if (isRequired) {
|
||||
const tooltipText = p.required
|
||||
? "This plugin is required for Vencord to function."
|
||||
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
|
||||
|
||||
requiredPlugins.push(
|
||||
<Tooltip text={tooltipText} key={p.name}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<PluginCard
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onRestartNeeded={name => changes.handleChange(name)}
|
||||
disabled={true}
|
||||
plugin={p}
|
||||
/>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
plugins.push(
|
||||
<PluginCard
|
||||
onRestartNeeded={name => changes.handleChange(name)}
|
||||
disabled={false}
|
||||
plugin={p}
|
||||
isNew={newPlugins?.includes(p.name)}
|
||||
key={p.name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (isRequired) {
|
||||
const tooltipText = p.required
|
||||
? "This plugin is required for Vencord to function."
|
||||
: makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled));
|
||||
|
||||
requiredPlugins.push(
|
||||
<Tooltip text={tooltipText} key={p.name}>
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<PluginCard
|
||||
onMouseLeave={onMouseLeave}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onRestartNeeded={name => changes.handleChange(name)}
|
||||
disabled={true}
|
||||
plugin={p}
|
||||
key={p.name}
|
||||
/>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
plugins.push(
|
||||
<PluginCard
|
||||
onRestartNeeded={name => changes.handleChange(name)}
|
||||
disabled={false}
|
||||
plugin={p}
|
||||
isNew={newPlugins?.includes(p.name)}
|
||||
key={p.name}
|
||||
/>
|
||||
);
|
||||
}
|
||||
} else {
|
||||
plugins = requiredPlugins = <Text variant="text-md/normal">No plugins meet search criteria.</Text>;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -313,8 +334,8 @@ export default function PluginSettings() {
|
|||
Filters
|
||||
</Forms.FormTitle>
|
||||
|
||||
<div className={cl("filter-controls")}>
|
||||
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} className={Margins.bottom20} />
|
||||
<div className={classes(Margins.bottom20, cl("filter-controls"))}>
|
||||
<TextInput autoFocus value={searchValue.value} placeholder="Search for a plugin..." onChange={onSearch} />
|
||||
<div className={InputStyles.inputWrapper}>
|
||||
<Select
|
||||
options={[
|
||||
|
@ -327,15 +348,25 @@ export default function PluginSettings() {
|
|||
select={onStatusChange}
|
||||
isSelected={v => v === searchValue.status}
|
||||
closeOnSelect={true}
|
||||
className={InputStyles.inputDefault}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Forms.FormTitle className={Margins.top20}>Plugins</Forms.FormTitle>
|
||||
|
||||
<div className={cl("grid")}>
|
||||
{plugins}
|
||||
</div>
|
||||
{plugins.length || requiredPlugins.length
|
||||
? (
|
||||
<div className={cl("grid")}>
|
||||
{plugins.length
|
||||
? plugins
|
||||
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
: <ExcludedPluginsList search={search} />
|
||||
}
|
||||
|
||||
|
||||
<Forms.FormDivider className={Margins.top20} />
|
||||
|
||||
|
@ -343,7 +374,10 @@ export default function PluginSettings() {
|
|||
Required Plugins
|
||||
</Forms.FormTitle>
|
||||
<div className={cl("grid")}>
|
||||
{requiredPlugins}
|
||||
{requiredPlugins.length
|
||||
? requiredPlugins
|
||||
: <Text variant="text-md/normal">No plugins meet the search criteria.</Text>
|
||||
}
|
||||
</div>
|
||||
</SettingsTab >
|
||||
);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
import { showNotification } from "@api/Notifications";
|
||||
import { Settings, useSettings } from "@api/Settings";
|
||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
||||
import { Grid } from "@components/Grid";
|
||||
import { Link } from "@components/Link";
|
||||
import { authorizeCloud, cloudLogger, deauthorizeCloud, getCloudAuth, getCloudUrl } from "@utils/cloud";
|
||||
import { Margins } from "@utils/margins";
|
||||
|
@ -85,7 +86,9 @@ function SettingsSyncSection() {
|
|||
size={Button.Sizes.SMALL}
|
||||
disabled={!sectionEnabled}
|
||||
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!">
|
||||
{({ onMouseLeave, onMouseEnter }) => (
|
||||
<Button
|
||||
|
@ -95,7 +98,9 @@ function SettingsSyncSection() {
|
|||
color={Button.Colors.RED}
|
||||
disabled={!sectionEnabled}
|
||||
onClick={() => getCloudSettings(true, true)}
|
||||
>Sync from Cloud</Button>
|
||||
>
|
||||
Sync from Cloud
|
||||
</Button>
|
||||
)}
|
||||
</Tooltip>
|
||||
<Button
|
||||
|
@ -103,7 +108,9 @@ function SettingsSyncSection() {
|
|||
color={Button.Colors.RED}
|
||||
disabled={!sectionEnabled}
|
||||
onClick={() => deleteCloudSettings()}
|
||||
>Delete Cloud Settings</Button>
|
||||
>
|
||||
Delete Cloud Settings
|
||||
</Button>
|
||||
</div>
|
||||
</Forms.FormSection>
|
||||
);
|
||||
|
@ -124,7 +131,12 @@ function CloudTab() {
|
|||
<Switch
|
||||
key="backend"
|
||||
value={settings.cloud.authenticated}
|
||||
onChange={v => { v && authorizeCloud(); if (!v) settings.cloud.authenticated = v; }}
|
||||
onChange={v => {
|
||||
if (v)
|
||||
authorizeCloud();
|
||||
else
|
||||
settings.cloud.authenticated = v;
|
||||
}}
|
||||
note="This will request authorization if you have not yet set up cloud integrations."
|
||||
>
|
||||
Enable Cloud Integrations
|
||||
|
@ -136,23 +148,43 @@ function CloudTab() {
|
|||
<CheckedTextInput
|
||||
key="backendUrl"
|
||||
value={settings.cloud.url}
|
||||
onChange={v => { settings.cloud.url = v; settings.cloud.authenticated = false; deauthorizeCloud(); }}
|
||||
onChange={async v => {
|
||||
settings.cloud.url = v;
|
||||
settings.cloud.authenticated = false;
|
||||
deauthorizeCloud();
|
||||
}}
|
||||
validate={validateUrl}
|
||||
/>
|
||||
<Button
|
||||
className={Margins.top8}
|
||||
size={Button.Sizes.MEDIUM}
|
||||
color={Button.Colors.RED}
|
||||
disabled={!settings.cloud.authenticated}
|
||||
onClick={() => Alerts.show({
|
||||
title: "Are you sure?",
|
||||
body: "Once your data is erased, we cannot recover it. There's no going back!",
|
||||
onConfirm: eraseAllData,
|
||||
confirmText: "Erase it!",
|
||||
confirmColor: "vc-cloud-erase-data-danger-btn",
|
||||
cancelText: "Nevermind"
|
||||
})}
|
||||
>Erase All Data</Button>
|
||||
|
||||
<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
|
||||
size={Button.Sizes.MEDIUM}
|
||||
color={Button.Colors.RED}
|
||||
disabled={!settings.cloud.authenticated}
|
||||
onClick={() => Alerts.show({
|
||||
title: "Are you sure?",
|
||||
body: "Once your data is erased, we cannot recover it. There's no going back!",
|
||||
onConfirm: eraseAllData,
|
||||
confirmText: "Erase it!",
|
||||
confirmColor: "vc-cloud-erase-data-danger-btn",
|
||||
cancelText: "Nevermind"
|
||||
})}
|
||||
>
|
||||
Erase All Data
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Forms.FormDivider className={Margins.top16} />
|
||||
</Forms.FormSection >
|
||||
<SettingsSyncSection />
|
||||
|
|
106
src/components/VencordSettings/NotificationSettings.tsx
Normal file
106
src/components/VencordSettings/NotificationSettings.tsx
Normal file
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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>
|
||||
));
|
||||
}
|
|
@ -17,16 +17,19 @@
|
|||
*/
|
||||
|
||||
import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
||||
import { Settings, useSettings } from "@api/Settings";
|
||||
import { useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import DonateButton from "@components/DonateButton";
|
||||
import { ErrorCard } from "@components/ErrorCard";
|
||||
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { identity } from "@utils/misc";
|
||||
import { relaunch, showItemInFolder } from "@utils/native";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import { Button, Card, Forms, React, Select, Slider, Switch } from "@webpack/common";
|
||||
import { Button, Card, Forms, React, Select, Switch, TooltipContainer } from "@webpack/common";
|
||||
import { ComponentType } from "react";
|
||||
|
||||
import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "..";
|
||||
import { openNotificationSettingsModal } from "./NotificationSettings";
|
||||
import { SettingsTab, wrapTab } from "./shared";
|
||||
|
||||
const cl = classNameFactory("vc-settings-");
|
||||
|
@ -38,6 +41,18 @@ type KeysOfType<Object, Type> = {
|
|||
[K in keyof Object]: Object[K] extends Type ? K : never;
|
||||
}[keyof Object];
|
||||
|
||||
const iconWithTooltip = (Icon: ComponentType<{ className?: string; }>, tooltip: string) => () => (
|
||||
<TooltipContainer text={tooltip}>
|
||||
<Icon className={cl("quick-actions-img")} />
|
||||
</TooltipContainer>
|
||||
);
|
||||
|
||||
const NotificationLogIcon = iconWithTooltip(LogIcon, "Open Notification Log");
|
||||
const QuickCssIcon = iconWithTooltip(PaintbrushIcon, "Edit QuickCSS");
|
||||
const RelaunchIcon = iconWithTooltip(RestartIcon, "Relaunch Discord");
|
||||
const OpenSettingsDirIcon = iconWithTooltip(FolderIcon, "Open Settings Directory");
|
||||
const OpenGithubIcon = iconWithTooltip(GithubIcon, "View Vencord's GitHub Repository");
|
||||
|
||||
function VencordSettings() {
|
||||
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
|
||||
fallbackValue: "Loading..."
|
||||
|
@ -78,7 +93,7 @@ function VencordSettings() {
|
|||
!IS_WEB && {
|
||||
key: "transparent",
|
||||
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 && {
|
||||
key: "winCtrlQ",
|
||||
|
@ -97,44 +112,59 @@ function VencordSettings() {
|
|||
<DonateCard image={donateImage} />
|
||||
<Forms.FormSection title="Quick Actions">
|
||||
<Card className={cl("quick-actions-card")}>
|
||||
<React.Fragment>
|
||||
{!IS_WEB && (
|
||||
<Button
|
||||
onClick={relaunch}
|
||||
size={Button.Sizes.SMALL}>
|
||||
Restart Client
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={openNotificationLogModal}
|
||||
look={Button.Looks.BLANK}
|
||||
>
|
||||
<NotificationLogIcon />
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => VencordNative.quickCss.openEditor()}
|
||||
look={Button.Looks.BLANK}
|
||||
>
|
||||
<QuickCssIcon />
|
||||
</Button>
|
||||
{!IS_WEB && (
|
||||
<Button
|
||||
onClick={() => VencordNative.quickCss.openEditor()}
|
||||
size={Button.Sizes.SMALL}
|
||||
disabled={settingsDir === "Loading..."}>
|
||||
Open QuickCSS File
|
||||
onClick={relaunch}
|
||||
look={Button.Looks.BLANK}
|
||||
>
|
||||
<RelaunchIcon />
|
||||
</Button>
|
||||
{!IS_WEB && (
|
||||
<Button
|
||||
onClick={() => showItemInFolder(settingsDir)}
|
||||
size={Button.Sizes.SMALL}
|
||||
disabled={settingsDirPending}>
|
||||
Open Settings Folder
|
||||
</Button>
|
||||
)}
|
||||
)}
|
||||
{!IS_WEB && (
|
||||
<Button
|
||||
onClick={() => VencordNative.native.openExternal("https://github.com/Vendicated/Vencord")}
|
||||
size={Button.Sizes.SMALL}
|
||||
disabled={settingsDirPending}>
|
||||
Open in GitHub
|
||||
onClick={() => showItemInFolder(settingsDir)}
|
||||
look={Button.Looks.BLANK}
|
||||
disabled={settingsDirPending}
|
||||
>
|
||||
<OpenSettingsDirIcon />
|
||||
</Button>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Button
|
||||
onClick={() => VencordNative.native.openExternal("https://github.com/Vendicated/Vencord")}
|
||||
look={Button.Looks.BLANK}
|
||||
disabled={settingsDirPending}
|
||||
>
|
||||
<OpenGithubIcon />
|
||||
</Button>
|
||||
</Card>
|
||||
</Forms.FormSection>
|
||||
|
||||
<Forms.FormDivider />
|
||||
|
||||
<Forms.FormSection className={Margins.top16} title="Settings" tag="h5">
|
||||
<Forms.FormText className={Margins.bottom20}>
|
||||
Hint: You can change the position of this settings section in the settings of the "Settings" plugin!
|
||||
<Forms.FormText className={Margins.bottom20} style={{ color: "var(--text-muted)" }}>
|
||||
Hint: You can change the position of this settings section in the
|
||||
{" "}<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>
|
||||
|
||||
{Switches.map(s => s && (
|
||||
<Switch
|
||||
key={s.key}
|
||||
|
@ -212,94 +242,20 @@ function VencordSettings() {
|
|||
serialize={identity} />
|
||||
</>}
|
||||
|
||||
{typeof Notification !== "undefined" && <NotificationSection settings={settings.notifications} />}
|
||||
<Forms.FormSection className={Margins.top16} title="Vencord Notifications" tag="h5">
|
||||
<Flex>
|
||||
<Button onClick={openNotificationSettingsModal}>
|
||||
Notification Settings
|
||||
</Button>
|
||||
<Button onClick={openNotificationLogModal}>
|
||||
View Notification Log
|
||||
</Button>
|
||||
</Flex>
|
||||
</Forms.FormSection>
|
||||
</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 {
|
||||
image: string;
|
||||
}
|
||||
|
|
|
@ -11,16 +11,25 @@
|
|||
}
|
||||
|
||||
.vc-settings-quick-actions-card {
|
||||
color: var(--text-normal);
|
||||
padding: 1em;
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
flex-grow: 1;
|
||||
flex-flow: row wrap;
|
||||
gap: 1em;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.vc-settings-quick-actions-card button {
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.vc-settings-quick-actions-img {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
.vc-settings-donate {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
@ -39,9 +39,8 @@ async function runReporter() {
|
|||
}
|
||||
if (searchType === "waitForStore") method = "findStore";
|
||||
|
||||
let result: any;
|
||||
try {
|
||||
let result: any;
|
||||
|
||||
if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||
const [factory] = args;
|
||||
result = factory();
|
||||
|
@ -50,16 +49,26 @@ async function runReporter() {
|
|||
|
||||
result = await Webpack.extractAndLoadChunks(code, matcher);
|
||||
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 {
|
||||
// @ts-ignore
|
||||
result = Webpack[method](...args);
|
||||
}
|
||||
|
||||
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw "a rock at ben shapiro";
|
||||
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
||||
} catch (e) {
|
||||
let logMessage = searchType;
|
||||
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 === "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(", ")})`;
|
||||
|
||||
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
<title>Vencord QuickCSS Editor</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.40.0/min/vs/editor/editor.main.min.css"
|
||||
integrity="sha512-MOoQ02h80hklccfLrXFYkCzG+WVjORflOp9Zp8dltiaRP+35LYnO4LKOklR64oMGfGgJDLO8WJpkM1o5gZXYZQ=="
|
||||
href="https://cdn.jsdelivr.net/npm/monaco-editor@0.50.0/min/vs/editor/editor.main.css"
|
||||
integrity="sha256-tiJPQ2O04z/pZ/AwdyIghrOMzewf+PIvEl1YKbQvsZk="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
/>
|
||||
|
@ -29,8 +29,8 @@
|
|||
<body>
|
||||
<div id="container"></div>
|
||||
<script
|
||||
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.40.0/min/vs/loader.min.js"
|
||||
integrity="sha512-QzMpXeCPciAHP4wbYlV2PYgrQcaEkDQUjzkPU4xnjyVSD9T36/udamxtNBqb4qK4/bMQMPZ8ayrBe9hrGdBFjQ=="
|
||||
src="https://cdn.jsdelivr.net/npm/monaco-editor@0.50.0/min/vs/loader.js"
|
||||
integrity="sha256-KcU48TGr84r7unF7J5IgBo95aeVrEbrGe04S7TcFUjs="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer"
|
||||
></script>
|
||||
|
@ -38,7 +38,7 @@
|
|||
<script>
|
||||
require.config({
|
||||
paths: {
|
||||
vs: "https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.40.0/min/vs",
|
||||
vs: "https://cdn.jsdelivr.net/npm/monaco-editor@0.50.0/min/vs",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
5
src/modules.d.ts
vendored
5
src/modules.d.ts
vendored
|
@ -22,6 +22,11 @@
|
|||
declare module "~plugins" {
|
||||
const plugins: Record<string, import("./utils/types").Plugin>;
|
||||
export default plugins;
|
||||
export const PluginMeta: Record<string, {
|
||||
folderName: string;
|
||||
userPlugin: boolean;
|
||||
}>;
|
||||
export const ExcludedPlugins: Record<string, "web" | "discordDesktop" | "vencordDesktop" | "desktop" | "dev">;
|
||||
}
|
||||
|
||||
declare module "~pluginNatives" {
|
||||
|
|
|
@ -136,6 +136,8 @@ export default definePlugin({
|
|||
},
|
||||
|
||||
getBadges(props: { userId: string; user?: User; guildId: string; }) {
|
||||
if (!props) return [];
|
||||
|
||||
try {
|
||||
props.userId ??= props.user?.id!;
|
||||
|
||||
|
|
|
@ -20,23 +20,30 @@ import { Devs } from "@utils/constants";
|
|||
import definePlugin from "@utils/types";
|
||||
|
||||
export default definePlugin({
|
||||
name: "SettingsStoreAPI",
|
||||
description: "Patches Discord's SettingsStores to expose their group and name",
|
||||
name: "UserSettingsAPI",
|
||||
description: "Patches Discord's UserSettings to expose their group and name.",
|
||||
authors: [Devs.Nuckyz],
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: ",updateSetting:",
|
||||
replacement: [
|
||||
// Main setting definition
|
||||
{
|
||||
match: /(?<=INFREQUENT_USER_ACTION.{0,20}),useSetting:/,
|
||||
replace: ",settingsStoreApiGroup:arguments[0],settingsStoreApiName:arguments[1]$&"
|
||||
match: /(?<=INFREQUENT_USER_ACTION.{0,20},)useSetting:/,
|
||||
replace: "userSettingsAPIGroup:arguments[0],userSettingsAPIName:arguments[1],$&"
|
||||
},
|
||||
// some wrapper. just make it copy the group and name
|
||||
// Selective wrapper
|
||||
{
|
||||
match: /updateSetting:.{0,20}shouldSync/,
|
||||
replace: "settingsStoreApiGroup:arguments[0].settingsStoreApiGroup,settingsStoreApiName:arguments[0].settingsStoreApiName,$&"
|
||||
match: /updateSetting:.{0,100}SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE/,
|
||||
replace: "userSettingsAPIGroup:arguments[0].userSettingsAPIGroup,userSettingsAPIName:arguments[0].userSettingsAPIName,$&"
|
||||
},
|
||||
// Override wrapper
|
||||
{
|
||||
match: /updateSetting:.{0,60}USER_SETTINGS_OVERRIDE_CLEAR/,
|
||||
replace: "userSettingsAPIGroup:arguments[0].userSettingsAPIGroup,userSettingsAPIName:arguments[0].userSettingsAPIName,$&"
|
||||
}
|
||||
|
||||
]
|
||||
}
|
||||
]
|
|
@ -18,7 +18,8 @@
|
|||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
disableAnalytics: {
|
||||
|
@ -46,13 +47,6 @@ export default definePlugin({
|
|||
replace: "()=>{}",
|
||||
},
|
||||
},
|
||||
{
|
||||
find: "window.DiscordSentry=",
|
||||
replacement: {
|
||||
match: /^.+$/,
|
||||
replace: "()=>{}",
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".METRICS,",
|
||||
replacement: [
|
||||
|
@ -74,5 +68,66 @@ export default definePlugin({
|
|||
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");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -56,33 +56,18 @@ export default definePlugin({
|
|||
}
|
||||
]
|
||||
},
|
||||
// Discord Stable
|
||||
// FIXME: remove once change merged to stable
|
||||
{
|
||||
find: "Messages.ACTIVITY_SETTINGS",
|
||||
noWarn: true,
|
||||
replacement: {
|
||||
get match() {
|
||||
switch (Settings.plugins.Settings.settingsLocation) {
|
||||
case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS/;
|
||||
case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS/;
|
||||
case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS/;
|
||||
case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/;
|
||||
case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/;
|
||||
case "aboveActivity":
|
||||
default:
|
||||
return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS/;
|
||||
}
|
||||
replacement: [
|
||||
{
|
||||
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: "...$self.makeSettingsCategories($1),$&"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "Messages.ACTIVITY_SETTINGS",
|
||||
replacement: {
|
||||
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}`
|
||||
}
|
||||
{
|
||||
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})`
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: "Messages.USER_SETTINGS_ACTIONS_MENU_LABEL",
|
||||
|
|
|
@ -16,24 +16,34 @@
|
|||
* 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 { Flex } from "@components/Flex";
|
||||
import { Link } from "@components/Link";
|
||||
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
||||
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
||||
import { sendMessage } from "@utils/discord";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { isPluginDev } from "@utils/misc";
|
||||
import { isPluginDev, tryOrElse } from "@utils/misc";
|
||||
import { relaunch } from "@utils/native";
|
||||
import { onlyOnce } from "@utils/onlyOnce";
|
||||
import { makeCodeblock } from "@utils/text";
|
||||
import definePlugin from "@utils/types";
|
||||
import { isOutdated, update } from "@utils/updater";
|
||||
import { Alerts, Card, ChannelStore, Forms, GuildMemberStore, NavigationRouter, Parser, RelationshipStore, UserStore } from "@webpack/common";
|
||||
import { checkForUpdates, isOutdated, update } from "@utils/updater";
|
||||
import { Alerts, Button, Card, ChannelStore, Forms, GuildMemberStore, Parser, RelationshipStore, showToast, Text, Toasts, UserStore } from "@webpack/common";
|
||||
|
||||
import gitHash from "~git-hash";
|
||||
import plugins from "~plugins";
|
||||
import plugins, { PluginMeta } from "~plugins";
|
||||
|
||||
import settings from "./settings";
|
||||
import SettingsPlugin from "./settings";
|
||||
|
||||
const VENCORD_GUILD_ID = "1015060230222131221";
|
||||
const VENBOT_USER_ID = "1017176847865352332";
|
||||
const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920";
|
||||
const CodeBlockRe = /```js\n(.+?)```/s;
|
||||
|
||||
const AllowedChannelIds = [
|
||||
SUPPORT_CHANNEL_ID,
|
||||
|
@ -47,12 +57,94 @@ const TrustedRolesIds = [
|
|||
"1042507929485586532", // donor
|
||||
];
|
||||
|
||||
const AsyncFunction = async function () { }.constructor;
|
||||
|
||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||
|
||||
async function forceUpdate() {
|
||||
const outdated = await checkForUpdates();
|
||||
if (outdated) {
|
||||
await update();
|
||||
relaunch();
|
||||
}
|
||||
|
||||
return outdated;
|
||||
}
|
||||
|
||||
async function generateDebugInfoMessage() {
|
||||
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
|
||||
|
||||
const client = (() => {
|
||||
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
|
||||
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
|
||||
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
|
||||
|
||||
// @ts-expect-error
|
||||
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
|
||||
return `${name} (${navigator.userAgent})`;
|
||||
})();
|
||||
|
||||
const info = {
|
||||
Vencord:
|
||||
`v${VERSION} • [${gitHash}](<https://github.com/Vendicated/Vencord/commit/${gitHash}>)` +
|
||||
`${SettingsPlugin.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`,
|
||||
Client: `${RELEASE_CHANNEL} ~ ${client}`,
|
||||
Platform: window.navigator.platform
|
||||
};
|
||||
|
||||
if (IS_DISCORD_DESKTOP) {
|
||||
info["Last Crash Reason"] = (await tryOrElse(() => DiscordNative.processUtils.getLastCrash(), undefined))?.rendererCrashReason ?? "N/A";
|
||||
}
|
||||
|
||||
const commonIssues = {
|
||||
"NoRPC enabled": Vencord.Plugins.isPluginEnabled("NoRPC"),
|
||||
"Activity Sharing disabled": tryOrElse(() => !ShowCurrentGame.getSetting(), false),
|
||||
"Vencord DevBuild": !IS_STANDALONE,
|
||||
"Has UserPlugins": Object.values(PluginMeta).some(m => m.userPlugin),
|
||||
"More than two weeks out of date": BUILD_TIMESTAMP < Date.now() - 12096e5,
|
||||
};
|
||||
|
||||
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: ["CommandsAPI"],
|
||||
dependencies: ["CommandsAPI", "UserSettingsAPI", "MessageAccessoriesAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
patches: [{
|
||||
find: ".BEGINNING_DM.format",
|
||||
|
@ -62,51 +154,20 @@ export default definePlugin({
|
|||
}
|
||||
}],
|
||||
|
||||
commands: [{
|
||||
name: "vencord-debug",
|
||||
description: "Send Vencord Debug info",
|
||||
predicate: ctx => isPluginDev(UserStore.getCurrentUser()?.id) || AllowedChannelIds.includes(ctx.channel.id),
|
||||
async execute() {
|
||||
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
|
||||
|
||||
const client = (() => {
|
||||
if (IS_DISCORD_DESKTOP) return `Discord Desktop v${DiscordNative.app.getVersion()}`;
|
||||
if (IS_VESKTOP) return `Vesktop v${VesktopNative.app.getVersion()}`;
|
||||
if ("armcord" in window) return `ArmCord v${window.armcord.version}`;
|
||||
|
||||
// @ts-expect-error
|
||||
const name = typeof unsafeWindow !== "undefined" ? "UserScript" : "Web";
|
||||
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 = {
|
||||
Vencord:
|
||||
`v${VERSION} • [${gitHash}](<https://github.com/Vendicated/Vencord/commit/${gitHash}>)` +
|
||||
`${settings.additionalInfo} - ${Intl.DateTimeFormat("en-GB", { dateStyle: "medium" }).format(BUILD_TIMESTAMP)}`,
|
||||
Client: `${RELEASE_CHANNEL} ~ ${client}`,
|
||||
Platform: window.navigator.platform
|
||||
};
|
||||
|
||||
if (IS_DISCORD_DESKTOP) {
|
||||
info["Last Crash Reason"] = (await DiscordNative.processUtils.getLastCrash())?.rendererCrashReason ?? "N/A";
|
||||
}
|
||||
|
||||
const debugInfo = `
|
||||
>>> ${Object.entries(info).map(([k, v]) => `**${k}**: ${v}`).join("\n")}
|
||||
|
||||
Enabled Plugins (${enabledPlugins.length}):
|
||||
${makeCodeblock(enabledPlugins.join(", "))}
|
||||
`;
|
||||
|
||||
return {
|
||||
content: debugInfo.trim().replaceAll("```\n", "```")
|
||||
};
|
||||
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: {
|
||||
async CHANNEL_SELECT({ channelId }) {
|
||||
|
@ -115,24 +176,25 @@ ${makeCodeblock(enabledPlugins.join(", "))}
|
|||
const selfId = UserStore.getCurrentUser()?.id;
|
||||
if (!selfId || isPluginDev(selfId)) return;
|
||||
|
||||
if (isOutdated) {
|
||||
return Alerts.show({
|
||||
title: "Hold on!",
|
||||
body: <div>
|
||||
<Forms.FormText>You are using an outdated version of Vencord! Chances are, your issue is already fixed.</Forms.FormText>
|
||||
<Forms.FormText className={Margins.top8}>
|
||||
Please first update before asking for support!
|
||||
</Forms.FormText>
|
||||
</div>,
|
||||
onCancel: () => openUpdaterModal!(),
|
||||
cancelText: "View Updates",
|
||||
confirmText: "Update & Restart Now",
|
||||
async onConfirm() {
|
||||
await update();
|
||||
relaunch();
|
||||
},
|
||||
secondaryConfirmText: "I know what I'm doing or I can't update"
|
||||
});
|
||||
if (!IS_UPDATER_DISABLED) {
|
||||
await checkForUpdatesOnce().catch(() => { });
|
||||
|
||||
if (isOutdated) {
|
||||
return Alerts.show({
|
||||
title: "Hold on!",
|
||||
body: <div>
|
||||
<Forms.FormText>You are using an outdated version of Vencord! Chances are, your issue is already fixed.</Forms.FormText>
|
||||
<Forms.FormText className={Margins.top8}>
|
||||
Please first update before asking for support!
|
||||
</Forms.FormText>
|
||||
</div>,
|
||||
onCancel: () => openUpdaterModal!(),
|
||||
cancelText: "View Updates",
|
||||
confirmText: "Update & Restart Now",
|
||||
onConfirm: forceUpdate,
|
||||
secondaryConfirmText: "I know what I'm doing or I can't update"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-ignore outdated type
|
||||
|
@ -148,23 +210,26 @@ ${makeCodeblock(enabledPlugins.join(", "))}
|
|||
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.
|
||||
</Forms.FormText>
|
||||
</div>,
|
||||
onCloseCallback: () => setTimeout(() => NavigationRouter.back(), 50)
|
||||
</div>
|
||||
});
|
||||
}
|
||||
|
||||
const repo = await VencordNative.updater.getRepo();
|
||||
if (repo.ok && !repo.value.includes("Vendicated/Vencord")) {
|
||||
if (!IS_STANDALONE && !settings.store.dismissedDevBuildWarning) {
|
||||
return Alerts.show({
|
||||
title: "Hold on!",
|
||||
body: <div>
|
||||
<Forms.FormText>You are using a fork of Vencord, which we do not provide support for!</Forms.FormText>
|
||||
<Forms.FormText>You are using a custom build of Vencord, which we do not provide support for!</Forms.FormText>
|
||||
|
||||
<Forms.FormText className={Margins.top8}>
|
||||
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.
|
||||
We only provide support for <Link href="https://vencord.dev/download">official builds</Link>.
|
||||
Either <Link href="https://vencord.dev/download">switch to an official build</Link> or figure your issue out yourself.
|
||||
</Forms.FormText>
|
||||
|
||||
<Text variant="text-md/bold" className={Margins.top8}>You will be banned from receiving support if you ignore this rule.</Text>
|
||||
</div>,
|
||||
onCloseCallback: () => setTimeout(() => NavigationRouter.back(), 50)
|
||||
confirmText: "Understood",
|
||||
secondaryConfirmText: "Don't show again",
|
||||
onConfirmSecondary: () => settings.store.dismissedDevBuildWarning = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -172,7 +237,7 @@ ${makeCodeblock(enabledPlugins.join(", "))}
|
|||
|
||||
ContributorDmWarningCard: ErrorBoundary.wrap(({ userId }) => {
|
||||
if (!isPluginDev(userId)) return null;
|
||||
if (RelationshipStore.isFriend(userId)) return null;
|
||||
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
||||
|
||||
return (
|
||||
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
||||
|
@ -182,5 +247,86 @@ ${makeCodeblock(enabledPlugins.join(", "))}
|
|||
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
||||
</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;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import { Devs } from "@utils/constants";
|
|||
import definePlugin, { OptionType, PluginNative, ReporterTestable } from "@utils/types";
|
||||
import { ApplicationAssetUtils, FluxDispatcher, Forms } from "@webpack/common";
|
||||
|
||||
const Native = VencordNative.pluginHelpers.AppleMusic as PluginNative<typeof import("./native")>;
|
||||
const Native = VencordNative.pluginHelpers.AppleMusicRichPresence as PluginNative<typeof import("./native")>;
|
||||
|
||||
interface ActivityAssets {
|
||||
large_image?: string;
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { getSettingStoreLazy } from "@api/SettingsStores";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import { ImageIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
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 DeveloperMode = getSettingStoreLazy("appearance", "developerMode")!;
|
||||
const DeveloperMode = getUserSettingLazy("appearance", "developerMode")!;
|
||||
|
||||
function PencilIcon() {
|
||||
return (
|
||||
|
@ -65,7 +65,7 @@ export default definePlugin({
|
|||
name: "BetterRoleContext",
|
||||
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],
|
||||
dependencies: ["SettingsStoreAPI"],
|
||||
dependencies: ["UserSettingsAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { getSettingStoreLazy } from "@api/SettingsStores";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import { ErrorCard } from "@components/ErrorCard";
|
||||
import { Link } from "@components/Link";
|
||||
import { Devs } from "@utils/constants";
|
||||
|
@ -33,8 +33,7 @@ const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gra
|
|||
const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile");
|
||||
const ActivityClassName = findByPropsLazy("activity", "buttonColor");
|
||||
|
||||
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame")!;
|
||||
|
||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||
|
||||
async function getApplicationAsset(key: string): Promise<string> {
|
||||
if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, "");
|
||||
|
@ -394,7 +393,7 @@ export default definePlugin({
|
|||
name: "CustomRPC",
|
||||
description: "Allows you to set a custom rich presence.",
|
||||
authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev],
|
||||
dependencies: ["SettingsStoreAPI"],
|
||||
dependencies: ["UserSettingsAPI"],
|
||||
start: setRpc,
|
||||
stop: () => setRpc(true),
|
||||
settings,
|
||||
|
|
|
@ -33,16 +33,13 @@ export default definePlugin({
|
|||
authors: [Devs.newwares],
|
||||
settings,
|
||||
patches: [
|
||||
{
|
||||
find: "IDLE_DURATION:function(){return",
|
||||
replacement: {
|
||||
match: /(IDLE_DURATION:function\(\){return )\i/,
|
||||
replace: "$1$self.getIdleTimeout()"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: 'type:"IDLE",idle:',
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=Date\.now\(\)-\i>)\i\.\i/,
|
||||
replace: "$self.getIdleTimeout()"
|
||||
},
|
||||
{
|
||||
match: /Math\.min\((\i\.\i\.getSetting\(\)\*\i\.\i\.\i\.SECOND),\i\.\i\)/,
|
||||
replace: "$1" // Decouple idle from afk (phone notifications will remain at user setting or 10 min maximum)
|
||||
|
|
|
@ -23,7 +23,7 @@ import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
|
|||
import { getCurrentGuild } from "@utils/discord";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
|
||||
import { findByCodeLazy, findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
|
||||
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
|
||||
import type { Emoji } from "@webpack/types";
|
||||
import type { Message } from "discord-types/general";
|
||||
|
@ -52,6 +52,7 @@ const PreloadedUserSettingsActionCreators = proxyLazyWebpack(() => UserSettingsA
|
|||
const AppearanceSettingsActionCreators = proxyLazyWebpack(() => searchProtoClassField("appearance", PreloadedUserSettingsActionCreators.ProtoClass));
|
||||
const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoClassField("clientThemeSettings", AppearanceSettingsActionCreators));
|
||||
|
||||
const isUnusableRoleSubscriptionEmoji = findByCodeLazy(".getUserIsAdmin(");
|
||||
|
||||
const enum EmojiIntentions {
|
||||
REACTION,
|
||||
|
@ -234,16 +235,14 @@ export default definePlugin({
|
|||
}
|
||||
]
|
||||
},
|
||||
// FIXME
|
||||
// Allows the usage of subscription-locked emojis
|
||||
/* {
|
||||
{
|
||||
find: ".getUserIsAdmin(",
|
||||
replacement: {
|
||||
match: /(?=.+?\.getUserIsAdmin\((?<=function (\i)\(\i,\i\){.+?))(\i):function\(\){return \1}/,
|
||||
// Replace the original export with a func that always returns false and alias the original
|
||||
replace: "$2:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function(){return $1}"
|
||||
match: /(function \i\(\i,\i)\){(.{0,250}.getUserIsAdmin\(.+?return!1})/,
|
||||
replace: (_, rest1, rest2) => `${rest1},fakeNitroOriginal){if(!fakeNitroOriginal)return false;${rest2}`
|
||||
}
|
||||
}, */
|
||||
},
|
||||
// Allow stickers to be sent everywhere
|
||||
{
|
||||
find: "canUseCustomStickersEverywhere:function",
|
||||
|
@ -817,9 +816,7 @@ export default definePlugin({
|
|||
if (e.type === 0) return true;
|
||||
if (e.available === false) return false;
|
||||
|
||||
// FIXME
|
||||
/* const isUnusableRoleSubEmoji = isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji;
|
||||
if (isUnusableRoleSubEmoji(e, this.guildId)) return false; */
|
||||
if (isUnusableRoleSubscriptionEmoji(e, this.guildId, true)) return false;
|
||||
|
||||
if (this.canUseEmotes)
|
||||
return e.guildId === this.guildId || hasExternalEmojiPerms(channelId);
|
||||
|
|
|
@ -109,9 +109,9 @@ interface ProfileModalProps {
|
|||
}
|
||||
|
||||
const ColorPicker = findComponentByCodeLazy<ColorPickerProps>(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
|
||||
const ProfileModal = findComponentByCodeLazy<ProfileModalProps>('"ProfileCustomizationPreview"');
|
||||
const ProfileModal = findComponentByCodeLazy<ProfileModalProps>("isTryItOutFlow:", "pendingThemeColors:", "pendingAvatarDecoration:", "EDIT_PROFILE_BANNER");
|
||||
|
||||
const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i\("?(.+?)"?\).then\(\i\.bind\(\i,"?(.+?)"?\)\)/);
|
||||
const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i(\("?.+?"?\)).then\(\i\.bind\(\i,"?(.+?)"?\)\)/);
|
||||
|
||||
export default definePlugin({
|
||||
name: "FakeProfileThemes",
|
||||
|
|
|
@ -4,50 +4,64 @@
|
|||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getCurrentChannel } from "@utils/discord";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { classes } from "@utils/misc";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||
import { Heading, React, RelationshipStore, Text } from "@webpack/common";
|
||||
import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack";
|
||||
import { Heading, RelationshipStore, Text } from "@webpack/common";
|
||||
|
||||
const container = findByPropsLazy("memberSinceWrapper");
|
||||
const containerWrapper = findByPropsLazy("memberSinceWrapper");
|
||||
const container = findByPropsLazy("memberSince");
|
||||
const getCreatedAtDate = findByCodeLazy('month:"short",day:"numeric"');
|
||||
const locale = findByPropsLazy("getLocale");
|
||||
const lastSection = findByPropsLazy("lastSection");
|
||||
|
||||
const cl = classNameFactory("vc-friendssince-");
|
||||
const section = findLazy((m: any) => m.section !== void 0 && Object.values(m).length === 1);
|
||||
|
||||
export default definePlugin({
|
||||
name: "FriendsSince",
|
||||
description: "Shows when you became friends with someone in the user popout",
|
||||
authors: [Devs.Elvyra],
|
||||
authors: [Devs.Elvyra, Devs.Antti],
|
||||
patches: [
|
||||
// User popup
|
||||
// User popup - old layout
|
||||
{
|
||||
find: ".USER_PROFILE}};return",
|
||||
replacement: {
|
||||
match: /,{userId:(\i.id).{0,30}}\)/,
|
||||
replace: "$&,$self.friendsSince({ userId: $1 })"
|
||||
replace: "$&,$self.friendsSinceOld({ userId: $1 })"
|
||||
}
|
||||
},
|
||||
// User DMs "User Profile" popup in the right
|
||||
// DM User Sidebar - old layout
|
||||
{
|
||||
find: ".PROFILE_PANEL,",
|
||||
replacement: {
|
||||
match: /,{userId:([^,]+?)}\)/,
|
||||
replace: "$&,$self.friendsSince({ userId: $1 })"
|
||||
replace: "$&,$self.friendsSinceOld({ userId: $1 })"
|
||||
}
|
||||
},
|
||||
// User Profile Modal
|
||||
// User Profile Modal - old layout
|
||||
{
|
||||
find: ".userInfoSectionHeader,",
|
||||
replacement: {
|
||||
match: /(\.Messages\.USER_PROFILE_MEMBER_SINCE.+?userId:(.+?),textClassName:)(\i\.userInfoText)}\)/,
|
||||
replace: (_, rest, userId, textClassName) => `${rest}!$self.getFriendSince(${userId}) ? ${textClassName} : void 0 }), $self.friendsSince({ userId: ${userId}, textClassName: ${textClassName} })`
|
||||
replace: (_, rest, userId, textClassName) => `${rest}!$self.getFriendSince(${userId}) ? ${textClassName} : void 0 }), $self.friendsSinceOld({ userId: ${userId}, textClassName: ${textClassName} })`
|
||||
}
|
||||
},
|
||||
// DM User Sidebar - new layout
|
||||
{
|
||||
find: ".PANEL}),nicknameIcons",
|
||||
replacement: {
|
||||
match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id)}\)}\)/,
|
||||
replace: "$&,$self.friendsSinceNew({userId:$1,isSidebar:true})"
|
||||
}
|
||||
},
|
||||
// User Profile Modal - new layout
|
||||
{
|
||||
find: "action:\"PRESS_APP_CONNECTION\"",
|
||||
replacement: {
|
||||
match: /USER_PROFILE_MEMBER_SINCE,.{0,100}userId:(\i\.id),.{0,100}}\)}\),/,
|
||||
replace: "$&,$self.friendsSinceNew({userId:$1,isSidebar:false}),"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -63,7 +77,7 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
|
||||
friendsSince: ErrorBoundary.wrap(({ userId, textClassName }: { userId: string; textClassName?: string; }) => {
|
||||
friendsSinceOld: ErrorBoundary.wrap(({ userId, textClassName }: { userId: string; textClassName?: string; }) => {
|
||||
if (!RelationshipStore.isFriend(userId)) return null;
|
||||
|
||||
const friendsSince = RelationshipStore.getSince(userId);
|
||||
|
@ -71,11 +85,11 @@ export default definePlugin({
|
|||
|
||||
return (
|
||||
<div className={lastSection.section}>
|
||||
<Heading variant="eyebrow" className={cl("title")}>
|
||||
<Heading variant="eyebrow">
|
||||
Friends Since
|
||||
</Heading>
|
||||
|
||||
<div className={container.memberSinceWrapper}>
|
||||
<div className={containerWrapper.memberSinceWrapper}>
|
||||
{!!getCurrentChannel()?.guild_id && (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
|
@ -88,11 +102,55 @@ export default definePlugin({
|
|||
<path d="M3 5v-.75C3 3.56 3.56 3 4.25 3s1.24.56 1.33 1.25C6.12 8.65 9.46 12 13 12h1a8 8 0 0 1 8 8 2 2 0 0 1-2 2 .21.21 0 0 1-.2-.15 7.65 7.65 0 0 0-1.32-2.3c-.15-.2-.42-.06-.39.17l.25 2c.02.15-.1.28-.25.28H9a2 2 0 0 1-2-2v-2.22c0-1.57-.67-3.05-1.53-4.37A15.85 15.85 0 0 1 3 5Z" />
|
||||
</svg>
|
||||
)}
|
||||
<Text variant="text-sm/normal" className={classes(cl("body"), textClassName)}>
|
||||
<Text variant="text-sm/normal" className={textClassName}>
|
||||
{getCreatedAtDate(friendsSince, locale.getLocale())}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}, { noop: true })
|
||||
}, { noop: true }),
|
||||
|
||||
friendsSinceNew: ErrorBoundary.wrap(({ userId, isSidebar }: { userId: string; isSidebar: boolean; }) => {
|
||||
if (!RelationshipStore.isFriend(userId)) return null;
|
||||
|
||||
const friendsSince = RelationshipStore.getSince(userId);
|
||||
if (!friendsSince) return null;
|
||||
|
||||
return (
|
||||
<section className={section.section}>
|
||||
<Heading variant="text-xs/semibold" style={isSidebar ? {} : { color: "var(--header-secondary)" }}>
|
||||
Friends Since
|
||||
</Heading>
|
||||
|
||||
{
|
||||
isSidebar ? (
|
||||
<Text variant="text-sm/normal">
|
||||
{getCreatedAtDate(friendsSince, locale.getLocale())}
|
||||
</Text>
|
||||
) : (
|
||||
<div className={containerWrapper.memberSinceWrapper}>
|
||||
<div className={container.memberSince}>
|
||||
{!!getCurrentChannel()?.guild_id && (
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 24 24"
|
||||
fill="var(--interactive-normal)"
|
||||
>
|
||||
<path d="M13 10a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z" />
|
||||
<path d="M3 5v-.75C3 3.56 3.56 3 4.25 3s1.24.56 1.33 1.25C6.12 8.65 9.46 12 13 12h1a8 8 0 0 1 8 8 2 2 0 0 1-2 2 .21.21 0 0 1-.2-.15 7.65 7.65 0 0 0-1.32-2.3c-.15-.2-.42-.06-.39.17l.25 2c.02.15-.1.28-.25.28H9a2 2 0 0 1-2-2v-2.22c0-1.57-.67-3.05-1.53-4.37A15.85 15.85 0 0 1 3 5Z" />
|
||||
</svg>
|
||||
)}
|
||||
<Text variant="text-sm/normal">
|
||||
{getCreatedAtDate(friendsSince, locale.getLocale())}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
</section>
|
||||
);
|
||||
}, { noop: true }),
|
||||
});
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
/* copy pasted from discord */
|
||||
|
||||
.vc-friendssince-title {
|
||||
display: flex;
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px
|
||||
}
|
||||
|
||||
.vc-friendssince-body {
|
||||
font-size: 14px;
|
||||
line-height: 18px
|
||||
}
|
|
@ -17,8 +17,8 @@
|
|||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { getSettingStoreLazy } from "@api/SettingsStores";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
@ -28,7 +28,7 @@ import style from "./style.css?managed";
|
|||
|
||||
const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:");
|
||||
|
||||
const ShowCurrentGame = getSettingStoreLazy<boolean>("status", "showCurrentGame")!;
|
||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||
|
||||
function makeIcon(showCurrentGame?: boolean) {
|
||||
const { oldIcon } = settings.use(["oldIcon"]);
|
||||
|
@ -87,7 +87,7 @@ export default definePlugin({
|
|||
name: "GameActivityToggle",
|
||||
description: "Adds a button next to the mic and deafen button to toggle game activity.",
|
||||
authors: [Devs.Nuckyz, Devs.RuukuLada],
|
||||
dependencies: ["SettingsStoreAPI"],
|
||||
dependencies: ["UserSettingsAPI"],
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[class*="withTagAsButton"] {
|
||||
min-width: 88px !important;
|
||||
[class*="panels"] [class*="avatarWrapper"] {
|
||||
min-width: 88px;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import * as DataStore from "@api/DataStore";
|
||||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { getSettingStoreLazy } from "@api/SettingsStores";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Devs } from "@utils/constants";
|
||||
|
@ -28,7 +28,7 @@ interface IgnoredActivity {
|
|||
|
||||
const RunningGameStore = findStoreLazy("RunningGameStore");
|
||||
|
||||
const ShowCurrentGame = getSettingStoreLazy("status", "showCurrentGame")!;
|
||||
const ShowCurrentGame = getUserSettingLazy("status", "showCurrentGame")!;
|
||||
|
||||
function ToggleIcon(activity: IgnoredActivity, tooltipText: string, path: string, fill: string) {
|
||||
return (
|
||||
|
@ -208,7 +208,7 @@ export default definePlugin({
|
|||
name: "IgnoreActivities",
|
||||
authors: [Devs.Nuckyz],
|
||||
description: "Ignore activities from showing up on your status ONLY. You can configure which ones are specifically ignored from the Registered Games and Activities tabs, or use the general settings below.",
|
||||
dependencies: ["SettingsStoreAPI"],
|
||||
dependencies: ["UserSettingsAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
|
@ -222,6 +222,13 @@ export default definePlugin({
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
find: '="ActivityTrackingStore",',
|
||||
replacement: {
|
||||
match: /getVisibleRunningGames\(\).+?;(?=for)(?<=(\i)=\i\.\i\.getVisibleRunningGames.+?)/,
|
||||
replace: (m, runningGames) => `${m}${runningGames}=${runningGames}.filter(({id,name})=>$self.isActivityNotIgnored({type:0,application_id:id,name}));`
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY",
|
||||
replacement: {
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
import { registerCommand, unregisterCommand } from "@api/Commands";
|
||||
import { addContextMenuPatch, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { Settings } from "@api/Settings";
|
||||
import { onceDefined } from "@shared/onceDefined";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import { canonicalizeFind } from "@utils/patches";
|
||||
import { Patch, Plugin, ReporterTestable, StartAt } from "@utils/types";
|
||||
|
@ -34,7 +33,7 @@ const logger = new Logger("PluginManager", "#a6d189");
|
|||
|
||||
export const PMLogger = logger;
|
||||
export const plugins = Plugins;
|
||||
export let patches = [] as Patch[];
|
||||
export const patches = [] as Patch[];
|
||||
|
||||
/** Whether we have subscribed to flux events of all the enabled plugins when FluxDispatcher was ready */
|
||||
let enabledPluginsSubscribedFlux = false;
|
||||
|
@ -43,9 +42,6 @@ const subscribedFluxEventsPlugins = new Set<string>();
|
|||
const pluginsValues = Object.values(Plugins);
|
||||
const settings = Settings.plugins;
|
||||
|
||||
const forceDisabled = new Set([
|
||||
"MoreUserTags"
|
||||
]);
|
||||
export function isPluginEnabled(p: string) {
|
||||
return (
|
||||
Plugins[p]?.required ||
|
||||
|
@ -126,17 +122,9 @@ for (const p of pluginsValues) {
|
|||
}
|
||||
}
|
||||
|
||||
onceDefined(window, "GLOBAL_ENV", v => {
|
||||
if (v.SENTRY_TAGS.buildId !== "366c746173a6ca0a801e9f4a4d7b6745e6de45d4") {
|
||||
patches = patches.filter(p => !forceDisabled.has(p.plugin));
|
||||
}
|
||||
});
|
||||
|
||||
export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins(target: StartAt) {
|
||||
logger.info(`Starting plugins (stage ${target})`);
|
||||
for (const name in Plugins) {
|
||||
if (window.GLOBAL_ENV?.SENTRY_TAGS.buildId !== "366c746173a6ca0a801e9f4a4d7b6745e6de45d4" && forceDisabled.has(name)) continue;
|
||||
|
||||
if (isPluginEnabled(name) && (!IS_REPORTER || isReporterTestable(Plugins[name], ReporterTestable.Start))) {
|
||||
const p = Plugins[name];
|
||||
|
||||
|
@ -181,7 +169,18 @@ export function subscribePluginFluxEvents(p: Plugin, fluxDispatcher: typeof Flux
|
|||
|
||||
logger.debug("Subscribing to flux events of plugin", p.name);
|
||||
for (const [event, handler] of Object.entries(p.flux)) {
|
||||
fluxDispatcher.subscribe(event as FluxEvents, handler);
|
||||
const wrappedHandler = p.flux[event] = function () {
|
||||
try {
|
||||
const res = handler.apply(p, arguments as any);
|
||||
return res instanceof Promise
|
||||
? res.catch(e => logger.error(`${p.name}: Error while handling ${event}\n`, e))
|
||||
: res;
|
||||
} catch (e) {
|
||||
logger.error(`${p.name}: Error while handling ${event}\n`, e);
|
||||
}
|
||||
};
|
||||
|
||||
fluxDispatcher.subscribe(event as FluxEvents, wrappedHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +206,7 @@ export function subscribeAllPluginsFluxEvents(fluxDispatcher: typeof FluxDispatc
|
|||
}
|
||||
|
||||
export const startPlugin = traceFunction("startPlugin", function startPlugin(p: Plugin) {
|
||||
const { name, commands, flux, contextMenus } = p;
|
||||
const { name, commands, contextMenus } = p;
|
||||
|
||||
if (p.start) {
|
||||
logger.info("Starting plugin", name);
|
||||
|
@ -253,7 +252,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p:
|
|||
}, p => `startPlugin ${p.name}`);
|
||||
|
||||
export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) {
|
||||
const { name, commands, flux, contextMenus } = p;
|
||||
const { name, commands, contextMenus } = p;
|
||||
|
||||
if (p.stop) {
|
||||
logger.info("Stopping plugin", name);
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||
import { updateMessage } from "@api/MessageUpdater";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { getSettingStoreLazy } from "@api/SettingsStores";
|
||||
import { getUserSettingLazy } from "@api/UserSettings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants.js";
|
||||
import { classes } from "@utils/misc";
|
||||
|
@ -54,7 +54,7 @@ const ChannelMessage = findComponentByCodeLazy("childrenExecutedCommand:", ".hid
|
|||
const SearchResultClasses = findByPropsLazy("message", "searchResult");
|
||||
const EmbedClasses = findByPropsLazy("embedAuthorIcon", "embedAuthor", "embedAuthor");
|
||||
|
||||
const MessageDisplayCompact = getSettingStoreLazy("textAndImages", "messageDisplayCompact")!;
|
||||
const MessageDisplayCompact = getUserSettingLazy("textAndImages", "messageDisplayCompact")!;
|
||||
|
||||
const messageLinkRegex = /(?<!<)https?:\/\/(?:\w+\.)?discord(?:app)?\.com\/channels\/(?:\d{17,20}|@me)\/(\d{17,20})\/(\d{17,20})/g;
|
||||
const tenorRegex = /^https:\/\/(?:www\.)?tenor\.com\//;
|
||||
|
@ -366,7 +366,7 @@ export default definePlugin({
|
|||
name: "MessageLinkEmbeds",
|
||||
description: "Adds a preview to messages that link another message",
|
||||
authors: [Devs.TheSun, Devs.Ven, Devs.RyanCaoDev],
|
||||
dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI", "SettingsStoreAPI"],
|
||||
dependencies: ["MessageAccessoriesAPI", "MessageUpdaterAPI", "UserSettingsAPI"],
|
||||
|
||||
settings,
|
||||
|
||||
|
|
|
@ -27,12 +27,12 @@ export default definePlugin({
|
|||
dependencies: ["CommandsAPI"],
|
||||
commands: [
|
||||
{ name: "dissatisfaction", description: " >﹏<" },
|
||||
{ name: "smug", description: " ಠ_ಠ" },
|
||||
{ name: "happy", description: " ヽ(´▽`)/" },
|
||||
{ name: "crying", description: " ಥ_ಥ" },
|
||||
{ name: "angry", description: " ヽ(`Д´)ノ" },
|
||||
{ name: "anger", description: " ヽ(o`皿′o)ノ" },
|
||||
{ name: "joy", description: " <( ̄︶ ̄)>" },
|
||||
{ name: "smug", description: "ಠ_ಠ" },
|
||||
{ name: "happy", description: "ヽ(´▽`)/" },
|
||||
{ name: "crying", description: "ಥ_ಥ" },
|
||||
{ name: "angry", description: "ヽ(`Д´)ノ" },
|
||||
{ name: "anger", description: "ヽ(o`皿′o)ノ" },
|
||||
{ name: "joy", description: "<( ̄︶ ̄)>" },
|
||||
{ name: "blush", description: "૮ ˶ᵔ ᵕ ᵔ˶ ა" },
|
||||
{ name: "confused", description: "(•ิ_•ิ)?" },
|
||||
{ name: "sleeping", description: "(ᴗ_ᴗ)" },
|
||||
|
@ -42,7 +42,7 @@ export default definePlugin({
|
|||
...data,
|
||||
options: [OptionalMessageOption],
|
||||
execute: opts => ({
|
||||
content: findOption(opts, "message", "") + data.description
|
||||
content: findOption(opts, "message", "") + " " + data.description
|
||||
})
|
||||
}))
|
||||
});
|
||||
|
|
|
@ -21,12 +21,10 @@ import { Flex } from "@components/Flex";
|
|||
import { Devs } from "@utils/constants";
|
||||
import { Margins } from "@utils/margins";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findLazy } from "@webpack";
|
||||
import { findByCodeLazy, findLazy } from "@webpack";
|
||||
import { Card, ChannelStore, Forms, GuildStore, PermissionsBits, Switch, TextInput, Tooltip, useState } from "@webpack/common";
|
||||
import { RC } from "@webpack/types";
|
||||
import { Channel, Message, User } from "discord-types/general";
|
||||
|
||||
type PermissionName = "CREATE_INSTANT_INVITE" | "KICK_MEMBERS" | "BAN_MEMBERS" | "ADMINISTRATOR" | "MANAGE_CHANNELS" | "MANAGE_GUILD" | "CHANGE_NICKNAME" | "MANAGE_NICKNAMES" | "MANAGE_ROLES" | "MANAGE_WEBHOOKS" | "MANAGE_GUILD_EXPRESSIONS" | "CREATE_GUILD_EXPRESSIONS" | "VIEW_AUDIT_LOG" | "VIEW_CHANNEL" | "VIEW_GUILD_ANALYTICS" | "VIEW_CREATOR_MONETIZATION_ANALYTICS" | "MODERATE_MEMBERS" | "SEND_MESSAGES" | "SEND_TTS_MESSAGES" | "MANAGE_MESSAGES" | "EMBED_LINKS" | "ATTACH_FILES" | "READ_MESSAGE_HISTORY" | "MENTION_EVERYONE" | "USE_EXTERNAL_EMOJIS" | "ADD_REACTIONS" | "USE_APPLICATION_COMMANDS" | "MANAGE_THREADS" | "CREATE_PUBLIC_THREADS" | "CREATE_PRIVATE_THREADS" | "USE_EXTERNAL_STICKERS" | "SEND_MESSAGES_IN_THREADS" | "CONNECT" | "SPEAK" | "MUTE_MEMBERS" | "DEAFEN_MEMBERS" | "MOVE_MEMBERS" | "USE_VAD" | "PRIORITY_SPEAKER" | "STREAM" | "USE_EMBEDDED_ACTIVITIES" | "USE_SOUNDBOARD" | "USE_EXTERNAL_SOUNDS" | "REQUEST_TO_SPEAK" | "MANAGE_EVENTS" | "CREATE_EVENTS";
|
||||
import type { Permissions, RC } from "@webpack/types";
|
||||
import type { Channel, Guild, Message, User } from "discord-types/general";
|
||||
|
||||
interface Tag {
|
||||
// name used for identifying, must be alphanumeric + underscores
|
||||
|
@ -34,7 +32,7 @@ interface Tag {
|
|||
// name shown on the tag itself, can be anything probably; automatically uppercase'd
|
||||
displayName: string;
|
||||
description: string;
|
||||
permissions?: PermissionName[];
|
||||
permissions?: Permissions[];
|
||||
condition?(message: Message | null, user: User, channel: Channel): boolean;
|
||||
}
|
||||
|
||||
|
@ -54,10 +52,14 @@ interface TagSettings {
|
|||
[k: string]: TagSetting;
|
||||
}
|
||||
|
||||
// PermissionStore.computePermissions is not the same function and doesn't work here
|
||||
const PermissionUtil = findByPropsLazy("computePermissions", "canEveryoneRole") as {
|
||||
computePermissions({ ...args }): bigint;
|
||||
};
|
||||
// PermissionStore.computePermissions will not work here since it only gets permissions for the current user
|
||||
const computePermissions: (options: {
|
||||
user?: { id: string; } | string | null;
|
||||
context?: Guild | Channel | null;
|
||||
overwrites?: Channel["permissionOverwrites"] | null;
|
||||
checkElevated?: boolean /* = true */;
|
||||
excludeGuildPermissions?: boolean /* = false */;
|
||||
}) => bigint = findByCodeLazy(".getCurrentUser()", ".computeLurkerPermissionsAllowList()");
|
||||
|
||||
const Tag = findLazy(m => m.Types?.[0] === "BOT") as RC<{ type?: number, className?: string, useRemSizes?: boolean; }> & { Types: Record<string, number>; };
|
||||
|
||||
|
@ -193,7 +195,7 @@ export default definePlugin({
|
|||
patches: [
|
||||
// add tags to the tag list
|
||||
{
|
||||
find: "BotTagTypes:",
|
||||
find: ".ORIGINAL_POSTER=",
|
||||
replacement: {
|
||||
match: /\((\i)=\{\}\)\)\[(\i)\.BOT/,
|
||||
replace: "($1=$self.getTagTypes()))[$2.BOT"
|
||||
|
@ -222,7 +224,7 @@ export default definePlugin({
|
|||
},
|
||||
// in messages
|
||||
{
|
||||
find: "renderSystemTag:",
|
||||
find: ".Types.ORIGINAL_POSTER",
|
||||
replacement: {
|
||||
match: /;return\((\(null==\i\?void 0:\i\.isSystemDM\(\).+?.Types.ORIGINAL_POSTER\)),null==(\i)\)/,
|
||||
replace: ";$1;$2=$self.getTag({...arguments[0],origType:$2,location:'chat'});return $2 == null"
|
||||
|
@ -283,7 +285,7 @@ export default definePlugin({
|
|||
const guild = GuildStore.getGuild(channel?.guild_id);
|
||||
if (!guild) return [];
|
||||
|
||||
const permissions = PermissionUtil.computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
|
||||
const permissions = computePermissions({ user, context: guild, overwrites: channel.permissionOverwrites });
|
||||
return Object.entries(PermissionsBits)
|
||||
.map(([perm, permInt]) =>
|
||||
permissions & permInt ? perm : ""
|
||||
|
@ -330,7 +332,7 @@ export default definePlugin({
|
|||
}: {
|
||||
message?: Message,
|
||||
user: User & { isClyde(): boolean; },
|
||||
channel?: Channel & { isForumPost(): boolean; },
|
||||
channel?: Channel & { isForumPost(): boolean; isMediaPost(): boolean; },
|
||||
channelId?: string;
|
||||
origType?: number;
|
||||
location: "chat" | "not-chat";
|
||||
|
@ -367,7 +369,7 @@ export default definePlugin({
|
|||
tag.permissions?.some(perm => perms.includes(perm)) ||
|
||||
(tag.condition?.(message!, user, channel))
|
||||
) {
|
||||
if (channel.isForumPost() && channel.ownerId === user.id)
|
||||
if ((channel.isForumPost() || channel.isMediaPost()) && channel.ownerId === user.id)
|
||||
type = Tag.Types[`${tag.name}-OP`];
|
||||
else if (user.bot && !isWebhook(message!, user) && !settings.dontShowBotTag)
|
||||
type = Tag.Types[`${tag.name}-BOT`];
|
||||
|
|
|
@ -49,7 +49,7 @@ export default definePlugin({
|
|||
find: ".Messages.MUTUAL_GUILDS_WITH_END_COUNT", // Note: the module is lazy-loaded
|
||||
replacement: {
|
||||
match: /(?<=\.tabBarItem.{0,50}MUTUAL_GUILDS.+?}\),)(?=.+?(\(0,\i\.jsxs?\)\(.{0,100}id:))/,
|
||||
replace: '(arguments[0].user.bot||arguments[0].isCurrentUser)?null:$1"MUTUAL_GDMS",children:"Mutual Groups"}),'
|
||||
replace: '$self.isBotOrSelf(arguments[0].user)?null:$1"MUTUAL_GDMS",children:"Mutual Groups"}),'
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -58,9 +58,24 @@ export default definePlugin({
|
|||
match: /(?<={user:(\i),onClose:(\i)}\);)(?=case \i\.\i\.MUTUAL_FRIENDS)/,
|
||||
replace: "case \"MUTUAL_GDMS\":return $self.renderMutualGDMs({user: $1, onClose: $2});"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".MUTUAL_FRIENDS?(",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=onItemSelect:\i,children:)(\i)\.map/,
|
||||
replace: "[...$1, ...($self.isBotOrSelf(arguments[0].user) ? [] : [{section:'MUTUAL_GDMS',text:'Mutual Groups'}])].map"
|
||||
},
|
||||
{
|
||||
match: /\(0,\i\.jsx\)\(\i,\{items:\i,section:(\i)/,
|
||||
replace: "$1==='MUTUAL_GDMS'?$self.renderMutualGDMs(arguments[0]):$&"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
isBotOrSelf: (user: User) => user.bot || user.id === UserStore.getCurrentUser().id,
|
||||
|
||||
renderMutualGDMs: ErrorBoundary.wrap(({ user, onClose }: { user: User, onClose: () => void; }) => {
|
||||
const entries = ChannelStore.getSortedPrivateChannels().filter(c => c.isGroupDM() && c.recipients.includes(user.id)).map(c => (
|
||||
<Clickable
|
||||
|
|
|
@ -16,13 +16,25 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {
|
||||
findGroupChildrenByChildId,
|
||||
NavContextMenuPatchCallback
|
||||
} from "@api/ContextMenu";
|
||||
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
|
||||
import { CogWheel } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||
import { findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
|
||||
import { Menu } from "@webpack/common";
|
||||
import { Guild } from "discord-types/general";
|
||||
|
||||
const { updateGuildNotificationSettings } = findByPropsLazy("updateGuildNotificationSettings");
|
||||
const { toggleShowAllChannels } = findByPropsLazy("toggleShowAllChannels");
|
||||
const { toggleShowAllChannels } = mapMangledModuleLazy(".onboardExistingMember(", {
|
||||
toggleShowAllChannels: m => {
|
||||
const s = String(m);
|
||||
return s.length < 100 && !s.includes("onboardExistingMember") && !s.includes("getOptedInChannels");
|
||||
}
|
||||
});
|
||||
const isOptInEnabledForGuild = findByCodeLazy(".COMMUNITY)||", ".isOptInEnabled(");
|
||||
|
||||
const settings = definePluginSettings({
|
||||
|
@ -68,48 +80,68 @@ const settings = definePluginSettings({
|
|||
}
|
||||
});
|
||||
|
||||
const makeContextMenuPatch: (shouldAddIcon: boolean) => NavContextMenuPatchCallback = (shouldAddIcon: boolean) => (children, { guild }: { guild: Guild, onClose(): void; }) => {
|
||||
if (!guild) return;
|
||||
|
||||
const group = findGroupChildrenByChildId("privacy", children);
|
||||
group?.push(
|
||||
<Menu.MenuItem
|
||||
label="Apply NewGuildSettings"
|
||||
id="vc-newguildsettings-apply"
|
||||
icon={shouldAddIcon ? CogWheel : void 0}
|
||||
action={() => applyDefaultSettings(guild.id)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
function applyDefaultSettings(guildId: string | null) {
|
||||
if (guildId === "@me" || guildId === "null" || guildId == null) return;
|
||||
updateGuildNotificationSettings(guildId,
|
||||
{
|
||||
muted: settings.store.guild,
|
||||
suppress_everyone: settings.store.everyone,
|
||||
suppress_roles: settings.store.role,
|
||||
mute_scheduled_events: settings.store.events,
|
||||
notify_highlights: settings.store.highlights ? 1 : 0
|
||||
});
|
||||
if (settings.store.messages !== 3) {
|
||||
updateGuildNotificationSettings(guildId,
|
||||
{
|
||||
message_notifications: settings.store.messages,
|
||||
});
|
||||
}
|
||||
if (settings.store.showAllChannels && isOptInEnabledForGuild(guildId)) {
|
||||
toggleShowAllChannels(guildId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
migratePluginSettings("NewGuildSettings", "MuteNewGuild");
|
||||
export default definePlugin({
|
||||
name: "NewGuildSettings",
|
||||
description: "Automatically mute new servers and change various other settings upon joining",
|
||||
tags: ["MuteNewGuild", "mute", "server"],
|
||||
authors: [Devs.Glitch, Devs.Nuckyz, Devs.carince, Devs.Mopi, Devs.GabiRP],
|
||||
contextMenus: {
|
||||
"guild-context": makeContextMenuPatch(false),
|
||||
"guild-header-popout": makeContextMenuPatch(true)
|
||||
},
|
||||
patches: [
|
||||
{
|
||||
find: ",acceptInvite(",
|
||||
replacement: {
|
||||
match: /INVITE_ACCEPT_SUCCESS.+?,(\i)=null!==.+?;/,
|
||||
replace: (m, guildId) => `${m}$self.handleMute(${guildId});`
|
||||
replace: (m, guildId) => `${m}$self.applyDefaultSettings(${guildId});`
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "{joinGuild:",
|
||||
replacement: {
|
||||
match: /guildId:(\i),lurker:(\i).{0,20}}\)\);/,
|
||||
replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.handleMute(${guildId});`
|
||||
replace: (m, guildId, lurker) => `${m}if(!${lurker})$self.applyDefaultSettings(${guildId});`
|
||||
}
|
||||
}
|
||||
],
|
||||
settings,
|
||||
|
||||
handleMute(guildId: string | null) {
|
||||
if (guildId === "@me" || guildId === "null" || guildId == null) return;
|
||||
updateGuildNotificationSettings(guildId,
|
||||
{
|
||||
muted: settings.store.guild,
|
||||
suppress_everyone: settings.store.everyone,
|
||||
suppress_roles: settings.store.role,
|
||||
mute_scheduled_events: settings.store.events,
|
||||
notify_highlights: settings.store.highlights ? 1 : 0
|
||||
});
|
||||
if (settings.store.messages !== 3) {
|
||||
updateGuildNotificationSettings(guildId,
|
||||
{
|
||||
message_notifications: settings.store.messages,
|
||||
});
|
||||
}
|
||||
if (settings.store.showAllChannels && isOptInEnabledForGuild(guildId)) {
|
||||
toggleShowAllChannels(guildId);
|
||||
}
|
||||
}
|
||||
applyDefaultSettings
|
||||
});
|
||||
|
|
|
@ -43,7 +43,7 @@ const Classes = proxyLazyWebpack(() =>
|
|||
))
|
||||
) as Record<"roles" | "rolePill" | "rolePillBorder" | "desaturateUserColors" | "flex" | "alignCenter" | "justifyCenter" | "svg" | "background" | "dot" | "dotBorderColor" | "roleCircle" | "dotBorderBase" | "flex" | "alignCenter" | "justifyCenter" | "wrap" | "root" | "role" | "roleRemoveButton" | "roleDot" | "roleFlowerStar" | "roleRemoveIcon" | "roleRemoveIconFocused" | "roleVerifiedIcon" | "roleName" | "roleNameOverflow" | "actionButton" | "overflowButton" | "addButton" | "addButtonIcon" | "overflowRolesPopout" | "overflowRolesPopoutArrowWrapper" | "overflowRolesPopoutArrow" | "popoutBottom" | "popoutTop" | "overflowRolesPopoutHeader" | "overflowRolesPopoutHeaderIcon" | "overflowRolesPopoutHeaderText" | "roleIcon", string>;
|
||||
|
||||
function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: Guild; guildMember: GuildMember; showBorder: boolean; }) {
|
||||
function UserPermissionsComponent({ guild, guildMember, showBorder, forceOpen = false }: { guild: Guild; guildMember: GuildMember; showBorder: boolean; forceOpen?: boolean; }) {
|
||||
const stns = settings.use(["permissionsSortOrder"]);
|
||||
|
||||
const [rolePermissions, userPermissions] = useMemo(() => {
|
||||
|
@ -95,6 +95,7 @@ function UserPermissionsComponent({ guild, guildMember, showBorder }: { guild: G
|
|||
|
||||
return (
|
||||
<ExpandableHeader
|
||||
forceOpen={forceOpen}
|
||||
headerText="Permissions"
|
||||
moreTooltipText="Role Details"
|
||||
onMoreClick={() =>
|
||||
|
|
|
@ -20,15 +20,22 @@ import "./styles.css";
|
|||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { SafetyIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { classes } from "@utils/misc";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { ChannelStore, GuildMemberStore, GuildStore, Menu, PermissionsBits, UserStore } from "@webpack/common";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, ChannelStore, Dialog, GuildMemberStore, GuildStore, Menu, PermissionsBits, Popout, TooltipContainer, UserStore } from "@webpack/common";
|
||||
import type { Guild, GuildMember } from "discord-types/general";
|
||||
|
||||
import openRolesAndUsersPermissionsModal, { PermissionType, RoleOrUserPermission } from "./components/RolesAndUsersPermissions";
|
||||
import UserPermissions from "./components/UserPermissions";
|
||||
import { getSortedRoles, sortPermissionOverwrites } from "./utils";
|
||||
|
||||
const PopoutClasses = findByPropsLazy("container", "scroller", "list");
|
||||
const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "text");
|
||||
|
||||
export const enum PermissionsSortOrder {
|
||||
HighestRole,
|
||||
LowestRole
|
||||
|
@ -168,10 +175,45 @@ export default definePlugin({
|
|||
match: /showBorder:(.{0,60})}\),(?<=guild:(\i),guildMember:(\i),.+?)/,
|
||||
replace: (m, showBoder, guild, guildMember) => `${m}$self.UserPermissions(${guild},${guildMember},${showBoder}),`
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".VIEW_ALL_ROLES,",
|
||||
replacement: {
|
||||
match: /children:"\+"\.concat\(\i\.length-\i\.length\).{0,20}\}\),/,
|
||||
replace: "$&$self.ViewPermissionsButton(arguments[0]),"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBoder: boolean) => !!guildMember && <UserPermissions guild={guild} guildMember={guildMember} showBorder={showBoder} />,
|
||||
UserPermissions: (guild: Guild, guildMember: GuildMember | undefined, showBorder: boolean) =>
|
||||
!!guildMember && <UserPermissions guild={guild} guildMember={guildMember} showBorder={showBorder} />,
|
||||
|
||||
ViewPermissionsButton: ErrorBoundary.wrap(({ guild, guildMember }: { guild: Guild; guildMember: GuildMember; }) => (
|
||||
<Popout
|
||||
position="bottom"
|
||||
align="center"
|
||||
renderPopout={() => (
|
||||
<Dialog className={PopoutClasses.container} style={{ width: "500px" }}>
|
||||
<UserPermissions guild={guild} guildMember={guildMember} showBorder forceOpen />
|
||||
</Dialog>
|
||||
)}
|
||||
>
|
||||
{popoutProps => (
|
||||
<TooltipContainer text="View Permissions">
|
||||
<Button
|
||||
{...popoutProps}
|
||||
color={Button.Colors.CUSTOM}
|
||||
look={Button.Looks.FILLED}
|
||||
size={Button.Sizes.NONE}
|
||||
innerClassName={classes(RoleButtonClasses.buttonInner, RoleButtonClasses.icon)}
|
||||
className={classes(RoleButtonClasses.button, RoleButtonClasses.icon, "vc-permviewer-role-button")}
|
||||
>
|
||||
<SafetyIcon height="16" width="16" />
|
||||
</Button>
|
||||
</TooltipContainer>
|
||||
)}
|
||||
</Popout>
|
||||
), { noop: true }),
|
||||
|
||||
contextMenus: {
|
||||
"user-context": makeContextMenuPatch("roles", MenuItemParentType.User),
|
||||
|
|
|
@ -149,3 +149,21 @@
|
|||
.vc-permviewer-perms-perms-item .vc-info-icon:hover {
|
||||
color: var(--interactive-active);
|
||||
}
|
||||
|
||||
/* copy pasted from discord cause impossible to webpack find */
|
||||
.vc-permviewer-role-button {
|
||||
border-radius: var(--radius-xs);
|
||||
background: var(--bg-mod-faint);
|
||||
color: var(--interactive-normal);
|
||||
border: 1px solid var(--border-faint);
|
||||
/* stylelint-disable-next-line value-no-vendor-prefix */
|
||||
width: -moz-fit-content;
|
||||
width: fit-content;
|
||||
height: 24px;
|
||||
padding: 4px
|
||||
}
|
||||
|
||||
.custom-profile-theme .vc-permviewer-role-button {
|
||||
background: rgb(var(--bg-overlay-color)/var(--bg-overlay-opacity-6));
|
||||
border-color: var(--profile-body-border-color)
|
||||
}
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { addBadge, BadgePosition, ProfileBadge, removeBadge } from "@api/Badges";
|
||||
import "./style.css";
|
||||
|
||||
import { addBadge, BadgePosition, BadgeUserArgs, ProfileBadge, removeBadge } from "@api/Badges";
|
||||
import { addDecorator, removeDecorator } from "@api/MemberListDecorators";
|
||||
import { addDecoration, removeDecoration } from "@api/MessageDecorations";
|
||||
import { Settings } from "@api/Settings";
|
||||
|
@ -27,7 +29,20 @@ import { findByPropsLazy, findStoreLazy } from "@webpack";
|
|||
import { PresenceStore, Tooltip, UserStore } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
|
||||
const SessionsStore = findStoreLazy("SessionsStore");
|
||||
export interface Session {
|
||||
sessionId: string;
|
||||
status: string;
|
||||
active: boolean;
|
||||
clientInfo: {
|
||||
version: number;
|
||||
os: string;
|
||||
client: string;
|
||||
};
|
||||
}
|
||||
|
||||
const SessionsStore = findStoreLazy("SessionsStore") as {
|
||||
getSessions(): Record<string, Session>;
|
||||
};
|
||||
|
||||
function Icon(path: string, opts?: { viewBox?: string; width?: number; height?: number; }) {
|
||||
return ({ color, tooltip, small }: { color: string; tooltip: string; small: boolean; }) => (
|
||||
|
@ -67,15 +82,11 @@ const PlatformIcon = ({ platform, status, small }: { platform: Platform, status:
|
|||
return <Icon color={StatusUtils.useStatusFillColor(status)} tooltip={tooltip} small={small} />;
|
||||
};
|
||||
|
||||
const getStatus = (id: string): Record<Platform, string> => PresenceStore.getState()?.clientStatuses?.[id];
|
||||
|
||||
const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => {
|
||||
if (!user || user.bot) return null;
|
||||
|
||||
function ensureOwnStatus(user: User) {
|
||||
if (user.id === UserStore.getCurrentUser().id) {
|
||||
const sessions = SessionsStore.getSessions();
|
||||
if (typeof sessions !== "object") return null;
|
||||
const sortedSessions = Object.values(sessions).sort(({ status: a }: any, { status: b }: any) => {
|
||||
const sortedSessions = Object.values(sessions).sort(({ status: a }, { status: b }) => {
|
||||
if (a === b) return 0;
|
||||
if (a === "online") return 1;
|
||||
if (b === "online") return -1;
|
||||
|
@ -84,7 +95,7 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma
|
|||
return 0;
|
||||
});
|
||||
|
||||
const ownStatus = Object.values(sortedSessions).reduce((acc: any, curr: any) => {
|
||||
const ownStatus = Object.values(sortedSessions).reduce((acc, curr) => {
|
||||
if (curr.clientInfo.client !== "unknown")
|
||||
acc[curr.clientInfo.client] = curr.status;
|
||||
return acc;
|
||||
|
@ -93,6 +104,37 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma
|
|||
const { clientStatuses } = PresenceStore.getState();
|
||||
clientStatuses[UserStore.getCurrentUser().id] = ownStatus;
|
||||
}
|
||||
}
|
||||
|
||||
function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] {
|
||||
const user = UserStore.getUser(userId);
|
||||
|
||||
if (!user || user.bot) return [];
|
||||
|
||||
ensureOwnStatus(user);
|
||||
|
||||
const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record<Platform, string>;
|
||||
if (!status) return [];
|
||||
|
||||
return Object.entries(status).map(([platform, status]) => ({
|
||||
component: () => (
|
||||
<span className="vc-platform-indicator">
|
||||
<PlatformIcon
|
||||
key={platform}
|
||||
platform={platform as Platform}
|
||||
status={status}
|
||||
small={false}
|
||||
/>
|
||||
</span>
|
||||
),
|
||||
key: `vc-platform-indicator-${platform}`
|
||||
}));
|
||||
}
|
||||
|
||||
const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => {
|
||||
if (!user || user.bot) return null;
|
||||
|
||||
ensureOwnStatus(user);
|
||||
|
||||
const status = PresenceStore.getState()?.clientStatuses?.[user.id] as Record<Platform, string>;
|
||||
if (!status) return null;
|
||||
|
@ -112,17 +154,10 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma
|
|||
<span
|
||||
className="vc-platform-indicator"
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
marginLeft: wantMargin ? 4 : 0,
|
||||
verticalAlign: "top",
|
||||
position: "relative",
|
||||
top: wantTopMargin ? 2 : 0,
|
||||
padding: !wantMargin ? 1 : 0,
|
||||
gap: 2
|
||||
}}
|
||||
|
||||
>
|
||||
{icons}
|
||||
</span>
|
||||
|
@ -130,10 +165,8 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma
|
|||
};
|
||||
|
||||
const badge: ProfileBadge = {
|
||||
component: p => <PlatformIndicator {...p} user={UserStore.getUser(p.userId)} wantMargin={false} />,
|
||||
getBadges,
|
||||
position: BadgePosition.START,
|
||||
shouldShow: userInfo => !!Object.keys(getStatus(userInfo.userId) ?? {}).length,
|
||||
key: "indicator"
|
||||
};
|
||||
|
||||
const indicatorLocations = {
|
||||
|
|
7
src/plugins/platformIndicators/style.css
Normal file
7
src/plugins/platformIndicators/style.css
Normal file
|
@ -0,0 +1,7 @@
|
|||
.vc-platform-indicator {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
}
|
|
@ -13,13 +13,12 @@ import { Flex, Menu } from "@webpack/common";
|
|||
const DefaultEngines = {
|
||||
Google: "https://www.google.com/search?q=",
|
||||
DuckDuckGo: "https://duckduckgo.com/",
|
||||
Brave: "https://search.brave.com/search?q=",
|
||||
Bing: "https://www.bing.com/search?q=",
|
||||
Yahoo: "https://search.yahoo.com/search?p=",
|
||||
GitHub: "https://github.com/search?q=",
|
||||
Kagi: "https://kagi.com/search?q=",
|
||||
Yandex: "https://yandex.com/search/?text=",
|
||||
AOL: "https://search.aol.com/aol/search?q=",
|
||||
Baidu: "https://www.baidu.com/s?wd=",
|
||||
GitHub: "https://github.com/search?q=",
|
||||
Reddit: "https://www.reddit.com/search?q=",
|
||||
Wikipedia: "https://wikipedia.org/w/index.php?search=",
|
||||
} as const;
|
||||
|
||||
|
@ -55,7 +54,7 @@ function makeSearchItem(src: string) {
|
|||
key="search-text"
|
||||
id="vc-search-text"
|
||||
>
|
||||
{Object.keys(Engines).map((engine, i) => {
|
||||
{Object.keys(Engines).map(engine => {
|
||||
const key = "vc-search-content-" + engine;
|
||||
return (
|
||||
<Menu.MenuItem
|
||||
|
@ -70,7 +69,7 @@ function makeSearchItem(src: string) {
|
|||
aria-hidden="true"
|
||||
height={16}
|
||||
width={16}
|
||||
src={`https://www.google.com/s2/favicons?domain=${Engines[engine]}`}
|
||||
src={`https://www.google.com/s2/favicons?domain=${Engines[engine]}&sz=64`}
|
||||
/>
|
||||
{engine}
|
||||
</Flex>
|
||||
|
|
|
@ -108,7 +108,7 @@ export default definePlugin({
|
|||
|
||||
patches: [
|
||||
{
|
||||
find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL",
|
||||
find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL,shouldHideMediaOptions",
|
||||
replacement: {
|
||||
match: /favoriteableType:\i,(?<=(\i)\.getAttribute\("data-type"\).+?)/,
|
||||
replace: (m, target) => `${m}reverseImageSearchType:${target}.getAttribute("data-role"),`
|
||||
|
|
|
@ -27,7 +27,7 @@ import { cl } from "../utils";
|
|||
import ReviewComponent from "./ReviewComponent";
|
||||
import ReviewsView, { ReviewsInputComponent } from "./ReviewsView";
|
||||
|
||||
function Modal({ modalProps, discordId, name }: { modalProps: any; discordId: string; name: string; }) {
|
||||
function Modal({ modalProps, modalKey, discordId, name }: { modalProps: any; modalKey: string, discordId: string; name: string; }) {
|
||||
const [data, setData] = useState<Response>();
|
||||
const [signal, refetch] = useForceUpdater(true);
|
||||
const [page, setPage] = useState(1);
|
||||
|
@ -76,6 +76,7 @@ function Modal({ modalProps, discordId, name }: { modalProps: any; discordId: st
|
|||
discordId={discordId}
|
||||
name={name}
|
||||
refetch={refetch}
|
||||
modalKey={modalKey}
|
||||
/>
|
||||
|
||||
{!!reviewCount && (
|
||||
|
@ -95,11 +96,14 @@ function Modal({ modalProps, discordId, name }: { modalProps: any; discordId: st
|
|||
}
|
||||
|
||||
export function openReviewsModal(discordId: string, name: string) {
|
||||
const modalKey = "vc-rdb-modal-" + Date.now();
|
||||
|
||||
openModal(props => (
|
||||
<Modal
|
||||
modalKey={modalKey}
|
||||
modalProps={props}
|
||||
discordId={discordId}
|
||||
name={name}
|
||||
/>
|
||||
));
|
||||
), { modalKey });
|
||||
}
|
||||
|
|
|
@ -119,7 +119,9 @@ function ReviewList({ refetch, reviews, hideOwnReview, profileId }: { refetch():
|
|||
}
|
||||
|
||||
|
||||
export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: { discordId: string, name: string; isAuthor: boolean; refetch(): void; }) {
|
||||
export function ReviewsInputComponent(
|
||||
{ discordId, isAuthor, refetch, name, modalKey }: { discordId: string, name: string; isAuthor: boolean; refetch(): void; modalKey?: string; }
|
||||
) {
|
||||
const { token } = Auth;
|
||||
const editorRef = useRef<any>(null);
|
||||
const inputType = ChatInputTypes.FORM;
|
||||
|
@ -148,6 +150,7 @@ export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: {
|
|||
type={inputType}
|
||||
disableThemedBackground={true}
|
||||
setEditorRef={ref => editorRef.current = ref}
|
||||
parentModalKey={modalKey}
|
||||
textValue=""
|
||||
onSubmit={
|
||||
async res => {
|
||||
|
|
|
@ -21,10 +21,12 @@ import "./style.css";
|
|||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { ExpandableHeader } from "@components/ExpandableHeader";
|
||||
import { OpenExternalIcon } from "@components/Icons";
|
||||
import { NotesIcon, OpenExternalIcon } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { classes } from "@utils/misc";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Alerts, Menu, Parser, useState } from "@webpack/common";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Alerts, Button, Menu, Parser, TooltipContainer, useState } from "@webpack/common";
|
||||
import { Guild, User } from "discord-types/general";
|
||||
|
||||
import { Auth, initAuth, updateAuth } from "./auth";
|
||||
|
@ -35,6 +37,9 @@ import { getCurrentUserInfo, readNotification } from "./reviewDbApi";
|
|||
import { settings } from "./settings";
|
||||
import { showToast } from "./utils";
|
||||
|
||||
const PopoutClasses = findByPropsLazy("container", "scroller", "list");
|
||||
const RoleButtonClasses = findByPropsLazy("button", "buttonInner", "icon", "text");
|
||||
|
||||
const guildPopoutPatch: NavContextMenuPatchCallback = (children, { guild }: { guild: Guild, onClose(): void; }) => {
|
||||
if (!guild) return;
|
||||
children.push(
|
||||
|
@ -69,7 +74,8 @@ export default definePlugin({
|
|||
"guild-header-popout": guildPopoutPatch,
|
||||
"guild-context": guildPopoutPatch,
|
||||
"user-context": userContextPatch,
|
||||
"user-profile-actions": userContextPatch
|
||||
"user-profile-actions": userContextPatch,
|
||||
"user-profile-overflow-menu": userContextPatch
|
||||
},
|
||||
|
||||
patches: [
|
||||
|
@ -79,6 +85,13 @@ export default definePlugin({
|
|||
match: /user:(\i),setNote:\i,canDM.+?\}\)/,
|
||||
replace: "$&,$self.getReviewsComponent($1)"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".BITE_SIZE,user:",
|
||||
replacement: {
|
||||
match: /(?<=\.BITE_SIZE,children:\[)\(0,\i\.jsx\)\(\i\.\i,\{user:(\i),/,
|
||||
replace: "$self.BiteSizeReviewsButton({user:$1}),$&"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -159,5 +172,22 @@ export default definePlugin({
|
|||
/>
|
||||
</ExpandableHeader>
|
||||
);
|
||||
}, { message: "Failed to render Reviews" })
|
||||
}, { message: "Failed to render Reviews" }),
|
||||
|
||||
BiteSizeReviewsButton: ErrorBoundary.wrap(({ user }: { user: User; }) => {
|
||||
return (
|
||||
<TooltipContainer text="View Reviews">
|
||||
<Button
|
||||
onClick={() => openReviewsModal(user.id, user.username)}
|
||||
look={Button.Looks.FILLED}
|
||||
size={Button.Sizes.NONE}
|
||||
color={RoleButtonClasses.color}
|
||||
className={classes(RoleButtonClasses.button, RoleButtonClasses.banner)}
|
||||
innerClassName={classes(RoleButtonClasses.buttonInner, RoleButtonClasses.banner)}
|
||||
>
|
||||
<NotesIcon height={16} width={16} />
|
||||
</Button>
|
||||
</TooltipContainer>
|
||||
);
|
||||
}, { noop: true })
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
|||
import { ChannelStore, GuildStore } from "@webpack/common";
|
||||
|
||||
const SummaryStore = findByPropsLazy("allSummaries", "findSummary");
|
||||
const createSummaryFromServer = findByCodeLazy(".people)),startId:");
|
||||
const createSummaryFromServer = findByCodeLazy(".people)),startId:", ".type}");
|
||||
|
||||
const settings = definePluginSettings({
|
||||
summaryExpiryThresholdDays: {
|
||||
|
|
6
src/plugins/showAllRoles/README.md
Normal file
6
src/plugins/showAllRoles/README.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# ShowAllRoles
|
||||
|
||||
Display all roles on the new profiles instead of limiting them to the default two rows.
|
||||
|
||||
![image](https://github.com/Vendicated/Vencord/assets/71079641/3f021f03-c6f9-4fe5-83ac-a1891b5e4b37)
|
||||
|
23
src/plugins/showAllRoles/index.ts
Normal file
23
src/plugins/showAllRoles/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* 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: "ShowAllRoles",
|
||||
description: "Show all roles in new profiles.",
|
||||
authors: [Devs.Luna],
|
||||
patches: [
|
||||
{
|
||||
find: ".Messages.VIEW_ALL_ROLES",
|
||||
replacement: {
|
||||
match: /return null!=\i(?=\?\i\.slice)/,
|
||||
replace: "return false"
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
|
@ -211,7 +211,7 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
{
|
||||
find: ".BITE_SIZE,onOpenProfile",
|
||||
find: /\.BITE_SIZE,onOpenProfile:\i,usernameIcon:/,
|
||||
replacement: {
|
||||
match: /currentUser:\i,guild:\i,onOpenProfile:.+?}\)(?=])(?<=user:(\i),bio:null==(\i)\?.+?)/,
|
||||
replace: "$&,$self.profilePopoutComponent({ user: $1, displayProfile: $2, simplified: true })"
|
||||
|
|
|
@ -477,12 +477,17 @@ export default definePlugin({
|
|||
],
|
||||
|
||||
isHiddenChannel(channel: Channel & { channelId?: string; }, checkConnect = false) {
|
||||
if (!channel) return false;
|
||||
try {
|
||||
if (!channel) return false;
|
||||
|
||||
if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId);
|
||||
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false;
|
||||
if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId);
|
||||
if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false;
|
||||
|
||||
return !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || checkConnect && !PermissionStore.can(PermissionsBits.CONNECT, channel);
|
||||
return !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, channel) || checkConnect && !PermissionStore.can(PermissionsBits.CONNECT, channel);
|
||||
} catch (e) {
|
||||
console.error("[ViewHiddenChannels#isHiddenChannel]: ", e);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
resolveGuildChannels(channels: Record<string | number, Array<{ channel: Channel; comparator: number; }> | string | number>, shouldIncludeHidden: boolean) {
|
||||
|
|
|
@ -64,18 +64,18 @@ export default definePlugin({
|
|||
},
|
||||
},
|
||||
{
|
||||
find: "useShouldShowInvitesDisabledNotif:",
|
||||
find: "2022-07_invites_disabled",
|
||||
predicate: () => settings.store.showInvitesPaused,
|
||||
replacement: {
|
||||
match: /\i\.\i\.can\(\i\.Permissions.MANAGE_GUILD,\i\)/,
|
||||
match: /\i\.\i\.can\(\i\.\i.MANAGE_GUILD,\i\)/,
|
||||
replace: "true",
|
||||
},
|
||||
},
|
||||
{
|
||||
find: "canAccessGuildMemberModViewWithExperiment:",
|
||||
find: /context:\i,checkElevated:!1\}\),\i\.\i.{0,200}autoTrackExposure/,
|
||||
predicate: () => settings.store.showModView,
|
||||
replacement: {
|
||||
match: /return \i\.hasAny\(\i\.computePermissions\(\{user:\i,context:\i,checkElevated:!1\}\),\i\.MemberSafetyPagePermissions\)/,
|
||||
match: /return \i\.\i\(\i\.\i\(\{user:\i,context:\i,checkElevated:!1\}\),\i\.\i\)/,
|
||||
replace: "return true",
|
||||
}
|
||||
},
|
||||
|
@ -87,28 +87,40 @@ export default definePlugin({
|
|||
replace: "{}"
|
||||
}
|
||||
},
|
||||
// remove the 200 server minimum
|
||||
{
|
||||
find: "MINIMUM_MEMBER_COUNT:",
|
||||
find: '">200"',
|
||||
predicate: () => settings.store.disableDiscoveryFilters,
|
||||
replacement: {
|
||||
match: /MINIMUM_MEMBER_COUNT:function\(\)\{return \i}/,
|
||||
replace: "MINIMUM_MEMBER_COUNT:() => \">0\""
|
||||
match: '">200"',
|
||||
replace: '">0"'
|
||||
}
|
||||
},
|
||||
// empty word filter (why would anyone search "horny" in fucking server discovery... please... why are we patching this again??)
|
||||
{
|
||||
find: "DiscoveryBannedSearchWords.includes",
|
||||
find: '"horny","fart"',
|
||||
predicate: () => settings.store.disableDisallowedDiscoveryFilters,
|
||||
replacement: {
|
||||
match: /(?<=function\(\){)(?=.{0,130}DiscoveryBannedSearchWords\.includes)/,
|
||||
replace: "return false;"
|
||||
match: /=\["egirl",.+?\]/,
|
||||
replace: "=[]"
|
||||
}
|
||||
},
|
||||
// empty 2nd word filter
|
||||
{
|
||||
find: "Endpoints.GUILD_DISCOVERY_VALID_TERM",
|
||||
find: '"pepe","nude"',
|
||||
predicate: () => settings.store.disableDisallowedDiscoveryFilters,
|
||||
replacement: {
|
||||
match: /(?<=[?=])\["pepe",.+?\]/,
|
||||
replace: "[]",
|
||||
},
|
||||
},
|
||||
// patch request that queries if term is allowed
|
||||
{
|
||||
find: ".GUILD_DISCOVERY_VALID_TERM",
|
||||
predicate: () => settings.store.disableDisallowedDiscoveryFilters,
|
||||
all: true,
|
||||
replacement: {
|
||||
match: /\i\.HTTP\.get\(\{url:\i\.Endpoints\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0\}\);/g,
|
||||
match: /\i\.\i\.get\(\{url:\i\.\i\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0\}\);/g,
|
||||
replace: "Promise.resolve({ body: { valid: true } });"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,12 +66,16 @@ export default definePlugin({
|
|||
|
||||
const { nick } = author;
|
||||
const prefix = withMentionPrefix ? "@" : "";
|
||||
if (username === nick || isRepliedMessage && !settings.store.inReplies)
|
||||
|
||||
if (isRepliedMessage && !settings.store.inReplies || username === nick.toLowerCase())
|
||||
return <>{prefix}{nick}</>;
|
||||
|
||||
if (settings.store.mode === "user-nick")
|
||||
return <>{prefix}{username} <span className="vc-smyn-suffix">{nick}</span></>;
|
||||
|
||||
if (settings.store.mode === "nick-user")
|
||||
return <>{prefix}{nick} <span className="vc-smyn-suffix">{username}</span></>;
|
||||
|
||||
return <>{prefix}{username}</>;
|
||||
} catch {
|
||||
return <>{author?.nick}</>;
|
||||
|
|
|
@ -74,9 +74,9 @@ const SilentMessageToggle: ChatBarButton = ({ isMainChat }) => {
|
|||
viewBox="0 0 24 24"
|
||||
style={{ scale: "1.2" }}
|
||||
>
|
||||
<path fill="currentColor" mask="url(#_)" d="M18 10.7101C15.1085 9.84957 13 7.17102 13 4c0-.30736.0198-.6101.0582-.907C12.7147 3.03189 12.3611 3 12 3 8.686 3 6 5.686 6 9v5c0 1.657-1.344 3-3 3v1h18v-1c-1.656 0-3-1.343-3-3v-3.2899ZM8.55493 19c.693 1.19 1.96897 2 3.44497 2s2.752-.81 3.445-2H8.55493ZM18.2624 5.50209 21 2.5V1h-4.9651v1.49791h2.4411L16 5.61088V7h5V5.50209h-2.7376Z" />
|
||||
<path fill="currentColor" mask="url(#vc-silent-msg-mask)" d="M18 10.7101C15.1085 9.84957 13 7.17102 13 4c0-.30736.0198-.6101.0582-.907C12.7147 3.03189 12.3611 3 12 3 8.686 3 6 5.686 6 9v5c0 1.657-1.344 3-3 3v1h18v-1c-1.656 0-3-1.343-3-3v-3.2899ZM8.55493 19c.693 1.19 1.96897 2 3.44497 2s2.752-.81 3.445-2H8.55493ZM18.2624 5.50209 21 2.5V1h-4.9651v1.49791h2.4411L16 5.61088V7h5V5.50209h-2.7376Z" />
|
||||
{!enabled && <>
|
||||
<mask id="_">
|
||||
<mask id="vc-silent-msg-mask">
|
||||
<path fill="#fff" d="M0 0h24v24H0Z" />
|
||||
<path stroke="#000" stroke-width="5.99068" d="M0 24 24 0" />
|
||||
</mask>
|
||||
|
|
|
@ -48,10 +48,10 @@ export default definePlugin({
|
|||
},
|
||||
patches: [
|
||||
{
|
||||
find: "showTaglessAccountPanel:",
|
||||
find: '"AccountConnected"',
|
||||
replacement: {
|
||||
// react.jsx)(AccountPanel, { ..., showTaglessAccountPanel: blah })
|
||||
match: /(?<=\i\.jsxs?\)\()(\i),{(?=[^}]*?showTaglessAccountPanel:)/,
|
||||
match: /(?<=\i\.jsxs?\)\()(\i),{(?=[^}]*?userTag:\i,hidePrivateData:)/,
|
||||
// react.jsx(WrapperComponent, { VencordOriginal: AccountPanel, ...
|
||||
replace: "$self.PanelWrapper,{VencordOriginal:$1,"
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ const settings = definePluginSettings({
|
|||
});
|
||||
|
||||
function stringToRegex(str: string) {
|
||||
const match = str.match(/^(\/)?(.+?)(?:\/([gimsuy]*))?$/); // Regex to match regex
|
||||
const match = str.match(/^(\/)?(.+?)(?:\/([gimsuyv]*))?$/); // Regex to match regex
|
||||
return match
|
||||
? new RegExp(
|
||||
match[2], // Pattern
|
||||
|
|
|
@ -98,8 +98,8 @@ export default definePlugin({
|
|||
{
|
||||
find: ".popularApplicationCommandIds,",
|
||||
replacement: {
|
||||
match: /applicationId:\i\.id}\),(?=.{0,50}setNote:\i)/,
|
||||
replace: "$&$self.patchPopout(arguments[0]),",
|
||||
match: /(?<=,)(?=!\i&&!\i&&.{0,50}setNote:)/,
|
||||
replace: "$self.patchPopout(arguments[0]),",
|
||||
}
|
||||
},
|
||||
// below username
|
||||
|
|
|
@ -21,6 +21,7 @@ import { Button, showToast, Toasts, useState } from "@webpack/common";
|
|||
|
||||
import type { VoiceRecorder } from ".";
|
||||
import { settings } from "./settings";
|
||||
import { MediaEngineStore } from "./utils";
|
||||
|
||||
const Native = VencordNative.pluginHelpers.VoiceMessages as PluginNative<typeof import("./native")>;
|
||||
|
||||
|
@ -41,6 +42,7 @@ export const VoiceRecorderDesktop: VoiceRecorder = ({ setAudioBlob, onRecordingC
|
|||
{
|
||||
echoCancellation: settings.store.echoCancellation,
|
||||
noiseCancellation: settings.store.noiseSuppression,
|
||||
deviceId: MediaEngineStore.getInputDeviceId(),
|
||||
},
|
||||
(success: boolean) => {
|
||||
if (success)
|
||||
|
|
|
@ -20,6 +20,7 @@ import { Button, useState } from "@webpack/common";
|
|||
|
||||
import type { VoiceRecorder } from ".";
|
||||
import { settings } from "./settings";
|
||||
import { MediaEngineStore } from "./utils";
|
||||
|
||||
export const VoiceRecorderWeb: VoiceRecorder = ({ setAudioBlob, onRecordingChange }) => {
|
||||
const [recording, setRecording] = useState(false);
|
||||
|
@ -40,6 +41,7 @@ export const VoiceRecorderWeb: VoiceRecorder = ({ setAudioBlob, onRecordingChang
|
|||
audio: {
|
||||
echoCancellation: settings.store.echoCancellation,
|
||||
noiseSuppression: settings.store.noiseSuppression,
|
||||
deviceId: MediaEngineStore.getInputDeviceId()
|
||||
}
|
||||
}).then(stream => {
|
||||
const chunks = [] as Blob[];
|
||||
|
|
|
@ -17,5 +17,7 @@
|
|||
*/
|
||||
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
|
||||
export const MediaEngineStore = findStoreLazy("MediaEngineStore");
|
||||
export const cl = classNameFactory("vc-vmsg-");
|
||||
|
|
|
@ -200,6 +200,34 @@ export default definePlugin({
|
|||
match: /supports\(\i\)\{switch\(\i\)\{(case (\i).\i)/,
|
||||
replace: "$&.DISABLE_VIDEO:return true;$1"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".Messages.SEARCH_WITH_GOOGLE",
|
||||
replacement: {
|
||||
match: /\i\.isPlatformEmbedded/,
|
||||
replace: "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
find: ".Messages.COPY,hint:",
|
||||
replacement: [
|
||||
{
|
||||
match: /\i\.isPlatformEmbedded/,
|
||||
replace: "true"
|
||||
},
|
||||
{
|
||||
match: /\i\.\i\.copy/,
|
||||
replace: "Vencord.Webpack.Common.Clipboard.copy"
|
||||
}]
|
||||
},
|
||||
// Automod add filter words
|
||||
{
|
||||
find: '("interactionUsernameProfile',
|
||||
replacement:
|
||||
{
|
||||
match: /\i\.isPlatformEmbedded(?=.{0,50}\.tagName)/,
|
||||
replace: "true"
|
||||
},
|
||||
}
|
||||
],
|
||||
|
||||
|
|
|
@ -136,7 +136,7 @@ const settings = definePluginSettings({
|
|||
},
|
||||
});
|
||||
|
||||
const Native = VencordNative.pluginHelpers.XsOverlay as PluginNative<typeof import("./native")>;
|
||||
const Native = VencordNative.pluginHelpers.XSOverlay as PluginNative<typeof import("./native")>;
|
||||
|
||||
export default definePlugin({
|
||||
name: "XSOverlay",
|
||||
|
@ -154,104 +154,99 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
MESSAGE_CREATE({ message, optimistic }: { message: Message; optimistic: boolean; }) {
|
||||
// Apparently without this try/catch, discord's socket connection dies if any part of this errors
|
||||
try {
|
||||
if (optimistic) return;
|
||||
const channel = ChannelStore.getChannel(message.channel_id);
|
||||
if (!shouldNotify(message, message.channel_id)) return;
|
||||
if (optimistic) return;
|
||||
const channel = ChannelStore.getChannel(message.channel_id);
|
||||
if (!shouldNotify(message, message.channel_id)) return;
|
||||
|
||||
const pingColor = settings.store.pingColor.replaceAll("#", "").trim();
|
||||
const channelPingColor = settings.store.channelPingColor.replaceAll("#", "").trim();
|
||||
let finalMsg = message.content;
|
||||
let titleString = "";
|
||||
const pingColor = settings.store.pingColor.replaceAll("#", "").trim();
|
||||
const channelPingColor = settings.store.channelPingColor.replaceAll("#", "").trim();
|
||||
let finalMsg = message.content;
|
||||
let titleString = "";
|
||||
|
||||
if (channel.guild_id) {
|
||||
const guild = GuildStore.getGuild(channel.guild_id);
|
||||
titleString = `${message.author.username} (${guild.name}, #${channel.name})`;
|
||||
}
|
||||
|
||||
|
||||
switch (channel.type) {
|
||||
case ChannelTypes.DM:
|
||||
titleString = message.author.username.trim();
|
||||
break;
|
||||
case ChannelTypes.GROUP_DM:
|
||||
const channelName = channel.name.trim() ?? channel.rawRecipients.map(e => e.username).join(", ");
|
||||
titleString = `${message.author.username} (${channelName})`;
|
||||
break;
|
||||
}
|
||||
|
||||
if (message.referenced_message) {
|
||||
titleString += " (reply)";
|
||||
}
|
||||
|
||||
if (message.embeds.length > 0) {
|
||||
finalMsg += " [embed] ";
|
||||
if (message.content === "") {
|
||||
finalMsg = "sent message embed(s)";
|
||||
}
|
||||
}
|
||||
|
||||
if (message.sticker_items) {
|
||||
finalMsg += " [sticker] ";
|
||||
if (message.content === "") {
|
||||
finalMsg = "sent a sticker";
|
||||
}
|
||||
}
|
||||
|
||||
const images = message.attachments.filter(e =>
|
||||
typeof e?.content_type === "string"
|
||||
&& e?.content_type.startsWith("image")
|
||||
);
|
||||
|
||||
|
||||
images.forEach(img => {
|
||||
finalMsg += ` [image: ${img.filename}] `;
|
||||
});
|
||||
|
||||
message.attachments.filter(a => a && !a.content_type?.startsWith("image")).forEach(a => {
|
||||
finalMsg += ` [attachment: ${a.filename}] `;
|
||||
});
|
||||
|
||||
// make mentions readable
|
||||
if (message.mentions.length > 0) {
|
||||
finalMsg = finalMsg.replace(/<@!?(\d{17,20})>/g, (_, id) => `<color=#${pingColor}><b>@${UserStore.getUser(id)?.username || "unknown-user"}</color></b>`);
|
||||
}
|
||||
|
||||
// color role mentions (unity styling btw lol)
|
||||
if (message.mention_roles.length > 0) {
|
||||
for (const roleId of message.mention_roles) {
|
||||
const role = GuildStore.getRole(channel.guild_id, roleId);
|
||||
if (!role) continue;
|
||||
const roleColor = role.colorString ?? `#${pingColor}`;
|
||||
finalMsg = finalMsg.replace(`<@&${roleId}>`, `<b><color=${roleColor}>@${role.name}</color></b>`);
|
||||
}
|
||||
}
|
||||
|
||||
// make emotes and channel mentions readable
|
||||
const emoteMatches = finalMsg.match(new RegExp("(<a?:\\w+:\\d+>)", "g"));
|
||||
const channelMatches = finalMsg.match(new RegExp("<(#\\d+)>", "g"));
|
||||
|
||||
if (emoteMatches) {
|
||||
for (const eMatch of emoteMatches) {
|
||||
finalMsg = finalMsg.replace(new RegExp(`${eMatch}`, "g"), `:${eMatch.split(":")[1]}:`);
|
||||
}
|
||||
}
|
||||
|
||||
// color channel mentions
|
||||
if (channelMatches) {
|
||||
for (const cMatch of channelMatches) {
|
||||
let channelId = cMatch.split("<#")[1];
|
||||
channelId = channelId.substring(0, channelId.length - 1);
|
||||
finalMsg = finalMsg.replace(new RegExp(`${cMatch}`, "g"), `<b><color=#${channelPingColor}>#${ChannelStore.getChannel(channelId).name}</color></b>`);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldIgnoreForChannelType(channel)) return;
|
||||
sendMsgNotif(titleString, finalMsg, message);
|
||||
} catch (err) {
|
||||
XSLog.error(`Failed to catch MESSAGE_CREATE: ${err}`);
|
||||
if (channel.guild_id) {
|
||||
const guild = GuildStore.getGuild(channel.guild_id);
|
||||
titleString = `${message.author.username} (${guild.name}, #${channel.name})`;
|
||||
}
|
||||
|
||||
|
||||
switch (channel.type) {
|
||||
case ChannelTypes.DM:
|
||||
titleString = message.author.username.trim();
|
||||
break;
|
||||
case ChannelTypes.GROUP_DM:
|
||||
const channelName = channel.name.trim() ?? channel.rawRecipients.map(e => e.username).join(", ");
|
||||
titleString = `${message.author.username} (${channelName})`;
|
||||
break;
|
||||
}
|
||||
|
||||
if (message.referenced_message) {
|
||||
titleString += " (reply)";
|
||||
}
|
||||
|
||||
if (message.embeds.length > 0) {
|
||||
finalMsg += " [embed] ";
|
||||
if (message.content === "") {
|
||||
finalMsg = "sent message embed(s)";
|
||||
}
|
||||
}
|
||||
|
||||
if (message.sticker_items) {
|
||||
finalMsg += " [sticker] ";
|
||||
if (message.content === "") {
|
||||
finalMsg = "sent a sticker";
|
||||
}
|
||||
}
|
||||
|
||||
const images = message.attachments.filter(e =>
|
||||
typeof e?.content_type === "string"
|
||||
&& e?.content_type.startsWith("image")
|
||||
);
|
||||
|
||||
|
||||
images.forEach(img => {
|
||||
finalMsg += ` [image: ${img.filename}] `;
|
||||
});
|
||||
|
||||
message.attachments.filter(a => a && !a.content_type?.startsWith("image")).forEach(a => {
|
||||
finalMsg += ` [attachment: ${a.filename}] `;
|
||||
});
|
||||
|
||||
// make mentions readable
|
||||
if (message.mentions.length > 0) {
|
||||
finalMsg = finalMsg.replace(/<@!?(\d{17,20})>/g, (_, id) => `<color=#${pingColor}><b>@${UserStore.getUser(id)?.username || "unknown-user"}</color></b>`);
|
||||
}
|
||||
|
||||
// color role mentions (unity styling btw lol)
|
||||
if (message.mention_roles.length > 0) {
|
||||
for (const roleId of message.mention_roles) {
|
||||
const role = GuildStore.getRole(channel.guild_id, roleId);
|
||||
if (!role) continue;
|
||||
const roleColor = role.colorString ?? `#${pingColor}`;
|
||||
finalMsg = finalMsg.replace(`<@&${roleId}>`, `<b><color=${roleColor}>@${role.name}</color></b>`);
|
||||
}
|
||||
}
|
||||
|
||||
// make emotes and channel mentions readable
|
||||
const emoteMatches = finalMsg.match(new RegExp("(<a?:\\w+:\\d+>)", "g"));
|
||||
const channelMatches = finalMsg.match(new RegExp("<(#\\d+)>", "g"));
|
||||
|
||||
if (emoteMatches) {
|
||||
for (const eMatch of emoteMatches) {
|
||||
finalMsg = finalMsg.replace(new RegExp(`${eMatch}`, "g"), `:${eMatch.split(":")[1]}:`);
|
||||
}
|
||||
}
|
||||
|
||||
// color channel mentions
|
||||
if (channelMatches) {
|
||||
for (const cMatch of channelMatches) {
|
||||
let channelId = cMatch.split("<#")[1];
|
||||
channelId = channelId.substring(0, channelId.length - 1);
|
||||
finalMsg = finalMsg.replace(new RegExp(`${cMatch}`, "g"), `<b><color=#${channelPingColor}>#${ChannelStore.getChannel(channelId).name}</color></b>`);
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldIgnoreForChannelType(channel)) return;
|
||||
sendMsgNotif(titleString, finalMsg, message);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -533,6 +533,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
|||
nekohaxx: {
|
||||
name: "nekohaxx",
|
||||
id: 1176270221628153886n
|
||||
},
|
||||
Antti: {
|
||||
name: "Antti",
|
||||
id: 312974985876471810n
|
||||
}
|
||||
} satisfies Record<string, Dev>);
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ export function openImageModal(url: string, props?: Partial<React.ComponentProps
|
|||
placeholder={url}
|
||||
src={url}
|
||||
renderLinkComponent={props => <MaskedLink {...props} />}
|
||||
// FIXME: wtf is this? do we need to pass some proper component??
|
||||
// Don't render forward message button
|
||||
renderForwardComponent={() => null}
|
||||
shouldHideMediaOptions={false}
|
||||
shouldAnimate
|
||||
|
|
|
@ -99,3 +99,14 @@ export const isPluginDev = (id: string) => Object.hasOwn(DevsById, id);
|
|||
export function pluralise(amount: number, singular: string, plural = singular + "s") {
|
||||
return amount === 1 ? `${amount} ${singular}` : `${amount} ${plural}`;
|
||||
}
|
||||
|
||||
export function tryOrElse<T>(func: () => T, fallback: T): T {
|
||||
try {
|
||||
const res = func();
|
||||
return res instanceof Promise
|
||||
? res.catch(() => fallback) as T
|
||||
: res;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
|
@ -38,7 +38,7 @@ const enum ModalTransitionState {
|
|||
|
||||
export interface ModalProps {
|
||||
transitionState: ModalTransitionState;
|
||||
onClose(): Promise<void>;
|
||||
onClose(): void;
|
||||
}
|
||||
|
||||
export interface ModalOptions {
|
||||
|
|
|
@ -131,3 +131,18 @@ export function makeCodeblock(text: string, language?: string) {
|
|||
const chars = "```";
|
||||
return `${chars}${language || ""}\n${text.replaceAll("```", "\\`\\`\\`")}\n${chars}`;
|
||||
}
|
||||
|
||||
export function stripIndent(strings: TemplateStringsArray, ...values: any[]) {
|
||||
const string = String.raw({ raw: strings }, ...values);
|
||||
|
||||
const match = string.match(/^[ \t]*(?=\S)/gm);
|
||||
if (!match) return string.trim();
|
||||
|
||||
const minIndent = match.reduce((r, a) => Math.min(r, a.length), Infinity);
|
||||
return string.replace(new RegExp(`^[ \\t]{${minIndent}}`, "gm"), "").trim();
|
||||
}
|
||||
|
||||
export const ZWSP = "\u200b";
|
||||
export function toInlineCode(s: string) {
|
||||
return "``" + ZWSP + s.replaceAll("`", ZWSP + "`" + ZWSP) + ZWSP + "``";
|
||||
}
|
||||
|
|
|
@ -128,7 +128,7 @@ export interface PluginDef {
|
|||
* Allows you to subscribe to Flux events
|
||||
*/
|
||||
flux?: {
|
||||
[E in FluxEvents]?: (event: any) => void;
|
||||
[E in FluxEvents]?: (event: any) => void | Promise<void>;
|
||||
};
|
||||
/**
|
||||
* Allows you to manipulate context menus
|
||||
|
|
|
@ -8,7 +8,12 @@ export let EXTENSION_BASE_URL: string;
|
|||
export let EXTENSION_VERSION: string;
|
||||
|
||||
if (IS_EXTENSION) {
|
||||
const script = document.querySelector("#vencord-script") as HTMLScriptElement;
|
||||
EXTENSION_BASE_URL = script.dataset.extensionBaseUrl!;
|
||||
EXTENSION_VERSION = script.dataset.version!;
|
||||
const listener = (e: MessageEvent) => {
|
||||
if (e.data?.type === "vencord:meta") {
|
||||
({ EXTENSION_BASE_URL, EXTENSION_VERSION } = e.data.meta);
|
||||
window.removeEventListener("message", listener);
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("message", listener);
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ export let Card: t.Card;
|
|||
export let Button: t.Button;
|
||||
export let Switch: t.Switch;
|
||||
export let Tooltip: t.Tooltip;
|
||||
export let TooltipContainer: t.TooltipContainer;
|
||||
export let TextInput: t.TextInput;
|
||||
export let TextArea: t.TextArea;
|
||||
export let Text: t.Text;
|
||||
|
@ -66,6 +67,7 @@ waitFor(["FormItem", "Button"], m => {
|
|||
Button,
|
||||
FormSwitch: Switch,
|
||||
Tooltip,
|
||||
TooltipContainer,
|
||||
TextInput,
|
||||
TextArea,
|
||||
Text,
|
||||
|
|
|
@ -20,9 +20,9 @@ export * from "./classes";
|
|||
export * from "./components";
|
||||
export * from "./menu";
|
||||
export * from "./react";
|
||||
export * from "./settingsStores";
|
||||
export * from "./stores";
|
||||
export * as ComponentTypes from "./types/components.d";
|
||||
export * as MenuTypes from "./types/menu.d";
|
||||
export * as UtilTypes from "./types/utils.d";
|
||||
export * from "./userSettings";
|
||||
export * from "./utils";
|
||||
|
|
44
src/webpack/common/types/components.d.ts
vendored
44
src/webpack/common/types/components.d.ts
vendored
|
@ -101,6 +101,28 @@ export type Tooltip = ComponentType<{
|
|||
|
||||
export type TooltipPositions = Record<"BOTTOM" | "CENTER" | "LEFT" | "RIGHT" | "TOP" | "WINDOW_CENTER", string>;
|
||||
|
||||
export type TooltipContainer = ComponentType<PropsWithChildren<{
|
||||
text: ReactNode;
|
||||
element?: "div" | "span";
|
||||
"aria-label"?: string | false;
|
||||
|
||||
delay?: number;
|
||||
/** Tooltip.Colors.BLACK */
|
||||
color?: string;
|
||||
/** TooltipPositions.TOP */
|
||||
position?: string;
|
||||
spacing?: number;
|
||||
|
||||
className?: string;
|
||||
tooltipClassName?: string | null;
|
||||
tooltipContentClassName?: string | null;
|
||||
|
||||
allowOverflow?: boolean;
|
||||
forceOpen?: boolean;
|
||||
hideOnClick?: boolean;
|
||||
disableTooltipPointerEvents?: boolean;
|
||||
}>>;
|
||||
|
||||
export type Card = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement> & {
|
||||
editable?: boolean;
|
||||
outline?: boolean;
|
||||
|
@ -110,6 +132,26 @@ export type Card = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement> & {
|
|||
Types: Record<"BRAND" | "CUSTOM" | "DANGER" | "PRIMARY" | "SUCCESS" | "WARNING", string>;
|
||||
};
|
||||
|
||||
export type ComboboxPopout = ComponentType<PropsWithChildren<{
|
||||
value: Set<any>;
|
||||
placeholder: string;
|
||||
children(query: string): ReactNode[];
|
||||
|
||||
onChange(value: any): void;
|
||||
itemToString?: (item: any) => string;
|
||||
onClose?(): void;
|
||||
|
||||
className?: string;
|
||||
listClassName?: string;
|
||||
|
||||
|
||||
autoFocus?: boolean;
|
||||
multiSelect?: boolean;
|
||||
maxVisibleItems?: number;
|
||||
showScrollbar?: boolean;
|
||||
|
||||
}>>;
|
||||
|
||||
export type Button = ComponentType<PropsWithChildren<Omit<HTMLProps<HTMLButtonElement>, "size"> & {
|
||||
/** Button.Looks.FILLED */
|
||||
look?: string;
|
||||
|
@ -375,7 +417,7 @@ export type Popout = ComponentType<{
|
|||
Animation: typeof PopoutAnimation;
|
||||
};
|
||||
|
||||
export type Dialog = ComponentType<PropsWithChildren<any>>;
|
||||
export type Dialog = ComponentType<JSX.IntrinsicElements["div"]>;
|
||||
|
||||
type Resolve = (data: { theme: "light" | "dark", saturation: number; }) => {
|
||||
hex(): string;
|
||||
|
|
1
src/webpack/common/types/index.d.ts
vendored
1
src/webpack/common/types/index.d.ts
vendored
|
@ -21,6 +21,5 @@ export * from "./components";
|
|||
export * from "./fluxEvents";
|
||||
export * from "./i18nMessages";
|
||||
export * from "./menu";
|
||||
export * from "./settingsStores";
|
||||
export * from "./stores";
|
||||
export * from "./utils";
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export interface SettingsStore<T = any> {
|
||||
getSetting(): T;
|
||||
updateSetting(value: T): void;
|
||||
useSetting(): T;
|
||||
}
|
37
src/webpack/common/types/utils.d.ts
vendored
37
src/webpack/common/types/utils.d.ts
vendored
|
@ -18,6 +18,7 @@
|
|||
|
||||
import { Guild, GuildMember } from "discord-types/general";
|
||||
import type { ReactNode } from "react";
|
||||
import { LiteralUnion } from "type-fest";
|
||||
|
||||
import type { FluxEvents } from "./fluxEvents";
|
||||
import { i18nMessages } from "./i18nMessages";
|
||||
|
@ -81,7 +82,7 @@ interface RestRequestData {
|
|||
retries?: number;
|
||||
}
|
||||
|
||||
export type RestAPI = Record<"delete" | "get" | "patch" | "post" | "put", (data: RestRequestData) => Promise<any>>;
|
||||
export type RestAPI = Record<"del" | "get" | "patch" | "post" | "put", (data: RestRequestData) => Promise<any>>;
|
||||
|
||||
export type Permissions = "CREATE_INSTANT_INVITE"
|
||||
| "KICK_MEMBERS"
|
||||
|
@ -221,3 +222,37 @@ export interface Constants {
|
|||
UserFlags: Record<string, number>;
|
||||
FriendsSections: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface ExpressionPickerStore {
|
||||
closeExpressionPicker(activeViewType?: any): void;
|
||||
openExpressionPicker(activeView: LiteralUnion<"emoji" | "gif" | "sticker", string>, activeViewType?: any): void;
|
||||
}
|
||||
|
||||
export interface BrowserWindowFeatures {
|
||||
toolbar?: boolean;
|
||||
menubar?: boolean;
|
||||
location?: boolean;
|
||||
directories?: boolean;
|
||||
width?: number;
|
||||
height?: number;
|
||||
defaultWidth?: number;
|
||||
defaultHeight?: number;
|
||||
left?: number;
|
||||
top?: number;
|
||||
defaultAlwaysOnTop?: boolean;
|
||||
movable?: boolean;
|
||||
resizable?: boolean;
|
||||
frame?: boolean;
|
||||
alwaysOnTop?: boolean;
|
||||
hasShadow?: boolean;
|
||||
transparent?: boolean;
|
||||
skipTaskbar?: boolean;
|
||||
titleBarStyle?: string | null;
|
||||
backgroundColor?: string;
|
||||
}
|
||||
|
||||
export interface PopoutActions {
|
||||
open(key: string, render: (windowKey: string) => ReactNode, features?: BrowserWindowFeatures);
|
||||
close(key: string): void;
|
||||
setAlwaysOnTop(key: string, alwaysOnTop: boolean): void;
|
||||
}
|
||||
|
|
|
@ -160,9 +160,15 @@ export const InviteActions = findByPropsLazy("resolveInvite");
|
|||
|
||||
export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL");
|
||||
|
||||
const openExpressionPickerMatcher = canonicalizeMatch(/setState\({activeView:\i/);
|
||||
const openExpressionPickerMatcher = canonicalizeMatch(/setState\({activeView:\i,activeViewType:/);
|
||||
// TODO: type
|
||||
export const ExpressionPickerStore = mapMangledModuleLazy("expression-picker-last-active-view", {
|
||||
export const ExpressionPickerStore: t.ExpressionPickerStore = mapMangledModuleLazy("expression-picker-last-active-view", {
|
||||
closeExpressionPicker: filters.byCode("setState({activeView:null"),
|
||||
openExpressionPicker: m => typeof m === "function" && openExpressionPickerMatcher.test(m.toString()),
|
||||
});
|
||||
|
||||
export const PopoutActions: t.PopoutActions = mapMangledModuleLazy('type:"POPOUT_WINDOW_OPEN"', {
|
||||
open: filters.byCode('type:"POPOUT_WINDOW_OPEN"'),
|
||||
close: filters.byCode('type:"POPOUT_WINDOW_CLOSE"'),
|
||||
setAlwaysOnTop: filters.byCode('type:"POPOUT_WINDOW_SET_ALWAYS_ON_TOP"'),
|
||||
});
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue