diff --git a/.eslintrc.json b/.eslintrc.json index ab78ac85b..3005e0996 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,7 @@ "parserOptions": { "project": "./tsconfig.json" }, - "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 77c72369c..79f0f2cd3 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 04b811e9c..29b1506e2 100644 --- a/package.json +++ b/package.json @@ -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 065d5d4e7..b03585799 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 e2c901226..3b1473e1c 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/src/main/ipcMain.ts b/src/main/ipcMain.ts index 9c9741db5..62785867e 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/modules.d.ts b/src/modules.d.ts index 489799256..83a512b00 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/ctrlEnterSend/index.ts b/src/plugins/ctrlEnterSend/index.ts index 4b9dd8e06..817da0532 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/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index 66dcd78da..f969cb12b 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -812,7 +812,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/friendsSince/index.tsx b/src/plugins/friendsSince/index.tsx index e2c92805d..b8f11b307 100644 --- a/src/plugins/friendsSince/index.tsx +++ b/src/plugins/friendsSince/index.tsx @@ -52,6 +52,8 @@ export default definePlugin({ getFriendSince(userId: string) { try { + if (!RelationshipStore.isFriend(userId)) return null; + return RelationshipStore.getSince(userId); } catch (err) { new Logger("FriendsSince").error(err); @@ -60,6 +62,8 @@ export default definePlugin({ }, friendsSince: ErrorBoundary.wrap(({ userId, textClassName }: { userId: string; textClassName?: string; }) => { + if (!RelationshipStore.isFriend(userId)) return null; + const friendsSince = RelationshipStore.getSince(userId); if (!friendsSince) return null; diff --git a/src/plugins/messageLatency/index.tsx b/src/plugins/messageLatency/index.tsx index 819802bdf..31b2c3432 100644 --- a/src/plugins/messageLatency/index.tsx +++ b/src/plugins/messageLatency/index.tsx @@ -97,6 +97,9 @@ export default definePlugin({ // Message wasn't received through gateway if (!isNonNullish(nonce)) return null; + // Bots basically never send a nonce, and if someone does do it then it's usually not a snowflake + if (message.bot) return null; + let isDiscordKotlin = false; let delta = SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce); // milliseconds if (!showMillis) { diff --git a/src/plugins/replaceGoogleSearch/index.tsx b/src/plugins/replaceGoogleSearch/index.tsx index dff593a3b..9882809f7 100644 --- a/src/plugins/replaceGoogleSearch/index.tsx +++ b/src/plugins/replaceGoogleSearch/index.tsx @@ -37,7 +37,7 @@ const settings = definePluginSettings({ }); function search(src: string, engine: string) { - open(engine + encodeURIComponent(src), "_blank"); + open(engine + encodeURIComponent(src.trim()), "_blank"); } function makeSearchItem(src: string) { diff --git a/src/plugins/seeSummaries/README.md b/src/plugins/seeSummaries/README.md new file mode 100644 index 000000000..dfb90bab3 --- /dev/null +++ b/src/plugins/seeSummaries/README.md @@ -0,0 +1,7 @@ +# Summaries + +Enables Discord's experimental Summaries feature on every server, displaying AI generated summaries of conversations. + +Note that this plugin can't fetch old summaries, it can only display ones created while your Discord is running with the plugin enabled. + +![](https://github.com/Vendicated/Vencord/assets/45497981/bd931b0c-2e85-4c10-9f7c-8ba01eb55745) diff --git a/src/plugins/seeSummaries/index.tsx b/src/plugins/seeSummaries/index.tsx new file mode 100644 index 000000000..131fe8206 --- /dev/null +++ b/src/plugins/seeSummaries/index.tsx @@ -0,0 +1,114 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { DataStore } from "@api/index"; +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByProps } from "@webpack"; +import { ChannelStore, GuildStore } from "@webpack/common"; + +const SummaryStore = findByProps("allSummaries", "findSummary"); +const { createSummaryFromServer } = findByProps("createSummaryFromServer"); + +const settings = definePluginSettings({ + summaryExpiryThresholdDays: { + type: OptionType.SLIDER, + description: "The time in days before a summary is removed. Note that only up to 50 summaries are kept per channel", + markers: [1, 3, 5, 7, 10, 15, 20, 25, 30], + stickToMarkers: false, + default: 3, + } +}); + +interface Summary { + count: number; + end_id: string; + id: string; + message_ids: string[]; + people: string[]; + source: number; + start_id: string; + summ_short: string; + topic: string; + type: number; + unsafe: boolean; +} + +interface ChannelSummaries { + type: string; + channel_id: string; + guild_id: string; + summaries: Summary[]; + + // custom property + time?: number; +} + +export default definePlugin({ + name: "Summaries", + description: "Enables Discord's experimental Summaries feature on every server, displaying AI generated summaries of conversations", + authors: [Devs.mantikafasi], + settings, + patches: [ + { + find: "ChannelTypesSets.SUMMARIZEABLE.has", + replacement: { + match: /\i\.hasFeature\(\i\.GuildFeatures\.SUMMARIES_ENABLED\w+?\)/g, + replace: "true" + } + }, + { + find: "RECEIVE_CHANNEL_SUMMARY(", + replacement: { + match: /shouldFetch\((\i),\i\){/, + replace: "$& if(!$self.shouldFetch($1)) return false;" + } + } + ], + flux: { + CONVERSATION_SUMMARY_UPDATE(data) { + const incomingSummaries: ChannelSummaries[] = data.summaries.map((summary: any) => ({ ...createSummaryFromServer(summary), time: Date.now() })); + + // idk if this is good for performance but it doesnt seem to be a problem in my experience + DataStore.update("summaries-data", summaries => { + summaries ??= {}; + summaries[data.channel_id] ? summaries[data.channel_id].unshift(...incomingSummaries) : (summaries[data.channel_id] = incomingSummaries); + if (summaries[data.channel_id].length > 50) + summaries[data.channel_id] = summaries[data.channel_id].slice(0, 50); + + return summaries; + }); + } + }, + + async start() { + await DataStore.update("summaries-data", summaries => { + for (const key of Object.keys(summaries)) { + for (let i = summaries[key].length - 1; i >= 0; i--) { + if (summaries[key][i].time < Date.now() - 1000 * 60 * 60 * 24 * settings.store.summaryExpiryThresholdDays) { + summaries[key].splice(i, 1); + } + } + + if (summaries[key].length === 0) { + delete summaries[key]; + } + } + + Object.assign(SummaryStore.allSummaries(), summaries); + return summaries; + }); + }, + + shouldFetch(channelId: string) { + const channel = ChannelStore.getChannel(channelId); + // SUMMARIES_ENABLED feature is not in discord-types + const guild = GuildStore.getGuild(channel.guild_id); + // @ts-ignore + return guild.hasFeature("SUMMARIES_ENABLED_GA"); + } +}); diff --git a/src/plugins/shikiCodeblocks.desktop/index.ts b/src/plugins/shikiCodeblocks.desktop/index.ts index ef1b5d3df..27463195d 100644 --- a/src/plugins/shikiCodeblocks.desktop/index.ts +++ b/src/plugins/shikiCodeblocks.desktop/index.ts @@ -21,8 +21,7 @@ import "./shiki.css"; import { enableStyle } from "@api/Styles"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; - -import previewExampleText from "~fileContent/previewExample.tsx"; +import previewExampleText from "file://previewExample.tsx"; import { shiki } from "./api/shiki"; import { createHighlighter } from "./components/Highlighter"; diff --git a/src/plugins/watchTogetherAdblock.desktop/README.md b/src/plugins/watchTogetherAdblock.desktop/README.md new file mode 100644 index 000000000..4c64df67b --- /dev/null +++ b/src/plugins/watchTogetherAdblock.desktop/README.md @@ -0,0 +1,6 @@ +# WatchTogetherAdblock + +Block ads in the YouTube WatchTogether activity via AdGuard + +Note that this only works for yourself, other users in the activity will still see ads. +Powered by a modified version of [Adguard's BlockYoutubeAdsShortcut](https://github.com/AdguardTeam/BlockYouTubeAdsShortcut) diff --git a/src/plugins/watchTogetherAdblock.desktop/adguard.js b/src/plugins/watchTogetherAdblock.desktop/adguard.js new file mode 100644 index 000000000..945f76bd5 --- /dev/null +++ b/src/plugins/watchTogetherAdblock.desktop/adguard.js @@ -0,0 +1,262 @@ +/* eslint-disable */ + +/** + * This file is part of AdGuard's Block YouTube Ads (https://github.com/AdguardTeam/BlockYouTubeAdsShortcut). + * + * Copyright (C) AdGuard Team + * + * AdGuard's Block YouTube Ads 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. + * + * AdGuard's Block YouTube Ads 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 AdGuard's Block YouTube Ads. If not, see . + */ + +const LOGO_ID = "block-youtube-ads-logo"; +const hiddenCSS = [ + "#__ffYoutube1", + "#__ffYoutube2", + "#__ffYoutube3", + "#__ffYoutube4", + "#feed-pyv-container", + "#feedmodule-PRO", + "#homepage-chrome-side-promo", + "#merch-shelf", + "#offer-module", + '#pla-shelf > ytd-pla-shelf-renderer[class="style-scope ytd-watch"]', + "#pla-shelf", + "#premium-yva", + "#promo-info", + "#promo-list", + "#promotion-shelf", + "#related > ytd-watch-next-secondary-results-renderer > #items > ytd-compact-promoted-video-renderer.ytd-watch-next-secondary-results-renderer", + "#search-pva", + "#shelf-pyv-container", + "#video-masthead", + "#watch-branded-actions", + "#watch-buy-urls", + "#watch-channel-brand-div", + "#watch7-branded-banner", + "#YtKevlarVisibilityIdentifier", + "#YtSparklesVisibilityIdentifier", + ".carousel-offer-url-container", + ".companion-ad-container", + ".GoogleActiveViewElement", + '.list-view[style="margin: 7px 0pt;"]', + ".promoted-sparkles-text-search-root-container", + ".promoted-videos", + ".searchView.list-view", + ".sparkles-light-cta", + ".watch-extra-info-column", + ".watch-extra-info-right", + ".ytd-carousel-ad-renderer", + ".ytd-compact-promoted-video-renderer", + ".ytd-companion-slot-renderer", + ".ytd-merch-shelf-renderer", + ".ytd-player-legacy-desktop-watch-ads-renderer", + ".ytd-promoted-sparkles-text-search-renderer", + ".ytd-promoted-video-renderer", + ".ytd-search-pyv-renderer", + ".ytd-video-masthead-ad-v3-renderer", + ".ytp-ad-action-interstitial-background-container", + ".ytp-ad-action-interstitial-slot", + ".ytp-ad-image-overlay", + ".ytp-ad-overlay-container", + ".ytp-ad-progress", + ".ytp-ad-progress-list", + '[class*="ytd-display-ad-"]', + '[layout*="display-ad-"]', + 'a[href^="http://www.youtube.com/cthru?"]', + 'a[href^="https://www.youtube.com/cthru?"]', + "ytd-action-companion-ad-renderer", + "ytd-banner-promo-renderer", + "ytd-compact-promoted-video-renderer", + "ytd-companion-slot-renderer", + "ytd-display-ad-renderer", + "ytd-promoted-sparkles-text-search-renderer", + "ytd-promoted-sparkles-web-renderer", + "ytd-search-pyv-renderer", + "ytd-single-option-survey-renderer", + "ytd-video-masthead-ad-advertiser-info-renderer", + "ytd-video-masthead-ad-v3-renderer", + "YTM-PROMOTED-VIDEO-RENDERER", +]; +/** +* Adds CSS to the page +*/ +const hideElements = () => { + const selectors = hiddenCSS; + if (!selectors) { + return; + } + const rule = selectors.join(", ") + " { display: none!important; }"; + const style = document.createElement("style"); + style.innerHTML = rule; + document.head.appendChild(style); +}; +/** +* Calls the "callback" function on every DOM change, but not for the tracked events +* @param {Function} callback callback function +*/ +const observeDomChanges = callback => { + const domMutationObserver = new MutationObserver(mutations => { + callback(mutations); + }); + domMutationObserver.observe(document.documentElement, { + childList: true, + subtree: true, + }); +}; +/** +* This function is supposed to be called on every DOM change +*/ +const hideDynamicAds = () => { + const elements = document.querySelectorAll("#contents > ytd-rich-item-renderer ytd-display-ad-renderer"); + if (elements.length === 0) { + return; + } + elements.forEach(el => { + if (el.parentNode && el.parentNode.parentNode) { + const parent = el.parentNode.parentNode; + if (parent.localName === "ytd-rich-item-renderer") { + parent.style.display = "none"; + } + } + }); +}; +/** +* This function checks if the video ads are currently running +* and auto-clicks the skip button. +*/ +const autoSkipAds = () => { + // If there's a video that plays the ad at this moment, scroll this ad + if (document.querySelector(".ad-showing")) { + const video = document.querySelector("video"); + if (video && video.duration) { + video.currentTime = video.duration; + // Skip button should appear after that, + // now simply click it automatically + setTimeout(() => { + const skipBtn = document.querySelector("button.ytp-ad-skip-button"); + if (skipBtn) { + skipBtn.click(); + } + }, 100); + } + } +}; +/** +* This function overrides a property on the specified object. +* +* @param {object} obj object to look for properties in +* @param {string} propertyName property to override +* @param {*} overrideValue value to set +*/ +const overrideObject = (obj, propertyName, overrideValue) => { + if (!obj) { + return false; + } + let overriden = false; + for (const key in obj) { + // eslint-disable-next-line no-prototype-builtins + if (obj.hasOwnProperty(key) && key === propertyName) { + obj[key] = overrideValue; + overriden = true; + // eslint-disable-next-line no-prototype-builtins + } else if (obj.hasOwnProperty(key) && typeof obj[key] === "object") { + if (overrideObject(obj[key], propertyName, overrideValue)) { + overriden = true; + } + } + } + return overriden; +}; +/** +* Overrides JSON.parse and Response.json functions. +* Examines these functions arguments, looks for properties with the specified name there +* and if it exists, changes it's value to what was specified. +* +* @param {string} propertyName name of the property +* @param {*} overrideValue new value for the property +*/ +const jsonOverride = (propertyName, overrideValue) => { + const nativeJSONParse = JSON.parse; + JSON.parse = (...args) => { + const obj = nativeJSONParse.apply(this, args); + // Override it's props and return back to the caller + overrideObject(obj, propertyName, overrideValue); + return obj; + }; + // Override Response.prototype.json + const nativeResponseJson = Response.prototype.json; + Response.prototype.json = new Proxy(nativeResponseJson, { + apply(...args) { + // Call the target function, get the original Promise + const promise = Reflect.apply(...args); + // Create a new one and override the JSON inside + return new Promise((resolve, reject) => { + promise.then(data => { + overrideObject(data, propertyName, overrideValue); + resolve(data); + }).catch(error => reject(error)); + }); + }, + }); +}; +const addAdGuardLogoStyle = () => { }; +const addAdGuardLogo = () => { + if (document.getElementById(LOGO_ID)) { + return; + } + const logo = document.createElement("span"); + logo.innerHTML = "__logo_text__"; + logo.setAttribute("id", LOGO_ID); + if (window.location.hostname === "m.youtube.com") { + const btn = document.querySelector("header.mobile-topbar-header > button"); + if (btn) { + btn.parentNode?.insertBefore(logo, btn.nextSibling); + addAdGuardLogoStyle(); + } + } else if (window.location.hostname === "www.youtube.com") { + const code = document.getElementById("country-code"); + if (code) { + code.innerHTML = ""; + code.appendChild(logo); + addAdGuardLogoStyle(); + } + } else if (window.location.hostname === "music.youtube.com") { + const el = document.querySelector(".ytmusic-nav-bar#left-content"); + if (el) { + el.appendChild(logo); + addAdGuardLogoStyle(); + } + } else if (window.location.hostname === "www.youtube-nocookie.com") { + const code = document.querySelector("#yt-masthead #logo-container .content-region"); + if (code) { + code.innerHTML = ""; + code.appendChild(logo); + addAdGuardLogoStyle(); + } + } +}; +// Removes ads metadata from YouTube XHR requests +jsonOverride("adPlacements", []); +jsonOverride("playerAds", []); +// Applies CSS that hides YouTube ad elements +hideElements(); +// Some changes should be re-evaluated on every page change +addAdGuardLogo(); +hideDynamicAds(); +autoSkipAds(); +observeDomChanges(() => { + addAdGuardLogo(); + hideDynamicAds(); + autoSkipAds(); +}); diff --git a/src/plugins/watchTogetherAdblock.desktop/index.ts b/src/plugins/watchTogetherAdblock.desktop/index.ts new file mode 100644 index 000000000..2dbc13d4a --- /dev/null +++ b/src/plugins/watchTogetherAdblock.desktop/index.ts @@ -0,0 +1,15 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +// The entire code of this plugin can be found in native.ts +export default definePlugin({ + name: "WatchTogetherAdblock", + description: "Block ads in the YouTube WatchTogether activity via AdGuard", + authors: [Devs.ImLvna], +}); diff --git a/src/plugins/watchTogetherAdblock.desktop/native.ts b/src/plugins/watchTogetherAdblock.desktop/native.ts new file mode 100644 index 000000000..c4106c349 --- /dev/null +++ b/src/plugins/watchTogetherAdblock.desktop/native.ts @@ -0,0 +1,21 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2023 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { RendererSettings } from "@main/settings"; +import { app } from "electron"; +import adguard from "file://adguard.js?minify"; + +app.on("browser-window-created", (_, win) => { + win.webContents.on("frame-created", (_, { frame }) => { + frame.once("dom-ready", () => { + if (frame.url.includes("discordsays") && frame.url.includes("youtube.com")) { + if (!RendererSettings.store.plugins?.WatchTogetherAdblock?.enabled) return; + + frame.executeJavaScript(adguard); + } + }); + }); +}); diff --git a/src/webpack/common/types/stores.d.ts b/src/webpack/common/types/stores.d.ts index d6fb9aec7..2da06af79 100644 --- a/src/webpack/common/types/stores.d.ts +++ b/src/webpack/common/types/stores.d.ts @@ -63,7 +63,7 @@ export interface CustomEmoji { originalName?: string; require_colons: boolean; roles: string[]; - type: "GUILD_EMOJI"; + type: 1; } export interface UnicodeEmoji { @@ -75,7 +75,7 @@ export interface UnicodeEmoji { }; index: number; surrogates: string; - type: "UNICODE"; + type: 0; uniqueName: string; useSpriteSheet: boolean; get allNamesString(): string; diff --git a/tsconfig.json b/tsconfig.json index 34750a974..8db0ab3c1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -39,5 +39,5 @@ } ] }, - "include": ["src/**/*", "browser/**/*", "scripts/**/*", "packages/**/*"] + "include": ["src/**/*", "browser/**/*", "scripts/**/*"] }