Compare commits
146 Commits
SwcParser
...
features/c
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c3da99eeee | ||
![]() |
0e7bd87cee | ||
![]() |
5fac8be0ae | ||
![]() |
ffbb52512c | ||
![]() |
ff9d904fcb | ||
![]() |
50c0d472d7 | ||
![]() |
abbc08fb06 | ||
![]() |
409fb6ff4e | ||
![]() |
934a89add0 | ||
![]() |
a3b0556a9a | ||
![]() |
35d2b8d1cf | ||
![]() |
0328966e0f | ||
![]() |
2eb8f3ae19 | ||
![]() |
61fd38d6d9 | ||
![]() |
a7dbd73547 | ||
![]() |
c116d00d03 | ||
![]() |
44f6f71c3e | ||
![]() |
23d4cae123 | ||
![]() |
0da02e009c | ||
![]() |
7d555a96ea | ||
![]() |
f92f3f1a5e | ||
![]() |
6769de29cd | ||
![]() |
e2b622c76b | ||
![]() |
4b1e96b76e | ||
![]() |
e93111fb67 | ||
![]() |
ccf7f66a79 | ||
![]() |
d8afde2b4d | ||
![]() |
a15d5de493 | ||
![]() |
05051399b6 | ||
![]() |
e4068ef9a6 | ||
![]() |
c80ed1b824 | ||
![]() |
50047dd3c2 | ||
![]() |
36f4478a4f | ||
![]() |
350e7b0a6a | ||
![]() |
7eba5b99b0 | ||
![]() |
f81ab5ef93 | ||
![]() |
1f50f78912 | ||
![]() |
efab399309 | ||
![]() |
dd1537a5d6 | ||
![]() |
d97c3e2e02 | ||
![]() |
7cdc4e4c03 | ||
![]() |
d3bf5cec9a | ||
![]() |
a4303e3810 | ||
![]() |
1ea8a0b69b | ||
![]() |
139dd7a92e | ||
![]() |
b66903cf52 | ||
![]() |
287173458f | ||
![]() |
beb9aae26b | ||
![]() |
9d6021f0b9 | ||
![]() |
5a18292d92 | ||
![]() |
5625d63e46 | ||
![]() |
ae730e8398 | ||
![]() |
ad054d5c65 | ||
![]() |
82d53b1928 | ||
![]() |
c7c5ffdd44 | ||
![]() |
0ccea16453 | ||
![]() |
20237f5664 | ||
![]() |
01ae0983b3 | ||
![]() |
845088ec02 | ||
![]() |
9c7b548a9e | ||
![]() |
c8d87da62d | ||
![]() |
0d996633f2 | ||
![]() |
a4e98f9252 | ||
![]() |
53794ec180 | ||
![]() |
296336535f | ||
![]() |
563f2fb1dc | ||
![]() |
d73a6e2c89 | ||
![]() |
2cb6c23347 | ||
![]() |
87b6d6ab12 | ||
![]() |
bf49acd535 | ||
![]() |
ea0ded0f11 | ||
![]() |
d26196d6c5 | ||
![]() |
5fe04c5882 | ||
![]() |
a73e10fc77 | ||
![]() |
8817e2dff7 | ||
![]() |
267b2b1a07 | ||
![]() |
83d480a68c | ||
![]() |
ebe62a1790 | ||
![]() |
09b3f6d19b | ||
![]() |
8dff79d3f7 | ||
![]() |
9b7ebe4680 | ||
![]() |
8e93c5cb43 | ||
![]() |
66f8fde353 | ||
![]() |
071508c61a | ||
![]() |
bfb4114e18 | ||
![]() |
6afd959530 | ||
![]() |
86eacea74d | ||
![]() |
516f8c488a | ||
![]() |
c32426882e | ||
![]() |
39a7b2f5a9 | ||
![]() |
7a0560b9d4 | ||
![]() |
e685e399f9 | ||
![]() |
54198b1a4a | ||
![]() |
124d1ad9c7 | ||
![]() |
abfade4f38 | ||
![]() |
a89e17a390 | ||
![]() |
5610df8b37 | ||
![]() |
9e6ee4df52 | ||
![]() |
304bf4fe29 | ||
![]() |
25a64ab6be | ||
![]() |
a3c2da31c3 | ||
![]() |
f875d63c6d | ||
![]() |
e7fb4ebd4e | ||
![]() |
2105de8ca5 | ||
![]() |
bb7332cefd | ||
![]() |
43951456d3 | ||
![]() |
d3c581eb4e | ||
![]() |
151f2eef8a | ||
![]() |
e0bbdd89bd | ||
![]() |
b101e643d5 | ||
![]() |
dea34503ef | ||
![]() |
0109381a4f | ||
![]() |
8842ad7652 | ||
![]() |
73a1bc94d1 | ||
![]() |
ea14bad85d | ||
![]() |
f9a682f1c3 | ||
![]() |
175c1a78f8 | ||
![]() |
74c3930e0a | ||
![]() |
e563521416 | ||
![]() |
a9e67aa340 | ||
![]() |
25fcc528ea | ||
![]() |
443978929b | ||
![]() |
45644dec43 | ||
![]() |
3e0355cb53 | ||
![]() |
7e526e4172 | ||
![]() |
98cfa090d4 | ||
![]() |
77aa0c78a0 | ||
![]() |
e010b2d63e | ||
![]() |
dafbd39113 | ||
![]() |
88542b9ede | ||
![]() |
c5e0c7a6e7 | ||
![]() |
e1027e06c1 | ||
![]() |
f1a31a6184 | ||
![]() |
8186fe290e | ||
![]() |
a6551957e7 | ||
![]() |
3a9f692644 | ||
![]() |
e35393b40c | ||
![]() |
0a2c637c61 | ||
![]() |
cc25753314 | ||
![]() |
a9eae106c7 | ||
![]() |
f2d913c672 | ||
![]() |
8fe60971f5 | ||
![]() |
71a59f4020 | ||
![]() |
07ed4fa01f | ||
![]() |
e454ffbfed | ||
![]() |
d102d5d976 |
93
.eslintrc.json
Normal file
93
.eslintrc.json
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"ignorePatterns": ["dist", "browser"],
|
||||||
|
"plugins": ["header", "simple-import-sort", "unused-imports"],
|
||||||
|
"rules": {
|
||||||
|
// Since it's only been a month and Vencord has already been stolen
|
||||||
|
// by random skids who rebranded it to "AlphaCord" and erased all license
|
||||||
|
// information
|
||||||
|
"header/header": [
|
||||||
|
2,
|
||||||
|
"block",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"pattern": "!?",
|
||||||
|
"template": " "
|
||||||
|
},
|
||||||
|
" * Vencord, a modification for Discord's desktop app",
|
||||||
|
{
|
||||||
|
"pattern": " \\* Copyright \\(c\\) \\d{4}",
|
||||||
|
"template": " * Copyright (c) 2022 Vendicated and contributors"
|
||||||
|
},
|
||||||
|
" *",
|
||||||
|
" * This program is free software: you can redistribute it and/or modify",
|
||||||
|
" * it under the terms of the GNU General Public License as published by",
|
||||||
|
" * the Free Software Foundation, either version 3 of the License, or",
|
||||||
|
" * (at your option) any later version.",
|
||||||
|
" *",
|
||||||
|
" * This program is distributed in the hope that it will be useful,",
|
||||||
|
" * but WITHOUT ANY WARRANTY; without even the implied warranty of",
|
||||||
|
" * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the",
|
||||||
|
" * GNU General Public License for more details.",
|
||||||
|
" *",
|
||||||
|
" * You should have received a copy of the GNU General Public License",
|
||||||
|
" * along with this program. If not, see <https://www.gnu.org/licenses/>.",
|
||||||
|
""
|
||||||
|
],
|
||||||
|
2
|
||||||
|
],
|
||||||
|
"quotes": ["error", "double", { "avoidEscape": true }],
|
||||||
|
"jsx-quotes": ["error", "prefer-double"],
|
||||||
|
"no-mixed-spaces-and-tabs": "error",
|
||||||
|
"indent": ["error", 4, { "SwitchCase": 1 }],
|
||||||
|
"arrow-parens": ["error", "as-needed"],
|
||||||
|
"eol-last": ["error", "always"],
|
||||||
|
"func-call-spacing": ["error", "never"],
|
||||||
|
"no-multi-spaces": "error",
|
||||||
|
"no-trailing-spaces": "error",
|
||||||
|
"no-whitespace-before-property": "error",
|
||||||
|
"semi": ["error", "always"],
|
||||||
|
"semi-style": ["error", "last"],
|
||||||
|
"space-in-parens": ["error", "never"],
|
||||||
|
"block-spacing": ["error", "always"],
|
||||||
|
"object-curly-spacing": ["error", "always"],
|
||||||
|
"eqeqeq": ["error", "always", { "null": "ignore" }],
|
||||||
|
"spaced-comment": ["error", "always", { "markers": ["!"] }],
|
||||||
|
"yoda": "error",
|
||||||
|
"prefer-destructuring": ["error", { "object": true, "array": false }],
|
||||||
|
"operator-assignment": ["error", "always"],
|
||||||
|
"no-useless-computed-key": "error",
|
||||||
|
"no-unneeded-ternary": ["error", { "defaultAssignment": false }],
|
||||||
|
"no-invalid-regexp": "error",
|
||||||
|
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||||
|
"no-duplicate-imports": "error",
|
||||||
|
"no-extra-semi": "error",
|
||||||
|
"consistent-return": ["warn", { "treatUndefinedAsUnspecified": true }],
|
||||||
|
"dot-notation": "error",
|
||||||
|
"no-useless-escape": "error",
|
||||||
|
"no-fallthrough": "error",
|
||||||
|
"for-direction": "error",
|
||||||
|
"no-async-promise-executor": "error",
|
||||||
|
"no-cond-assign": "error",
|
||||||
|
"no-dupe-else-if": "error",
|
||||||
|
"no-duplicate-case": "error",
|
||||||
|
"no-irregular-whitespace": "error",
|
||||||
|
"no-loss-of-precision": "error",
|
||||||
|
"no-misleading-character-class": "error",
|
||||||
|
"no-prototype-builtins": "error",
|
||||||
|
"no-regex-spaces": "error",
|
||||||
|
"no-shadow-restricted-names": "error",
|
||||||
|
"no-unexpected-multiline": "error",
|
||||||
|
"no-unsafe-optional-chaining": "error",
|
||||||
|
"no-useless-backreference": "error",
|
||||||
|
"use-isnan": "error",
|
||||||
|
"prefer-const": "error",
|
||||||
|
"prefer-spread": "error",
|
||||||
|
|
||||||
|
"simple-import-sort/imports": "error",
|
||||||
|
"simple-import-sort/exports": "error",
|
||||||
|
|
||||||
|
"unused-imports/no-unused-imports": "error"
|
||||||
|
}
|
||||||
|
}
|
56
.github/workflows/build.yml
vendored
Normal file
56
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
name: Build latest
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
env:
|
||||||
|
FORCE_COLOR: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
Build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||||
|
|
||||||
|
- name: Use Node.js 18
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build web
|
||||||
|
run: pnpm buildWeb
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: pnpm build --standalone
|
||||||
|
|
||||||
|
- name: Get some values needed for the release
|
||||||
|
id: vars
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=sha_short::$(git rev-parse --short HEAD)"
|
||||||
|
|
||||||
|
- uses: dev-drprasad/delete-tag-and-release@085c6969f18bad0de1b9f3fe6692a3cd01f64fe5 # v0.2.0
|
||||||
|
with:
|
||||||
|
delete_release: true
|
||||||
|
tag_name: devbuild
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Create the release
|
||||||
|
uses: softprops/action-gh-release@1e07f4398721186383de40550babbdf2b84acfc5 # v1
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tag_name: devbuild
|
||||||
|
name: Dev Build ${{ steps.vars.outputs.sha_short }}
|
||||||
|
draft: false
|
||||||
|
prerelease: false
|
||||||
|
files: |
|
||||||
|
dist/*
|
27
.github/workflows/test.yml
vendored
Normal file
27
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
name: test
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json
|
||||||
|
|
||||||
|
- name: Use Node.js 18
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
cache: "pnpm"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Lint & Test if it compiles
|
||||||
|
run: pnpm test
|
20
.gitignore
vendored
20
.gitignore
vendored
@ -1,2 +1,20 @@
|
|||||||
dist
|
dist
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
vencord_installer
|
||||||
|
|
||||||
|
.idea
|
||||||
|
|
||||||
|
yarn.lock
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
src/userplugins
|
||||||
|
16
.vscode/settings.json
vendored
Normal file
16
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.fixAll.eslint": true
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||||
|
},
|
||||||
|
"[typescriptreact]": {
|
||||||
|
"editor.defaultFormatter": "vscode.typescript-language-features"
|
||||||
|
},
|
||||||
|
"javascript.format.semicolons": "insert",
|
||||||
|
"typescript.format.semicolons": "insert",
|
||||||
|
"typescript.preferences.quoteStyle": "double",
|
||||||
|
"javascript.preferences.quoteStyle": "double"
|
||||||
|
}
|
@ -4,15 +4,16 @@ First of all, thank you for contributing! :3
|
|||||||
|
|
||||||
To ensure your contribution is robust, please follow the below guide!
|
To ensure your contribution is robust, please follow the below guide!
|
||||||
|
|
||||||
|
For a friendly introduction to plugins, see [Megu's Plugin Guide!](docs/2_PLUGINS.md)
|
||||||
|
|
||||||
## Style Guide
|
## Style Guide
|
||||||
|
|
||||||
- This project has a very minimal .editorconfig. Make sure your editor supports this!
|
- 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,
|
If you are using VSCode, it should automatically recommend you the extension; If not,
|
||||||
please install the Editorconfig extension
|
please install the Editorconfig extension
|
||||||
- Try to follow the formatting in the rest of the project and stay consistent
|
- 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
|
- 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
|
or React Component, in which case they should be PascalCase
|
||||||
|
|
||||||
|
|
||||||
## Contributing a Plugin
|
## Contributing a Plugin
|
||||||
|
|
||||||
@ -23,7 +24,6 @@ This way we can ensure compatibility and high quality patches.
|
|||||||
|
|
||||||
Follow the below guide to make your first plugin!
|
Follow the below guide to make your first plugin!
|
||||||
|
|
||||||
|
|
||||||
### Finding the right module to patch
|
### Finding the right module to patch
|
||||||
|
|
||||||
If the thing you want to patch is an action performed when interacting with a part of the UI, use React DevTools.
|
If the thing you want to patch is an action performed when interacting with a part of the UI, use React DevTools.
|
||||||
@ -50,20 +50,22 @@ This is the regex that will operate on the module found with "find". Just like i
|
|||||||
this only matches exactly the part you want to patch and no other parts in the file.
|
this only matches exactly the part you want to patch and no other parts in the file.
|
||||||
|
|
||||||
The easiest way to write and test your regex is the following:
|
The easiest way to write and test your regex is the following:
|
||||||
- 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).
|
- 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
|
||||||
- Now paste the following into the console: `Vencord.Webpack.wreq.m[447887].toString()` (Changing the number to your ID)
|
see something like `447887: (e,t,n)=>{` (Obviously the number will differ).
|
||||||
- Now either test regexes on this string in the console or use a tool like https://regex101.com
|
- 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:
|
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.
|
- Never hardcode variable or parameter names or any other minified names. They will change in the future. The only Exception to this rule
|
||||||
Instead, use one of the following approaches where applicable:
|
are the react props parameter which seems to always be `e`, but even then only rely on this if it is necessary.
|
||||||
- Match 1 or 2 of any character: `.{1,2}`, for example to match the variable name in `var a=b`, `var (.{1,2})=`
|
Instead, use one of the following approaches where applicable:
|
||||||
- Match any but a guaranteed terminating character: `[^;]+`, for example to match the entire assigned value in `var a=b||c||func();`,
|
- Match 1 or 2 of any character: `.{1,2}`, for example to match the variable name in `var a=b`, `var (.{1,2})=`
|
||||||
`var .{1,2}=([^;]+);`
|
- Match any but a guaranteed terminating character: `[^;]+`, for example to match the entire assigned value in `var a=b||c||func();`,
|
||||||
- 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
|
`var .{1,2}=([^;]+);`
|
||||||
- Additionally, as you might have noticed, all of the appove approaches use regex groups (`(...)`) to capture the variable name. You can then use those groups in your replacement to access those variables dynamically
|
- 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 appove approaches use regex groups (`(...)`) to capture the variable name. You can then use those groups in your replacement to access those variables dynamically
|
||||||
|
|
||||||
#### "replace"
|
#### "replace"
|
||||||
|
|
||||||
@ -75,6 +77,6 @@ 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.
|
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
|
This includes spaces, tabs and especially newlines
|
||||||
|
|
||||||
___
|
---
|
||||||
|
|
||||||
And that's it! Now open a Pull Request with your Plugin
|
And that's it! Now open a Pull Request with your Plugin
|
||||||
|
47
README.md
47
README.md
@ -1,34 +1,49 @@
|
|||||||
# Vencord
|
# Vencord
|
||||||
|
|
||||||
My own Discord Desktop mod :)
|
A Discord client mod that does things differently
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Works on Discord's latest swc update that breaks all other mods
|
- Works on Discord's latest update that breaks all other mods
|
||||||
- Proper context isolation -> Works in newer Electron versions (Confirmed working on versions 13-21)
|
- Browser Support (experimental): Run Vencord in your Browser instead of the desktop app
|
||||||
- Inline patches: Patch Discord's code with regex replacements! See [the experiments plugin](src/plugins/experiments.ts) for an example. While being more complex, this is more powerful than monkey patching since you can patch only small parts of functions instead of fully replacing them, access non exported/local variables and even replace constants (like in the aforementioned experiments patch!)
|
- Custom Css and Themes: Manually edit `%appdata%/Vencord/settings/quickCss.css` / `~/.config/Vencord/settings/quickCss.css` with your favourite editor and the client will automatically apply your changes. To import BetterDiscord themes, just add `@import url(theUrl)` on the top of this file. (Make sure the url is a github raw URL or similar and only contains plain text, and NOT a nice looking website)
|
||||||
- Experiments
|
- Many Useful™ plugins - [List](https://github.com/Vendicated/Vencord/tree/main/src/plugins)
|
||||||
- Custom Css: Manually edit `%appdata%/Vencord/settings/quickCss.css` / `~/.config/Vencord/settings/quickCss.css` with your favourite editor and the client will automatically apply your changes
|
- Experiments
|
||||||
- Many Useful™ plugins - [List](https://github.com/Vendicated/Vencord/tree/main/src/plugins)
|
- Proper context isolation -> Works in newer Electron versions (Confirmed working on versions 13-22)
|
||||||
|
- Inline patches: Patch Discord's code with regex replacements! See [the experiments plugin](src/plugins/experiments.ts) for an example. While being more complex, this is more powerful than monkey patching since you can patch only small parts of functions instead of fully replacing them, access non exported/local variables and even replace constants (like in the aforementioned experiments patch!)
|
||||||
|
|
||||||
## Installing
|
## Installing / Uninstalling
|
||||||
|
|
||||||
|
Read [Megu's Installation Guide!](docs/1_INSTALLING.md)
|
||||||
|
|
||||||
|
## Installing on Browser
|
||||||
|
|
||||||
|
Run the same commands as in the regular install method. Now run
|
||||||
|
|
||||||
Make sure you have NodeJs and git installed. I will be using pnpm, you can use npm instead
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/Vendicated/Vencord
|
pnpm buildWeb
|
||||||
cd Vencord
|
|
||||||
pnpm i
|
|
||||||
pnpm build
|
|
||||||
```
|
```
|
||||||
The builds are now in the dist/ folder (Vencord/dist).
|
|
||||||
Now install with either the powershell/bash script or use [X1nto's installer](https://github.com/X1nto/VencordInstaller/releases/latest)
|
You will find the built extension at dist/extension.zip. Now just install this extension in your Browser
|
||||||
|
|
||||||
|
## Installing Plugins
|
||||||
|
|
||||||
|
Vencord comes with a bunch of plugins out of the box!
|
||||||
|
However, if you want to install your own ones, create a `userplugins` folder in the `src` directory and create or clone your plugins in there.
|
||||||
|
Don't forget to rebuild!
|
||||||
|
|
||||||
|
Want to learn how to create your own plugin, and maybe PR it into Vencord? See the [Contributing](#contributing) section below!
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](CONTRIBUTING.md) and [Megu's Plugin Guide!](docs/2_PLUGINS.md)
|
||||||
|
|
||||||
[contribute]: CONTRIBUTING.md
|
[contribute]: CONTRIBUTING.md
|
||||||
|
|
||||||
[contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute]
|
[contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute] [contribute]
|
||||||
|
|
||||||
## Join
|
## Join
|
||||||
|
|
||||||
[join]: https://discord.gg/D9uwnFnqmd
|
[join]: https://discord.gg/D9uwnFnqmd
|
||||||
|
|
||||||
[join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join]
|
[join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join] [join]
|
||||||
|
21
browser/Vencord.ts
Normal file
21
browser/Vencord.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
/*!
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./VencordNativeStub";
|
||||||
|
|
||||||
|
export * from "../src/Vencord";
|
66
browser/VencordNativeStub.ts
Normal file
66
browser/VencordNativeStub.ts
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import IpcEvents from "../src/utils/IpcEvents";
|
||||||
|
import * as DataStore from "../src/api/DataStore";
|
||||||
|
|
||||||
|
// Discord deletes this so need to store in variable
|
||||||
|
const { localStorage } = window;
|
||||||
|
|
||||||
|
// listeners for ipc.on
|
||||||
|
const listeners = {} as Record<string, Set<Function>>;
|
||||||
|
|
||||||
|
const handlers = {
|
||||||
|
[IpcEvents.GET_REPO]: () => "https://github.com/Vendicated/Vencord", // shrug
|
||||||
|
[IpcEvents.GET_SETTINGS_DIR]: () => "LocalStorage",
|
||||||
|
|
||||||
|
[IpcEvents.GET_QUICK_CSS]: () => DataStore.get("VencordQuickCss").then(s => s ?? ""),
|
||||||
|
[IpcEvents.SET_QUICK_CSS]: (css: string) => {
|
||||||
|
DataStore.set("VencordQuickCss", css);
|
||||||
|
listeners[IpcEvents.QUICK_CSS_UPDATE]?.forEach(l => l(null, css));
|
||||||
|
},
|
||||||
|
|
||||||
|
[IpcEvents.GET_SETTINGS]: () => localStorage.getItem("VencordSettings") || "{}",
|
||||||
|
[IpcEvents.SET_SETTINGS]: (s: string) => localStorage.setItem("VencordSettings", s),
|
||||||
|
|
||||||
|
[IpcEvents.GET_UPDATES]: () => ({ ok: true, value: [] }),
|
||||||
|
|
||||||
|
[IpcEvents.OPEN_EXTERNAL]: (url: string) => open(url, "_blank"),
|
||||||
|
};
|
||||||
|
|
||||||
|
function onEvent(event: string, ...args: any[]) {
|
||||||
|
const handler = handlers[event];
|
||||||
|
if (!handler) throw new Error(`Event ${event} not implemented.`);
|
||||||
|
return handler(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
// probably should make this less cursed at some point
|
||||||
|
window.VencordNative = {
|
||||||
|
getVersions: () => ({}),
|
||||||
|
ipc: {
|
||||||
|
send: (event: string, ...args: any[]) => void onEvent(event, ...args),
|
||||||
|
sendSync: onEvent,
|
||||||
|
on(event: string, listener: () => {}) {
|
||||||
|
(listeners[event] ??= new Set()).add(listener);
|
||||||
|
},
|
||||||
|
off(event: string, listener: () => {}) {
|
||||||
|
return listeners[event]?.delete(listener);
|
||||||
|
},
|
||||||
|
invoke: (event: string, ...args: any[]) => Promise.resolve(onEvent(event, ...args))
|
||||||
|
},
|
||||||
|
};
|
24
browser/background.js
Normal file
24
browser/background.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
if (typeof browser === "undefined") {
|
||||||
|
var browser = chrome;
|
||||||
|
}
|
||||||
|
|
||||||
|
browser.webRequest.onHeadersReceived.addListener(({ responseHeaders, url }) => {
|
||||||
|
const cspIdx = responseHeaders.findIndex(h => h.name === "content-security-policy");
|
||||||
|
if (cspIdx !== -1)
|
||||||
|
responseHeaders.splice(cspIdx, 1);
|
||||||
|
|
||||||
|
if (url.endsWith(".css")) {
|
||||||
|
const contentType = responseHeaders.find(h => h.name === "content-type");
|
||||||
|
if (contentType)
|
||||||
|
contentType.value = "text/css";
|
||||||
|
else
|
||||||
|
responseHeaders.push({
|
||||||
|
name: "content-type",
|
||||||
|
value: "text/json"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
responseHeaders
|
||||||
|
};
|
||||||
|
}, { urls: ["*://*.discord.com/*"] }, ["blocking", "responseHeaders"]);
|
8
browser/content.js
Normal file
8
browser/content.js
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
if (typeof browser === "undefined") {
|
||||||
|
var browser = chrome;
|
||||||
|
}
|
||||||
|
|
||||||
|
var script = document.createElement("script");
|
||||||
|
script.src = browser.runtime.getURL("dist/Vencord.js");
|
||||||
|
// documentElement because we load before body/head are ready
|
||||||
|
document.documentElement.appendChild(script);
|
32
browser/manifest.json
Normal file
32
browser/manifest.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 2,
|
||||||
|
"name": "Vencord Web",
|
||||||
|
"description": "Yeee",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"author": "Vendicated",
|
||||||
|
"homepage_url": "https://github.com/Vendicated/Vencord",
|
||||||
|
"background": {
|
||||||
|
"scripts": [
|
||||||
|
"background.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"content_scripts": [
|
||||||
|
{
|
||||||
|
"run_at": "document_start",
|
||||||
|
"matches": [
|
||||||
|
"*://*.discord.com/*"
|
||||||
|
],
|
||||||
|
"js": [
|
||||||
|
"content.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"permissions": [
|
||||||
|
"*://*.discord.com/*",
|
||||||
|
"webRequest",
|
||||||
|
"webRequestBlocking"
|
||||||
|
],
|
||||||
|
"web_accessible_resources": [
|
||||||
|
"dist/Vencord.js"
|
||||||
|
]
|
||||||
|
}
|
24
browser/userscript.meta.js
Normal file
24
browser/userscript.meta.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// ==UserScript==
|
||||||
|
// @name Vencord
|
||||||
|
// @description A Discord client mod - Web version
|
||||||
|
// @version %version%
|
||||||
|
// @author Vendicated (https://github.com/Vendicated)
|
||||||
|
// @namespace https://github.com/Vendicated/Vencord
|
||||||
|
// @supportURL https://github.com/Vendicated/Vencord
|
||||||
|
// @license GPL-3.0
|
||||||
|
// @match *://*.discord.com/*
|
||||||
|
// @grant none
|
||||||
|
// @run-at document-start
|
||||||
|
// @compatible chrome Chrome + Tampermonkey or Violentmonkey
|
||||||
|
// @compatible firefox Firefox Tampermonkey
|
||||||
|
// @compatible opera Opera + Tampermonkey or Violentmonkey
|
||||||
|
// @compatible edge Edge + Tampermonkey or Violentmonkey
|
||||||
|
// @compatible safari Safari + Tampermonkey or Violentmonkey
|
||||||
|
// ==/UserScript==
|
||||||
|
|
||||||
|
|
||||||
|
// this UserScript DOES NOT work on Firefox with Violentmonkey or Greasemonkey due to a bug that makes it impossible
|
||||||
|
// to overwrite stuff on the window on sites that use CSP. Use Tampermonkey or use a chromium based browser
|
||||||
|
// https://github.com/violentmonkey/violentmonkey/issues/997
|
||||||
|
|
||||||
|
// this is a compiled and minified version of Vencord. For the source code, visit the GitHub repo
|
123
build.mjs
Executable file → Normal file
123
build.mjs
Executable file → Normal file
@ -1,122 +1,3 @@
|
|||||||
#!/usr/bin/node
|
// FIXME: Delete this soon, for now it is needed so people can update
|
||||||
import { execSync } from "child_process";
|
|
||||||
import esbuild from "esbuild";
|
|
||||||
import { readdirSync } from "fs";
|
|
||||||
import { performance } from "perf_hooks";
|
|
||||||
|
|
||||||
/**
|
import("./scripts/build/build.mjs");
|
||||||
* @type {esbuild.WatchMode|false}
|
|
||||||
*/
|
|
||||||
const watch = process.argv.includes("--watch");
|
|
||||||
|
|
||||||
// https://github.com/evanw/esbuild/issues/619#issuecomment-751995294
|
|
||||||
/**
|
|
||||||
* @type {esbuild.Plugin}
|
|
||||||
*/
|
|
||||||
const makeAllPackagesExternalPlugin = {
|
|
||||||
name: 'make-all-packages-external',
|
|
||||||
setup(build) {
|
|
||||||
let filter = /^[^.\/]|^\.[^.\/]|^\.\.[^\/]/; // Must not start with "/" or "./" or "../"
|
|
||||||
build.onResolve({ filter }, args => ({ path: args.path, external: true }));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {esbuild.Plugin}
|
|
||||||
*/
|
|
||||||
const globPlugins = {
|
|
||||||
name: "glob-plugins",
|
|
||||||
setup: build => {
|
|
||||||
build.onResolve({ filter: /^plugins$/ }, args => {
|
|
||||||
return {
|
|
||||||
namespace: "import-plugins",
|
|
||||||
path: args.path
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
build.onLoad({ filter: /^plugins$/, namespace: "import-plugins" }, () => {
|
|
||||||
const files = readdirSync("./src/plugins");
|
|
||||||
let code = "";
|
|
||||||
let obj = "";
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
|
||||||
if (files[i] === "index.ts") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const mod = `__pluginMod${i}`;
|
|
||||||
code += `import ${mod} from "./${files[i].replace(/.tsx?$/, "")}";\n`;
|
|
||||||
obj += `[${mod}.name]: ${mod},`;
|
|
||||||
}
|
|
||||||
code += `export default {${obj}}`;
|
|
||||||
return {
|
|
||||||
contents: code,
|
|
||||||
resolveDir: "./src/plugins"
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const gitHash = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
|
|
||||||
/**
|
|
||||||
* @type {esbuild.Plugin}
|
|
||||||
*/
|
|
||||||
const gitHashPlugin = {
|
|
||||||
name: "git-hash-plugin",
|
|
||||||
setup: build => {
|
|
||||||
const filter = /^git-hash$/;
|
|
||||||
build.onResolve({ filter }, args => ({
|
|
||||||
namespace: "git-hash", path: args.path
|
|
||||||
}));
|
|
||||||
build.onLoad({ filter, namespace: "git-hash" }, () => ({
|
|
||||||
contents: `export default "${gitHash}"`
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
await Promise.all([
|
|
||||||
esbuild.build({
|
|
||||||
logLevel: "info",
|
|
||||||
entryPoints: ["src/preload.ts"],
|
|
||||||
outfile: "dist/preload.js",
|
|
||||||
format: "cjs",
|
|
||||||
bundle: true,
|
|
||||||
platform: "node",
|
|
||||||
target: ["esnext"],
|
|
||||||
sourcemap: "linked",
|
|
||||||
plugins: [makeAllPackagesExternalPlugin],
|
|
||||||
watch
|
|
||||||
}),
|
|
||||||
esbuild.build({
|
|
||||||
logLevel: "info",
|
|
||||||
entryPoints: ["src/patcher.ts"],
|
|
||||||
outfile: "dist/patcher.js",
|
|
||||||
bundle: true,
|
|
||||||
format: "cjs",
|
|
||||||
target: ["esnext"],
|
|
||||||
external: ["electron"],
|
|
||||||
platform: "node",
|
|
||||||
sourcemap: "linked",
|
|
||||||
plugins: [makeAllPackagesExternalPlugin],
|
|
||||||
watch
|
|
||||||
}),
|
|
||||||
esbuild.build({
|
|
||||||
logLevel: "info",
|
|
||||||
entryPoints: ["src/Vencord.ts"],
|
|
||||||
outfile: "dist/renderer.js",
|
|
||||||
format: "iife",
|
|
||||||
bundle: true,
|
|
||||||
target: ["esnext"],
|
|
||||||
footer: { js: "//# sourceURL=VencordRenderer" },
|
|
||||||
globalName: "Vencord",
|
|
||||||
external: ["plugins", "git-hash"],
|
|
||||||
plugins: [
|
|
||||||
globPlugins,
|
|
||||||
gitHashPlugin
|
|
||||||
],
|
|
||||||
sourcemap: false,
|
|
||||||
watch,
|
|
||||||
minify: true,
|
|
||||||
})
|
|
||||||
]).catch(err => {
|
|
||||||
console.error("Build failed");
|
|
||||||
console.error(err.message);
|
|
||||||
});
|
|
||||||
|
200
docs/1_INSTALLING.md
Normal file
200
docs/1_INSTALLING.md
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
# 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)
|
||||||
|
- [Manually Installing Vencord](#manually-installing-vencord)
|
||||||
|
- [On Windows](#on-windows)
|
||||||
|
- [On Linux](#on-linux)
|
||||||
|
- [On MacOS](#on-macos)
|
||||||
|
- [Manual Patching](#manual-patching)
|
||||||
|
- [Manually Uninstalling Vencord](#manually-uninstalling-vencord)
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
- Install Git from https://git-scm.com/download
|
||||||
|
- Install Node.JS LTS from here: https://nodejs.dev/en/
|
||||||
|
|
||||||
|
## Installing Vencord
|
||||||
|
|
||||||
|
> :exclamation: If this doesn't work, see [Manually Installing Vencord](#manually-installing-vencord)
|
||||||
|
|
||||||
|
Install `pnpm`:
|
||||||
|
|
||||||
|
> :exclamation: this may need to be run as admin depending on your system, and you may need to close and reopen your terminal.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm i -g pnpm
|
||||||
|
```
|
||||||
|
|
||||||
|
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 neccessary 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
|
||||||
|
```
|
||||||
|
|
||||||
|
The above command may ask you to also run:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
pnpm install --frozen-lockfile
|
||||||
|
pnpm uninject
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manually Installing Vencord
|
||||||
|
|
||||||
|
- [Windows](#on-windows)
|
||||||
|
- [Linux](#on-linux)
|
||||||
|
- [MacOS](#on-macos)
|
||||||
|
|
||||||
|
### On Windows
|
||||||
|
|
||||||
|
Press Win+R and enter: `%LocalAppData%` and hit enter. In this page, find the page (Discord, DiscordPTB, DiscordCanary, etc) that you want to patch.
|
||||||
|
|
||||||
|
Now follow the instructions at [Manual Patching](#manual-patching)
|
||||||
|
|
||||||
|
### On Linux
|
||||||
|
|
||||||
|
The Discord folder is usually in one of the following paths:
|
||||||
|
|
||||||
|
- /usr/share
|
||||||
|
- /usr/lib64
|
||||||
|
- /opt
|
||||||
|
- /home/$USER/.local/share
|
||||||
|
|
||||||
|
If you use flatpak, it will usually be in one of the following paths:
|
||||||
|
|
||||||
|
- /var/lib/flatpak/app/com.discordapp.Discord/current/active/files
|
||||||
|
- /home/$USER/.local/share/flatpak/app/com.discordapp.Discord/current/active/files
|
||||||
|
|
||||||
|
You will need to give flatpak access to vencord with one of the following commands:
|
||||||
|
|
||||||
|
> :exclamation: If not on stable, replace `com.discordapp.Discord` with your branch name, e.g., `com.discordapp.DiscordCanary`
|
||||||
|
|
||||||
|
> :exclamation: Replace `/path/to/vencord/` with the path to your vencord folder (NOT the dist folder)
|
||||||
|
|
||||||
|
If Discord flatpak install is in /home/:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
flatpak override --user com.discordapp.Discord --filesystem="/path/to/vencord/"
|
||||||
|
```
|
||||||
|
|
||||||
|
If Discord flatpak install not in /home/:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
sudo flatpak override com.discordapp.Discord --filesystem="/path/to/vencord"
|
||||||
|
```
|
||||||
|
|
||||||
|
Now follow the instructions at [Manual Patching](#manual-patching)
|
||||||
|
|
||||||
|
### On MacOS
|
||||||
|
|
||||||
|
Open finder and go to your Applications folder. Right-Click on the Discord application you want to patch, and view contents.
|
||||||
|
|
||||||
|
Go to the `Contents/Resources` folder.
|
||||||
|
|
||||||
|
Now follow the instructions at [Manual Patching](#manual-patching)
|
||||||
|
|
||||||
|
### Manual Patching
|
||||||
|
|
||||||
|
> :exclamation: If using Flatpak on linux, go to the folder that contains the `app.asar` file, and skip to where we create the `app` folder below.
|
||||||
|
|
||||||
|
> :exclamation: On Linux/MacOS, there's a chance there won't be an `app-<number>` folder, but there probably is a `resources` folder, so keep reading :)
|
||||||
|
|
||||||
|
Inside there, look for the `app-<number>` folders. If you have multiple, use the highest number. If that doesn't work, do it for the rest of the `app-<number>` folders.
|
||||||
|
|
||||||
|
Inside there, go to the `resources` folder. There should be a file called `app.asar`. If there isn't, look at a different `app-<number>` folder instead.
|
||||||
|
|
||||||
|
Make a new folder in `resources` called `app`. In here, we will make two files:
|
||||||
|
|
||||||
|
`package.json` and `index.js`
|
||||||
|
|
||||||
|
In `index.js`:
|
||||||
|
|
||||||
|
> :exclamation: Replace the path in the first line with the path to `patcher.js` in your vencord dist folder.
|
||||||
|
> On Windows, you can get this by shift-rightclicking the patcher.js file and selecting "copy as path"
|
||||||
|
|
||||||
|
```js
|
||||||
|
require("C:/Users/<your user>/path/to/vencord/dist/patcher.js");
|
||||||
|
require("../app.asar");
|
||||||
|
```
|
||||||
|
|
||||||
|
And in `package.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "name": "discord", "main": "index.js" }
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, fully close & reopen your Discord client and check to see that `Vencord` appears in settings!
|
||||||
|
|
||||||
|
### Manually Uninstalling Vencord
|
||||||
|
|
||||||
|
> :exclamation: Do not delete `app.asar` - Only delete the `app` folder we created.
|
||||||
|
|
||||||
|
Use the instructions above to find the `app` folder, and delete it. Then Close & Reopen Discord.
|
||||||
|
|
||||||
|
If you need more help, ask in the support channel in our [Discord Server](https://discord.gg/D9uwnFnqmd).
|
111
docs/2_PLUGINS.md
Normal file
111
docs/2_PLUGINS.md
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
# Plugins Guide
|
||||||
|
|
||||||
|
Welcome to Megu's Plugin Guide! In this file, you will learn about how to write your own plugin!
|
||||||
|
|
||||||
|
You don't need to run `pnpm build` every time you make a change. Instead, use `pnpm watch` - this will auto-compile Vencord whenever you make a change. If using code patches (recommended), you will need to CTRL+R to load the changes.
|
||||||
|
|
||||||
|
## Plugin Entrypoint
|
||||||
|
|
||||||
|
> If it doesn't already exist, create a folder called `userplugins` in the `src` directory of this repo.
|
||||||
|
|
||||||
|
1. Create a folder in `src/userplugins/` with the name of your plugin. For example, `src/userplugins/epicPlugin/` - All of your plugin files will go here.
|
||||||
|
|
||||||
|
2. Create a file in that folder called `index.ts`
|
||||||
|
|
||||||
|
3. In `index.ts`, copy-paste the following template code:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import definePlugin from "../../utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "Epic Plugin",
|
||||||
|
description: "This plugin is absolutely epic",
|
||||||
|
authors: [
|
||||||
|
{
|
||||||
|
id: 12345n,
|
||||||
|
name: "Your Name",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
patches: [],
|
||||||
|
// Delete these two below if you are only using code patches
|
||||||
|
start() {},
|
||||||
|
stop() {},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Change the name, description, and authors to your own information.
|
||||||
|
|
||||||
|
Replace `12345n` with your user ID ending in `n` (e.g., `545581357812678656n`). If you don't want to share your Discord account, use `0n` instead!
|
||||||
|
|
||||||
|
## How Plugins Work In Vencord
|
||||||
|
|
||||||
|
Vencord uses a different way of making mods than you're used to.
|
||||||
|
Instead of monkeypatching webpack, we directly modify the code before Discord loads it.
|
||||||
|
|
||||||
|
This is _significantly_ more efficient than monkeypatching webpack, and is surprisingly easy, but it may be confusing at first.
|
||||||
|
|
||||||
|
## Making your patch
|
||||||
|
|
||||||
|
For an in-depth guide into patching code, see [CONTRIBUTING.md](../CONTRIBUTING.md)
|
||||||
|
|
||||||
|
in the `index.ts` file we made earlier, you'll see a `patches` array.
|
||||||
|
|
||||||
|
> You'll see examples of how patches are used in all the existing plugins, and it'll be easier to understand by looking at those examples, so do that first, and then return here!
|
||||||
|
|
||||||
|
> For a good example of a plugin using code patches AND runtime patching, check `src/plugins/unindent.ts`, which uses code patches to run custom runtime code.
|
||||||
|
|
||||||
|
One of the patches in the `isStaff` plugin, looks like this:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
match: /(\w+)\.isStaff=function\(\){return\s*!1};/,
|
||||||
|
replace: "$1.isStaff=function(){return true};",
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
The above regex matches the string in discord that will look something like:
|
||||||
|
|
||||||
|
```js
|
||||||
|
abc.isStaff = function () {
|
||||||
|
return !1;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
Remember that Discord code is minified, so there won't be any newlines, and there will only be spaces where necessary. So the source code looks something like:
|
||||||
|
|
||||||
|
```
|
||||||
|
abc.isStaff=function(){return!1;}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can find these snippets by opening the devtools (`ctrl+shift+i`) and pressing `ctrl+shift+f`, searching for what you're looking to modify in there, and beautifying the file to make it more readable.
|
||||||
|
|
||||||
|
In the `match` regex in the example shown above, you'll notice at the start there is a `(\w+)`.
|
||||||
|
Anything in the brackets will be accessible in the `replace` string using `$<number>`. e.g., the first pair of brackets will be `$1`, the second will be `$2`, etc.
|
||||||
|
|
||||||
|
The replacement string we used is:
|
||||||
|
|
||||||
|
```
|
||||||
|
"$1.isStaff=function(){return true;};"
|
||||||
|
```
|
||||||
|
|
||||||
|
Which, using the above example, would replace the code with:
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> In this example, `$1` becomes `abc`
|
||||||
|
|
||||||
|
```js
|
||||||
|
abc.isStaff = function () {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The match value _can_ be a string, rather than regex, however usually regex will be better suited, as it can work with unknown values, whereas strings must be exact matches.
|
||||||
|
|
||||||
|
Once you've made your plugin, make sure you run `pnpm test` and make sure your code is nice and clean!
|
||||||
|
|
||||||
|
If you want to publish your plugin into the Vencord repo, move your plugin from `src/userplugins` into the `src/plugins` folder and open a PR!
|
||||||
|
|
||||||
|
> **Warning**
|
||||||
|
> Make sure you've read [CONTRIBUTING.md](../CONTRIBUTING.md) before opening a PR
|
||||||
|
|
||||||
|
If you need more help, ask in the support channel in our [Discord Server](https://discord.gg/D9uwnFnqmd).
|
90
install.ps1
90
install.ps1
@ -1,90 +0,0 @@
|
|||||||
# Vencord Windows Installer
|
|
||||||
|
|
||||||
$patcher = "$PWD\dist\patcher.js"
|
|
||||||
$patcher_safe = $patcher -replace '\\', '\\'
|
|
||||||
|
|
||||||
$APP_PATCH = @"
|
|
||||||
require("$patcher_safe");
|
|
||||||
require("../app.asar");
|
|
||||||
"@
|
|
||||||
|
|
||||||
$PACKAGE_JSON = @"
|
|
||||||
{
|
|
||||||
"main": "index.js",
|
|
||||||
"name": "discord"
|
|
||||||
}
|
|
||||||
"@
|
|
||||||
|
|
||||||
$branch_paths = Get-ChildItem -Directory -Path $env:LOCALAPPDATA |
|
|
||||||
Select-String -Pattern "Discord\w*" -AllMatches |
|
|
||||||
Select-String -Pattern "DiscordGames" -NotMatch # Ignore DiscordGames folder
|
|
||||||
|
|
||||||
$branches = @()
|
|
||||||
|
|
||||||
foreach ($branch in $branch_paths) {
|
|
||||||
$branch = $branch.Line.Split("\")[-1]
|
|
||||||
|
|
||||||
if ($branch -eq "Discord") {
|
|
||||||
$branch = "Discord Stable"
|
|
||||||
} else {
|
|
||||||
$branch = $branch.Replace("Discord", "Discord ")
|
|
||||||
}
|
|
||||||
|
|
||||||
$branches = $branches + $branch
|
|
||||||
}
|
|
||||||
|
|
||||||
$branch_count = $branches.Count
|
|
||||||
|
|
||||||
Write-Output "Found $branch_count Branches"
|
|
||||||
Write-Output "====================================="
|
|
||||||
Write-Output "===== Select a Branch to patch ======"
|
|
||||||
|
|
||||||
$i = 0
|
|
||||||
foreach ($branch in $branches) {
|
|
||||||
Write-Output "=== $i. $branch"
|
|
||||||
$i++
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Output "====================================="
|
|
||||||
$pos = Read-Host "Enter a number"
|
|
||||||
|
|
||||||
if ($null -eq $branches[$pos]) {
|
|
||||||
Write-Output "Invalid branch selection"
|
|
||||||
exit
|
|
||||||
}
|
|
||||||
|
|
||||||
$branch = $branches.Get($pos)
|
|
||||||
$discord_root = $branch_paths.Get($pos)
|
|
||||||
|
|
||||||
Write-Output "`nPatching $branch"
|
|
||||||
|
|
||||||
$app_folders = Get-ChildItem -Directory -Path $discord_root |
|
|
||||||
Select-String -Pattern "app-"
|
|
||||||
|
|
||||||
foreach ($folder in $app_folders)
|
|
||||||
{
|
|
||||||
$version = [regex]::match($folder, 'app-([\d\.]+)').Groups[1].Value
|
|
||||||
Write-Output "Patching Version $version"
|
|
||||||
|
|
||||||
$resources = "$folder\resources"
|
|
||||||
if (-not(Test-Path -Path "$resources")) {
|
|
||||||
Write-Error "Resources folder does not exist. Outdated version?`n"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if (-not(Test-Path -Path "$resources\app.asar")) {
|
|
||||||
Write-Error "Failed to find app.asar in $folder`n"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
$app = "$resources\app"
|
|
||||||
if (Test-Path -Path $app) {
|
|
||||||
Write-Error "Are you already patched? App folder already exists at $resources`n"
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
$null = New-Item -Path $app -ItemType Directory
|
|
||||||
$null = Tee-Object -InputObject $APP_PATCH -FilePath "$app\index.js"
|
|
||||||
$null = Tee-Object -InputObject $PACKAGE_JSON -FilePath "$app\package.json"
|
|
||||||
|
|
||||||
Write-Output "Patched $branch (version $version) successfully"
|
|
||||||
}
|
|
75
install.sh
75
install.sh
@ -1,75 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
#
|
|
||||||
# Super simple installer. You should probably run this as root.
|
|
||||||
# If you are getting permission issues, this is probably why.
|
|
||||||
#
|
|
||||||
# If this doesn't work for you, or you're not on Linux, just
|
|
||||||
# - locate your Discord folder
|
|
||||||
# - inside the resources folder, create a new folder "app"
|
|
||||||
# - inside app create the files index.js and package.json.
|
|
||||||
# See the two tee commands at the end of the file for their contents
|
|
||||||
|
|
||||||
patcher="$PWD/dist/patcher.js"
|
|
||||||
|
|
||||||
discord_bin="$(which discord)"
|
|
||||||
discord_actual="$(readlink "$discord_bin")"
|
|
||||||
|
|
||||||
if [ -z "$discord_actual" ]; then
|
|
||||||
case "$(head -n1 "$discord_bin")" in
|
|
||||||
# has shebang?
|
|
||||||
\#!/*)
|
|
||||||
# Wrapper script, assume 2nd line has exec electron call and try to match asar path
|
|
||||||
path="$(tail -1 "$discord_bin" | grep -Eo "\S+/app.asar" | sed 's/${name}/discord/')"
|
|
||||||
if [ -z "$path" ]; then
|
|
||||||
echo "Unsupported Install. $discord_bin is wrapper script but last line isn't exec call?"
|
|
||||||
exit
|
|
||||||
elif [ -e "$path" ]; then
|
|
||||||
discord="$(dirname "$path")"
|
|
||||||
else
|
|
||||||
echo "Unsupported Install. $path not found"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Unsupported Install. $discord_bin is neither symlink nor a wrapper script.";
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
else
|
|
||||||
discord="$(dirname "$discord_actual")"
|
|
||||||
fi
|
|
||||||
|
|
||||||
resources="$discord/resources"
|
|
||||||
app="$resources/app"
|
|
||||||
app_asar="app.asar"
|
|
||||||
|
|
||||||
if [ ! -e "$resources" ]; then
|
|
||||||
if [ -e "$discord/app.asar.unpacked" ]; then
|
|
||||||
# System Electron Install
|
|
||||||
mv "$discord/app.asar" "$discord/_app.asar"
|
|
||||||
mv "$discord/app.asar.unpacked" "$discord/_app.asar.unpacked"
|
|
||||||
app="$discord/app.asar"
|
|
||||||
app_asar="_app.asar"
|
|
||||||
else
|
|
||||||
echo "Unsupported Install. $discord has no resources folder but also isn't system electron install"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -e "$app" ]; then
|
|
||||||
echo "app folder exists. Looks like your Discord is already modified."
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir "$app"
|
|
||||||
tee > "$app/index.js" << EOF
|
|
||||||
require("$patcher");
|
|
||||||
require("../$app_asar");
|
|
||||||
EOF
|
|
||||||
|
|
||||||
tee > "$app/package.json" << EOF
|
|
||||||
{
|
|
||||||
"main": "index.js",
|
|
||||||
"name": "discord"
|
|
||||||
}
|
|
||||||
EOF
|
|
68
package.json
68
package.json
@ -1,19 +1,51 @@
|
|||||||
{
|
{
|
||||||
"devDependencies": {
|
"name": "vencord",
|
||||||
"@types/flux": "^3.1.11",
|
"private": "true",
|
||||||
"@types/node": "^18.7.13",
|
"version": "1.0.0",
|
||||||
"@types/react": "^18.0.17",
|
"description": "A Discord client mod that does things differently",
|
||||||
"electron": "^20.1.0",
|
"keywords": [],
|
||||||
"esbuild": "^0.15.5"
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
},
|
"bugs": {
|
||||||
"dependencies": {
|
"url": "https://github.com/Vendicated/Vencord/issues"
|
||||||
"discord-types": "^1.3.26",
|
},
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"repository": {
|
||||||
"jsposed": "^1.0.2",
|
"type": "git",
|
||||||
"prettier": "^2.7.1"
|
"url": "git+https://github.com/Vendicated/Vencord.git"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"license": "GPL-3.0",
|
||||||
"build": "node build.mjs",
|
"author": "Vendicated",
|
||||||
"watch": "node build.mjs --watch"
|
"directories": {
|
||||||
}
|
"doc": "docs"
|
||||||
}
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "node scripts/build/build.mjs",
|
||||||
|
"buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs",
|
||||||
|
"inject": "node scripts/patcher/install.js",
|
||||||
|
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
|
||||||
|
"lint:fix": "pnpm lint --fix",
|
||||||
|
"test": "pnpm lint && pnpm build && pnpm testTsc",
|
||||||
|
"testTsc": "tsc --noEmit",
|
||||||
|
"uninject": "node scripts/patcher/uninstall.js",
|
||||||
|
"watch": "node scripts/build/build.mjs --watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"console-menu": "^0.1.0",
|
||||||
|
"fflate": "^0.7.4"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^18.7.13",
|
||||||
|
"@types/react": "^18.0.17",
|
||||||
|
"@types/yazl": "^2.4.2",
|
||||||
|
"@typescript-eslint/parser": "^5.39.0",
|
||||||
|
"discord-types": "^1.3.26",
|
||||||
|
"esbuild": "^0.15.5",
|
||||||
|
"eslint": "^8.24.0",
|
||||||
|
"eslint-plugin-header": "^3.1.1",
|
||||||
|
"eslint-plugin-simple-import-sort": "^8.0.0",
|
||||||
|
"eslint-plugin-unused-imports": "^2.0.0",
|
||||||
|
"standalone-electron-types": "^1.0.0",
|
||||||
|
"type-fest": "^3.1.0",
|
||||||
|
"typescript": "^4.8.4"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@7.13.4"
|
||||||
|
}
|
||||||
|
1260
pnpm-lock.yaml
generated
1260
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
94
scripts/build/build.mjs
Executable file
94
scripts/build/build.mjs
Executable file
@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/node
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import esbuild from "esbuild";
|
||||||
|
|
||||||
|
import { commonOpts, gitHash, globPlugins, isStandalone } from "./common.mjs";
|
||||||
|
|
||||||
|
const defines = {
|
||||||
|
IS_STANDALONE: isStandalone
|
||||||
|
};
|
||||||
|
if (defines.IS_STANDALONE === "false")
|
||||||
|
// If this is a local build (not standalone), optimise
|
||||||
|
// for the specific platform we're on
|
||||||
|
defines["process.platform"] = JSON.stringify(process.platform);
|
||||||
|
|
||||||
|
const header = `
|
||||||
|
// Vencord ${gitHash}
|
||||||
|
// Standalone: ${defines.IS_STANDALONE}
|
||||||
|
// Platform: ${defines["process.platform"] || "Universal"}
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {esbuild.BuildOptions}
|
||||||
|
*/
|
||||||
|
const nodeCommonOpts = {
|
||||||
|
...commonOpts,
|
||||||
|
format: "cjs",
|
||||||
|
platform: "node",
|
||||||
|
target: ["esnext"],
|
||||||
|
minify: true,
|
||||||
|
bundle: true,
|
||||||
|
external: ["electron", ...commonOpts.external],
|
||||||
|
define: defines,
|
||||||
|
banner: {
|
||||||
|
js: header
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
esbuild.build({
|
||||||
|
...nodeCommonOpts,
|
||||||
|
entryPoints: ["src/preload.ts"],
|
||||||
|
outfile: "dist/preload.js",
|
||||||
|
footer: { js: "//# sourceURL=VencordPreload\n//# sourceMappingURL=vencord://preload.js.map" },
|
||||||
|
sourcemap: "external",
|
||||||
|
}),
|
||||||
|
esbuild.build({
|
||||||
|
...nodeCommonOpts,
|
||||||
|
entryPoints: ["src/patcher.ts"],
|
||||||
|
outfile: "dist/patcher.js",
|
||||||
|
footer: { js: "//# sourceURL=VencordPatcher\n//# sourceMappingURL=vencord://patcher.js.map" },
|
||||||
|
sourcemap: "external",
|
||||||
|
}),
|
||||||
|
esbuild.build({
|
||||||
|
...commonOpts,
|
||||||
|
entryPoints: ["src/Vencord.ts"],
|
||||||
|
outfile: "dist/renderer.js",
|
||||||
|
format: "iife",
|
||||||
|
target: ["esnext"],
|
||||||
|
footer: { js: "//# sourceURL=VencordRenderer\n//# sourceMappingURL=vencord://renderer.js.map" },
|
||||||
|
globalName: "Vencord",
|
||||||
|
sourcemap: "external",
|
||||||
|
plugins: [
|
||||||
|
globPlugins,
|
||||||
|
...commonOpts.plugins
|
||||||
|
],
|
||||||
|
define: {
|
||||||
|
IS_WEB: "false",
|
||||||
|
IS_STANDALONE: isStandalone
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
]).catch(err => {
|
||||||
|
console.error("Build failed");
|
||||||
|
console.error(err.message);
|
||||||
|
// make ci fail
|
||||||
|
if (!commonOpts.watch)
|
||||||
|
process.exitCode = 1;
|
||||||
|
});
|
90
scripts/build/buildWeb.mjs
Executable file
90
scripts/build/buildWeb.mjs
Executable file
@ -0,0 +1,90 @@
|
|||||||
|
#!/usr/bin/node
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
import esbuild from "esbuild";
|
||||||
|
import { zip } from "fflate";
|
||||||
|
import { readFileSync, writeFileSync } from "fs";
|
||||||
|
import { readFile } from "fs/promises";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
// wtf is this assert syntax
|
||||||
|
import PackageJSON from "../../package.json" assert { type: "json" };
|
||||||
|
import { commonOpts, fileIncludePlugin, gitHashPlugin, gitRemotePlugin, globPlugins } from "./common.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {esbuild.BuildOptions}
|
||||||
|
*/
|
||||||
|
const commonOptions = {
|
||||||
|
...commonOpts,
|
||||||
|
entryPoints: ["browser/Vencord.ts"],
|
||||||
|
globalName: "Vencord",
|
||||||
|
format: "iife",
|
||||||
|
external: ["plugins", "git-hash"],
|
||||||
|
plugins: [
|
||||||
|
globPlugins,
|
||||||
|
gitHashPlugin,
|
||||||
|
gitRemotePlugin,
|
||||||
|
fileIncludePlugin
|
||||||
|
],
|
||||||
|
target: ["esnext"],
|
||||||
|
define: {
|
||||||
|
IS_WEB: "true",
|
||||||
|
IS_STANDALONE: "true"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
[
|
||||||
|
esbuild.build({
|
||||||
|
...commonOptions,
|
||||||
|
outfile: "dist/browser.js",
|
||||||
|
footer: { js: "//# sourceURL=VencordWeb" },
|
||||||
|
}),
|
||||||
|
esbuild.build({
|
||||||
|
...commonOptions,
|
||||||
|
outfile: "dist/Vencord.user.js",
|
||||||
|
banner: {
|
||||||
|
js: readFileSync("browser/userscript.meta.js", "utf-8").replace("%version%", PackageJSON.version)
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
|
||||||
|
js: "Object.defineProperty(window,'Vencord',{get:()=>Vencord});"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
zip({
|
||||||
|
dist: {
|
||||||
|
"Vencord.js": readFileSync("dist/browser.js")
|
||||||
|
},
|
||||||
|
...Object.fromEntries(await Promise.all(["background.js", "content.js", "manifest.json"].map(async f => [
|
||||||
|
f,
|
||||||
|
await readFile(join("browser", f))
|
||||||
|
]))),
|
||||||
|
}, {}, (err, data) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
process.exitCode = 1;
|
||||||
|
} else {
|
||||||
|
writeFileSync("dist/extension.zip", data);
|
||||||
|
console.info("Extension written to dist/extension.zip");
|
||||||
|
}
|
||||||
|
});
|
156
scripts/build/common.mjs
Normal file
156
scripts/build/common.mjs
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { exec, execSync } from "child_process";
|
||||||
|
import esbuild from "esbuild";
|
||||||
|
import { existsSync } from "fs";
|
||||||
|
import { readdir, readFile } from "fs/promises";
|
||||||
|
import { join } from "path";
|
||||||
|
import { promisify } from "util";
|
||||||
|
|
||||||
|
export const watch = process.argv.includes("--watch");
|
||||||
|
export const isStandalone = JSON.stringify(process.argv.includes("--standalone"));
|
||||||
|
|
||||||
|
// https://github.com/evanw/esbuild/issues/619#issuecomment-751995294
|
||||||
|
/**
|
||||||
|
* @type {esbuild.Plugin}
|
||||||
|
*/
|
||||||
|
export const makeAllPackagesExternalPlugin = {
|
||||||
|
name: "make-all-packages-external",
|
||||||
|
setup(build) {
|
||||||
|
const filter = /^[^./]|^\.[^./]|^\.\.[^/]/; // Must not start with "/" or "./" or "../"
|
||||||
|
build.onResolve({ filter }, args => ({ path: args.path, external: true }));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {esbuild.Plugin}
|
||||||
|
*/
|
||||||
|
export const globPlugins = {
|
||||||
|
name: "glob-plugins",
|
||||||
|
setup: build => {
|
||||||
|
const filter = /^~plugins$/;
|
||||||
|
build.onResolve({ filter }, args => {
|
||||||
|
return {
|
||||||
|
namespace: "import-plugins",
|
||||||
|
path: args.path
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
build.onLoad({ filter, namespace: "import-plugins" }, async () => {
|
||||||
|
const pluginDirs = ["plugins", "userplugins"];
|
||||||
|
let code = "";
|
||||||
|
let plugins = "\n";
|
||||||
|
let i = 0;
|
||||||
|
for (const dir of pluginDirs) {
|
||||||
|
if (!existsSync(`./src/${dir}`)) continue;
|
||||||
|
const files = await readdir(`./src/${dir}`);
|
||||||
|
for (const file of files) {
|
||||||
|
if (file === "index.ts") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const mod = `p${i}`;
|
||||||
|
code += `import ${mod} from "./${dir}/${file.replace(/.tsx?$/, "")}";\n`;
|
||||||
|
plugins += `[${mod}.name]:${mod},\n`;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
code += `export default {${plugins}};`;
|
||||||
|
return {
|
||||||
|
contents: code,
|
||||||
|
resolveDir: "./src"
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const gitHash = execSync("git rev-parse --short HEAD", { encoding: "utf-8" }).trim();
|
||||||
|
/**
|
||||||
|
* @type {esbuild.Plugin}
|
||||||
|
*/
|
||||||
|
export const gitHashPlugin = {
|
||||||
|
name: "git-hash-plugin",
|
||||||
|
setup: build => {
|
||||||
|
const filter = /^~git-hash$/;
|
||||||
|
build.onResolve({ filter }, args => ({
|
||||||
|
namespace: "git-hash", path: args.path
|
||||||
|
}));
|
||||||
|
build.onLoad({ filter, namespace: "git-hash" }, () => ({
|
||||||
|
contents: `export default "${gitHash}"`
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {esbuild.Plugin}
|
||||||
|
*/
|
||||||
|
export const gitRemotePlugin = {
|
||||||
|
name: "git-remote-plugin",
|
||||||
|
setup: build => {
|
||||||
|
const filter = /^~git-remote$/;
|
||||||
|
build.onResolve({ filter }, args => ({
|
||||||
|
namespace: "git-remote", path: args.path
|
||||||
|
}));
|
||||||
|
build.onLoad({ filter, namespace: "git-remote" }, async () => {
|
||||||
|
const res = await promisify(exec)("git remote get-url origin", { encoding: "utf-8" });
|
||||||
|
const remote = res.stdout.trim()
|
||||||
|
.replace("https://github.com/", "")
|
||||||
|
.replace("git@github.com:", "")
|
||||||
|
.replace(/.git$/, "");
|
||||||
|
|
||||||
|
return { contents: `export default "${remote}"` };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {esbuild.Plugin}
|
||||||
|
*/
|
||||||
|
export const fileIncludePlugin = {
|
||||||
|
name: "file-include-plugin",
|
||||||
|
setup: build => {
|
||||||
|
const filter = /^~fileContent\/.+$/;
|
||||||
|
build.onResolve({ filter }, args => ({
|
||||||
|
namespace: "include-file",
|
||||||
|
path: args.path,
|
||||||
|
pluginData: {
|
||||||
|
path: join(args.resolveDir, args.path.slice("include-file/".length))
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
build.onLoad({ filter, namespace: "include-file" }, async ({ pluginData: { path } }) => {
|
||||||
|
const [name, format] = path.split(";");
|
||||||
|
return {
|
||||||
|
contents: `export default ${JSON.stringify(await readFile(name, format ?? "utf-8"))}`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {esbuild.BuildOptions}
|
||||||
|
*/
|
||||||
|
export const commonOpts = {
|
||||||
|
logLevel: "info",
|
||||||
|
bundle: true,
|
||||||
|
watch,
|
||||||
|
minify: !watch,
|
||||||
|
sourcemap: watch ? "inline" : "",
|
||||||
|
legalComments: "linked",
|
||||||
|
plugins: [fileIncludePlugin, gitHashPlugin, gitRemotePlugin],
|
||||||
|
external: ["~plugins", "~git-hash", "~git-remote"]
|
||||||
|
};
|
341
scripts/patcher/common.js
Normal file
341
scripts/patcher/common.js
Normal file
@ -0,0 +1,341 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const path = require("path");
|
||||||
|
const readline = require("readline");
|
||||||
|
const fs = require("fs");
|
||||||
|
const menu = require("console-menu");
|
||||||
|
|
||||||
|
const BRANCH_NAMES = [
|
||||||
|
"Discord",
|
||||||
|
"DiscordPTB",
|
||||||
|
"DiscordCanary",
|
||||||
|
"DiscordDevelopment",
|
||||||
|
"discord",
|
||||||
|
"discordptb",
|
||||||
|
"discordcanary",
|
||||||
|
"discorddevelopment",
|
||||||
|
"discord-ptb",
|
||||||
|
"discord-canary",
|
||||||
|
"discord-development",
|
||||||
|
// Flatpak
|
||||||
|
"com.discordapp.Discord",
|
||||||
|
"com.discordapp.DiscordPTB",
|
||||||
|
"com.discordapp.DiscordCanary",
|
||||||
|
"com.discordapp.DiscordDevelopment",
|
||||||
|
];
|
||||||
|
|
||||||
|
const MACOS_DISCORD_DIRS = [
|
||||||
|
"Discord.app",
|
||||||
|
"Discord PTB.app",
|
||||||
|
"Discord Canary.app",
|
||||||
|
"Discord Development.app",
|
||||||
|
];
|
||||||
|
|
||||||
|
if (process.platform === "linux" && process.env.SUDO_USER) {
|
||||||
|
process.env.HOME = fs
|
||||||
|
.readFileSync("/etc/passwd", "utf-8")
|
||||||
|
.match(new RegExp(`^${process.env.SUDO_USER}.+$`, "m"))[0]
|
||||||
|
.split(":")[5];
|
||||||
|
}
|
||||||
|
|
||||||
|
const LINUX_DISCORD_DIRS = [
|
||||||
|
"/usr/share",
|
||||||
|
"/usr/lib64",
|
||||||
|
"/opt",
|
||||||
|
`${process.env.HOME}/.local/share`,
|
||||||
|
"/var/lib/flatpak/app",
|
||||||
|
`${process.env.HOME}/.local/share/flatpak/app`,
|
||||||
|
];
|
||||||
|
|
||||||
|
const FLATPAK_NAME_MAPPING = {
|
||||||
|
DiscordCanary: "discord-canary",
|
||||||
|
DiscordPTB: "discord-ptb",
|
||||||
|
DiscordDevelopment: "discord-development",
|
||||||
|
Discord: "discord",
|
||||||
|
};
|
||||||
|
|
||||||
|
const ENTRYPOINT = path
|
||||||
|
.join(process.cwd(), "dist", "patcher.js")
|
||||||
|
.replace(/\\/g, "/");
|
||||||
|
|
||||||
|
function question(question) {
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
terminal: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
rl.question(question, answer => {
|
||||||
|
rl.close();
|
||||||
|
resolve(answer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getMenuItem(installations) {
|
||||||
|
const menuItems = installations.map(info => ({
|
||||||
|
title: info.patched ? "[MODIFIED] " + info.location : info.location,
|
||||||
|
info,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const result = await menu(
|
||||||
|
[
|
||||||
|
...menuItems,
|
||||||
|
{ title: "Specify custom path", info: "custom" },
|
||||||
|
{ title: "Exit without patching", exit: true }
|
||||||
|
],
|
||||||
|
{
|
||||||
|
header: "Select a Discord installation to patch:",
|
||||||
|
border: true,
|
||||||
|
helpMessage:
|
||||||
|
"Use the up/down arrow keys to select an option. " +
|
||||||
|
"Press ENTER to confirm.",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result || !result.info || result.exit) {
|
||||||
|
console.log("No installation selected.");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.info === "custom") {
|
||||||
|
const customPath = await question("Please enter the path: ");
|
||||||
|
if (!customPath || !fs.existsSync(customPath)) {
|
||||||
|
console.log("No such Path or not specifed.");
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const resourceDir = path.join(customPath, "resources");
|
||||||
|
if (!fs.existsSync(path.join(resourceDir, "app.asar"))) {
|
||||||
|
console.log("Unsupported Install. resources/app.asar not found");
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const appDir = path.join(resourceDir, "app");
|
||||||
|
result.info = {
|
||||||
|
branch: "unknown",
|
||||||
|
patched: fs.existsSync(appDir),
|
||||||
|
location: customPath,
|
||||||
|
versions: [{
|
||||||
|
path: appDir,
|
||||||
|
name: null
|
||||||
|
}],
|
||||||
|
arch: process.platform === "linux" ? "linux" : "win32",
|
||||||
|
isFlatpak: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.info.patched) {
|
||||||
|
const answer = await question(
|
||||||
|
"This installation has already been modified. Overwrite? [Y/n]: "
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!["y", "yes", "yeah", ""].includes(answer.toLowerCase())) {
|
||||||
|
console.log("Not patching.");
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.info;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWindowsDirs() {
|
||||||
|
const dirs = [];
|
||||||
|
for (const dir of fs.readdirSync(process.env.LOCALAPPDATA)) {
|
||||||
|
if (!BRANCH_NAMES.includes(dir)) continue;
|
||||||
|
|
||||||
|
const location = path.join(process.env.LOCALAPPDATA, dir);
|
||||||
|
if (!fs.statSync(location).isDirectory()) continue;
|
||||||
|
|
||||||
|
const appDirs = fs
|
||||||
|
.readdirSync(location, { withFileTypes: true })
|
||||||
|
.filter(file => file.isDirectory())
|
||||||
|
.filter(file => file.name.startsWith("app-"))
|
||||||
|
.map(file => path.join(location, file.name));
|
||||||
|
|
||||||
|
const versions = [];
|
||||||
|
let patched = false;
|
||||||
|
|
||||||
|
for (const fqAppDir of appDirs) {
|
||||||
|
const resourceDir = path.join(fqAppDir, "resources");
|
||||||
|
if (!fs.existsSync(path.join(resourceDir, "app.asar"))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const appDir = path.join(resourceDir, "app");
|
||||||
|
if (fs.existsSync(appDir)) {
|
||||||
|
patched = true;
|
||||||
|
}
|
||||||
|
versions.push({
|
||||||
|
path: appDir,
|
||||||
|
name: /app-([0-9.]+)/.exec(fqAppDir)[1],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appDirs.length) {
|
||||||
|
dirs.push({
|
||||||
|
branch: dir,
|
||||||
|
patched,
|
||||||
|
location,
|
||||||
|
versions,
|
||||||
|
arch: "win32",
|
||||||
|
flatpak: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDarwinDirs() {
|
||||||
|
const dirs = [];
|
||||||
|
for (const dir of fs.readdirSync("/Applications")) {
|
||||||
|
if (!MACOS_DISCORD_DIRS.includes(dir)) continue;
|
||||||
|
|
||||||
|
const location = path.join("/Applications", dir, "Contents");
|
||||||
|
if (!fs.existsSync(location)) continue;
|
||||||
|
if (!fs.statSync(location).isDirectory()) continue;
|
||||||
|
|
||||||
|
const appDirs = fs
|
||||||
|
.readdirSync(location, { withFileTypes: true })
|
||||||
|
.filter(file => file.isDirectory())
|
||||||
|
.filter(file => file.name.startsWith("Resources"))
|
||||||
|
.map(file => path.join(location, file.name));
|
||||||
|
|
||||||
|
const versions = [];
|
||||||
|
let patched = false;
|
||||||
|
|
||||||
|
for (const resourceDir of appDirs) {
|
||||||
|
if (!fs.existsSync(path.join(resourceDir, "app.asar"))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const appDir = path.join(resourceDir, "app");
|
||||||
|
if (fs.existsSync(appDir)) {
|
||||||
|
patched = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
versions.push({
|
||||||
|
path: appDir,
|
||||||
|
name: null, // MacOS installs have no version number
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appDirs.length) {
|
||||||
|
dirs.push({
|
||||||
|
branch: dir,
|
||||||
|
patched,
|
||||||
|
location,
|
||||||
|
versions,
|
||||||
|
arch: "win32",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLinuxDirs() {
|
||||||
|
const dirs = [];
|
||||||
|
for (const dir of LINUX_DISCORD_DIRS) {
|
||||||
|
if (!fs.existsSync(dir)) continue;
|
||||||
|
for (const branch of fs.readdirSync(dir)) {
|
||||||
|
if (!BRANCH_NAMES.includes(branch)) continue;
|
||||||
|
|
||||||
|
const location = path.join(dir, branch);
|
||||||
|
if (!fs.statSync(location).isDirectory()) continue;
|
||||||
|
|
||||||
|
const isFlatpak = location.includes("/flatpak/");
|
||||||
|
|
||||||
|
let appDirs = [];
|
||||||
|
|
||||||
|
if (isFlatpak) {
|
||||||
|
const fqDir = path.join(location, "current", "active", "files");
|
||||||
|
if (!/com\.discordapp\.(\w+)\//.test(fqDir)) continue;
|
||||||
|
const branchName = /com\.discordapp\.(\w+)\//.exec(fqDir)[1];
|
||||||
|
if (!Object.keys(FLATPAK_NAME_MAPPING).includes(branchName)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const appDir = path.join(
|
||||||
|
fqDir,
|
||||||
|
FLATPAK_NAME_MAPPING[branchName]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!fs.existsSync(appDir)) continue;
|
||||||
|
if (!fs.statSync(appDir).isDirectory()) continue;
|
||||||
|
|
||||||
|
const resourceDir = path.join(appDir, "resources");
|
||||||
|
|
||||||
|
appDirs.push(resourceDir);
|
||||||
|
} else {
|
||||||
|
appDirs = fs
|
||||||
|
.readdirSync(location, { withFileTypes: true })
|
||||||
|
.filter(file => file.isDirectory())
|
||||||
|
.filter(
|
||||||
|
file =>
|
||||||
|
file.name.startsWith("app-") ||
|
||||||
|
file.name === "resources"
|
||||||
|
)
|
||||||
|
.map(file => path.join(location, file.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
const versions = [];
|
||||||
|
let patched = false;
|
||||||
|
|
||||||
|
for (const resourceDir of appDirs) {
|
||||||
|
if (!fs.existsSync(path.join(resourceDir, "app.asar"))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const appDir = path.join(resourceDir, "app");
|
||||||
|
if (fs.existsSync(appDir)) {
|
||||||
|
patched = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const version = /app-([0-9.]+)/.exec(resourceDir);
|
||||||
|
|
||||||
|
versions.push({
|
||||||
|
path: appDir,
|
||||||
|
name: version && version.length > 1 ? version[1] : null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appDirs.length) {
|
||||||
|
dirs.push({
|
||||||
|
branch,
|
||||||
|
patched,
|
||||||
|
location,
|
||||||
|
versions,
|
||||||
|
arch: "linux",
|
||||||
|
isFlatpak,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return dirs;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
BRANCH_NAMES,
|
||||||
|
MACOS_DISCORD_DIRS,
|
||||||
|
LINUX_DISCORD_DIRS,
|
||||||
|
FLATPAK_NAME_MAPPING,
|
||||||
|
ENTRYPOINT,
|
||||||
|
question,
|
||||||
|
getMenuItem,
|
||||||
|
getWindowsDirs,
|
||||||
|
getDarwinDirs,
|
||||||
|
getLinuxDirs,
|
||||||
|
};
|
130
scripts/patcher/install.js
Executable file
130
scripts/patcher/install.js
Executable file
@ -0,0 +1,130 @@
|
|||||||
|
#!/usr/bin/node
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const path = require("path");
|
||||||
|
const fs = require("fs");
|
||||||
|
const { execSync } = require("child_process");
|
||||||
|
|
||||||
|
console.log("\nVencord Installer\n");
|
||||||
|
|
||||||
|
if (!fs.existsSync(path.join(process.cwd(), "node_modules"))) {
|
||||||
|
console.log("You need to install dependencies first. Run:", "pnpm install --frozen-lockfile");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fs.existsSync(path.join(process.cwd(), "dist", "patcher.js"))) {
|
||||||
|
console.log("You need to build the project first. Run:", "pnpm build");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
getMenuItem,
|
||||||
|
getWindowsDirs,
|
||||||
|
getDarwinDirs,
|
||||||
|
getLinuxDirs,
|
||||||
|
ENTRYPOINT,
|
||||||
|
} = require("./common");
|
||||||
|
|
||||||
|
switch (process.platform) {
|
||||||
|
case "win32":
|
||||||
|
install(getWindowsDirs());
|
||||||
|
break;
|
||||||
|
case "darwin":
|
||||||
|
install(getDarwinDirs());
|
||||||
|
break;
|
||||||
|
case "linux":
|
||||||
|
install(getLinuxDirs());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("Unknown OS");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function install(installations) {
|
||||||
|
const selected = await getMenuItem(installations);
|
||||||
|
|
||||||
|
// Attempt to give flatpak perms
|
||||||
|
if (selected.isFlatpak) {
|
||||||
|
try {
|
||||||
|
const { branch } = selected;
|
||||||
|
const cwd = process.cwd();
|
||||||
|
const globalCmd = `flatpak override ${branch} --filesystem=${cwd}`;
|
||||||
|
const userCmd = `flatpak override --user ${branch} --filesystem=${cwd}`;
|
||||||
|
const cmd = selected.location.startsWith("/home")
|
||||||
|
? userCmd
|
||||||
|
: globalCmd;
|
||||||
|
execSync(cmd);
|
||||||
|
console.log("Successfully gave write perms to Discord Flatpak.");
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Failed to give write perms to Discord Flatpak.");
|
||||||
|
console.log(
|
||||||
|
"Try running this script as an administrator:",
|
||||||
|
"sudo pnpm inject"
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const version of selected.versions) {
|
||||||
|
const dir = version.path;
|
||||||
|
// Check if we have write perms to the install directory...
|
||||||
|
try {
|
||||||
|
fs.accessSync(selected.location, fs.constants.W_OK);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("No write access to", selected.location);
|
||||||
|
console.error(
|
||||||
|
"Try running this script as an administrator:",
|
||||||
|
"sudo pnpm inject"
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (fs.existsSync(dir) && fs.lstatSync(dir).isDirectory()) {
|
||||||
|
fs.rmSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(dir, "index.js"),
|
||||||
|
`require("${ENTRYPOINT}");`
|
||||||
|
);
|
||||||
|
fs.writeFileSync(
|
||||||
|
path.join(dir, "package.json"),
|
||||||
|
JSON.stringify({
|
||||||
|
name: "discord",
|
||||||
|
main: "index.js",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const requiredFiles = ["index.js", "package.json"];
|
||||||
|
|
||||||
|
if (requiredFiles.every(f => fs.existsSync(path.join(dir, f)))) {
|
||||||
|
console.log(
|
||||||
|
"Successfully patched",
|
||||||
|
version.name
|
||||||
|
? `${selected.branch} ${version.name}`
|
||||||
|
: selected.branch
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("Failed to patch", dir);
|
||||||
|
console.log("Files in directory:", fs.readdirSync(dir));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
scripts/patcher/uninstall.js
Executable file
78
scripts/patcher/uninstall.js
Executable file
@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/node
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const path = require("path");
|
||||||
|
const fs = require("fs");
|
||||||
|
|
||||||
|
console.log("\nVencord Uninstaller\n");
|
||||||
|
|
||||||
|
if (!fs.existsSync(path.join(process.cwd(), "node_modules"))) {
|
||||||
|
console.log("You need to install dependencies first. Run:", "pnpm install --frozen-lockfile");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
getMenuItem,
|
||||||
|
getWindowsDirs,
|
||||||
|
getDarwinDirs,
|
||||||
|
getLinuxDirs,
|
||||||
|
} = require("./common");
|
||||||
|
|
||||||
|
switch (process.platform) {
|
||||||
|
case "win32":
|
||||||
|
uninstall(getWindowsDirs());
|
||||||
|
break;
|
||||||
|
case "darwin":
|
||||||
|
uninstall(getDarwinDirs());
|
||||||
|
break;
|
||||||
|
case "linux":
|
||||||
|
uninstall(getLinuxDirs());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log("Unknown OS");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uninstall(installations) {
|
||||||
|
const selected = await getMenuItem(installations);
|
||||||
|
|
||||||
|
for (const version of selected.versions) {
|
||||||
|
const dir = version.path;
|
||||||
|
// Check if we have write perms to the install directory...
|
||||||
|
try {
|
||||||
|
fs.accessSync(selected.location, fs.constants.W_OK);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("No write access to", selected.location);
|
||||||
|
console.error(
|
||||||
|
"Try running this script as an administrator:",
|
||||||
|
"sudo pnpm uninject"
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
if (fs.existsSync(dir)) {
|
||||||
|
fs.rmSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
console.log(
|
||||||
|
"Successfully unpatched",
|
||||||
|
version.name
|
||||||
|
? `${selected.branch} ${version.name}`
|
||||||
|
: selected.branch
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
24
scripts/suppressExperimentalWarnings.js
Normal file
24
scripts/suppressExperimentalWarnings.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
process.emit = (originalEmit => function (name, data) {
|
||||||
|
if (name === "warning" && data?.name === "ExperimentalWarning")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return originalEmit.apply(process, arguments);
|
||||||
|
})(process.emit);
|
@ -1,43 +1,65 @@
|
|||||||
export * as Plugins from "./plugins";
|
/*!
|
||||||
export * as Webpack from "./webpack";
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
export * as Api from "./api";
|
export * as Api from "./api";
|
||||||
export * as Updater from "./utils/updater";
|
export * as Plugins from "./plugins";
|
||||||
|
export * as Util from "./utils";
|
||||||
export * as QuickCss from "./utils/quickCss";
|
export * as QuickCss from "./utils/quickCss";
|
||||||
|
export * as Updater from "./utils/updater";
|
||||||
|
export * as Webpack from "./webpack";
|
||||||
|
|
||||||
import { popNotice, showNotice } from "./api/Notices";
|
import { popNotice, showNotice } from "./api/Notices";
|
||||||
import { Settings } from "./api/settings";
|
import { PlainSettings,Settings } from "./api/settings";
|
||||||
import { startAllPlugins } from "./plugins";
|
import { startAllPlugins } from "./plugins";
|
||||||
|
|
||||||
export { Settings };
|
export { PlainSettings,Settings };
|
||||||
|
|
||||||
import "./webpack/patchWebpack";
|
import "./webpack/patchWebpack";
|
||||||
import "./utils/quickCss";
|
import "./utils/quickCss";
|
||||||
import { checkForUpdates, UpdateLogger } from './utils/updater';
|
|
||||||
|
import { checkForUpdates, UpdateLogger } from "./utils/updater";
|
||||||
import { onceReady } from "./webpack";
|
import { onceReady } from "./webpack";
|
||||||
import { Router } from "./webpack/common";
|
import { Router } from "./webpack/common";
|
||||||
|
|
||||||
export let Components;
|
export let Components: any;
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
await onceReady;
|
await onceReady;
|
||||||
startAllPlugins();
|
startAllPlugins();
|
||||||
Components = await import("./components");
|
Components = await import("./components");
|
||||||
|
|
||||||
try {
|
if (!IS_WEB) {
|
||||||
const isOutdated = await checkForUpdates();
|
try {
|
||||||
if (isOutdated && Settings.notifyAboutUpdates)
|
const isOutdated = await checkForUpdates();
|
||||||
setTimeout(() => {
|
if (isOutdated && Settings.notifyAboutUpdates)
|
||||||
showNotice(
|
setTimeout(() => {
|
||||||
"A Vencord update is available!",
|
showNotice(
|
||||||
"View Update",
|
"A Vencord update is available!",
|
||||||
() => {
|
"View Update",
|
||||||
popNotice();
|
() => {
|
||||||
Router.open("VencordUpdater");
|
popNotice();
|
||||||
}
|
Router.open("VencordUpdater");
|
||||||
);
|
}
|
||||||
}, 10000);
|
);
|
||||||
} catch (err) {
|
}, 10000);
|
||||||
UpdateLogger.error("Failed to check for updates", err);
|
} catch (err) {
|
||||||
|
UpdateLogger.error("Failed to check for updates", err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,24 @@
|
|||||||
import IPC_EVENTS from './utils/IpcEvents';
|
/*
|
||||||
import { IpcRenderer, ipcRenderer } from 'electron';
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022
|
||||||
|
*
|
||||||
|
* 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 { IpcRenderer, ipcRenderer } from "electron";
|
||||||
|
|
||||||
|
import IPC_EVENTS from "./utils/IpcEvents";
|
||||||
|
|
||||||
function assertEventAllowed(event: string) {
|
function assertEventAllowed(event: string) {
|
||||||
if (!(event in IPC_EVENTS)) throw new Error(`Event ${event} not allowed.`);
|
if (!(event in IPC_EVENTS)) throw new Error(`Event ${event} not allowed.`);
|
||||||
@ -27,14 +46,5 @@ export default {
|
|||||||
assertEventAllowed(event);
|
assertEventAllowed(event);
|
||||||
return ipcRenderer.invoke(event, ...args);
|
return ipcRenderer.invoke(event, ...args);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
require(mod: string) {
|
|
||||||
const settings = ipcRenderer.sendSync(IPC_EVENTS.GET_SETTINGS);
|
|
||||||
try {
|
|
||||||
if (!JSON.parse(settings).unsafeRequire) throw "no";
|
|
||||||
} catch {
|
|
||||||
throw new Error("Unsafe require is not allowed. Enable it in settings and try again.");
|
|
||||||
}
|
|
||||||
return require(mod);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
61
src/api/Commands/commandHelpers.ts
Normal file
61
src/api/Commands/commandHelpers.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Message } from "discord-types/general";
|
||||||
|
import type { PartialDeep } from "type-fest";
|
||||||
|
|
||||||
|
import { lazyWebpack, mergeDefaults } from "../../utils/misc";
|
||||||
|
import { filters, waitFor } from "../../webpack";
|
||||||
|
import { Argument } from "./types";
|
||||||
|
|
||||||
|
const createBotMessage = lazyWebpack(filters.byCode('username:"Clyde"'));
|
||||||
|
const MessageSender = lazyWebpack(filters.byProps(["receiveMessage"]));
|
||||||
|
|
||||||
|
let SnowflakeUtils: any;
|
||||||
|
waitFor("fromTimestamp", m => SnowflakeUtils = m);
|
||||||
|
|
||||||
|
export function generateId() {
|
||||||
|
return `-${SnowflakeUtils.fromTimestamp(Date.now())}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a message as Clyde
|
||||||
|
* @param {string} channelId ID of channel to send message to
|
||||||
|
* @param {Message} message Message to send
|
||||||
|
* @returns {Message}
|
||||||
|
*/
|
||||||
|
export function sendBotMessage(channelId: string, message: PartialDeep<Message>): Message {
|
||||||
|
const botMessage = createBotMessage({ channelId, content: "", embeds: [] });
|
||||||
|
|
||||||
|
MessageSender.receiveMessage(channelId, mergeDefaults(message, botMessage));
|
||||||
|
|
||||||
|
return message as Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the value of an option by name
|
||||||
|
* @param args Arguments array (first argument passed to execute)
|
||||||
|
* @param name Name of the argument
|
||||||
|
* @param fallbackValue Fallback value in case this option wasn't passed
|
||||||
|
* @returns Value
|
||||||
|
*/
|
||||||
|
export function findOption<T>(args: Argument[], name: string): T & {} | undefined;
|
||||||
|
export function findOption<T>(args: Argument[], name: string, fallbackValue: T): T & {};
|
||||||
|
export function findOption(args: Argument[], name: string, fallbackValue?: any) {
|
||||||
|
return (args.find(a => a.name === name)?.value || fallbackValue) as any;
|
||||||
|
}
|
129
src/api/Commands/index.ts
Normal file
129
src/api/Commands/index.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { makeCodeblock } from "../../utils/misc";
|
||||||
|
import { generateId, sendBotMessage } from "./commandHelpers";
|
||||||
|
import { ApplicationCommandInputType, ApplicationCommandType, Argument, Command, CommandContext, Option } from "./types";
|
||||||
|
|
||||||
|
export * from "./commandHelpers";
|
||||||
|
export * from "./types";
|
||||||
|
|
||||||
|
export let BUILT_IN: Command[];
|
||||||
|
export const commands = {} as Record<string, Command>;
|
||||||
|
|
||||||
|
// hack for plugins being evaluated before we can grab these from webpack
|
||||||
|
const OptPlaceholder = Symbol("OptionalMessageOption") as any as Option;
|
||||||
|
const ReqPlaceholder = Symbol("RequiredMessageOption") as any as Option;
|
||||||
|
/**
|
||||||
|
* Optional message option named "message" you can use in commands.
|
||||||
|
* Used in "tableflip" or "shrug"
|
||||||
|
* @see {@link RequiredMessageOption}
|
||||||
|
*/
|
||||||
|
export let OptionalMessageOption: Option = OptPlaceholder;
|
||||||
|
/**
|
||||||
|
* Required message option named "message" you can use in commands.
|
||||||
|
* Used in "me"
|
||||||
|
* @see {@link OptionalMessageOption}
|
||||||
|
*/
|
||||||
|
export let RequiredMessageOption: Option = ReqPlaceholder;
|
||||||
|
|
||||||
|
export const _init = function (cmds: Command[]) {
|
||||||
|
try {
|
||||||
|
BUILT_IN = cmds;
|
||||||
|
OptionalMessageOption = cmds.find(c => c.name === "shrug")!.options![0];
|
||||||
|
RequiredMessageOption = cmds.find(c => c.name === "me")!.options![0];
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Failed to load CommandsApi");
|
||||||
|
}
|
||||||
|
return cmds;
|
||||||
|
} as never;
|
||||||
|
|
||||||
|
export const _handleCommand = function (cmd: Command, args: Argument[], ctx: CommandContext) {
|
||||||
|
if (!cmd.isVencordCommand)
|
||||||
|
return cmd.execute(args, ctx);
|
||||||
|
|
||||||
|
const handleError = (err: any) => {
|
||||||
|
// TODO: cancel send if cmd.inputType === BUILT_IN_TEXT
|
||||||
|
const msg = `An Error occurred while executing command "${cmd.name}"`;
|
||||||
|
const reason = err instanceof Error ? err.stack || err.message : String(err);
|
||||||
|
|
||||||
|
console.error(msg, err);
|
||||||
|
sendBotMessage(ctx.channel.id, {
|
||||||
|
content: `${msg}:\n${makeCodeblock(reason)}`,
|
||||||
|
author: {
|
||||||
|
username: "Vencord"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = cmd.execute(args, ctx);
|
||||||
|
return res instanceof Promise ? res.catch(handleError) : res;
|
||||||
|
} catch (err) {
|
||||||
|
return handleError(err);
|
||||||
|
}
|
||||||
|
} as never;
|
||||||
|
|
||||||
|
function modifyOpt(opt: Option | Command) {
|
||||||
|
opt.displayName ||= opt.name;
|
||||||
|
opt.displayDescription ||= opt.description;
|
||||||
|
opt.options?.forEach((opt, i, opts) => {
|
||||||
|
// See comment above Placeholders
|
||||||
|
if (opt === OptPlaceholder) opts[i] = OptionalMessageOption;
|
||||||
|
else if (opt === ReqPlaceholder) opts[i] = RequiredMessageOption;
|
||||||
|
opt.choices?.forEach(x => x.displayName ||= x.name);
|
||||||
|
|
||||||
|
modifyOpt(opts[i]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function registerCommand(command: Command, plugin: string) {
|
||||||
|
if (!BUILT_IN) {
|
||||||
|
console.warn(
|
||||||
|
"[CommandsAPI]",
|
||||||
|
`Not registering ${command.name} as the CommandsAPI hasn't been initialised.`,
|
||||||
|
"Please restart to use commands"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (BUILT_IN.some(c => c.name === command.name))
|
||||||
|
throw new Error(`Command '${command.name}' already exists.`);
|
||||||
|
|
||||||
|
command.isVencordCommand = true;
|
||||||
|
command.id ??= generateId();
|
||||||
|
command.applicationId ??= "-1"; // BUILT_IN;
|
||||||
|
command.type ??= ApplicationCommandType.CHAT_INPUT;
|
||||||
|
command.inputType ??= ApplicationCommandInputType.BUILT_IN_TEXT;
|
||||||
|
command.plugin ||= plugin;
|
||||||
|
|
||||||
|
modifyOpt(command);
|
||||||
|
commands[command.name] = command;
|
||||||
|
BUILT_IN.push(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregisterCommand(name: string) {
|
||||||
|
const idx = BUILT_IN.findIndex(c => c.name === name);
|
||||||
|
if (idx === -1)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
BUILT_IN.splice(idx, 1);
|
||||||
|
delete commands[name];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
103
src/api/Commands/types.ts
Normal file
103
src/api/Commands/types.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Channel, Guild } from "discord-types/general";
|
||||||
|
import { Promisable } from "type-fest";
|
||||||
|
|
||||||
|
export interface CommandContext {
|
||||||
|
channel: Channel;
|
||||||
|
guild?: Guild;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ApplicationCommandOptionType {
|
||||||
|
SUB_COMMAND = 1,
|
||||||
|
SUB_COMMAND_GROUP = 2,
|
||||||
|
STRING = 3,
|
||||||
|
INTEGER = 4,
|
||||||
|
BOOLEAN = 5,
|
||||||
|
USER = 6,
|
||||||
|
CHANNEL = 7,
|
||||||
|
ROLE = 8,
|
||||||
|
MENTIONABLE = 9,
|
||||||
|
NUMBER = 10,
|
||||||
|
ATTACHMENT = 11,
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ApplicationCommandInputType {
|
||||||
|
BUILT_IN = 0,
|
||||||
|
BUILT_IN_TEXT = 1,
|
||||||
|
BUILT_IN_INTEGRATION = 2,
|
||||||
|
BOT = 3,
|
||||||
|
PLACEHOLDER = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Option {
|
||||||
|
name: string;
|
||||||
|
displayName?: string;
|
||||||
|
type: ApplicationCommandOptionType;
|
||||||
|
description: string;
|
||||||
|
displayDescription?: string;
|
||||||
|
required?: boolean;
|
||||||
|
options?: Option[];
|
||||||
|
choices?: Array<ChoicesOption>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChoicesOption {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
name: string;
|
||||||
|
displayName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ApplicationCommandType {
|
||||||
|
CHAT_INPUT = 1,
|
||||||
|
USER = 2,
|
||||||
|
MESSAGE = 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CommandReturnValue {
|
||||||
|
content: string;
|
||||||
|
/** TODO: implement */
|
||||||
|
cancel?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Argument {
|
||||||
|
type: ApplicationCommandOptionType;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
focused: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Command {
|
||||||
|
id?: string;
|
||||||
|
applicationId?: string;
|
||||||
|
type?: ApplicationCommandType;
|
||||||
|
inputType?: ApplicationCommandInputType;
|
||||||
|
plugin?: string;
|
||||||
|
isVencordCommand?: boolean;
|
||||||
|
|
||||||
|
name: string;
|
||||||
|
displayName?: string;
|
||||||
|
description: string;
|
||||||
|
displayDescription?: string;
|
||||||
|
|
||||||
|
options?: Option[];
|
||||||
|
predicate?(ctx: CommandContext): boolean;
|
||||||
|
|
||||||
|
execute(args: Argument[], ctx: CommandContext): Promisable<void | CommandReturnValue>;
|
||||||
|
}
|
202
src/api/DataStore/LICENSE
Normal file
202
src/api/DataStore/LICENSE
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
279
src/api/DataStore/index.ts
Normal file
279
src/api/DataStore/index.ts
Normal file
@ -0,0 +1,279 @@
|
|||||||
|
/* eslint-disable header/header */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* idb-keyval v6.2.0
|
||||||
|
* Copyright 2016, Jake Archibald
|
||||||
|
* Copyright 2022, Vendicated
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function promisifyRequest<T = undefined>(
|
||||||
|
request: IDBRequest<T> | IDBTransaction,
|
||||||
|
): Promise<T> {
|
||||||
|
return new Promise<T>((resolve, reject) => {
|
||||||
|
// @ts-ignore - file size hacks
|
||||||
|
request.oncomplete = request.onsuccess = () => resolve(request.result);
|
||||||
|
// @ts-ignore - file size hacks
|
||||||
|
request.onabort = request.onerror = () => reject(request.error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createStore(dbName: string, storeName: string): UseStore {
|
||||||
|
const request = indexedDB.open(dbName);
|
||||||
|
request.onupgradeneeded = () => request.result.createObjectStore(storeName);
|
||||||
|
const dbp = promisifyRequest(request);
|
||||||
|
|
||||||
|
return (txMode, callback) =>
|
||||||
|
dbp.then(db =>
|
||||||
|
callback(db.transaction(storeName, txMode).objectStore(storeName)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UseStore = <T>(
|
||||||
|
txMode: IDBTransactionMode,
|
||||||
|
callback: (store: IDBObjectStore) => T | PromiseLike<T>,
|
||||||
|
) => Promise<T>;
|
||||||
|
|
||||||
|
let defaultGetStoreFunc: UseStore | undefined;
|
||||||
|
|
||||||
|
function defaultGetStore() {
|
||||||
|
if (!defaultGetStoreFunc) {
|
||||||
|
defaultGetStoreFunc = createStore("VencordData", "VencordStore");
|
||||||
|
}
|
||||||
|
return defaultGetStoreFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a value by its key.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||||
|
*/
|
||||||
|
export function get<T = any>(
|
||||||
|
key: IDBValidKey,
|
||||||
|
customStore = defaultGetStore(),
|
||||||
|
): Promise<T | undefined> {
|
||||||
|
return customStore("readonly", store => promisifyRequest(store.get(key)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a value with a key.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param value
|
||||||
|
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||||
|
*/
|
||||||
|
export function set(
|
||||||
|
key: IDBValidKey,
|
||||||
|
value: any,
|
||||||
|
customStore = defaultGetStore(),
|
||||||
|
): Promise<void> {
|
||||||
|
return customStore("readwrite", store => {
|
||||||
|
store.put(value, key);
|
||||||
|
return promisifyRequest(store.transaction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set multiple values at once. This is faster than calling set() multiple times.
|
||||||
|
* It's also atomic – if one of the pairs can't be added, none will be added.
|
||||||
|
*
|
||||||
|
* @param entries Array of entries, where each entry is an array of `[key, value]`.
|
||||||
|
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||||
|
*/
|
||||||
|
export function setMany(
|
||||||
|
entries: [IDBValidKey, any][],
|
||||||
|
customStore = defaultGetStore(),
|
||||||
|
): Promise<void> {
|
||||||
|
return customStore("readwrite", store => {
|
||||||
|
entries.forEach(entry => store.put(entry[1], entry[0]));
|
||||||
|
return promisifyRequest(store.transaction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get multiple values by their keys
|
||||||
|
*
|
||||||
|
* @param keys
|
||||||
|
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||||
|
*/
|
||||||
|
export function getMany<T = any>(
|
||||||
|
keys: IDBValidKey[],
|
||||||
|
customStore = defaultGetStore(),
|
||||||
|
): Promise<T[]> {
|
||||||
|
return customStore("readonly", store =>
|
||||||
|
Promise.all(keys.map(key => promisifyRequest(store.get(key)))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a value. This lets you see the old value and update it as an atomic operation.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param updater A callback that takes the old value and returns a new value.
|
||||||
|
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||||
|
*/
|
||||||
|
export function update<T = any>(
|
||||||
|
key: IDBValidKey,
|
||||||
|
updater: (oldValue: T | undefined) => T,
|
||||||
|
customStore = defaultGetStore(),
|
||||||
|
): Promise<void> {
|
||||||
|
return customStore(
|
||||||
|
"readwrite",
|
||||||
|
store =>
|
||||||
|
// Need to create the promise manually.
|
||||||
|
// If I try to chain promises, the transaction closes in browsers
|
||||||
|
// that use a promise polyfill (IE10/11).
|
||||||
|
new Promise((resolve, reject) => {
|
||||||
|
store.get(key).onsuccess = function () {
|
||||||
|
try {
|
||||||
|
store.put(updater(this.result), key);
|
||||||
|
resolve(promisifyRequest(store.transaction));
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a particular key from the store.
|
||||||
|
*
|
||||||
|
* @param key
|
||||||
|
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||||
|
*/
|
||||||
|
export function del(
|
||||||
|
key: IDBValidKey,
|
||||||
|
customStore = defaultGetStore(),
|
||||||
|
): Promise<void> {
|
||||||
|
return customStore("readwrite", store => {
|
||||||
|
store.delete(key);
|
||||||
|
return promisifyRequest(store.transaction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete multiple keys at once.
|
||||||
|
*
|
||||||
|
* @param keys List of keys to delete.
|
||||||
|
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||||
|
*/
|
||||||
|
export function delMany(
|
||||||
|
keys: IDBValidKey[],
|
||||||
|
customStore = defaultGetStore(),
|
||||||
|
): Promise<void> {
|
||||||
|
return customStore("readwrite", (store: IDBObjectStore) => {
|
||||||
|
keys.forEach((key: IDBValidKey) => store.delete(key));
|
||||||
|
return promisifyRequest(store.transaction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all values in the store.
|
||||||
|
*
|
||||||
|
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||||
|
*/
|
||||||
|
export function clear(customStore = defaultGetStore()): Promise<void> {
|
||||||
|
return customStore("readwrite", store => {
|
||||||
|
store.clear();
|
||||||
|
return promisifyRequest(store.transaction);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function eachCursor(
|
||||||
|
store: IDBObjectStore,
|
||||||
|
callback: (cursor: IDBCursorWithValue) => void,
|
||||||
|
): Promise<void> {
|
||||||
|
store.openCursor().onsuccess = function () {
|
||||||
|
if (!this.result) return;
|
||||||
|
callback(this.result);
|
||||||
|
this.result.continue();
|
||||||
|
};
|
||||||
|
return promisifyRequest(store.transaction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all keys in the store.
|
||||||
|
*
|
||||||
|
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||||
|
*/
|
||||||
|
export function keys<KeyType extends IDBValidKey>(
|
||||||
|
customStore = defaultGetStore(),
|
||||||
|
): Promise<KeyType[]> {
|
||||||
|
return customStore("readonly", store => {
|
||||||
|
// Fast path for modern browsers
|
||||||
|
if (store.getAllKeys) {
|
||||||
|
return promisifyRequest(
|
||||||
|
store.getAllKeys() as unknown as IDBRequest<KeyType[]>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const items: KeyType[] = [];
|
||||||
|
|
||||||
|
return eachCursor(store, cursor =>
|
||||||
|
items.push(cursor.key as KeyType),
|
||||||
|
).then(() => items);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all values in the store.
|
||||||
|
*
|
||||||
|
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||||
|
*/
|
||||||
|
export function values<T = any>(customStore = defaultGetStore()): Promise<T[]> {
|
||||||
|
return customStore("readonly", store => {
|
||||||
|
// Fast path for modern browsers
|
||||||
|
if (store.getAll) {
|
||||||
|
return promisifyRequest(store.getAll() as IDBRequest<T[]>);
|
||||||
|
}
|
||||||
|
|
||||||
|
const items: T[] = [];
|
||||||
|
|
||||||
|
return eachCursor(store, cursor => items.push(cursor.value as T)).then(
|
||||||
|
() => items,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all entries in the store. Each entry is an array of `[key, value]`.
|
||||||
|
*
|
||||||
|
* @param customStore Method to get a custom store. Use with caution (see the docs).
|
||||||
|
*/
|
||||||
|
export function entries<KeyType extends IDBValidKey, ValueType = any>(
|
||||||
|
customStore = defaultGetStore(),
|
||||||
|
): Promise<[KeyType, ValueType][]> {
|
||||||
|
return customStore("readonly", store => {
|
||||||
|
// Fast path for modern browsers
|
||||||
|
// (although, hopefully we'll get a simpler path some day)
|
||||||
|
if (store.getAll && store.getAllKeys) {
|
||||||
|
return Promise.all([
|
||||||
|
promisifyRequest(
|
||||||
|
store.getAllKeys() as unknown as IDBRequest<KeyType[]>,
|
||||||
|
),
|
||||||
|
promisifyRequest(store.getAll() as IDBRequest<ValueType[]>),
|
||||||
|
]).then(([keys, values]) => keys.map((key, i) => [key, values[i]]));
|
||||||
|
}
|
||||||
|
|
||||||
|
const items: [KeyType, ValueType][] = [];
|
||||||
|
|
||||||
|
return customStore("readonly", store =>
|
||||||
|
eachCursor(store, cursor =>
|
||||||
|
items.push([cursor.key as KeyType, cursor.value]),
|
||||||
|
).then(() => items),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
59
src/api/MessageAccessories.ts
Normal file
59
src/api/MessageAccessories.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element;
|
||||||
|
export type Accessory = {
|
||||||
|
callback: AccessoryCallback;
|
||||||
|
position?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const accessories = new Map<String, Accessory>();
|
||||||
|
|
||||||
|
export function addAccessory(
|
||||||
|
identifier: string,
|
||||||
|
callback: AccessoryCallback,
|
||||||
|
position?: number
|
||||||
|
) {
|
||||||
|
accessories.set(identifier, {
|
||||||
|
callback,
|
||||||
|
position,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function removeAccessory(identifier: string) {
|
||||||
|
accessories.delete(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function _modifyAccessories(
|
||||||
|
elements: JSX.Element[],
|
||||||
|
props: Record<string, any>
|
||||||
|
) {
|
||||||
|
for (const accessory of accessories.values()) {
|
||||||
|
elements.splice(
|
||||||
|
accessory.position != null
|
||||||
|
? accessory.position < 0
|
||||||
|
? elements.length + accessory.position
|
||||||
|
: accessory.position
|
||||||
|
: elements.length,
|
||||||
|
0,
|
||||||
|
accessory.callback(props)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return elements;
|
||||||
|
}
|
@ -1,5 +1,24 @@
|
|||||||
import type { Message, Channel } from 'discord-types/general';
|
/*
|
||||||
import Logger from '../utils/logger';
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { Channel,Message } from "discord-types/general";
|
||||||
|
|
||||||
|
import Logger from "../utils/logger";
|
||||||
|
|
||||||
const MessageEventsLogger = new Logger("MessageEvents", "#e5c890");
|
const MessageEventsLogger = new Logger("MessageEvents", "#e5c890");
|
||||||
|
|
||||||
@ -67,7 +86,7 @@ type ClickListener = (message: Message, channel: Channel, event: MouseEvent) =>
|
|||||||
|
|
||||||
const listeners = new Set<ClickListener>();
|
const listeners = new Set<ClickListener>();
|
||||||
|
|
||||||
export function _handleClick(message, channel, event) {
|
export function _handleClick(message: Message, channel: Channel, event: MouseEvent) {
|
||||||
for (const listener of listeners) {
|
for (const listener of listeners) {
|
||||||
try {
|
try {
|
||||||
listener(message, channel, event);
|
listener(message, channel, event);
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import { waitFor } from "../webpack";
|
import { waitFor } from "../webpack";
|
||||||
|
|
||||||
let NoticesModule: any;
|
let NoticesModule: any;
|
||||||
|
@ -1,2 +1,61 @@
|
|||||||
export * as MessageEvents from "./MessageEvents";
|
/*
|
||||||
export * as Notices from "./Notices";
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as $Commands from "./Commands";
|
||||||
|
import * as $DataStore from "./DataStore";
|
||||||
|
import * as $MessageAccessories from "./MessageAccessories";
|
||||||
|
import * as $MessageEventsAPI from "./MessageEvents";
|
||||||
|
import * as $Notices from "./Notices";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An API allowing you to listen to Message Clicks or run your own logic
|
||||||
|
* before a message is sent
|
||||||
|
*
|
||||||
|
* If your plugin uses this, you must add MessageEventsAPI to its dependencies
|
||||||
|
*/
|
||||||
|
const MessageEvents = $MessageEventsAPI;
|
||||||
|
/**
|
||||||
|
* An API allowing you to create custom notices
|
||||||
|
* (snackbars on the top, like the Update prompt)
|
||||||
|
*/
|
||||||
|
const Notices = $Notices;
|
||||||
|
/**
|
||||||
|
* An API allowing you to register custom commands
|
||||||
|
*/
|
||||||
|
const Commands = $Commands;
|
||||||
|
/**
|
||||||
|
* A wrapper around IndexedDB. This can store arbitrarily
|
||||||
|
* large data and supports a lot of datatypes (Blob, Map, ...).
|
||||||
|
* For a full list, see the mdn link below
|
||||||
|
*
|
||||||
|
* This should always be preferred over the Settings API if possible, as
|
||||||
|
* localstorage has very strict size restrictions and blocks the event loop
|
||||||
|
*
|
||||||
|
* Make sure your keys are unique (tip: prefix them with ur plugin name)
|
||||||
|
* and please clean up no longer needed entries.
|
||||||
|
*
|
||||||
|
* This is actually just idb-keyval, so if you're familiar with that, you're golden!
|
||||||
|
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#supported_types}
|
||||||
|
*/
|
||||||
|
const DataStore = $DataStore;
|
||||||
|
/**
|
||||||
|
* An API allowing you to add custom components as message accessories
|
||||||
|
*/
|
||||||
|
const MessageAccessories = $MessageAccessories;
|
||||||
|
|
||||||
|
export { Commands,DataStore, MessageAccessories, MessageEvents, Notices };
|
||||||
|
@ -1,12 +1,32 @@
|
|||||||
import plugins from "plugins";
|
/*
|
||||||
import IpcEvents from "../utils/IpcEvents";
|
* Vencord, a modification for Discord's desktop app
|
||||||
import { React } from "../webpack/common";
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
import { mergeDefaults } from '../utils/misc';
|
*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
interface Settings {
|
import plugins from "~plugins";
|
||||||
|
|
||||||
|
import IpcEvents from "../utils/IpcEvents";
|
||||||
|
import { mergeDefaults } from "../utils/misc";
|
||||||
|
import { OptionType } from "../utils/types";
|
||||||
|
import { React } from "../webpack/common";
|
||||||
|
|
||||||
|
export interface Settings {
|
||||||
notifyAboutUpdates: boolean;
|
notifyAboutUpdates: boolean;
|
||||||
unsafeRequire: boolean;
|
|
||||||
useQuickCss: boolean;
|
useQuickCss: boolean;
|
||||||
|
enableReactDevtools: boolean;
|
||||||
plugins: {
|
plugins: {
|
||||||
[plugin: string]: {
|
[plugin: string]: {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@ -17,8 +37,8 @@ interface Settings {
|
|||||||
|
|
||||||
const DefaultSettings: Settings = {
|
const DefaultSettings: Settings = {
|
||||||
notifyAboutUpdates: true,
|
notifyAboutUpdates: true,
|
||||||
unsafeRequire: false,
|
|
||||||
useQuickCss: true,
|
useQuickCss: true,
|
||||||
|
enableReactDevtools: false,
|
||||||
plugins: {}
|
plugins: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -30,9 +50,6 @@ for (const plugin in plugins) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
var settings = JSON.parse(VencordNative.ipc.sendSync(IpcEvents.GET_SETTINGS)) as Settings;
|
var settings = JSON.parse(VencordNative.ipc.sendSync(IpcEvents.GET_SETTINGS)) as Settings;
|
||||||
for (const key in DefaultSettings) {
|
|
||||||
settings[key] ??= DefaultSettings[key];
|
|
||||||
}
|
|
||||||
mergeDefaults(settings, DefaultSettings);
|
mergeDefaults(settings, DefaultSettings);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Corrupt settings file. ", err);
|
console.error("Corrupt settings file. ", err);
|
||||||
@ -42,33 +59,71 @@ try {
|
|||||||
type SubscriptionCallback = ((newValue: any, path: string) => void) & { _path?: string; };
|
type SubscriptionCallback = ((newValue: any, path: string) => void) & { _path?: string; };
|
||||||
const subscriptions = new Set<SubscriptionCallback>();
|
const subscriptions = new Set<SubscriptionCallback>();
|
||||||
|
|
||||||
|
// Wraps the passed settings object in a Proxy to nicely handle change listeners and default values
|
||||||
function makeProxy(settings: Settings, root = settings, path = ""): Settings {
|
function makeProxy(settings: Settings, root = settings, path = ""): Settings {
|
||||||
return new Proxy(settings, {
|
return new Proxy(settings, {
|
||||||
get(target, p: string) {
|
get(target, p: string) {
|
||||||
const v = target[p];
|
const v = target[p];
|
||||||
if (typeof v === "object" && !Array.isArray(v))
|
|
||||||
|
// using "in" is important in the following cases to properly handle falsy or nullish values
|
||||||
|
if (!(p in target)) {
|
||||||
|
// Since the property is not set, check if this is a plugin's setting and if so, try to resolve
|
||||||
|
// the default value.
|
||||||
|
if (path.startsWith("plugins.")) {
|
||||||
|
const plugin = path.slice("plugins.".length);
|
||||||
|
if (plugin in plugins) {
|
||||||
|
const setting = plugins[plugin].options?.[p];
|
||||||
|
if (!setting) return v;
|
||||||
|
if ("default" in setting)
|
||||||
|
// normal setting with a default value
|
||||||
|
return setting.default;
|
||||||
|
if (setting.type === OptionType.SELECT)
|
||||||
|
return setting.options.find(o => o.default)?.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively proxy Objects with the updated property path
|
||||||
|
if (typeof v === "object" && !Array.isArray(v) && v !== null)
|
||||||
return makeProxy(v, root, `${path}${path && "."}${p}`);
|
return makeProxy(v, root, `${path}${path && "."}${p}`);
|
||||||
|
|
||||||
|
// primitive or similar, no need to proxy further
|
||||||
return v;
|
return v;
|
||||||
},
|
},
|
||||||
|
|
||||||
set(target, p: string, v) {
|
set(target, p: string, v) {
|
||||||
|
// avoid unnecessary updates to React Components and other listeners
|
||||||
if (target[p] === v) return true;
|
if (target[p] === v) return true;
|
||||||
|
|
||||||
target[p] = v;
|
target[p] = v;
|
||||||
|
// Call any listeners that are listening to a setting of this path
|
||||||
const setPath = `${path}${path && "."}${p}`;
|
const setPath = `${path}${path && "."}${p}`;
|
||||||
for (const subscription of subscriptions) {
|
for (const subscription of subscriptions) {
|
||||||
if (!subscription._path || subscription._path === setPath) {
|
if (!subscription._path || subscription._path === setPath) {
|
||||||
subscription(v, setPath);
|
subscription(v, setPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// And don't forget to persist the settings!
|
||||||
VencordNative.ipc.invoke(IpcEvents.SET_SETTINGS, JSON.stringify(root, null, 4));
|
VencordNative.ipc.invoke(IpcEvents.SET_SETTINGS, JSON.stringify(root, null, 4));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as {@link Settings} but unproxied. You should treat this as readonly,
|
||||||
|
* as modifying properties on this will not save to disk or call settings
|
||||||
|
* listeners.
|
||||||
|
* WARNING: default values specified in plugin.options will not be ensured here. In other words,
|
||||||
|
* settings for which you specified a default value may be uninitialised. If you need proper
|
||||||
|
* handling for default values, use {@link Settings}
|
||||||
|
*/
|
||||||
|
export const PlainSettings = settings;
|
||||||
/**
|
/**
|
||||||
* A smart settings object. Altering props automagically saves
|
* A smart settings object. Altering props automagically saves
|
||||||
* the updated settings to disk.
|
* the updated settings to disk.
|
||||||
|
* This recursively proxies objects. If you need the object non proxied, use {@link PlainSettings}
|
||||||
*/
|
*/
|
||||||
export const Settings = makeProxy(settings);
|
export const Settings = makeProxy(settings);
|
||||||
|
|
||||||
|
@ -1,10 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import Logger from "../utils/logger";
|
import Logger from "../utils/logger";
|
||||||
import { Card, React } from "../webpack/common";
|
import { Margins, React } from "../webpack/common";
|
||||||
import { ErrorCard } from "./ErrorCard";
|
import { ErrorCard } from "./ErrorCard";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; }>>;
|
fallback?: React.ComponentType<React.PropsWithChildren<{ error: any; message: string; stack: string; }>>;
|
||||||
onError?(error: Error, errorInfo: React.ErrorInfo): void;
|
onError?(error: Error, errorInfo: React.ErrorInfo): void;
|
||||||
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const color = "#e78284";
|
const color = "#e78284";
|
||||||
@ -15,7 +34,7 @@ const NO_ERROR = {};
|
|||||||
|
|
||||||
export default class ErrorBoundary extends React.Component<React.PropsWithChildren<Props>> {
|
export default class ErrorBoundary extends React.Component<React.PropsWithChildren<Props>> {
|
||||||
static wrap<T = any>(Component: React.ComponentType<T>): (props: T) => React.ReactElement {
|
static wrap<T = any>(Component: React.ComponentType<T>): (props: T) => React.ReactElement {
|
||||||
return (props) => (
|
return props => (
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<Component {...props as any/* I hate react typings ??? */} />
|
<Component {...props as any/* I hate react typings ??? */} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
@ -24,14 +43,23 @@ export default class ErrorBoundary extends React.Component<React.PropsWithChildr
|
|||||||
|
|
||||||
state = {
|
state = {
|
||||||
error: NO_ERROR as any,
|
error: NO_ERROR as any,
|
||||||
|
stack: "",
|
||||||
message: ""
|
message: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
static getDerivedStateFromError(error: any) {
|
static getDerivedStateFromError(error: any) {
|
||||||
|
let stack = error?.stack ?? "";
|
||||||
|
let message = error?.message || String(error);
|
||||||
|
|
||||||
return {
|
if (error instanceof Error && stack) {
|
||||||
error: error?.stack?.replace(/https:\/\/\S+\/assets\//g, "") || error?.message || String(error)
|
const eolIdx = stack.indexOf("\n");
|
||||||
};
|
if (eolIdx !== -1) {
|
||||||
|
message = stack.slice(0, eolIdx);
|
||||||
|
stack = stack.slice(eolIdx + 1).replace(/https:\/\/\S+\/assets\//g, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { error, stack, message };
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||||
@ -46,22 +74,24 @@ export default class ErrorBoundary extends React.Component<React.PropsWithChildr
|
|||||||
if (this.props.fallback)
|
if (this.props.fallback)
|
||||||
return <this.props.fallback
|
return <this.props.fallback
|
||||||
children={this.props.children}
|
children={this.props.children}
|
||||||
error={this.state.error}
|
{...this.state}
|
||||||
/>;
|
/>;
|
||||||
|
|
||||||
|
const msg = this.props.message || "An error occurred while rendering this Component. More info can be found below and in your console.";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorCard style={{
|
<ErrorCard style={{
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}>
|
}}>
|
||||||
<h1>Oh no!</h1>
|
<h1>Oh no!</h1>
|
||||||
<p>
|
<p>{msg}</p>
|
||||||
An error occurred while rendering this Component. More info can be found below
|
|
||||||
and in your console.
|
|
||||||
</p>
|
|
||||||
<code>
|
<code>
|
||||||
<pre>
|
{this.state.message}
|
||||||
{this.state.error}
|
{!!this.state.stack && (
|
||||||
</pre>
|
<pre className={Margins.marginTop8}>
|
||||||
|
{this.state.stack}
|
||||||
|
</pre>
|
||||||
|
)}
|
||||||
</code>
|
</code>
|
||||||
</ErrorCard>
|
</ErrorCard>
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import { Card } from "../webpack/common";
|
import { Card } from "../webpack/common";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
@ -1,11 +1,28 @@
|
|||||||
import { PropsWithChildren } from "react";
|
/*
|
||||||
import type { React } from '../webpack/common';
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { React } from "../webpack/common";
|
||||||
|
|
||||||
export function Flex(props: React.PropsWithChildren<{
|
export function Flex(props: React.PropsWithChildren<{
|
||||||
flexDirection?: React.CSSProperties["flexDirection"];
|
flexDirection?: React.CSSProperties["flexDirection"];
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
className?: string;
|
className?: string;
|
||||||
}>) {
|
} & React.HTMLProps<HTMLDivElement>>) {
|
||||||
props.style ??= {};
|
props.style ??= {};
|
||||||
props.style.flexDirection ||= props.flexDirection;
|
props.style.flexDirection ||= props.flexDirection;
|
||||||
props.style.gap ??= "1em";
|
props.style.gap ??= "1em";
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import { React } from "../webpack/common";
|
import { React } from "../webpack/common";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
42
src/components/Monaco.ts
Normal file
42
src/components/Monaco.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import monacoHtml from "~fileContent/monacoWin.html";
|
||||||
|
|
||||||
|
import { IpcEvents } from "../utils";
|
||||||
|
import { debounce } from "../utils/debounce";
|
||||||
|
import { Queue } from "../utils/Queue";
|
||||||
|
import { find } from "../webpack/webpack";
|
||||||
|
|
||||||
|
const queue = new Queue();
|
||||||
|
const setCss = debounce((css: string) => {
|
||||||
|
queue.add(() => VencordNative.ipc.invoke(IpcEvents.SET_QUICK_CSS, css));
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function launchMonacoEditor() {
|
||||||
|
const win = open("about:blank", void 0, "popup,width=1000,height=1000")!;
|
||||||
|
|
||||||
|
win.setCss = setCss;
|
||||||
|
win.getCurrentCss = () => VencordNative.ipc.invoke(IpcEvents.GET_QUICK_CSS);
|
||||||
|
win.getTheme = () => find(m => m.ProtoClass?.typeName.endsWith("PreloadedUserSettings"))
|
||||||
|
.getCurrentValue().appearance.theme === 1
|
||||||
|
? "vs-dark"
|
||||||
|
: "vs-light";
|
||||||
|
|
||||||
|
win.document.write(monacoHtml);
|
||||||
|
}
|
225
src/components/PluginSettings/PluginModal.tsx
Normal file
225
src/components/PluginSettings/PluginModal.tsx
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Forms } from "@components";
|
||||||
|
import { User } from "discord-types/general";
|
||||||
|
import { Constructor } from "type-fest";
|
||||||
|
|
||||||
|
import { generateId } from "../../api/Commands";
|
||||||
|
import { useSettings } from "../../api/settings";
|
||||||
|
import { lazyWebpack, proxyLazy } from "../../utils";
|
||||||
|
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "../../utils/modal";
|
||||||
|
import { OptionType, Plugin } from "../../utils/types";
|
||||||
|
import { filters } from "../../webpack";
|
||||||
|
import { Button, FluxDispatcher, React, Text, Tooltip, UserStore, UserUtils } from "../../webpack/common";
|
||||||
|
import ErrorBoundary from "../ErrorBoundary";
|
||||||
|
import { Flex } from "../Flex";
|
||||||
|
import {
|
||||||
|
SettingBooleanComponent,
|
||||||
|
SettingInputComponent,
|
||||||
|
SettingNumericComponent,
|
||||||
|
SettingSelectComponent,
|
||||||
|
SettingSliderComponent
|
||||||
|
} from "./components";
|
||||||
|
|
||||||
|
const UserSummaryItem = lazyWebpack(filters.byCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
|
||||||
|
const AvatarStyles = lazyWebpack(filters.byProps(["moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"]));
|
||||||
|
const UserRecord: Constructor<Partial<User>> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any;
|
||||||
|
|
||||||
|
interface PluginModalProps extends ModalProps {
|
||||||
|
plugin: Plugin;
|
||||||
|
onRestartNeeded(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** To stop discord making unwanted requests... */
|
||||||
|
function makeDummyUser(user: { name: string, id: BigInt; }) {
|
||||||
|
const newUser = new UserRecord({
|
||||||
|
username: user.name,
|
||||||
|
id: generateId(),
|
||||||
|
bot: true,
|
||||||
|
});
|
||||||
|
FluxDispatcher.dispatch({
|
||||||
|
type: "USER_UPDATE",
|
||||||
|
user: newUser,
|
||||||
|
});
|
||||||
|
return newUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
||||||
|
const [authors, setAuthors] = React.useState<Partial<User>[]>([]);
|
||||||
|
|
||||||
|
const pluginSettings = useSettings().plugins[plugin.name];
|
||||||
|
|
||||||
|
const [tempSettings, setTempSettings] = React.useState<Record<string, any>>({});
|
||||||
|
|
||||||
|
const [errors, setErrors] = React.useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
|
const canSubmit = () => Object.values(errors).every(e => !e);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
for (const user of plugin.authors.slice(0, 6)) {
|
||||||
|
const author = user.id ? await UserUtils.fetchUser(`${user.id}`).catch(() => null) : makeDummyUser(user);
|
||||||
|
setAuthors(a => [...a, author || makeDummyUser(user)]);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function saveAndClose() {
|
||||||
|
if (!plugin.options) {
|
||||||
|
onClose();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let restartNeeded = false;
|
||||||
|
for (const [key, value] of Object.entries(tempSettings)) {
|
||||||
|
const option = plugin.options[key];
|
||||||
|
pluginSettings[key] = value;
|
||||||
|
option?.onChange?.(value);
|
||||||
|
if (option?.restartNeeded) restartNeeded = true;
|
||||||
|
}
|
||||||
|
if (restartNeeded) onRestartNeeded();
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSettings() {
|
||||||
|
if (!pluginSettings || !plugin.options) {
|
||||||
|
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const options: JSX.Element[] = [];
|
||||||
|
for (const [key, setting] of Object.entries(plugin.options)) {
|
||||||
|
function onChange(newValue) {
|
||||||
|
setTempSettings(s => ({ ...s, [key]: newValue }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function onError(hasError: boolean) {
|
||||||
|
setErrors(e => ({ ...e, [key]: hasError }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = { onChange, pluginSettings, id: key, onError };
|
||||||
|
switch (setting.type) {
|
||||||
|
case OptionType.SELECT: {
|
||||||
|
options.push(<SettingSelectComponent key={key} option={setting} {...props} />);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OptionType.STRING: {
|
||||||
|
options.push(<SettingInputComponent key={key} option={setting} {...props} />);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OptionType.NUMBER:
|
||||||
|
case OptionType.BIGINT: {
|
||||||
|
options.push(<SettingNumericComponent key={key} option={setting} {...props} />);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OptionType.BOOLEAN: {
|
||||||
|
options.push(<SettingBooleanComponent key={key} option={setting} {...props} />);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OptionType.SLIDER: {
|
||||||
|
options.push(<SettingSliderComponent key={key} option={setting} {...props} />);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <Flex flexDirection="column" style={{ gap: 12 }}>{options}</Flex>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderMoreUsers(_label: string, count: number) {
|
||||||
|
const sliceCount = plugin.authors.length - count;
|
||||||
|
const sliceStart = plugin.authors.length - sliceCount;
|
||||||
|
const sliceEnd = sliceStart + plugin.authors.length - count;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip text={plugin.authors.slice(sliceStart, sliceEnd).map(u => u.name).join(", ")}>
|
||||||
|
{({ onMouseEnter, onMouseLeave }) => (
|
||||||
|
<div
|
||||||
|
className={AvatarStyles.moreUsers}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
>
|
||||||
|
+{sliceCount}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM}>
|
||||||
|
<ModalHeader>
|
||||||
|
<Text variant="heading-md/bold">{plugin.name}</Text>
|
||||||
|
</ModalHeader>
|
||||||
|
<ModalContent style={{ marginBottom: 8, marginTop: 8 }}>
|
||||||
|
<Forms.FormSection>
|
||||||
|
<Forms.FormTitle tag="h3">About {plugin.name}</Forms.FormTitle>
|
||||||
|
<Forms.FormText>{plugin.description}</Forms.FormText>
|
||||||
|
<div style={{ marginTop: 8, marginBottom: 8, width: "fit-content" }}>
|
||||||
|
<UserSummaryItem
|
||||||
|
users={authors}
|
||||||
|
count={plugin.authors.length}
|
||||||
|
guildId={undefined}
|
||||||
|
renderIcon={false}
|
||||||
|
max={6}
|
||||||
|
showDefaultAvatarsForNullUsers
|
||||||
|
showUserPopout
|
||||||
|
renderMoreUsers={renderMoreUsers}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Forms.FormSection>
|
||||||
|
{!!plugin.settingsAboutComponent && (
|
||||||
|
<div style={{ marginBottom: 8 }}>
|
||||||
|
<Forms.FormSection>
|
||||||
|
<ErrorBoundary message="An error occurred while rendering this plugin's custom InfoComponent">
|
||||||
|
<plugin.settingsAboutComponent />
|
||||||
|
</ErrorBoundary>
|
||||||
|
</Forms.FormSection>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Forms.FormSection>
|
||||||
|
<Forms.FormTitle tag="h3">Settings</Forms.FormTitle>
|
||||||
|
{renderSettings()}
|
||||||
|
</Forms.FormSection>
|
||||||
|
</ModalContent>
|
||||||
|
<ModalFooter>
|
||||||
|
<Flex>
|
||||||
|
<Button
|
||||||
|
onClick={onClose}
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
color={Button.Colors.RED}
|
||||||
|
>
|
||||||
|
Exit Without Saving
|
||||||
|
</Button>
|
||||||
|
<Tooltip text="You must fix all errors before saving" shouldShow={!canSubmit()}>
|
||||||
|
{({ onMouseEnter, onMouseLeave }) => (
|
||||||
|
<Button
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
color={Button.Colors.BRAND}
|
||||||
|
onClick={saveAndClose}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
disabled={!canSubmit()}
|
||||||
|
>
|
||||||
|
Save & Exit
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
</Flex>
|
||||||
|
</ModalFooter>
|
||||||
|
</ModalRoot>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Forms } from "@components";
|
||||||
|
|
||||||
|
import { PluginOptionBoolean } from "../../../utils/types";
|
||||||
|
import { React, Select } from "../../../webpack/common";
|
||||||
|
import { ISettingElementProps } from ".";
|
||||||
|
|
||||||
|
export function SettingBooleanComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionBoolean>) {
|
||||||
|
const def = pluginSettings[id] ?? option.default;
|
||||||
|
|
||||||
|
const [state, setState] = React.useState(def ?? false);
|
||||||
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
onError(error !== null);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
{ label: "Enabled", value: true, default: def === true },
|
||||||
|
{ label: "Disabled", value: false, default: typeof def === "undefined" || def === false },
|
||||||
|
];
|
||||||
|
|
||||||
|
function handleChange(newValue: boolean): void {
|
||||||
|
const isValid = (option.isValid && option.isValid(newValue)) ?? true;
|
||||||
|
if (typeof isValid === "string") setError(isValid);
|
||||||
|
else if (!isValid) setError("Invalid input provided.");
|
||||||
|
else {
|
||||||
|
setError(null);
|
||||||
|
setState(newValue);
|
||||||
|
onChange(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Forms.FormSection>
|
||||||
|
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
||||||
|
<Select
|
||||||
|
isDisabled={option.disabled?.() ?? false}
|
||||||
|
options={options}
|
||||||
|
placeholder={option.placeholder ?? "Select an option"}
|
||||||
|
maxVisibleItems={5}
|
||||||
|
closeOnSelect={true}
|
||||||
|
select={handleChange}
|
||||||
|
isSelected={v => v === state}
|
||||||
|
serialize={v => String(v)}
|
||||||
|
{...option.componentProps}
|
||||||
|
/>
|
||||||
|
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
|
||||||
|
</Forms.FormSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Forms } from "@components";
|
||||||
|
|
||||||
|
import { OptionType, PluginOptionNumber } from "../../../utils/types";
|
||||||
|
import { React, TextInput } from "../../../webpack/common";
|
||||||
|
import { ISettingElementProps } from ".";
|
||||||
|
|
||||||
|
const MAX_SAFE_NUMBER = BigInt(Number.MAX_SAFE_INTEGER);
|
||||||
|
|
||||||
|
export function SettingNumericComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionNumber>) {
|
||||||
|
function serialize(value: any) {
|
||||||
|
if (option.type === OptionType.BIGINT) return BigInt(value);
|
||||||
|
return Number(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [state, setState] = React.useState<any>(`${pluginSettings[id] ?? option.default ?? 0}`);
|
||||||
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
onError(error !== null);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
function handleChange(newValue) {
|
||||||
|
const isValid = (option.isValid && option.isValid(newValue)) ?? true;
|
||||||
|
if (typeof isValid === "string") setError(isValid);
|
||||||
|
else if (!isValid) setError("Invalid input provided.");
|
||||||
|
else if (option.type === OptionType.NUMBER && BigInt(newValue) >= MAX_SAFE_NUMBER) {
|
||||||
|
setState(`${Number.MAX_SAFE_INTEGER}`);
|
||||||
|
onChange(serialize(newValue));
|
||||||
|
} else {
|
||||||
|
setState(newValue);
|
||||||
|
onChange(serialize(newValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Forms.FormSection>
|
||||||
|
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
||||||
|
<TextInput
|
||||||
|
type="number"
|
||||||
|
pattern="-?[0-9]+"
|
||||||
|
value={state}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder={option.placeholder ?? "Enter a number"}
|
||||||
|
disabled={option.disabled?.() ?? false}
|
||||||
|
{...option.componentProps}
|
||||||
|
/>
|
||||||
|
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
|
||||||
|
</Forms.FormSection>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FormSection, FormText, FormTitle } from "@components/Forms";
|
||||||
|
import Select from "@components/Select";
|
||||||
|
|
||||||
|
import { PluginOptionSelect } from "../../../utils/types";
|
||||||
|
import { React } from "../../../webpack/common";
|
||||||
|
import { ISettingElementProps } from ".";
|
||||||
|
|
||||||
|
export function SettingSelectComponent({ option, pluginSettings, onChange, onError, id }: ISettingElementProps<PluginOptionSelect>) {
|
||||||
|
const def = pluginSettings[id] ?? option.options?.find(o => o.default)?.value;
|
||||||
|
|
||||||
|
const [state, setState] = React.useState<any>(def ?? null);
|
||||||
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
onError(error !== null);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
function handleChange(newValue) {
|
||||||
|
const isValid = (option.isValid && option.isValid(newValue)) ?? true;
|
||||||
|
if (typeof isValid === "string") setError(isValid);
|
||||||
|
else if (!isValid) setError("Invalid input provided.");
|
||||||
|
else {
|
||||||
|
setState(newValue);
|
||||||
|
onChange(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormSection>
|
||||||
|
<FormTitle>{option.description}</FormTitle>
|
||||||
|
<Select
|
||||||
|
isDisabled={option.disabled?.() ?? false}
|
||||||
|
options={option.options}
|
||||||
|
placeholder={option.placeholder ?? "Select an option"}
|
||||||
|
maxVisibleItems={5}
|
||||||
|
closeOnSelect={true}
|
||||||
|
select={handleChange}
|
||||||
|
isSelected={v => v === state}
|
||||||
|
serialize={v => String(v)}
|
||||||
|
{...option.componentProps}
|
||||||
|
/>
|
||||||
|
{error && <FormText style={{ color: "var(--text-danger)" }}>{error}</FormText>}
|
||||||
|
</FormSection>
|
||||||
|
);
|
||||||
|
}
|
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Forms } from "@components";
|
||||||
|
|
||||||
|
import { PluginOptionSlider } from "../../../utils/types";
|
||||||
|
import { React, Slider } from "../../../webpack/common";
|
||||||
|
import { ISettingElementProps } from ".";
|
||||||
|
|
||||||
|
export function makeRange(start: number, end: number, step = 1) {
|
||||||
|
const ranges: number[] = [];
|
||||||
|
for (let value = start; value <= end; value += step) {
|
||||||
|
ranges.push(Math.round(value * 100) / 100);
|
||||||
|
}
|
||||||
|
return ranges;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SettingSliderComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionSlider>) {
|
||||||
|
const def = pluginSettings[id] ?? option.default;
|
||||||
|
|
||||||
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
onError(error !== null);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
function handleChange(newValue: number): void {
|
||||||
|
const isValid = (option.isValid && option.isValid(newValue)) ?? true;
|
||||||
|
if (typeof isValid === "string") setError(isValid);
|
||||||
|
else if (!isValid) setError("Invalid input provided.");
|
||||||
|
else {
|
||||||
|
setError(null);
|
||||||
|
onChange(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Forms.FormSection>
|
||||||
|
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
||||||
|
<Slider
|
||||||
|
disabled={option.disabled?.() ?? false}
|
||||||
|
markers={option.markers}
|
||||||
|
minValue={option.markers[0]}
|
||||||
|
maxValue={option.markers[option.markers.length - 1]}
|
||||||
|
initialValue={def}
|
||||||
|
onValueChange={handleChange}
|
||||||
|
onValueRender={(v: number) => String(v.toFixed(2))}
|
||||||
|
stickToMarkers={option.stickToMarkers ?? true}
|
||||||
|
{...option.componentProps}
|
||||||
|
/>
|
||||||
|
</Forms.FormSection>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Forms } from "@components";
|
||||||
|
|
||||||
|
import { PluginOptionString } from "../../../utils/types";
|
||||||
|
import { React, TextInput } from "../../../webpack/common";
|
||||||
|
import { ISettingElementProps } from ".";
|
||||||
|
|
||||||
|
export function SettingInputComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionString>) {
|
||||||
|
const [state, setState] = React.useState(pluginSettings[id] ?? option.default ?? null);
|
||||||
|
const [error, setError] = React.useState<string | null>(null);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
onError(error !== null);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
function handleChange(newValue) {
|
||||||
|
const isValid = (option.isValid && option.isValid(newValue)) ?? true;
|
||||||
|
if (typeof isValid === "string") setError(isValid);
|
||||||
|
else if (!isValid) setError("Invalid input provided.");
|
||||||
|
else {
|
||||||
|
setState(newValue);
|
||||||
|
onChange(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Forms.FormSection>
|
||||||
|
<Forms.FormTitle>{option.description}</Forms.FormTitle>
|
||||||
|
<TextInput
|
||||||
|
type="text"
|
||||||
|
value={state}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder={option.placeholder ?? "Enter a value"}
|
||||||
|
disabled={option.disabled?.() ?? false}
|
||||||
|
{...option.componentProps}
|
||||||
|
/>
|
||||||
|
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
|
||||||
|
</Forms.FormSection>
|
||||||
|
);
|
||||||
|
}
|
36
src/components/PluginSettings/components/index.ts
Normal file
36
src/components/PluginSettings/components/index.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { PluginOptionBase } from "../../../utils/types";
|
||||||
|
|
||||||
|
export interface ISettingElementProps<T extends PluginOptionBase> {
|
||||||
|
option: T;
|
||||||
|
onChange(newValue: any): void;
|
||||||
|
pluginSettings: {
|
||||||
|
[setting: string]: any;
|
||||||
|
enabled: boolean;
|
||||||
|
};
|
||||||
|
id: string;
|
||||||
|
onError(hasError: boolean): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export * from "./SettingBooleanComponent";
|
||||||
|
export * from "./SettingNumericComponent";
|
||||||
|
export * from "./SettingSelectComponent";
|
||||||
|
export * from "./SettingSliderComponent";
|
||||||
|
export * from "./SettingTextComponent";
|
323
src/components/PluginSettings/index.tsx
Normal file
323
src/components/PluginSettings/index.tsx
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FormDivider, FormSection, FormText, FormTitle } from "@components/Forms";
|
||||||
|
|
||||||
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
|
import { showNotice } from "../../api/Notices";
|
||||||
|
import { Settings, useSettings } from "../../api/settings";
|
||||||
|
import { startDependenciesRecursive, startPlugin, stopPlugin } from "../../plugins";
|
||||||
|
import { Logger, Modals } from "../../utils";
|
||||||
|
import { ChangeList } from "../../utils/ChangeList";
|
||||||
|
import { classes, lazyWebpack } from "../../utils/misc";
|
||||||
|
import { Plugin } from "../../utils/types";
|
||||||
|
import { filters } from "../../webpack";
|
||||||
|
import { Alerts, Button, Margins, Parser, React, Switch, Text, TextInput, Toasts, Tooltip } from "../../webpack/common";
|
||||||
|
import ErrorBoundary from "../ErrorBoundary";
|
||||||
|
import { ErrorCard } from "../ErrorCard";
|
||||||
|
import { Flex } from "../Flex";
|
||||||
|
import PluginModal from "./PluginModal";
|
||||||
|
import * as styles from "./styles";
|
||||||
|
|
||||||
|
const logger = new Logger("PluginSettings", "#a6d189");
|
||||||
|
|
||||||
|
const Select = lazyWebpack(filters.byCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems"));
|
||||||
|
const InputStyles = lazyWebpack(filters.byProps(["inputDefault", "inputWrapper"]));
|
||||||
|
|
||||||
|
const CogWheel = lazyWebpack(filters.byCode("18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069"));
|
||||||
|
const InfoIcon = lazyWebpack(filters.byCode("4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16"));
|
||||||
|
|
||||||
|
function showErrorToast(message: string) {
|
||||||
|
Toasts.show({
|
||||||
|
message,
|
||||||
|
type: Toasts.Type.FAILURE,
|
||||||
|
id: Toasts.genId(),
|
||||||
|
options: {
|
||||||
|
position: Toasts.Position.BOTTOM
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReloadRequiredCardProps extends React.HTMLProps<HTMLDivElement> {
|
||||||
|
plugins: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function ReloadRequiredCard({ plugins, ...props }: ReloadRequiredCardProps) {
|
||||||
|
if (plugins.length === 0) return null;
|
||||||
|
|
||||||
|
const pluginPrefix = plugins.length === 1 ? "The plugin" : "The following plugins require a reload to apply changes:";
|
||||||
|
const pluginSuffix = plugins.length === 1 ? " requires a reload to apply changes." : ".";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorCard {...props} style={{ padding: "1em", display: "grid", gridTemplateColumns: "1fr auto", gap: 8, ...props.style }}>
|
||||||
|
<span style={{ margin: "auto 0" }}>
|
||||||
|
{pluginPrefix} <code>{plugins.join(", ")}</code>{pluginSuffix}
|
||||||
|
</span>
|
||||||
|
<Button look={Button.Looks.INVERTED} onClick={() => location.reload()}>Reload</Button>
|
||||||
|
</ErrorCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
|
||||||
|
plugin: Plugin;
|
||||||
|
disabled: boolean;
|
||||||
|
onRestartNeeded(name: string): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave }: PluginCardProps) {
|
||||||
|
const settings = useSettings();
|
||||||
|
const pluginSettings = settings.plugins[plugin.name];
|
||||||
|
|
||||||
|
const [iconHover, setIconHover] = React.useState(false);
|
||||||
|
|
||||||
|
function isEnabled() {
|
||||||
|
return pluginSettings?.enabled || plugin.started;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openModal() {
|
||||||
|
Modals.openModalLazy(async () => {
|
||||||
|
return modalProps => {
|
||||||
|
return <PluginModal {...modalProps} plugin={plugin} onRestartNeeded={() => onRestartNeeded(plugin.name)} />;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleEnabled() {
|
||||||
|
const wasEnabled = isEnabled();
|
||||||
|
|
||||||
|
// If we're enabling a plugin, make sure all deps are enabled recursively.
|
||||||
|
if (!wasEnabled) {
|
||||||
|
const { restartNeeded, failures } = startDependenciesRecursive(plugin);
|
||||||
|
if (failures.length) {
|
||||||
|
logger.error(`Failed to start dependencies for ${plugin.name}: ${failures.join(", ")}`);
|
||||||
|
showNotice("Failed to start dependencies: " + failures.join(", "), "Close", () => null);
|
||||||
|
return;
|
||||||
|
} else if (restartNeeded) {
|
||||||
|
// If any dependencies have patches, don't start the plugin yet.
|
||||||
|
pluginSettings.enabled = true;
|
||||||
|
onRestartNeeded(plugin.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the plugin has patches, dont use stopPlugin/startPlugin. Wait for restart to apply changes.
|
||||||
|
if (plugin.patches) {
|
||||||
|
pluginSettings.enabled = !wasEnabled;
|
||||||
|
onRestartNeeded(plugin.name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the plugin is enabled, but hasn't been started, then we can just toggle it off.
|
||||||
|
if (wasEnabled && !plugin.started) {
|
||||||
|
pluginSettings.enabled = !wasEnabled;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = wasEnabled ? stopPlugin(plugin) : startPlugin(plugin);
|
||||||
|
const action = wasEnabled ? "stop" : "start";
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
logger.error(`Failed to ${action} plugin ${plugin.name}`);
|
||||||
|
showErrorToast(`Failed to ${action} plugin: ${plugin.name}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginSettings.enabled = !wasEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex style={styles.PluginsGridItem} flexDirection="column" onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave}>
|
||||||
|
<Switch
|
||||||
|
onChange={toggleEnabled}
|
||||||
|
disabled={disabled}
|
||||||
|
value={isEnabled()}
|
||||||
|
note={<Text variant="text-md/normal" style={{ height: 40, overflow: "hidden" }}>{plugin.description}</Text>}
|
||||||
|
hideBorder={true}
|
||||||
|
>
|
||||||
|
<Flex style={{ marginTop: "auto", width: "100%", height: "100%", alignItems: "center" }}>
|
||||||
|
<Text variant="text-md/bold" style={{ flexGrow: "1" }}>{plugin.name}</Text>
|
||||||
|
<button role="switch" onClick={() => openModal()} style={styles.SettingsIcon} className="button-12Fmur">
|
||||||
|
{plugin.options
|
||||||
|
? <CogWheel
|
||||||
|
style={{ color: iconHover ? "" : "var(--text-muted)" }}
|
||||||
|
onMouseEnter={() => setIconHover(true)}
|
||||||
|
onMouseLeave={() => setIconHover(false)}
|
||||||
|
/>
|
||||||
|
: <InfoIcon
|
||||||
|
width="24" height="24"
|
||||||
|
style={{ color: iconHover ? "" : "var(--text-muted)" }}
|
||||||
|
onMouseEnter={() => setIconHover(true)}
|
||||||
|
onMouseLeave={() => setIconHover(false)}
|
||||||
|
/>}
|
||||||
|
</button>
|
||||||
|
</Flex>
|
||||||
|
</Switch>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ErrorBoundary.wrap(function Settings() {
|
||||||
|
const settings = useSettings();
|
||||||
|
const changes = React.useMemo(() => new ChangeList<string>(), []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
return () => void (changes.hasChanges && Alerts.show({
|
||||||
|
title: "Restart required",
|
||||||
|
body: (
|
||||||
|
<>
|
||||||
|
<p>The following plugins require a restart:</p>
|
||||||
|
<div>{changes.map((s, i) => (
|
||||||
|
<>
|
||||||
|
{i > 0 && ", "}
|
||||||
|
{Parser.parse("`" + s + "`")}
|
||||||
|
</>
|
||||||
|
))}</div>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
confirmText: "Restart now",
|
||||||
|
cancelText: "Later!",
|
||||||
|
onConfirm: () => location.reload()
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const depMap = React.useMemo(() => {
|
||||||
|
const o = {} as Record<string, string[]>;
|
||||||
|
for (const plugin in Plugins) {
|
||||||
|
const deps = Plugins[plugin].dependencies;
|
||||||
|
if (deps) {
|
||||||
|
for (const dep of deps) {
|
||||||
|
o[dep] ??= [];
|
||||||
|
o[dep].push(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
function hasDependents(plugin: Plugin) {
|
||||||
|
const enabledDependants = depMap[plugin.name]?.filter(d => settings.plugins[d].enabled);
|
||||||
|
return !!enabledDependants?.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortedPlugins = React.useMemo(() => Object.values(Plugins)
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name)), []);
|
||||||
|
|
||||||
|
const [searchValue, setSearchValue] = React.useState({ value: "", status: "all" });
|
||||||
|
|
||||||
|
const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query }));
|
||||||
|
const onStatusChange = (status: string) => setSearchValue(prev => ({ ...prev, status }));
|
||||||
|
|
||||||
|
const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => {
|
||||||
|
const showEnabled = searchValue.status === "enabled" || searchValue.status === "all";
|
||||||
|
const showDisabled = searchValue.status === "disabled" || searchValue.status === "all";
|
||||||
|
const enabled = settings.plugins[plugin.name]?.enabled || plugin.started;
|
||||||
|
return (
|
||||||
|
((showEnabled && enabled) || (showDisabled && !enabled)) &&
|
||||||
|
(
|
||||||
|
plugin.name.toLowerCase().includes(searchValue.value.toLowerCase()) ||
|
||||||
|
plugin.description.toLowerCase().includes(searchValue.value.toLowerCase())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormSection tag="h1" title="Vencord">
|
||||||
|
<FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}>
|
||||||
|
Plugins
|
||||||
|
</FormTitle>
|
||||||
|
|
||||||
|
<ReloadRequiredCard plugins={[...changes.getChanges()]} style={{ marginBottom: 16 }} />
|
||||||
|
|
||||||
|
<div style={styles.FiltersBar}>
|
||||||
|
<TextInput value={searchValue.value} placeholder={"Search for a plugin..."} onChange={onSearch} style={{ marginBottom: 24 }} />
|
||||||
|
<div className={InputStyles.inputWrapper}>
|
||||||
|
<Select
|
||||||
|
className={InputStyles.inputDefault}
|
||||||
|
options={[
|
||||||
|
{ label: "Show All", value: "all", default: true },
|
||||||
|
{ label: "Show Enabled", value: "enabled" },
|
||||||
|
{ label: "Show Disabled", value: "disabled" }
|
||||||
|
]}
|
||||||
|
serialize={v => String(v)}
|
||||||
|
select={onStatusChange}
|
||||||
|
isSelected={v => v === searchValue.status}
|
||||||
|
closeOnSelect={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={styles.PluginsGrid}>
|
||||||
|
{sortedPlugins?.length ? sortedPlugins
|
||||||
|
.filter(a => !a.required && !dependencyCheck(a.name, depMap).length && pluginFilter(a))
|
||||||
|
.map(plugin => {
|
||||||
|
const enabledDependants = depMap[plugin.name]?.filter(d => settings.plugins[d].enabled);
|
||||||
|
const dependency = enabledDependants?.length;
|
||||||
|
return <PluginCard
|
||||||
|
onRestartNeeded={name => changes.add(name)}
|
||||||
|
disabled={plugin.required || !!dependency}
|
||||||
|
plugin={plugin}
|
||||||
|
/>;
|
||||||
|
})
|
||||||
|
: <Text variant="text-md/normal">No plugins meet search criteria.</Text>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<FormDivider />
|
||||||
|
<FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}>
|
||||||
|
Required Plugins
|
||||||
|
</FormTitle>
|
||||||
|
<div style={styles.PluginsGrid}>
|
||||||
|
{sortedPlugins?.length ? sortedPlugins
|
||||||
|
.filter(a => a.required || dependencyCheck(a.name, depMap).length && pluginFilter(a))
|
||||||
|
.map(plugin => {
|
||||||
|
const enabledDependants = depMap[plugin.name]?.filter(d => settings.plugins[d].enabled);
|
||||||
|
const dependency = enabledDependants?.length;
|
||||||
|
const tooltipText = plugin.required
|
||||||
|
? "This plugin is required for Vencord to function."
|
||||||
|
: makeDependencyList(dependencyCheck(plugin.name, depMap));
|
||||||
|
return <Tooltip text={tooltipText}>
|
||||||
|
{({ onMouseLeave, onMouseEnter }) => (
|
||||||
|
<PluginCard
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onRestartNeeded={name => changes.add(name)}
|
||||||
|
disabled={plugin.required || !!dependency}
|
||||||
|
plugin={plugin}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Tooltip>;
|
||||||
|
})
|
||||||
|
: <Text variant="text-md/normal">No plugins meet search criteria.</Text>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</FormSection>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function makeDependencyList(deps: string[]) {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<FormText>This plugin is required by:</FormText>
|
||||||
|
{deps.map((dep: string) => <FormText style={{ margin: "0 auto" }}>{dep}</FormText>)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dependencyCheck(pluginName: string, depMap: Record<string, string[]>): string[] {
|
||||||
|
return depMap[pluginName]?.filter(d => Settings.plugins[d].enabled) || [];
|
||||||
|
}
|
50
src/components/PluginSettings/styles.ts
Normal file
50
src/components/PluginSettings/styles.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const PluginsGrid: React.CSSProperties = {
|
||||||
|
marginTop: 16,
|
||||||
|
display: "grid",
|
||||||
|
gridGap: 16,
|
||||||
|
gridTemplateColumns: "repeat(auto-fill, minmax(250px, 1fr))",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PluginsGridItem: React.CSSProperties = {
|
||||||
|
backgroundColor: "var(--background-modifier-selected)",
|
||||||
|
color: "var(--interactive-active)",
|
||||||
|
borderRadius: 3,
|
||||||
|
cursor: "pointer",
|
||||||
|
display: "block",
|
||||||
|
height: "min-content",
|
||||||
|
padding: 10,
|
||||||
|
width: "100%",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const FiltersBar: React.CSSProperties = {
|
||||||
|
gap: 10,
|
||||||
|
height: 40,
|
||||||
|
gridTemplateColumns: "1fr 150px",
|
||||||
|
display: "grid"
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SettingsIcon: React.CSSProperties = {
|
||||||
|
height: "24px",
|
||||||
|
width: "24px",
|
||||||
|
padding: "0",
|
||||||
|
background: "transparent",
|
||||||
|
marginRight: 8
|
||||||
|
};
|
@ -1,30 +1,36 @@
|
|||||||
import { classes, humanFriendlyJoin, useAwaiter } from "../utils/misc";
|
/*
|
||||||
import Plugins from 'plugins';
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FormDivider, FormSection, FormText, FormTitle } from "@components/Forms";
|
||||||
|
|
||||||
import { useSettings } from "../api/settings";
|
import { useSettings } from "../api/settings";
|
||||||
|
import { ChangeList } from "../utils/ChangeList";
|
||||||
import IpcEvents from "../utils/IpcEvents";
|
import IpcEvents from "../utils/IpcEvents";
|
||||||
|
import { useAwaiter } from "../utils/misc";
|
||||||
import { Button, Switch, Forms, React, Margins, Toasts, Alerts, Parser } from "../webpack/common";
|
import { Alerts, Button, Margins, Parser, React, Switch } from "../webpack/common";
|
||||||
import ErrorBoundary from "./ErrorBoundary";
|
import ErrorBoundary from "./ErrorBoundary";
|
||||||
import { startPlugin } from "../plugins";
|
import { Flex } from "./Flex";
|
||||||
import { stopPlugin } from '../plugins/index';
|
import { launchMonacoEditor } from "./Monaco";
|
||||||
import { Flex } from './Flex';
|
|
||||||
import { ChangeList } from '../utils/ChangeList';
|
|
||||||
|
|
||||||
function showErrorToast(message: string) {
|
|
||||||
Toasts.show({
|
|
||||||
message,
|
|
||||||
type: Toasts.Type.FAILURE,
|
|
||||||
id: Toasts.genId(),
|
|
||||||
options: {
|
|
||||||
position: Toasts.Position.BOTTOM
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ErrorBoundary.wrap(function Settings() {
|
export default ErrorBoundary.wrap(function Settings() {
|
||||||
const [settingsDir, , settingsDirPending] = useAwaiter(() => VencordNative.ipc.invoke<string>(IpcEvents.GET_SETTINGS_DIR), "Loading...");
|
const [settingsDir, , settingsDirPending] = useAwaiter(() => VencordNative.ipc.invoke<string>(IpcEvents.GET_SETTINGS_DIR), "Loading...");
|
||||||
const settings = useSettings();
|
const settings = useSettings();
|
||||||
const changes = React.useMemo(() => new ChangeList<string>, []);
|
const changes = React.useMemo(() => new ChangeList<string>(), []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
return () => void (changes.hasChanges && Alerts.show({
|
return () => void (changes.hasChanges && Alerts.show({
|
||||||
@ -35,7 +41,7 @@ export default ErrorBoundary.wrap(function Settings() {
|
|||||||
<div>{changes.map((s, i) => (
|
<div>{changes.map((s, i) => (
|
||||||
<>
|
<>
|
||||||
{i > 0 && ", "}
|
{i > 0 && ", "}
|
||||||
{Parser.parse('`' + s + '`')}
|
{Parser.parse("`" + s + "`")}
|
||||||
</>
|
</>
|
||||||
))}</div>
|
))}</div>
|
||||||
</>
|
</>
|
||||||
@ -46,33 +52,17 @@ export default ErrorBoundary.wrap(function Settings() {
|
|||||||
}));
|
}));
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const depMap = React.useMemo(() => {
|
|
||||||
const o = {} as Record<string, string[]>;
|
|
||||||
for (const plugin in Plugins) {
|
|
||||||
const deps = Plugins[plugin].dependencies;
|
|
||||||
if (deps) {
|
|
||||||
for (const dep of deps) {
|
|
||||||
o[dep] ??= [];
|
|
||||||
o[dep].push(plugin);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return o;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const sortedPlugins = React.useMemo(() => Object.values(Plugins).sort((a, b) => a.name.localeCompare(b.name)), []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection tag="h1" title="Vencord">
|
<FormSection tag="h1" title="Vencord">
|
||||||
<Forms.FormTitle tag="h5">
|
<FormTitle tag="h5">
|
||||||
Settings
|
Settings
|
||||||
</Forms.FormTitle>
|
</FormTitle>
|
||||||
|
|
||||||
<Forms.FormText>
|
<FormText>
|
||||||
SettingsDir: <code style={{ userSelect: 'text', cursor: 'text' }}>{settingsDir}</code>
|
Settings Directory: <code style={{ userSelect: "text", cursor: "text" }}>{settingsDir}</code>
|
||||||
</Forms.FormText>
|
</FormText>
|
||||||
|
|
||||||
<Flex className={classes(Margins.marginBottom20)}>
|
{!IS_WEB && <Flex className={Margins.marginBottom20} style={{ marginTop: 8 }}>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => window.DiscordNative.app.relaunch()}
|
onClick={() => window.DiscordNative.app.relaunch()}
|
||||||
size={Button.Sizes.SMALL}
|
size={Button.Sizes.SMALL}
|
||||||
@ -88,86 +78,44 @@ export default ErrorBoundary.wrap(function Settings() {
|
|||||||
Launch Directory
|
Launch Directory
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => VencordNative.ipc.invoke(IpcEvents.OPEN_PATH, settingsDir, "quickCss.css")}
|
onClick={() => VencordNative.ipc.invoke(IpcEvents.OPEN_MONACO_EDITOR)}
|
||||||
size={Button.Sizes.SMALL}
|
size={Button.Sizes.SMALL}
|
||||||
disabled={settingsDir === "Loading..."}
|
disabled={settingsDir === "Loading..."}
|
||||||
>
|
>
|
||||||
Open QuickCSS File
|
Open QuickCSS File
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>}
|
||||||
<Forms.FormDivider />
|
|
||||||
<Forms.FormTitle tag="h5">Settings</Forms.FormTitle>
|
{IS_WEB && <Button
|
||||||
|
onClick={launchMonacoEditor}
|
||||||
|
size={Button.Sizes.SMALL}
|
||||||
|
disabled={settingsDir === "Loading..."}
|
||||||
|
>
|
||||||
|
Open QuickCSS File
|
||||||
|
</Button>}
|
||||||
|
|
||||||
|
<FormDivider />
|
||||||
<Switch
|
<Switch
|
||||||
value={settings.useQuickCss}
|
value={settings.useQuickCss}
|
||||||
onChange={(v: boolean) => settings.useQuickCss = v}
|
onChange={(v: boolean) => settings.useQuickCss = v}
|
||||||
note="Enable QuickCSS"
|
note="Loads styles from your QuickCss file"
|
||||||
>
|
>
|
||||||
Use QuickCss
|
Use QuickCss
|
||||||
</Switch>
|
</Switch>
|
||||||
<Switch
|
{!IS_WEB && <Switch
|
||||||
|
value={settings.enableReactDevtools}
|
||||||
|
onChange={(v: boolean) => settings.enableReactDevtools = v}
|
||||||
|
note="Requires a full restart"
|
||||||
|
>
|
||||||
|
Enable React Developer Tools
|
||||||
|
</Switch>}
|
||||||
|
{!IS_WEB && <Switch
|
||||||
value={settings.notifyAboutUpdates}
|
value={settings.notifyAboutUpdates}
|
||||||
onChange={(v: boolean) => settings.notifyAboutUpdates = v}
|
onChange={(v: boolean) => settings.notifyAboutUpdates = v}
|
||||||
note="Shows a Toast on StartUp"
|
note="Shows a Toast on StartUp"
|
||||||
>
|
>
|
||||||
Get notified about new Updates
|
Get notified about new Updates
|
||||||
</Switch>
|
</Switch>}
|
||||||
<Switch
|
</FormSection>
|
||||||
value={settings.unsafeRequire}
|
|
||||||
onChange={(v: boolean) => settings.unsafeRequire = v}
|
|
||||||
note="Enables VencordNative.require. Useful for testing, very bad for security. Leave this off unless you need it."
|
|
||||||
>
|
|
||||||
Enable Unsafe Require
|
|
||||||
</Switch>
|
|
||||||
|
|
||||||
<Forms.FormDivider />
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}>
|
|
||||||
Plugins
|
|
||||||
</Forms.FormTitle>
|
|
||||||
|
|
||||||
{sortedPlugins.map(p => {
|
|
||||||
const enabledDependants = depMap[p.name]?.filter(d => settings.plugins[d].enabled);
|
|
||||||
const dependency = enabledDependants?.length;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Switch
|
|
||||||
disabled={p.required || dependency}
|
|
||||||
key={p.name}
|
|
||||||
value={settings.plugins[p.name].enabled || p.required || dependency}
|
|
||||||
onChange={v => {
|
|
||||||
settings.plugins[p.name].enabled = v;
|
|
||||||
if (v) {
|
|
||||||
p.dependencies?.forEach(d => {
|
|
||||||
settings.plugins[d].enabled = true;
|
|
||||||
if (!Plugins[d].started && !stopPlugin) {
|
|
||||||
settings.plugins[p.name].enabled = false;
|
|
||||||
showErrorToast(`Failed to start dependency ${d}. Check the console for more info.`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!p.started && !startPlugin(p)) {
|
|
||||||
showErrorToast(`Failed to start plugin ${p.name}. Check the console for more info.`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (p.started && !stopPlugin(p)) {
|
|
||||||
showErrorToast(`Failed to stop plugin ${p.name}. Check the console for more info.`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (p.patches) changes.handleChange(p.name);
|
|
||||||
}}
|
|
||||||
note={p.description}
|
|
||||||
tooltipNote={
|
|
||||||
p.required ?
|
|
||||||
"This plugin is required. Thus you cannot disable it."
|
|
||||||
: dependency ?
|
|
||||||
`${humanFriendlyJoin(enabledDependants)} ${enabledDependants.length === 1 ? "depends" : "depend"} on this plugin. Thus you cannot disable it.`
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{p.name}
|
|
||||||
</Switch>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</Forms.FormSection >
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,12 +1,32 @@
|
|||||||
import gitHash from "git-hash";
|
/*
|
||||||
import { changes, checkForUpdates, getRepo, rebuild, update, UpdateLogger, updateError } from '../utils/updater';
|
* Vencord, a modification for Discord's desktop app
|
||||||
import { React, Forms, Button, Margins, Alerts, Card, Parser, Toasts } from '../webpack/common';
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
import { Flex } from "./Flex";
|
*
|
||||||
import { useAwaiter } from '../utils/misc';
|
* This program is free software: you can redistribute it and/or modify
|
||||||
import { Link } from "./Link";
|
* 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 { Forms } from "@components";
|
||||||
|
|
||||||
|
import gitHash from "~git-hash";
|
||||||
|
|
||||||
|
import { classes, useAwaiter } from "../utils/misc";
|
||||||
|
import { changes, checkForUpdates, getRepo, isNewer, rebuild, update, updateError, UpdateLogger } from "../utils/updater";
|
||||||
|
import { Alerts, Button, Card, Margins, Parser, React, Toasts } from "../webpack/common";
|
||||||
import ErrorBoundary from "./ErrorBoundary";
|
import ErrorBoundary from "./ErrorBoundary";
|
||||||
import { ErrorCard } from "./ErrorCard";
|
import { ErrorCard } from "./ErrorCard";
|
||||||
|
import { Flex } from "./Flex";
|
||||||
|
import { Link } from "./Link";
|
||||||
|
|
||||||
function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>>, action: () => any) {
|
function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>>, action: () => any) {
|
||||||
return async () => {
|
return async () => {
|
||||||
@ -43,35 +63,40 @@ function withDispatcher(dispatcher: React.Dispatch<React.SetStateAction<boolean>
|
|||||||
dispatcher(false);
|
dispatcher(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ErrorBoundary.wrap(function Updater() {
|
interface CommonProps {
|
||||||
const [repo, err, repoPending] = useAwaiter(getRepo, "Loading...");
|
repo: string;
|
||||||
|
repoPending: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Changes({ updates, repo, repoPending }: CommonProps & { updates: typeof changes; }) {
|
||||||
|
return (
|
||||||
|
<Card style={{ padding: ".5em" }}>
|
||||||
|
{updates.map(({ hash, author, message }) => (
|
||||||
|
<div>
|
||||||
|
<Link href={`${repo}/commit/${hash}`} disabled={repoPending}>
|
||||||
|
<code>{hash}</code>
|
||||||
|
</Link>
|
||||||
|
<span style={{
|
||||||
|
marginLeft: "0.5em",
|
||||||
|
color: "var(--text-normal)"
|
||||||
|
}}>{message} - {author}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Updatable(props: CommonProps) {
|
||||||
|
const [updates, setUpdates] = React.useState(changes);
|
||||||
const [isChecking, setIsChecking] = React.useState(false);
|
const [isChecking, setIsChecking] = React.useState(false);
|
||||||
const [isUpdating, setIsUpdating] = React.useState(false);
|
const [isUpdating, setIsUpdating] = React.useState(false);
|
||||||
const [updates, setUpdates] = React.useState(changes);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
const isOutdated = (updates?.length ?? 0) > 0;
|
||||||
if (err)
|
|
||||||
UpdateLogger.error("Failed to retrieve repo", err);
|
|
||||||
}, [err]);
|
|
||||||
|
|
||||||
const isOutdated = updates?.length > 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Forms.FormSection tag="h1" title="Vencord Updater">
|
<>
|
||||||
<Forms.FormTitle tag="h5">Repo</Forms.FormTitle>
|
|
||||||
|
|
||||||
<Forms.FormText>{repoPending ? repo : err ? "Failed to retrieve - check console" : (
|
|
||||||
<Link href={repo}>
|
|
||||||
{repo.split("/").slice(-2).join("/")}
|
|
||||||
</Link>
|
|
||||||
)} ({gitHash})</Forms.FormText>
|
|
||||||
|
|
||||||
<Forms.FormDivider />
|
|
||||||
|
|
||||||
<Forms.FormTitle tag="h5">Updates</Forms.FormTitle>
|
|
||||||
|
|
||||||
{!updates && updateError ? (
|
{!updates && updateError ? (
|
||||||
<>
|
<>
|
||||||
<Forms.FormText>Failed to check updates. Check the console for more info</Forms.FormText>
|
<Forms.FormText>Failed to check updates. Check the console for more info</Forms.FormText>
|
||||||
@ -79,31 +104,15 @@ export default ErrorBoundary.wrap(function Updater() {
|
|||||||
<p>{updateError.stderr || updateError.stdout || "An unknown error occurred"}</p>
|
<p>{updateError.stderr || updateError.stdout || "An unknown error occurred"}</p>
|
||||||
</ErrorCard>
|
</ErrorCard>
|
||||||
</>
|
</>
|
||||||
) :
|
) : (
|
||||||
(
|
<Forms.FormText className={Margins.marginBottom8}>
|
||||||
<Forms.FormText className={Margins.marginBottom8}>
|
{isOutdated ? `There are ${updates.length} Updates` : "Up to Date!"}
|
||||||
{isOutdated ? `There are ${updates.length} Updates` : "Up to Date!"}
|
</Forms.FormText>
|
||||||
</Forms.FormText>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
{isOutdated && (
|
|
||||||
<Card style={{ padding: ".5em" }}>
|
|
||||||
{updates.map(({ hash, author, message }) => (
|
|
||||||
<div>
|
|
||||||
<Link href={`${repo}/commit/${hash}`} disabled={repoPending}>
|
|
||||||
<code>{hash}</code>
|
|
||||||
</Link>
|
|
||||||
<span style={{
|
|
||||||
marginLeft: "0.5em",
|
|
||||||
color: "var(--text-normal)"
|
|
||||||
}}>{message} - {author}</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</Card>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Flex className={`${Margins.marginBottom8} ${Margins.marginTop8}`}>
|
{isOutdated && <Changes updates={updates} {...props} />}
|
||||||
|
|
||||||
|
<Flex className={classes(Margins.marginBottom8, Margins.marginTop8)}>
|
||||||
{isOutdated && <Button
|
{isOutdated && <Button
|
||||||
size={Button.Sizes.SMALL}
|
size={Button.Sizes.SMALL}
|
||||||
disabled={isUpdating || isChecking}
|
disabled={isUpdating || isChecking}
|
||||||
@ -155,6 +164,51 @@ export default ErrorBoundary.wrap(function Updater() {
|
|||||||
Check for Updates
|
Check for Updates
|
||||||
</Button>
|
</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Newer(props: CommonProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Forms.FormText className={Margins.marginBottom8}>
|
||||||
|
Your local copy has more recent commits. Please stash or reset them.
|
||||||
|
</Forms.FormText>
|
||||||
|
<Changes {...props} updates={changes} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Updater() {
|
||||||
|
const [repo, err, repoPending] = useAwaiter(getRepo, "Loading...");
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (err)
|
||||||
|
UpdateLogger.error("Failed to retrieve repo", err);
|
||||||
|
}, [err]);
|
||||||
|
|
||||||
|
const commonProps: CommonProps = {
|
||||||
|
repo,
|
||||||
|
repoPending
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Forms.FormSection tag="h1" title="Vencord Updater">
|
||||||
|
<Forms.FormTitle tag="h5">Repo</Forms.FormTitle>
|
||||||
|
|
||||||
|
<Forms.FormText>{repoPending ? repo : err ? "Failed to retrieve - check console" : (
|
||||||
|
<Link href={repo}>
|
||||||
|
{repo.split("/").slice(-2).join("/")}
|
||||||
|
</Link>
|
||||||
|
)} ({gitHash})</Forms.FormText>
|
||||||
|
|
||||||
|
<Forms.FormDivider />
|
||||||
|
|
||||||
|
<Forms.FormTitle tag="h5">Updates</Forms.FormTitle>
|
||||||
|
|
||||||
|
{isNewer ? <Newer {...commonProps} /> : <Updatable {...commonProps} />}
|
||||||
</Forms.FormSection >
|
</Forms.FormSection >
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
export default IS_WEB ? null : ErrorBoundary.wrap(Updater);
|
||||||
|
@ -1,2 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { default as PluginSettings } from "./PluginSettings";
|
||||||
export { default as Settings } from "./Settings";
|
export { default as Settings } from "./Settings";
|
||||||
export { default as Updater } from "./Updater";
|
export { default as Updater } from "./Updater";
|
||||||
|
52
src/components/monacoWin.html
Normal file
52
src/components/monacoWin.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>QuickCss Editor</title>
|
||||||
|
<link rel="stylesheet" data-name="vs/editor/editor.main"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.min.css">
|
||||||
|
<style>
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
#container {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="container"></div>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.min.js"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
require.config({ paths: { 'vs': 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs' } });
|
||||||
|
require(["vs/editor/editor.main"], () => {
|
||||||
|
getCurrentCss().then(css => {
|
||||||
|
var editor = monaco.editor.create(document.getElementById('container'), {
|
||||||
|
value: css,
|
||||||
|
language: 'css',
|
||||||
|
theme: getTheme(),
|
||||||
|
});
|
||||||
|
editor.onDidChangeModelContent(() =>
|
||||||
|
setCss(editor.getValue())
|
||||||
|
);
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
// make monaco re-layout
|
||||||
|
editor.layout();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
44
src/globals.d.ts
vendored
44
src/globals.d.ts
vendored
@ -1,16 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
|
/**
|
||||||
|
* This exists only at build time, so references to it in patches should insert it
|
||||||
|
* via String interpolation OR use different replacement code based on this
|
||||||
|
* but NEVER refrence it inside the patched code
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // BAD
|
||||||
|
* replace: "IS_WEB?foo:bar"
|
||||||
|
* // GOOD
|
||||||
|
* replace: IS_WEB ? "foo" : "bar"
|
||||||
|
* // also good
|
||||||
|
* replace: `${IS_WEB}?foo:bar`
|
||||||
|
*/
|
||||||
|
export var IS_WEB: boolean;
|
||||||
|
export var IS_STANDALONE: boolean;
|
||||||
|
|
||||||
export var VencordNative: typeof import("./VencordNative").default;
|
export var VencordNative: typeof import("./VencordNative").default;
|
||||||
export var Vencord: typeof import("./Vencord");
|
export var Vencord: typeof import("./Vencord");
|
||||||
export var appSettings: {
|
export var appSettings: {
|
||||||
set(setting: string, v: any): void;
|
set(setting: string, v: any): void;
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Only available when running in Electron, undefined on web.
|
||||||
|
* Thus, avoid using this or only use it inside an {@link IS_WEB} guard.
|
||||||
|
*
|
||||||
|
* If you really must use it, mark your plugin as Desktop App only via
|
||||||
|
* `target: "DESKTOP"`
|
||||||
|
*/
|
||||||
|
export var DiscordNative: any;
|
||||||
|
|
||||||
interface Window {
|
interface Window {
|
||||||
webpackChunkdiscord_app: {
|
webpackChunkdiscord_app: {
|
||||||
push(chunk: any): any;
|
push(chunk: any): any;
|
||||||
pop(): any;
|
pop(): any;
|
||||||
};
|
};
|
||||||
[k: PropertyKey]: any;
|
[k: string]: any;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
35
src/ipcMain/constants.ts
Normal file
35
src/ipcMain/constants.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { app } from "electron";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
export const DATA_DIR = process.env.VENCORD_USER_DATA_DIR ?? (
|
||||||
|
process.env.DISCORD_USER_DATA_DIR
|
||||||
|
? join(process.env.DISCORD_USER_DATA_DIR, "..", "VencordData")
|
||||||
|
: join(app.getPath("userData"), "..", "Vencord")
|
||||||
|
);
|
||||||
|
export const SETTINGS_DIR = join(DATA_DIR, "settings");
|
||||||
|
export const QUICKCSS_PATH = join(SETTINGS_DIR, "quickCss.css");
|
||||||
|
export const SETTINGS_FILE = join(SETTINGS_DIR, "settings.json");
|
||||||
|
export const ALLOWED_PROTOCOLS = [
|
||||||
|
"https:",
|
||||||
|
"http:",
|
||||||
|
"steam:",
|
||||||
|
"spotify:"
|
||||||
|
];
|
57
src/ipcMain/crxToZip.ts
Normal file
57
src/ipcMain/crxToZip.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/* eslint-disable header/header */
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* crxToZip
|
||||||
|
* Copyright (c) 2013 Rob Wu <rob@robwu.nl>
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function crxToZip(buf: Buffer) {
|
||||||
|
function calcLength(a: number, b: number, c: number, d: number) {
|
||||||
|
let length = 0;
|
||||||
|
|
||||||
|
length += a << 0;
|
||||||
|
length += b << 8;
|
||||||
|
length += c << 16;
|
||||||
|
length += d << 24 >>> 0;
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 50 4b 03 04
|
||||||
|
// This is actually a zip file
|
||||||
|
if (buf[0] === 80 && buf[1] === 75 && buf[2] === 3 && buf[3] === 4) {
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 43 72 32 34 (Cr24)
|
||||||
|
if (buf[0] !== 67 || buf[1] !== 114 || buf[2] !== 50 || buf[3] !== 52) {
|
||||||
|
throw new Error("Invalid header: Does not start with Cr24");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 02 00 00 00
|
||||||
|
// or
|
||||||
|
// 03 00 00 00
|
||||||
|
const isV3 = buf[4] === 3;
|
||||||
|
const isV2 = buf[4] === 2;
|
||||||
|
|
||||||
|
if ((!isV2 && !isV3) || buf[5] || buf[6] || buf[7]) {
|
||||||
|
throw new Error("Unexpected crx format version number.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isV2) {
|
||||||
|
const publicKeyLength = calcLength(buf[8], buf[9], buf[10], buf[11]);
|
||||||
|
const signatureLength = calcLength(buf[12], buf[13], buf[14], buf[15]);
|
||||||
|
|
||||||
|
// 16 = Magic number (4), CRX format version (4), lengths (2x4)
|
||||||
|
const zipStartOffset = 16 + publicKeyLength + signatureLength;
|
||||||
|
|
||||||
|
return buf.subarray(zipStartOffset, buf.length);
|
||||||
|
}
|
||||||
|
// v3 format has header size and then header
|
||||||
|
const headerSize = calcLength(buf[8], buf[9], buf[10], buf[11]);
|
||||||
|
const zipStartOffset = 12 + headerSize;
|
||||||
|
|
||||||
|
return buf.subarray(zipStartOffset, buf.length);
|
||||||
|
}
|
76
src/ipcMain/extensions.ts
Normal file
76
src/ipcMain/extensions.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { session } from "electron";
|
||||||
|
import { unzip } from "fflate";
|
||||||
|
import { constants as fsConstants } from "fs";
|
||||||
|
import { access, mkdir, rm, writeFile } from "fs/promises";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
import { DATA_DIR } from "./constants";
|
||||||
|
import { crxToZip } from "./crxToZip";
|
||||||
|
import { get } from "./simpleGet";
|
||||||
|
|
||||||
|
const extensionCacheDir = join(DATA_DIR, "ExtensionCache");
|
||||||
|
|
||||||
|
async function extract(data: Buffer, outDir: string) {
|
||||||
|
await mkdir(outDir, { recursive: true });
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
unzip(data, (err, files) => {
|
||||||
|
if (err) return void reject(err);
|
||||||
|
Promise.all(Object.keys(files).map(async f => {
|
||||||
|
// Signature stuff
|
||||||
|
// 'Cannot load extension with file or directory name
|
||||||
|
// _metadata. Filenames starting with "_" are reserved for use by the system.';
|
||||||
|
if (f.startsWith("_metadata/")) return;
|
||||||
|
|
||||||
|
if (f.endsWith("/")) return void mkdir(join(outDir, f), { recursive: true });
|
||||||
|
|
||||||
|
const pathElements = f.split("/");
|
||||||
|
const name = pathElements.pop()!;
|
||||||
|
const directories = pathElements.join("/");
|
||||||
|
const dir = join(outDir, directories);
|
||||||
|
|
||||||
|
if (directories) {
|
||||||
|
await mkdir(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
await writeFile(join(dir, name), files[f]);
|
||||||
|
}))
|
||||||
|
.then(() => resolve())
|
||||||
|
.catch(err => {
|
||||||
|
rm(outDir, { recursive: true, force: true });
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function installExt(id: string) {
|
||||||
|
const extDir = join(extensionCacheDir, `${id}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await access(extDir, fsConstants.F_OK);
|
||||||
|
} catch (err) {
|
||||||
|
const url = `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=32`;
|
||||||
|
const buf = await get(url);
|
||||||
|
await extract(crxToZip(buf), extDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.defaultSession.loadExtension(extDir);
|
||||||
|
}
|
@ -1,16 +1,34 @@
|
|||||||
import { app, BrowserWindow, desktopCapturer, ipcMain, shell } from "electron";
|
/*
|
||||||
import { mkdirSync, readFileSync, watch } from "fs";
|
* Vencord, a modification for Discord's desktop app
|
||||||
import { open, readFile, writeFile } from "fs/promises";
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
import { join } from 'path';
|
*
|
||||||
import { debounce } from "../utils/debounce";
|
* This program is free software: you can redistribute it and/or modify
|
||||||
import IpcEvents from '../utils/IpcEvents';
|
* 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 "./updater";
|
import "./updater";
|
||||||
|
|
||||||
const DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
import { BrowserWindow, desktopCapturer, ipcMain, shell } from "electron";
|
||||||
const SETTINGS_DIR = join(DATA_DIR, "settings");
|
import { mkdirSync, readFileSync, watch } from "fs";
|
||||||
const QUICKCSS_PATH = join(SETTINGS_DIR, "quickCss.css");
|
import { open, readFile, writeFile } from "fs/promises";
|
||||||
const SETTINGS_FILE = join(SETTINGS_DIR, "settings.json");
|
import { join } from "path";
|
||||||
|
|
||||||
|
import monacoHtml from "~fileContent/../components/monacoWin.html;base64";
|
||||||
|
|
||||||
|
import { debounce } from "../utils/debounce";
|
||||||
|
import IpcEvents from "../utils/IpcEvents";
|
||||||
|
import { Queue } from "../utils/Queue";
|
||||||
|
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE } from "./constants";
|
||||||
|
|
||||||
mkdirSync(SETTINGS_DIR, { recursive: true });
|
mkdirSync(SETTINGS_DIR, { recursive: true });
|
||||||
|
|
||||||
@ -18,7 +36,7 @@ function readCss() {
|
|||||||
return readFile(QUICKCSS_PATH, "utf-8").catch(() => "");
|
return readFile(QUICKCSS_PATH, "utf-8").catch(() => "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function readSettings() {
|
export function readSettings() {
|
||||||
try {
|
try {
|
||||||
return readFileSync(SETTINGS_FILE, "utf-8");
|
return readFileSync(SETTINGS_FILE, "utf-8");
|
||||||
} catch {
|
} catch {
|
||||||
@ -29,18 +47,33 @@ function readSettings() {
|
|||||||
// Fix for screensharing in Electron >= 17
|
// Fix for screensharing in Electron >= 17
|
||||||
ipcMain.handle(IpcEvents.GET_DESKTOP_CAPTURE_SOURCES, (_, opts) => desktopCapturer.getSources(opts));
|
ipcMain.handle(IpcEvents.GET_DESKTOP_CAPTURE_SOURCES, (_, opts) => desktopCapturer.getSources(opts));
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.OPEN_PATH, (_, ...pathElements) => shell.openPath(join(...pathElements)));
|
ipcMain.handle(IpcEvents.OPEN_QUICKCSS, () => shell.openPath(QUICKCSS_PATH));
|
||||||
ipcMain.handle(IpcEvents.OPEN_EXTERNAL, (_, url) => shell.openExternal(url));
|
|
||||||
|
|
||||||
|
ipcMain.handle(IpcEvents.OPEN_EXTERNAL, (_, url) => {
|
||||||
|
try {
|
||||||
|
var { protocol } = new URL(url);
|
||||||
|
} catch {
|
||||||
|
throw "Malformed URL";
|
||||||
|
}
|
||||||
|
if (!ALLOWED_PROTOCOLS.includes(protocol))
|
||||||
|
throw "Disallowed protocol.";
|
||||||
|
|
||||||
|
shell.openExternal(url);
|
||||||
|
});
|
||||||
|
|
||||||
|
const cssWriteQueue = new Queue();
|
||||||
|
const settingsWriteQueue = new Queue();
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.GET_QUICK_CSS, () => readCss());
|
ipcMain.handle(IpcEvents.GET_QUICK_CSS, () => readCss());
|
||||||
|
ipcMain.handle(IpcEvents.SET_QUICK_CSS, (_, css) =>
|
||||||
|
cssWriteQueue.add(() => writeFile(QUICKCSS_PATH, css))
|
||||||
|
);
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR);
|
ipcMain.handle(IpcEvents.GET_SETTINGS_DIR, () => SETTINGS_DIR);
|
||||||
ipcMain.on(IpcEvents.GET_SETTINGS, (e) => e.returnValue = readSettings());
|
ipcMain.on(IpcEvents.GET_SETTINGS, e => e.returnValue = readSettings());
|
||||||
|
|
||||||
let settingsWriteQueue = Promise.resolve();
|
|
||||||
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, s) => {
|
ipcMain.handle(IpcEvents.SET_SETTINGS, (_, s) => {
|
||||||
settingsWriteQueue = settingsWriteQueue.then(() => writeFile(SETTINGS_FILE, s));
|
settingsWriteQueue.add(() => writeFile(SETTINGS_FILE, s));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
@ -52,3 +85,13 @@ export function initIpc(mainWindow: BrowserWindow) {
|
|||||||
}, 50));
|
}, 50));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => {
|
||||||
|
const win = new BrowserWindow({
|
||||||
|
title: "QuickCss Editor",
|
||||||
|
webPreferences: {
|
||||||
|
preload: join(__dirname, "preload.js"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await win.loadURL(`data:text/html;base64,${monacoHtml}`);
|
||||||
|
});
|
||||||
|
37
src/ipcMain/simpleGet.ts
Normal file
37
src/ipcMain/simpleGet.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import https from "https";
|
||||||
|
|
||||||
|
export function get(url: string, options: https.RequestOptions = {}) {
|
||||||
|
return new Promise<Buffer>((resolve, reject) => {
|
||||||
|
https.get(url, options, res => {
|
||||||
|
const { statusCode, statusMessage, headers } = res;
|
||||||
|
if (statusCode! >= 400)
|
||||||
|
return void reject(`${statusCode}: ${statusMessage} - ${url}`);
|
||||||
|
if (statusCode! >= 300)
|
||||||
|
return void resolve(get(headers.location!, options));
|
||||||
|
|
||||||
|
const chunks = [] as Buffer[];
|
||||||
|
res.on("error", reject);
|
||||||
|
|
||||||
|
res.on("data", chunk => chunks.push(chunk));
|
||||||
|
res.once("end", () => resolve(Buffer.concat(chunks)));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -1,94 +0,0 @@
|
|||||||
import { ipcMain } from 'electron';
|
|
||||||
import { promisify } from "util";
|
|
||||||
import IpcEvents from "../utils/IpcEvents";
|
|
||||||
import { execFile as cpExecFile } from 'child_process';
|
|
||||||
import { join } from 'path';
|
|
||||||
import { createReadStream } from 'fs';
|
|
||||||
import { createHash } from 'crypto';
|
|
||||||
|
|
||||||
const VENCORD_SRC_DIR = join(__dirname, "..");
|
|
||||||
|
|
||||||
const execFile = promisify(cpExecFile);
|
|
||||||
|
|
||||||
function git(...args: string[]) {
|
|
||||||
return execFile("git", args, {
|
|
||||||
cwd: VENCORD_SRC_DIR
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function calculateHashes() {
|
|
||||||
const hashes = {} as Record<string, string>;
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
["patcher.js", "preload.js", "renderer.js"].map(file => new Promise<void>(r => {
|
|
||||||
const fis = createReadStream(join(__dirname, file));
|
|
||||||
const hash = createHash("sha1", { encoding: "hex" });
|
|
||||||
fis.once("end", () => {
|
|
||||||
hash.end();
|
|
||||||
hashes[file] = hash.read();
|
|
||||||
r();
|
|
||||||
});
|
|
||||||
fis.pipe(hash);
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
return hashes;
|
|
||||||
}
|
|
||||||
|
|
||||||
function serializeErrors(func: (...args: any[]) => any) {
|
|
||||||
return async function () {
|
|
||||||
try {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
value: await func(...arguments)
|
|
||||||
};
|
|
||||||
} catch (e: any) {
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
error: e instanceof Error ? {
|
|
||||||
// prototypes get lost, so turn error into plain object
|
|
||||||
...e
|
|
||||||
} : e
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getRepo() {
|
|
||||||
const res = await git("remote", "get-url", "origin");
|
|
||||||
return res.stdout.trim()
|
|
||||||
.replace(/git@(.+):/, "https://$1/")
|
|
||||||
.replace(/\.git$/, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function calculateGitChanges() {
|
|
||||||
await git("fetch");
|
|
||||||
|
|
||||||
const res = await git("log", `HEAD...origin/main`, "--pretty=format:%an/%h/%s");
|
|
||||||
|
|
||||||
const commits = res.stdout.trim();
|
|
||||||
return commits ? commits.split("\n").map(line => {
|
|
||||||
const [author, hash, ...rest] = line.split("/");
|
|
||||||
return {
|
|
||||||
hash, author, message: rest.join("/")
|
|
||||||
};
|
|
||||||
}) : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function pull() {
|
|
||||||
const res = await git("pull");
|
|
||||||
return res.stdout.includes("Fast-forward");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function build() {
|
|
||||||
const res = await execFile("node", ["build.mjs"], {
|
|
||||||
cwd: VENCORD_SRC_DIR
|
|
||||||
});
|
|
||||||
return !res.stderr.includes("Build failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
ipcMain.handle(IpcEvents.GET_HASHES, serializeErrors(calculateHashes));
|
|
||||||
ipcMain.handle(IpcEvents.GET_REPO, serializeErrors(getRepo));
|
|
||||||
ipcMain.handle(IpcEvents.GET_UPDATES, serializeErrors(calculateGitChanges));
|
|
||||||
ipcMain.handle(IpcEvents.UPDATE, serializeErrors(pull));
|
|
||||||
ipcMain.handle(IpcEvents.BUILD, serializeErrors(build));
|
|
59
src/ipcMain/updater/common.ts
Normal file
59
src/ipcMain/updater/common.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createHash } from "crypto";
|
||||||
|
import { createReadStream } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
export async function calculateHashes() {
|
||||||
|
const hashes = {} as Record<string, string>;
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
["patcher.js", "preload.js", "renderer.js"].map(file => new Promise<void>(r => {
|
||||||
|
const fis = createReadStream(join(__dirname, file));
|
||||||
|
const hash = createHash("sha1", { encoding: "hex" });
|
||||||
|
fis.once("end", () => {
|
||||||
|
hash.end();
|
||||||
|
hashes[file] = hash.read();
|
||||||
|
r();
|
||||||
|
});
|
||||||
|
fis.pipe(hash);
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
return hashes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeErrors(func: (...args: any[]) => any) {
|
||||||
|
return async function () {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
value: await func(...arguments)
|
||||||
|
};
|
||||||
|
} catch (e: any) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
error: e instanceof Error ? {
|
||||||
|
// prototypes get lost, so turn error into plain object
|
||||||
|
...e
|
||||||
|
} : e
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
74
src/ipcMain/updater/git.ts
Normal file
74
src/ipcMain/updater/git.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { execFile as cpExecFile } from "child_process";
|
||||||
|
import { ipcMain } from "electron";
|
||||||
|
import { join } from "path";
|
||||||
|
import { promisify } from "util";
|
||||||
|
|
||||||
|
import IpcEvents from "../../utils/IpcEvents";
|
||||||
|
import { calculateHashes, serializeErrors } from "./common";
|
||||||
|
|
||||||
|
const VENCORD_SRC_DIR = join(__dirname, "..");
|
||||||
|
|
||||||
|
const execFile = promisify(cpExecFile);
|
||||||
|
|
||||||
|
function git(...args: string[]) {
|
||||||
|
return execFile("git", args, {
|
||||||
|
cwd: VENCORD_SRC_DIR
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getRepo() {
|
||||||
|
const res = await git("remote", "get-url", "origin");
|
||||||
|
return res.stdout.trim()
|
||||||
|
.replace(/git@(.+):/, "https://$1/")
|
||||||
|
.replace(/\.git$/, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function calculateGitChanges() {
|
||||||
|
await git("fetch");
|
||||||
|
|
||||||
|
const res = await git("log", "HEAD...origin/main", "--pretty=format:%an/%h/%s");
|
||||||
|
|
||||||
|
const commits = res.stdout.trim();
|
||||||
|
return commits ? commits.split("\n").map(line => {
|
||||||
|
const [author, hash, ...rest] = line.split("/");
|
||||||
|
return {
|
||||||
|
hash, author, message: rest.join("/")
|
||||||
|
};
|
||||||
|
}) : [];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function pull() {
|
||||||
|
const res = await git("pull");
|
||||||
|
return res.stdout.includes("Fast-forward");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function build() {
|
||||||
|
const res = await execFile("node", ["scripts/build/build.mjs"], {
|
||||||
|
cwd: VENCORD_SRC_DIR
|
||||||
|
});
|
||||||
|
return !res.stderr.includes("Build failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle(IpcEvents.GET_HASHES, serializeErrors(calculateHashes));
|
||||||
|
ipcMain.handle(IpcEvents.GET_REPO, serializeErrors(getRepo));
|
||||||
|
ipcMain.handle(IpcEvents.GET_UPDATES, serializeErrors(calculateGitChanges));
|
||||||
|
ipcMain.handle(IpcEvents.UPDATE, serializeErrors(pull));
|
||||||
|
ipcMain.handle(IpcEvents.BUILD, serializeErrors(build));
|
86
src/ipcMain/updater/http.ts
Normal file
86
src/ipcMain/updater/http.ts
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ipcMain } from "electron";
|
||||||
|
import { writeFile } from "fs/promises";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
import gitHash from "~git-hash";
|
||||||
|
import gitRemote from "~git-remote";
|
||||||
|
|
||||||
|
import { VENCORD_USER_AGENT } from "../../utils/constants";
|
||||||
|
import IpcEvents from "../../utils/IpcEvents";
|
||||||
|
import { get } from "../simpleGet";
|
||||||
|
import { calculateHashes, serializeErrors } from "./common";
|
||||||
|
|
||||||
|
const API_BASE = `https://api.github.com/repos/${gitRemote}`;
|
||||||
|
let PendingUpdates = [] as [string, Buffer][];
|
||||||
|
|
||||||
|
async function githubGet(endpoint: string) {
|
||||||
|
return get(API_BASE + endpoint, {
|
||||||
|
headers: {
|
||||||
|
Accept: "application/vnd.github+json",
|
||||||
|
// "All API requests MUST include a valid User-Agent header.
|
||||||
|
// Requests with no User-Agent header will be rejected."
|
||||||
|
"User-Agent": VENCORD_USER_AGENT,
|
||||||
|
// todo: perhaps add support for (optional) api token?
|
||||||
|
// unauthorised rate limit is 60 reqs/h
|
||||||
|
// https://github.com/settings/tokens/new?description=Vencord%20Updater
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function calculateGitChanges() {
|
||||||
|
const res = await githubGet(`/compare/${gitHash}...HEAD`);
|
||||||
|
|
||||||
|
const data = JSON.parse(res.toString("utf-8"));
|
||||||
|
return data.commits.map(c => ({
|
||||||
|
// github api only sends the long sha
|
||||||
|
hash: c.sha.slice(0, 7),
|
||||||
|
author: c.author.login,
|
||||||
|
message: c.commit.message
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchUpdates() {
|
||||||
|
const release = await githubGet("/releases/latest");
|
||||||
|
|
||||||
|
const data = JSON.parse(release.toString());
|
||||||
|
const hash = data.name.slice(data.name.lastIndexOf(" ") + 1);
|
||||||
|
if (hash === gitHash)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
await Promise.all(data.assets.map(async ({ name, browser_download_url }) => {
|
||||||
|
if (["patcher.js", "preload.js", "renderer.js"].some(s => name.startsWith(s))) {
|
||||||
|
PendingUpdates.push([name, await get(browser_download_url)]);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function applyUpdates() {
|
||||||
|
await Promise.all(PendingUpdates.map(([name, data]) => writeFile(join(__dirname, name), data)));
|
||||||
|
PendingUpdates = [];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle(IpcEvents.GET_HASHES, serializeErrors(calculateHashes));
|
||||||
|
ipcMain.handle(IpcEvents.GET_REPO, serializeErrors(() => `https://github.com/${gitRemote}`));
|
||||||
|
ipcMain.handle(IpcEvents.GET_UPDATES, serializeErrors(calculateGitChanges));
|
||||||
|
ipcMain.handle(IpcEvents.UPDATE, serializeErrors(fetchUpdates));
|
||||||
|
ipcMain.handle(IpcEvents.BUILD, serializeErrors(applyUpdates));
|
19
src/ipcMain/updater/index.ts
Normal file
19
src/ipcMain/updater/index.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import(IS_STANDALONE ? "./http" : "./git");
|
39
src/modules.d.ts
vendored
Normal file
39
src/modules.d.ts
vendored
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line spaced-comment
|
||||||
|
/// <reference types="standalone-electron-types"/>
|
||||||
|
|
||||||
|
declare module "~plugins" {
|
||||||
|
const plugins: Record<string, import("./utils/types").Plugin>;
|
||||||
|
export default plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "~git-hash" {
|
||||||
|
const hash: string;
|
||||||
|
export default hash;
|
||||||
|
}
|
||||||
|
declare module "~git-remote" {
|
||||||
|
const remote: string;
|
||||||
|
export default remote;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module "~fileContent/*" {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
107
src/patchWin32Updater.ts
Normal file
107
src/patchWin32Updater.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { app, autoUpdater } from "electron";
|
||||||
|
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "fs";
|
||||||
|
import { basename, dirname, join } from "path";
|
||||||
|
|
||||||
|
const { setAppUserModelId } = app;
|
||||||
|
|
||||||
|
// Apparently requiring Discords updater too early leads into issues,
|
||||||
|
// copied this workaround from powerCord
|
||||||
|
app.setAppUserModelId = function (id: string) {
|
||||||
|
app.setAppUserModelId = setAppUserModelId;
|
||||||
|
|
||||||
|
setAppUserModelId.call(this, id);
|
||||||
|
|
||||||
|
patchUpdater();
|
||||||
|
};
|
||||||
|
|
||||||
|
function isNewer($new: string, old: string) {
|
||||||
|
const newParts = $new.slice(4).split(".").map(Number);
|
||||||
|
const oldParts = old.slice(4).split(".").map(Number);
|
||||||
|
|
||||||
|
for (let i = 0; i < oldParts.length; i++) {
|
||||||
|
if (newParts[i] > oldParts[i]) return true;
|
||||||
|
if (newParts[i] < oldParts[i]) return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchLatest() {
|
||||||
|
const currentAppPath = dirname(process.execPath);
|
||||||
|
const currentVersion = basename(currentAppPath);
|
||||||
|
const discordPath = join(currentAppPath, "..");
|
||||||
|
|
||||||
|
const latestVersion = readdirSync(discordPath).reduce((prev, curr) => {
|
||||||
|
return (curr.startsWith("app-") && isNewer(curr, prev))
|
||||||
|
? curr
|
||||||
|
: prev;
|
||||||
|
}, currentVersion as string);
|
||||||
|
|
||||||
|
if (latestVersion === currentVersion) return;
|
||||||
|
|
||||||
|
const app = join(discordPath, latestVersion, "resources", "app");
|
||||||
|
if (existsSync(app)) return;
|
||||||
|
|
||||||
|
console.info("[Vencord] Detected Host Update. Repatching...");
|
||||||
|
|
||||||
|
const patcherPath = join(__dirname, "patcher.js");
|
||||||
|
mkdirSync(app);
|
||||||
|
writeFileSync(join(app, "package.json"), JSON.stringify({
|
||||||
|
name: "discord",
|
||||||
|
main: "index.js"
|
||||||
|
}));
|
||||||
|
writeFileSync(join(app, "index.js"), `require(${JSON.stringify(patcherPath)});`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows Host Updates install to a new folder app-{HOST_VERSION}, so we
|
||||||
|
// need to reinject
|
||||||
|
function patchUpdater() {
|
||||||
|
const main = require.main!;
|
||||||
|
const buildInfo = require(join(process.resourcesPath, "build_info.json"));
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (buildInfo?.newUpdater) {
|
||||||
|
const autoStartScript = join(main.filename, "..", "autoStart", "win32.js");
|
||||||
|
const { update } = require(autoStartScript);
|
||||||
|
|
||||||
|
// New Updater Injection
|
||||||
|
require.cache[autoStartScript]!.exports.update = function () {
|
||||||
|
patchLatest();
|
||||||
|
update.apply(this, arguments);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const hostUpdaterScript = join(main.filename, "..", "hostUpdater.js");
|
||||||
|
const { quitAndInstall } = require(hostUpdaterScript);
|
||||||
|
|
||||||
|
// Old Updater Injection
|
||||||
|
require.cache[hostUpdaterScript]!.exports.quitAndInstall = function () {
|
||||||
|
patchLatest();
|
||||||
|
quitAndInstall.apply(this, arguments);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// OpenAsar uses electrons autoUpdater on Windows
|
||||||
|
const { quitAndInstall } = autoUpdater;
|
||||||
|
autoUpdater.quitAndInstall = function () {
|
||||||
|
patchLatest();
|
||||||
|
quitAndInstall.call(this);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import electron, { app, BrowserWindowConstructorOptions } from "electron";
|
import electron, { app, BrowserWindowConstructorOptions } from "electron";
|
||||||
import installExt, { REACT_DEVELOPER_TOOLS } from "electron-devtools-installer";
|
import { readFileSync } from "fs";
|
||||||
import { join } from "path";
|
import { dirname, join } from "path";
|
||||||
import { initIpc } from './ipcMain';
|
|
||||||
|
import { initIpc } from "./ipcMain";
|
||||||
|
import { installExt } from "./ipcMain/extensions";
|
||||||
|
import { readSettings } from "./ipcMain/index";
|
||||||
|
|
||||||
console.log("[Vencord] Starting up...");
|
console.log("[Vencord] Starting up...");
|
||||||
|
|
||||||
|
// Our injector file at app/index.js
|
||||||
|
const injectorPath = require.main!.filename;
|
||||||
|
|
||||||
|
// special discord_arch_electron injection method
|
||||||
|
const asarName = injectorPath.endsWith("app.asar/index.js") ? "_app.asar" : "app.asar";
|
||||||
|
|
||||||
|
// The original app.asar
|
||||||
|
const asarPath = join(dirname(injectorPath), "..", asarName);
|
||||||
|
|
||||||
|
const discordPkg = require(join(asarPath, "package.json"));
|
||||||
|
require.main!.filename = join(asarPath, discordPkg.main);
|
||||||
|
|
||||||
|
// @ts-ignore Untyped method? Dies from cringe
|
||||||
|
app.setAppPath(asarPath);
|
||||||
|
|
||||||
|
// Repatch after host updates on Windows
|
||||||
|
if (process.platform === "win32")
|
||||||
|
require("./patchWin32Updater");
|
||||||
|
|
||||||
class BrowserWindow extends electron.BrowserWindow {
|
class BrowserWindow extends electron.BrowserWindow {
|
||||||
constructor(options: BrowserWindowConstructorOptions) {
|
constructor(options: BrowserWindowConstructorOptions) {
|
||||||
if (options?.webPreferences?.preload && options.title) {
|
if (options?.webPreferences?.preload && options.title) {
|
||||||
@ -47,9 +87,29 @@ Object.defineProperty(global, "appSettings", {
|
|||||||
process.env.DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
process.env.DATA_DIR = join(app.getPath("userData"), "..", "Vencord");
|
||||||
|
|
||||||
electron.app.whenReady().then(() => {
|
electron.app.whenReady().then(() => {
|
||||||
installExt(REACT_DEVELOPER_TOOLS)
|
// Source Maps! Maybe there's a better way but since the renderer is executed
|
||||||
.then(() => console.info("Installed React DevTools"))
|
// from a string I don't think any other form of sourcemaps would work
|
||||||
.catch((err) => console.error("Failed to install React DevTools", err));
|
electron.protocol.registerFileProtocol("vencord", ({ url: unsafeUrl }, cb) => {
|
||||||
|
let url = unsafeUrl.slice("vencord://".length);
|
||||||
|
if (url.endsWith("/")) url = url.slice(0, -1);
|
||||||
|
switch (url) {
|
||||||
|
case "renderer.js.map":
|
||||||
|
case "preload.js.map":
|
||||||
|
case "patcher.js.map": // doubt
|
||||||
|
cb(join(__dirname, url));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cb({ statusCode: 403 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const settings = JSON.parse(readSettings());
|
||||||
|
if (settings.enableReactDevtools)
|
||||||
|
installExt("fmkadmapgofadopljbjfkapdkoienihi")
|
||||||
|
.then(() => console.info("[Vencord] Installed React Developer Tools"))
|
||||||
|
.catch(err => console.error("[Vencord] Failed to install React Developer Tools", err));
|
||||||
|
} catch { }
|
||||||
|
|
||||||
// Remove CSP
|
// Remove CSP
|
||||||
electron.session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, url }, cb) => {
|
electron.session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, url }, cb) => {
|
||||||
@ -64,10 +124,25 @@ electron.app.whenReady().then(() => {
|
|||||||
}
|
}
|
||||||
cb({ cancel: false, responseHeaders });
|
cb({ cancel: false, responseHeaders });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Drop science and sentry requests
|
|
||||||
electron.session.defaultSession.webRequest.onBeforeRequest(
|
|
||||||
{ urls: ["https://*/api/v*/science", "https://sentry.io/*"] },
|
|
||||||
(_, callback) => callback({ cancel: true })
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log("[Vencord] Loading original Discord app.asar");
|
||||||
|
// Legacy Vencord Injector requires "../app.asar". However, because we
|
||||||
|
// restore the require.main above this is messed up, so monkey patch Module._load to
|
||||||
|
// redirect such requires
|
||||||
|
// FIXME: remove this eventually
|
||||||
|
if (readFileSync(injectorPath, "utf-8").includes('require("../app.asar")')) {
|
||||||
|
console.warn("[Vencord] [--> WARNING <--] You have a legacy Vencord install. Please reinject");
|
||||||
|
const Module = require("module");
|
||||||
|
const loadModule = Module._load;
|
||||||
|
Module._load = function (path: string) {
|
||||||
|
if (path === "../app.asar") {
|
||||||
|
Module._load = loadModule;
|
||||||
|
arguments[0] = require.main!.filename;
|
||||||
|
}
|
||||||
|
return loadModule.apply(this, arguments);
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
console.log(require.main!.filename);
|
||||||
|
require(require.main!.filename);
|
||||||
|
}
|
||||||
|
@ -1,5 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
import definePlugin from "../utils/types";
|
import definePlugin from "../utils/types";
|
||||||
import { Devs } from '../utils/constants';
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "STFU",
|
name: "STFU",
|
||||||
@ -8,8 +26,8 @@ export default definePlugin({
|
|||||||
patches: [{
|
patches: [{
|
||||||
find: "setDevtoolsCallbacks",
|
find: "setDevtoolsCallbacks",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.setDevtoolsCallbacks\(.+?else/,
|
match: /if\(.{0,10}\|\|"0.0.0"!==.{0,2}\.remoteApp\.getVersion\(\)\)/,
|
||||||
replace: ".setDevtoolsCallbacks(null,null);else"
|
replace: "if(false)"
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
});
|
});
|
||||||
|
89
src/plugins/anonymiseFileNames.ts
Normal file
89
src/plugins/anonymiseFileNames.ts
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "../utils/types";
|
||||||
|
import { Settings } from "../Vencord";
|
||||||
|
|
||||||
|
enum Methods {
|
||||||
|
Random,
|
||||||
|
Consistent,
|
||||||
|
Timestamp,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "AnonymiseFileNames",
|
||||||
|
authors: [Devs.obscurity],
|
||||||
|
description: "Anonymise uploaded file names",
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "instantBatchUpload:function",
|
||||||
|
replacement: {
|
||||||
|
match: /uploadFiles:(.{1,2}),/,
|
||||||
|
replace:
|
||||||
|
"uploadFiles:(...args)=>(args[0].uploads.forEach(f=>f.filename=Vencord.Plugins.plugins.AnonymiseFileNames.anonymise(f.filename)),$1(...args)),",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
options: {
|
||||||
|
method: {
|
||||||
|
description: "Anonymising method",
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
options: [
|
||||||
|
{ label: "Random Characters", value: Methods.Random, default: true },
|
||||||
|
{ label: "Consistent", value: Methods.Consistent },
|
||||||
|
{ label: "Timestamp (4chan-like)", value: Methods.Timestamp },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
randomisedLength: {
|
||||||
|
description: "Random characters length",
|
||||||
|
type: OptionType.NUMBER,
|
||||||
|
default: 7,
|
||||||
|
disabled: () => Settings.plugins.AnonymiseFileNames.method !== Methods.Random,
|
||||||
|
},
|
||||||
|
consistent: {
|
||||||
|
description: "Consistent filename",
|
||||||
|
type: OptionType.STRING,
|
||||||
|
default: "image",
|
||||||
|
disabled: () => Settings.plugins.AnonymiseFileNames.method !== Methods.Consistent,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
anonymise(file: string) {
|
||||||
|
let name = "image";
|
||||||
|
const ext = file.match(/\..+$/g)?.[0] ?? "";
|
||||||
|
switch (Settings.plugins.AnonymiseFileNames.method) {
|
||||||
|
case Methods.Random:
|
||||||
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
name = Array.from(
|
||||||
|
{ length: Settings.plugins.AnonymiseFileNames.randomisedLength },
|
||||||
|
() => chars[Math.floor(Math.random() * chars.length)]
|
||||||
|
).join("");
|
||||||
|
break;
|
||||||
|
case Methods.Consistent:
|
||||||
|
name = Settings.plugins.AnonymiseFileNames.consistent;
|
||||||
|
break;
|
||||||
|
case Methods.Timestamp:
|
||||||
|
// UNIX timestamp in nanos, i could not find a better dependency-less way
|
||||||
|
name = `${Math.floor(Date.now() / 1000)}${Math.floor(window.performance.now())}`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return name + ext;
|
||||||
|
},
|
||||||
|
});
|
52
src/plugins/apiCommands.ts
Normal file
52
src/plugins/apiCommands.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
|
import definePlugin from "../utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "CommandsAPI",
|
||||||
|
authors: [Devs.Arjix],
|
||||||
|
description: "Api required by anything that uses commands",
|
||||||
|
patches: [
|
||||||
|
// obtain BUILT_IN_COMMANDS instance
|
||||||
|
{
|
||||||
|
find: '"giphy","tenor"',
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
// Matches BUILT_IN_COMMANDS. This is not exported so this is
|
||||||
|
// the only way. _init() just returns the same object to make the
|
||||||
|
// patch simpler
|
||||||
|
|
||||||
|
// textCommands = builtInCommands.filter(...)
|
||||||
|
match: /(?<=\w=)(\w)(\.filter\(.{0,30}giphy)/,
|
||||||
|
replace: "Vencord.Api.Commands._init($1)$2",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// command error handling
|
||||||
|
{
|
||||||
|
find: "Unexpected value for option",
|
||||||
|
replacement: {
|
||||||
|
// return [2, cmd.execute(args, ctx)]
|
||||||
|
match: /,(.{1,2})\.execute\((.{1,2}),(.{1,2})\)]/,
|
||||||
|
replace: (_, cmd, args, ctx) => `,Vencord.Api.Commands._handleCommand(${cmd}, ${args}, ${ctx})]`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
});
|
36
src/plugins/apiMessageAccessories.ts
Normal file
36
src/plugins/apiMessageAccessories.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
|
import definePlugin from "../utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "MessageAccessoriesAPI",
|
||||||
|
description: "API to add message accessories.",
|
||||||
|
authors: [Devs.Cyn],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "_messageAttachmentToEmbedMedia",
|
||||||
|
replacement: {
|
||||||
|
match: /\(\)\.container\)},(.+?)\)};return/,
|
||||||
|
replace: (_, accessories) =>
|
||||||
|
`().container)},Vencord.Api.MessageAccessories._modifyAccessories([${accessories}],this.props))};return`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import { Devs } from "../utils/constants";
|
import { Devs } from "../utils/constants";
|
||||||
import definePlugin from "../utils/types";
|
import definePlugin from "../utils/types";
|
||||||
|
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import { Devs } from "../utils/constants";
|
import { Devs } from "../utils/constants";
|
||||||
import definePlugin from "../utils/types";
|
import definePlugin from "../utils/types";
|
||||||
|
|
||||||
@ -13,7 +31,7 @@ export default definePlugin({
|
|||||||
{
|
{
|
||||||
match: /;(.{1,2}=null;)(?=.{0,50}updateNotice)/g,
|
match: /;(.{1,2}=null;)(?=.{0,50}updateNotice)/g,
|
||||||
replace:
|
replace:
|
||||||
';if(Vencord.Api.Notices.currentNotice)return !1;$1'
|
";if(Vencord.Api.Notices.currentNotice)return !1;$1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /(?<=NOTICE_DISMISS:function.+?){(?=if\(null==(.+?)\))/,
|
match: /(?<=NOTICE_DISMISS:function.+?){(?=if\(null==(.+?)\))/,
|
||||||
|
@ -1,19 +1,49 @@
|
|||||||
import definePlugin from "../utils/types";
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "../utils/types";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "BANger",
|
name: "BANger",
|
||||||
description: "Replaces the GIF in the ban dialogue with a custom one.",
|
description: "Replaces the GIF in the ban dialogue with a custom one.",
|
||||||
authors: [{
|
authors: [
|
||||||
name: "Xinto",
|
{
|
||||||
id: 423915768191647755n
|
name: "Xinto",
|
||||||
}],
|
id: 423915768191647755n
|
||||||
|
},
|
||||||
|
Devs.Glitch
|
||||||
|
],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "BanConfirm",
|
find: "BAN_CONFIRM_TITLE.",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /src:\w\(\d+\)/g,
|
match: /src:\w\(\d+\)/g,
|
||||||
replace: 'src: "https://i.imgur.com/wp5q52C.mp4"'
|
replace: "src: Vencord.Settings.plugins.BANger.source"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
options: {
|
||||||
|
source: {
|
||||||
|
description: "Source to replace ban GIF with (Video or Gif)",
|
||||||
|
type: OptionType.STRING,
|
||||||
|
default: "https://i.imgur.com/wp5q52C.mp4",
|
||||||
|
restartNeeded: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import { Devs } from "../utils/constants";
|
import { Devs } from "../utils/constants";
|
||||||
import definePlugin from "../utils/types";
|
import definePlugin from "../utils/types";
|
||||||
|
|
||||||
@ -26,7 +44,7 @@ export default definePlugin({
|
|||||||
],
|
],
|
||||||
|
|
||||||
altify(props: any) {
|
altify(props: any) {
|
||||||
if (props.alt !== "GIF") return;
|
if (props.alt !== "GIF") return props.alt;
|
||||||
|
|
||||||
let url: string = props.original || props.src;
|
let url: string = props.original || props.src;
|
||||||
try {
|
try {
|
||||||
|
36
src/plugins/betterUploadButton.ts
Normal file
36
src/plugins/betterUploadButton.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
|
import definePlugin from "../utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "BetterUploadButton",
|
||||||
|
authors: [Devs.obscurity],
|
||||||
|
description: "Upload with a single click, open menu with right click",
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "Messages.CHAT_ATTACH_UPLOAD_OR_INVITE",
|
||||||
|
replacement: {
|
||||||
|
match: /CHAT_ATTACH_UPLOAD_OR_INVITE,onDoubleClick:([^,]+),onClick:([^,]+)}}/,
|
||||||
|
replace:
|
||||||
|
"CHAT_ATTACH_UPLOAD_OR_INVITE,onClick:$1,onContextMenu:$2}}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
138
src/plugins/clearURLs/defaultRules.ts
Normal file
138
src/plugins/clearURLs/defaultRules.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
export const defaultRules = [
|
||||||
|
"action_object_map",
|
||||||
|
"action_type_map",
|
||||||
|
"action_ref_map",
|
||||||
|
"spm@*.aliexpress.com",
|
||||||
|
"scm@*.aliexpress.com",
|
||||||
|
"aff_platform",
|
||||||
|
"aff_trace_key",
|
||||||
|
"algo_expid@*.aliexpress.*",
|
||||||
|
"algo_pvid@*.aliexpress.*",
|
||||||
|
"btsid",
|
||||||
|
"ws_ab_test",
|
||||||
|
"pd_rd_*@amazon.*",
|
||||||
|
"_encoding@amazon.*",
|
||||||
|
"psc@amazon.*",
|
||||||
|
"tag@amazon.*",
|
||||||
|
"ref_@amazon.*",
|
||||||
|
"pf_rd_*@amazon.*",
|
||||||
|
"pf@amazon.*",
|
||||||
|
"crid@amazon.*",
|
||||||
|
"keywords@amazon.*",
|
||||||
|
"sprefix@amazon.*",
|
||||||
|
"sr@amazon.*",
|
||||||
|
"ie@amazon.*",
|
||||||
|
"node@amazon.*",
|
||||||
|
"qid@amazon.*",
|
||||||
|
"callback@bilibili.com",
|
||||||
|
"cvid@bing.com",
|
||||||
|
"form@bing.com",
|
||||||
|
"sk@bing.com",
|
||||||
|
"sp@bing.com",
|
||||||
|
"sc@bing.com",
|
||||||
|
"qs@bing.com",
|
||||||
|
"pq@bing.com",
|
||||||
|
"sc_cid",
|
||||||
|
"mkt_tok",
|
||||||
|
"trk",
|
||||||
|
"trkCampaign",
|
||||||
|
"ga_*",
|
||||||
|
"gclid",
|
||||||
|
"gclsrc",
|
||||||
|
"hmb_campaign",
|
||||||
|
"hmb_medium",
|
||||||
|
"hmb_source",
|
||||||
|
"spReportId",
|
||||||
|
"spJobID",
|
||||||
|
"spUserID",
|
||||||
|
"spMailingID",
|
||||||
|
"itm_*",
|
||||||
|
"s_cid",
|
||||||
|
"elqTrackId",
|
||||||
|
"elqTrack",
|
||||||
|
"assetType",
|
||||||
|
"assetId",
|
||||||
|
"recipientId",
|
||||||
|
"campaignId",
|
||||||
|
"siteId",
|
||||||
|
"mc_cid",
|
||||||
|
"mc_eid",
|
||||||
|
"pk_*",
|
||||||
|
"sc_campaign",
|
||||||
|
"sc_channel",
|
||||||
|
"sc_content",
|
||||||
|
"sc_medium",
|
||||||
|
"sc_outcome",
|
||||||
|
"sc_geo",
|
||||||
|
"sc_country",
|
||||||
|
"nr_email_referer",
|
||||||
|
"vero_conv",
|
||||||
|
"vero_id",
|
||||||
|
"yclid",
|
||||||
|
"_openstat",
|
||||||
|
"mbid",
|
||||||
|
"cmpid",
|
||||||
|
"cid",
|
||||||
|
"c_id",
|
||||||
|
"campaign_id",
|
||||||
|
"Campaign",
|
||||||
|
"hash@ebay.*",
|
||||||
|
"fb_action_ids",
|
||||||
|
"fb_action_types",
|
||||||
|
"fb_ref",
|
||||||
|
"fb_source",
|
||||||
|
"fbclid",
|
||||||
|
"refsrc@facebook.com",
|
||||||
|
"hrc@facebook.com",
|
||||||
|
"gs_l",
|
||||||
|
"gs_lcp@google.*",
|
||||||
|
"ved@google.*",
|
||||||
|
"ei@google.*",
|
||||||
|
"sei@google.*",
|
||||||
|
"gws_rd@google.*",
|
||||||
|
"gs_gbg@google.*",
|
||||||
|
"gs_mss@google.*",
|
||||||
|
"gs_rn@google.*",
|
||||||
|
"_hsenc",
|
||||||
|
"_hsmi",
|
||||||
|
"__hssc",
|
||||||
|
"__hstc",
|
||||||
|
"hsCtaTracking",
|
||||||
|
"source@sourceforge.net",
|
||||||
|
"position@sourceforge.net",
|
||||||
|
"t@*.twitter.com",
|
||||||
|
"s@*.twitter.com",
|
||||||
|
"ref_*@*.twitter.com",
|
||||||
|
"tt_medium",
|
||||||
|
"tt_content",
|
||||||
|
"lr@yandex.*",
|
||||||
|
"redircnt@yandex.*",
|
||||||
|
"feature@youtube.com",
|
||||||
|
"kw@youtube.com",
|
||||||
|
"wt_zmc",
|
||||||
|
"utm_source",
|
||||||
|
"utm_content",
|
||||||
|
"utm_medium",
|
||||||
|
"utm_campaign",
|
||||||
|
"utm_term",
|
||||||
|
"si@open.spotify.com",
|
||||||
|
];
|
152
src/plugins/clearURLs/index.ts
Normal file
152
src/plugins/clearURLs/index.ts
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
addPreEditListener,
|
||||||
|
addPreSendListener,
|
||||||
|
MessageObject,
|
||||||
|
removePreEditListener,
|
||||||
|
removePreSendListener,
|
||||||
|
} from "../../api/MessageEvents";
|
||||||
|
import definePlugin from "../../utils/types";
|
||||||
|
import { defaultRules } from "./defaultRules";
|
||||||
|
|
||||||
|
// From lodash
|
||||||
|
const reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
|
||||||
|
const reHasRegExpChar = RegExp(reRegExpChar.source);
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "clearURLs",
|
||||||
|
description: "Removes tracking garbage from URLs",
|
||||||
|
authors: [
|
||||||
|
{
|
||||||
|
name: "adryd",
|
||||||
|
id: 0n,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dependencies: ["MessageEventsAPI"],
|
||||||
|
|
||||||
|
escapeRegExp(str: string) {
|
||||||
|
return (str && reHasRegExpChar.test(str))
|
||||||
|
? str.replace(reRegExpChar, "\\$&")
|
||||||
|
: (str || "");
|
||||||
|
},
|
||||||
|
|
||||||
|
createRules() {
|
||||||
|
// Can be extended upon once user configs are available
|
||||||
|
// Eg. (useDefaultRules: boolean, customRules: Array[string])
|
||||||
|
const rules = defaultRules;
|
||||||
|
|
||||||
|
this.universalRules = new Set();
|
||||||
|
this.rulesByHost = new Map();
|
||||||
|
this.hostRules = new Map();
|
||||||
|
|
||||||
|
for (const rule of rules) {
|
||||||
|
const splitRule = rule.split("@");
|
||||||
|
const paramRule = new RegExp(
|
||||||
|
"^" +
|
||||||
|
this.escapeRegExp(splitRule[0]).replace(/\\\*/, ".+?") +
|
||||||
|
"$"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!splitRule[1]) {
|
||||||
|
this.universalRules.add(paramRule);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const hostRule = new RegExp(
|
||||||
|
"^(www\\.)?" +
|
||||||
|
this.escapeRegExp(splitRule[1])
|
||||||
|
.replace(/\\\./, "\\.")
|
||||||
|
.replace(/^\\\*\\\./, "(.+?\\.)?")
|
||||||
|
.replace(/\\\*/, ".+?") +
|
||||||
|
"$"
|
||||||
|
);
|
||||||
|
const hostRuleIndex = hostRule.toString();
|
||||||
|
|
||||||
|
this.hostRules.set(hostRuleIndex, hostRule);
|
||||||
|
if (this.rulesByHost.get(hostRuleIndex) == null) {
|
||||||
|
this.rulesByHost.set(hostRuleIndex, new Set());
|
||||||
|
}
|
||||||
|
this.rulesByHost.get(hostRuleIndex).add(paramRule);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
removeParam(rule: string | RegExp, param: string, parent: URLSearchParams) {
|
||||||
|
if (param === rule || rule instanceof RegExp && rule.test(param)) {
|
||||||
|
parent.delete(param);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
replacer(match: string) {
|
||||||
|
// Parse URL without throwing errors
|
||||||
|
try {
|
||||||
|
var url = new URL(match);
|
||||||
|
} catch (error) {
|
||||||
|
// Don't modify anything if we can't parse the URL
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cheap way to check if there are any search params
|
||||||
|
if (url.searchParams.entries().next().done) {
|
||||||
|
// If there are none, we don't need to modify anything
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all universal rules
|
||||||
|
this.universalRules.forEach(rule => {
|
||||||
|
url.searchParams.forEach((_value, param, parent) => {
|
||||||
|
this.removeParam(rule, param, parent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check rules for each hosts that match
|
||||||
|
this.hostRules.forEach((regex, hostRuleName) => {
|
||||||
|
if (!regex.test(url.hostname)) return;
|
||||||
|
this.rulesByHost.get(hostRuleName).forEach(rule => {
|
||||||
|
url.searchParams.forEach((_value, param, parent) => {
|
||||||
|
this.removeParam(rule, param, parent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return url.toString();
|
||||||
|
},
|
||||||
|
|
||||||
|
onSend(msg: MessageObject) {
|
||||||
|
// Only run on messages that contain URLs
|
||||||
|
if (msg.content.match(/http(s)?:\/\//)) {
|
||||||
|
msg.content = msg.content.replace(
|
||||||
|
/(https?:\/\/[^\s<]+[^<.,:;"'>)|\]\s])/g,
|
||||||
|
match => this.replacer(match)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.createRules();
|
||||||
|
this.preSend = addPreSendListener((_, msg) => this.onSend(msg));
|
||||||
|
this.preEdit = addPreEditListener((_cid, _mid, msg) =>
|
||||||
|
this.onSend(msg)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
removePreSendListener(this.preSend);
|
||||||
|
removePreEditListener(this.preEdit);
|
||||||
|
},
|
||||||
|
});
|
@ -1,6 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import { Devs } from "../utils/constants";
|
import { Devs } from "../utils/constants";
|
||||||
import definePlugin from "../utils/types";
|
import definePlugin from "../utils/types";
|
||||||
import { Toasts } from '../webpack/common';
|
import { Toasts } from "../webpack/common";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "ClickableRoleDot",
|
name: "ClickableRoleDot",
|
||||||
@ -18,7 +36,16 @@ export default definePlugin({
|
|||||||
],
|
],
|
||||||
|
|
||||||
copyToClipBoard(color: string) {
|
copyToClipBoard(color: string) {
|
||||||
window.DiscordNative.clipboard.copy(color);
|
if (IS_WEB) {
|
||||||
|
navigator.clipboard.writeText(color)
|
||||||
|
.then(() => this.notifySuccess);
|
||||||
|
} else {
|
||||||
|
DiscordNative.clipboard.copy(color);
|
||||||
|
this.notifySuccess();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
notifySuccess() {
|
||||||
Toasts.show({
|
Toasts.show({
|
||||||
message: "Copied to Clipboard!",
|
message: "Copied to Clipboard!",
|
||||||
type: Toasts.Type.SUCCESS,
|
type: Toasts.Type.SUCCESS,
|
||||||
|
@ -1,6 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import { Devs } from "../utils/constants";
|
import { Devs } from "../utils/constants";
|
||||||
import definePlugin from "../utils/types";
|
import definePlugin from "../utils/types";
|
||||||
|
|
||||||
|
const WEB_ONLY = (f: string) => () => {
|
||||||
|
throw new Error(`'${f}' is Discord Desktop only.`);
|
||||||
|
};
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "ConsoleShortcuts",
|
name: "ConsoleShortcuts",
|
||||||
description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.",
|
description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.",
|
||||||
@ -8,8 +30,8 @@ export default definePlugin({
|
|||||||
|
|
||||||
getShortcuts() {
|
getShortcuts() {
|
||||||
return {
|
return {
|
||||||
toClip: window.DiscordNative.clipboard.copy,
|
toClip: IS_WEB ? WEB_ONLY("toClip") : window.DiscordNative.clipboard.copy,
|
||||||
fromClip: window.DiscordNative.clipboard.read,
|
fromClip: IS_WEB ? WEB_ONLY("fromClip") : window.DiscordNative.clipboard.read,
|
||||||
wp: Vencord.Webpack,
|
wp: Vencord.Webpack,
|
||||||
wpc: Vencord.Webpack.wreq.c,
|
wpc: Vencord.Webpack.wreq.c,
|
||||||
wreq: Vencord.Webpack.wreq,
|
wreq: Vencord.Webpack.wreq,
|
||||||
@ -22,7 +44,7 @@ export default definePlugin({
|
|||||||
Settings: Vencord.Settings,
|
Settings: Vencord.Settings,
|
||||||
Api: Vencord.Api,
|
Api: Vencord.Api,
|
||||||
reload: () => location.reload(),
|
reload: () => location.reload(),
|
||||||
restart: () => window.DiscordNative.app.relaunch()
|
restart: IS_WEB ? WEB_ONLY("restart") : window.DiscordNative.app.relaunch
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import { Devs } from "../utils/constants";
|
|
||||||
import definePlugin from '../utils/types';
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "Experiments",
|
|
||||||
authors: [Devs.Ven, Devs.Megu],
|
|
||||||
description: "Enable Experiments",
|
|
||||||
patches: [{
|
|
||||||
find: "Object.defineProperties(this,{isDeveloper",
|
|
||||||
replacement: {
|
|
||||||
match: /(?<={isDeveloper:\{[^}]+,get:function\(\)\{return )\w/,
|
|
||||||
replace: "true"
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
find: 'type:"user",revision',
|
|
||||||
replacement: {
|
|
||||||
match: /(\w)\|\|"CONNECTION_OPEN".+?;/g,
|
|
||||||
replace: "$1=!0;"
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
});
|
|
94
src/plugins/experiments.tsx
Normal file
94
src/plugins/experiments.tsx
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Forms } from "@components";
|
||||||
|
|
||||||
|
import { lazyWebpack } from "../utils";
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "../utils/types";
|
||||||
|
import { Settings } from "../Vencord";
|
||||||
|
import { filters } from "../webpack";
|
||||||
|
import { React } from "../webpack/common";
|
||||||
|
|
||||||
|
const KbdStyles = lazyWebpack(filters.byProps(["key", "removeBuildOverride"]));
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "Experiments",
|
||||||
|
authors: [
|
||||||
|
Devs.Megu,
|
||||||
|
Devs.Ven,
|
||||||
|
{ name: "Nickyux", id: 427146305651998721n },
|
||||||
|
{ name: "BanTheNons", id: 460478012794863637n },
|
||||||
|
],
|
||||||
|
description: "Enable Access to Experiments in Discord!",
|
||||||
|
patches: [{
|
||||||
|
find: "Object.defineProperties(this,{isDeveloper",
|
||||||
|
replacement: {
|
||||||
|
match: /(?<={isDeveloper:\{[^}]+,get:function\(\)\{return )\w/,
|
||||||
|
replace: "true"
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
find: 'type:"user",revision',
|
||||||
|
replacement: {
|
||||||
|
match: /(\w)\|\|"CONNECTION_OPEN".+?;/g,
|
||||||
|
replace: "$1=!0;"
|
||||||
|
},
|
||||||
|
}, {
|
||||||
|
find: ".isStaff=function(){",
|
||||||
|
predicate: () => Settings.plugins.Experiments.enableIsStaff === true,
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /return\s*(\w+)\.hasFlag\((.+?)\.STAFF\)}/,
|
||||||
|
replace: "return Vencord.Webpack.Common.UserStore.getCurrentUser().id===$1.id||$1.hasFlag($2.STAFF)}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /hasFreePremium=function\(\){return this.isStaff\(\)\s*\|\|/,
|
||||||
|
replace: "hasFreePremium=function(){return ",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}],
|
||||||
|
options: {
|
||||||
|
enableIsStaff: {
|
||||||
|
description: "Enable isStaff (requires restart)",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: false,
|
||||||
|
restartNeeded: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
settingsAboutComponent: () => {
|
||||||
|
const isMacOS = navigator.platform.includes("Mac");
|
||||||
|
const modKey = isMacOS ? "cmd" : "ctrl";
|
||||||
|
const altKey = isMacOS ? "opt" : "alt";
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Forms.FormTitle tag="h3">More Information</Forms.FormTitle>
|
||||||
|
<Forms.FormText>
|
||||||
|
You can enable client DevTools{" "}
|
||||||
|
<kbd className={KbdStyles.key}>{modKey}</kbd> +{" "}
|
||||||
|
<kbd className={KbdStyles.key}>{altKey}</kbd> +{" "}
|
||||||
|
<kbd className={KbdStyles.key}>O</kbd>{" "}
|
||||||
|
after enabling <code>isStaff</code> below
|
||||||
|
</Forms.FormText>
|
||||||
|
<Forms.FormText>
|
||||||
|
and then toggling <code>Enable DevTools</code> in the <code>Developer Options</code> tab in settings.
|
||||||
|
</Forms.FormText>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
61
src/plugins/fart.ts
Normal file
61
src/plugins/fart.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ApplicationCommandOptionType } from "../api/Commands";
|
||||||
|
import { makeRange } from "../components/PluginSettings/components";
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "../utils/types";
|
||||||
|
import { Settings } from "../Vencord";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "Fart2",
|
||||||
|
authors: [Devs.Animal],
|
||||||
|
description: "Enable farting v2, a slash command that allows you to perform or request that someone perform a little toot.",
|
||||||
|
dependencies: ["CommandsAPI"],
|
||||||
|
commands: [{
|
||||||
|
name: "fart",
|
||||||
|
description: "A simple command in which you may either request that a user do a little toot for you, or conduct one yourself.",
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
type: ApplicationCommandOptionType.USER,
|
||||||
|
name: "user",
|
||||||
|
description: "A Discord™ user of which you would humbly request a toot from.",
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
execute(args) {
|
||||||
|
const fart = new Audio("https://raw.githubusercontent.com/ItzOnlyAnimal/AliuPlugins/main/fart.mp3");
|
||||||
|
fart.volume = Settings.plugins.Fart2.volume;
|
||||||
|
fart.play();
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: (args[0]) ? `<@${args[0].value}> fart` : "fart"
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}],
|
||||||
|
options: {
|
||||||
|
volume: {
|
||||||
|
description: "how loud you wanna fart (aka volume)",
|
||||||
|
type: OptionType.SLIDER,
|
||||||
|
markers: makeRange(0, 1, 0.1),
|
||||||
|
default: 0.5,
|
||||||
|
stickToMarkers: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
42
src/plugins/fxTwitter.ts
Normal file
42
src/plugins/fxTwitter.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Samu
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { addPreSendListener, MessageObject, removePreSendListener } from "../api/MessageEvents";
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
|
import definePlugin from "../utils/types";
|
||||||
|
|
||||||
|
const re = /https?:\/\/twitter\.com(?=\/\w+?\/status\/)/g;
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "FxTwitter",
|
||||||
|
description: "Uses FxTwitter to improve embeds from twitter on send",
|
||||||
|
authors: [Devs.Samu],
|
||||||
|
dependencies: ["MessageEventsAPI"],
|
||||||
|
|
||||||
|
addPrefix(msg: MessageObject) {
|
||||||
|
msg.content = msg.content.replace(re, "https://fxtwitter.com");
|
||||||
|
},
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.preSend = addPreSendListener((_, msg) => this.addPrefix(msg));
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
removePreSendListener(this.preSend);
|
||||||
|
}
|
||||||
|
});
|
@ -1,3 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import { Devs } from "../utils/constants";
|
import { Devs } from "../utils/constants";
|
||||||
import definePlugin from "../utils/types";
|
import definePlugin from "../utils/types";
|
||||||
|
|
||||||
@ -6,10 +24,7 @@ export default definePlugin({
|
|||||||
description: "Do not hide messages from 'likely spammers'",
|
description: "Do not hide messages from 'likely spammers'",
|
||||||
authors: [
|
authors: [
|
||||||
Devs.botato,
|
Devs.botato,
|
||||||
{
|
Devs.Animal,
|
||||||
name: "Iryis",
|
|
||||||
id: 118437263754395652n,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
|
@ -1,21 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
import { Devs } from "../utils/constants";
|
import { Devs } from "../utils/constants";
|
||||||
import definePlugin from "../utils/types";
|
import definePlugin, { OptionType } from "../utils/types";
|
||||||
|
import { Settings } from "../Vencord";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "Ify",
|
name: "Ify",
|
||||||
description: "Disabes Spotify auto-pausing and premium checks",
|
description: "Disables Spotify auto-pausing, allows activity to continue playing when idling and bypasses premium checks, allowing you to listen along with others.",
|
||||||
authors: [Devs.Cyn],
|
authors: [
|
||||||
patches: [
|
Devs.Cyn,
|
||||||
{
|
Devs.Nuckyz
|
||||||
find: '.displayName="SpotifyStore"',
|
],
|
||||||
replacement: [{
|
|
||||||
match: /\.isPremium=.;/,
|
patches: [{
|
||||||
replace: ".isPremium=true;",
|
find: 'dispatch({type:"SPOTIFY_PROFILE_UPDATE"',
|
||||||
}, ...["SPEAKING", "VOICE_STATE_UPDATES", "MEDIA_ENGINE_SET_DESKTOP_SOURCE"].map(event => ({
|
replacement: [{
|
||||||
match: new RegExp(`${event}:function\\(.\\){.+?}(,|}\\))`),
|
match: /(function\((.{1,2})\){)(.{1,6}dispatch\({type:"SPOTIFY_PROFILE_UPDATE")/,
|
||||||
replace: (_, ending) => `${event}:function(){}${ending}`,
|
replace: (_, functionStart, data, functionBody) => `${functionStart}${data}.body.product="premium";${functionBody}`
|
||||||
})),
|
}],
|
||||||
],
|
}, {
|
||||||
|
find: '.displayName="SpotifyStore"',
|
||||||
|
predicate: () => Settings.plugins.Ify.noSpotifyAutoPause,
|
||||||
|
replacement: {
|
||||||
|
match: /function (.{1,2})\(\).{0,200}SPOTIFY_AUTO_PAUSED\);.{0,}}}}/,
|
||||||
|
replace: "function $1(){}"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
find: '.displayName="SpotifyStore"',
|
||||||
|
predicate: () => Settings.plugins.Ify.keepSpotifyActivityOnIdle,
|
||||||
|
replacement: {
|
||||||
|
match: /(shouldShowActivity=function\(\){.{1,50})&&!.{1,6}\.isIdle\(\)(.{0,}?})/,
|
||||||
|
replace: (_, functionDeclarationAndExpression, restOfFunction) => `${functionDeclarationAndExpression}${restOfFunction}`
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
|
||||||
|
options: {
|
||||||
|
noSpotifyAutoPause: {
|
||||||
|
description: "Disable Spotify auto-pause",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
restartNeeded: true,
|
||||||
},
|
},
|
||||||
]
|
keepSpotifyActivityOnIdle: {
|
||||||
|
description: "Keep Spotify activity playing when idling",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: false,
|
||||||
|
restartNeeded: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,24 @@
|
|||||||
import Plugins from "plugins";
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Plugins from "~plugins";
|
||||||
|
|
||||||
|
import { registerCommand, unregisterCommand } from "../api/Commands";
|
||||||
import { Settings } from "../api/settings";
|
import { Settings } from "../api/settings";
|
||||||
import Logger from "../utils/logger";
|
import Logger from "../utils/logger";
|
||||||
import { Patch, Plugin } from "../utils/types";
|
import { Patch, Plugin } from "../utils/types";
|
||||||
@ -17,44 +37,90 @@ for (const plugin of Object.values(Plugins)) if (plugin.patches && Settings.plug
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function startAllPlugins() {
|
export function startAllPlugins() {
|
||||||
for (const plugin in Plugins) if (Settings.plugins[plugin].enabled) {
|
for (const name in Plugins) if (Settings.plugins[name].enabled) {
|
||||||
startPlugin(Plugins[plugin]);
|
startPlugin(Plugins[name]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function startDependenciesRecursive(p: Plugin) {
|
||||||
|
let restartNeeded = false;
|
||||||
|
const failures: string[] = [];
|
||||||
|
if (p.dependencies) for (const dep of p.dependencies) {
|
||||||
|
if (!Settings.plugins[dep].enabled) {
|
||||||
|
startDependenciesRecursive(Plugins[dep]);
|
||||||
|
// If the plugin has patches, don't start the plugin, just enable it.
|
||||||
|
if (Plugins[dep].patches) {
|
||||||
|
logger.warn(`Enabling dependency ${dep} requires restart.`);
|
||||||
|
Settings.plugins[dep].enabled = true;
|
||||||
|
restartNeeded = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const result = startPlugin(Plugins[dep]);
|
||||||
|
if (!result) failures.push(dep);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { restartNeeded, failures };
|
||||||
|
}
|
||||||
|
|
||||||
export function startPlugin(p: Plugin) {
|
export function startPlugin(p: Plugin) {
|
||||||
if (!p.start) return true;
|
if (p.start) {
|
||||||
|
logger.info("Starting plugin", p.name);
|
||||||
logger.info("Starting plugin", p.name);
|
if (p.started) {
|
||||||
if (p.started) {
|
logger.warn(`${p.name} already started`);
|
||||||
logger.warn(`${p.name} already started`);
|
return false;
|
||||||
return false;
|
}
|
||||||
|
try {
|
||||||
|
p.start();
|
||||||
|
p.started = true;
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`Failed to start ${p.name}\n`, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (p.commands?.length) {
|
||||||
p.start();
|
logger.info("Registering commands of plugin", p.name);
|
||||||
p.started = true;
|
for (const cmd of p.commands) {
|
||||||
return true;
|
try {
|
||||||
} catch (err: any) {
|
registerCommand(cmd, p.name);
|
||||||
logger.error(`Failed to start ${p.name}\n`, err);
|
} catch (e) {
|
||||||
return false;
|
logger.error(`Failed to register command ${cmd.name}\n`, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function stopPlugin(p: Plugin) {
|
export function stopPlugin(p: Plugin) {
|
||||||
if (!p.stop) return true;
|
if (p.stop) {
|
||||||
|
logger.info("Stopping plugin", p.name);
|
||||||
|
if (!p.started) {
|
||||||
|
logger.warn(`${p.name} already stopped`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
p.stop();
|
||||||
|
p.started = false;
|
||||||
|
} catch (e) {
|
||||||
|
logger.error(`Failed to stop ${p.name}\n`, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
logger.info("Stopping plugin", p.name);
|
if (p.commands?.length) {
|
||||||
if (!p.started) {
|
logger.info("Unregistering commands of plugin", p.name);
|
||||||
logger.warn(`${p.name} already stopped / never started`);
|
for (const cmd of p.commands) {
|
||||||
return false;
|
try {
|
||||||
}
|
unregisterCommand(cmd.name);
|
||||||
try {
|
} catch (e) {
|
||||||
p.stop();
|
logger.error(`Failed to unregister command ${cmd.name}\n`, e);
|
||||||
p.started = false;
|
return false;
|
||||||
return true;
|
}
|
||||||
} catch (err: any) {
|
}
|
||||||
logger.error(`Failed to stop ${p.name}\n`, err);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
153
src/plugins/interactionKeybinds.ts
Normal file
153
src/plugins/interactionKeybinds.ts
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
|
import { lazyWebpack } from "../utils/misc";
|
||||||
|
import definePlugin from "../utils/types";
|
||||||
|
import { filters } from "../webpack";
|
||||||
|
import { ChannelStore, FluxDispatcher as Dispatcher, SelectedChannelStore, UserStore } from "../webpack/common";
|
||||||
|
|
||||||
|
const MessageStore = lazyWebpack(filters.byProps(["getRawMessages"]));
|
||||||
|
|
||||||
|
const isMac = navigator.platform.includes("Mac"); // bruh
|
||||||
|
let replyIdx = -1;
|
||||||
|
let editIdx = -1;
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "InteractionKeybinds",
|
||||||
|
authors: [Devs.obscurity, Devs.Ven],
|
||||||
|
description: "Reply to (ctrl + up/down) and edit (ctrl + shift + up/down) messages via keybinds",
|
||||||
|
|
||||||
|
start() {
|
||||||
|
Dispatcher.subscribe("DELETE_PENDING_REPLY", onDeletePendingReply);
|
||||||
|
Dispatcher.subscribe("MESSAGE_END_EDIT", onEndEdit);
|
||||||
|
Dispatcher.subscribe("MESSAGE_START_EDIT", onStartEdit);
|
||||||
|
Dispatcher.subscribe("CREATE_PENDING_REPLY", onCreatePendingReply);
|
||||||
|
document.addEventListener("keydown", onKeydown);
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
Dispatcher.unsubscribe("DELETE_PENDING_REPLY", onDeletePendingReply);
|
||||||
|
Dispatcher.unsubscribe("MESSAGE_END_EDIT", onEndEdit);
|
||||||
|
Dispatcher.unsubscribe("MESSAGE_START_EDIT", onStartEdit);
|
||||||
|
Dispatcher.unsubscribe("CREATE_PENDING_REPLY", onCreatePendingReply);
|
||||||
|
document.removeEventListener("keydown", onKeydown);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onDeletePendingReply = () => replyIdx = -1;
|
||||||
|
const onEndEdit = () => editIdx = -1;
|
||||||
|
|
||||||
|
function calculateIdx(messages: Message[], id: string) {
|
||||||
|
const idx = messages.findIndex(m => m.id === id);
|
||||||
|
return idx === -1
|
||||||
|
? idx
|
||||||
|
: messages.length - idx - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onStartEdit({ channelId, messageId, _isQuickEdit }: any) {
|
||||||
|
if (_isQuickEdit) return;
|
||||||
|
|
||||||
|
const meId = UserStore.getCurrentUser().id;
|
||||||
|
|
||||||
|
const messages = MessageStore.getMessages(channelId)._array.filter(m => m.author.id === meId);
|
||||||
|
editIdx = calculateIdx(messages, messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCreatePendingReply({ message, _isQuickReply }: { message: Message; _isQuickReply: boolean; }) {
|
||||||
|
if (_isQuickReply) return;
|
||||||
|
|
||||||
|
replyIdx = calculateIdx(MessageStore.getMessages(message.channel_id)._array, message.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isCtrl = (e: KeyboardEvent) => isMac ? e.metaKey : e.ctrlKey;
|
||||||
|
const isAltOrMeta = (e: KeyboardEvent) => e.altKey || (!isMac && e.metaKey);
|
||||||
|
|
||||||
|
function onKeydown(e: KeyboardEvent) {
|
||||||
|
const isUp = e.key === "ArrowUp";
|
||||||
|
if (!isUp && e.key !== "ArrowDown") return;
|
||||||
|
if (!isCtrl(e) || isAltOrMeta(e)) return;
|
||||||
|
|
||||||
|
if (e.shiftKey)
|
||||||
|
nextEdit(isUp);
|
||||||
|
else
|
||||||
|
nextReply(isUp);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNextMessage(isUp: boolean, isReply: boolean) {
|
||||||
|
let messages: Message[] = MessageStore.getMessages(SelectedChannelStore.getChannelId())._array;
|
||||||
|
if (!isReply) { // we are editing so only include own
|
||||||
|
const meId = UserStore.getCurrentUser().id;
|
||||||
|
messages = messages.filter(m => m.author.id === meId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const mutate = (i: number) => isUp
|
||||||
|
? Math.min(messages.length - 1, i + 1)
|
||||||
|
: Math.max(-1, i - 1);
|
||||||
|
|
||||||
|
let i: number;
|
||||||
|
if (isReply)
|
||||||
|
replyIdx = i = mutate(replyIdx);
|
||||||
|
else
|
||||||
|
editIdx = i = mutate(editIdx);
|
||||||
|
|
||||||
|
return i === - 1 ? undefined : messages[messages.length - i - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle next/prev reply
|
||||||
|
function nextReply(isUp: boolean) {
|
||||||
|
const message = getNextMessage(isUp, true);
|
||||||
|
|
||||||
|
if (!message)
|
||||||
|
return void Dispatcher.dispatch({
|
||||||
|
type: "DELETE_PENDING_REPLY",
|
||||||
|
channelId: SelectedChannelStore.getChannelId(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const channel = ChannelStore.getChannel(message.channel_id);
|
||||||
|
const meId = UserStore.getCurrentUser().id;
|
||||||
|
Dispatcher.dispatch({
|
||||||
|
type: "CREATE_PENDING_REPLY",
|
||||||
|
channel,
|
||||||
|
message,
|
||||||
|
shouldMention: true,
|
||||||
|
showMentionToggle: channel.guild_id !== null && message.author.id !== meId,
|
||||||
|
_isQuickReply: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle next/prev edit
|
||||||
|
function nextEdit(isUp: boolean) {
|
||||||
|
const message = getNextMessage(isUp, false);
|
||||||
|
|
||||||
|
if (!message)
|
||||||
|
Dispatcher.dispatch({
|
||||||
|
type: "MESSAGE_END_EDIT",
|
||||||
|
channelId: SelectedChannelStore.getChannelId()
|
||||||
|
});
|
||||||
|
else
|
||||||
|
Dispatcher.dispatch({
|
||||||
|
type: "MESSAGE_START_EDIT",
|
||||||
|
channelId: message.channel_id,
|
||||||
|
messageId: message.id,
|
||||||
|
content: message.content,
|
||||||
|
_isQuickEdit: true
|
||||||
|
});
|
||||||
|
}
|
@ -1,28 +0,0 @@
|
|||||||
import { Devs } from "../utils/constants";
|
|
||||||
import definePlugin from "../utils/types";
|
|
||||||
|
|
||||||
export default definePlugin({
|
|
||||||
name: "isStaff",
|
|
||||||
description:
|
|
||||||
"Gives access to client devtools & other things locked behind isStaff",
|
|
||||||
authors: [Devs.Megu],
|
|
||||||
patches: [
|
|
||||||
{
|
|
||||||
find: ".isStaff=function(){",
|
|
||||||
replacement: [
|
|
||||||
{
|
|
||||||
match: /(\w+)\.isStaff=function\(\){return\s*!1};/,
|
|
||||||
replace: "$1.isStaff=function(){return true};",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /return\s*\w+\.hasFlag\(.+?STAFF\)}/,
|
|
||||||
replace: "return true}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
match: /hasFreePremium=function\(\){return this.isStaff\(\)\s*\|\|/,
|
|
||||||
replace: "hasFreePremium=function(){return ",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
@ -1,4 +1,22 @@
|
|||||||
import { addClickListener, removeClickListener } from '../api/MessageEvents';
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { addClickListener, removeClickListener } from "../api/MessageEvents";
|
||||||
import { Devs } from "../utils/constants";
|
import { Devs } from "../utils/constants";
|
||||||
import definePlugin from "../utils/types";
|
import definePlugin from "../utils/types";
|
||||||
import { find, findByProps } from "../webpack";
|
import { find, findByProps } from "../webpack";
|
||||||
@ -15,10 +33,10 @@ export default definePlugin({
|
|||||||
dependencies: ["MessageEventsAPI"],
|
dependencies: ["MessageEventsAPI"],
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
const { deleteMessage, startEditMessage } = findByProps("deleteMessage");
|
const { deleteMessage, startEditMessage } = findByProps("deleteMessage", "startEditMessage");
|
||||||
const { can } = findByProps("can", "initialize");
|
const { can } = findByProps("can", "initialize");
|
||||||
const { MANAGE_MESSAGES } = find(m => typeof m.MANAGE_MESSAGES === "bigint");
|
const { MANAGE_MESSAGES } = find(m => typeof m.MANAGE_MESSAGES === "bigint");
|
||||||
const { isEditing } = findByProps("isEditing");
|
const { isEditing } = findByProps("isEditing", "isEditingAny");
|
||||||
|
|
||||||
document.addEventListener("keydown", keydown);
|
document.addEventListener("keydown", keydown);
|
||||||
document.addEventListener("keyup", keyup);
|
document.addEventListener("keyup", keyup);
|
||||||
|
73
src/plugins/moreCommands.ts
Normal file
73
src/plugins/moreCommands.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated, Samu 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 { ApplicationCommandInputType, findOption, OptionalMessageOption, RequiredMessageOption, sendBotMessage } from "../api/Commands";
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
|
import definePlugin from "../utils/types";
|
||||||
|
|
||||||
|
|
||||||
|
function mock(input: string): string {
|
||||||
|
let output = "";
|
||||||
|
for (let i = 0; i < input.length; i++) {
|
||||||
|
output += i % 2 ? input[i].toUpperCase() : input[i].toLowerCase();
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "MoreCommands",
|
||||||
|
description: "echo, lenny, mock",
|
||||||
|
authors: [
|
||||||
|
Devs.Arjix,
|
||||||
|
Devs.echo,
|
||||||
|
{
|
||||||
|
name: "ICodeInAssembly",
|
||||||
|
id: 702973430449832038n
|
||||||
|
}
|
||||||
|
],
|
||||||
|
dependencies: ["CommandsAPI"],
|
||||||
|
commands: [
|
||||||
|
{
|
||||||
|
name: "echo",
|
||||||
|
description: "Sends a message as Clyde (locally)",
|
||||||
|
options: [OptionalMessageOption],
|
||||||
|
inputType: ApplicationCommandInputType.BOT,
|
||||||
|
execute: (opts, ctx) => {
|
||||||
|
const content = findOption(opts, "message", "");
|
||||||
|
|
||||||
|
sendBotMessage(ctx.channel.id, { content });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "lenny",
|
||||||
|
description: "Sends a lenny face",
|
||||||
|
options: [OptionalMessageOption],
|
||||||
|
execute: opts => ({
|
||||||
|
content: findOption(opts, "message", "") + " ( ͡° ͜ʖ ͡°)"
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mock",
|
||||||
|
description: "mOcK PeOpLe",
|
||||||
|
options: [RequiredMessageOption],
|
||||||
|
execute: opts => ({
|
||||||
|
content: mock(findOption(opts, "message", ""))
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
});
|
148
src/plugins/moyai.ts
Normal file
148
src/plugins/moyai.ts
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Message, ReactionEmoji } from "discord-types/general";
|
||||||
|
|
||||||
|
import { makeRange } from "../components/PluginSettings/components/SettingSliderComponent";
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
|
import { sleep } from "../utils/misc";
|
||||||
|
import definePlugin, { OptionType } from "../utils/types";
|
||||||
|
import { Settings } from "../Vencord";
|
||||||
|
import { FluxDispatcher, SelectedChannelStore, UserStore } from "../webpack/common";
|
||||||
|
|
||||||
|
interface IMessageCreate {
|
||||||
|
type: "MESSAGE_CREATE";
|
||||||
|
optimistic: boolean;
|
||||||
|
isPushNotification: boolean;
|
||||||
|
channelId: string;
|
||||||
|
message: Message;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IReactionAdd {
|
||||||
|
type: "MESSAGE_REACTION_ADD";
|
||||||
|
optimistic: boolean;
|
||||||
|
channelId: string;
|
||||||
|
messageId: string;
|
||||||
|
userId: "195136840355807232";
|
||||||
|
emoji: ReactionEmoji;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MOYAI = "🗿";
|
||||||
|
const MOYAI_URL =
|
||||||
|
"https://raw.githubusercontent.com/MeguminSama/VencordPlugins/main/plugins/moyai/moyai.mp3";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "Moyai",
|
||||||
|
authors: [Devs.Megu, Devs.Nuckyz],
|
||||||
|
description: "🗿🗿🗿🗿🗿🗿🗿🗿",
|
||||||
|
|
||||||
|
async onMessage(e: IMessageCreate) {
|
||||||
|
if (e.optimistic || e.type !== "MESSAGE_CREATE") return;
|
||||||
|
if (e.message.state === "SENDING") return;
|
||||||
|
if (Settings.plugins.Moyai.ignoreBots && e.message.author?.bot) return;
|
||||||
|
if (!e.message.content) return;
|
||||||
|
if (e.channelId !== SelectedChannelStore.getChannelId()) return;
|
||||||
|
|
||||||
|
const moyaiCount = getMoyaiCount(e.message.content);
|
||||||
|
|
||||||
|
for (let i = 0; i < moyaiCount; i++) {
|
||||||
|
boom();
|
||||||
|
await sleep(300);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onReaction(e: IReactionAdd) {
|
||||||
|
if (e.optimistic || e.type !== "MESSAGE_REACTION_ADD") return;
|
||||||
|
if (Settings.plugins.Moyai.ignoreBots && UserStore.getUser(e.userId)?.bot) return;
|
||||||
|
if (e.channelId !== SelectedChannelStore.getChannelId()) return;
|
||||||
|
|
||||||
|
const name = e.emoji.name.toLowerCase();
|
||||||
|
if (name !== MOYAI && !name.includes("moyai") && !name.includes("moai")) return;
|
||||||
|
|
||||||
|
boom();
|
||||||
|
},
|
||||||
|
|
||||||
|
start() {
|
||||||
|
FluxDispatcher.subscribe("MESSAGE_CREATE", this.onMessage);
|
||||||
|
FluxDispatcher.subscribe("MESSAGE_REACTION_ADD", this.onReaction);
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
FluxDispatcher.unsubscribe("MESSAGE_CREATE", this.onMessage);
|
||||||
|
FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD", this.onReaction);
|
||||||
|
},
|
||||||
|
|
||||||
|
options: {
|
||||||
|
volume: {
|
||||||
|
description: "Volume of the 🗿🗿🗿",
|
||||||
|
type: OptionType.SLIDER,
|
||||||
|
markers: makeRange(0, 1, 0.1),
|
||||||
|
default: 0.5,
|
||||||
|
stickToMarkers: false,
|
||||||
|
},
|
||||||
|
triggerWhenUnfocused: {
|
||||||
|
description: "Trigger the 🗿 even when the window is unfocused",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
restartNeeded: false,
|
||||||
|
},
|
||||||
|
ignoreBots: {
|
||||||
|
description: "Ignore bots",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
restartNeeded: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function countOccurrences(sourceString: string, subString: string) {
|
||||||
|
let i = 0;
|
||||||
|
let lastIdx = 0;
|
||||||
|
while ((lastIdx = sourceString.indexOf(subString, lastIdx) + 1) !== 0)
|
||||||
|
i++;
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
function countMatches(sourceString: string, pattern: RegExp) {
|
||||||
|
if (!pattern.global)
|
||||||
|
throw new Error("pattern must be global");
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
while (pattern.test(sourceString))
|
||||||
|
i++;
|
||||||
|
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
const customMoyaiRe = /<a?:\w*moy?ai\w*:\d{17,20}>/gi;
|
||||||
|
|
||||||
|
function getMoyaiCount(message: string) {
|
||||||
|
const count = countOccurrences(message, MOYAI)
|
||||||
|
+ countMatches(message, customMoyaiRe);
|
||||||
|
|
||||||
|
return Math.min(count, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function boom() {
|
||||||
|
if (!Settings.plugins.Moyai.triggerWhenUnfocused && !document.hasFocus()) return;
|
||||||
|
const audioElement = document.createElement("audio");
|
||||||
|
audioElement.src = MOYAI_URL;
|
||||||
|
audioElement.volume = Settings.plugins.Moyai.volume;
|
||||||
|
audioElement.play();
|
||||||
|
}
|
@ -1,12 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
import definePlugin from "../utils/types";
|
import definePlugin from "../utils/types";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MuteNewGuild",
|
name: "MuteNewGuild",
|
||||||
description: "Mutes newly joined guilds",
|
description: "Mutes newly joined guilds",
|
||||||
authors: [{
|
authors: [Devs.Glitch],
|
||||||
name:"Glitchy",
|
|
||||||
id: 269567451199569920n
|
|
||||||
}],
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ",acceptInvite:function",
|
find: ",acceptInvite:function",
|
||||||
@ -16,4 +32,4 @@ export default definePlugin({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
})
|
});
|
||||||
|
@ -1,8 +1,27 @@
|
|||||||
import { addPreSendListener, addPreEditListener, SendListener, removePreSendListener, removePreEditListener } from '../api/MessageEvents';
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { addPreEditListener, addPreSendListener, removePreEditListener,removePreSendListener } from "../api/MessageEvents";
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "../utils/types";
|
||||||
|
import { Settings } from "../Vencord";
|
||||||
import { findByProps } from "../webpack";
|
import { findByProps } from "../webpack";
|
||||||
import definePlugin from "../utils/types";
|
import { UserStore } from "../webpack/common";
|
||||||
import { Devs } from '../utils/constants';
|
|
||||||
import { UserStore } from '../webpack/common';
|
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "NitroBypass",
|
name: "NitroBypass",
|
||||||
@ -11,11 +30,25 @@ export default definePlugin({
|
|||||||
dependencies: ["MessageEventsAPI"],
|
dependencies: ["MessageEventsAPI"],
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: `canUseAnimatedEmojis:function`,
|
find: "canUseAnimatedEmojis:function",
|
||||||
|
predicate: () => Settings.plugins.NitroBypass.enableEmojiBypass === true,
|
||||||
replacement: [
|
replacement: [
|
||||||
"canUseAnimatedEmojis",
|
"canUseAnimatedEmojis",
|
||||||
"canUseEmojisEverywhere",
|
"canUseEmojisEverywhere"
|
||||||
"canUseHigherFramerate"
|
].map(func => {
|
||||||
|
return {
|
||||||
|
match: new RegExp(`${func}:function\\(.+?}`),
|
||||||
|
replace: `${func}:function (e) { return true; }`
|
||||||
|
};
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "canUseAnimatedEmojis:function",
|
||||||
|
predicate: () => Settings.plugins.NitroBypass.enableStreamQualityBypass === true,
|
||||||
|
replacement: [
|
||||||
|
"canUseHighVideoUploadQuality",
|
||||||
|
"canStreamHighQuality",
|
||||||
|
"canStreamMidQuality"
|
||||||
].map(func => {
|
].map(func => {
|
||||||
return {
|
return {
|
||||||
match: new RegExp(`${func}:function\\(.+?}`),
|
match: new RegExp(`${func}:function\\(.+?}`),
|
||||||
@ -25,12 +58,27 @@ export default definePlugin({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: "STREAM_FPS_OPTION.format",
|
find: "STREAM_FPS_OPTION.format",
|
||||||
|
predicate: () => Settings.plugins.NitroBypass.enableStreamQualityBypass === true,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(userPremiumType|guildPremiumTier):.{0,10}TIER_\d,?/g,
|
match: /(userPremiumType|guildPremiumTier):.{0,10}TIER_\d,?/g,
|
||||||
replace: ""
|
replace: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
options: {
|
||||||
|
enableEmojiBypass: {
|
||||||
|
description: "Allow sending fake emojis",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
restartNeeded: true,
|
||||||
|
},
|
||||||
|
enableStreamQualityBypass: {
|
||||||
|
description: "Allow streaming in nitro quality",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
restartNeeded: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
get guildId() {
|
get guildId() {
|
||||||
return window.location.href.split("channels/")[1].split("/")[0];
|
return window.location.href.split("channels/")[1].split("/")[0];
|
||||||
@ -41,6 +89,10 @@ export default definePlugin({
|
|||||||
},
|
},
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
|
if (!Settings.plugins.NitroBypass.enableEmojiBypass) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.canUseEmotes) {
|
if (this.canUseEmotes) {
|
||||||
console.info("[NitroBypass] Skipping start because you have nitro");
|
console.info("[NitroBypass] Skipping start because you have nitro");
|
||||||
return;
|
return;
|
||||||
@ -48,27 +100,36 @@ export default definePlugin({
|
|||||||
|
|
||||||
const { getCustomEmojiById } = findByProps("getCustomEmojiById");
|
const { getCustomEmojiById } = findByProps("getCustomEmojiById");
|
||||||
|
|
||||||
|
function getWordBoundary(origStr, offset) {
|
||||||
|
return (!origStr[offset] || /\s/.test(origStr[offset])) ? "" : " ";
|
||||||
|
}
|
||||||
|
|
||||||
this.preSend = addPreSendListener((_, messageObj) => {
|
this.preSend = addPreSendListener((_, messageObj) => {
|
||||||
const guildId = this.guildId;
|
const { guildId } = this;
|
||||||
for (const emoji of messageObj.validNonShortcutEmojis) {
|
for (const emoji of messageObj.validNonShortcutEmojis) {
|
||||||
if (!emoji.require_colons) continue;
|
if (!emoji.require_colons) continue;
|
||||||
if (emoji.guildId === guildId && !emoji.animated) continue;
|
if (emoji.guildId === guildId && !emoji.animated) continue;
|
||||||
|
|
||||||
const emojiString = `<${emoji.animated ? 'a' : ''}:${emoji.originalName || emoji.name}:${emoji.id}>`;
|
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
|
||||||
const url = emoji.url.replace(/\?size=[0-9]+/, `?size=48`);
|
const url = emoji.url.replace(/\?size=[0-9]+/, "?size=48");
|
||||||
messageObj.content = messageObj.content.replace(emojiString, ` ${url} `);
|
messageObj.content = messageObj.content.replace(emojiString, (match, offset, origStr) => {
|
||||||
|
return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.preEdit = addPreEditListener((_, __, messageObj) => {
|
this.preEdit = addPreEditListener((_, __, messageObj) => {
|
||||||
const guildId = this.guildId;
|
const { guildId } = this;
|
||||||
|
|
||||||
for (const [emojiStr, _, emojiId] of messageObj.content.matchAll(/(?<!\\)<a?:(\w+):(\d+)>/ig)) {
|
for (const [emojiStr, _, emojiId] of messageObj.content.matchAll(/(?<!\\)<a?:(\w+):(\d+)>/ig)) {
|
||||||
const emoji = getCustomEmojiById(emojiId);
|
const emoji = getCustomEmojiById(emojiId);
|
||||||
if (emoji == null || (emoji.guildId === guildId && !emoji.animated)) continue;
|
if (emoji == null || (emoji.guildId === guildId && !emoji.animated)) continue;
|
||||||
|
if (!emoji.require_colons) continue;
|
||||||
|
|
||||||
const url = emoji.url.replace(/\?size=[0-9]+/, `?size=48`);
|
const url = emoji.url.replace(/\?size=[0-9]+/, "?size=48");
|
||||||
messageObj.content = messageObj.content.replace(emojiStr, ` ${url} `);
|
messageObj.content = messageObj.content.replace(emojiStr, (match, offset, origStr) => {
|
||||||
|
return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
37
src/plugins/noBlockedMessages.ts
Normal file
37
src/plugins/noBlockedMessages.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
|
import definePlugin from "../utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "NoBlockedMessages",
|
||||||
|
description: "Hides all blocked messages from chat completely.",
|
||||||
|
authors: [Devs.rushii],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: 'safety_prompt:"DMSpamExperiment",response:"show_redacted_messages"',
|
||||||
|
replacement: [
|
||||||
|
{
|
||||||
|
match: /collapsedReason;return (?=\w{1,2}.createElement)/,
|
||||||
|
replace: "collapsedReason; return null;"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
62
src/plugins/noCanaryMessageLinks.ts
Normal file
62
src/plugins/noCanaryMessageLinks.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Samu and Linnea Gräf
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { addPreSendListener, MessageObject, removePreSendListener } from "../api/MessageEvents";
|
||||||
|
import { Devs } from "../utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "../utils/types";
|
||||||
|
import { Settings } from "../Vencord";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "NoCanaryMessageLinks",
|
||||||
|
description: "Allows you to change/remove the subdomain of discord message and channel links",
|
||||||
|
authors: [
|
||||||
|
Devs.Samu,
|
||||||
|
Devs.nea,
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
linkPrefix: {
|
||||||
|
description: "The subdomain for your discord message links",
|
||||||
|
type: OptionType.STRING,
|
||||||
|
default: "",
|
||||||
|
restartNeeded: false,
|
||||||
|
},
|
||||||
|
alwaysUseDiscordHost: {
|
||||||
|
description: "Always use discord.com host (replace discordapp.com)",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: false,
|
||||||
|
restartNeeded: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dependencies: ["MessageEventsAPI"],
|
||||||
|
|
||||||
|
removeBetas(msg: MessageObject) {
|
||||||
|
const settings = Settings.plugins.NoCanaryMessageLinks;
|
||||||
|
msg.content = msg.content.replace(
|
||||||
|
/https:\/\/(?:canary\.|ptb\.)?(discord(?:app)?\.com)(\/channels\/(?:\d{17,20}|@me)\/\d{17,20}(?:\/\d{17,20})?)/g,
|
||||||
|
(_, host, path) => "https://" + (settings.linkPrefix ? settings.linkPrefix + "." : "") + (settings.alwaysUseDiscordHost ? "discord.com" : host) + path
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.preSend = addPreSendListener((_, msg) => this.removeBetas(msg));
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
removePreSendListener(this.preSend);
|
||||||
|
}
|
||||||
|
});
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user