diff --git a/.eslintrc.json b/.eslintrc.json
index 2ee24e8b..918edca0 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -1,7 +1,7 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
- "ignorePatterns": ["dist", "browser"],
+ "ignorePatterns": ["dist", "browser", "packages/vencord-types"],
"plugins": [
"@typescript-eslint",
"simple-header",
diff --git a/browser/VencordNativeStub.ts b/browser/VencordNativeStub.ts
index 77c72369..79f0f2cd 100644
--- a/browser/VencordNativeStub.ts
+++ b/browser/VencordNativeStub.ts
@@ -19,8 +19,8 @@
///
///
-import monacoHtmlLocal from "~fileContent/monacoWin.html";
-import monacoHtmlCdn from "~fileContent/../src/main/monacoWin.html";
+import monacoHtmlLocal from "file://monacoWin.html?minify";
+import monacoHtmlCdn from "file://../src/main/monacoWin.html?minify";
import * as DataStore from "../src/api/DataStore";
import { debounce } from "../src/utils";
import { EXTENSION_BASE_URL } from "../src/utils/web-metadata";
diff --git a/package.json b/package.json
index a99b0ad7..29b1506e 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vencord",
"private": "true",
- "version": "1.8.5",
+ "version": "1.8.6",
"description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": {
@@ -18,8 +18,9 @@
},
"scripts": {
"build": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs",
+ "buildStandalone": "pnpm build --standalone",
"buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs",
- "watch": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/build.mjs --watch",
+ "watch": "pnpm build --watch",
"generatePluginJson": "tsx scripts/generatePluginList.ts",
"generateTypes": "tspc --emitDeclarationOnly --declaration --outDir packages/vencord-types",
"inject": "node scripts/runInstaller.mjs",
@@ -27,7 +28,7 @@
"lint": "eslint . --ext .js,.jsx,.ts,.tsx --ignore-pattern src/userplugins",
"lint-styles": "stylelint \"src/**/*.css\" --ignore-pattern src/userplugins",
"lint:fix": "pnpm lint --fix",
- "test": "pnpm build && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
+ "test": "pnpm buildStandalone && pnpm lint && pnpm lint-styles && pnpm testTsc && pnpm generatePluginJson",
"testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc",
"testTsc": "tsc --noEmit"
},
@@ -61,6 +62,7 @@
"eslint-plugin-simple-import-sort": "^10.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"highlight.js": "10.6.0",
+ "html-minifier-terser": "^7.2.0",
"moment": "^2.29.4",
"puppeteer-core": "^19.11.1",
"standalone-electron-types": "^1.0.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 065d5d4e..b0358579 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -98,6 +98,9 @@ importers:
highlight.js:
specifier: 10.6.0
version: 10.6.0
+ html-minifier-terser:
+ specifier: ^7.2.0
+ version: 7.2.0
moment:
specifier: ^2.29.4
version: 2.29.4
@@ -393,6 +396,27 @@ packages:
'@humanwhocodes/object-schema@1.2.1':
resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
+ '@jridgewell/gen-mapping@0.3.5':
+ resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/resolve-uri@3.1.2':
+ resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/set-array@1.2.1':
+ resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==}
+ engines: {node: '>=6.0.0'}
+
+ '@jridgewell/source-map@0.3.6':
+ resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==}
+
+ '@jridgewell/sourcemap-codec@1.4.15':
+ resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
+
+ '@jridgewell/trace-mapping@0.3.25':
+ resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -688,6 +712,9 @@ packages:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
+ camel-case@4.1.2:
+ resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==}
+
camelcase-keys@6.2.2:
resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==}
engines: {node: '>=8'}
@@ -716,6 +743,10 @@ packages:
resolution: {integrity: sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==}
engines: {node: '>=0.10.0'}
+ clean-css@5.3.3:
+ resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==}
+ engines: {node: '>= 10.0'}
+
cliui@8.0.1:
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
engines: {node: '>=12'}
@@ -740,6 +771,13 @@ packages:
colord@2.9.3:
resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
+ commander@10.0.1:
+ resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==}
+ engines: {node: '>=14'}
+
+ commander@2.20.3:
+ resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
+
component-emitter@1.3.0:
resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==}
@@ -871,12 +909,19 @@ packages:
resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
engines: {node: '>=6.0.0'}
+ dot-case@3.0.4:
+ resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==}
+
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
+ entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
@@ -1402,6 +1447,11 @@ packages:
resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==}
engines: {node: '>=10'}
+ html-minifier-terser@7.2.0:
+ resolution: {integrity: sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==}
+ engines: {node: ^14.13.1 || >=16.0.0}
+ hasBin: true
+
html-tags@3.3.1:
resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==}
engines: {node: '>=8'}
@@ -1673,6 +1723,9 @@ packages:
lodash.truncate@4.4.2:
resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==}
+ lower-case@2.0.2:
+ resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==}
+
lru-cache@6.0.0:
resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
engines: {node: '>=10'}
@@ -1770,6 +1823,9 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ no-case@3.0.4:
+ resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==}
+
node-fetch@2.6.7:
resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
engines: {node: 4.x || >=6.0.0}
@@ -1855,6 +1911,9 @@ packages:
pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
+ param-case@3.0.4:
+ resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==}
+
parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
@@ -1863,6 +1922,9 @@ packages:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
+ pascal-case@3.1.2:
+ resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
+
pascalcase@0.1.1:
resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==}
engines: {node: '>=0.10.0'}
@@ -1985,6 +2047,10 @@ packages:
resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==}
engines: {node: '>= 0.4'}
+ relateurl@0.2.7:
+ resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==}
+ engines: {node: '>= 0.10'}
+
require-directory@2.1.1:
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
engines: {node: '>=0.10.0'}
@@ -2228,6 +2294,11 @@ packages:
resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
engines: {node: '>=6'}
+ terser@5.31.0:
+ resolution: {integrity: sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==}
+ engines: {node: '>=10'}
+ hasBin: true
+
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
@@ -2263,6 +2334,9 @@ packages:
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
+ tslib@2.6.2:
+ resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
+
tsutils@3.21.0:
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
@@ -2599,6 +2673,28 @@ snapshots:
'@humanwhocodes/object-schema@1.2.1': {}
+ '@jridgewell/gen-mapping@0.3.5':
+ dependencies:
+ '@jridgewell/set-array': 1.2.1
+ '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/trace-mapping': 0.3.25
+
+ '@jridgewell/resolve-uri@3.1.2': {}
+
+ '@jridgewell/set-array@1.2.1': {}
+
+ '@jridgewell/source-map@0.3.6':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.5
+ '@jridgewell/trace-mapping': 0.3.25
+
+ '@jridgewell/sourcemap-codec@1.4.15': {}
+
+ '@jridgewell/trace-mapping@0.3.25':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.4.15
+
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -2958,6 +3054,11 @@ snapshots:
callsites@3.1.0: {}
+ camel-case@4.1.2:
+ dependencies:
+ pascal-case: 3.1.2
+ tslib: 2.6.2
+
camelcase-keys@6.2.2:
dependencies:
camelcase: 5.3.1
@@ -2991,6 +3092,10 @@ snapshots:
isobject: 3.0.1
static-extend: 0.1.2
+ clean-css@5.3.3:
+ dependencies:
+ source-map: 0.6.1
+
cliui@8.0.1:
dependencies:
string-width: 4.2.3
@@ -3016,6 +3121,10 @@ snapshots:
colord@2.9.3: {}
+ commander@10.0.1: {}
+
+ commander@2.20.3: {}
+
component-emitter@1.3.0: {}
concat-map@0.0.1: {}
@@ -3139,12 +3248,19 @@ snapshots:
dependencies:
esutils: 2.0.3
+ dot-case@3.0.4:
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.6.2
+
emoji-regex@8.0.0: {}
end-of-stream@1.4.4:
dependencies:
once: 1.4.0
+ entities@4.5.0: {}
+
error-ex@1.3.2:
dependencies:
is-arrayish: 0.2.1
@@ -3733,6 +3849,16 @@ snapshots:
dependencies:
lru-cache: 6.0.0
+ html-minifier-terser@7.2.0:
+ dependencies:
+ camel-case: 4.1.2
+ clean-css: 5.3.3
+ commander: 10.0.1
+ entities: 4.5.0
+ param-case: 3.0.4
+ relateurl: 0.2.7
+ terser: 5.31.0
+
html-tags@3.3.1: {}
https-proxy-agent@5.0.1:
@@ -3974,6 +4100,10 @@ snapshots:
lodash.truncate@4.4.2: {}
+ lower-case@2.0.2:
+ dependencies:
+ tslib: 2.6.2
+
lru-cache@6.0.0:
dependencies:
yallist: 4.0.0
@@ -4071,6 +4201,11 @@ snapshots:
natural-compare@1.4.0: {}
+ no-case@3.0.4:
+ dependencies:
+ lower-case: 2.0.2
+ tslib: 2.6.2
+
node-fetch@2.6.7:
dependencies:
whatwg-url: 5.0.0
@@ -4168,6 +4303,11 @@ snapshots:
pako@1.0.11: {}
+ param-case@3.0.4:
+ dependencies:
+ dot-case: 3.0.4
+ tslib: 2.6.2
+
parent-module@1.0.1:
dependencies:
callsites: 3.1.0
@@ -4179,6 +4319,11 @@ snapshots:
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
+ pascal-case@3.1.2:
+ dependencies:
+ no-case: 3.0.4
+ tslib: 2.6.2
+
pascalcase@0.1.1: {}
path-exists@4.0.0: {}
@@ -4296,6 +4441,8 @@ snapshots:
es-errors: 1.3.0
set-function-name: 2.0.2
+ relateurl@0.2.7: {}
+
require-directory@2.1.1: {}
require-from-string@2.0.2: {}
@@ -4605,6 +4752,13 @@ snapshots:
inherits: 2.0.4
readable-stream: 3.6.2
+ terser@5.31.0:
+ dependencies:
+ '@jridgewell/source-map': 0.3.6
+ acorn: 8.10.0
+ commander: 2.20.3
+ source-map-support: 0.5.21
+
text-table@0.2.0: {}
through@2.3.8: {}
@@ -4646,6 +4800,8 @@ snapshots:
tslib@1.14.1: {}
+ tslib@2.6.2: {}
+
tsutils@3.21.0(typescript@5.4.5):
dependencies:
tslib: 1.14.1
diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs
index e2c90122..3b1473e1 100644
--- a/scripts/build/common.mjs
+++ b/scripts/build/common.mjs
@@ -20,8 +20,10 @@ import "../suppressExperimentalWarnings.js";
import "../checkNodeVersion.js";
import { exec, execSync } from "child_process";
+import esbuild from "esbuild";
import { constants as FsConstants, readFileSync } from "fs";
import { access, readdir, readFile } from "fs/promises";
+import { minify as minifyHtml } from "html-minifier-terser";
import { join, relative } from "path";
import { promisify } from "util";
@@ -161,21 +163,60 @@ export const gitRemotePlugin = {
/**
* @type {import("esbuild").Plugin}
*/
-export const fileIncludePlugin = {
- name: "file-include-plugin",
+export const fileUrlPlugin = {
+ name: "file-uri-plugin",
setup: build => {
- const filter = /^~fileContent\/.+$/;
+ const filter = /^file:\/\/.+$/;
build.onResolve({ filter }, args => ({
- namespace: "include-file",
+ namespace: "file-uri",
path: args.path,
pluginData: {
- path: join(args.resolveDir, args.path.slice("include-file/".length))
+ uri: args.path,
+ path: join(args.resolveDir, args.path.slice("file://".length).split("?")[0])
}
}));
- build.onLoad({ filter, namespace: "include-file" }, async ({ pluginData: { path } }) => {
- const [name, format] = path.split(";");
+ build.onLoad({ filter, namespace: "file-uri" }, async ({ pluginData: { path, uri } }) => {
+ const { searchParams } = new URL(uri);
+ const base64 = searchParams.has("base64");
+ const minify = isStandalone === "true" && searchParams.has("minify");
+ const noTrim = searchParams.get("trim") === "false";
+
+ const encoding = base64 ? "base64" : "utf-8";
+
+ let content;
+ if (!minify) {
+ content = await readFile(path, encoding);
+ if (!noTrim) content = content.trimEnd();
+ } else {
+ if (path.endsWith(".html")) {
+ content = await minifyHtml(await readFile(path, "utf-8"), {
+ collapseWhitespace: true,
+ removeComments: true,
+ minifyCSS: true,
+ minifyJS: true,
+ removeEmptyAttributes: true,
+ removeRedundantAttributes: true,
+ removeScriptTypeAttributes: true,
+ removeStyleLinkTypeAttributes: true,
+ useShortDoctype: true
+ });
+ } else if (/[mc]?[jt]sx?$/.test(path)) {
+ const res = await esbuild.build({
+ entryPoints: [path],
+ write: false,
+ minify: true
+ });
+ content = res.outputFiles[0].text;
+ } else {
+ throw new Error(`Don't know how to minify file type: ${path}`);
+ }
+
+ if (base64)
+ content = Buffer.from(content).toString("base64");
+ }
+
return {
- contents: `export default ${JSON.stringify(await readFile(name, format ?? "utf-8"))}`
+ contents: `export default ${JSON.stringify(content)}`
};
});
}
@@ -217,7 +258,7 @@ export const commonOpts = {
sourcemap: watch ? "inline" : "",
legalComments: "linked",
banner,
- plugins: [fileIncludePlugin, gitHashPlugin, gitRemotePlugin, stylePlugin],
+ plugins: [fileUrlPlugin, gitHashPlugin, gitRemotePlugin, stylePlugin],
external: ["~plugins", "~git-hash", "~git-remote", "/assets/*"],
inject: ["./scripts/build/inject/react.mjs"],
jsxFactory: "VencordCreateElement",
diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 164e409d..8bb87d81 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -243,19 +243,27 @@ page.on("console", async e => {
}
}
- if (isDebug) {
- console.error(e.text());
- } else if (level === "error") {
- const text = await Promise.all(
- e.args().map(async a => {
- try {
+ async function getText() {
+ try {
+ return await Promise.all(
+ e.args().map(async a => {
return await maybeGetError(a) || await a.jsonValue();
- } catch (e) {
- return a.toString();
- }
- })
- ).then(a => a.join(" ").trim());
+ })
+ ).then(a => a.join(" ").trim());
+ } catch {
+ return e.text();
+ }
+ }
+ if (isDebug) {
+ const text = await getText();
+
+ console.error(text);
+ if (text.includes("A fatal error occurred:")) {
+ process.exit(1);
+ }
+ } else if (level === "error") {
+ const text = await getText();
if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) {
console.error("[Unexpected Error]", text);
@@ -322,22 +330,31 @@ async function runtime(token: string) {
const validChunks = new Set();
const invalidChunks = new Set();
+ const deferredRequires = new Set();
let chunksSearchingResolve: (value: void | PromiseLike) => void;
const chunksSearchingDone = new Promise(r => chunksSearchingResolve = r);
// True if resolved, false otherwise
const chunksSearchPromises = [] as Array<() => boolean>;
- const lazyChunkRegex = canonicalizeMatch(/Promise\.all\((\[\i\.\i\(".+?"\).+?\])\).then\(\i\.bind\(\i,"(.+?)"\)\)/g);
- const chunkIdsRegex = canonicalizeMatch(/\("(.+?)"\)/g);
+
+ const LazyChunkRegex = canonicalizeMatch(/(?:Promise\.all\(\[(\i\.\i\("[^)]+?"\)[^\]]+?)\]\)|(\i\.\i\("[^)]+?"\)))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
async function searchAndLoadLazyChunks(factoryCode: string) {
- const lazyChunks = factoryCode.matchAll(lazyChunkRegex);
+ const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
- await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
- const chunkIds = Array.from(rawChunkIds.matchAll(chunkIdsRegex)).map(m => m[1]);
- if (chunkIds.length === 0) return;
+ // Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
+ // the chunk containing the component
+ const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
+
+ await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIdsArray, rawChunkIdsSingle, entryPoint]) => {
+ const rawChunkIds = rawChunkIdsArray ?? rawChunkIdsSingle;
+ const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Vencord.Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
+
+ if (chunkIds.length === 0) {
+ return;
+ }
let invalidChunkGroup = false;
@@ -373,6 +390,11 @@ async function runtime(token: string) {
// Requires the entry points for all valid chunk groups
for (const [, entryPoint] of validChunkGroups) {
try {
+ if (shouldForceDefer) {
+ deferredRequires.add(entryPoint);
+ continue;
+ }
+
if (wreq.m[entryPoint]) wreq(entryPoint as any);
} catch (err) {
console.error(err);
@@ -435,6 +457,11 @@ async function runtime(token: string) {
await chunksSearchingDone;
+ // Require deferred entry points
+ for (const deferredRequire of deferredRequires) {
+ wreq!(deferredRequire as any);
+ }
+
// All chunks Discord has mapped to asset files, even if they are not used anymore
const allChunks = [] as string[];
@@ -514,7 +541,6 @@ async function runtime(token: string) {
setTimeout(() => console.log("[PUPPETEER_TEST_DONE_SIGNAL]"), 1000);
} catch (e) {
console.log("[PUP_DEBUG]", "A fatal error occurred:", e);
- process.exit(1);
}
}
diff --git a/src/main/ipcMain.ts b/src/main/ipcMain.ts
index 9c9741db..62785867 100644
--- a/src/main/ipcMain.ts
+++ b/src/main/ipcMain.ts
@@ -23,12 +23,11 @@ import "./settings";
import { debounce } from "@shared/debounce";
import { IpcEvents } from "@shared/IpcEvents";
import { BrowserWindow, ipcMain, shell, systemPreferences } from "electron";
+import monacoHtml from "file://monacoWin.html?minify&base64";
import { FSWatcher, mkdirSync, watch, writeFileSync } from "fs";
import { open, readdir, readFile } from "fs/promises";
import { join, normalize } from "path";
-import monacoHtml from "~fileContent/monacoWin.html;base64";
-
import { getThemeInfo, stripBOM, UserThemeHeader } from "./themes";
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, THEMES_DIR } from "./utils/constants";
import { makeLinksOpenExternally } from "./utils/externalLinks";
diff --git a/src/main/patcher.ts b/src/main/patcher.ts
index 0d79a96f..a3725ef9 100644
--- a/src/main/patcher.ts
+++ b/src/main/patcher.ts
@@ -73,6 +73,9 @@ if (!IS_VANILLA) {
const original = options.webPreferences.preload;
options.webPreferences.preload = join(__dirname, IS_DISCORD_DESKTOP ? "preload.js" : "vencordDesktopPreload.js");
options.webPreferences.sandbox = false;
+ // work around discord unloading when in background
+ options.webPreferences.backgroundThrottling = false;
+
if (settings.frameless) {
options.frame = false;
} else if (process.platform === "win32" && settings.winNativeTitleBar) {
@@ -136,6 +139,15 @@ if (!IS_VANILLA) {
}
return originalAppend.apply(this, args);
};
+
+ // disable renderer backgrounding to prevent the app from unloading when in the background
+ // https://github.com/electron/electron/issues/2822
+ // https://github.com/GoogleChrome/chrome-launcher/blob/5a27dd574d47a75fec0fb50f7b774ebf8a9791ba/docs/chrome-flags-for-tools.md#task-throttling
+ // Work around discord unloading when in background
+ // Discord also recently started adding these flags but only on windows for some reason dunno why, it happens on Linux too
+ app.commandLine.appendSwitch("disable-renderer-backgrounding");
+ app.commandLine.appendSwitch("disable-background-timer-throttling");
+ app.commandLine.appendSwitch("disable-backgrounding-occluded-windows");
} else {
console.log("[Vencord] Running in vanilla mode. Not loading Vencord");
}
diff --git a/src/modules.d.ts b/src/modules.d.ts
index 48979925..83a512b0 100644
--- a/src/modules.d.ts
+++ b/src/modules.d.ts
@@ -38,7 +38,7 @@ declare module "~git-remote" {
export default remote;
}
-declare module "~fileContent/*" {
+declare module "file://*" {
const content: string;
export default content;
}
diff --git a/src/plugins/_api/notices.ts b/src/plugins/_api/notices.ts
index 90ae6ded..0c6f6e1d 100644
--- a/src/plugins/_api/notices.ts
+++ b/src/plugins/_api/notices.ts
@@ -29,7 +29,7 @@ export default definePlugin({
find: '"NoticeStore"',
replacement: [
{
- match: /\i=null;(?=.{0,80}getPremiumSubscription\(\))/g,
+ match: /(?<=!1;)\i=null;(?=.{0,80}getPremiumSubscription\(\))/g,
replace: "if(Vencord.Api.Notices.currentNotice)return false;$&"
},
{
diff --git a/src/plugins/_core/settings.tsx b/src/plugins/_core/settings.tsx
index b743b006..fd221d27 100644
--- a/src/plugins/_core/settings.tsx
+++ b/src/plugins/_core/settings.tsx
@@ -56,6 +56,26 @@ export default definePlugin({
}
]
},
+ // Discord Stable
+ // FIXME: remove once change merged to stable
+ {
+ find: "Messages.ACTIVITY_SETTINGS",
+ replacement: {
+ get match() {
+ switch (Settings.plugins.Settings.settingsLocation) {
+ case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS/;
+ case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS/;
+ case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS/;
+ case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/;
+ case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/;
+ case "aboveActivity":
+ default:
+ return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS/;
+ }
+ },
+ replace: "...$self.makeSettingsCategories($1),$&"
+ }
+ },
{
find: "Messages.ACTIVITY_SETTINGS",
replacement: {
diff --git a/src/plugins/alwaysAnimate/index.ts b/src/plugins/alwaysAnimate/index.ts
index dbec3b4e..20cb4f97 100644
--- a/src/plugins/alwaysAnimate/index.ts
+++ b/src/plugins/alwaysAnimate/index.ts
@@ -31,10 +31,10 @@ export default definePlugin({
// Some modules match the find but the replacement is returned untouched
noWarn: true,
replacement: {
- match: /canAnimate:.+?(?=([,}].*?\)))/g,
+ match: /canAnimate:.+?([,}].*?\))/g,
replace: (m, rest) => {
const destructuringMatch = rest.match(/}=.+/);
- if (destructuringMatch == null) return "canAnimate:!0";
+ if (destructuringMatch == null) return `canAnimate:!0${rest}`;
return m;
}
}
diff --git a/src/plugins/anonymiseFileNames/index.tsx b/src/plugins/anonymiseFileNames/index.tsx
index b424b7a5..526ccd12 100644
--- a/src/plugins/anonymiseFileNames/index.tsx
+++ b/src/plugins/anonymiseFileNames/index.tsx
@@ -73,13 +73,13 @@ export default definePlugin({
{
find: "instantBatchUpload:function",
replacement: {
- match: /uploadFiles:(.{1,2}),/,
+ match: /uploadFiles:(\i),/,
replace:
"uploadFiles:(...args)=>(args[0].uploads.forEach(f=>f.filename=$self.anonymise(f)),$1(...args)),",
},
},
{
- find: "message.attachments",
+ find: 'addFilesTo:"message.attachments"',
replacement: {
match: /(\i.uploadFiles\((\i),)/,
replace: "$2.forEach(f=>f.filename=$self.anonymise(f)),$1"
diff --git a/src/plugins/betterFolders/index.tsx b/src/plugins/betterFolders/index.tsx
index 795f1990..38e1b841 100644
--- a/src/plugins/betterFolders/index.tsx
+++ b/src/plugins/betterFolders/index.tsx
@@ -112,8 +112,8 @@ export default definePlugin({
replacement: [
// Create the isBetterFolders variable in the GuildsBar component
{
- match: /(?<=let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?)(?=}=\i,)/,
- replace: ",isBetterFolders"
+ match: /let{disableAppDownload:\i=\i\.isPlatformEmbedded,isOverlay:.+?(?=}=\i,)/,
+ replace: "$&,isBetterFolders"
},
// If we are rendering the Better Folders sidebar, we filter out guilds that are not in folders and unexpanded folders
{
diff --git a/src/plugins/betterRoleContext/README.md b/src/plugins/betterRoleContext/README.md
index 3f3086bd..e54e1e31 100644
--- a/src/plugins/betterRoleContext/README.md
+++ b/src/plugins/betterRoleContext/README.md
@@ -1,6 +1,6 @@
# BetterRoleContext
-Adds options to copy role color and edit role when right clicking roles in the user profile
+Adds options to copy role color, edit role and view role icon when right clicking roles in the user profile
-![](https://github.com/Vendicated/Vencord/assets/45497981/d1765e9e-7db2-4a3c-b110-139c59235326)
+![](https://github.com/Vendicated/Vencord/assets/45497981/354220a4-09f3-4c5f-a28e-4b19ca775190)
diff --git a/src/plugins/betterRoleContext/index.tsx b/src/plugins/betterRoleContext/index.tsx
index 3db3494f..ecb1ed40 100644
--- a/src/plugins/betterRoleContext/index.tsx
+++ b/src/plugins/betterRoleContext/index.tsx
@@ -4,9 +4,11 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
+import { definePluginSettings } from "@api/Settings";
+import { ImageIcon } from "@components/Icons";
import { Devs } from "@utils/constants";
-import { getCurrentGuild } from "@utils/discord";
-import definePlugin from "@utils/types";
+import { getCurrentGuild, openImageModal } from "@utils/discord";
+import definePlugin, { OptionType } from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Clipboard, GuildStore, Menu, PermissionStore, TextAndImagesSettingsStores } from "@webpack/common";
@@ -34,10 +36,34 @@ function AppearanceIcon() {
);
}
+const settings = definePluginSettings({
+ roleIconFileFormat: {
+ type: OptionType.SELECT,
+ description: "File format to use when viewing role icons",
+ options: [
+ {
+ label: "png",
+ value: "png",
+ default: true
+ },
+ {
+ label: "webp",
+ value: "webp",
+ },
+ {
+ label: "jpg",
+ value: "jpg"
+ }
+ ]
+ }
+});
+
export default definePlugin({
name: "BetterRoleContext",
- description: "Adds options to copy role color / edit role when right clicking roles in the user profile",
- authors: [Devs.Ven],
+ description: "Adds options to copy role color / edit role / view role icon when right clicking roles in the user profile",
+ authors: [Devs.Ven, Devs.goodbee],
+
+ settings,
start() {
// DeveloperMode needs to be enabled for the context menu to be shown
@@ -63,6 +89,20 @@ export default definePlugin({
);
}
+ if (role.icon) {
+ children.push(
+ {
+ openImageModal(`${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/role-icons/${role.id}/${role.icon}.${settings.store.roleIconFileFormat}`);
+ }}
+ icon={ImageIcon}
+ />
+
+ );
+ }
+
if (PermissionStore.getGuildPermissionProps(guild).canManageRoles) {
children.push(
$2)(),"
+ match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?openContextMenuLazy.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/,
+ replace: "$&(async ()=>$2)(),"
},
predicate: () => settings.store.eagerLoad
},
diff --git a/src/plugins/colorSighted/index.ts b/src/plugins/colorSighted/index.ts
index d741aaae..025cfb94 100644
--- a/src/plugins/colorSighted/index.ts
+++ b/src/plugins/colorSighted/index.ts
@@ -34,9 +34,9 @@ export default definePlugin({
{
find: ".AVATAR_STATUS_MOBILE_16;",
replacement: {
- match: /(?<=fromIsMobile:\i=!0,.+?)status:(\i)/,
+ match: /(fromIsMobile:\i=!0,.+?)status:(\i)/,
// Rename field to force it to always use "online"
- replace: 'status_$:$1="online"'
+ replace: '$1status_$:$2="online"'
}
}
]
diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts
index e25e7cb3..ee86b5fc 100644
--- a/src/plugins/consoleShortcuts/index.ts
+++ b/src/plugins/consoleShortcuts/index.ts
@@ -17,119 +17,199 @@
*/
import { Devs } from "@utils/constants";
+import { getCurrentChannel, getCurrentGuild } from "@utils/discord";
+import { SYM_LAZY_CACHED, SYM_LAZY_GET } from "@utils/lazy";
import { relaunch } from "@utils/native";
import { canonicalizeMatch, canonicalizeReplace, canonicalizeReplacement } from "@utils/patches";
-import definePlugin from "@utils/types";
+import definePlugin, { PluginNative, StartAt } from "@utils/types";
import * as Webpack from "@webpack";
-import { extract, filters, findAll, search } from "@webpack";
-import { React, ReactDOM } from "@webpack/common";
+import { extract, filters, findAll, findModuleId, search } from "@webpack";
+import * as Common from "@webpack/common";
import type { ComponentType } from "react";
-const WEB_ONLY = (f: string) => () => {
+const DESKTOP_ONLY = (f: string) => () => {
throw new Error(`'${f}' is Discord Desktop only.`);
};
+const define: typeof Object.defineProperty =
+ (obj, prop, desc) => {
+ if (Object.hasOwn(desc, "value"))
+ desc.writable = true;
+
+ return Object.defineProperty(obj, prop, {
+ configurable: true,
+ enumerable: true,
+ ...desc
+ });
+ };
+
+function makeShortcuts() {
+ function newFindWrapper(filterFactory: (...props: any[]) => Webpack.FilterFn) {
+ const cache = new Map();
+
+ return function (...filterProps: unknown[]) {
+ const cacheKey = String(filterProps);
+ if (cache.has(cacheKey)) return cache.get(cacheKey);
+
+ const matches = findAll(filterFactory(...filterProps));
+
+ const result = (() => {
+ switch (matches.length) {
+ case 0: return null;
+ case 1: return matches[0];
+ default:
+ const uniqueMatches = [...new Set(matches)];
+ if (uniqueMatches.length > 1)
+ console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches);
+
+ return matches[0];
+ }
+ })();
+ if (result && cacheKey) cache.set(cacheKey, result);
+ return result;
+ };
+ }
+
+ let fakeRenderWin: WeakRef | undefined;
+ const find = newFindWrapper(f => f);
+ const findByProps = newFindWrapper(filters.byProps);
+
+ return {
+ ...Object.fromEntries(Object.keys(Common).map(key => [key, { getter: () => Common[key] }])),
+ wp: Webpack,
+ wpc: { getter: () => Webpack.cache },
+ wreq: { getter: () => Webpack.wreq },
+ wpsearch: search,
+ wpex: extract,
+ wpexs: (code: string) => extract(findModuleId(code)!),
+ find,
+ findAll: findAll,
+ findByProps,
+ findAllByProps: (...props: string[]) => findAll(filters.byProps(...props)),
+ findByCode: newFindWrapper(filters.byCode),
+ findAllByCode: (code: string) => findAll(filters.byCode(code)),
+ findComponentByCode: newFindWrapper(filters.componentByCode),
+ findAllComponentsByCode: (...code: string[]) => findAll(filters.componentByCode(...code)),
+ findExportedComponent: (...props: string[]) => findByProps(...props)[props[0]],
+ findStore: newFindWrapper(filters.byStoreName),
+ PluginsApi: { getter: () => Vencord.Plugins },
+ plugins: { getter: () => Vencord.Plugins.plugins },
+ Settings: { getter: () => Vencord.Settings },
+ Api: { getter: () => Vencord.Api },
+ Util: { getter: () => Vencord.Util },
+ reload: () => location.reload(),
+ restart: IS_WEB ? DESKTOP_ONLY("restart") : relaunch,
+ canonicalizeMatch,
+ canonicalizeReplace,
+ canonicalizeReplacement,
+ fakeRender: (component: ComponentType, props: any) => {
+ const prevWin = fakeRenderWin?.deref();
+ const win = prevWin?.closed === false
+ ? prevWin
+ : window.open("about:blank", "Fake Render", "popup,width=500,height=500")!;
+ fakeRenderWin = new WeakRef(win);
+ win.focus();
+
+ const doc = win.document;
+ doc.body.style.margin = "1em";
+
+ if (!win.prepared) {
+ win.prepared = true;
+
+ [...document.querySelectorAll("style"), ...document.querySelectorAll("link[rel=stylesheet]")].forEach(s => {
+ const n = s.cloneNode(true) as HTMLStyleElement | HTMLLinkElement;
+
+ if (s.parentElement?.tagName === "HEAD")
+ doc.head.append(n);
+ else if (n.id?.startsWith("vencord-") || n.id?.startsWith("vcd-"))
+ doc.documentElement.append(n);
+ else
+ doc.body.append(n);
+ });
+ }
+
+ Common.ReactDOM.render(Common.React.createElement(component, props), doc.body.appendChild(document.createElement("div")));
+ },
+
+ preEnable: (plugin: string) => (Vencord.Settings.plugins[plugin] ??= { enabled: true }).enabled = true,
+
+ channel: { getter: () => getCurrentChannel(), preload: false },
+ channelId: { getter: () => Common.SelectedChannelStore.getChannelId(), preload: false },
+ guild: { getter: () => getCurrentGuild(), preload: false },
+ guildId: { getter: () => Common.SelectedGuildStore.getGuildId(), preload: false },
+ me: { getter: () => Common.UserStore.getCurrentUser(), preload: false },
+ meId: { getter: () => Common.UserStore.getCurrentUser().id, preload: false },
+ messages: { getter: () => Common.MessageStore.getMessages(Common.SelectedChannelStore.getChannelId()), preload: false }
+ };
+}
+
+function loadAndCacheShortcut(key: string, val: any, forceLoad: boolean) {
+ const currentVal = val.getter();
+ if (!currentVal || val.preload === false) return currentVal;
+
+ const value = currentVal[SYM_LAZY_GET]
+ ? forceLoad ? currentVal[SYM_LAZY_GET]() : currentVal[SYM_LAZY_CACHED]
+ : currentVal;
+
+ if (value) define(window.shortcutList, key, { value });
+
+ return value;
+}
+
export default definePlugin({
name: "ConsoleShortcuts",
description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.",
authors: [Devs.Ven],
- getShortcuts() {
- function newFindWrapper(filterFactory: (...props: any[]) => Webpack.FilterFn) {
- const cache = new Map();
+ startAt: StartAt.Init,
+ start() {
+ const shortcuts = makeShortcuts();
+ window.shortcutList = {};
- return function (...filterProps: unknown[]) {
- const cacheKey = String(filterProps);
- if (cache.has(cacheKey)) return cache.get(cacheKey);
+ for (const [key, val] of Object.entries(shortcuts)) {
+ if ("getter" in val) {
+ define(window.shortcutList, key, {
+ get: () => loadAndCacheShortcut(key, val, true)
+ });
- const matches = findAll(filterFactory(...filterProps));
-
- const result = (() => {
- switch (matches.length) {
- case 0: return null;
- case 1: return matches[0];
- default:
- const uniqueMatches = [...new Set(matches)];
- if (uniqueMatches.length > 1)
- console.warn(`Warning: This filter matches ${matches.length} modules. Make it more specific!\n`, uniqueMatches);
-
- return matches[0];
- }
- })();
- if (result && cacheKey) cache.set(cacheKey, result);
- return result;
- };
+ define(window, key, {
+ get: () => window.shortcutList[key]
+ });
+ } else {
+ window.shortcutList[key] = val;
+ window[key] = val;
+ }
}
- let fakeRenderWin: WeakRef | undefined;
- const find = newFindWrapper(f => f);
- const findByProps = newFindWrapper(filters.byProps);
- return {
- ...Vencord.Webpack.Common,
- wp: Vencord.Webpack,
- wpc: Webpack.wreq.c,
- wreq: Webpack.wreq,
- wpsearch: search,
- wpex: extract,
- wpexs: (code: string) => extract(Webpack.findModuleId(code)!),
- find,
- findAll,
- findByProps,
- findAllByProps: (...props: string[]) => findAll(filters.byProps(...props)),
- findByCode: newFindWrapper(filters.byCode),
- findAllByCode: (code: string) => findAll(filters.byCode(code)),
- findComponentByCode: newFindWrapper(filters.componentByCode),
- findAllComponentsByCode: (...code: string[]) => findAll(filters.componentByCode(...code)),
- findExportedComponent: (...props: string[]) => findByProps(...props)[props[0]],
- findStore: newFindWrapper(filters.byStoreName),
- PluginsApi: Vencord.Plugins,
- plugins: Vencord.Plugins.plugins,
- Settings: Vencord.Settings,
- Api: Vencord.Api,
- reload: () => location.reload(),
- restart: IS_WEB ? WEB_ONLY("restart") : relaunch,
- canonicalizeMatch,
- canonicalizeReplace,
- canonicalizeReplacement,
- fakeRender: (component: ComponentType, props: any) => {
- const prevWin = fakeRenderWin?.deref();
- const win = prevWin?.closed === false ? prevWin : window.open("about:blank", "Fake Render", "popup,width=500,height=500")!;
- fakeRenderWin = new WeakRef(win);
- win.focus();
+ // unproxy loaded modules
+ Webpack.onceReady.then(() => {
+ setTimeout(() => this.eagerLoad(false), 1000);
- const doc = win.document;
- doc.body.style.margin = "1em";
-
- if (!win.prepared) {
- win.prepared = true;
-
- [...document.querySelectorAll("style"), ...document.querySelectorAll("link[rel=stylesheet]")].forEach(s => {
- const n = s.cloneNode(true) as HTMLStyleElement | HTMLLinkElement;
-
- if (s.parentElement?.tagName === "HEAD")
- doc.head.append(n);
- else if (n.id?.startsWith("vencord-") || n.id?.startsWith("vcd-"))
- doc.documentElement.append(n);
- else
- doc.body.append(n);
- });
- }
-
- ReactDOM.render(React.createElement(component, props), doc.body.appendChild(document.createElement("div")));
+ if (!IS_WEB) {
+ const Native = VencordNative.pluginHelpers.ConsoleShortcuts as PluginNative;
+ Native.initDevtoolsOpenEagerLoad();
}
- };
+ });
},
- start() {
- const shortcuts = this.getShortcuts();
- window.shortcutList = shortcuts;
- for (const [key, val] of Object.entries(shortcuts))
- window[key] = val;
+ async eagerLoad(forceLoad: boolean) {
+ await Webpack.onceReady;
+
+ const shortcuts = makeShortcuts();
+
+ for (const [key, val] of Object.entries(shortcuts)) {
+ if (!Object.hasOwn(val, "getter") || (val as any).preload === false) continue;
+
+ try {
+ loadAndCacheShortcut(key, val, forceLoad);
+ } catch { } // swallow not found errors in DEV
+ }
},
stop() {
delete window.shortcutList;
- for (const key in this.getShortcuts())
+ for (const key in makeShortcuts()) {
delete window[key];
+ }
}
});
diff --git a/src/plugins/consoleShortcuts/native.ts b/src/plugins/consoleShortcuts/native.ts
new file mode 100644
index 00000000..763b239a
--- /dev/null
+++ b/src/plugins/consoleShortcuts/native.ts
@@ -0,0 +1,16 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { IpcMainInvokeEvent } from "electron";
+
+export function initDevtoolsOpenEagerLoad(e: IpcMainInvokeEvent) {
+ const handleDevtoolsOpened = () => e.sender.executeJavaScript("Vencord.Plugins.plugins.ConsoleShortcuts.eagerLoad(true)");
+
+ if (e.sender.isDevToolsOpened())
+ handleDevtoolsOpened();
+ else
+ e.sender.once("devtools-opened", () => handleDevtoolsOpened());
+}
diff --git a/src/plugins/ctrlEnterSend/index.ts b/src/plugins/ctrlEnterSend/index.ts
index 4b9dd8e0..817da053 100644
--- a/src/plugins/ctrlEnterSend/index.ts
+++ b/src/plugins/ctrlEnterSend/index.ts
@@ -18,7 +18,7 @@ export default definePlugin({
type: OptionType.SELECT,
options: [
{
- label: "Ctrl+Enter (Enter or Shift+Enter for new line)",
+ label: "Ctrl+Enter (Enter or Shift+Enter for new line) (cmd+enter on macOS)",
value: "ctrl+enter"
},
{
@@ -54,7 +54,7 @@ export default definePlugin({
result = event.shiftKey;
break;
case "ctrl+enter":
- result = event.ctrlKey;
+ result = navigator.platform.includes("Mac") ? event.metaKey : event.ctrlKey;
break;
case "enter":
result = !event.shiftKey && !event.ctrlKey;
diff --git a/src/plugins/dearrow/index.tsx b/src/plugins/dearrow/index.tsx
index 888e2bb4..89199da8 100644
--- a/src/plugins/dearrow/index.tsx
+++ b/src/plugins/dearrow/index.tsx
@@ -182,8 +182,8 @@ export default definePlugin({
// add dearrow button
{
- match: /children:\[(?=null!=\i\?\i\.renderSuppressButton)/,
- replace: "children:[$self.renderButton(this),",
+ match: /children:\[(?=null!=\i\?(\i)\.renderSuppressButton)/,
+ replace: "children:[$self.renderButton($1),",
predicate: () => !settings.store.hideButton
}
]
diff --git a/src/plugins/dontRoundMyTimestamps/index.ts b/src/plugins/dontRoundMyTimestamps/index.ts
new file mode 100644
index 00000000..4c432c73
--- /dev/null
+++ b/src/plugins/dontRoundMyTimestamps/index.ts
@@ -0,0 +1,35 @@
+/*
+ * 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 .
+*/
+
+import { Devs } from "@utils/constants";
+import definePlugin from "@utils/types";
+import { moment } from "@webpack/common";
+
+export default definePlugin({
+ name: "DontRoundMyTimestamps",
+ authors: [Devs.Lexi],
+ description: "Always rounds relative timestamps down, so 7.6y becomes 7y instead of 8y",
+
+ start() {
+ moment.relativeTimeRounding(Math.floor);
+ },
+
+ stop() {
+ moment.relativeTimeRounding(Math.round);
+ }
+});
diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx
index 9c8af1e7..427f36ce 100644
--- a/src/plugins/fakeNitro/index.tsx
+++ b/src/plugins/fakeNitro/index.tsx
@@ -344,8 +344,8 @@ export default definePlugin({
{
// Patch the stickers array to add fake nitro stickers
predicate: () => settings.store.transformStickers,
- match: /(?<=renderStickersAccessories\((\i)\){let (\i)=\(0,\i\.\i\)\(\i\).+?;)/,
- replace: (_, message, stickers) => `${stickers}=$self.patchFakeNitroStickers(${stickers},${message});`
+ match: /renderStickersAccessories\((\i)\){let (\i)=\(0,\i\.\i\)\(\i\).+?;/,
+ replace: (m, message, stickers) => `${m}${stickers}=$self.patchFakeNitroStickers(${stickers},${message});`
},
{
// Filter attachments to remove fake nitro stickers or emojis
@@ -813,7 +813,7 @@ export default definePlugin({
},
canUseEmote(e: Emoji, channelId: string) {
- if (e.type === "UNICODE") return true;
+ if (e.type === 0) return true;
if (e.available === false) return false;
const isUnusableRoleSubEmoji = RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji;
diff --git a/src/plugins/fakeProfileThemes/index.css b/src/plugins/fakeProfileThemes/index.css
new file mode 100644
index 00000000..1c9bebf2
--- /dev/null
+++ b/src/plugins/fakeProfileThemes/index.css
@@ -0,0 +1,3 @@
+.vc-fpt-preview * {
+ pointer-events: none;
+}
diff --git a/src/plugins/fakeProfileThemes/index.tsx b/src/plugins/fakeProfileThemes/index.tsx
index a1b629d1..7a6bda9a 100644
--- a/src/plugins/fakeProfileThemes/index.tsx
+++ b/src/plugins/fakeProfileThemes/index.tsx
@@ -17,13 +17,17 @@
*/
// This plugin is a port from Alyxia's Vendetta plugin
+import "./index.css";
+
import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { Margins } from "@utils/margins";
-import { copyWithToast } from "@utils/misc";
+import { classes, copyWithToast } from "@utils/misc";
+import { useAwaiter } from "@utils/react";
import definePlugin, { OptionType } from "@utils/types";
-import { Button, Forms } from "@webpack/common";
+import { extractAndLoadChunksLazy, findComponentByCodeLazy } from "@webpack";
+import { Button, Flex, Forms, React, Text, UserProfileStore, UserStore, useState } from "@webpack/common";
import { User } from "discord-types/general";
import virtualMerge from "virtual-merge";
@@ -81,6 +85,34 @@ const settings = definePluginSettings({
}
});
+interface ColorPickerProps {
+ color: number | null;
+ label: React.ReactElement;
+ showEyeDropper?: boolean;
+ suggestedColors?: string[];
+ onChange(value: number | null): void;
+}
+
+// I can't be bothered to figure out the semantics of this component. The
+// functions surely get some event argument sent to them and they likely aren't
+// all required. If anyone who wants to use this component stumbles across this
+// code, you'll have to do the research yourself.
+interface ProfileModalProps {
+ user: User;
+ pendingThemeColors: [number, number];
+ onAvatarChange: () => void;
+ onBannerChange: () => void;
+ canUsePremiumCustomization: boolean;
+ hideExampleButton: boolean;
+ hideFakeActivity: boolean;
+ isTryItOutFlow: boolean;
+}
+
+const ColorPicker = findComponentByCodeLazy(".Messages.USER_SETTINGS_PROFILE_COLOR_SELECT_COLOR", ".BACKGROUND_PRIMARY)");
+const ProfileModal = findComponentByCodeLazy('"ProfileCustomizationPreview"');
+
+const requireColorPicker = extractAndLoadChunksLazy(["USER_SETTINGS_PROFILE_COLOR_DEFAULT_BUTTON.format"], /createPromise:\(\)=>\i\.\i\("(.+?)"\).then\(\i\.bind\(\i,"(.+?)"\)\)/);
+
export default definePlugin({
name: "FakeProfileThemes",
description: "Allows profile theming by hiding the colors in your bio thanks to invisible 3y3 encoding",
@@ -101,21 +133,98 @@ export default definePlugin({
}
}
],
- settingsAboutComponent: () => (
-
- Usage
-
- After enabling this plugin, you will see custom colors in the profiles of other people using compatible plugins.
- To set your own colors:
-
-
• go to your profile settings
-
• choose your own colors in the Nitro preview
-
• click the "Copy 3y3" button
-
• paste the invisible text anywhere in your bio
-
- Please note: if you are using a theme which hides nitro ads, you should disable it temporarily to set colors.
-
- ),
+ settingsAboutComponent: () => {
+ const existingColors = decode(
+ UserProfileStore.getUserProfile(UserStore.getCurrentUser().id).bio
+ ) ?? [0, 0];
+ const [color1, setColor1] = useState(existingColors[0]);
+ const [color2, setColor2] = useState(existingColors[1]);
+
+ const [, , loadingColorPickerChunk] = useAwaiter(requireColorPicker);
+
+ return (
+
+ Usage
+
+ After enabling this plugin, you will see custom colors in
+ the profiles of other people using compatible plugins.{" "}
+
+ To set your own colors:
+
+
+ • use the color pickers below to choose your colors
+