mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-02-24 07:25:10 +00:00
Merge branch 'main' into CustomBanReasons
This commit is contained in:
commit
373f1e1cdb
204 changed files with 4750 additions and 4139 deletions
10
.github/ISSUE_TEMPLATE/blank.yml
vendored
10
.github/ISSUE_TEMPLATE/blank.yml
vendored
|
@ -5,15 +5,9 @@ body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
# READ THIS BEFORE OPENING AN ISSUE
|

|
||||||
|
|
||||||
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
|
GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer.
|
||||||
|
|
||||||
DO NOT USE THIS FORM, unless
|
|
||||||
- you are a vencord contributor
|
|
||||||
- you were given explicit permission to use this form by a moderator in our support server
|
|
||||||
|
|
||||||
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
|
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: content
|
id: content
|
||||||
|
|
21
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
21
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
|
@ -7,24 +7,9 @@ body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
value: |
|
value: |
|
||||||
# READ THIS BEFORE OPENING AN ISSUE
|

|
||||||
|
|
||||||
This form is ONLY FOR DEVELOPERS. YOUR ISSUE WILL BE CLOSED AND YOU WILL POSSIBLY BE BLOCKED FROM THE REPOSITORY IF YOU IGNORE THIS.
|
GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer.
|
||||||
|
|
||||||
DO NOT USE THIS FORM, unless
|
|
||||||
- you are a vencord contributor
|
|
||||||
- you were given explicit permission to use this form by a moderator in our support server
|
|
||||||
|
|
||||||
DO NOT USE THIS FORM FOR SECURITY RELATED ISSUES. [CREATE A SECURITY ADVISORY INSTEAD.](https://github.com/Vendicated/Vencord/security/advisories/new)
|
|
||||||
|
|
||||||
- type: input
|
|
||||||
id: discord
|
|
||||||
attributes:
|
|
||||||
label: Discord Account
|
|
||||||
description: Who on Discord is making this request? Not required but encouraged for easier follow-up
|
|
||||||
placeholder: username#0000
|
|
||||||
validations:
|
|
||||||
required: false
|
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: bug-description
|
id: bug-description
|
||||||
|
@ -77,5 +62,5 @@ body:
|
||||||
options:
|
options:
|
||||||
- label: I am using Discord Stable or tried on Stable and this bug happens there as well
|
- label: I am using Discord Stable or tried on Stable and this bug happens there as well
|
||||||
required: true
|
required: true
|
||||||
- label: I have read the requirements for opening an issue above
|
- label: I am a Vencord Developer
|
||||||
required: true
|
required: true
|
||||||
|
|
BIN
.github/ISSUE_TEMPLATE/developer-banner.png
vendored
Normal file
BIN
.github/ISSUE_TEMPLATE/developer-banner.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
|
@ -42,7 +42,7 @@ jobs:
|
||||||
|
|
||||||
- name: Clean up obsolete files
|
- name: Clean up obsolete files
|
||||||
run: |
|
run: |
|
||||||
rm -rf dist/*-unpacked dist/monaco Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
|
rm -rf dist/*-unpacked dist/vendor Vencord.user.css vencordDesktopRenderer.css vencordDesktopRenderer.css.map
|
||||||
|
|
||||||
- name: Get some values needed for the release
|
- name: Get some values needed for the release
|
||||||
id: release_values
|
id: release_values
|
||||||
|
|
66
.github/workflows/reportBrokenPlugins.yml
vendored
66
.github/workflows/reportBrokenPlugins.yml
vendored
|
@ -1,9 +1,22 @@
|
||||||
name: Test Patches
|
name: Test Patches
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
schedule:
|
inputs:
|
||||||
# Every day at midnight
|
discord_branch:
|
||||||
- cron: 0 0 * * *
|
type: choice
|
||||||
|
description: "Discord Branch to test patches on"
|
||||||
|
options:
|
||||||
|
- both
|
||||||
|
- stable
|
||||||
|
- canary
|
||||||
|
default: both
|
||||||
|
webhook_url:
|
||||||
|
type: string
|
||||||
|
description: "Webhook URL that the report will be posted to. This will be visible for everyone, so DO NOT pass sensitive webhooks like discord webhook. This is meant to be used by Venbot."
|
||||||
|
required: false
|
||||||
|
# schedule:
|
||||||
|
# # Every day at midnight
|
||||||
|
# - cron: 0 0 * * *
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
TestPlugins:
|
TestPlugins:
|
||||||
|
@ -40,28 +53,43 @@ jobs:
|
||||||
- name: Build Vencord Reporter Version
|
- name: Build Vencord Reporter Version
|
||||||
run: pnpm buildReporter
|
run: pnpm buildReporter
|
||||||
|
|
||||||
- name: Create Report
|
- name: Run Reporter
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
run: |
|
run: |
|
||||||
export PATH="$PWD/node_modules/.bin:$PATH"
|
export PATH="$PWD/node_modules/.bin:$PATH"
|
||||||
export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }}
|
export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }}
|
||||||
|
|
||||||
esbuild scripts/generateReport.ts > dist/report.mjs
|
esbuild scripts/generateReport.ts > dist/report.mjs
|
||||||
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
|
|
||||||
env:
|
|
||||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
|
||||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
|
||||||
|
|
||||||
- name: Create Report (Canary)
|
stable_output_file=$(mktemp)
|
||||||
timeout-minutes: 10
|
canary_output_file=$(mktemp)
|
||||||
if: success() || failure() # even run if previous one failed
|
|
||||||
run: |
|
|
||||||
export PATH="$PWD/node_modules/.bin:$PATH"
|
|
||||||
export CHROMIUM_BIN=${{ steps.setup-chrome.outputs.chrome-path }}
|
|
||||||
export USE_CANARY=true
|
|
||||||
|
|
||||||
esbuild scripts/generateReport.ts > dist/report.mjs
|
pids=""
|
||||||
node dist/report.mjs >> $GITHUB_STEP_SUMMARY
|
|
||||||
|
branch="${{ inputs.discord_branch }}"
|
||||||
|
if [[ "${{ github.event_name }}" = "schedule" ]]; then
|
||||||
|
branch="both"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$branch" = "both" || "$branch" = "stable" ]]; then
|
||||||
|
node dist/report.mjs > "$stable_output_file" &
|
||||||
|
pids+=" $!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$branch" = "both" || "$branch" = "canary" ]]; then
|
||||||
|
USE_CANARY=true node dist/report.mjs > "$canary_output_file" &
|
||||||
|
pids+=" $!"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit_code=0
|
||||||
|
for pid in $pids; do
|
||||||
|
if ! wait "$pid"; then
|
||||||
|
exit_code=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
cat "$stable_output_file" "$canary_output_file" >> $GITHUB_STEP_SUMMARY
|
||||||
|
exit $exit_code
|
||||||
env:
|
env:
|
||||||
DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
WEBHOOK_URL: ${{ inputs.webhook_url || secrets.DISCORD_WEBHOOK }}
|
||||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }}
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -8,6 +8,7 @@ vencord_installer
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
bun.lock
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|
||||||
*.log
|
*.log
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
|
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
{
|
{
|
||||||
"resources": ["dist/*", "third-party/*"],
|
"resources": ["dist/*", "vendor/*"],
|
||||||
"matches": ["*://*.discord.com/*"]
|
"matches": ["*://*.discord.com/*"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -15,7 +15,7 @@ declare global {
|
||||||
const getTheme: () => string;
|
const getTheme: () => string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const BASE = "/dist/monaco/vs";
|
const BASE = "/vendor/monaco/vs";
|
||||||
|
|
||||||
self.MonacoEnvironment = {
|
self.MonacoEnvironment = {
|
||||||
getWorkerUrl(_moduleId: unknown, label: string) {
|
getWorkerUrl(_moduleId: unknown, label: string) {
|
||||||
|
|
|
@ -24,12 +24,12 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
script.src = new URL("/dist/monaco/index.js", baseUrl);
|
script.src = new URL("/vendor/monaco/index.js", baseUrl);
|
||||||
|
|
||||||
const style = document.createElement("link");
|
const style = document.createElement("link");
|
||||||
style.type = "text/css";
|
style.type = "text/css";
|
||||||
style.rel = "stylesheet";
|
style.rel = "stylesheet";
|
||||||
style.href = new URL("/dist/monaco/index.css", baseUrl);
|
style.href = new URL("/vendor/monaco/index.css", baseUrl);
|
||||||
|
|
||||||
document.body.append(style, script);
|
document.body.append(style, script);
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -105,7 +105,13 @@ export default tseslint.config(
|
||||||
"no-invalid-regexp": "error",
|
"no-invalid-regexp": "error",
|
||||||
"no-constant-condition": ["error", { "checkLoops": false }],
|
"no-constant-condition": ["error", { "checkLoops": false }],
|
||||||
"no-duplicate-imports": "error",
|
"no-duplicate-imports": "error",
|
||||||
"dot-notation": "error",
|
"@typescript-eslint/dot-notation": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"allowPrivateClassPropertyAccess": true,
|
||||||
|
"allowProtectedClassPropertyAccess": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"no-useless-escape": [
|
"no-useless-escape": [
|
||||||
"error",
|
"error",
|
||||||
{
|
{
|
||||||
|
@ -128,7 +134,7 @@ export default tseslint.config(
|
||||||
"no-unsafe-optional-chaining": "error",
|
"no-unsafe-optional-chaining": "error",
|
||||||
"no-useless-backreference": "error",
|
"no-useless-backreference": "error",
|
||||||
"use-isnan": "error",
|
"use-isnan": "error",
|
||||||
"prefer-const": "error",
|
"prefer-const": ["error", { destructuring: "all" }],
|
||||||
"prefer-spread": "error",
|
"prefer-spread": "error",
|
||||||
|
|
||||||
// Plugin Rules
|
// Plugin Rules
|
||||||
|
|
49
package.json
49
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.11.0",
|
"version": "1.11.5",
|
||||||
"description": "The cutest Discord client mod",
|
"description": "The cutest Discord client mod",
|
||||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -24,19 +24,18 @@
|
||||||
"dev": "pnpm watch",
|
"dev": "pnpm watch",
|
||||||
"watchWeb": "pnpm buildWeb --watch",
|
"watchWeb": "pnpm buildWeb --watch",
|
||||||
"generatePluginJson": "tsx scripts/generatePluginList.ts",
|
"generatePluginJson": "tsx scripts/generatePluginList.ts",
|
||||||
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
|
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types --allowJs false",
|
||||||
"inject": "node scripts/runInstaller.mjs",
|
"inject": "node scripts/runInstaller.mjs",
|
||||||
"uninject": "node scripts/runInstaller.mjs",
|
"uninject": "node scripts/runInstaller.mjs",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
|
||||||
"lint:fix": "pnpm lint --fix",
|
"lint:fix": "pnpm lint --fix",
|
||||||
"test": "pnpm buildStandalone && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
|
"test": "pnpm buildStandalone && pnpm testTsc && pnpm lint && pnpm lint-styles && pnpm generatePluginJson",
|
||||||
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
|
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
|
||||||
"testTsc": "tsc --noEmit"
|
"testTsc": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@intrnl/xxhash64": "^0.1.2",
|
"@intrnl/xxhash64": "^0.1.2",
|
||||||
"@sapphi-red/web-noise-suppressor": "0.3.5",
|
|
||||||
"@vap/core": "0.0.12",
|
"@vap/core": "0.0.12",
|
||||||
"@vap/shiki": "0.10.5",
|
"@vap/shiki": "0.10.5",
|
||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
|
@ -46,31 +45,31 @@
|
||||||
"virtual-merge": "^1.0.1"
|
"virtual-merge": "^1.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@stylistic/eslint-plugin": "^2.12.1",
|
"@stylistic/eslint-plugin": "^4.0.0",
|
||||||
"@types/chrome": "^0.0.287",
|
"@types/chrome": "^0.0.304",
|
||||||
"@types/diff": "^6.0.0",
|
"@types/diff": "^7.0.1",
|
||||||
"@types/lodash": "^4.17.14",
|
"@types/lodash": "^4.17.14",
|
||||||
"@types/node": "^22.10.5",
|
"@types/node": "^22.10.5",
|
||||||
"@types/react": "^19.0.2",
|
"@types/react": "^19.0.10",
|
||||||
"@types/react-dom": "^19.0.2",
|
"@types/react-dom": "^19.0.4",
|
||||||
"@types/yazl": "^2.4.5",
|
"@types/yazl": "^2.4.5",
|
||||||
"diff": "^7.0.0",
|
"diff": "^7.0.0",
|
||||||
"discord-types": "^1.3.26",
|
"discord-types": "^1.3.26",
|
||||||
"esbuild": "^0.15.18",
|
"esbuild": "^0.25.0",
|
||||||
"eslint": "^9.17.0",
|
"eslint": "^9.20.1",
|
||||||
"eslint-import-resolver-alias": "^1.1.2",
|
"eslint-import-resolver-alias": "^1.1.2",
|
||||||
"eslint-plugin-path-alias": "2.1.0",
|
"eslint-plugin-path-alias": "2.1.0",
|
||||||
"eslint-plugin-react": "^7.37.3",
|
"eslint-plugin-react": "^7.37.3",
|
||||||
"eslint-plugin-simple-header": "^1.2.1",
|
"eslint-plugin-simple-header": "^1.2.1",
|
||||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||||
"eslint-plugin-unused-imports": "^4.1.4",
|
"eslint-plugin-unused-imports": "^4.1.4",
|
||||||
"highlight.js": "11.7.0",
|
"highlight.js": "11.11.1",
|
||||||
"html-minifier-terser": "^7.2.0",
|
"html-minifier-terser": "^7.2.0",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
"puppeteer-core": "^23.11.1",
|
"puppeteer-core": "^24.2.1",
|
||||||
"standalone-electron-types": "^1.0.0",
|
"standalone-electron-types": "^34.2.0",
|
||||||
"stylelint": "^16.12.0",
|
"stylelint": "^16.12.0",
|
||||||
"stylelint-config-standard": "^36.0.1",
|
"stylelint-config-standard": "^37.0.0",
|
||||||
"ts-patch": "^3.3.0",
|
"ts-patch": "^3.3.0",
|
||||||
"ts-pattern": "^5.6.0",
|
"ts-pattern": "^5.6.0",
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.19.2",
|
||||||
|
@ -80,10 +79,10 @@
|
||||||
"typescript-transform-paths": "^3.5.3",
|
"typescript-transform-paths": "^3.5.3",
|
||||||
"zip-local": "^0.3.5"
|
"zip-local": "^0.3.5"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.1.0",
|
"packageManager": "pnpm@10.4.1",
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"patchedDependencies": {
|
"patchedDependencies": {
|
||||||
"eslint@9.17.0": "patches/eslint@9.17.0.patch",
|
"eslint@9.20.1": "patches/eslint@9.20.1.patch",
|
||||||
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
|
"eslint-plugin-path-alias@2.1.0": "patches/eslint-plugin-path-alias@2.1.0.patch"
|
||||||
},
|
},
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
|
@ -96,18 +95,14 @@
|
||||||
"source-map-resolve": "*",
|
"source-map-resolve": "*",
|
||||||
"resolve-url": "*",
|
"resolve-url": "*",
|
||||||
"source-map-url": "*",
|
"source-map-url": "*",
|
||||||
"urix": "*"
|
"urix": "*",
|
||||||
}
|
"q": "*"
|
||||||
},
|
},
|
||||||
"webExt": {
|
"onlyBuiltDependencies": [
|
||||||
"artifactsDir": "./dist",
|
"esbuild"
|
||||||
"build": {
|
]
|
||||||
"overwriteDest": true
|
|
||||||
},
|
|
||||||
"sourceDir": "./dist/firefox-unpacked"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18",
|
"node": ">=18"
|
||||||
"pnpm": ">=9"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@vencord/types",
|
"name": "@vencord/types",
|
||||||
"private": false,
|
"private": false,
|
||||||
"version": "0.1.3",
|
"version": "1.11.5",
|
||||||
"description": "",
|
"description": "",
|
||||||
"types": "index.d.ts",
|
"types": "index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -13,16 +13,16 @@
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/fs-extra": "^11.0.4",
|
"@types/fs-extra": "^11.0.4",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.3.0",
|
||||||
"tsx": "^3.12.6"
|
"tsx": "^4.19.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/lodash": "^4.14.191",
|
"@types/lodash": "4.17.15",
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^22.13.4",
|
||||||
"@types/react": "^18.2.0",
|
"@types/react": "18.3.1",
|
||||||
"@types/react-dom": "^18.0.10",
|
"@types/react-dom": "18.3.1",
|
||||||
"discord-types": "^1.3.26",
|
"discord-types": "^1.3.26",
|
||||||
"standalone-electron-types": "^1.0.0",
|
"standalone-electron-types": "^34.2.0",
|
||||||
"type-fest": "^3.5.3"
|
"type-fest": "^4.35.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
1922
pnpm-lock.yaml
generated
1922
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -17,38 +17,41 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import esbuild from "esbuild";
|
// @ts-check
|
||||||
|
|
||||||
import { readdir } from "fs/promises";
|
import { readdir } from "fs/promises";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
|
|
||||||
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch } from "./common.mjs";
|
import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch, buildOrWatchAll, stringifyValues } from "./common.mjs";
|
||||||
|
|
||||||
const defines = {
|
const defines = stringifyValues({
|
||||||
IS_STANDALONE,
|
IS_STANDALONE,
|
||||||
IS_DEV,
|
IS_DEV,
|
||||||
IS_REPORTER,
|
IS_REPORTER,
|
||||||
IS_UPDATER_DISABLED,
|
IS_UPDATER_DISABLED,
|
||||||
IS_WEB: false,
|
IS_WEB: false,
|
||||||
IS_EXTENSION: false,
|
IS_EXTENSION: false,
|
||||||
VERSION: JSON.stringify(VERSION),
|
VERSION,
|
||||||
BUILD_TIMESTAMP
|
BUILD_TIMESTAMP
|
||||||
};
|
});
|
||||||
|
|
||||||
if (defines.IS_STANDALONE === false)
|
if (defines.IS_STANDALONE === "false") {
|
||||||
// If this is a local build (not standalone), optimize
|
// If this is a local build (not standalone), optimize
|
||||||
// for the specific platform we're on
|
// for the specific platform we're on
|
||||||
defines["process.platform"] = JSON.stringify(process.platform);
|
defines["process.platform"] = JSON.stringify(process.platform);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {esbuild.BuildOptions}
|
* @type {import("esbuild").BuildOptions}
|
||||||
*/
|
*/
|
||||||
const nodeCommonOpts = {
|
const nodeCommonOpts = {
|
||||||
...commonOpts,
|
...commonOpts,
|
||||||
|
define: defines,
|
||||||
format: "cjs",
|
format: "cjs",
|
||||||
platform: "node",
|
platform: "node",
|
||||||
target: ["esnext"],
|
target: ["esnext"],
|
||||||
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external],
|
// @ts-ignore this is never undefined
|
||||||
define: defines
|
external: ["electron", "original-fs", "~pluginNatives", ...commonOpts.external]
|
||||||
};
|
};
|
||||||
|
|
||||||
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
|
const sourceMapFooter = s => watch ? "" : `//# sourceMappingURL=vencord://${s}.js.map`;
|
||||||
|
@ -102,25 +105,27 @@ const globNativesPlugin = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
await Promise.all([
|
/** @type {import("esbuild").BuildOptions[]} */
|
||||||
|
const buildConfigs = ([
|
||||||
// Discord Desktop main & renderer & preload
|
// Discord Desktop main & renderer & preload
|
||||||
esbuild.build({
|
{
|
||||||
...nodeCommonOpts,
|
...nodeCommonOpts,
|
||||||
entryPoints: ["src/main/index.ts"],
|
entryPoints: ["src/main/index.ts"],
|
||||||
outfile: "dist/patcher.js",
|
outfile: "dist/patcher.js",
|
||||||
footer: { js: "//# sourceURL=VencordPatcher\n" + sourceMapFooter("patcher") },
|
footer: { js: "//# sourceURL=VencordPatcher\n" + sourceMapFooter("patcher") },
|
||||||
sourcemap,
|
sourcemap,
|
||||||
define: {
|
|
||||||
...defines,
|
|
||||||
IS_DISCORD_DESKTOP: true,
|
|
||||||
IS_VESKTOP: false
|
|
||||||
},
|
|
||||||
plugins: [
|
plugins: [
|
||||||
|
// @ts-ignore this is never undefined
|
||||||
...nodeCommonOpts.plugins,
|
...nodeCommonOpts.plugins,
|
||||||
globNativesPlugin
|
globNativesPlugin
|
||||||
]
|
],
|
||||||
}),
|
define: {
|
||||||
esbuild.build({
|
...defines,
|
||||||
|
IS_DISCORD_DESKTOP: "true",
|
||||||
|
IS_VESKTOP: "false"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
...commonOpts,
|
...commonOpts,
|
||||||
entryPoints: ["src/Vencord.ts"],
|
entryPoints: ["src/Vencord.ts"],
|
||||||
outfile: "dist/renderer.js",
|
outfile: "dist/renderer.js",
|
||||||
|
@ -135,11 +140,11 @@ await Promise.all([
|
||||||
],
|
],
|
||||||
define: {
|
define: {
|
||||||
...defines,
|
...defines,
|
||||||
IS_DISCORD_DESKTOP: true,
|
IS_DISCORD_DESKTOP: "true",
|
||||||
IS_VESKTOP: false
|
IS_VESKTOP: "false"
|
||||||
}
|
}
|
||||||
}),
|
},
|
||||||
esbuild.build({
|
{
|
||||||
...nodeCommonOpts,
|
...nodeCommonOpts,
|
||||||
entryPoints: ["src/preload.ts"],
|
entryPoints: ["src/preload.ts"],
|
||||||
outfile: "dist/preload.js",
|
outfile: "dist/preload.js",
|
||||||
|
@ -147,29 +152,29 @@ await Promise.all([
|
||||||
sourcemap,
|
sourcemap,
|
||||||
define: {
|
define: {
|
||||||
...defines,
|
...defines,
|
||||||
IS_DISCORD_DESKTOP: true,
|
IS_DISCORD_DESKTOP: "true",
|
||||||
IS_VESKTOP: false
|
IS_VESKTOP: "false"
|
||||||
}
|
}
|
||||||
}),
|
},
|
||||||
|
|
||||||
// Vencord Desktop main & renderer & preload
|
// Vencord Desktop main & renderer & preload
|
||||||
esbuild.build({
|
{
|
||||||
...nodeCommonOpts,
|
...nodeCommonOpts,
|
||||||
entryPoints: ["src/main/index.ts"],
|
entryPoints: ["src/main/index.ts"],
|
||||||
outfile: "dist/vencordDesktopMain.js",
|
outfile: "dist/vencordDesktopMain.js",
|
||||||
footer: { js: "//# sourceURL=VencordDesktopMain\n" + sourceMapFooter("vencordDesktopMain") },
|
footer: { js: "//# sourceURL=VencordDesktopMain\n" + sourceMapFooter("vencordDesktopMain") },
|
||||||
sourcemap,
|
sourcemap,
|
||||||
define: {
|
|
||||||
...defines,
|
|
||||||
IS_DISCORD_DESKTOP: false,
|
|
||||||
IS_VESKTOP: true
|
|
||||||
},
|
|
||||||
plugins: [
|
plugins: [
|
||||||
...nodeCommonOpts.plugins,
|
...nodeCommonOpts.plugins,
|
||||||
globNativesPlugin
|
globNativesPlugin
|
||||||
]
|
],
|
||||||
}),
|
define: {
|
||||||
esbuild.build({
|
...defines,
|
||||||
|
IS_DISCORD_DESKTOP: "false",
|
||||||
|
IS_VESKTOP: "true"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
...commonOpts,
|
...commonOpts,
|
||||||
entryPoints: ["src/Vencord.ts"],
|
entryPoints: ["src/Vencord.ts"],
|
||||||
outfile: "dist/vencordDesktopRenderer.js",
|
outfile: "dist/vencordDesktopRenderer.js",
|
||||||
|
@ -184,11 +189,11 @@ await Promise.all([
|
||||||
],
|
],
|
||||||
define: {
|
define: {
|
||||||
...defines,
|
...defines,
|
||||||
IS_DISCORD_DESKTOP: false,
|
IS_DISCORD_DESKTOP: "false",
|
||||||
IS_VESKTOP: true
|
IS_VESKTOP: "true"
|
||||||
}
|
}
|
||||||
}),
|
},
|
||||||
esbuild.build({
|
{
|
||||||
...nodeCommonOpts,
|
...nodeCommonOpts,
|
||||||
entryPoints: ["src/preload.ts"],
|
entryPoints: ["src/preload.ts"],
|
||||||
outfile: "dist/vencordDesktopPreload.js",
|
outfile: "dist/vencordDesktopPreload.js",
|
||||||
|
@ -196,14 +201,10 @@ await Promise.all([
|
||||||
sourcemap,
|
sourcemap,
|
||||||
define: {
|
define: {
|
||||||
...defines,
|
...defines,
|
||||||
IS_DISCORD_DESKTOP: false,
|
IS_DISCORD_DESKTOP: "false",
|
||||||
IS_VESKTOP: true
|
IS_VESKTOP: "true"
|
||||||
}
|
}
|
||||||
}),
|
}
|
||||||
]).catch(err => {
|
]);
|
||||||
console.error("Build failed");
|
|
||||||
console.error(err.message);
|
await buildOrWatchAll(buildConfigs);
|
||||||
// make ci fail
|
|
||||||
if (!commonOpts.watch)
|
|
||||||
process.exitCode = 1;
|
|
||||||
});
|
|
||||||
|
|
|
@ -17,29 +17,30 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import esbuild from "esbuild";
|
// @ts-check
|
||||||
|
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
|
import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import Zip from "zip-local";
|
import Zip from "zip-local";
|
||||||
|
|
||||||
import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION, commonRendererPlugins } from "./common.mjs";
|
import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION, commonRendererPlugins, buildOrWatchAll, stringifyValues } from "./common.mjs";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {esbuild.BuildOptions}
|
* @type {import("esbuild").BuildOptions}
|
||||||
*/
|
*/
|
||||||
const commonOptions = {
|
const commonOptions = {
|
||||||
...commonOpts,
|
...commonOpts,
|
||||||
entryPoints: ["browser/Vencord.ts"],
|
entryPoints: ["browser/Vencord.ts"],
|
||||||
globalName: "Vencord",
|
|
||||||
format: "iife",
|
format: "iife",
|
||||||
|
globalName: "Vencord",
|
||||||
external: ["~plugins", "~git-hash", "/assets/*"],
|
external: ["~plugins", "~git-hash", "/assets/*"],
|
||||||
|
target: ["esnext"],
|
||||||
plugins: [
|
plugins: [
|
||||||
globPlugins("web"),
|
globPlugins("web"),
|
||||||
...commonRendererPlugins
|
...commonRendererPlugins
|
||||||
],
|
],
|
||||||
target: ["esnext"],
|
define: stringifyValues({
|
||||||
define: {
|
|
||||||
IS_WEB: true,
|
IS_WEB: true,
|
||||||
IS_EXTENSION: false,
|
IS_EXTENSION: false,
|
||||||
IS_STANDALONE: true,
|
IS_STANDALONE: true,
|
||||||
|
@ -48,9 +49,9 @@ const commonOptions = {
|
||||||
IS_DISCORD_DESKTOP: false,
|
IS_DISCORD_DESKTOP: false,
|
||||||
IS_VESKTOP: false,
|
IS_VESKTOP: false,
|
||||||
IS_UPDATER_DISABLED: true,
|
IS_UPDATER_DISABLED: true,
|
||||||
VERSION: JSON.stringify(VERSION),
|
VERSION,
|
||||||
BUILD_TIMESTAMP
|
BUILD_TIMESTAMP
|
||||||
}
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const MonacoWorkerEntryPoints = [
|
const MonacoWorkerEntryPoints = [
|
||||||
|
@ -58,52 +59,45 @@ const MonacoWorkerEntryPoints = [
|
||||||
"vs/editor/editor.worker.js"
|
"vs/editor/editor.worker.js"
|
||||||
];
|
];
|
||||||
|
|
||||||
const RnNoiseFiles = [
|
/** @type {import("esbuild").BuildOptions[]} */
|
||||||
"dist/rnnoise.wasm",
|
const buildConfigs = [
|
||||||
"dist/rnnoise_simd.wasm",
|
{
|
||||||
"dist/rnnoise/workletProcessor.js",
|
|
||||||
"LICENSE"
|
|
||||||
];
|
|
||||||
|
|
||||||
await Promise.all(
|
|
||||||
[
|
|
||||||
esbuild.build({
|
|
||||||
entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`),
|
entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`),
|
||||||
bundle: true,
|
bundle: true,
|
||||||
minify: true,
|
minify: true,
|
||||||
format: "iife",
|
format: "iife",
|
||||||
outbase: "node_modules/monaco-editor/esm/",
|
outbase: "node_modules/monaco-editor/esm/",
|
||||||
outdir: "dist/monaco"
|
outdir: "dist/vendor/monaco"
|
||||||
}),
|
},
|
||||||
esbuild.build({
|
{
|
||||||
entryPoints: ["browser/monaco.ts"],
|
entryPoints: ["browser/monaco.ts"],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
minify: true,
|
minify: true,
|
||||||
format: "iife",
|
format: "iife",
|
||||||
outfile: "dist/monaco/index.js",
|
outfile: "dist/vendor/monaco/index.js",
|
||||||
loader: {
|
loader: {
|
||||||
".ttf": "file"
|
".ttf": "file"
|
||||||
}
|
}
|
||||||
}),
|
},
|
||||||
esbuild.build({
|
{
|
||||||
...commonOptions,
|
...commonOptions,
|
||||||
outfile: "dist/browser.js",
|
outfile: "dist/browser.js",
|
||||||
footer: { js: "//# sourceURL=VencordWeb" }
|
footer: { js: "//# sourceURL=VencordWeb" }
|
||||||
}),
|
},
|
||||||
esbuild.build({
|
{
|
||||||
...commonOptions,
|
...commonOptions,
|
||||||
outfile: "dist/extension.js",
|
outfile: "dist/extension.js",
|
||||||
define: {
|
define: {
|
||||||
...commonOptions?.define,
|
...commonOptions.define,
|
||||||
IS_EXTENSION: true,
|
IS_EXTENSION: "true"
|
||||||
},
|
},
|
||||||
footer: { js: "//# sourceURL=VencordWeb" }
|
footer: { js: "//# sourceURL=VencordWeb" }
|
||||||
}),
|
},
|
||||||
esbuild.build({
|
{
|
||||||
...commonOptions,
|
...commonOptions,
|
||||||
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
|
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
|
||||||
define: {
|
define: {
|
||||||
...(commonOptions?.define),
|
...commonOptions.define,
|
||||||
window: "unsafeWindow",
|
window: "unsafeWindow",
|
||||||
},
|
},
|
||||||
outfile: "dist/Vencord.user.js",
|
outfile: "dist/Vencord.user.js",
|
||||||
|
@ -114,14 +108,10 @@ await Promise.all(
|
||||||
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
|
// UserScripts get wrapped in an iife, so define Vencord prop on window that returns our local
|
||||||
js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});"
|
js: "Object.defineProperty(unsafeWindow,'Vencord',{get:()=>Vencord});"
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
]
|
];
|
||||||
).catch(err => {
|
|
||||||
console.error("Build failed");
|
await buildOrWatchAll(buildConfigs);
|
||||||
console.error(err.message);
|
|
||||||
if (!commonOpts.watch)
|
|
||||||
process.exit(1);
|
|
||||||
});;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {(dir: string) => Promise<string[]>}
|
* @type {(dir: string) => Promise<string[]>}
|
||||||
|
@ -155,16 +145,13 @@ async function buildExtension(target, files) {
|
||||||
const entries = {
|
const entries = {
|
||||||
"dist/Vencord.js": await readFile("dist/extension.js"),
|
"dist/Vencord.js": await readFile("dist/extension.js"),
|
||||||
"dist/Vencord.css": await readFile("dist/extension.css"),
|
"dist/Vencord.css": await readFile("dist/extension.css"),
|
||||||
...await loadDir("dist/monaco"),
|
...await loadDir("dist/vendor/monaco", "dist/"),
|
||||||
...Object.fromEntries(await Promise.all(RnNoiseFiles.map(async file =>
|
|
||||||
[`third-party/rnnoise/${file.replace(/^dist\//, "")}`, await readFile(`node_modules/@sapphi-red/web-noise-suppressor/${file}`)]
|
|
||||||
))),
|
|
||||||
...Object.fromEntries(await Promise.all(files.map(async f => {
|
...Object.fromEntries(await Promise.all(files.map(async f => {
|
||||||
let content = await readFile(join("browser", f));
|
let content = await readFile(join("browser", f));
|
||||||
if (f.startsWith("manifest")) {
|
if (f.startsWith("manifest")) {
|
||||||
const json = JSON.parse(content.toString("utf-8"));
|
const json = JSON.parse(content.toString("utf-8"));
|
||||||
json.version = VERSION;
|
json.version = VERSION;
|
||||||
content = new TextEncoder().encode(JSON.stringify(json));
|
content = Buffer.from(new TextEncoder().encode(JSON.stringify(json)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
@ -210,7 +197,6 @@ if (!process.argv.includes("--skip-extension")) {
|
||||||
|
|
||||||
Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
|
Zip.sync.zip("dist/firefox-unpacked").compress().save("dist/extension-firefox.zip");
|
||||||
console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
|
console.info("Packed Firefox Extension written to dist/extension-firefox.zip");
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
await appendCssRuntime;
|
await appendCssRuntime;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,13 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// @ts-check
|
||||||
|
|
||||||
import "../suppressExperimentalWarnings.js";
|
import "../suppressExperimentalWarnings.js";
|
||||||
import "../checkNodeVersion.js";
|
import "../checkNodeVersion.js";
|
||||||
|
|
||||||
import { exec, execSync } from "child_process";
|
import { exec, execSync } from "child_process";
|
||||||
import esbuild from "esbuild";
|
import esbuild, { build, context } from "esbuild";
|
||||||
import { constants as FsConstants, readFileSync } from "fs";
|
import { constants as FsConstants, readFileSync } from "fs";
|
||||||
import { access, readdir, readFile } from "fs/promises";
|
import { access, readdir, readFile } from "fs/promises";
|
||||||
import { minify as minifyHtml } from "html-minifier-terser";
|
import { minify as minifyHtml } from "html-minifier-terser";
|
||||||
|
@ -31,7 +33,7 @@ import { getPluginTarget } from "../utils.mjs";
|
||||||
import { builtinModules } from "module";
|
import { builtinModules } from "module";
|
||||||
|
|
||||||
/** @type {import("../../package.json")} */
|
/** @type {import("../../package.json")} */
|
||||||
const PackageJSON = JSON.parse(readFileSync("package.json"));
|
const PackageJSON = JSON.parse(readFileSync("package.json", "utf-8"));
|
||||||
|
|
||||||
export const VERSION = PackageJSON.version;
|
export const VERSION = PackageJSON.version;
|
||||||
// https://reproducible-builds.org/docs/source-date-epoch/
|
// https://reproducible-builds.org/docs/source-date-epoch/
|
||||||
|
@ -54,6 +56,34 @@ export const banner = {
|
||||||
`.trim()
|
`.trim()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* JSON.stringify all values in an object
|
||||||
|
* @type {(obj: Record<string, any>) => Record<string, string>}
|
||||||
|
*/
|
||||||
|
export function stringifyValues(obj) {
|
||||||
|
for (const key in obj) {
|
||||||
|
obj[key] = JSON.stringify(obj[key]);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {import("esbuild").BuildOptions[]} buildConfigs
|
||||||
|
*/
|
||||||
|
export async function buildOrWatchAll(buildConfigs) {
|
||||||
|
if (watch) {
|
||||||
|
await Promise.all(buildConfigs.map(cfg =>
|
||||||
|
context(cfg).then(ctx => ctx.watch())
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
await Promise.all(buildConfigs.map(cfg => build(cfg)))
|
||||||
|
.catch(error => {
|
||||||
|
console.error(error.message);
|
||||||
|
process.exit(1); // exit immediately to skip the rest of the builds
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const PluginDefinitionNameMatcher = /definePlugin\(\{\s*(["'])?name\1:\s*(["'`])(.+?)\2/;
|
const PluginDefinitionNameMatcher = /definePlugin\(\{\s*(["'])?name\1:\s*(["'`])(.+?)\2/;
|
||||||
/**
|
/**
|
||||||
* @param {string} base
|
* @param {string} base
|
||||||
|
@ -311,18 +341,16 @@ export const banImportPlugin = (filter, message) => ({
|
||||||
export const commonOpts = {
|
export const commonOpts = {
|
||||||
logLevel: "info",
|
logLevel: "info",
|
||||||
bundle: true,
|
bundle: true,
|
||||||
watch,
|
minify: !watch && !IS_REPORTER,
|
||||||
minify: !watch,
|
sourcemap: watch ? "inline" : "external",
|
||||||
sourcemap: watch ? "inline" : "",
|
|
||||||
legalComments: "linked",
|
legalComments: "linked",
|
||||||
banner,
|
banner,
|
||||||
plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin],
|
plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin],
|
||||||
external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"],
|
external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"],
|
||||||
inject: ["./scripts/build/inject/react.mjs"],
|
inject: ["./scripts/build/inject/react.mjs"],
|
||||||
|
jsx: "transform",
|
||||||
jsxFactory: "VencordCreateElement",
|
jsxFactory: "VencordCreateElement",
|
||||||
jsxFragment: "VencordFragment",
|
jsxFragment: "VencordFragment"
|
||||||
// Work around https://github.com/evanw/esbuild/issues/2460
|
|
||||||
tsconfig: "./scripts/build/tsconfig.esbuild.json"
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const escapedBuiltinModules = builtinModules
|
const escapedBuiltinModules = builtinModules
|
||||||
|
@ -335,5 +363,6 @@ export const commonRendererPlugins = [
|
||||||
banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"),
|
banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"),
|
||||||
banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"),
|
banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"),
|
||||||
banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"),
|
banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"),
|
||||||
|
// @ts-ignore this is never undefined
|
||||||
...commonOpts.plugins
|
...commonOpts.plugins
|
||||||
];
|
];
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
// Work around https://github.com/evanw/esbuild/issues/2460
|
|
||||||
{
|
|
||||||
"extends": "../../tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"jsx": "react"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -16,24 +16,27 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* eslint-disable no-fallthrough */
|
|
||||||
|
|
||||||
// eslint-disable-next-line spaced-comment
|
|
||||||
/// <reference types="../src/globals" />
|
/// <reference types="../src/globals" />
|
||||||
// eslint-disable-next-line spaced-comment
|
|
||||||
/// <reference types="../src/modules" />
|
/// <reference types="../src/modules" />
|
||||||
|
|
||||||
|
import { createHmac } from "crypto";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import pup, { JSHandle } from "puppeteer-core";
|
import pup, { JSHandle } from "puppeteer-core";
|
||||||
|
|
||||||
for (const variable of ["DISCORD_TOKEN", "CHROMIUM_BIN"]) {
|
const logStderr = (...data: any[]) => console.error(`${CANARY ? "CANARY" : "STABLE"} ---`, ...data);
|
||||||
|
|
||||||
|
for (const variable of ["CHROMIUM_BIN"]) {
|
||||||
if (!process.env[variable]) {
|
if (!process.env[variable]) {
|
||||||
console.error(`Missing environment variable ${variable}`);
|
logStderr(`Missing environment variable ${variable}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const CANARY = process.env.USE_CANARY === "true";
|
const CANARY = process.env.USE_CANARY === "true";
|
||||||
|
let metaData = {
|
||||||
|
buildNumber: "Unknown Build Number",
|
||||||
|
buildHash: "Unknown Build Hash"
|
||||||
|
};
|
||||||
|
|
||||||
const browser = await pup.launch({
|
const browser = await pup.launch({
|
||||||
headless: true,
|
headless: true,
|
||||||
|
@ -51,14 +54,17 @@ async function maybeGetError(handle: JSHandle): Promise<string | undefined> {
|
||||||
.catch(() => undefined);
|
.catch(() => undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
const report = {
|
interface PatchInfo {
|
||||||
badPatches: [] as {
|
|
||||||
plugin: string;
|
plugin: string;
|
||||||
type: string;
|
type: string;
|
||||||
id: string;
|
id: string;
|
||||||
match: string;
|
match: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
}[],
|
};
|
||||||
|
|
||||||
|
const report = {
|
||||||
|
badPatches: [] as PatchInfo[],
|
||||||
|
slowPatches: [] as PatchInfo[],
|
||||||
badStarts: [] as {
|
badStarts: [] as {
|
||||||
plugin: string;
|
plugin: string;
|
||||||
error: string;
|
error: string;
|
||||||
|
@ -128,35 +134,39 @@ async function printReport() {
|
||||||
|
|
||||||
console.log();
|
console.log();
|
||||||
|
|
||||||
if (process.env.DISCORD_WEBHOOK) {
|
if (process.env.WEBHOOK_URL) {
|
||||||
await fetch(process.env.DISCORD_WEBHOOK, {
|
const patchesToEmbed = (title: string, patches: PatchInfo[], color: number) => ({
|
||||||
method: "POST",
|
title,
|
||||||
headers: {
|
color,
|
||||||
"Content-Type": "application/json"
|
description: patches.map(p => {
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
description: "Here's the latest Vencord Report!",
|
|
||||||
username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""),
|
|
||||||
embeds: [
|
|
||||||
{
|
|
||||||
title: "Bad Patches",
|
|
||||||
description: report.badPatches.map(p => {
|
|
||||||
const lines = [
|
const lines = [
|
||||||
`**__${p.plugin} (${p.type}):__**`,
|
`**__${p.plugin} (${p.type}):__**`,
|
||||||
`ID: \`${p.id}\``,
|
`ID: \`${p.id}\``,
|
||||||
`Match: ${toCodeBlock(p.match, "Match: ".length, true)}`
|
`Match: ${toCodeBlock(p.match, "Match: ".length, true)}`
|
||||||
];
|
];
|
||||||
if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`);
|
if (p.error) lines.push(`Error: ${toCodeBlock(p.error, "Error: ".length, true)}`);
|
||||||
|
|
||||||
return lines.join("\n");
|
return lines.join("\n");
|
||||||
}).join("\n\n") || "None",
|
}).join("\n\n"),
|
||||||
color: report.badPatches.length ? 0xff0000 : 0x00ff00
|
});
|
||||||
},
|
|
||||||
|
const embeds = [
|
||||||
{
|
{
|
||||||
|
author: {
|
||||||
|
name: `Discord ${CANARY ? "Canary" : "Stable"} (${metaData.buildNumber})`,
|
||||||
|
url: `https://nelly.tools/builds/app/${metaData.buildHash}`,
|
||||||
|
icon_url: CANARY ? "https://cdn.discordapp.com/emojis/1252721945699549327.png?size=128" : "https://cdn.discordapp.com/emojis/1252721943463985272.png?size=128"
|
||||||
|
},
|
||||||
|
color: CANARY ? 0xfbb642 : 0x5865f2
|
||||||
|
},
|
||||||
|
report.badPatches.length > 0 && patchesToEmbed("Bad Patches", report.badPatches, 0xff0000),
|
||||||
|
report.slowPatches.length > 0 && patchesToEmbed("Slow Patches", report.slowPatches, 0xf0b232),
|
||||||
|
report.badWebpackFinds.length > 0 && {
|
||||||
title: "Bad Webpack Finds",
|
title: "Bad Webpack Finds",
|
||||||
description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None",
|
description: report.badWebpackFinds.map(f => toCodeBlock(f, 0, true)).join("\n") || "None",
|
||||||
color: report.badWebpackFinds.length ? 0xff0000 : 0x00ff00
|
color: 0xff0000
|
||||||
},
|
},
|
||||||
{
|
report.badStarts.length > 0 && {
|
||||||
title: "Bad Starts",
|
title: "Bad Starts",
|
||||||
description: report.badStarts.map(p => {
|
description: report.badStarts.map(p => {
|
||||||
const lines = [
|
const lines = [
|
||||||
|
@ -166,18 +176,46 @@ async function printReport() {
|
||||||
return lines.join("\n");
|
return lines.join("\n");
|
||||||
}
|
}
|
||||||
).join("\n\n") || "None",
|
).join("\n\n") || "None",
|
||||||
color: report.badStarts.length ? 0xff0000 : 0x00ff00
|
color: 0xff0000
|
||||||
},
|
},
|
||||||
{
|
report.otherErrors.length > 0 && {
|
||||||
title: "Discord Errors",
|
title: "Discord Errors",
|
||||||
description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None",
|
description: report.otherErrors.length ? toCodeBlock(report.otherErrors.join("\n"), 0, true) : "None",
|
||||||
color: report.otherErrors.length ? 0xff0000 : 0x00ff00
|
color: 0xff0000
|
||||||
}
|
}
|
||||||
]
|
].filter(Boolean);
|
||||||
})
|
|
||||||
|
if (embeds.length === 1) {
|
||||||
|
embeds.push({
|
||||||
|
title: "No issues found",
|
||||||
|
description: "Seems like everything is working fine (for now) <:shipit:1330992641466433556>",
|
||||||
|
color: 0x00ff00
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = JSON.stringify({
|
||||||
|
username: "Vencord Reporter" + (CANARY ? " (Canary)" : ""),
|
||||||
|
embeds
|
||||||
|
});
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
};
|
||||||
|
|
||||||
|
// functions similar to https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
|
||||||
|
// used by venbot to ensure webhook invocations are genuine (since we will pass the webhook url as a workflow input which is publicly visible)
|
||||||
|
// generate a secret with something like `openssl rand -hex 128`
|
||||||
|
if (process.env.WEBHOOK_SECRET) {
|
||||||
|
headers["X-Signature"] = "sha256=" + createHmac("sha256", process.env.WEBHOOK_SECRET).update(body).digest("hex");
|
||||||
|
}
|
||||||
|
|
||||||
|
await fetch(process.env.WEBHOOK_URL, {
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
if (!res.ok) console.error(`Webhook failed with status ${res.status}`);
|
if (!res.ok) logStderr(`Webhook failed with status ${res.status}`);
|
||||||
else console.error("Posted to Discord Webhook successfully");
|
else logStderr("Posted to Webhook successfully");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -186,10 +224,13 @@ page.on("console", async e => {
|
||||||
const level = e.type();
|
const level = e.type();
|
||||||
const rawArgs = e.args();
|
const rawArgs = e.args();
|
||||||
|
|
||||||
async function getText() {
|
async function getText(skipFirst = true) {
|
||||||
|
let args = e.args();
|
||||||
|
if (skipFirst) args = args.slice(1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await Promise.all(
|
return await Promise.all(
|
||||||
e.args().map(async a => {
|
args.map(async a => {
|
||||||
return await maybeGetError(a) || await a.jsonValue();
|
return await maybeGetError(a) || await a.jsonValue();
|
||||||
})
|
})
|
||||||
).then(a => a.join(" ").trim());
|
).then(a => a.join(" ").trim());
|
||||||
|
@ -202,6 +243,12 @@ page.on("console", async e => {
|
||||||
|
|
||||||
const isVencord = firstArg === "[Vencord]";
|
const isVencord = firstArg === "[Vencord]";
|
||||||
const isDebug = firstArg === "[PUP_DEBUG]";
|
const isDebug = firstArg === "[PUP_DEBUG]";
|
||||||
|
const isReporterMeta = firstArg === "[REPORTER_META]";
|
||||||
|
|
||||||
|
if (isReporterMeta) {
|
||||||
|
metaData = await rawArgs[1].jsonValue() as any;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
outer:
|
outer:
|
||||||
if (isVencord) {
|
if (isVencord) {
|
||||||
|
@ -215,18 +262,21 @@ page.on("console", async e => {
|
||||||
|
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
case "WebpackInterceptor:":
|
case "WebpackInterceptor:":
|
||||||
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/)!;
|
const patchFailMatch = message.match(/Patch by (.+?) (had no effect|errored|found no module) \(Module id is (.+?)\): (.+)/);
|
||||||
if (!patchFailMatch) break;
|
const patchSlowMatch = message.match(/Patch by (.+?) (took [\d.]+?ms) \(Module id is (.+?)\): (.+)/);
|
||||||
|
const match = patchFailMatch ?? patchSlowMatch;
|
||||||
|
if (!match) break;
|
||||||
|
|
||||||
console.error(await getText());
|
logStderr(await getText());
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
|
|
||||||
const [, plugin, type, id, regex] = patchFailMatch;
|
const [, plugin, type, id, regex] = match;
|
||||||
report.badPatches.push({
|
const list = patchFailMatch ? report.badPatches : report.slowPatches;
|
||||||
|
list.push({
|
||||||
plugin,
|
plugin,
|
||||||
type,
|
type,
|
||||||
id,
|
id,
|
||||||
match: regex.replace(/\(\?:\[A-Za-z_\$\]\[\\w\$\]\*\)/g, "\\i"),
|
match: regex,
|
||||||
error: await maybeGetError(e.args()[3])
|
error: await maybeGetError(e.args()[3])
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -235,7 +285,7 @@ page.on("console", async e => {
|
||||||
const failedToStartMatch = message.match(/Failed to start (.+)/);
|
const failedToStartMatch = message.match(/Failed to start (.+)/);
|
||||||
if (!failedToStartMatch) break;
|
if (!failedToStartMatch) break;
|
||||||
|
|
||||||
console.error(await getText());
|
logStderr(await getText());
|
||||||
process.exitCode = 1;
|
process.exitCode = 1;
|
||||||
|
|
||||||
const [, name] = failedToStartMatch;
|
const [, name] = failedToStartMatch;
|
||||||
|
@ -246,7 +296,7 @@ page.on("console", async e => {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "LazyChunkLoader:":
|
case "LazyChunkLoader:":
|
||||||
console.error(await getText());
|
logStderr(await getText());
|
||||||
|
|
||||||
switch (message) {
|
switch (message) {
|
||||||
case "A fatal error occurred:":
|
case "A fatal error occurred:":
|
||||||
|
@ -255,7 +305,7 @@ page.on("console", async e => {
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case "Reporter:":
|
case "Reporter:":
|
||||||
console.error(await getText());
|
logStderr(await getText());
|
||||||
|
|
||||||
switch (message) {
|
switch (message) {
|
||||||
case "A fatal error occurred:":
|
case "A fatal error occurred:":
|
||||||
|
@ -273,47 +323,36 @@ page.on("console", async e => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isDebug) {
|
if (isDebug) {
|
||||||
console.error(await getText());
|
logStderr(await getText());
|
||||||
} else if (level === "error") {
|
} else if (level === "error") {
|
||||||
const text = await getText();
|
const text = await getText(false);
|
||||||
|
|
||||||
if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) {
|
if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) {
|
||||||
if (IGNORED_DISCORD_ERRORS.some(regex => text.match(regex))) {
|
if (IGNORED_DISCORD_ERRORS.some(regex => text.match(regex))) {
|
||||||
report.ignoredErrors.push(text);
|
report.ignoredErrors.push(text);
|
||||||
} else {
|
} else {
|
||||||
console.error("[Unexpected Error]", text);
|
logStderr("[Unexpected Error]", text);
|
||||||
report.otherErrors.push(text);
|
report.otherErrors.push(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
page.on("error", e => console.error("[Error]", e.message));
|
page.on("error", e => logStderr("[Error]", e.message));
|
||||||
page.on("pageerror", e => {
|
page.on("pageerror", e => {
|
||||||
if (e.message.includes("Sentry successfully disabled")) return;
|
if (e.message.includes("Sentry successfully disabled")) return;
|
||||||
|
|
||||||
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module")) {
|
if (!e.message.startsWith("Object") && !e.message.includes("Cannot find module") && !/^.{1,2}$/.test(e.message)) {
|
||||||
console.error("[Page Error]", e.message);
|
logStderr("[Page Error]", e.message);
|
||||||
report.otherErrors.push(e.message);
|
report.otherErrors.push(e.message);
|
||||||
} else {
|
} else {
|
||||||
report.ignoredErrors.push(e.message);
|
report.ignoredErrors.push(e.message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function reporterRuntime(token: string) {
|
|
||||||
Vencord.Webpack.waitFor(
|
|
||||||
"loginToken",
|
|
||||||
m => {
|
|
||||||
console.log("[PUP_DEBUG]", "Logging in with token...");
|
|
||||||
m.loginToken(token);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await page.evaluateOnNewDocument(`
|
await page.evaluateOnNewDocument(`
|
||||||
if (location.host.endsWith("discord.com")) {
|
if (location.host.endsWith("discord.com")) {
|
||||||
${readFileSync("./dist/browser.js", "utf-8")};
|
${readFileSync("./dist/browser.js", "utf-8")};
|
||||||
(${reporterRuntime.toString()})(${JSON.stringify(process.env.DISCORD_TOKEN)});
|
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ 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 Updater from "./utils/updater";
|
||||||
export * as Webpack from "./webpack";
|
export * as Webpack from "./webpack";
|
||||||
|
export * as WebpackPatcher from "./webpack/patchWebpack";
|
||||||
export { PlainSettings, Settings };
|
export { PlainSettings, Settings };
|
||||||
|
|
||||||
import "./utils/quickCss";
|
import "./utils/quickCss";
|
||||||
|
|
|
@ -4,11 +4,11 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import type { Settings } from "@api/Settings";
|
||||||
import { PluginIpcMappings } from "@main/ipcPlugins";
|
import { PluginIpcMappings } from "@main/ipcPlugins";
|
||||||
import type { UserThemeHeader } from "@main/themes";
|
import type { UserThemeHeader } from "@main/themes";
|
||||||
import { IpcEvents } from "@shared/IpcEvents";
|
import { IpcEvents } from "@shared/IpcEvents";
|
||||||
import { IpcRes } from "@utils/types";
|
import { IpcRes } from "@utils/types";
|
||||||
import type { Settings } from "api/Settings";
|
|
||||||
import { ipcRenderer } from "electron";
|
import { ipcRenderer } from "electron";
|
||||||
|
|
||||||
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
|
function invoke<T = any>(event: IpcEvents, ...args: any[]) {
|
||||||
|
|
|
@ -57,7 +57,7 @@ const Badges = new Set<ProfileBadge>();
|
||||||
* Register a new badge with the Badges API
|
* Register a new badge with the Badges API
|
||||||
* @param badge The badge to register
|
* @param badge The badge to register
|
||||||
*/
|
*/
|
||||||
export function addBadge(badge: ProfileBadge) {
|
export function addProfileBadge(badge: ProfileBadge) {
|
||||||
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
|
badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true });
|
||||||
Badges.add(badge);
|
Badges.add(badge);
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,7 @@ export function addBadge(badge: ProfileBadge) {
|
||||||
* Unregister a badge from the Badges API
|
* Unregister a badge from the Badges API
|
||||||
* @param badge The badge to remove
|
* @param badge The badge to remove
|
||||||
*/
|
*/
|
||||||
export function removeBadge(badge: ProfileBadge) {
|
export function removeProfileBadge(badge: ProfileBadge) {
|
||||||
return Badges.delete(badge);
|
return Badges.delete(badge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,20 +100,3 @@ export interface BadgeUserArgs {
|
||||||
userId: string;
|
userId: string;
|
||||||
guildId: string;
|
guildId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ConnectedAccount {
|
|
||||||
type: string;
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
verified: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Profile {
|
|
||||||
connectedAccounts: ConnectedAccount[];
|
|
||||||
premiumType: number;
|
|
||||||
premiumSince: string;
|
|
||||||
premiumGuildSince?: any;
|
|
||||||
lastFetched: number;
|
|
||||||
profileFetchFailed: boolean;
|
|
||||||
application?: any;
|
|
||||||
}
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import "./ChatButton.css";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { waitFor } from "@webpack";
|
import { waitFor } from "@webpack";
|
||||||
import { Button, ButtonLooks, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
import { Button, ButtonWrapperClasses, Tooltip } from "@webpack/common";
|
||||||
import { Channel } from "discord-types/general";
|
import { Channel } from "discord-types/general";
|
||||||
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
|
import { HTMLProps, JSX, MouseEventHandler, ReactNode } from "react";
|
||||||
|
|
||||||
|
@ -74,9 +74,9 @@ export interface ChatBarProps {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChatBarButton = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null;
|
||||||
|
|
||||||
const buttonFactories = new Map<string, ChatBarButton>();
|
const buttonFactories = new Map<string, ChatBarButtonFactory>();
|
||||||
const logger = new Logger("ChatButtons");
|
const logger = new Logger("ChatButtons");
|
||||||
|
|
||||||
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||||
|
@ -91,7 +91,7 @@ export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button);
|
export const addChatBarButton = (id: string, button: ChatBarButtonFactory) => buttonFactories.set(id, button);
|
||||||
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
export const removeChatBarButton = (id: string) => buttonFactories.delete(id);
|
||||||
|
|
||||||
export interface ChatBarButtonProps {
|
export interface ChatBarButtonProps {
|
||||||
|
@ -110,7 +110,7 @@ export const ChatBarButton = ErrorBoundary.wrap((props: ChatBarButtonProps) => {
|
||||||
<Button
|
<Button
|
||||||
aria-label={props.tooltip}
|
aria-label={props.tooltip}
|
||||||
size=""
|
size=""
|
||||||
look={ButtonLooks.BLANK}
|
look={Button.Looks.BLANK}
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
|
innerClassName={`${ButtonWrapperClasses.button} ${ChannelTextAreaClasses?.button}`}
|
||||||
|
|
|
@ -122,7 +122,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContextMenuProps {
|
interface ContextMenuProps {
|
||||||
contextMenuApiArguments?: Array<any>;
|
contextMenuAPIArguments?: Array<any>;
|
||||||
navId: string;
|
navId: string;
|
||||||
children: Array<ReactElement<any> | null>;
|
children: Array<ReactElement<any> | null>;
|
||||||
"aria-label": string;
|
"aria-label": string;
|
||||||
|
@ -136,7 +136,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
children: cloneMenuChildren(props.children),
|
children: cloneMenuChildren(props.children),
|
||||||
};
|
};
|
||||||
|
|
||||||
props.contextMenuApiArguments ??= [];
|
props.contextMenuAPIArguments ??= [];
|
||||||
const contextMenuPatches = navPatches.get(props.navId);
|
const contextMenuPatches = navPatches.get(props.navId);
|
||||||
|
|
||||||
if (!Array.isArray(props.children)) props.children = [props.children];
|
if (!Array.isArray(props.children)) props.children = [props.children];
|
||||||
|
@ -144,7 +144,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
if (contextMenuPatches) {
|
if (contextMenuPatches) {
|
||||||
for (const patch of contextMenuPatches) {
|
for (const patch of contextMenuPatches) {
|
||||||
try {
|
try {
|
||||||
patch(props.children, ...props.contextMenuApiArguments);
|
patch(props.children, ...props.contextMenuAPIArguments);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
ContextMenuLogger.error(`Patch for ${props.navId} errored,`, err);
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,7 @@ export function _usePatchContextMenu(props: ContextMenuProps) {
|
||||||
|
|
||||||
for (const patch of globalPatches) {
|
for (const patch of globalPatches) {
|
||||||
try {
|
try {
|
||||||
patch(props.navId, props.children, ...props.contextMenuApiArguments);
|
patch(props.navId, props.children, ...props.contextMenuAPIArguments);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ContextMenuLogger.error("Global patch errored,", err);
|
ContextMenuLogger.error("Global patch errored,", err);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Channel, User } from "discord-types/general/index.js";
|
import { Channel, User } from "discord-types/general/index.js";
|
||||||
import { JSX } from "react";
|
import { JSX } from "react";
|
||||||
|
|
||||||
|
@ -39,27 +40,39 @@ interface DecoratorProps {
|
||||||
user: User;
|
user: User;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
export type Decorator = (props: DecoratorProps) => JSX.Element | null;
|
export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null;
|
||||||
type OnlyIn = "guilds" | "dms";
|
type OnlyIn = "guilds" | "dms";
|
||||||
|
|
||||||
export const decorators = new Map<string, { decorator: Decorator, onlyIn?: OnlyIn; }>();
|
export const decoratorsFactories = new Map<string, { render: MemberListDecoratorFactory, onlyIn?: OnlyIn; }>();
|
||||||
|
|
||||||
export function addDecorator(identifier: string, decorator: Decorator, onlyIn?: OnlyIn) {
|
export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) {
|
||||||
decorators.set(identifier, { decorator, onlyIn });
|
decoratorsFactories.set(identifier, { render, onlyIn });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeDecorator(identifier: string) {
|
export function removeMemberListDecorator(identifier: string) {
|
||||||
decorators.delete(identifier);
|
decoratorsFactories.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] {
|
export function __getDecorators(props: DecoratorProps): JSX.Element {
|
||||||
const isInGuild = !!(props.guildId);
|
const isInGuild = !!(props.guildId);
|
||||||
return Array.from(decorators.values(), decoratorObj => {
|
|
||||||
const { decorator, onlyIn } = decoratorObj;
|
const decorators = Array.from(
|
||||||
// this can most likely be done cleaner
|
decoratorsFactories.entries(),
|
||||||
if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) {
|
([key, { render: Decorator, onlyIn }]) => {
|
||||||
return decorator(props);
|
if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild))
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
});
|
|
||||||
|
return (
|
||||||
|
<ErrorBoundary noop key={key} message={`Failed to render ${key} Member List Decorator`}>
|
||||||
|
<Decorator {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="vc-member-list-decorators-wrapper">
|
||||||
|
{decorators}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -16,28 +16,29 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { JSX } from "react";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import { JSX, ReactNode } from "react";
|
||||||
|
|
||||||
export type AccessoryCallback = (props: Record<string, any>) => JSX.Element | null | Array<JSX.Element | null>;
|
export type MessageAccessoryFactory = (props: Record<string, any>) => ReactNode;
|
||||||
export type Accessory = {
|
export type MessageAccessory = {
|
||||||
callback: AccessoryCallback;
|
render: MessageAccessoryFactory;
|
||||||
position?: number;
|
position?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const accessories = new Map<String, Accessory>();
|
export const accessories = new Map<string, MessageAccessory>();
|
||||||
|
|
||||||
export function addAccessory(
|
export function addMessageAccessory(
|
||||||
identifier: string,
|
identifier: string,
|
||||||
callback: AccessoryCallback,
|
render: MessageAccessoryFactory,
|
||||||
position?: number
|
position?: number
|
||||||
) {
|
) {
|
||||||
accessories.set(identifier, {
|
accessories.set(identifier, {
|
||||||
callback,
|
render,
|
||||||
position,
|
position,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeAccessory(identifier: string) {
|
export function removeMessageAccessory(identifier: string) {
|
||||||
accessories.delete(identifier);
|
accessories.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,15 +46,12 @@ export function _modifyAccessories(
|
||||||
elements: JSX.Element[],
|
elements: JSX.Element[],
|
||||||
props: Record<string, any>
|
props: Record<string, any>
|
||||||
) {
|
) {
|
||||||
for (const accessory of accessories.values()) {
|
for (const [key, accessory] of accessories.entries()) {
|
||||||
let accessories = accessory.callback(props);
|
const res = (
|
||||||
if (accessories == null)
|
<ErrorBoundary message={`Failed to render ${key} Message Accessory`} key={key}>
|
||||||
continue;
|
<accessory.render {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
if (!Array.isArray(accessories))
|
);
|
||||||
accessories = [accessories];
|
|
||||||
else if (accessories.length === 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
elements.splice(
|
elements.splice(
|
||||||
accessory.position != null
|
accessory.position != null
|
||||||
|
@ -62,7 +60,7 @@ export function _modifyAccessories(
|
||||||
: accessory.position
|
: accessory.position
|
||||||
: elements.length,
|
: elements.length,
|
||||||
0,
|
0,
|
||||||
...accessories.filter(e => e != null) as JSX.Element[]
|
res
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Channel, Message } from "discord-types/general/index.js";
|
import { Channel, Message } from "discord-types/general/index.js";
|
||||||
import { JSX } from "react";
|
import { JSX } from "react";
|
||||||
|
|
||||||
interface DecorationProps {
|
export interface MessageDecorationProps {
|
||||||
author: {
|
author: {
|
||||||
/**
|
/**
|
||||||
* Will be username if the user has no nickname
|
* Will be username if the user has no nickname
|
||||||
|
@ -45,20 +46,31 @@ interface DecorationProps {
|
||||||
message: Message;
|
message: Message;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
export type Decoration = (props: DecorationProps) => JSX.Element | null;
|
export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null;
|
||||||
|
|
||||||
export const decorations = new Map<string, Decoration>();
|
export const decorationsFactories = new Map<string, MessageDecorationFactory>();
|
||||||
|
|
||||||
export function addDecoration(identifier: string, decoration: Decoration) {
|
export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) {
|
||||||
decorations.set(identifier, decoration);
|
decorationsFactories.set(identifier, decoration);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeDecoration(identifier: string) {
|
export function removeMessageDecoration(identifier: string) {
|
||||||
decorations.delete(identifier);
|
decorationsFactories.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function __addDecorationsToMessage(props: DecorationProps): (JSX.Element | null)[] {
|
export function __addDecorationsToMessage(props: MessageDecorationProps): JSX.Element {
|
||||||
return [...decorations.values()].map(decoration => {
|
const decorations = Array.from(
|
||||||
return decoration(props);
|
decorationsFactories.entries(),
|
||||||
});
|
([key, Decoration]) => (
|
||||||
|
<ErrorBoundary noop message={`Failed to render ${key} Message Decoration`} key={key}>
|
||||||
|
<Decoration {...props} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="vc-message-decorations-wrapper">
|
||||||
|
{decorations}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
|
@ -73,11 +73,11 @@ export interface MessageExtra {
|
||||||
openWarningPopout: (props: any) => any;
|
openWarningPopout: (props: any) => any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
export type MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable<void | { cancel: boolean; }>;
|
||||||
export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable<void | { cancel: boolean; }>;
|
||||||
|
|
||||||
const sendListeners = new Set<SendListener>();
|
const sendListeners = new Set<MessageSendListener>();
|
||||||
const editListeners = new Set<EditListener>();
|
const editListeners = new Set<MessageEditListener>();
|
||||||
|
|
||||||
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
|
export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) {
|
||||||
extra.replyOptions = replyOptions;
|
extra.replyOptions = replyOptions;
|
||||||
|
@ -111,29 +111,29 @@ export async function _handlePreEdit(channelId: string, messageId: string, messa
|
||||||
/**
|
/**
|
||||||
* Note: This event fires off before a message is sent, allowing you to edit the message.
|
* Note: This event fires off before a message is sent, allowing you to edit the message.
|
||||||
*/
|
*/
|
||||||
export function addPreSendListener(listener: SendListener) {
|
export function addMessagePreSendListener(listener: MessageSendListener) {
|
||||||
sendListeners.add(listener);
|
sendListeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Note: This event fires off before a message's edit is applied, allowing you to further edit the message.
|
* Note: This event fires off before a message's edit is applied, allowing you to further edit the message.
|
||||||
*/
|
*/
|
||||||
export function addPreEditListener(listener: EditListener) {
|
export function addMessagePreEditListener(listener: MessageEditListener) {
|
||||||
editListeners.add(listener);
|
editListeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
export function removePreSendListener(listener: SendListener) {
|
export function removeMessagePreSendListener(listener: MessageSendListener) {
|
||||||
return sendListeners.delete(listener);
|
return sendListeners.delete(listener);
|
||||||
}
|
}
|
||||||
export function removePreEditListener(listener: EditListener) {
|
export function removeMessagePreEditListener(listener: MessageEditListener) {
|
||||||
return editListeners.delete(listener);
|
return editListeners.delete(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Message clicks
|
// Message clicks
|
||||||
type ClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
|
export type MessageClickListener = (message: Message, channel: Channel, event: MouseEvent) => void;
|
||||||
|
|
||||||
const listeners = new Set<ClickListener>();
|
const listeners = new Set<MessageClickListener>();
|
||||||
|
|
||||||
export function _handleClick(message: Message, channel: Channel, event: MouseEvent) {
|
export function _handleClick(message: Message, channel: Channel, event: MouseEvent) {
|
||||||
// message object may be outdated, so (try to) fetch latest one
|
// message object may be outdated, so (try to) fetch latest one
|
||||||
|
@ -147,11 +147,11 @@ export function _handleClick(message: Message, channel: Channel, event: MouseEve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addClickListener(listener: ClickListener) {
|
export function addMessageClickListener(listener: MessageClickListener) {
|
||||||
listeners.add(listener);
|
listeners.add(listener);
|
||||||
return listener;
|
return listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeClickListener(listener: ClickListener) {
|
export function removeMessageClickListener(listener: MessageClickListener) {
|
||||||
return listeners.delete(listener);
|
return listeners.delete(listener);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ import type { ComponentType, MouseEventHandler } from "react";
|
||||||
|
|
||||||
const logger = new Logger("MessagePopover");
|
const logger = new Logger("MessagePopover");
|
||||||
|
|
||||||
export interface ButtonItem {
|
export interface MessagePopoverButtonItem {
|
||||||
key?: string,
|
key?: string,
|
||||||
label: string,
|
label: string,
|
||||||
icon: ComponentType<any>,
|
icon: ComponentType<any>,
|
||||||
|
@ -33,23 +33,23 @@ export interface ButtonItem {
|
||||||
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
onContextMenu?: MouseEventHandler<HTMLButtonElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type getButtonItem = (message: Message) => ButtonItem | null;
|
export type MessagePopoverButtonFactory = (message: Message) => MessagePopoverButtonItem | null;
|
||||||
|
|
||||||
export const buttons = new Map<string, getButtonItem>();
|
export const buttons = new Map<string, MessagePopoverButtonFactory>();
|
||||||
|
|
||||||
export function addButton(
|
export function addMessagePopoverButton(
|
||||||
identifier: string,
|
identifier: string,
|
||||||
item: getButtonItem,
|
item: MessagePopoverButtonFactory,
|
||||||
) {
|
) {
|
||||||
buttons.set(identifier, item);
|
buttons.set(identifier, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeButton(identifier: string) {
|
export function removeMessagePopoverButton(identifier: string) {
|
||||||
buttons.delete(identifier);
|
buttons.delete(identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function _buildPopoverElements(
|
export function _buildPopoverElements(
|
||||||
Component: React.ComponentType<ButtonItem>,
|
Component: React.ComponentType<MessagePopoverButtonItem>,
|
||||||
message: Message
|
message: Message
|
||||||
) {
|
) {
|
||||||
const items: React.ReactNode[] = [];
|
const items: React.ReactNode[] = [];
|
||||||
|
|
|
@ -16,41 +16,36 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Logger } from "@utils/Logger";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { JSX } from "react";
|
import { ComponentType } from "react";
|
||||||
|
|
||||||
const logger = new Logger("ServerListAPI");
|
|
||||||
|
|
||||||
export const enum ServerListRenderPosition {
|
export const enum ServerListRenderPosition {
|
||||||
Above,
|
Above,
|
||||||
In,
|
In,
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderFunctionsAbove = new Set<Function>();
|
const componentsAbove = new Set<ComponentType>();
|
||||||
const renderFunctionsIn = new Set<Function>();
|
const componentsBelow = new Set<ComponentType>();
|
||||||
|
|
||||||
function getRenderFunctions(position: ServerListRenderPosition) {
|
function getRenderFunctions(position: ServerListRenderPosition) {
|
||||||
return position === ServerListRenderPosition.Above ? renderFunctionsAbove : renderFunctionsIn;
|
return position === ServerListRenderPosition.Above ? componentsAbove : componentsBelow;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
export function addServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||||
getRenderFunctions(position).add(renderFunction);
|
getRenderFunctions(position).add(renderFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: Function) {
|
export function removeServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) {
|
||||||
getRenderFunctions(position).delete(renderFunction);
|
getRenderFunctions(position).delete(renderFunction);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const renderAll = (position: ServerListRenderPosition) => {
|
export const renderAll = (position: ServerListRenderPosition) => {
|
||||||
const ret: Array<JSX.Element> = [];
|
return Array.from(
|
||||||
|
getRenderFunctions(position),
|
||||||
for (const renderFunction of getRenderFunctions(position)) {
|
(Component, i) => (
|
||||||
try {
|
<ErrorBoundary noop key={i}>
|
||||||
ret.unshift(renderFunction());
|
<Component />
|
||||||
} catch (e) {
|
</ErrorBoundary>
|
||||||
logger.error("Failed to render server list element:", e);
|
)
|
||||||
}
|
);
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
};
|
|
@ -32,9 +32,10 @@ export interface Settings {
|
||||||
autoUpdate: boolean;
|
autoUpdate: boolean;
|
||||||
autoUpdateNotification: boolean,
|
autoUpdateNotification: boolean,
|
||||||
useQuickCss: boolean;
|
useQuickCss: boolean;
|
||||||
|
eagerPatches: boolean;
|
||||||
|
enabledThemes: string[];
|
||||||
enableReactDevtools: boolean;
|
enableReactDevtools: boolean;
|
||||||
themeLinks: string[];
|
themeLinks: string[];
|
||||||
enabledThemes: string[];
|
|
||||||
frameless: boolean;
|
frameless: boolean;
|
||||||
transparent: boolean;
|
transparent: boolean;
|
||||||
winCtrlQ: boolean;
|
winCtrlQ: boolean;
|
||||||
|
@ -81,6 +82,7 @@ const DefaultSettings: Settings = {
|
||||||
autoUpdateNotification: true,
|
autoUpdateNotification: true,
|
||||||
useQuickCss: true,
|
useQuickCss: true,
|
||||||
themeLinks: [],
|
themeLinks: [],
|
||||||
|
eagerPatches: IS_REPORTER,
|
||||||
enabledThemes: [],
|
enabledThemes: [],
|
||||||
enableReactDevtools: false,
|
enableReactDevtools: false,
|
||||||
frameless: false,
|
frameless: false,
|
||||||
|
@ -220,6 +222,17 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function migratePluginSetting(pluginName: string, oldSetting: string, newSetting: string) {
|
||||||
|
const settings = SettingsStore.plain.plugins[pluginName];
|
||||||
|
if (!settings) return;
|
||||||
|
|
||||||
|
if (!Object.hasOwn(settings, oldSetting) || Object.hasOwn(settings, newSetting)) return;
|
||||||
|
|
||||||
|
settings[newSetting] = settings[oldSetting];
|
||||||
|
delete settings[oldSetting];
|
||||||
|
SettingsStore.markAsChanged();
|
||||||
|
}
|
||||||
|
|
||||||
export function definePluginSettings<
|
export function definePluginSettings<
|
||||||
Def extends SettingsDefinition,
|
Def extends SettingsDefinition,
|
||||||
Checks extends SettingsChecks<Def>,
|
Checks extends SettingsChecks<Def>,
|
||||||
|
|
|
@ -17,16 +17,22 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Button } from "@webpack/common";
|
import { Button } from "@webpack/common";
|
||||||
|
import { ButtonProps } from "@webpack/types";
|
||||||
|
|
||||||
import { Heart } from "./Heart";
|
import { Heart } from "./Heart";
|
||||||
|
|
||||||
export default function DonateButton(props: any) {
|
export default function DonateButton({
|
||||||
|
look = Button.Looks.LINK,
|
||||||
|
color = Button.Colors.TRANSPARENT,
|
||||||
|
...props
|
||||||
|
}: Partial<ButtonProps>) {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
{...props}
|
{...props}
|
||||||
look={Button.Looks.LINK}
|
look={look}
|
||||||
color={Button.Colors.TRANSPARENT}
|
color={color}
|
||||||
onClick={() => VencordNative.native.openExternal("https://github.com/sponsors/Vendicated")}
|
onClick={() => VencordNative.native.openExternal("https://github.com/sponsors/Vendicated")}
|
||||||
|
innerClassName="vc-donate-button"
|
||||||
>
|
>
|
||||||
<Heart />
|
<Heart />
|
||||||
Donate
|
Donate
|
||||||
|
|
|
@ -70,8 +70,7 @@ const ErrorBoundary = LazyComponent(() => {
|
||||||
|
|
||||||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||||
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps });
|
||||||
logger.error("A component threw an Error\n", error);
|
logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack);
|
||||||
logger.error("Component Stack", errorInfo.componentStack);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -16,14 +16,18 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function Heart() {
|
import { classes } from "@utils/misc";
|
||||||
|
import { SVGProps } from "react";
|
||||||
|
|
||||||
|
export function Heart(props: SVGProps<SVGSVGElement>) {
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
height="16"
|
|
||||||
viewBox="0 0 16 16"
|
viewBox="0 0 16 16"
|
||||||
|
height="16"
|
||||||
width="16"
|
width="16"
|
||||||
style={{ marginRight: "0.5em", transform: "translateY(2px)" }}
|
{...props}
|
||||||
|
className={classes("vc-heart-icon", props.className)}
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="#db61a2"
|
fill="#db61a2"
|
||||||
|
|
|
@ -37,6 +37,7 @@ import { Constructor } from "type-fest";
|
||||||
import { PluginMeta } from "~plugins";
|
import { PluginMeta } from "~plugins";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ISettingCustomElementProps,
|
||||||
ISettingElementProps,
|
ISettingElementProps,
|
||||||
SettingBooleanComponent,
|
SettingBooleanComponent,
|
||||||
SettingCustomComponent,
|
SettingCustomComponent,
|
||||||
|
@ -74,14 +75,15 @@ function makeDummyUser(user: { username: string; id?: string; avatar?: string; }
|
||||||
return newUser;
|
return newUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any>>> = {
|
const Components: Record<OptionType, React.ComponentType<ISettingElementProps<any> | ISettingCustomElementProps<any>>> = {
|
||||||
[OptionType.STRING]: SettingTextComponent,
|
[OptionType.STRING]: SettingTextComponent,
|
||||||
[OptionType.NUMBER]: SettingNumericComponent,
|
[OptionType.NUMBER]: SettingNumericComponent,
|
||||||
[OptionType.BIGINT]: SettingNumericComponent,
|
[OptionType.BIGINT]: SettingNumericComponent,
|
||||||
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
[OptionType.BOOLEAN]: SettingBooleanComponent,
|
||||||
[OptionType.SELECT]: SettingSelectComponent,
|
[OptionType.SELECT]: SettingSelectComponent,
|
||||||
[OptionType.SLIDER]: SettingSliderComponent,
|
[OptionType.SLIDER]: SettingSliderComponent,
|
||||||
[OptionType.COMPONENT]: SettingCustomComponent
|
[OptionType.COMPONENT]: SettingCustomComponent,
|
||||||
|
[OptionType.CUSTOM]: () => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
|
||||||
|
@ -129,7 +131,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
for (const [key, value] of Object.entries(tempSettings)) {
|
for (const [key, value] of Object.entries(tempSettings)) {
|
||||||
const option = plugin.options[key];
|
const option = plugin.options[key];
|
||||||
pluginSettings[key] = value;
|
pluginSettings[key] = value;
|
||||||
option?.onChange?.(value);
|
|
||||||
|
if (option.type === OptionType.CUSTOM) continue;
|
||||||
if (option?.restartNeeded) restartNeeded = true;
|
if (option?.restartNeeded) restartNeeded = true;
|
||||||
}
|
}
|
||||||
if (restartNeeded) onRestartNeeded();
|
if (restartNeeded) onRestartNeeded();
|
||||||
|
@ -141,7 +144,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||||
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
|
||||||
} else {
|
} else {
|
||||||
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
const options = Object.entries(plugin.options).map(([key, setting]) => {
|
||||||
if (setting.hidden) return null;
|
if (setting.type === OptionType.CUSTOM || setting.hidden) return null;
|
||||||
|
|
||||||
function onChange(newValue: any) {
|
function onChange(newValue: any) {
|
||||||
setTempSettings(s => ({ ...s, [key]: newValue }));
|
setTempSettings(s => ({ ...s, [key]: newValue }));
|
||||||
|
|
|
@ -18,8 +18,8 @@
|
||||||
|
|
||||||
import { PluginOptionComponent } from "@utils/types";
|
import { PluginOptionComponent } from "@utils/types";
|
||||||
|
|
||||||
import { ISettingElementProps } from ".";
|
import { ISettingCustomElementProps } from ".";
|
||||||
|
|
||||||
export function SettingCustomComponent({ option, onChange, onError }: ISettingElementProps<PluginOptionComponent>) {
|
export function SettingCustomComponent({ option, onChange, onError }: ISettingCustomElementProps<PluginOptionComponent>) {
|
||||||
return option.component({ setValue: onChange, setError: onError, option });
|
return option.component({ setValue: onChange, setError: onError, option });
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
import { DefinedSettings, PluginOptionBase } from "@utils/types";
|
import { DefinedSettings, PluginOptionBase } from "@utils/types";
|
||||||
|
|
||||||
export interface ISettingElementProps<T extends PluginOptionBase> {
|
interface ISettingElementPropsBase<T> {
|
||||||
option: T;
|
option: T;
|
||||||
onChange(newValue: any): void;
|
onChange(newValue: any): void;
|
||||||
pluginSettings: {
|
pluginSettings: {
|
||||||
|
@ -30,6 +30,9 @@ export interface ISettingElementProps<T extends PluginOptionBase> {
|
||||||
definedSettings?: DefinedSettings;
|
definedSettings?: DefinedSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type ISettingElementProps<T extends PluginOptionBase> = ISettingElementPropsBase<T>;
|
||||||
|
export type ISettingCustomElementProps<T extends Omit<PluginOptionBase, "description" | "placeholder">> = ISettingElementPropsBase<T>;
|
||||||
|
|
||||||
export * from "../../Badge";
|
export * from "../../Badge";
|
||||||
export * from "./SettingBooleanComponent";
|
export * from "./SettingBooleanComponent";
|
||||||
export * from "./SettingCustomComponent";
|
export * from "./SettingCustomComponent";
|
||||||
|
|
|
@ -69,7 +69,7 @@ function ReloadRequiredCard({ required }: { required: boolean; }) {
|
||||||
<Forms.FormText className={cl("dep-text")}>
|
<Forms.FormText className={cl("dep-text")}>
|
||||||
Restart now to apply new plugins and their settings
|
Restart now to apply new plugins and their settings
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<Button onClick={() => location.reload()}>
|
<Button onClick={() => location.reload()} className={cl("restart-button")}>
|
||||||
Restart
|
Restart
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
|
@ -158,8 +158,8 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
|
||||||
className={classes(ButtonClasses.button, cl("info-button"))}
|
className={classes(ButtonClasses.button, cl("info-button"))}
|
||||||
>
|
>
|
||||||
{plugin.options && !isObjectEmpty(plugin.options)
|
{plugin.options && !isObjectEmpty(plugin.options)
|
||||||
? <CogWheel />
|
? <CogWheel className={cl("info-icon")} />
|
||||||
: <InfoIcon />}
|
: <InfoIcon className={cl("info-icon")} />}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -63,10 +63,7 @@
|
||||||
height: 8em;
|
height: 8em;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
gap: 0.25em;
|
||||||
|
|
||||||
.vc-plugins-info-card div {
|
|
||||||
line-height: 32px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-plugins-restart-card {
|
.vc-plugins-restart-card {
|
||||||
|
@ -76,11 +73,11 @@
|
||||||
color: var(--info-warning-text);
|
color: var(--info-warning-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-plugins-restart-card button {
|
.vc-plugins-restart-button {
|
||||||
margin-top: 0.5em;
|
margin-top: 0.5em;
|
||||||
background: var(--info-warning-foreground) !important;
|
background: var(--info-warning-foreground) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-plugins-info-button svg:not(:hover, :focus) {
|
.vc-plugins-info-icon:not(:hover, :focus) {
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,7 +65,7 @@ function ReplacementComponent({ module, match, replacement, setReplacementError
|
||||||
}
|
}
|
||||||
const canonicalMatch = canonicalizeMatch(new RegExp(match));
|
const canonicalMatch = canonicalizeMatch(new RegExp(match));
|
||||||
try {
|
try {
|
||||||
const canonicalReplace = canonicalizeReplace(replacement, "YourPlugin");
|
const canonicalReplace = canonicalizeReplace(replacement, 'Vencord.Plugins.plugins["YourPlugin"]');
|
||||||
var patched = src.replace(canonicalMatch, canonicalReplace as string);
|
var patched = src.replace(canonicalMatch, canonicalReplace as string);
|
||||||
setReplacementError(void 0);
|
setReplacementError(void 0);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
77
src/components/VencordSettings/SpecialCard.tsx
Normal file
77
src/components/VencordSettings/SpecialCard.tsx
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "./specialCard.css";
|
||||||
|
|
||||||
|
import { classNameFactory } from "@api/Styles";
|
||||||
|
import { Card, Clickable, Forms, React } from "@webpack/common";
|
||||||
|
import type { PropsWithChildren } from "react";
|
||||||
|
|
||||||
|
const cl = classNameFactory("vc-special-");
|
||||||
|
|
||||||
|
interface StyledCardProps {
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
description: string;
|
||||||
|
cardImage?: string;
|
||||||
|
backgroundImage?: string;
|
||||||
|
backgroundColor?: string;
|
||||||
|
buttonTitle?: string;
|
||||||
|
buttonOnClick?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SpecialCard({ title, subtitle, description, cardImage, backgroundImage, backgroundColor, buttonTitle, buttonOnClick: onClick, children }: PropsWithChildren<StyledCardProps>) {
|
||||||
|
const cardStyle: React.CSSProperties = {
|
||||||
|
backgroundColor: backgroundColor || "#9c85ef",
|
||||||
|
backgroundImage: `url(${backgroundImage || ""})`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={cl("card", "card-special")} style={cardStyle}>
|
||||||
|
<div className={cl("card-flex")}>
|
||||||
|
<div className={cl("card-flex-main")}>
|
||||||
|
<Forms.FormTitle className={cl("title")} tag="h5">{title}</Forms.FormTitle>
|
||||||
|
<Forms.FormText className={cl("subtitle")}>{subtitle}</Forms.FormText>
|
||||||
|
<Forms.FormText className={cl("text")}>{description}</Forms.FormText>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
{cardImage && (
|
||||||
|
<div className={cl("image-container")}>
|
||||||
|
<img
|
||||||
|
role="presentation"
|
||||||
|
src={cardImage}
|
||||||
|
alt=""
|
||||||
|
className={cl("image")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{buttonTitle && (
|
||||||
|
<>
|
||||||
|
<Forms.FormDivider className={cl("seperator")} />
|
||||||
|
<Clickable onClick={onClick} className={cl("hyperlink")}>
|
||||||
|
<Forms.FormText className={cl("hyperlink-text")}>
|
||||||
|
{buttonTitle}
|
||||||
|
</Forms.FormText>
|
||||||
|
</Clickable>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
|
@ -20,29 +20,38 @@ import { openNotificationLogModal } from "@api/Notifications/notificationLog";
|
||||||
import { useSettings } from "@api/Settings";
|
import { useSettings } from "@api/Settings";
|
||||||
import { classNameFactory } from "@api/Styles";
|
import { classNameFactory } from "@api/Styles";
|
||||||
import DonateButton from "@components/DonateButton";
|
import DonateButton from "@components/DonateButton";
|
||||||
|
import { openContributorModal } from "@components/PluginSettings/ContributorModal";
|
||||||
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
import { openPluginModal } from "@components/PluginSettings/PluginModal";
|
||||||
import { gitRemote } from "@shared/vencordUserAgent";
|
import { gitRemote } from "@shared/vencordUserAgent";
|
||||||
|
import { DONOR_ROLE_ID, VENCORD_GUILD_ID } from "@utils/constants";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { identity } from "@utils/misc";
|
import { identity, isPluginDev } from "@utils/misc";
|
||||||
import { relaunch, showItemInFolder } from "@utils/native";
|
import { relaunch, showItemInFolder } from "@utils/native";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { Button, Card, Forms, React, Select, Switch } from "@webpack/common";
|
import { Button, Forms, GuildMemberStore, React, Select, Switch, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
|
import BadgeAPI from "../../plugins/_api/badges";
|
||||||
import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "..";
|
import { Flex, FolderIcon, GithubIcon, LogIcon, PaintbrushIcon, RestartIcon } from "..";
|
||||||
import { openNotificationSettingsModal } from "./NotificationSettings";
|
import { openNotificationSettingsModal } from "./NotificationSettings";
|
||||||
import { QuickAction, QuickActionCard } from "./quickActions";
|
import { QuickAction, QuickActionCard } from "./quickActions";
|
||||||
import { SettingsTab, wrapTab } from "./shared";
|
import { SettingsTab, wrapTab } from "./shared";
|
||||||
|
import { SpecialCard } from "./SpecialCard";
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-");
|
const cl = classNameFactory("vc-settings-");
|
||||||
|
|
||||||
const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png";
|
const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png";
|
||||||
const SHIGGY_DONATE_IMAGE = "https://media.discordapp.net/stickers/1039992459209490513.png";
|
const SHIGGY_DONATE_IMAGE = "https://media.discordapp.net/stickers/1039992459209490513.png";
|
||||||
|
|
||||||
|
const VENNIE_DONATOR_IMAGE = "https://cdn.discordapp.com/emojis/1238120638020063377.png";
|
||||||
|
const COZY_CONTRIB_IMAGE = "https://cdn.discordapp.com/emojis/1026533070955872337.png";
|
||||||
|
|
||||||
|
const DONOR_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070116305436712.png?size=2048";
|
||||||
|
const CONTRIB_BACKGROUND_IMAGE = "https://media.discordapp.net/stickers/1311070166481895484.png?size=2048";
|
||||||
|
|
||||||
type KeysOfType<Object, Type> = {
|
type KeysOfType<Object, Type> = {
|
||||||
[K in keyof Object]: Object[K] extends Type ? K : never;
|
[K in keyof Object]: Object[K] extends Type ? K : never;
|
||||||
}[keyof Object];
|
}[keyof Object];
|
||||||
|
|
||||||
|
|
||||||
function VencordSettings() {
|
function VencordSettings() {
|
||||||
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
|
const [settingsDir, , settingsDirPending] = useAwaiter(VencordNative.settings.getSettingsDir, {
|
||||||
fallbackValue: "Loading..."
|
fallbackValue: "Loading..."
|
||||||
|
@ -55,6 +64,8 @@ function VencordSettings() {
|
||||||
const isMac = navigator.platform.toLowerCase().startsWith("mac");
|
const isMac = navigator.platform.toLowerCase().startsWith("mac");
|
||||||
const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac;
|
const needsVibrancySettings = IS_DISCORD_DESKTOP && isMac;
|
||||||
|
|
||||||
|
const user = UserStore.getCurrentUser();
|
||||||
|
|
||||||
const Switches: Array<false | {
|
const Switches: Array<false | {
|
||||||
key: KeysOfType<typeof settings, boolean>;
|
key: KeysOfType<typeof settings, boolean>;
|
||||||
title: string;
|
title: string;
|
||||||
|
@ -99,7 +110,44 @@ function VencordSettings() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsTab title="Vencord Settings">
|
<SettingsTab title="Vencord Settings">
|
||||||
<DonateCard image={donateImage} />
|
{isDonor(user?.id)
|
||||||
|
? (
|
||||||
|
<SpecialCard
|
||||||
|
title="Donations"
|
||||||
|
subtitle="Thank you for donating!"
|
||||||
|
description="All Vencord users can see your badge! You can change it at any time by messaging @vending.machine."
|
||||||
|
cardImage={VENNIE_DONATOR_IMAGE}
|
||||||
|
backgroundImage={DONOR_BACKGROUND_IMAGE}
|
||||||
|
backgroundColor="#ED87A9"
|
||||||
|
>
|
||||||
|
<DonateButtonComponent />
|
||||||
|
</SpecialCard>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<SpecialCard
|
||||||
|
title="Support the Project"
|
||||||
|
description="Please consider supporting the development of Vencord by donating!"
|
||||||
|
cardImage={donateImage}
|
||||||
|
backgroundImage={DONOR_BACKGROUND_IMAGE}
|
||||||
|
backgroundColor="#c3a3ce"
|
||||||
|
>
|
||||||
|
<DonateButtonComponent />
|
||||||
|
</SpecialCard>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
{isPluginDev(user?.id) && (
|
||||||
|
<SpecialCard
|
||||||
|
title="Contributions"
|
||||||
|
subtitle="Thank you for contributing!"
|
||||||
|
description="Since you've contributed to Vencord you now have a cool new badge!"
|
||||||
|
cardImage={COZY_CONTRIB_IMAGE}
|
||||||
|
backgroundImage={CONTRIB_BACKGROUND_IMAGE}
|
||||||
|
backgroundColor="#EDCC87"
|
||||||
|
buttonTitle="See what you've contributed to"
|
||||||
|
buttonOnClick={() => openContributorModal(user)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<Forms.FormSection title="Quick Actions">
|
<Forms.FormSection title="Quick Actions">
|
||||||
<QuickActionCard>
|
<QuickActionCard>
|
||||||
<QuickAction
|
<QuickAction
|
||||||
|
@ -239,31 +287,19 @@ function VencordSettings() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DonateCardProps {
|
function DonateButtonComponent() {
|
||||||
image: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function DonateCard({ image }: DonateCardProps) {
|
|
||||||
return (
|
return (
|
||||||
<Card className={cl("card", "donate")}>
|
<DonateButton
|
||||||
<div>
|
look={Button.Looks.FILLED}
|
||||||
<Forms.FormTitle tag="h5">Support the Project</Forms.FormTitle>
|
color={Button.Colors.WHITE}
|
||||||
<Forms.FormText>Please consider supporting the development of Vencord by donating!</Forms.FormText>
|
style={{ marginTop: "1em" }}
|
||||||
<DonateButton style={{ transform: "translateX(-1em)" }} />
|
|
||||||
</div>
|
|
||||||
<img
|
|
||||||
role="presentation"
|
|
||||||
src={image}
|
|
||||||
alt=""
|
|
||||||
height={128}
|
|
||||||
style={{
|
|
||||||
imageRendering: image === SHIGGY_DONATE_IMAGE ? "pixelated" : void 0,
|
|
||||||
marginLeft: "auto",
|
|
||||||
transform: image === DEFAULT_DONATE_IMAGE ? "rotate(10deg)" : void 0
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Card>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isDonor(userId: string): boolean {
|
||||||
|
const donorBadges = BadgeAPI.getDonorBadges(userId);
|
||||||
|
return GuildMemberStore.getMember(VENCORD_GUILD_ID, userId)?.roles.includes(DONOR_ROLE_ID) || !!donorBadges;
|
||||||
|
}
|
||||||
|
|
||||||
export default wrapTab(VencordSettings, "Vencord Settings");
|
export default wrapTab(VencordSettings, "Vencord Settings");
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
.vc-settings-quickActions-card {
|
.vc-settings-quickActions-card {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, minmax(200px, max-content));
|
grid-template-columns: repeat(3, 1fr);
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
justify-content: center;
|
padding: 0.5em;
|
||||||
padding: 0.5em 0;
|
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (width <=1040px) {
|
||||||
|
.vc-settings-quickActions-card {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.vc-settings-quickActions-pill {
|
.vc-settings-quickActions-pill {
|
||||||
all: unset;
|
all: unset;
|
||||||
background: var(--background-secondary);
|
background: var(--background-secondary);
|
||||||
|
@ -14,12 +19,16 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
padding: 8px 12px;
|
padding: 8px 9px;
|
||||||
border-radius: 9999px;
|
border-radius: 8px;
|
||||||
|
transition: 0.1s ease-out;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-settings-quickActions-pill:hover {
|
.vc-settings-quickActions-pill:hover {
|
||||||
background: var(--background-secondary-alt);
|
background: var(--background-secondary-alt);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: var(--elevation-high);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-settings-quickActions-pill:focus-visible {
|
.vc-settings-quickActions-pill:focus-visible {
|
||||||
|
|
92
src/components/VencordSettings/specialCard.css
Normal file
92
src/components/VencordSettings/specialCard.css
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
.vc-donate-button {
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-donate-button .vc-heart-icon {
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-donate-button:hover .vc-heart-icon {
|
||||||
|
transform: scale(1.1);
|
||||||
|
z-index: 10;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-settings-card {
|
||||||
|
padding: 1em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-special-card-special {
|
||||||
|
padding: 1em 1.5em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-special-card-flex {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-special-card-flex-main {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-special-title {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-special-subtitle {
|
||||||
|
color: black;
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-special-text {
|
||||||
|
color: black;
|
||||||
|
font-size: 1em;
|
||||||
|
margin-top: .75em;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-special-seperator {
|
||||||
|
margin-top: .75em;
|
||||||
|
border-top: 1px solid white;
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-special-hyperlink {
|
||||||
|
margin-top: 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.vc-special-hyperlink-text {
|
||||||
|
color: black;
|
||||||
|
font-size: 1em;
|
||||||
|
font-weight: bold;
|
||||||
|
text-align: center;
|
||||||
|
transition: text-decoration 0.5s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover .vc-special-hyperlink-text {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-special-image-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 1em;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-special-image {
|
||||||
|
width: 65%;
|
||||||
|
}
|
|
@ -5,3 +5,8 @@
|
||||||
.vc-owner-crown-icon {
|
.vc-owner-crown-icon {
|
||||||
color: var(--text-warning);
|
color: var(--text-warning);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.vc-heart-icon {
|
||||||
|
margin-right: 0.5em;
|
||||||
|
translate: 0 2px;
|
||||||
|
}
|
||||||
|
|
|
@ -23,35 +23,61 @@ if (IS_DEV || IS_REPORTER) {
|
||||||
var logger = new Logger("Tracer", "#FFD166");
|
var logger = new Logger("Tracer", "#FFD166");
|
||||||
}
|
}
|
||||||
|
|
||||||
const noop = function () { };
|
export const beginTrace = !(IS_DEV || IS_REPORTER) ? () => { } :
|
||||||
|
|
||||||
export const beginTrace = !(IS_DEV || IS_REPORTER) ? noop :
|
|
||||||
function beginTrace(name: string, ...args: any[]) {
|
function beginTrace(name: string, ...args: any[]) {
|
||||||
if (name in traces)
|
if (name in traces) {
|
||||||
throw new Error(`Trace ${name} already exists!`);
|
throw new Error(`Trace ${name} already exists!`);
|
||||||
|
}
|
||||||
|
|
||||||
traces[name] = [performance.now(), args];
|
traces[name] = [performance.now(), args];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const finishTrace = !(IS_DEV || IS_REPORTER) ? noop : function finishTrace(name: string) {
|
export const finishTrace = !(IS_DEV || IS_REPORTER) ? () => 0 :
|
||||||
|
function finishTrace(name: string) {
|
||||||
const end = performance.now();
|
const end = performance.now();
|
||||||
|
|
||||||
const [start, args] = traces[name];
|
const [start, args] = traces[name];
|
||||||
delete traces[name];
|
delete traces[name];
|
||||||
|
|
||||||
logger.debug(`${name} took ${end - start}ms`, args);
|
const totalTime = end - start;
|
||||||
|
logger.debug(`${name} took ${totalTime}ms`, args);
|
||||||
|
|
||||||
|
return totalTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Func = (...args: any[]) => any;
|
type Func = (...args: any[]) => any;
|
||||||
type TraceNameMapper<F extends Func> = (...args: Parameters<F>) => string;
|
type TraceNameMapper<F extends Func> = (...args: Parameters<F>) => string;
|
||||||
|
|
||||||
const noopTracer =
|
function noopTracerWithResults<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) {
|
||||||
<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) => f;
|
return function (this: unknown, ...args: Parameters<F>): [ReturnType<F>, number] {
|
||||||
|
return [f.apply(this, args), 0];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function noopTracer<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>) {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const traceFunctionWithResults = !(IS_DEV || IS_REPORTER)
|
||||||
|
? noopTracerWithResults
|
||||||
|
: function traceFunctionWithResults<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>): (this: unknown, ...args: Parameters<F>) => [ReturnType<F>, number] {
|
||||||
|
return function (this: unknown, ...args: Parameters<F>) {
|
||||||
|
const traceName = mapper?.(...args) ?? name;
|
||||||
|
|
||||||
|
beginTrace(traceName, ...arguments);
|
||||||
|
try {
|
||||||
|
return [f.apply(this, args), finishTrace(traceName)];
|
||||||
|
} catch (e) {
|
||||||
|
finishTrace(traceName);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const traceFunction = !(IS_DEV || IS_REPORTER)
|
export const traceFunction = !(IS_DEV || IS_REPORTER)
|
||||||
? noopTracer
|
? noopTracer
|
||||||
: function traceFunction<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>): F {
|
: function traceFunction<F extends Func>(name: string, f: F, mapper?: TraceNameMapper<F>): F {
|
||||||
return function (this: any, ...args: Parameters<F>) {
|
return function (this: unknown, ...args: Parameters<F>) {
|
||||||
const traceName = mapper?.(...args) ?? name;
|
const traceName = mapper?.(...args) ?? name;
|
||||||
|
|
||||||
beginTrace(traceName, ...arguments);
|
beginTrace(traceName, ...arguments);
|
||||||
|
|
|
@ -8,23 +8,26 @@ import { Logger } from "@utils/Logger";
|
||||||
import { canonicalizeMatch } from "@utils/patches";
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
import * as Webpack from "@webpack";
|
import * as Webpack from "@webpack";
|
||||||
import { wreq } from "@webpack";
|
import { wreq } from "@webpack";
|
||||||
|
import { AnyModuleFactory, ModuleFactory } from "@webpack/wreq.d";
|
||||||
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
|
|
||||||
|
|
||||||
export async function loadLazyChunks() {
|
export async function loadLazyChunks() {
|
||||||
|
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
LazyChunkLoaderLogger.log("Loading all chunks...");
|
LazyChunkLoaderLogger.log("Loading all chunks...");
|
||||||
|
|
||||||
const validChunks = new Set<number>();
|
const validChunks = new Set<PropertyKey>();
|
||||||
const invalidChunks = new Set<number>();
|
const invalidChunks = new Set<PropertyKey>();
|
||||||
const deferredRequires = new Set<number>();
|
const deferredRequires = new Set<PropertyKey>();
|
||||||
|
|
||||||
let chunksSearchingResolve: (value: void | PromiseLike<void>) => void;
|
const { promise: chunksSearchingDone, resolve: chunksSearchingResolve } = Promise.withResolvers<void>();
|
||||||
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
|
|
||||||
|
|
||||||
// True if resolved, false otherwise
|
// True if resolved, false otherwise
|
||||||
const chunksSearchPromises = [] as Array<() => boolean>;
|
const chunksSearchPromises = [] as Array<() => boolean>;
|
||||||
|
|
||||||
|
/* This regex loads all language packs which makes webpack finds testing extremely slow, so for now, lets use one which doesnt include those
|
||||||
|
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i(?:\.\i)?\.bind\(\i,"?([^)]+?)"?(?:,[^)]+?)?\)\)/g);
|
||||||
|
*/
|
||||||
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
|
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
|
||||||
|
|
||||||
let foundCssDebuggingLoad = false;
|
let foundCssDebuggingLoad = false;
|
||||||
|
@ -34,12 +37,15 @@ export async function loadLazyChunks() {
|
||||||
const hasCssDebuggingLoad = foundCssDebuggingLoad ? false : (foundCssDebuggingLoad = factoryCode.includes(".cssDebuggingEnabled&&"));
|
const hasCssDebuggingLoad = foundCssDebuggingLoad ? false : (foundCssDebuggingLoad = factoryCode.includes(".cssDebuggingEnabled&&"));
|
||||||
|
|
||||||
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
|
const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
|
||||||
const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>();
|
const validChunkGroups = new Set<[chunkIds: PropertyKey[], entryPoint: PropertyKey]>();
|
||||||
|
|
||||||
const shouldForceDefer = false;
|
const shouldForceDefer = false;
|
||||||
|
|
||||||
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
|
await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
|
||||||
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => Number(m[1])) : [];
|
const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => {
|
||||||
|
const numChunkId = Number(m[1]);
|
||||||
|
return Number.isNaN(numChunkId) ? m[1] : numChunkId;
|
||||||
|
}) : [];
|
||||||
|
|
||||||
if (chunkIds.length === 0) {
|
if (chunkIds.length === 0) {
|
||||||
return;
|
return;
|
||||||
|
@ -62,7 +68,7 @@ export async function loadLazyChunks() {
|
||||||
|
|
||||||
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
||||||
.then(r => r.text())
|
.then(r => r.text())
|
||||||
.then(t => t.includes("importScripts("));
|
.then(t => /importScripts\(|self\.postMessage/.test(t));
|
||||||
|
|
||||||
if (isWorkerAsset) {
|
if (isWorkerAsset) {
|
||||||
invalidChunks.add(id);
|
invalidChunks.add(id);
|
||||||
|
@ -74,7 +80,8 @@ export async function loadLazyChunks() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!invalidChunkGroup) {
|
if (!invalidChunkGroup) {
|
||||||
validChunkGroups.add([chunkIds, Number(entryPoint)]);
|
const numEntryPoint = Number(entryPoint);
|
||||||
|
validChunkGroups.add([chunkIds, Number.isNaN(numEntryPoint) ? entryPoint : numEntryPoint]);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
@ -82,7 +89,7 @@ export async function loadLazyChunks() {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
Array.from(validChunkGroups)
|
Array.from(validChunkGroups)
|
||||||
.map(([chunkIds]) =>
|
.map(([chunkIds]) =>
|
||||||
Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
|
Promise.all(chunkIds.map(id => wreq.e(id)))
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -94,7 +101,7 @@ export async function loadLazyChunks() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wreq.m[entryPoint]) wreq(entryPoint as any);
|
if (wreq.m[entryPoint]) wreq(entryPoint);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
@ -122,41 +129,44 @@ export async function loadLazyChunks() {
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Webpack.factoryListeners.add(factory => {
|
function factoryListener(factory: AnyModuleFactory | ModuleFactory) {
|
||||||
let isResolved = false;
|
let isResolved = false;
|
||||||
searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
|
searchAndLoadLazyChunks(String(factory))
|
||||||
|
.then(() => isResolved = true)
|
||||||
|
.catch(() => isResolved = true);
|
||||||
|
|
||||||
chunksSearchPromises.push(() => isResolved);
|
chunksSearchPromises.push(() => isResolved);
|
||||||
});
|
}
|
||||||
|
|
||||||
for (const factoryId in wreq.m) {
|
Webpack.factoryListeners.add(factoryListener);
|
||||||
let isResolved = false;
|
for (const moduleId in wreq.m) {
|
||||||
searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true);
|
factoryListener(wreq.m[moduleId]);
|
||||||
|
|
||||||
chunksSearchPromises.push(() => isResolved);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await chunksSearchingDone;
|
await chunksSearchingDone;
|
||||||
|
Webpack.factoryListeners.delete(factoryListener);
|
||||||
|
|
||||||
// Require deferred entry points
|
// Require deferred entry points
|
||||||
for (const deferredRequire of deferredRequires) {
|
for (const deferredRequire of deferredRequires) {
|
||||||
wreq!(deferredRequire as any);
|
wreq(deferredRequire);
|
||||||
}
|
}
|
||||||
|
|
||||||
// All chunks Discord has mapped to asset files, even if they are not used anymore
|
// All chunks Discord has mapped to asset files, even if they are not used anymore
|
||||||
const allChunks = [] as number[];
|
const allChunks = [] as PropertyKey[];
|
||||||
|
|
||||||
// Matches "id" or id:
|
// Matches "id" or id:
|
||||||
for (const currentMatch of wreq!.u.toString().matchAll(/(?:"([\deE]+?)"(?![,}]))|(?:([\deE]+?):)/g)) {
|
for (const currentMatch of String(wreq.u).matchAll(/(?:"([\deE]+?)"(?![,}]))|(?:([\deE]+?):)/g)) {
|
||||||
const id = currentMatch[1] ?? currentMatch[2];
|
const id = currentMatch[1] ?? currentMatch[2];
|
||||||
if (id == null) continue;
|
if (id == null) continue;
|
||||||
|
|
||||||
allChunks.push(Number(id));
|
const numId = Number(id);
|
||||||
|
allChunks.push(Number.isNaN(numId) ? id : numId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
if (allChunks.length === 0) throw new Error("Failed to get all chunks");
|
||||||
|
|
||||||
// Chunks that are not loaded (not used) by Discord code anymore
|
// Chunks which our regex could not catch to load
|
||||||
|
// It will always contain WebWorker assets, and also currently contains some language packs which are loaded differently
|
||||||
const chunksLeft = allChunks.filter(id => {
|
const chunksLeft = allChunks.filter(id => {
|
||||||
return !(validChunks.has(id) || invalidChunks.has(id));
|
return !(validChunks.has(id) || invalidChunks.has(id));
|
||||||
});
|
});
|
||||||
|
@ -164,14 +174,11 @@ export async function loadLazyChunks() {
|
||||||
await Promise.all(chunksLeft.map(async id => {
|
await Promise.all(chunksLeft.map(async id => {
|
||||||
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
const isWorkerAsset = await fetch(wreq.p + wreq.u(id))
|
||||||
.then(r => r.text())
|
.then(r => r.text())
|
||||||
.then(t => t.includes("importScripts("));
|
.then(t => /importScripts\(|self\.postMessage/.test(t));
|
||||||
|
|
||||||
// Loads and requires a chunk
|
// Loads the chunk. Currently this only happens with the language packs which are loaded differently
|
||||||
if (!isWorkerAsset) {
|
if (!isWorkerAsset) {
|
||||||
await wreq.e(id as any);
|
await wreq.e(id);
|
||||||
// Technically, the id of the chunk does not match the entry point
|
|
||||||
// But, still try it because we have no way to get the actual entry point
|
|
||||||
if (wreq.m[id]) wreq(id as any);
|
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
|
@ -6,28 +6,56 @@
|
||||||
|
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import * as Webpack from "@webpack";
|
import * as Webpack from "@webpack";
|
||||||
import { patches } from "plugins";
|
import { getBuildNumber, patchTimings } from "@webpack/patcher";
|
||||||
|
|
||||||
|
import { addPatch, patches } from "../plugins";
|
||||||
import { loadLazyChunks } from "./loadLazyChunks";
|
import { loadLazyChunks } from "./loadLazyChunks";
|
||||||
|
|
||||||
|
async function runReporter() {
|
||||||
const ReporterLogger = new Logger("Reporter");
|
const ReporterLogger = new Logger("Reporter");
|
||||||
|
|
||||||
async function runReporter() {
|
|
||||||
try {
|
try {
|
||||||
ReporterLogger.log("Starting test...");
|
ReporterLogger.log("Starting test...");
|
||||||
|
|
||||||
let loadLazyChunksResolve: (value: void | PromiseLike<void>) => void;
|
const { promise: loadLazyChunksDone, resolve: loadLazyChunksResolve } = Promise.withResolvers<void>();
|
||||||
const loadLazyChunksDone = new Promise<void>(r => loadLazyChunksResolve = r);
|
|
||||||
|
// The main patch for starting the reporter chunk loading
|
||||||
|
addPatch({
|
||||||
|
find: '"Could not find app-mount"',
|
||||||
|
replacement: {
|
||||||
|
match: /(?<="use strict";)/,
|
||||||
|
replace: "Vencord.Webpack._initReporter();"
|
||||||
|
}
|
||||||
|
}, "Vencord Reporter");
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
Vencord.Webpack._initReporter = function () {
|
||||||
|
// initReporter is called in the patched entry point of Discord
|
||||||
|
// setImmediate to only start searching for lazy chunks after Discord initialized the app
|
||||||
|
setTimeout(() => loadLazyChunks().then(loadLazyChunksResolve), 0);
|
||||||
|
};
|
||||||
|
|
||||||
Webpack.beforeInitListeners.add(() => loadLazyChunks().then((loadLazyChunksResolve)));
|
|
||||||
await loadLazyChunksDone;
|
await loadLazyChunksDone;
|
||||||
|
|
||||||
|
if (IS_REPORTER && IS_WEB && !IS_VESKTOP) {
|
||||||
|
console.log("[REPORTER_META]", {
|
||||||
|
buildNumber: getBuildNumber(),
|
||||||
|
buildHash: window.GLOBAL_ENV.SENTRY_TAGS.buildId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
for (const patch of patches) {
|
for (const patch of patches) {
|
||||||
if (!patch.all) {
|
if (!patch.all) {
|
||||||
new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`);
|
new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const [plugin, moduleId, match, totalTime] of patchTimings) {
|
||||||
|
if (totalTime > 5) {
|
||||||
|
new Logger("WebpackInterceptor").warn(`Patch by ${plugin} took ${Math.round(totalTime * 100) / 100}ms (Module id is ${String(moduleId)}): ${match}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const [searchType, args] of Webpack.lazyWebpackSearchHistory) {
|
for (const [searchType, args] of Webpack.lazyWebpackSearchHistory) {
|
||||||
let method = searchType;
|
let method = searchType;
|
||||||
|
|
||||||
|
@ -50,9 +78,9 @@ async function runReporter() {
|
||||||
result = await Webpack.extractAndLoadChunks(code, matcher);
|
result = await Webpack.extractAndLoadChunks(code, matcher);
|
||||||
if (result === false) result = null;
|
if (result === false) result = null;
|
||||||
} else if (method === "mapMangledModule") {
|
} else if (method === "mapMangledModule") {
|
||||||
const [code, mapper] = args;
|
const [code, mapper, includeBlacklistedExports] = args;
|
||||||
|
|
||||||
result = Webpack.mapMangledModule(code, mapper);
|
result = Webpack.mapMangledModule(code, mapper, includeBlacklistedExports);
|
||||||
if (Object.keys(result).length !== Object.keys(mapper).length) throw new Error("Webpack Find Fail");
|
if (Object.keys(result).length !== Object.keys(mapper).length) throw new Error("Webpack Find Fail");
|
||||||
} else {
|
} else {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -62,14 +90,21 @@ async function runReporter() {
|
||||||
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
if (result == null || (result.$$vencordInternal != null && result.$$vencordInternal() == null)) throw new Error("Webpack Find Fail");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let logMessage = searchType;
|
let logMessage = searchType;
|
||||||
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
|
||||||
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
|
if (args[0].$$vencordProps != null) {
|
||||||
else if (method === "mapMangledModule") {
|
logMessage += `(${args[0].$$vencordProps.map(arg => `"${arg}"`).join(", ")})`;
|
||||||
|
} else {
|
||||||
|
logMessage += `(${args[0].toString().slice(0, 147)}...)`;
|
||||||
|
}
|
||||||
|
} else if (method === "extractAndLoadChunks") {
|
||||||
|
logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
|
||||||
|
} else if (method === "mapMangledModule") {
|
||||||
const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null);
|
const failedMappings = Object.keys(args[1]).filter(key => result?.[key] == null);
|
||||||
|
|
||||||
logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`;
|
logMessage += `("${args[0]}", {\n${failedMappings.map(mapping => `\t${mapping}: ${args[1][mapping].toString().slice(0, 147)}...`).join(",\n")}\n})`;
|
||||||
|
} else {
|
||||||
|
logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
||||||
}
|
}
|
||||||
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
|
|
||||||
|
|
||||||
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
ReporterLogger.log("Webpack Find Fail:", logMessage);
|
||||||
}
|
}
|
||||||
|
@ -81,4 +116,6 @@ async function runReporter() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
runReporter();
|
// Run after the Vencord object has been created.
|
||||||
|
// We need to add extra properties to it, and it is only created after all of Vencord code has ran
|
||||||
|
setTimeout(runReporter, 0);
|
||||||
|
|
7
src/globals.d.ts
vendored
7
src/globals.d.ts
vendored
|
@ -64,13 +64,8 @@ declare global {
|
||||||
export var Vesktop: any;
|
export var Vesktop: any;
|
||||||
export var VesktopNative: any;
|
export var VesktopNative: any;
|
||||||
|
|
||||||
interface Window {
|
interface Window extends Record<PropertyKey, any> {
|
||||||
webpackChunkdiscord_app: {
|
|
||||||
push(chunk: any): any;
|
|
||||||
pop(): any;
|
|
||||||
};
|
|
||||||
_: LoDashStatic;
|
_: LoDashStatic;
|
||||||
[k: string]: any;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { isPluginDev } from "@utils/misc";
|
import { isPluginDev } from "@utils/misc";
|
||||||
import { closeModal, Modals, openModal } from "@utils/modal";
|
import { closeModal, ModalContent, ModalFooter, ModalHeader, ModalRoot, openModal } from "@utils/modal";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { Forms, Toasts, UserStore } from "@webpack/common";
|
import { Forms, Toasts, UserStore } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
@ -102,8 +102,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
userProfileBadge: ContributorBadge,
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
Vencord.Api.Badges.addBadge(ContributorBadge);
|
|
||||||
await loadBadges();
|
await loadBadges();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -143,8 +144,8 @@ export default definePlugin({
|
||||||
closeModal(modalKey);
|
closeModal(modalKey);
|
||||||
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
|
VencordNative.native.openExternal("https://github.com/sponsors/Vendicated");
|
||||||
}}>
|
}}>
|
||||||
<Modals.ModalRoot {...props}>
|
<ModalRoot {...props}>
|
||||||
<Modals.ModalHeader>
|
<ModalHeader>
|
||||||
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
||||||
<Forms.FormTitle
|
<Forms.FormTitle
|
||||||
tag="h2"
|
tag="h2"
|
||||||
|
@ -158,8 +159,8 @@ export default definePlugin({
|
||||||
Vencord Donor
|
Vencord Donor
|
||||||
</Forms.FormTitle>
|
</Forms.FormTitle>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Modals.ModalHeader>
|
</ModalHeader>
|
||||||
<Modals.ModalContent>
|
<ModalContent>
|
||||||
<Flex>
|
<Flex>
|
||||||
<img
|
<img
|
||||||
role="presentation"
|
role="presentation"
|
||||||
|
@ -182,13 +183,13 @@ export default definePlugin({
|
||||||
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
Please consider supporting the development of Vencord by becoming a donor. It would mean a lot!!
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
</div>
|
</div>
|
||||||
</Modals.ModalContent>
|
</ModalContent>
|
||||||
<Modals.ModalFooter>
|
<ModalFooter>
|
||||||
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
<Flex style={{ width: "100%", justifyContent: "center" }}>
|
||||||
<DonateButton />
|
<DonateButton />
|
||||||
</Flex>
|
</Flex>
|
||||||
</Modals.ModalFooter>
|
</ModalFooter>
|
||||||
</Modals.ModalRoot>
|
</ModalRoot>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
|
|
@ -12,11 +12,16 @@ export default definePlugin({
|
||||||
description: "API to add buttons to the chat input",
|
description: "API to add buttons to the chat input",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
|
|
||||||
patches: [{
|
patches: [
|
||||||
|
{
|
||||||
find: '"sticker")',
|
find: '"sticker")',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /return\(!\i\.\i&&(?=\(\i\.isDM.+?(\i)\.push\(.{0,50}"gift")/,
|
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||||
replace: "$&(Vencord.Api.ChatButtons._injectButtons($1,arguments[0]),true)&&"
|
match: /return\((!)?\i\.\i(?:\|\||&&)(?=\(\i\.isDM.+?(\i)\.push)/,
|
||||||
|
replace: (m, not, children) => not
|
||||||
|
? `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),true)&&`
|
||||||
|
: `${m}(Vencord.Api.ChatButtons._injectButtons(${children},arguments[0]),false)||`
|
||||||
}
|
}
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -34,12 +34,22 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".Menu,{",
|
find: "navId:",
|
||||||
all: true,
|
all: true,
|
||||||
replacement: {
|
noWarn: true,
|
||||||
match: /Menu,{(?<=\.jsxs?\)\(\i\.Menu,{)/g,
|
replacement: [
|
||||||
replace: "$&contextMenuApiArguments:typeof arguments!=='undefined'?arguments:[],"
|
{
|
||||||
|
match: /navId:(?=.+?([,}].*?\)))/g,
|
||||||
|
replace: (m, rest) => {
|
||||||
|
// Check if this navId: match is a destructuring statement, ignore it if it is
|
||||||
|
const destructuringMatch = rest.match(/}=.+/);
|
||||||
|
if (destructuringMatch == null) {
|
||||||
|
return `contextMenuAPIArguments:typeof arguments!=='undefined'?arguments:[],${m}`;
|
||||||
|
}
|
||||||
|
return m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,8 +16,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "SCALE_DOWN:",
|
find: "SCALE_DOWN:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/,
|
match: /(?<="IMAGE"===\i\?)\i(?=\?)/,
|
||||||
replace: (_, width, height) => `!((null == ${width} || 0 === ${width}) && (null == ${height} || 0 === ${height}))`
|
replace: "true"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -19,10 +19,15 @@
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
import managedStyle from "./style.css?managed";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MemberListDecoratorsAPI",
|
name: "MemberListDecoratorsAPI",
|
||||||
description: "API to add decorators to member list (both in servers and DMs)",
|
description: "API to add decorators to member list (both in servers and DMs)",
|
||||||
authors: [Devs.TheSun, Devs.Ven],
|
authors: [Devs.TheSun, Devs.Ven],
|
||||||
|
|
||||||
|
managedStyle,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".lostPermission)",
|
find: ".lostPermission)",
|
||||||
|
@ -32,7 +37,7 @@ export default definePlugin({
|
||||||
replace: "$&vencordProps=$1,"
|
replace: "$&vencordProps=$1,"
|
||||||
}, {
|
}, {
|
||||||
match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
|
match: /#{intl::GUILD_OWNER}(?=.+?decorators:(\i)\(\)).+?\1=?\(\)=>.+?children:\[/,
|
||||||
replace: "$&...(typeof vencordProps=='undefined'?[]:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
|
replace: "$&(typeof vencordProps=='undefined'?null:Vencord.Api.MemberListDecorators.__getDecorators(vencordProps)),"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -40,8 +45,8 @@ export default definePlugin({
|
||||||
find: "PrivateChannel.renderAvatar",
|
find: "PrivateChannel.renderAvatar",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/,
|
match: /decorators:(\i\.isSystemDM\(\))\?(.+?):null/,
|
||||||
replace: "decorators:[...Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]), $1?$2:null]"
|
replace: "decorators:[Vencord.Api.MemberListDecorators.__getDecorators(arguments[0]),$1?$2:null]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
});
|
});
|
11
src/plugins/_api/memberListDecorators/style.css
Normal file
11
src/plugins/_api/memberListDecorators/style.css
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
.vc-member-list-decorators-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-member-list-decorators-wrapper:not(:empty) {
|
||||||
|
/* Margin to match default Discord decorators */
|
||||||
|
margin-left: 0.25em;
|
||||||
|
}
|
68
src/plugins/_api/menuItemDemangler.ts
Normal file
68
src/plugins/_api/menuItemDemangler.ts
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a Discord client mod
|
||||||
|
* Copyright (c) 2025 Vendicated and contributors
|
||||||
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import { canonicalizeMatch } from "@utils/patches";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
// duplicate values have multiple branches with different types. Just include all to be safe
|
||||||
|
const nameMap = {
|
||||||
|
radio: "MenuRadioItem",
|
||||||
|
separator: "MenuSeparator",
|
||||||
|
checkbox: "MenuCheckboxItem",
|
||||||
|
groupstart: "MenuGroup",
|
||||||
|
|
||||||
|
control: "MenuControlItem",
|
||||||
|
compositecontrol: "MenuControlItem",
|
||||||
|
|
||||||
|
item: "MenuItem",
|
||||||
|
customitem: "MenuItem",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "MenuItemDemanglerAPI",
|
||||||
|
description: "Demangles Discord's Menu Item module",
|
||||||
|
authors: [Devs.Ven],
|
||||||
|
required: true,
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: '"Menu API',
|
||||||
|
replacement: {
|
||||||
|
match: /function.{0,80}type===(\i\.\i)\).{0,50}navigable:.+?Menu API/s,
|
||||||
|
replace: (m, mod) => {
|
||||||
|
const nameAssignments = [] as string[];
|
||||||
|
|
||||||
|
// if (t.type === m.MenuItem)
|
||||||
|
const typeCheckRe = canonicalizeMatch(/\(\i\.type===(\i\.\i)\)/g);
|
||||||
|
// push({type:"item"})
|
||||||
|
const pushTypeRe = /type:"(\w+)"/g;
|
||||||
|
|
||||||
|
let typeMatch: RegExpExecArray | null;
|
||||||
|
// for each if (t.type === ...)
|
||||||
|
while ((typeMatch = typeCheckRe.exec(m)) !== null) {
|
||||||
|
// extract the current menu item
|
||||||
|
const item = typeMatch[1];
|
||||||
|
// Set the starting index of the second regex to that of the first to start
|
||||||
|
// matching from after the if
|
||||||
|
pushTypeRe.lastIndex = typeCheckRe.lastIndex;
|
||||||
|
// extract the first type: "..."
|
||||||
|
const type = pushTypeRe.exec(m)?.[1];
|
||||||
|
if (type && type in nameMap) {
|
||||||
|
const name = nameMap[type];
|
||||||
|
nameAssignments.push(`Object.defineProperty(${item},"name",{value:"${name}"})`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nameAssignments.length < 6) {
|
||||||
|
console.warn("[MenuItemDemanglerAPI] Expected to at least remap 6 items, only remapped", nameAssignments.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge all our redefines with the actual module
|
||||||
|
return `${nameAssignments.join(";")};${m}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
|
@ -19,17 +19,22 @@
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
import managedStyle from "./style.css?managed";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "MessageDecorationsAPI",
|
name: "MessageDecorationsAPI",
|
||||||
description: "API to add decorations to messages",
|
description: "API to add decorations to messages",
|
||||||
authors: [Devs.TheSun],
|
authors: [Devs.TheSun],
|
||||||
|
|
||||||
|
managedStyle,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: '"Message Username"',
|
find: '"Message Username"',
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/,
|
match: /#{intl::GUILD_COMMUNICATION_DISABLED_BOTTOM_SHEET_TITLE}.+?}\),\i(?=\])/,
|
||||||
replace: "$&,...Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
|
replace: "$&,Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0])"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
});
|
});
|
18
src/plugins/_api/messageDecorations/style.css
Normal file
18
src/plugins/_api/messageDecorations/style.css
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
.vc-message-decorations-wrapper {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-message-decorations-wrapper:not(:empty) {
|
||||||
|
/* Margin to match default Discord decorators */
|
||||||
|
margin-left: 0.25em;
|
||||||
|
|
||||||
|
/* Align vertically */
|
||||||
|
position: relative;
|
||||||
|
vertical-align: top;
|
||||||
|
top: 0.1rem;
|
||||||
|
height: calc(1rem + 4px);
|
||||||
|
max-height: calc(1rem + 4px)
|
||||||
|
}
|
|
@ -37,12 +37,9 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".handleSendMessage,onResize",
|
find: ".handleSendMessage,onResize",
|
||||||
replacement: {
|
replacement: {
|
||||||
// props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply);
|
// https://regex101.com/r/hBlXpl/1
|
||||||
// Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid)
|
match: /let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);(?<=\)\(({.+?})\)\.then.+?)/,
|
||||||
match: /(\{openWarningPopout:.{0,100}type:this.props.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);)(?<=\)\(({.+?})\)\.then.+?)/,
|
replace: (m, parsedMessage, channel, replyOptions, extra) => m +
|
||||||
// props.chatInputType...then((async function(isMessageValid)... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); if(await Vencord.api...) return { shoudClear:true, shouldRefocus:true };
|
|
||||||
replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" +
|
|
||||||
`${rest1}async ${rest2}` +
|
|
||||||
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
|
`if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` +
|
||||||
"return{shouldClear:false,shouldRefocus:true};"
|
"return{shouldClear:false,shouldRefocus:true};"
|
||||||
}
|
}
|
||||||
|
@ -52,7 +49,6 @@ export default definePlugin({
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
|
match: /let\{id:\i}=(\i),{id:\i}=(\i);return \i\.useCallback\((\i)=>\{/,
|
||||||
replace: (m, message, channel, event) =>
|
replace: (m, message, channel, event) =>
|
||||||
// the message param is shadowed by the event param, so need to alias them
|
|
||||||
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg,vcChan,${event});`
|
`const vcMsg=${message},vcChan=${channel};${m}Vencord.Api.MessageEvents._handleClick(vcMsg,vcChan,${event});`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||||
|
import { WebpackRequire } from "@webpack/wreq.d";
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
disableAnalytics: {
|
disableAnalytics: {
|
||||||
|
@ -81,9 +82,9 @@ export default definePlugin({
|
||||||
Object.defineProperty(Function.prototype, "g", {
|
Object.defineProperty(Function.prototype, "g", {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
|
||||||
set(v: any) {
|
set(this: WebpackRequire, globalObj: WebpackRequire["g"]) {
|
||||||
Object.defineProperty(this, "g", {
|
Object.defineProperty(this, "g", {
|
||||||
value: v,
|
value: globalObj,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
writable: true
|
writable: true
|
||||||
|
@ -92,11 +93,11 @@ export default definePlugin({
|
||||||
// Ensure this is most likely the Sentry WebpackInstance.
|
// Ensure this is most likely the Sentry WebpackInstance.
|
||||||
// Function.g is a very generic property and is not uncommon for another WebpackInstance (or even a React component: <g></g>) to include it
|
// Function.g is a very generic property and is not uncommon for another WebpackInstance (or even a React component: <g></g>) to include it
|
||||||
const { stack } = new Error();
|
const { stack } = new Error();
|
||||||
if (!(stack?.includes("discord.com") || stack?.includes("discordapp.com")) || !String(this).includes("exports:{}") || this.c != null) {
|
if (this.c != null || !stack?.includes("http") || !String(this).includes("exports:{}")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const assetPath = stack?.match(/\/assets\/.+?\.js/)?.[0];
|
const assetPath = stack.match(/http.+?(?=:\d+?:\d+?$)/m)?.[0];
|
||||||
if (!assetPath) {
|
if (!assetPath) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -106,7 +107,8 @@ export default definePlugin({
|
||||||
srcRequest.send();
|
srcRequest.send();
|
||||||
|
|
||||||
// Final condition to see if this is the Sentry WebpackInstance
|
// Final condition to see if this is the Sentry WebpackInstance
|
||||||
if (!srcRequest.responseText.includes("window.DiscordSentry=")) {
|
// This is matching window.DiscordSentry=, but without `window` to avoid issues on some proxies
|
||||||
|
if (!srcRequest.responseText.includes(".DiscordSentry=")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,8 @@ export default definePlugin({
|
||||||
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
|
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||||
|
match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?(?:function\(\){return |\(\)=>))\2/,
|
||||||
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
|
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -157,6 +158,9 @@ export default definePlugin({
|
||||||
aboveActivity: getIntlMessage("ACTIVITY_SETTINGS")
|
aboveActivity: getIntlMessage("ACTIVITY_SETTINGS")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!names[settingsLocation] || names[settingsLocation].endsWith("_SETTINGS"))
|
||||||
|
return firstChild === "PREMIUM";
|
||||||
|
|
||||||
return header === names[settingsLocation];
|
return header === names[settingsLocation];
|
||||||
} catch {
|
} catch {
|
||||||
return firstChild === "PREMIUM";
|
return firstChild === "PREMIUM";
|
||||||
|
|
|
@ -16,14 +16,13 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addAccessory } from "@api/MessageAccessories";
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Flex } from "@components/Flex";
|
import { Flex } from "@components/Flex";
|
||||||
import { Link } from "@components/Link";
|
import { Link } from "@components/Link";
|
||||||
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
import { openUpdaterModal } from "@components/VencordSettings/UpdaterTab";
|
||||||
import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
|
import { CONTRIB_ROLE_ID, Devs, DONOR_ROLE_ID, KNOWN_ISSUES_CHANNEL_ID, REGULAR_ROLE_ID, SUPPORT_CHANNEL_ID, VENBOT_USER_ID, VENCORD_GUILD_ID } from "@utils/constants";
|
||||||
import { sendMessage } from "@utils/discord";
|
import { sendMessage } from "@utils/discord";
|
||||||
import { Logger } from "@utils/Logger";
|
import { Logger } from "@utils/Logger";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
|
@ -41,9 +40,6 @@ import plugins, { PluginMeta } from "~plugins";
|
||||||
|
|
||||||
import SettingsPlugin from "./settings";
|
import SettingsPlugin from "./settings";
|
||||||
|
|
||||||
const VENCORD_GUILD_ID = "1015060230222131221";
|
|
||||||
const VENBOT_USER_ID = "1017176847865352332";
|
|
||||||
const KNOWN_ISSUES_CHANNEL_ID = "1222936386626129920";
|
|
||||||
const CodeBlockRe = /```js\n(.+?)```/s;
|
const CodeBlockRe = /```js\n(.+?)```/s;
|
||||||
|
|
||||||
const AllowedChannelIds = [
|
const AllowedChannelIds = [
|
||||||
|
@ -53,9 +49,9 @@ const AllowedChannelIds = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const TrustedRolesIds = [
|
const TrustedRolesIds = [
|
||||||
"1026534353167208489", // contributor
|
CONTRIB_ROLE_ID, // contributor
|
||||||
"1026504932959977532", // regular
|
REGULAR_ROLE_ID, // regular
|
||||||
"1042507929485586532", // donor
|
DONOR_ROLE_ID, // donor
|
||||||
];
|
];
|
||||||
|
|
||||||
const AsyncFunction = async function () { }.constructor;
|
const AsyncFunction = async function () { }.constructor;
|
||||||
|
@ -143,7 +139,7 @@ export default definePlugin({
|
||||||
required: true,
|
required: true,
|
||||||
description: "Helps us provide support to you",
|
description: "Helps us provide support to you",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["UserSettingsAPI", "MessageAccessoriesAPI"],
|
dependencies: ["UserSettingsAPI"],
|
||||||
|
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
@ -236,23 +232,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
|
renderMessageAccessory(props) {
|
||||||
const userId = channel.getRecipientId();
|
|
||||||
if (!isPluginDev(userId)) return null;
|
|
||||||
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
|
||||||
Please do not private message Vencord plugin developers for support!
|
|
||||||
<br />
|
|
||||||
Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")}
|
|
||||||
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}, { noop: true }),
|
|
||||||
|
|
||||||
start() {
|
|
||||||
addAccessory("vencord-debug", props => {
|
|
||||||
const buttons = [] as JSX.Element[];
|
const buttons = [] as JSX.Element[];
|
||||||
|
|
||||||
const shouldAddUpdateButton =
|
const shouldAddUpdateButton =
|
||||||
|
@ -329,6 +309,20 @@ export default definePlugin({
|
||||||
return buttons.length
|
return buttons.length
|
||||||
? <Flex>{buttons}</Flex>
|
? <Flex>{buttons}</Flex>
|
||||||
: null;
|
: null;
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => {
|
||||||
|
const userId = channel.getRecipientId();
|
||||||
|
if (!isPluginDev(userId)) return null;
|
||||||
|
if (RelationshipStore.isFriend(userId) || isPluginDev(UserStore.getCurrentUser()?.id)) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={`vc-plugins-restart-card ${Margins.top8}`}>
|
||||||
|
Please do not private message Vencord plugin developers for support!
|
||||||
|
<br />
|
||||||
|
Instead, use the Vencord support channel: {Parser.parse("https://discord.com/channels/1015060230222131221/1026515880080842772")}
|
||||||
|
{!ChannelStore.getChannel(SUPPORT_CHANNEL_ID) && " (Click the link to join)"}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}, { noop: true }),
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,7 @@ const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cann
|
||||||
const styles = findByPropsLazy("accountProfilePopoutWrapper");
|
const styles = findByPropsLazy("accountProfilePopoutWrapper");
|
||||||
|
|
||||||
let openAlternatePopout = false;
|
let openAlternatePopout = false;
|
||||||
let accountPanelRef: React.MutableRefObject<Record<PropertyKey, any> | null> = { current: null };
|
let accountPanelRef: React.RefObject<Record<PropertyKey, any> | null> = { current: null };
|
||||||
|
|
||||||
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
|
const AccountPanelContextMenu = ErrorBoundary.wrap(() => {
|
||||||
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
|
const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]);
|
||||||
|
|
|
@ -43,8 +43,8 @@ export default definePlugin({
|
||||||
// Status emojis
|
// Status emojis
|
||||||
find: "#{intl::GUILD_OWNER}),children:",
|
find: "#{intl::GUILD_OWNER}),children:",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=\.activityEmoji,.+?animate:)\i/,
|
match: /(\.CUSTOM_STATUS.+?animate:)\i/,
|
||||||
replace: "!0"
|
replace: (_, rest) => `${rest}!0`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,7 +23,7 @@ import definePlugin, { ReporterTestable } from "@utils/types";
|
||||||
import { findByCodeLazy } from "@webpack";
|
import { findByCodeLazy } from "@webpack";
|
||||||
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
|
import { ApplicationAssetUtils, FluxDispatcher, Forms, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
const fetchApplicationsRPC = findByCodeLazy("APPLICATION_RPC(", "Client ID");
|
const fetchApplicationsRPC = findByCodeLazy('"Invalid Origin"', ".application");
|
||||||
|
|
||||||
async function lookupAsset(applicationId: string, key: string): Promise<string> {
|
async function lookupAsset(applicationId: string, key: string): Promise<string> {
|
||||||
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
|
return (await ApplicationAssetUtils.fetchAssetIds(applicationId, [key]))[0];
|
||||||
|
|
|
@ -17,14 +17,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { useStateFromStores } from "@webpack/common";
|
import { Animations, useStateFromStores } from "@webpack/common";
|
||||||
import type { CSSProperties } from "react";
|
import type { CSSProperties } from "react";
|
||||||
|
|
||||||
import { ExpandedGuildFolderStore, settings } from ".";
|
import { ExpandedGuildFolderStore, settings } from ".";
|
||||||
|
|
||||||
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
|
const ChannelRTCStore = findStoreLazy("ChannelRTCStore");
|
||||||
const Animations = findByPropsLazy("a", "animated", "useTransition");
|
|
||||||
const GuildsBar = findComponentByCodeLazy('("guildsnav")');
|
const GuildsBar = findComponentByCodeLazy('("guildsnav")');
|
||||||
|
|
||||||
export default ErrorBoundary.wrap(guildsBarProps => {
|
export default ErrorBoundary.wrap(guildsBarProps => {
|
||||||
|
|
|
@ -173,8 +173,8 @@ export default definePlugin({
|
||||||
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
|
// Disable expanding and collapsing folders transition in the normal GuildsBar sidebar
|
||||||
{
|
{
|
||||||
predicate: () => !settings.store.keepIcons,
|
predicate: () => !settings.store.keepIcons,
|
||||||
match: /(?<=#{intl::SERVER_FOLDER_PLACEHOLDER}.+?useTransition\)\()/,
|
match: /(?=,\{from:\{height)/,
|
||||||
replace: "$self.shouldShowTransition(arguments[0])&&"
|
replace: "&&$self.shouldShowTransition(arguments[0])"
|
||||||
},
|
},
|
||||||
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
|
// If we are rendering the normal GuildsBar sidebar, we avoid rendering guilds from folders that are expanded
|
||||||
{
|
{
|
||||||
|
@ -185,8 +185,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
|
// Decide if we should render the expanded folder background if we are rendering the Better Folders sidebar
|
||||||
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
predicate: () => settings.store.showFolderIcon !== FolderIconDisplay.Always,
|
||||||
match: /(?<=\.isExpanded\),children:\[)/,
|
match: /\.isExpanded\),.{0,30}children:\[/,
|
||||||
replace: "$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&"
|
replace: "$&$self.shouldShowFolderIconAndBackground(!!arguments[0]?.isBetterFolders,arguments[0]?.betterFoldersExpandedIds)&&"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar
|
// Decide if we should render the expanded folder icon if we are rendering the Better Folders sidebar
|
||||||
|
|
|
@ -83,7 +83,7 @@ export default definePlugin({
|
||||||
if (!role) return;
|
if (!role) return;
|
||||||
|
|
||||||
if (role.colorString) {
|
if (role.colorString) {
|
||||||
children.push(
|
children.unshift(
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
id="vc-copy-role-color"
|
id="vc-copy-role-color"
|
||||||
label="Copy Role Color"
|
label="Copy Role Color"
|
||||||
|
@ -93,6 +93,20 @@ export default definePlugin({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
|
||||||
|
children.unshift(
|
||||||
|
<Menu.MenuItem
|
||||||
|
id="vc-edit-role"
|
||||||
|
label="Edit Role"
|
||||||
|
action={async () => {
|
||||||
|
await GuildSettingsActions.open(guild.id, "ROLES");
|
||||||
|
GuildSettingsActions.selectRole(id);
|
||||||
|
}}
|
||||||
|
icon={PencilIcon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (role.icon) {
|
if (role.icon) {
|
||||||
children.push(
|
children.push(
|
||||||
<Menu.MenuItem
|
<Menu.MenuItem
|
||||||
|
@ -110,20 +124,6 @@ export default definePlugin({
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
|
|
||||||
children.push(
|
|
||||||
<Menu.MenuItem
|
|
||||||
id="vc-edit-role"
|
|
||||||
label="Edit Role"
|
|
||||||
action={async () => {
|
|
||||||
await GuildSettingsActions.open(guild.id, "ROLES");
|
|
||||||
GuildSettingsActions.selectRole(id);
|
|
||||||
}}
|
|
||||||
icon={PencilIcon}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -21,7 +21,7 @@ import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
|
import { findByPropsLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { Constants, React, RestAPI, Tooltip } from "@webpack/common";
|
import { Constants, React, RestAPI, Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
import { RenameButton } from "./components/RenameButton";
|
import { RenameButton } from "./components/RenameButton";
|
||||||
|
@ -34,7 +34,7 @@ const UserSettingsModal = findByPropsLazy("saveAccountChanges", "open");
|
||||||
const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer");
|
const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer");
|
||||||
const SessionIconClasses = findByPropsLazy("sessionIcon");
|
const SessionIconClasses = findByPropsLazy("sessionIcon");
|
||||||
|
|
||||||
const BlobMask = findExportedComponentLazy("BlobMask");
|
const BlobMask = findComponentByCodeLazy("!1,lowerBadgeSize:");
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
backgroundCheck: {
|
backgroundCheck: {
|
||||||
|
|
|
@ -101,8 +101,8 @@ export default definePlugin({
|
||||||
find: 'minimal:"contentColumnMinimal"',
|
find: 'minimal:"contentColumnMinimal"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /\(0,\i\.useTransition\)\((\i)/,
|
match: /(?=\(0,\i\.\i\)\((\i),\{from:\{position:"absolute")/,
|
||||||
replace: "(_cb=>_cb(void 0,$1))||$&"
|
replace: "(_cb=>_cb(void 0,$1))||"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
match: /\i\.animated\.div/,
|
match: /\i\.animated\.div/,
|
||||||
|
|
|
@ -26,10 +26,18 @@ export default definePlugin({
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: '"ChannelAttachButton"',
|
find: '"ChannelAttachButton"',
|
||||||
replacement: {
|
replacement: [
|
||||||
|
{
|
||||||
|
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
||||||
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/,
|
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/,
|
||||||
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
|
replace: "$&onClick:$1,onContextMenu:$2.onClick,",
|
||||||
|
noWarn: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,100}\},(\i)\).{0,100}children:\i/,
|
||||||
|
replace: "$&,onClick:$1,onContextMenu:$2.onClick,",
|
||||||
|
},
|
||||||
|
]
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,14 +24,14 @@ let style: HTMLStyleElement;
|
||||||
|
|
||||||
function setCss() {
|
function setCss() {
|
||||||
style.textContent = `
|
style.textContent = `
|
||||||
.vc-nsfw-img [class^=imageWrapper] img,
|
.vc-nsfw-img [class^=imageContainer],
|
||||||
.vc-nsfw-img [class^=wrapperPaused] video {
|
.vc-nsfw-img [class^=wrapperPaused] {
|
||||||
filter: blur(${Settings.plugins.BlurNSFW.blurAmount}px);
|
filter: blur(${Settings.plugins.BlurNSFW.blurAmount}px);
|
||||||
transition: filter 0.2s;
|
transition: filter 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
filter: blur(0);
|
||||||
}
|
}
|
||||||
.vc-nsfw-img [class^=imageWrapper]:hover img,
|
|
||||||
.vc-nsfw-img [class^=wrapperPaused]:hover video {
|
|
||||||
filter: unset;
|
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ export default definePlugin({
|
||||||
options: {
|
options: {
|
||||||
blurAmount: {
|
blurAmount: {
|
||||||
type: OptionType.NUMBER,
|
type: OptionType.NUMBER,
|
||||||
description: "Blur Amount",
|
description: "Blur Amount (in pixels)",
|
||||||
default: 10,
|
default: 10,
|
||||||
onChange: setCss
|
onChange: setCss
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,8 +75,8 @@ export default definePlugin({
|
||||||
patches: [{
|
patches: [{
|
||||||
find: "renderConnectionStatus(){",
|
find: "renderConnectionStatus(){",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=renderConnectionStatus\(\){.+\.channel,children:).+?}\):\i(?=}\))/,
|
match: /(renderConnectionStatus\(\){.+\.channel,children:)(.+?}\):\i)(?=}\))/,
|
||||||
replace: "[$&, $self.renderTimer(this.props.channel.id)]"
|
replace: "$1[$2,$self.renderTimer(this.props.channel.id)]"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
|
|
|
@ -17,11 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addPreEditListener,
|
MessageObject
|
||||||
addPreSendListener,
|
|
||||||
MessageObject,
|
|
||||||
removePreEditListener,
|
|
||||||
removePreSendListener
|
|
||||||
} from "@api/MessageEvents";
|
} from "@api/MessageEvents";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
|
@ -36,7 +32,18 @@ export default definePlugin({
|
||||||
name: "ClearURLs",
|
name: "ClearURLs",
|
||||||
description: "Removes tracking garbage from URLs",
|
description: "Removes tracking garbage from URLs",
|
||||||
authors: [Devs.adryd],
|
authors: [Devs.adryd],
|
||||||
dependencies: ["MessageEventsAPI"],
|
|
||||||
|
start() {
|
||||||
|
this.createRules();
|
||||||
|
},
|
||||||
|
|
||||||
|
onBeforeMessageSend(_, msg) {
|
||||||
|
return this.onSend(msg);
|
||||||
|
},
|
||||||
|
|
||||||
|
onBeforeMessageEdit(_cid, _mid, msg) {
|
||||||
|
return this.onSend(msg);
|
||||||
|
},
|
||||||
|
|
||||||
escapeRegExp(str: string) {
|
escapeRegExp(str: string) {
|
||||||
return (str && reHasRegExpChar.test(str))
|
return (str && reHasRegExpChar.test(str))
|
||||||
|
@ -133,17 +140,4 @@ export default definePlugin({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
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,29 +1,29 @@
|
||||||
.client-theme-settings {
|
.vc-clientTheme-settings {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-theme-container {
|
.vc-clientTheme-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-theme-settings-labels {
|
.vc-clientTheme-labels {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-theme-container > [class^="colorSwatch"] > [class^="swatch"] {
|
.vc-clientTheme-container [class^="swatch"] {
|
||||||
border: thin solid var(--background-modifier-accent) !important;
|
border: thin solid var(--background-modifier-accent) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-theme-warning * {
|
.vc-clientTheme-warning-text {
|
||||||
color: var(--text-danger);
|
color: var(--text-danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
.client-theme-contrast-warning {
|
.vc-clientTheme-contrast-warning {
|
||||||
background-color: var(--background-primary);
|
background-color: var(--background-primary);
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
border-radius: .5rem;
|
border-radius: .5rem;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import "./clientTheme.css";
|
import "./clientTheme.css";
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { classNameFactory } from "@api/Styles";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
|
@ -14,6 +15,8 @@ import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||||
import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
import { findByCodeLazy, findComponentByCodeLazy, findStoreLazy } from "@webpack";
|
||||||
import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common";
|
import { Button, Forms, ThemeStore, useStateFromStores } from "@webpack/common";
|
||||||
|
|
||||||
|
const cl = classNameFactory("vc-clientTheme-");
|
||||||
|
|
||||||
const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
|
const ColorPicker = findComponentByCodeLazy("#{intl::USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR}", ".BACKGROUND_PRIMARY)");
|
||||||
|
|
||||||
const colorPresets = [
|
const colorPresets = [
|
||||||
|
@ -60,9 +63,9 @@ function ThemeSettings() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="client-theme-settings">
|
<div className={cl("settings")}>
|
||||||
<div className="client-theme-container">
|
<div className={cl("container")}>
|
||||||
<div className="client-theme-settings-labels">
|
<div className={cl("settings-labels")}>
|
||||||
<Forms.FormTitle tag="h3">Theme Color</Forms.FormTitle>
|
<Forms.FormTitle tag="h3">Theme Color</Forms.FormTitle>
|
||||||
<Forms.FormText>Add a color to your Discord client theme</Forms.FormText>
|
<Forms.FormText>Add a color to your Discord client theme</Forms.FormText>
|
||||||
</div>
|
</div>
|
||||||
|
@ -76,10 +79,10 @@ function ThemeSettings() {
|
||||||
{(contrastWarning || nitroThemeEnabled) && (<>
|
{(contrastWarning || nitroThemeEnabled) && (<>
|
||||||
<Forms.FormDivider className={classes(Margins.top8, Margins.bottom8)} />
|
<Forms.FormDivider className={classes(Margins.top8, Margins.bottom8)} />
|
||||||
<div className={`client-theme-contrast-warning ${contrastWarning ? (isLightTheme ? "theme-dark" : "theme-light") : ""}`}>
|
<div className={`client-theme-contrast-warning ${contrastWarning ? (isLightTheme ? "theme-dark" : "theme-light") : ""}`}>
|
||||||
<div className="client-theme-warning">
|
<div className={cl("warning")}>
|
||||||
<Forms.FormText>Warning, your theme won't look good:</Forms.FormText>
|
<Forms.FormText className={cl("warning-text")}>Warning, your theme won't look good:</Forms.FormText>
|
||||||
{contrastWarning && <Forms.FormText>Selected color won't contrast well with text</Forms.FormText>}
|
{contrastWarning && <Forms.FormText className={cl("warning-text")}>Selected color won't contrast well with text</Forms.FormText>}
|
||||||
{nitroThemeEnabled && <Forms.FormText>Nitro themes aren't supported</Forms.FormText>}
|
{nitroThemeEnabled && <Forms.FormText className={cl("warning-text")}>Nitro themes aren't supported</Forms.FormText>}
|
||||||
</div>
|
</div>
|
||||||
{(contrastWarning && fixableContrast) && <Button onClick={() => setTheme(oppositeTheme)} color={Button.Colors.RED}>Switch to {oppositeTheme} mode</Button>}
|
{(contrastWarning && fixableContrast) && <Button onClick={() => setTheme(oppositeTheme)} color={Button.Colors.RED}>Switch to {oppositeTheme} mode</Button>}
|
||||||
{(nitroThemeEnabled) && <Button onClick={() => setTheme(theme)} color={Button.Colors.RED}>Disable Nitro Theme</Button>}
|
{(nitroThemeEnabled) && <Button onClick={() => setTheme(theme)} color={Button.Colors.RED}>Disable Nitro Theme</Button>}
|
||||||
|
@ -91,15 +94,12 @@ function ThemeSettings() {
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
color: {
|
color: {
|
||||||
description: "Color your Discord client theme will be based around. Light mode isn't supported",
|
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
default: "313338",
|
default: "313338",
|
||||||
component: () => <ThemeSettings />
|
component: ThemeSettings
|
||||||
},
|
},
|
||||||
resetColor: {
|
resetColor: {
|
||||||
description: "Reset Theme Color",
|
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
default: "313338",
|
|
||||||
component: () => (
|
component: () => (
|
||||||
<Button onClick={() => onPickColor(0x313338)}>
|
<Button onClick={() => onPickColor(0x313338)}>
|
||||||
Reset Theme Color
|
Reset Theme Color
|
||||||
|
@ -126,6 +126,7 @@ export default definePlugin({
|
||||||
stop() {
|
stop() {
|
||||||
document.getElementById("clientThemeVars")?.remove();
|
document.getElementById("clientThemeVars")?.remove();
|
||||||
document.getElementById("clientThemeOffsets")?.remove();
|
document.getElementById("clientThemeOffsets")?.remove();
|
||||||
|
document.getElementById("clientThemeLightModeFixes")?.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -69,8 +69,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "https://github.com/highlightjs/highlight.js/issues/2277",
|
find: "https://github.com/highlightjs/highlight.js/issues/2277",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=&&\()console.log\(`Deprecated.+?`\),/,
|
match: /\(console.log\(`Deprecated.+?`\),/,
|
||||||
replace: ""
|
replace: "("
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -95,10 +95,9 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: 'console.warn("[DEPRECATED] Please use `subscribeWithSelector` middleware");',
|
find: '"AppCrashedFatalReport: getLastCrash not supported."',
|
||||||
all: true,
|
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\);/,
|
match: /console\.log\("AppCrashedFatalReport: getLastCrash not supported\."\);/,
|
||||||
replace: ""
|
replace: ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -63,7 +63,7 @@ function makeShortcuts() {
|
||||||
default:
|
default:
|
||||||
const uniqueMatches = [...new Set(matches)];
|
const uniqueMatches = [...new Set(matches)];
|
||||||
if (uniqueMatches.length > 1)
|
if (uniqueMatches.length > 1)
|
||||||
console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches);
|
console.warn(`Warning: This filter matches ${uniqueMatches.length} exports. Make it more specific!\n`, uniqueMatches);
|
||||||
|
|
||||||
return matches[0];
|
return matches[0];
|
||||||
}
|
}
|
||||||
|
@ -82,6 +82,8 @@ function makeShortcuts() {
|
||||||
wp: Webpack,
|
wp: Webpack,
|
||||||
wpc: { getter: () => Webpack.cache },
|
wpc: { getter: () => Webpack.cache },
|
||||||
wreq: { getter: () => Webpack.wreq },
|
wreq: { getter: () => Webpack.wreq },
|
||||||
|
wpPatcher: { getter: () => Vencord.WebpackPatcher },
|
||||||
|
wpInstances: { getter: () => Vencord.WebpackPatcher.allWebpackInstances },
|
||||||
wpsearch: search,
|
wpsearch: search,
|
||||||
wpex: extract,
|
wpex: extract,
|
||||||
wpexs: (code: string) => extract(findModuleId(code)!),
|
wpexs: (code: string) => extract(findModuleId(code)!),
|
||||||
|
@ -165,11 +167,38 @@ function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) {
|
||||||
const currentVal = val.getter();
|
const currentVal = val.getter();
|
||||||
if (!currentVal || val.preload === false) return currentVal;
|
if (!currentVal || val.preload === false) return currentVal;
|
||||||
|
|
||||||
const value = currentVal[SYM_LAZY_GET]
|
function unwrapProxy(value: any) {
|
||||||
? forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED]
|
if (value[SYM_LAZY_GET]) {
|
||||||
: currentVal;
|
forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED];
|
||||||
|
} else if (value.$$vencordInternal) {
|
||||||
|
return forceLoad ? value.$$vencordInternal() : value;
|
||||||
|
}
|
||||||
|
|
||||||
if (value) define(window.shortcutList, key, { value });
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = unwrapProxy(currentVal);
|
||||||
|
if (typeof value === "object" && value !== null) {
|
||||||
|
const descriptors = Object.getOwnPropertyDescriptors(value);
|
||||||
|
|
||||||
|
for (const propKey in descriptors) {
|
||||||
|
if (value[propKey] == null) continue;
|
||||||
|
|
||||||
|
const descriptor = descriptors[propKey];
|
||||||
|
if (descriptor.writable === true || descriptor.set != null) {
|
||||||
|
const currentValue = value[propKey];
|
||||||
|
const newValue = unwrapProxy(currentValue);
|
||||||
|
if (newValue != null && currentValue !== newValue) {
|
||||||
|
value[propKey] = newValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
define(window.shortcutList, key, { value });
|
||||||
|
define(window, key, { value });
|
||||||
|
}
|
||||||
|
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,11 +33,11 @@ function getEmojiMarkdown(target: Target, copyUnicode: boolean): string {
|
||||||
: `:${emojiName}:`;
|
: `:${emojiName}:`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const extension = target?.firstChild.src.match(
|
const url = new URL(target.firstChild.src);
|
||||||
/https:\/\/cdn\.discordapp\.com\/emojis\/\d+\.(\w+)/
|
const hasParam = url.searchParams.get("animated") === "true";
|
||||||
)?.[1];
|
const isGif = url.pathname.endsWith(".gif");
|
||||||
|
|
||||||
return `<${extension === "gif" ? "a" : ""}:${emojiName.replace(/~\d+$/, "")}:${emojiId}>`;
|
return `<${(hasParam || isGif) ? "a" : ""}:${emojiName.replace(/~\d+$/, "")}:${emojiId}>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
|
@ -55,7 +55,7 @@ export default definePlugin({
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
contextMenus: {
|
contextMenus: {
|
||||||
"expression-picker"(children, { target }: { target: Target }) {
|
"expression-picker"(children, { target }: { target: Target; }) {
|
||||||
if (target.dataset.type !== "emoji") return;
|
if (target.dataset.type !== "emoji") return;
|
||||||
|
|
||||||
children.push(
|
children.push(
|
||||||
|
|
|
@ -42,10 +42,11 @@ export default definePlugin({
|
||||||
// Only one of the two patches will be at effect; Discord often updates to switch between them.
|
// Only one of the two patches will be at effect; Discord often updates to switch between them.
|
||||||
// See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673
|
// See: https://discord.com/channels/1015060230222131221/1032770730703716362/1261398512017477673
|
||||||
{
|
{
|
||||||
find: ".ENTER&&(!",
|
find: ".selectPreviousCommandOption(",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=(\i)\.which===\i\.\i.ENTER&&).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=&&\(\i\.preventDefault)/,
|
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||||
replace: "$self.shouldSubmit($1, $2)"
|
match: /(?<=(\i)\.which(?:!==|===)\i\.\i.ENTER(\|\||&&)).{0,100}(\(0,\i\.\i\)\(\i\)).{0,100}(?=(?:\|\||&&)\(\i\.preventDefault)/,
|
||||||
|
replace: (_, event, condition, codeblock) => `${condition === "||" ? "!" : ""}$self.shouldSubmit(${event},${codeblock})`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -121,6 +121,7 @@ function DearrowButton({ component }: { component: Component<Props>; }) {
|
||||||
height="24px"
|
height="24px"
|
||||||
viewBox="0 0 36 36"
|
viewBox="0 0 36 36"
|
||||||
aria-label="Toggle Dearrow"
|
aria-label="Toggle Dearrow"
|
||||||
|
className="vc-dearrow-icon"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
fill="#1213BD"
|
fill="#1213BD"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
.vc-dearrow-toggle-off svg {
|
.vc-dearrow-toggle-off .vc-dearrow-icon {
|
||||||
filter: grayscale(1);
|
filter: grayscale(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,12 +50,24 @@ export default definePlugin({
|
||||||
find: ".decorationGridItem,",
|
find: ".decorationGridItem,",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?<==)\i=>{let{children.{20,100}decorationGridItem/,
|
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
||||||
replace: "$self.DecorationGridItem=$&"
|
match: /(?<==)\i=>{let{children.{20,200}decorationGridItem/,
|
||||||
|
replace: "$self.DecorationGridItem=$&",
|
||||||
|
noWarn: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// FIXME(Bundler spread transform related): Remove old compatiblity once enough time has passed, if they don't revert
|
||||||
match: /(?<==)\i=>{let{user:\i,avatarDecoration/,
|
match: /(?<==)\i=>{let{user:\i,avatarDecoration/,
|
||||||
replace: "$self.DecorationGridDecoration=$&"
|
replace: "$self.DecorationGridDecoration=$&",
|
||||||
|
noWarn: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /(?<==)\i=>{var{children.{20,200}decorationGridItem/,
|
||||||
|
replace: "$self.DecorationGridItem=$&",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match: /(?<==)\i=>{var{user:\i,avatarDecoration/,
|
||||||
|
replace: "$self.DecorationGridDecoration=$&",
|
||||||
},
|
},
|
||||||
// Remove NEW label from decor avatar decorations
|
// Remove NEW label from decor avatar decorations
|
||||||
{
|
{
|
||||||
|
|
|
@ -17,7 +17,6 @@ import DecorSection from "./ui/components/DecorSection";
|
||||||
export const settings = definePluginSettings({
|
export const settings = definePluginSettings({
|
||||||
changeDecoration: {
|
changeDecoration: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "Change your avatar decoration",
|
|
||||||
component() {
|
component() {
|
||||||
if (!Vencord.Plugins.plugins.Decor.started) return <Forms.FormText>
|
if (!Vencord.Plugins.plugins.Decor.started) return <Forms.FormText>
|
||||||
Enable Decor and restart your client to change your avatar decoration.
|
Enable Decor and restart your client to change your avatar decoration.
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CopyIcon, DeleteIcon } from "@components/Icons";
|
import { CopyIcon, DeleteIcon } from "@components/Icons";
|
||||||
import { Alerts, Clipboard, ContextMenuApi, Menu, UserStore } from "webpack/common";
|
import { Alerts, Clipboard, ContextMenuApi, Menu, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
import { Decoration } from "../../lib/api";
|
import { Decoration } from "../../lib/api";
|
||||||
import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore";
|
import { useCurrentUserDecorationsStore } from "../../lib/stores/CurrentUserDecorationsStore";
|
||||||
|
|
|
@ -160,7 +160,7 @@ function initWs(isManual = false) {
|
||||||
return reply("Expected exactly one 'find' matches, found " + keys.length);
|
return reply("Expected exactly one 'find' matches, found " + keys.length);
|
||||||
|
|
||||||
const mod = candidates[keys[0]];
|
const mod = candidates[keys[0]];
|
||||||
let src = String(mod.original ?? mod).replaceAll("\n", "");
|
let src = String(mod).replaceAll("\n", "");
|
||||||
|
|
||||||
if (src.startsWith("function(")) {
|
if (src.startsWith("function(")) {
|
||||||
src = "0," + src;
|
src = "0," + src;
|
||||||
|
@ -173,7 +173,7 @@ function initWs(isManual = false) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const matcher = canonicalizeMatch(parseNode(match));
|
const matcher = canonicalizeMatch(parseNode(match));
|
||||||
const replacement = canonicalizeReplace(parseNode(replace), "PlaceHolderPluginName");
|
const replacement = canonicalizeReplace(parseNode(replace), 'Vencord.Plugins.plugins["PlaceHolderPluginName"]');
|
||||||
|
|
||||||
const newSource = src.replace(matcher, replacement as string);
|
const newSource = src.replace(matcher, replacement as string);
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
|
import { addMessagePreEditListener, addMessagePreSendListener, removeMessagePreEditListener, removeMessagePreSendListener } from "@api/MessageEvents";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
|
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
|
||||||
|
@ -235,7 +235,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".PREMIUM_LOCKED;",
|
find: ".GUILD_SUBSCRIPTION_UNAVAILABLE;",
|
||||||
group: true,
|
group: true,
|
||||||
predicate: () => settings.store.enableEmojiBypass,
|
predicate: () => settings.store.enableEmojiBypass,
|
||||||
replacement: [
|
replacement: [
|
||||||
|
@ -256,8 +256,11 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Disallow the emoji for premium locked if the intention doesn't allow it
|
// Disallow the emoji for premium locked if the intention doesn't allow it
|
||||||
match: /!\i\.\i\.canUseEmojisEverywhere\(\i\)/,
|
// FIXME(Bundler change related): Remove old compatiblity once enough time has passed
|
||||||
replace: m => `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
|
match: /(!)?(\i\.\i\.canUseEmojisEverywhere\(\i\))/,
|
||||||
|
replace: (m, not) => not
|
||||||
|
? `(${m}&&!${IS_BYPASSEABLE_INTENTION})`
|
||||||
|
: `(${m}||${IS_BYPASSEABLE_INTENTION})`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Allow animated emojis to be used if the intention allows it
|
// Allow animated emojis to be used if the intention allows it
|
||||||
|
@ -853,7 +856,7 @@ export default definePlugin({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.preSend = addPreSendListener(async (channelId, messageObj, extra) => {
|
this.preSend = addMessagePreSendListener(async (channelId, messageObj, extra) => {
|
||||||
const { guildId } = this;
|
const { guildId } = this;
|
||||||
|
|
||||||
let hasBypass = false;
|
let hasBypass = false;
|
||||||
|
@ -941,7 +944,7 @@ export default definePlugin({
|
||||||
return { cancel: false };
|
return { cancel: false };
|
||||||
});
|
});
|
||||||
|
|
||||||
this.preEdit = addPreEditListener(async (channelId, __, messageObj) => {
|
this.preEdit = addMessagePreEditListener(async (channelId, __, messageObj) => {
|
||||||
if (!s.enableEmojiBypass) return;
|
if (!s.enableEmojiBypass) return;
|
||||||
|
|
||||||
let hasBypass = false;
|
let hasBypass = false;
|
||||||
|
@ -973,7 +976,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
removePreSendListener(this.preSend);
|
removeMessagePreSendListener(this.preSend);
|
||||||
removePreEditListener(this.preEdit);
|
removeMessagePreEditListener(this.preEdit);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { app } from "electron";
|
||||||
|
|
||||||
app.on("browser-window-created", (_, win) => {
|
app.on("browser-window-created", (_, win) => {
|
||||||
win.webContents.on("frame-created", (_, { frame }) => {
|
win.webContents.on("frame-created", (_, { frame }) => {
|
||||||
frame.once("dom-ready", () => {
|
frame?.once("dom-ready", () => {
|
||||||
if (frame.url.startsWith("https://open.spotify.com/embed/")) {
|
if (frame.url.startsWith("https://open.spotify.com/embed/")) {
|
||||||
const settings = RendererSettings.store.plugins?.FixSpotifyEmbeds;
|
const settings = RendererSettings.store.plugins?.FixSpotifyEmbeds;
|
||||||
if (!settings?.enabled) return;
|
if (!settings?.enabled) return;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { app } from "electron";
|
||||||
|
|
||||||
app.on("browser-window-created", (_, win) => {
|
app.on("browser-window-created", (_, win) => {
|
||||||
win.webContents.on("frame-created", (_, { frame }) => {
|
win.webContents.on("frame-created", (_, { frame }) => {
|
||||||
frame.once("dom-ready", () => {
|
frame?.once("dom-ready", () => {
|
||||||
if (frame.url.startsWith("https://www.youtube.com/")) {
|
if (frame.url.startsWith("https://www.youtube.com/")) {
|
||||||
const settings = RendererSettings.store.plugins?.FixYoutubeEmbeds;
|
const settings = RendererSettings.store.plugins?.FixYoutubeEmbeds;
|
||||||
if (!settings?.enabled) return;
|
if (!settings?.enabled) return;
|
||||||
|
|
|
@ -31,7 +31,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
name: "create friend invite",
|
name: "create friend invite",
|
||||||
description: "Generates a friend invite link.",
|
description: "Generates a friend invite link.",
|
||||||
inputType: ApplicationCommandInputType.BOT,
|
inputType: ApplicationCommandInputType.BUILT_IN,
|
||||||
|
|
||||||
execute: async (args, ctx) => {
|
execute: async (args, ctx) => {
|
||||||
const invite = await FriendInvites.createFriendInvite();
|
const invite = await FriendInvites.createFriendInvite();
|
||||||
|
@ -48,7 +48,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
name: "view friend invites",
|
name: "view friend invites",
|
||||||
description: "View a list of all generated friend invites.",
|
description: "View a list of all generated friend invites.",
|
||||||
inputType: ApplicationCommandInputType.BOT,
|
inputType: ApplicationCommandInputType.BUILT_IN,
|
||||||
execute: async (_, ctx) => {
|
execute: async (_, ctx) => {
|
||||||
const invites = await FriendInvites.getAllFriendInvites();
|
const invites = await FriendInvites.getAllFriendInvites();
|
||||||
const friendInviteList = invites.map(i =>
|
const friendInviteList = invites.map(i =>
|
||||||
|
@ -67,7 +67,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
name: "revoke friend invites",
|
name: "revoke friend invites",
|
||||||
description: "Revokes all generated friend invites.",
|
description: "Revokes all generated friend invites.",
|
||||||
inputType: ApplicationCommandInputType.BOT,
|
inputType: ApplicationCommandInputType.BUILT_IN,
|
||||||
execute: async (_, ctx) => {
|
execute: async (_, ctx) => {
|
||||||
await FriendInvites.revokeFriendInvites();
|
await FriendInvites.revokeFriendInvites();
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,11 @@ import { Devs } from "@utils/constants";
|
||||||
import { getIntlMessage } from "@utils/discord";
|
import { getIntlMessage } from "@utils/discord";
|
||||||
import { NoopComponent } from "@utils/react";
|
import { NoopComponent } from "@utils/react";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { filters, findByPropsLazy, waitFor } from "@webpack";
|
import { filters, findByCodeLazy, waitFor } from "@webpack";
|
||||||
import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common";
|
import { ChannelStore, ContextMenuApi, UserStore } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { Message } from "discord-types/general";
|
||||||
|
|
||||||
const { useMessageMenu } = findByPropsLazy("useMessageMenu");
|
const useMessageMenu = findByCodeLazy(".MESSAGE,commandTargetId:");
|
||||||
|
|
||||||
interface CopyIdMenuItemProps {
|
interface CopyIdMenuItemProps {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
@ -8,6 +8,7 @@ import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { findComponentByCodeLazy } from "@webpack";
|
import { findComponentByCodeLazy } from "@webpack";
|
||||||
|
import { UserStore, useStateFromStores } from "@webpack/common";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
const UserMentionComponent = findComponentByCodeLazy(".USER_MENTION)");
|
const UserMentionComponent = findComponentByCodeLazy(".USER_MENTION)");
|
||||||
|
@ -34,14 +35,19 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
UserMentionComponent: ErrorBoundary.wrap((props: UserMentionComponentProps) => (
|
UserMentionComponent: ErrorBoundary.wrap((props: UserMentionComponentProps) => {
|
||||||
<UserMentionComponent
|
const user = useStateFromStores([UserStore], () => UserStore.getUser(props.id));
|
||||||
|
if (user == null) {
|
||||||
|
return props.originalComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
return <UserMentionComponent
|
||||||
// This seems to be constant
|
// This seems to be constant
|
||||||
className="mention"
|
className="mention"
|
||||||
userId={props.id}
|
userId={props.id}
|
||||||
channelId={props.channelId}
|
channelId={props.channelId}
|
||||||
/>
|
/>;
|
||||||
), {
|
}, {
|
||||||
fallback: ({ wrappedProps: { originalComponent } }) => originalComponent()
|
fallback: ({ wrappedProps: { originalComponent } }) => originalComponent()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,16 +17,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findComponentByCodeLazy } from "@webpack";
|
import { findComponentByCodeLazy } from "@webpack";
|
||||||
|
|
||||||
import style from "./style.css?managed";
|
import managedStyle from "./style.css?managed";
|
||||||
|
|
||||||
const Button = findComponentByCodeLazy("Button.Sizes.NONE,disabled:");
|
const Button = findComponentByCodeLazy(".NONE,disabled:", ".PANEL_BUTTON");
|
||||||
|
|
||||||
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
const ShowCurrentGame = getUserSettingLazy<boolean>("status", "showCurrentGame")!;
|
||||||
|
|
||||||
|
@ -90,6 +89,8 @@ export default definePlugin({
|
||||||
dependencies: ["UserSettingsAPI"],
|
dependencies: ["UserSettingsAPI"],
|
||||||
settings,
|
settings,
|
||||||
|
|
||||||
|
managedStyle,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
|
find: "#{intl::ACCOUNT_SPEAKING_WHILE_MUTED}",
|
||||||
|
@ -102,11 +103,4 @@ export default definePlugin({
|
||||||
|
|
||||||
GameActivityToggleButton: ErrorBoundary.wrap(GameActivityToggleButton, { noop: true }),
|
GameActivityToggleButton: ErrorBoundary.wrap(GameActivityToggleButton, { noop: true }),
|
||||||
|
|
||||||
start() {
|
|
||||||
enableStyle(style);
|
|
||||||
},
|
|
||||||
|
|
||||||
stop() {
|
|
||||||
disableStyle(style);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,80 +16,92 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "./styles.css";
|
||||||
|
|
||||||
import { get, set } from "@api/DataStore";
|
import { get, set } from "@api/DataStore";
|
||||||
import { addButton, removeButton } from "@api/MessagePopover";
|
import { updateMessage } from "@api/MessageUpdater";
|
||||||
|
import { migratePluginSettings } from "@api/Settings";
|
||||||
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
import { ImageInvisible, ImageVisible } from "@components/Icons";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
import { classes } from "@utils/misc";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { ChannelStore } from "@webpack/common";
|
import { ChannelStore } from "@webpack/common";
|
||||||
|
import { MessageSnapshot } from "@webpack/types";
|
||||||
let style: HTMLStyleElement;
|
|
||||||
|
|
||||||
const KEY = "HideAttachments_HiddenIds";
|
const KEY = "HideAttachments_HiddenIds";
|
||||||
|
|
||||||
let hiddenMessages: Set<string> = new Set();
|
let hiddenMessages = new Set<string>();
|
||||||
const getHiddenMessages = () => get(KEY).then(set => {
|
|
||||||
hiddenMessages = set ?? new Set<string>();
|
async function getHiddenMessages() {
|
||||||
|
hiddenMessages = await get(KEY) ?? new Set();
|
||||||
return hiddenMessages;
|
return hiddenMessages;
|
||||||
});
|
}
|
||||||
|
|
||||||
const saveHiddenMessages = (ids: Set<string>) => set(KEY, ids);
|
const saveHiddenMessages = (ids: Set<string>) => set(KEY, ids);
|
||||||
|
|
||||||
|
migratePluginSettings("HideMedia", "HideAttachments");
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "HideAttachments",
|
name: "HideMedia",
|
||||||
description: "Hide attachments and Embeds for individual messages via hover button",
|
description: "Hide attachments and embeds for individual messages via hover button",
|
||||||
authors: [Devs.Ven],
|
authors: [Devs.Ven],
|
||||||
dependencies: ["MessagePopoverAPI"],
|
dependencies: ["MessageUpdaterAPI"],
|
||||||
|
|
||||||
async start() {
|
patches: [{
|
||||||
style = document.createElement("style");
|
find: "this.renderAttachments(",
|
||||||
style.id = "VencordHideAttachments";
|
replacement: {
|
||||||
document.head.appendChild(style);
|
match: /(?<=\i=)this\.render(?:Attachments|Embeds|StickersAccessories)\((\i)\)/g,
|
||||||
|
replace: "$self.shouldHide($1?.id)?null:$&"
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
|
||||||
await getHiddenMessages();
|
renderMessagePopoverButton(msg) {
|
||||||
await this.buildCss();
|
// @ts-ignore - discord-types lags behind discord.
|
||||||
|
const hasAttachmentsInShapshots = msg.messageSnapshots.some(
|
||||||
|
(snapshot: MessageSnapshot) => snapshot?.message.attachments.length
|
||||||
|
);
|
||||||
|
|
||||||
addButton("HideAttachments", msg => {
|
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length && !hasAttachmentsInShapshots) return null;
|
||||||
if (!msg.attachments.length && !msg.embeds.length && !msg.stickerItems.length) return null;
|
|
||||||
|
|
||||||
const isHidden = hiddenMessages.has(msg.id);
|
const isHidden = hiddenMessages.has(msg.id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: isHidden ? "Show Attachments" : "Hide Attachments",
|
label: isHidden ? "Show Media" : "Hide Media",
|
||||||
icon: isHidden ? ImageVisible : ImageInvisible,
|
icon: isHidden ? ImageVisible : ImageInvisible,
|
||||||
message: msg,
|
message: msg,
|
||||||
channel: ChannelStore.getChannel(msg.channel_id),
|
channel: ChannelStore.getChannel(msg.channel_id),
|
||||||
onClick: () => this.toggleHide(msg.id)
|
onClick: () => this.toggleHide(msg.channel_id, msg.id)
|
||||||
};
|
};
|
||||||
});
|
},
|
||||||
|
|
||||||
|
renderMessageAccessory({ message }) {
|
||||||
|
if (!this.shouldHide(message.id)) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span className={classes("vc-hideAttachments-accessory", !message.content && "vc-hideAttachments-no-content")}>
|
||||||
|
Media Hidden
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
async start() {
|
||||||
|
await getHiddenMessages();
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
style.remove();
|
|
||||||
hiddenMessages.clear();
|
hiddenMessages.clear();
|
||||||
removeButton("HideAttachments");
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async buildCss() {
|
shouldHide(messageId: string) {
|
||||||
const elements = [...hiddenMessages].map(id => `#message-accessories-${id}`).join(",");
|
return hiddenMessages.has(messageId);
|
||||||
style.textContent = `
|
|
||||||
:is(${elements}) :is([class*="embedWrapper"], [class*="clickableSticker"]) {
|
|
||||||
/* important is not necessary, but add it to make sure bad themes won't break it */
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
:is(${elements})::after {
|
|
||||||
content: "Attachments hidden";
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async toggleHide(id: string) {
|
async toggleHide(channelId: string, messageId: string) {
|
||||||
const ids = await getHiddenMessages();
|
const ids = await getHiddenMessages();
|
||||||
if (!ids.delete(id))
|
if (!ids.delete(messageId))
|
||||||
ids.add(id);
|
ids.add(messageId);
|
||||||
|
|
||||||
await saveHiddenMessages(ids);
|
await saveHiddenMessages(ids);
|
||||||
await this.buildCss();
|
updateMessage(channelId, messageId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
10
src/plugins/hideAttachments/styles.css
Normal file
10
src/plugins/hideAttachments/styles.css
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
.vc-hideAttachments-accessory {
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-top: 0.5em;
|
||||||
|
font-style: italic;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vc-hideAttachments-no-content {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: "hasFlag:{writable",
|
find: "hasFlag:{writable",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /if\((\i)<=(?:1<<30|1073741824)\)return/,
|
match: /if\((\i)<=(?:0x40000000|(?:1<<30|1073741824))\)return/,
|
||||||
replace: "if($1===(1<<20))return false;$&",
|
replace: "if($1===(1<<20))return false;$&",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as DataStore from "@api/DataStore";
|
|
||||||
import { definePluginSettings, Settings } from "@api/Settings";
|
import { definePluginSettings, Settings } from "@api/Settings";
|
||||||
import { getUserSettingLazy } from "@api/UserSettings";
|
import { getUserSettingLazy } from "@api/UserSettings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
@ -13,7 +12,7 @@ import { Devs } from "@utils/constants";
|
||||||
import { Margins } from "@utils/margins";
|
import { Margins } from "@utils/margins";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findStoreLazy } from "@webpack";
|
import { findStoreLazy } from "@webpack";
|
||||||
import { Button, Forms, showToast, TextInput, Toasts, Tooltip, useEffect, useState } from "webpack/common";
|
import { Button, Forms, showToast, TextInput, Toasts, Tooltip, useEffect, useState } from "@webpack/common";
|
||||||
|
|
||||||
const enum ActivitiesTypes {
|
const enum ActivitiesTypes {
|
||||||
Game,
|
Game,
|
||||||
|
@ -62,7 +61,7 @@ const ToggleIconOff = (activity: IgnoredActivity, fill: string) => ToggleIcon(ac
|
||||||
|
|
||||||
function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
||||||
const s = settings.use(["ignoredActivities"]);
|
const s = settings.use(["ignoredActivities"]);
|
||||||
const { ignoredActivities = [] } = s;
|
const { ignoredActivities } = s;
|
||||||
|
|
||||||
if (ignoredActivities.some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
|
if (ignoredActivities.some(act => act.id === activity.id)) return ToggleIconOff(activity, "var(--status-danger)");
|
||||||
return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)");
|
return ToggleIconOn(activity, isPlaying ? "var(--green-300)" : "var(--primary-400)");
|
||||||
|
@ -71,11 +70,9 @@ function ToggleActivityComponent(activity: IgnoredActivity, isPlaying = false) {
|
||||||
function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity) {
|
function handleActivityToggle(e: React.MouseEvent<HTMLButtonElement, MouseEvent>, activity: IgnoredActivity) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const ignoredActivityIndex = getIgnoredActivities().findIndex(act => act.id === activity.id);
|
const ignoredActivityIndex = settings.store.ignoredActivities.findIndex(act => act.id === activity.id);
|
||||||
if (ignoredActivityIndex === -1) settings.store.ignoredActivities = getIgnoredActivities().concat(activity);
|
if (ignoredActivityIndex === -1) settings.store.ignoredActivities.push(activity);
|
||||||
else settings.store.ignoredActivities = getIgnoredActivities().filter((_, index) => index !== ignoredActivityIndex);
|
else settings.store.ignoredActivities.splice(ignoredActivityIndex, 1);
|
||||||
|
|
||||||
recalculateActivities();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function recalculateActivities() {
|
function recalculateActivities() {
|
||||||
|
@ -150,8 +147,7 @@ function IdsListComponent(props: { setValue: (value: string) => void; }) {
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
importCustomRPC: {
|
importCustomRPC: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "",
|
component: ImportCustomRPCComponent
|
||||||
component: () => <ImportCustomRPCComponent />
|
|
||||||
},
|
},
|
||||||
listMode: {
|
listMode: {
|
||||||
type: OptionType.SELECT,
|
type: OptionType.SELECT,
|
||||||
|
@ -171,7 +167,6 @@ const settings = definePluginSettings({
|
||||||
},
|
},
|
||||||
idsList: {
|
idsList: {
|
||||||
type: OptionType.COMPONENT,
|
type: OptionType.COMPONENT,
|
||||||
description: "",
|
|
||||||
default: "",
|
default: "",
|
||||||
onChange(newValue: string) {
|
onChange(newValue: string) {
|
||||||
const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean));
|
const ids = new Set(newValue.split(",").map(id => id.trim()).filter(Boolean));
|
||||||
|
@ -209,14 +204,13 @@ const settings = definePluginSettings({
|
||||||
description: "Ignore all competing activities (These are normally special game activities)",
|
description: "Ignore all competing activities (These are normally special game activities)",
|
||||||
default: false,
|
default: false,
|
||||||
onChange: recalculateActivities
|
onChange: recalculateActivities
|
||||||
|
},
|
||||||
|
ignoredActivities: {
|
||||||
|
type: OptionType.CUSTOM,
|
||||||
|
default: [] as IgnoredActivity[],
|
||||||
|
onChange: recalculateActivities
|
||||||
}
|
}
|
||||||
}).withPrivateSettings<{
|
});
|
||||||
ignoredActivities: IgnoredActivity[];
|
|
||||||
}>();
|
|
||||||
|
|
||||||
function getIgnoredActivities() {
|
|
||||||
return settings.store.ignoredActivities ??= [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function isActivityTypeIgnored(type: number, id?: string) {
|
function isActivityTypeIgnored(type: number, id?: string) {
|
||||||
if (id && settings.store.idsList.includes(id)) {
|
if (id && settings.store.idsList.includes(id)) {
|
||||||
|
@ -247,7 +241,7 @@ export default definePlugin({
|
||||||
find: '"LocalActivityStore"',
|
find: '"LocalActivityStore"',
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /HANG_STATUS.+?(?=!\i\(\)\(\i,\i\)&&)(?<=(\i)\.push.+?)/,
|
match: /\.LISTENING.+?(?=!?\i\(\)\(\i,\i\))(?<=(\i)\.push.+?)/,
|
||||||
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
|
replace: (m, activities) => `${m}${activities}=${activities}.filter($self.isActivityNotIgnored);`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -284,29 +278,14 @@ export default definePlugin({
|
||||||
],
|
],
|
||||||
|
|
||||||
async start() {
|
async start() {
|
||||||
// Migrate allowedIds
|
if (settings.store.ignoredActivities.length !== 0) {
|
||||||
if (Settings.plugins.IgnoreActivities.allowedIds) {
|
|
||||||
settings.store.idsList = Settings.plugins.IgnoreActivities.allowedIds;
|
|
||||||
delete Settings.plugins.IgnoreActivities.allowedIds; // Remove allowedIds
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldIgnoredActivitiesData = await DataStore.get<Map<IgnoredActivity["id"], IgnoredActivity>>("IgnoreActivities_ignoredActivities");
|
|
||||||
|
|
||||||
if (oldIgnoredActivitiesData != null) {
|
|
||||||
settings.store.ignoredActivities = Array.from(oldIgnoredActivitiesData.values())
|
|
||||||
.map(activity => ({ ...activity, name: "Unknown Name" }));
|
|
||||||
|
|
||||||
DataStore.del("IgnoreActivities_ignoredActivities");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (getIgnoredActivities().length !== 0) {
|
|
||||||
const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[];
|
const gamesSeen = RunningGameStore.getGamesSeen() as { id?: string; exePath: string; }[];
|
||||||
|
|
||||||
for (const [index, ignoredActivity] of getIgnoredActivities().entries()) {
|
for (const [index, ignoredActivity] of settings.store.ignoredActivities.entries()) {
|
||||||
if (ignoredActivity.type !== ActivitiesTypes.Game) continue;
|
if (ignoredActivity.type !== ActivitiesTypes.Game) continue;
|
||||||
|
|
||||||
if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
|
if (!gamesSeen.some(game => game.id === ignoredActivity.id || game.exePath === ignoredActivity.id)) {
|
||||||
getIgnoredActivities().splice(index, 1);
|
settings.store.ignoredActivities.splice(index, 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -316,11 +295,11 @@ export default definePlugin({
|
||||||
if (isActivityTypeIgnored(props.type, props.application_id)) return false;
|
if (isActivityTypeIgnored(props.type, props.application_id)) return false;
|
||||||
|
|
||||||
if (props.application_id != null) {
|
if (props.application_id != null) {
|
||||||
return !getIgnoredActivities().some(activity => activity.id === props.application_id) || (settings.store.listMode === FilterMode.Whitelist && settings.store.idsList.includes(props.application_id));
|
return !settings.store.ignoredActivities.some(activity => activity.id === props.application_id) || (settings.store.listMode === FilterMode.Whitelist && settings.store.idsList.includes(props.application_id));
|
||||||
} else {
|
} else {
|
||||||
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
const exePath = RunningGameStore.getRunningGames().find(game => game.name === props.name)?.exePath;
|
||||||
if (exePath) {
|
if (exePath) {
|
||||||
return !getIgnoredActivities().some(activity => activity.id === exePath);
|
return !settings.store.ignoredActivities.some(activity => activity.id === exePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -195,6 +195,7 @@ export const Magnifier = ErrorBoundary.wrap<MagnifierProps>(({ instance, size: i
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<img
|
<img
|
||||||
|
className={cl("image")}
|
||||||
ref={imageRef}
|
ref={imageRef}
|
||||||
style={{
|
style={{
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
|
|
|
@ -18,7 +18,6 @@
|
||||||
|
|
||||||
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||||
import { definePluginSettings } from "@api/Settings";
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { disableStyle, enableStyle } from "@api/Styles";
|
|
||||||
import { makeRange } from "@components/PluginSettings/components";
|
import { makeRange } from "@components/PluginSettings/components";
|
||||||
import { debounce } from "@shared/debounce";
|
import { debounce } from "@shared/debounce";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
|
@ -29,7 +28,7 @@ import type { Root } from "react-dom/client";
|
||||||
|
|
||||||
import { Magnifier, MagnifierProps } from "./components/Magnifier";
|
import { Magnifier, MagnifierProps } from "./components/Magnifier";
|
||||||
import { ELEMENT_ID } from "./constants";
|
import { ELEMENT_ID } from "./constants";
|
||||||
import styles from "./styles.css?managed";
|
import managedStyle from "./styles.css?managed";
|
||||||
|
|
||||||
export const settings = definePluginSettings({
|
export const settings = definePluginSettings({
|
||||||
saveZoomValues: {
|
saveZoomValues: {
|
||||||
|
@ -81,7 +80,12 @@ export const settings = definePluginSettings({
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
const imageContextMenuPatch: NavContextMenuPatchCallback = children => {
|
const imageContextMenuPatch: NavContextMenuPatchCallback = (children, props) => {
|
||||||
|
// Discord re-uses the image context menu for links to for the copy and open buttons
|
||||||
|
if ("href" in props) return;
|
||||||
|
// emojis in user statuses
|
||||||
|
if (props.target?.classList?.contains("emoji")) return;
|
||||||
|
|
||||||
const { square, nearestNeighbour } = settings.use(["square", "nearestNeighbour"]);
|
const { square, nearestNeighbour } = settings.use(["square", "nearestNeighbour"]);
|
||||||
|
|
||||||
children.push(
|
children.push(
|
||||||
|
@ -155,6 +159,8 @@ export default definePlugin({
|
||||||
authors: [Devs.Aria],
|
authors: [Devs.Aria],
|
||||||
tags: ["ImageUtilities"],
|
tags: ["ImageUtilities"],
|
||||||
|
|
||||||
|
managedStyle,
|
||||||
|
|
||||||
patches: [
|
patches: [
|
||||||
{
|
{
|
||||||
find: ".contain,SCALE_DOWN:",
|
find: ".contain,SCALE_DOWN:",
|
||||||
|
@ -247,14 +253,12 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
enableStyle(styles);
|
|
||||||
this.element = document.createElement("div");
|
this.element = document.createElement("div");
|
||||||
this.element.classList.add("MagnifierContainer");
|
this.element.classList.add("MagnifierContainer");
|
||||||
document.body.appendChild(this.element);
|
document.body.appendChild(this.element);
|
||||||
},
|
},
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
disableStyle(styles);
|
|
||||||
// so componenetWillUnMount gets called if Magnifier component is still alive
|
// so componenetWillUnMount gets called if Magnifier component is still alive
|
||||||
this.root && this.root.unmount();
|
this.root && this.root.unmount();
|
||||||
this.element?.remove();
|
this.element?.remove();
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-imgzoom-nearest-neighbor>img {
|
.vc-imgzoom-nearest-neighbor > .vc-imgzoom-image {
|
||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
|
|
||||||
/* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */
|
/* https://googlechrome.github.io/samples/image-rendering-pixelated/index.html */
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue