Compare commits

..

3 Commits

Author SHA1 Message Date
Rie Takahashi
9bc373d87a fix: merge tti components, use css file 2023-09-17 20:45:04 +01:00
Rie Takahashi
fb7e1c64fd fix missing $ in regex 2023-09-17 19:42:59 +01:00
Rie Takahashi
1128e8f3ad feat(startup timings): add Time-To-Interactive info 2023-09-17 19:37:03 +01:00
70 changed files with 468 additions and 1993 deletions

View File

@ -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 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

View File

@ -19,11 +19,9 @@
/// <reference path="../src/modules.d.ts" /> /// <reference path="../src/modules.d.ts" />
/// <reference path="../src/globals.d.ts" /> /// <reference path="../src/globals.d.ts" />
import monacoHtmlLocal from "~fileContent/monacoWin.html"; import monacoHtml from "~fileContent/../src/components/monacoWin.html";
import monacoHtmlCdn from "~fileContent/../src/main/monacoWin.html";
import * as DataStore from "../src/api/DataStore"; import * as DataStore from "../src/api/DataStore";
import { debounce } from "../src/utils"; import { debounce } from "../src/utils";
import { EXTENSION_BASE_URL } from "../src/utils/web-metadata";
import { getTheme, Theme } from "../src/utils/discord"; import { getTheme, Theme } from "../src/utils/discord";
import { getThemeInfo } from "../src/main/themes"; import { getThemeInfo } from "../src/main/themes";
@ -82,7 +80,6 @@ window.VencordNative = {
return; return;
} }
win.baseUrl = EXTENSION_BASE_URL;
win.setCss = setCssDebounced; win.setCss = setCssDebounced;
win.getCurrentCss = () => VencordNative.quickCss.get(); win.getCurrentCss = () => VencordNative.quickCss.get();
win.getTheme = () => win.getTheme = () =>
@ -90,7 +87,7 @@ window.VencordNative = {
? "vs-light" ? "vs-light"
: "vs-dark"; : "vs-dark";
win.document.write(IS_EXTENSION ? monacoHtmlLocal : monacoHtmlCdn); win.document.write(monacoHtml);
}, },
}, },

32
browser/background.js Normal file
View File

@ -0,0 +1,32 @@
/**
* @template T
* @param {T[]} arr
* @param {(v: T) => boolean} predicate
*/
function removeFirst(arr, predicate) {
const idx = arr.findIndex(predicate);
if (idx !== -1) arr.splice(idx, 1);
}
chrome.webRequest.onHeadersReceived.addListener(
({ responseHeaders, type, url }) => {
if (!responseHeaders) return;
if (type === "main_frame") {
// In main frame requests, the CSP needs to be removed to enable fetching of custom css
// as desired by the user
removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-security-policy");
} else if (type === "stylesheet" && url.startsWith("https://raw.githubusercontent.com/")) {
// Most users will load css from GitHub, but GitHub doesn't set the correct content type,
// so we fix it here
removeFirst(responseHeaders, h => h.name.toLowerCase() === "content-type");
responseHeaders.push({
name: "Content-Type",
value: "text/css"
});
}
return { responseHeaders };
},
{ urls: ["https://raw.githubusercontent.com/*", "*://*.discord.com/*"], types: ["main_frame", "stylesheet"] },
["blocking", "responseHeaders"]
);

View File

@ -4,11 +4,6 @@ if (typeof browser === "undefined") {
const script = document.createElement("script"); const script = document.createElement("script");
script.src = browser.runtime.getURL("dist/Vencord.js"); script.src = browser.runtime.getURL("dist/Vencord.js");
script.id = "vencord-script";
Object.assign(script.dataset, {
extensionBaseUrl: browser.runtime.getURL(""),
version: browser.runtime.getManifest().version
});
const style = document.createElement("link"); const style = document.createElement("link");
style.type = "text/css"; style.type = "text/css";

View File

@ -28,7 +28,7 @@
"web_accessible_resources": [ "web_accessible_resources": [
{ {
"resources": ["dist/*", "third-party/*"], "resources": ["dist/Vencord.js", "dist/Vencord.css"],
"matches": ["*://*.discord.com/*"] "matches": ["*://*.discord.com/*"]
} }
], ],

View File

@ -26,7 +26,11 @@
} }
], ],
"web_accessible_resources": ["dist/*", "third-party/*"], "background": {
"scripts": ["background.js"]
},
"web_accessible_resources": ["dist/Vencord.js", "dist/Vencord.css"],
"browser_specific_settings": { "browser_specific_settings": {
"gecko": { "gecko": {

View File

@ -1,43 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./patch-worker";
import * as monaco from "monaco-editor/esm/vs/editor/editor.main.js";
declare global {
const baseUrl: string;
const getCurrentCss: () => Promise<string>;
const setCss: (css: string) => void;
const getTheme: () => string;
}
const BASE = "/dist/monaco/vs";
self.MonacoEnvironment = {
getWorkerUrl(_moduleId: unknown, label: string) {
const path = label === "css" ? "/language/css/css.worker.js" : "/editor/editor.worker.js";
return new URL(BASE + path, baseUrl).toString();
}
};
getCurrentCss().then(css => {
const editor = monaco.editor.create(
document.getElementById("container")!,
{
value: css,
language: "css",
theme: getTheme(),
}
);
editor.onDidChangeModelContent(() =>
setCss(editor.getValue())
);
window.addEventListener("resize", () => {
// make monaco re-layout
editor.layout();
});
});

View File

@ -1,37 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Vencord QuickCSS Editor</title>
<style>
html,
body,
#container {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
const script = document.createElement("script");
script.src = new URL("/dist/monaco/index.js", baseUrl);
const style = document.createElement("link");
style.type = "text/css";
style.rel = "stylesheet";
style.href = new URL("/dist/monaco/index.css", baseUrl);
document.body.append(style, script);
</script>
</body>
</html>

View File

@ -1,135 +0,0 @@
/*
Copyright 2013 Rob Wu <gwnRob@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Target: Chrome 20+
// W3-compliant Worker proxy.
// This module replaces the global Worker object.
// When invoked, the default Worker object is called.
// If this call fails with SECURITY_ERR, the script is fetched
// using async XHR, and transparently proxies all calls and
// setters/getters to the new Worker object.
// Note: This script does not magically circumvent the Same origin policy.
(function () {
'use strict';
var Worker_ = window.Worker;
var URL = window.URL || window.webkitURL;
// Create dummy worker for the following purposes:
// 1. Don't override the global Worker object if the fallback isn't
// going to work (future API changes?)
// 2. Use it to trigger early validation of postMessage calls
// Note: Blob constructor is supported since Chrome 20, but since
// some of the used Chrome APIs are only supported as of Chrome 20,
// I don't bother adding a BlobBuilder fallback.
var dummyWorker = new Worker_(
URL.createObjectURL(new Blob([], { type: 'text/javascript' })));
window.Worker = function Worker(scriptURL) {
if (arguments.length === 0) {
throw new TypeError('Not enough arguments');
}
try {
return new Worker_(scriptURL);
} catch (e) {
if (e.code === 18/*DOMException.SECURITY_ERR*/) {
return new WorkerXHR(scriptURL);
} else {
throw e;
}
}
};
// Bind events and replay queued messages
function bindWorker(worker, workerURL) {
if (worker._terminated) {
return;
}
worker.Worker = new Worker_(workerURL);
worker.Worker.onerror = worker._onerror;
worker.Worker.onmessage = worker._onmessage;
var o;
while ((o = worker._replayQueue.shift())) {
worker.Worker[o.method].apply(worker.Worker, o.arguments);
}
while ((o = worker._messageQueue.shift())) {
worker.Worker.postMessage.apply(worker.Worker, o);
}
}
function WorkerXHR(scriptURL) {
var worker = this;
var x = new XMLHttpRequest();
x.responseType = 'blob';
x.onload = function () {
// http://stackoverflow.com/a/10372280/938089
var workerURL = URL.createObjectURL(x.response);
bindWorker(worker, workerURL);
};
x.open('GET', scriptURL);
x.send();
worker._replayQueue = [];
worker._messageQueue = [];
}
WorkerXHR.prototype = {
constructor: Worker_,
terminate: function () {
if (!this._terminated) {
this._terminated = true;
if (this.Worker)
this.Worker.terminate();
}
},
postMessage: function (message, transfer) {
if (!(this instanceof WorkerXHR))
throw new TypeError('Illegal invocation');
if (this.Worker) {
this.Worker.postMessage.apply(this.Worker, arguments);
} else {
// Trigger validation:
dummyWorker.postMessage(message);
// Alright, push the valid message to the queue.
this._messageQueue.push(arguments);
}
}
};
// Implement the EventTarget interface
[
'addEventListener',
'removeEventListener',
'dispatchEvent'
].forEach(function (method) {
WorkerXHR.prototype[method] = function () {
if (!(this instanceof WorkerXHR)) {
throw new TypeError('Illegal invocation');
}
if (this.Worker) {
this.Worker[method].apply(this.Worker, arguments);
} else {
this._replayQueue.push({ method: method, arguments: arguments });
}
};
});
Object.defineProperties(WorkerXHR.prototype, {
onmessage: {
get: function () { return this._onmessage || null; },
set: function (func) {
this._onmessage = typeof func === 'function' ? func : null;
}
},
onerror: {
get: function () { return this._onerror || null; },
set: function (func) {
this._onerror = typeof func === 'function' ? func : null;
}
}
});
})();

View File

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.5.2", "version": "1.4.7",
"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": {
@ -36,13 +36,10 @@
"@vap/shiki": "0.10.5", "@vap/shiki": "0.10.5",
"eslint-plugin-simple-header": "^1.0.2", "eslint-plugin-simple-header": "^1.0.2",
"fflate": "^0.7.4", "fflate": "^0.7.4",
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
"monaco-editor": "^0.43.0",
"nanoid": "^4.0.2", "nanoid": "^4.0.2",
"virtual-merge": "^1.0.1" "virtual-merge": "^1.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/chrome": "^0.0.246",
"@types/diff": "^5.0.3", "@types/diff": "^5.0.3",
"@types/lodash": "^4.14.194", "@types/lodash": "^4.14.194",
"@types/node": "^18.16.3", "@types/node": "^18.16.3",
@ -67,8 +64,7 @@
"stylelint-config-standard": "^33.0.0", "stylelint-config-standard": "^33.0.0",
"tsx": "^3.12.7", "tsx": "^3.12.7",
"type-fest": "^3.9.0", "type-fest": "^3.9.0",
"typescript": "^5.0.4", "typescript": "^5.0.4"
"zip-local": "^0.3.5"
}, },
"packageManager": "pnpm@8.1.1", "packageManager": "pnpm@8.1.1",
"pnpm": { "pnpm": {

81
pnpm-lock.yaml generated
View File

@ -1,9 +1,5 @@
lockfileVersion: '6.0' lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
patchedDependencies: patchedDependencies:
eslint-plugin-path-alias@1.0.0: eslint-plugin-path-alias@1.0.0:
hash: m6sma4g6bh67km3q6igf6uxaja hash: m6sma4g6bh67km3q6igf6uxaja
@ -28,12 +24,6 @@ dependencies:
fflate: fflate:
specifier: ^0.7.4 specifier: ^0.7.4
version: 0.7.4 version: 0.7.4
gifenc:
specifier: github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3
version: github.com/mattdesl/gifenc/64842fca317b112a8590f8fef2bf3825da8f6fe3
monaco-editor:
specifier: ^0.43.0
version: 0.43.0
nanoid: nanoid:
specifier: ^4.0.2 specifier: ^4.0.2
version: 4.0.2 version: 4.0.2
@ -42,9 +32,6 @@ dependencies:
version: 1.0.1 version: 1.0.1
devDependencies: devDependencies:
'@types/chrome':
specifier: ^0.0.246
version: 0.0.246
'@types/diff': '@types/diff':
specifier: ^5.0.3 specifier: ^5.0.3
version: 5.0.3 version: 5.0.3
@ -120,9 +107,6 @@ devDependencies:
typescript: typescript:
specifier: ^5.0.4 specifier: ^5.0.4
version: 5.0.4 version: 5.0.4
zip-local:
specifier: ^0.3.5
version: 0.3.5
packages: packages:
@ -536,31 +520,10 @@ packages:
resolution: {integrity: sha512-gAC33DCXYwNTI/k1PxOVHmbbzakUSMbb/DHpoV6rn4pKZtPI1dduULSmAAm/y1ipgIlArnk2JcnQzw4n2tCZHw==} resolution: {integrity: sha512-gAC33DCXYwNTI/k1PxOVHmbbzakUSMbb/DHpoV6rn4pKZtPI1dduULSmAAm/y1ipgIlArnk2JcnQzw4n2tCZHw==}
dev: false dev: false
/@types/chrome@0.0.246:
resolution: {integrity: sha512-MxGxEomGxsJiL9xe/7ZwVgwdn8XVKWbPvxpVQl3nWOjrS0Ce63JsfzxUc4aU3GvRcUPYsfufHmJ17BFyKxeA4g==}
dependencies:
'@types/filesystem': 0.0.33
'@types/har-format': 1.2.13
dev: true
/@types/diff@5.0.3: /@types/diff@5.0.3:
resolution: {integrity: sha512-amrLbRqTU9bXMCc6uX0sWpxsQzRIo9z6MJPkH1pkez/qOxuqSZVuryJAWoBRq94CeG8JxY+VK4Le9HtjQR5T9A==} resolution: {integrity: sha512-amrLbRqTU9bXMCc6uX0sWpxsQzRIo9z6MJPkH1pkez/qOxuqSZVuryJAWoBRq94CeG8JxY+VK4Le9HtjQR5T9A==}
dev: true dev: true
/@types/filesystem@0.0.33:
resolution: {integrity: sha512-2KedRPzwu2K528vFkoXnnWdsG0MtUwPjuA7pRy4vKxlxHEe8qUDZibYHXJKZZr2Cl/ELdCWYqyb/MKwsUuzBWw==}
dependencies:
'@types/filewriter': 0.0.30
dev: true
/@types/filewriter@0.0.30:
resolution: {integrity: sha512-lB98tui0uxc7erbj0serZfJlHKLNJHwBltPnbmO1WRpL5T325GOHRiQfr2E29V2q+S1brDO63Fpdt6vb3bES9Q==}
dev: true
/@types/har-format@1.2.13:
resolution: {integrity: sha512-PwBsCBD3lDODn4xpje3Y1di0aDJp4Ww7aSfMRVw6ysnxD4I7Wmq2mBkSKaDtN403hqH5sp6c9xQUvFYY3+lkBg==}
dev: true
/@types/json-schema@7.0.11: /@types/json-schema@7.0.11:
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
dev: true dev: true
@ -880,10 +843,6 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/async@1.5.2:
resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==}
dev: true
/atob@2.1.2: /atob@2.1.2:
resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
engines: {node: '>= 4.5.0'} engines: {node: '>= 4.5.0'}
@ -1908,10 +1867,6 @@ packages:
resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==}
dev: true dev: true
/graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
dev: true
/grapheme-splitter@1.0.4: /grapheme-splitter@1.0.4:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
dev: true dev: true
@ -2229,12 +2184,6 @@ packages:
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
dev: false dev: false
/jszip@2.7.0:
resolution: {integrity: sha512-JIsRKRVC3gTRo2vM4Wy9WBC3TRcfnIZU8k65Phi3izkvPH975FowRYtKGT6PxevA0XnJ/yO8b0QwV0ydVyQwfw==}
dependencies:
pako: 1.0.11
dev: true
/kind-of@3.2.2: /kind-of@3.2.2:
resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2405,10 +2354,6 @@ packages:
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
dev: true dev: true
/monaco-editor@0.43.0:
resolution: {integrity: sha512-cnoqwQi/9fml2Szamv1XbSJieGJ1Dc8tENVMD26Kcfl7xGQWp7OBKMjlwKVGYFJ3/AXJjSOGvcqK7Ry/j9BM1Q==}
dev: false
/ms@2.0.0: /ms@2.0.0:
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
dev: true dev: true
@ -2566,10 +2511,6 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/pako@1.0.11:
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
dev: true
/parent-module@1.0.1: /parent-module@1.0.1:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'} engines: {node: '>=6'}
@ -2721,11 +2662,6 @@ packages:
- utf-8-validate - utf-8-validate
dev: true dev: true
/q@1.5.1:
resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==}
engines: {node: '>=0.6.0', teleport: '>=0.2.0'}
dev: true
/queue-microtask@1.2.3: /queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true dev: true
@ -3441,17 +3377,6 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
/zip-local@0.3.5: settings:
resolution: {integrity: sha512-GRV3D5TJY+/PqyeRm5CYBs7xVrKTKzljBoEXvocZu0HJ7tPEcgpSOYa2zFIsCZWgKWMuc4U3yMFgFkERGFIB9w==} autoInstallPeers: true
dependencies: excludeLinksFromLockfile: false
async: 1.5.2
graceful-fs: 4.2.11
jszip: 2.7.0
q: 1.5.1
dev: true
github.com/mattdesl/gifenc/64842fca317b112a8590f8fef2bf3825da8f6fe3:
resolution: {tarball: https://codeload.github.com/mattdesl/gifenc/tar.gz/64842fca317b112a8590f8fef2bf3825da8f6fe3}
name: gifenc
version: 1.0.3
dev: false

View File

@ -25,8 +25,6 @@ const defines = {
IS_STANDALONE: isStandalone, IS_STANDALONE: isStandalone,
IS_DEV: JSON.stringify(watch), IS_DEV: JSON.stringify(watch),
IS_UPDATER_DISABLED: updaterDisabled, IS_UPDATER_DISABLED: updaterDisabled,
IS_WEB: false,
IS_EXTENSION: false,
VERSION: JSON.stringify(VERSION), VERSION: JSON.stringify(VERSION),
BUILD_TIMESTAMP, BUILD_TIMESTAMP,
}; };
@ -79,6 +77,7 @@ await Promise.all([
], ],
define: { define: {
...defines, ...defines,
IS_WEB: false,
IS_DISCORD_DESKTOP: true, IS_DISCORD_DESKTOP: true,
IS_VESKTOP: false IS_VESKTOP: false
} }
@ -124,6 +123,7 @@ await Promise.all([
], ],
define: { define: {
...defines, ...defines,
IS_WEB: false,
IS_DISCORD_DESKTOP: false, IS_DISCORD_DESKTOP: false,
IS_VESKTOP: true IS_VESKTOP: true
} }

View File

@ -17,11 +17,12 @@
* 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"; import esbuild from "esbuild";
import { zip } from "fflate";
import { readFileSync } from "fs"; import { readFileSync } from "fs";
import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises"; import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises";
import { join } from "path"; import { join } from "path";
import Zip from "zip-local";
import { BUILD_TIMESTAMP, commonOpts, globPlugins, VERSION, watch } from "./common.mjs"; import { BUILD_TIMESTAMP, commonOpts, globPlugins, VERSION, watch } from "./common.mjs";
@ -41,7 +42,6 @@ const commonOptions = {
target: ["esnext"], target: ["esnext"],
define: { define: {
IS_WEB: "true", IS_WEB: "true",
IS_EXTENSION: "false",
IS_STANDALONE: "true", IS_STANDALONE: "true",
IS_DEV: JSON.stringify(watch), IS_DEV: JSON.stringify(watch),
IS_DISCORD_DESKTOP: "false", IS_DISCORD_DESKTOP: "false",
@ -52,58 +52,19 @@ const commonOptions = {
} }
}; };
const MonacoWorkerEntryPoints = [
"vs/language/css/css.worker.js",
"vs/editor/editor.worker.js"
];
const RnNoiseFiles = [
"dist/rnnoise.wasm",
"dist/rnnoise_simd.wasm",
"dist/rnnoise/workletProcessor.js",
"LICENSE"
];
await Promise.all( await Promise.all(
[ [
esbuild.build({
entryPoints: MonacoWorkerEntryPoints.map(entry => `node_modules/monaco-editor/esm/${entry}`),
bundle: true,
minify: true,
format: "iife",
outbase: "node_modules/monaco-editor/esm/",
outdir: "dist/monaco"
}),
esbuild.build({
entryPoints: ["browser/monaco.ts"],
bundle: true,
minify: true,
format: "iife",
outfile: "dist/monaco/index.js",
loader: {
".ttf": "file"
}
}),
esbuild.build({ esbuild.build({
...commonOptions, ...commonOptions,
outfile: "dist/browser.js", outfile: "dist/browser.js",
footer: { js: "//# sourceURL=VencordWeb" }, footer: { js: "//# sourceURL=VencordWeb" },
}), }),
esbuild.build({
...commonOptions,
outfile: "dist/extension.js",
define: {
...commonOptions?.define,
IS_EXTENSION: "true",
},
footer: { js: "//# sourceURL=VencordWeb" },
}),
esbuild.build({ esbuild.build({
...commonOptions, ...commonOptions,
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])], inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
define: { define: {
...(commonOptions?.define), "window": "unsafeWindow",
window: "unsafeWindow", ...(commonOptions?.define)
}, },
outfile: "dist/Vencord.user.js", outfile: "dist/Vencord.user.js",
banner: { banner: {
@ -118,41 +79,12 @@ await Promise.all(
); );
/** /**
* @type {(dir: string) => Promise<string[]>} * @type {(target: string, files: string[], shouldZip: boolean) => Promise<void>}
*/ */
async function globDir(dir) { async function buildPluginZip(target, files, shouldZip) {
const files = [];
for (const child of await readdir(dir, { withFileTypes: true })) {
const p = join(dir, child.name);
if (child.isDirectory())
files.push(...await globDir(p));
else
files.push(p);
}
return files;
}
/**
* @type {(dir: string, basePath?: string) => Promise<Record<string, string>>}
*/
async function loadDir(dir, basePath = "") {
const files = await globDir(dir);
return Object.fromEntries(await Promise.all(files.map(async f => [f.slice(basePath.length), await readFile(f)])));
}
/**
* @type {(target: string, files: string[]) => Promise<void>}
*/
async function buildExtension(target, files, noMonaco = false) {
const entries = { const entries = {
"dist/Vencord.js": await readFile("dist/extension.js"), "dist/Vencord.js": await readFile("dist/browser.js"),
"dist/Vencord.css": await readFile("dist/extension.css"), "dist/Vencord.css": await readFile("dist/browser.css"),
...(noMonaco ? {} : await loadDir("dist/monaco")),
...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")) {
@ -168,6 +100,21 @@ async function buildExtension(target, files, noMonaco = false) {
}))), }))),
}; };
if (shouldZip) {
return new Promise((resolve, reject) => {
zip(entries, {}, (err, data) => {
if (err) {
reject(err);
} else {
const out = join("dist", target);
writeFile(out, data).then(() => {
console.info("Extension written to " + out);
resolve();
}).catch(reject);
}
});
});
} else {
await rm(target, { recursive: true, force: true }); await rm(target, { recursive: true, force: true });
await Promise.all(Object.entries(entries).map(async ([file, content]) => { await Promise.all(Object.entries(entries).map(async ([file, content]) => {
const dest = join("dist", target, file); const dest = join("dist", target, file);
@ -178,6 +125,7 @@ async function buildExtension(target, files, noMonaco = false) {
console.info("Unpacked Extension written to dist/" + target); console.info("Unpacked Extension written to dist/" + target);
} }
}
const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content => { const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content => {
const cssRuntime = ` const cssRuntime = `
@ -194,9 +142,8 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content
await Promise.all([ await Promise.all([
appendCssRuntime, appendCssRuntime,
buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]), buildPluginZip("extension.zip", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"], true),
buildExtension("firefox-unpacked", ["content.js", "manifestv2.json", "icon.png"], true), buildPluginZip("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"], false),
buildPluginZip("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"], false),
]); ]);
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension.zip");
console.info("Packed Chromium Extension written to dist/extension.zip");

View File

@ -18,8 +18,7 @@
import { Dirent, readdirSync, readFileSync, writeFileSync } from "fs"; import { Dirent, readdirSync, readFileSync, writeFileSync } from "fs";
import { access, readFile } from "fs/promises"; import { access, readFile } from "fs/promises";
import { join, sep } from "path"; import { join } from "path";
import { normalize as posixNormalize, sep as posixSep } from "path/posix";
import { BigIntLiteral, createSourceFile, Identifier, isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, isVariableStatement, NamedDeclaration, NodeArray, ObjectLiteralExpression, ScriptTarget, StringLiteral, SyntaxKind } from "typescript"; import { BigIntLiteral, createSourceFile, Identifier, isArrayLiteralExpression, isCallExpression, isExportAssignment, isIdentifier, isObjectLiteralExpression, isPropertyAccessExpression, isPropertyAssignment, isSatisfiesExpression, isStringLiteral, isVariableStatement, NamedDeclaration, NodeArray, ObjectLiteralExpression, ScriptTarget, StringLiteral, SyntaxKind } from "typescript";
import { getPluginTarget } from "./utils.mjs"; import { getPluginTarget } from "./utils.mjs";
@ -40,7 +39,6 @@ interface PluginData {
required: boolean; required: boolean;
enabledByDefault: boolean; enabledByDefault: boolean;
target: "discordDesktop" | "vencordDesktop" | "web" | "dev"; target: "discordDesktop" | "vencordDesktop" | "web" | "dev";
filePath: string;
} }
const devs = {} as Record<string, Dev>; const devs = {} as Record<string, Dev>;
@ -167,12 +165,6 @@ async function parseFile(fileName: string) {
data.target = target as any; data.target = target as any;
} }
data.filePath = posixNormalize(fileName)
.split(sep)
.join(posixSep)
.replace(/\/index\.([jt]sx?)$/, "")
.replace(/^src\/plugins\//, "");
let readme = ""; let readme = "";
try { try {
readme = readFileSync(join(fileName, "..", "README.md"), "utf-8"); readme = readFileSync(join(fileName, "..", "README.md"), "utf-8");

View File

@ -28,8 +28,8 @@ interface BaseIconProps extends IconProps {
interface IconProps extends SVGProps<SVGSVGElement> { interface IconProps extends SVGProps<SVGSVGElement> {
className?: string; className?: string;
height?: string | number; height?: number;
width?: string | number; width?: number;
} }
function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) { function Icon({ height = 24, width = 24, className, children, viewBox, ...svgProps }: PropsWithChildren<BaseIconProps>) {
@ -97,7 +97,7 @@ export function OpenExternalIcon(props: IconProps) {
> >
<polygon <polygon
fill="currentColor" fill="currentColor"
fillRule="nonzero" fill-rule="nonzero"
points="13 20 11 20 11 8 5.5 13.5 4.08 12.08 12 4.16 19.92 12.08 18.5 13.5 13 8" points="13 20 11 20 11 8 5.5 13.5 4.08 12.08 12 4.16 19.92 12.08 18.5 13.5 13 8"
/> />
</Icon> </Icon>
@ -121,13 +121,9 @@ export function InfoIcon(props: IconProps) {
<Icon <Icon
{...props} {...props}
className={classes(props.className, "vc-info-icon")} className={classes(props.className, "vc-info-icon")}
viewBox="0 0 24 24" viewBox="0 0 12 12"
> >
<path <path fill="currentColor" d="M6 1C3.243 1 1 3.244 1 6c0 2.758 2.243 5 5 5s5-2.242 5-5c0-2.756-2.243-5-5-5zm0 2.376a.625.625 0 110 1.25.625.625 0 010-1.25zM7.5 8.5h-3v-1h1V6H5V5h1a.5.5 0 01.5.5v2h1v1z" />
fill="currentColor"
transform="translate(2 2)"
d="M9,7 L11,7 L11,5 L9,5 L9,7 Z M10,18 C5.59,18 2,14.41 2,10 C2,5.59 5.59,2 10,2 C14.41,2 18,5.59 18,10 C18,14.41 14.41,18 10,18 L10,18 Z M10,4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16,4.4771525 0,10 C-1.33226763e-15,12.6521649 1.0535684,15.195704 2.92893219,17.0710678 C4.80429597,18.9464316 7.3478351,20 10,20 C12.6521649,20 15.195704,18.9464316 17.0710678,17.0710678 C18.9464316,15.195704 20,12.6521649 20,10 C20,7.3478351 18.9464316,4.80429597 17.0710678,2.92893219 C15.195704,1.0535684 12.6521649,2.22044605e-16 10,0 L10,4.4408921e-16 Z M9,15 L11,15 L11,9 L9,9 L9,15 L9,15 Z"
/>
</Icon> </Icon>
); );
} }
@ -143,8 +139,8 @@ export function OwnerCrownIcon(props: IconProps) {
> >
<path <path
fill="currentColor" fill="currentColor"
fillRule="evenodd" fill-rule="evenodd"
clipRule="evenodd" clip-rule="evenodd"
d="M13.6572 5.42868C13.8879 5.29002 14.1806 5.30402 14.3973 5.46468C14.6133 5.62602 14.7119 5.90068 14.6473 6.16202L13.3139 11.4954C13.2393 11.7927 12.9726 12.0007 12.6666 12.0007H3.33325C3.02725 12.0007 2.76058 11.792 2.68592 11.4954L1.35258 6.16202C1.28792 5.90068 1.38658 5.62602 1.60258 5.46468C1.81992 5.30468 2.11192 5.29068 2.34325 5.42868L5.13192 7.10202L7.44592 3.63068C7.46173 3.60697 7.48377 3.5913 7.50588 3.57559C7.5192 3.56612 7.53255 3.55663 7.54458 3.54535L6.90258 2.90268C6.77325 2.77335 6.77325 2.56068 6.90258 2.43135L7.76458 1.56935C7.89392 1.44002 8.10658 1.44002 8.23592 1.56935L9.09792 2.43135C9.22725 2.56068 9.22725 2.77335 9.09792 2.90268L8.45592 3.54535C8.46794 3.55686 8.48154 3.56651 8.49516 3.57618C8.51703 3.5917 8.53897 3.60727 8.55458 3.63068L10.8686 7.10202L13.6572 5.42868ZM2.66667 12.6673H13.3333V14.0007H2.66667V12.6673Z" d="M13.6572 5.42868C13.8879 5.29002 14.1806 5.30402 14.3973 5.46468C14.6133 5.62602 14.7119 5.90068 14.6473 6.16202L13.3139 11.4954C13.2393 11.7927 12.9726 12.0007 12.6666 12.0007H3.33325C3.02725 12.0007 2.76058 11.792 2.68592 11.4954L1.35258 6.16202C1.28792 5.90068 1.38658 5.62602 1.60258 5.46468C1.81992 5.30468 2.11192 5.29068 2.34325 5.42868L5.13192 7.10202L7.44592 3.63068C7.46173 3.60697 7.48377 3.5913 7.50588 3.57559C7.5192 3.56612 7.53255 3.55663 7.54458 3.54535L6.90258 2.90268C6.77325 2.77335 6.77325 2.56068 6.90258 2.43135L7.76458 1.56935C7.89392 1.44002 8.10658 1.44002 8.23592 1.56935L9.09792 2.43135C9.22725 2.56068 9.22725 2.77335 9.09792 2.90268L8.45592 3.54535C8.46794 3.55686 8.48154 3.56651 8.49516 3.57618C8.51703 3.5917 8.53897 3.60727 8.55458 3.63068L10.8686 7.10202L13.6572 5.42868ZM2.66667 12.6673H13.3333V14.0007H2.66667V12.6673Z"
/> />
</Icon> </Icon>
@ -163,6 +159,8 @@ export function ScreenshareIcon(props: IconProps) {
> >
<path <path
fill="currentColor" fill="currentColor"
fill-rule="evenodd"
clip-rule="evenodd"
d="M2 4.5C2 3.397 2.897 2.5 4 2.5H20C21.103 2.5 22 3.397 22 4.5V15.5C22 16.604 21.103 17.5 20 17.5H13V19.5H17V21.5H7V19.5H11V17.5H4C2.897 17.5 2 16.604 2 15.5V4.5ZM13.2 14.3375V11.6C9.864 11.6 7.668 12.6625 6 15C6.672 11.6625 8.532 8.3375 13.2 7.6625V5L18 9.6625L13.2 14.3375Z" d="M2 4.5C2 3.397 2.897 2.5 4 2.5H20C21.103 2.5 22 3.397 22 4.5V15.5C22 16.604 21.103 17.5 20 17.5H13V19.5H17V21.5H7V19.5H11V17.5H4C2.897 17.5 2 16.604 2 15.5V4.5ZM13.2 14.3375V11.6C9.864 11.6 7.668 12.6625 6 15C6.672 11.6625 8.532 8.3375 13.2 7.6625V5L18 9.6625L13.2 14.3375Z"
/> />
</Icon> </Icon>
@ -200,24 +198,8 @@ export function Microphone(props: IconProps) {
className={classes(props.className, "vc-microphone")} className={classes(props.className, "vc-microphone")}
viewBox="0 0 24 24" viewBox="0 0 24 24"
> >
<path fillRule="evenodd" clipRule="evenodd" d="M14.99 11C14.99 12.66 13.66 14 12 14C10.34 14 9 12.66 9 11V5C9 3.34 10.34 2 12 2C13.66 2 15 3.34 15 5L14.99 11ZM12 16.1C14.76 16.1 17.3 14 17.3 11H19C19 14.42 16.28 17.24 13 17.72V21H11V17.72C7.72 17.23 5 14.41 5 11H6.7C6.7 14 9.24 16.1 12 16.1ZM12 4C11.2 4 11 4.66667 11 5V11C11 11.3333 11.2 12 12 12C12.8 12 13 11.3333 13 11V5C13 4.66667 12.8 4 12 4Z" fill="currentColor" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M14.99 11C14.99 12.66 13.66 14 12 14C10.34 14 9 12.66 9 11V5C9 3.34 10.34 2 12 2C13.66 2 15 3.34 15 5L14.99 11ZM12 16.1C14.76 16.1 17.3 14 17.3 11H19C19 14.42 16.28 17.24 13 17.72V21H11V17.72C7.72 17.23 5 14.41 5 11H6.7C6.7 14 9.24 16.1 12 16.1ZM12 4C11.2 4 11 4.66667 11 5V11C11 11.3333 11.2 12 12 12C12.8 12 13 11.3333 13 11V5C13 4.66667 12.8 4 12 4Z" fill="currentColor" />
<path fillRule="evenodd" clipRule="evenodd" d="M14.99 11C14.99 12.66 13.66 14 12 14C10.34 14 9 12.66 9 11V5C9 3.34 10.34 2 12 2C13.66 2 15 3.34 15 5L14.99 11ZM12 16.1C14.76 16.1 17.3 14 17.3 11H19C19 14.42 16.28 17.24 13 17.72V22H11V17.72C7.72 17.23 5 14.41 5 11H6.7C6.7 14 9.24 16.1 12 16.1Z" fill="currentColor" /> <path fill-rule="evenodd" clip-rule="evenodd" d="M14.99 11C14.99 12.66 13.66 14 12 14C10.34 14 9 12.66 9 11V5C9 3.34 10.34 2 12 2C13.66 2 15 3.34 15 5L14.99 11ZM12 16.1C14.76 16.1 17.3 14 17.3 11H19C19 14.42 16.28 17.24 13 17.72V22H11V17.72C7.72 17.23 5 14.41 5 11H6.7C6.7 14 9.24 16.1 12 16.1Z" fill="currentColor" />
</Icon >
);
}
export function CogWheel(props: IconProps) {
return (
<Icon
{...props}
className={classes(props.className, "vc-cog-wheel")}
viewBox="0 0 24 24"
>
<path
clipRule="evenodd"
fill="currentColor"
d="M19.738 10H22V14H19.739C19.498 14.931 19.1 15.798 18.565 16.564L20 18L18 20L16.565 18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069 19.498 8.203 19.099 7.436 18.564L6 20L4 18L5.436 16.564C4.901 15.799 4.502 14.932 4.262 14H2V10H4.262C4.502 9.068 4.9 8.202 5.436 7.436L4 6L6 4L7.436 5.436C8.202 4.9 9.068 4.502 10 4.262V2H14V4.261C14.932 4.502 15.797 4.9 16.565 5.435L18 3.999L20 5.999L18.564 7.436C19.099 8.202 19.498 9.069 19.738 10ZM12 16C14.2091 16 16 14.2091 16 12C16 9.79086 14.2091 8 12 8C9.79086 8 8 9.79086 8 12C8 14.2091 9.79086 16 12 16Z"
/>
</Icon > </Icon >
); );
} }

View File

@ -22,7 +22,6 @@ import * as DataStore from "@api/DataStore";
import { showNotice } from "@api/Notices"; import { showNotice } from "@api/Notices";
import { Settings, useSettings } from "@api/Settings"; import { Settings, useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { CogWheel, InfoIcon } from "@components/Icons";
import PluginModal from "@components/PluginSettings/PluginModal"; import PluginModal from "@components/PluginSettings/PluginModal";
import { AddonCard } from "@components/VencordSettings/AddonCard"; import { AddonCard } from "@components/VencordSettings/AddonCard";
import { SettingsTab } from "@components/VencordSettings/shared"; import { SettingsTab } from "@components/VencordSettings/shared";
@ -31,9 +30,9 @@ import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes, isObjectEmpty } from "@utils/misc"; import { classes, isObjectEmpty } from "@utils/misc";
import { openModalLazy } from "@utils/modal"; import { openModalLazy } from "@utils/modal";
import { useAwaiter } from "@utils/react"; import { LazyComponent, useAwaiter } from "@utils/react";
import { Plugin } from "@utils/types"; import { Plugin } from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByCode, findByPropsLazy } from "@webpack";
import { Alerts, Button, Card, Forms, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common"; import { Alerts, Button, Card, Forms, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common";
import Plugins from "~plugins"; import Plugins from "~plugins";
@ -47,6 +46,8 @@ const logger = new Logger("PluginSettings", "#a6d189");
const InputStyles = findByPropsLazy("inputDefault", "inputWrapper"); const InputStyles = findByPropsLazy("inputDefault", "inputWrapper");
const ButtonClasses = findByPropsLazy("button", "disabled", "enabled"); const ButtonClasses = findByPropsLazy("button", "disabled", "enabled");
const CogWheel = LazyComponent(() => findByCode("18.564C15.797 19.099 14.932 19.498 14 19.738V22H10V19.738C9.069"));
const InfoIcon = LazyComponent(() => findByCode("4.4408921e-16 C4.4771525,-1.77635684e-15 4.4408921e-16"));
function showErrorToast(message: string) { function showErrorToast(message: string) {
Toasts.show({ Toasts.show({
@ -162,7 +163,7 @@ export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, on
<button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}> <button role="switch" onClick={() => openModal()} className={classes(ButtonClasses.button, cl("info-button"))}>
{plugin.options && !isObjectEmpty(plugin.options) {plugin.options && !isObjectEmpty(plugin.options)
? <CogWheel /> ? <CogWheel />
: <InfoIcon />} : <InfoIcon width="24" height="24" />}
</button> </button>
} }
/> />

View File

@ -18,10 +18,8 @@
import { useSettings } from "@api/Settings"; import { useSettings } from "@api/Settings";
import { classNameFactory } from "@api/Styles"; import { classNameFactory } from "@api/Styles";
import { ErrorCard } from "@components/ErrorCard";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Link } from "@components/Link"; import { Link } from "@components/Link";
import { IsFirefox } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { showItemInFolder } from "@utils/native"; import { showItemInFolder } from "@utils/native";
@ -251,14 +249,12 @@ function ThemesTab() {
> >
Load missing Themes Load missing Themes
</Button> </Button>
{!IsFirefox && (
<Button <Button
onClick={() => VencordNative.quickCss.openEditor()} onClick={() => VencordNative.quickCss.openEditor()}
size={Button.Sizes.SMALL} size={Button.Sizes.SMALL}
> >
Edit QuickCSS Edit QuickCSS
</Button> </Button>
)}
</> </>
</Card> </Card>
@ -320,15 +316,6 @@ function ThemesTab() {
return ( return (
<SettingsTab title="Themes"> <SettingsTab title="Themes">
{IsFirefox && (
<ErrorCard>
<Forms.FormTitle tag="h5">Warning</Forms.FormTitle>
<Forms.FormText>
You are using Firefox. Expect the vast majority of themes to not work.
If this is a problem, use a chromium browser or Discord Desktop / Vesktop.
</Forms.FormText>
</ErrorCard>
)}
<TabBar <TabBar
type="top" type="top"
look="brand" look="brand"

View File

@ -21,7 +21,6 @@ import { Settings, 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 { ErrorCard } from "@components/ErrorCard"; import { ErrorCard } from "@components/ErrorCard";
import { IsFirefox } from "@utils/constants";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { identity } from "@utils/misc"; import { identity } from "@utils/misc";
import { relaunch, showItemInFolder } from "@utils/native"; import { relaunch, showItemInFolder } from "@utils/native";
@ -110,14 +109,12 @@ function VencordSettings() {
Restart Client Restart Client
</Button> </Button>
)} )}
{!IsFirefox && (
<Button <Button
onClick={() => VencordNative.quickCss.openEditor()} onClick={() => VencordNative.quickCss.openEditor()}
size={Button.Sizes.SMALL} size={Button.Sizes.SMALL}
disabled={settingsDir === "Loading..."}> disabled={settingsDir === "Loading..."}>
Open QuickCSS File Open QuickCSS File
</Button> </Button>
)}
{!IS_WEB && ( {!IS_WEB && (
<Button <Button
onClick={() => showItemInFolder(settingsDir)} onClick={() => showItemInFolder(settingsDir)}
@ -260,11 +257,7 @@ function DonateCard({ image }: DonateCardProps) {
src={image} src={image}
alt="" alt=""
height={128} height={128}
style={{ style={{ marginLeft: "auto", transform: image === DEFAULT_DONATE_IMAGE ? "rotate(10deg)" : "" }}
imageRendering: image === SHIGGY_DONATE_IMAGE ? "pixelated" : void 0,
marginLeft: "auto",
transform: image === DEFAULT_DONATE_IMAGE ? "rotate(10deg)" : void 0
}}
/> />
</Card> </Card>
); );

1
src/globals.d.ts vendored
View File

@ -33,7 +33,6 @@ declare global {
* replace: `${IS_WEB}?foo:bar` * replace: `${IS_WEB}?foo:bar`
*/ */
export var IS_WEB: boolean; export var IS_WEB: boolean;
export var IS_EXTENSION: boolean;
export var IS_DEV: boolean; export var IS_DEV: boolean;
export var IS_STANDALONE: boolean; export var IS_STANDALONE: boolean;
export var IS_UPDATER_DISABLED: boolean; export var IS_UPDATER_DISABLED: boolean;

View File

@ -27,7 +27,7 @@ import { mkdirSync, readFileSync, watch } from "fs";
import { open, readdir, readFile, writeFile } from "fs/promises"; import { open, readdir, readFile, writeFile } from "fs/promises";
import { join, normalize } from "path"; import { join, normalize } from "path";
import monacoHtml from "~fileContent/monacoWin.html;base64"; import monacoHtml from "~fileContent/../components/monacoWin.html;base64";
import { getThemeInfo, stripBOM, UserThemeHeader } from "./themes"; import { getThemeInfo, stripBOM, UserThemeHeader } from "./themes";
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE, THEMES_DIR } from "./utils/constants"; import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE, THEMES_DIR } from "./utils/constants";

View File

@ -23,7 +23,7 @@ import { Clipboard, Toasts } from "@webpack/common";
export default definePlugin({ export default definePlugin({
name: "BetterRoleDot", name: "BetterRoleDot",
authors: [Devs.Ven, Devs.AutumnVN], authors: [Devs.Ven],
description: description:
"Copy role colour on RoleDot (accessibility setting) click. Also allows using both RoleDot and coloured names simultaneously", "Copy role colour on RoleDot (accessibility setting) click. Also allows using both RoleDot and coloured names simultaneously",
@ -43,23 +43,6 @@ export default definePlugin({
match: /"(?:username|dot)"===\i(?!\.\i)/g, match: /"(?:username|dot)"===\i(?!\.\i)/g,
replace: "true", replace: "true",
}, },
},
{
find: ".ADD_ROLE_A11Y_LABEL",
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
replacement: {
match: /"dot"===\i/,
replace: "true"
}
},
{
find: ".roleVerifiedIcon",
predicate: () => Settings.plugins.BetterRoleDot.copyRoleColorInProfilePopout && !Settings.plugins.BetterRoleDot.bothStyles,
replacement: {
match: /"dot"===\i/,
replace: "true"
}
} }
], ],
@ -67,14 +50,7 @@ export default definePlugin({
bothStyles: { bothStyles: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
description: "Show both role dot and coloured names", description: "Show both role dot and coloured names",
restartNeeded: true,
default: false, default: false,
},
copyRoleColorInProfilePopout: {
type: OptionType.BOOLEAN,
description: "Allow click on role dot in profile popout to copy role color",
restartNeeded: true,
default: false
} }
}, },

View File

@ -127,8 +127,6 @@ export const defaultRules = [
"redircnt@yandex.*", "redircnt@yandex.*",
"feature@youtube.com", "feature@youtube.com",
"kw@youtube.com", "kw@youtube.com",
"si@youtube.com",
"pp@youtube.com",
"si@youtu.be", "si@youtu.be",
"wt_zmc", "wt_zmc",
"utm_source", "utm_source",

View File

@ -19,7 +19,7 @@
import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents"; import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
import { definePluginSettings, Settings } from "@api/Settings"; import { definePluginSettings, Settings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies"; import { ApngBlendOp, ApngDisposeOp, getGifEncoder, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord"; import { getCurrentGuild } from "@utils/discord";
import { proxyLazy } from "@utils/lazy"; import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
@ -27,7 +27,6 @@ import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
import { ChannelStore, EmojiStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common"; import { ChannelStore, EmojiStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common";
import type { Message } from "discord-types/general"; import type { Message } from "discord-types/general";
import { applyPalette, GIFEncoder, quantize } from "gifenc";
import type { ReactElement, ReactNode } from "react"; import type { ReactElement, ReactNode } from "react";
const DRAFT_TYPE = 0; const DRAFT_TYPE = 0;
@ -103,11 +102,6 @@ interface StickerPack {
stickers: Sticker[]; stickers: Sticker[];
} }
const enum FakeNoticeType {
Sticker,
Emoji
}
const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/; const fakeNitroEmojiRegex = /\/emojis\/(\d+?)\.(png|webp|gif)/;
const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./; const fakeNitroStickerRegex = /\/stickers\/(\d+?)\./;
const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/; const fakeNitroGifStickerRegex = /\/attachments\/\d+?\/\d+?\/(\d+?)\.gif/;
@ -311,24 +305,16 @@ export default definePlugin({
}, },
{ {
match: /(emojiSection.{0,50}description:)(\i)(?<=(\i)\.sticker,.+?)(?=,)/, match: /(emojiSection.{0,50}description:)(\i)(?<=(\i)\.sticker,.+?)(?=,)/,
replace: (_, rest, reactNode, props) => `${rest}$self.addFakeNotice(${FakeNoticeType.Sticker},${reactNode},!!${props}.renderableSticker?.fake)` replace: (_, rest, reactNode, props) => `${rest}$self.addFakeNotice("STICKER",${reactNode},!!${props}.renderableSticker?.fake)`
} }
] ]
}, },
{ {
find: ".EMOJI_UPSELL_POPOUT_MORE_EMOJIS_OPENED,", find: ".Messages.EMOJI_POPOUT_PREMIUM_JOINED_GUILD_DESCRIPTION",
predicate: () => settings.store.transformEmojis, predicate: () => settings.store.transformEmojis,
replacement: { replacement: {
match: /isDiscoverable:\i,shouldHideRoleSubscriptionCTA:\i,(?<=(\i)=\i\.node.+?)/, match: /((\i)=\i\.node,\i=\i\.expressionSourceGuild)(.+?return )(.{0,450}Messages\.EMOJI_POPOUT_PREMIUM_JOINED_GUILD_DESCRIPTION.+?}\))/,
replace: (m, node) => `${m}fakeNitroNode:${node},` replace: (_, rest1, node, rest2, reactNode) => `${rest1},fakeNitroNode=${node}${rest2}$self.addFakeNotice("EMOJI",${reactNode},fakeNitroNode.fake)`
}
},
{
find: ".Messages.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION",
predicate: () => settings.store.transformEmojis,
replacement: {
match: /(?<=\.Messages\.EMOJI_POPOUT_ADDED_PACK_DESCRIPTION.+?return ).{0,1200}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?(?=}\()/,
replace: reactNode => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!arguments[0]?.fakeNitroNode?.fake)`
} }
} }
], ],
@ -624,18 +610,18 @@ export default definePlugin({
return link.target && fakeNitroEmojiRegex.test(link.target); return link.target && fakeNitroEmojiRegex.test(link.target);
}, },
addFakeNotice(type: FakeNoticeType, node: Array<ReactNode>, fake: boolean) { addFakeNotice(type: "STICKER" | "EMOJI", node: Array<ReactNode>, fake: boolean) {
if (!fake) return node; if (!fake) return node;
node = Array.isArray(node) ? node : [node]; node = Array.isArray(node) ? node : [node];
switch (type) { switch (type) {
case FakeNoticeType.Sticker: { case "STICKER": {
node.push(" This is a FakeNitro sticker and renders like a real sticker only for you. Appears as a link to non-plugin users."); node.push(" This is a FakeNitro sticker and renders like a real sticker only for you. Appears as a link to non-plugin users.");
return node; return node;
} }
case FakeNoticeType.Emoji: { case "EMOJI": {
node.push(" This is a FakeNitro emoji and renders like a real emoji only for you. Appears as a link to non-plugin users."); node.push(" This is a FakeNitro emoji and renders like a real emoji only for you. Appears as a link to non-plugin users.");
return node; return node;
@ -664,11 +650,15 @@ export default definePlugin({
}, },
async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) { async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) {
const { parseURL } = importApngJs(); const [{ parseURL }, {
GIFEncoder,
quantize,
applyPalette
}] = await Promise.all([importApngJs(), getGifEncoder()]);
const { frames, width, height } = await parseURL(stickerLink); const { frames, width, height } = await parseURL(stickerLink);
const gif = GIFEncoder(); const gif = new GIFEncoder();
const resolution = Settings.plugins.FakeNitro.stickerSize; const resolution = Settings.plugins.FakeNitro.stickerSize;
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");

View File

@ -60,7 +60,7 @@ interface Instance {
} }
const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchHeader", "searchInput"); const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchHeader", "gutterSize");
export const settings = definePluginSettings({ export const settings = definePluginSettings({
searchOption: { searchOption: {

56
src/plugins/fixInbox.tsx Normal file
View File

@ -0,0 +1,56 @@
/*
* 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 { Devs } from "@utils/constants";
import definePlugin from "@utils/types";
import { Forms } from "@webpack/common";
export default definePlugin({
name: "FixInbox",
description: "Fixes the Unreads Inbox from crashing Discord when you're in lots of guilds.",
authors: [Devs.Megu],
patches: [{
find: "INBOX_OPEN:function",
replacement: {
// This function normally dispatches a subscribe event to every guild.
// this is badbadbadbadbad so we just get rid of it.
match: /INBOX_OPEN:function.+?\{/,
replace: "$&return true;"
}
}],
settingsAboutComponent() {
return (
<Forms.FormSection>
<Forms.FormTitle tag="h3">What's the problem?</Forms.FormTitle>
<Forms.FormText style={{ marginBottom: 8 }}>
By default, Discord emits a GUILD_SUBSCRIPTIONS event for every guild you're in.
When you're in a lot of guilds, this can cause the gateway to ratelimit you.
This causes the client to crash and get stuck in an infinite ratelimit loop as it tries to reconnect.
</Forms.FormText>
<Forms.FormTitle tag="h3">How does it work?</Forms.FormTitle>
<Forms.FormText>
This plugin works by stopping the client from sending GUILD_SUBSCRIPTIONS events to the gateway when you open the unreads inbox.
This means that not all unreads will be shown, instead only already-subscribed guilds' unreads will be shown, but your client won't crash anymore.
</Forms.FormText>
</Forms.FormSection>
);
}
});

View File

@ -1,6 +1,6 @@
/* /*
* Vencord, a modification for Discord's desktop app * Vencord, a modification for Discord's desktop app
* Copyright (c) 2023 Vendicated and contributors * Copyright (c) 2022 Vendicated and contributors
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -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 { ApplicationCommandInputType, ApplicationCommandOptionType, findOption, sendBotMessage } from "@api/Commands"; import { ApplicationCommandInputType, sendBotMessage } from "@api/Commands";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
@ -35,29 +35,14 @@ 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.BOT,
options: [{ execute: async (_, ctx) => {
name: "Uses", if (!UserStore.getCurrentUser().phone)
description: "How many uses?",
choices: [
{ label: "1", name: "1", value: "1" },
{ label: "5", name: "5", value: "5" }
],
required: false,
type: ApplicationCommandOptionType.INTEGER
}],
execute: async (args, ctx) => {
const uses = findOption<number>(args, "Uses", 5);
if (uses === 1 && !UserStore.getCurrentUser().phone)
return sendBotMessage(ctx.channel.id, { return sendBotMessage(ctx.channel.id, {
content: "You need to have a phone number connected to your account to create a friend invite with 1 use!" content: "You need to have a phone number connected to your account to create a friend invite!"
}); });
let invite: any;
if (uses === 1) {
const random = uuid.v4(); const random = uuid.v4();
const { body: { invite_suggestions } } = await RestAPI.post({ const invite = await RestAPI.post({
url: "/friend-finder/find-friends", url: "/friend-finder/find-friends",
body: { body: {
modified_contacts: { modified_contacts: {
@ -65,17 +50,15 @@ export default definePlugin({
}, },
phone_contact_methods_count: 1 phone_contact_methods_count: 1
} }
}); }).then(res =>
invite = await FriendInvites.createFriendInvite({ FriendInvites.createFriendInvite({
code: invite_suggestions[0][3], code: res.body.invite_suggestions[0][3],
recipient_phone_number_or_email: random, recipient_phone_number_or_email: random,
contact_visibility: 1, contact_visibility: 1,
filter_visibilities: [], filter_visibilities: [],
filtered_invite_suggestions_index: 1 filtered_invite_suggestions_index: 1
}); })
} else { );
invite = await FriendInvites.createFriendInvite();
}
sendBotMessage(ctx.channel.id, { sendBotMessage(ctx.channel.id, {
content: ` content: `
@ -84,7 +67,7 @@ export default definePlugin({
Max uses: \`${invite.max_uses}\` Max uses: \`${invite.max_uses}\`
`.trim().replace(/\s+/g, " ") `.trim().replace(/\s+/g, " ")
}); });
} },
}, },
{ {
name: "view friend invites", name: "view friend invites",
@ -112,7 +95,7 @@ export default definePlugin({
execute: async (_, ctx) => { execute: async (_, ctx) => {
await FriendInvites.revokeFriendInvites(); await FriendInvites.revokeFriendInvites();
sendBotMessage(ctx.channel.id, { return void sendBotMessage(ctx.channel.id, {
content: "All friend invites have been revoked." content: "All friend invites have been revoked."
}); });
}, },

View File

@ -185,7 +185,7 @@ const settings = definePluginSettings({
export default definePlugin({ export default definePlugin({
name: "MoreUserTags", name: "MoreUserTags",
description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)", description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)",
authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN], authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias],
settings, settings,
patches: [ patches: [
// add tags to the tag list // add tags to the tag list
@ -232,8 +232,9 @@ export default definePlugin({
{ {
find: ".renderBot=function(){", find: ".renderBot=function(){",
replacement: { replacement: {
match: /\.BOT;return null!=(\i)&&.{0,10}\?(.{0,50})\.botTag,type:\i/, match: /this.props.user;return null!=(\i)&&.{0,10}\?(.{0,50})\.botTag/,
replace: ".BOT;var type=$self.getTag({...this.props,origType:$1.bot?0:null,location:'not-chat'});return type!==null?$2.botTag,type" replace: "this.props.user;var type=$self.getTag({...this.props,origType:$1.bot?0:null,location:'not-chat'});\
return type!==null?$2.botTag,type"
} }
}, },
// pass channel id down props to be used in profiles // pass channel id down props to be used in profiles

View File

@ -18,10 +18,10 @@
import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, CommandContext, findOption, sendBotMessage } from "@api/Commands"; import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, CommandContext, findOption, sendBotMessage } from "@api/Commands";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getGifEncoder } from "@utils/dependencies";
import { makeLazy } from "@utils/lazy"; import { makeLazy } from "@utils/lazy";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { findByCodeLazy, findByPropsLazy } from "@webpack";
import { applyPalette, GIFEncoder, quantize } from "gifenc";
const DRAFT_TYPE = 0; const DRAFT_TYPE = 0;
const DEFAULT_DELAY = 20; const DEFAULT_DELAY = 20;
@ -124,6 +124,7 @@ export default definePlugin({
} }
], ],
execute: async (opts, cmdCtx) => { execute: async (opts, cmdCtx) => {
const { GIFEncoder, quantize, applyPalette } = await getGifEncoder();
const frames = await getFrames(); const frames = await getFrames();
const noServerPfp = findOption(opts, "no-server-pfp", false); const noServerPfp = findOption(opts, "no-server-pfp", false);
@ -142,7 +143,7 @@ export default definePlugin({
const delay = findOption(opts, "delay", DEFAULT_DELAY); const delay = findOption(opts, "delay", DEFAULT_DELAY);
const resolution = findOption(opts, "resolution", DEFAULT_RESOLUTION); const resolution = findOption(opts, "resolution", DEFAULT_RESOLUTION);
const gif = GIFEncoder(); const gif = new GIFEncoder();
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
canvas.width = canvas.height = resolution; canvas.width = canvas.height = resolution;

View File

@ -28,8 +28,8 @@ export default function ReviewBadge(badge: Badge) {
{({ onMouseEnter, onMouseLeave }) => ( {({ onMouseEnter, onMouseLeave }) => (
<img <img
className={cl("badge")} className={cl("badge")}
width="22px" width="24px"
height="22px" height="24px"
onMouseEnter={onMouseEnter} onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave} onMouseLeave={onMouseLeave}
src={badge.icon} src={badge.icon}

View File

@ -20,7 +20,7 @@ import { openUserProfile } from "@utils/discord";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import { LazyComponent } from "@utils/react"; import { LazyComponent } from "@utils/react";
import { filters, findBulk } from "@webpack"; import { filters, findBulk } from "@webpack";
import { Alerts, moment, Parser, Timestamp, UserStore } from "@webpack/common"; import { Alerts, moment, Timestamp, UserStore } from "@webpack/common";
import { Review, ReviewType } from "../entities"; import { Review, ReviewType } from "../entities";
import { deleteReview, reportReview } from "../reviewDbApi"; import { deleteReview, reportReview } from "../reviewDbApi";
@ -30,12 +30,12 @@ import { DeleteButton, ReportButton } from "./MessageButton";
import ReviewBadge from "./ReviewBadge"; import ReviewBadge from "./ReviewBadge";
export default LazyComponent(() => { export default LazyComponent(() => {
// this is terrible, blame ven // this is terrible, blame mantika
const p = filters.byProps; const p = filters.byProps;
const [ const [
{ cozyMessage, buttons, message, buttonsInner, groupStart }, { cozyMessage, buttons, message, buttonsInner, groupStart },
{ container, isHeader }, { container, isHeader },
{ avatar, clickable, username, wrapper, cozy }, { avatar, clickable, username, messageContent, wrapper, cozy },
buttonClasses, buttonClasses,
botTag botTag
] = findBulk( ] = findBulk(
@ -124,10 +124,12 @@ export default LazyComponent(() => {
</Timestamp>) </Timestamp>)
} }
<div className={cl("review-comment")}> <p
{Parser.parseGuildEventDescription(review.comment)} className={classes(messageContent)}
</div> style={{ fontSize: 15, marginTop: 4, color: "var(--text-normal)" }}
>
{review.comment}
</p>
{review.id !== 0 && ( {review.id !== 0 && (
<div className={classes(container, isHeader, buttons)} style={{ <div className={classes(container, isHeader, buttons)} style={{
padding: "0px", padding: "0px",

View File

@ -16,9 +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 { LazyComponent, useAwaiter, useForceUpdater } from "@utils/react"; import { classes } from "@utils/misc";
import { find, findByPropsLazy } from "@webpack"; import { useAwaiter, useForceUpdater } from "@utils/react";
import { Forms, React, RelationshipStore, useRef, UserStore } from "@webpack/common"; import { findByPropsLazy } from "@webpack";
import { Forms, React, RelationshipStore, UserStore } from "@webpack/common";
import type { KeyboardEvent } from "react";
import { Review } from "../entities"; import { Review } from "../entities";
import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "../reviewDbApi"; import { addReview, getReviews, Response, REVIEWS_PER_PAGE } from "../reviewDbApi";
@ -26,12 +28,7 @@ import { settings } from "../settings";
import { authorize, cl, showToast } from "../utils"; import { authorize, cl, showToast } from "../utils";
import ReviewComponent from "./ReviewComponent"; import ReviewComponent from "./ReviewComponent";
const Classes = findByPropsLazy("inputDefault", "editable");
const Editor = findByPropsLazy("start", "end", "addMark");
const Transform = findByPropsLazy("unwrapNodes");
const InputTypes = findByPropsLazy("VOICE_CHANNEL_STATUS", "SIDEBAR");
const InputComponent = LazyComponent(() => find(m => m?.type?.render?.toString().includes("CHANNEL_TEXT_AREA).AnalyticsLocationProvider")));
interface UserProps { interface UserProps {
discordId: string; discordId: string;
@ -116,38 +113,34 @@ function ReviewList({ refetch, reviews, hideOwnReview }: { refetch(): void; revi
); );
} }
export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: { discordId: string, name: string; isAuthor: boolean; refetch(): void; }) { export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: { discordId: string, name: string; isAuthor: boolean; refetch(): void; }) {
const { token } = settings.store; const { token } = settings.store;
const editorRef = useRef<any>(null);
const inputType = InputTypes.FORM;
inputType.disableAutoFocus = true;
const channel = { function onKeyPress({ key, target }: KeyboardEvent<HTMLTextAreaElement>) {
flags_: 256, if (key === "Enter") {
guild_id_: null, addReview({
id: "0", userid: discordId,
getGuildId: () => null, comment: (target as HTMLInputElement).value,
isPrivate: () => true, star: -1
isActiveThread: () => false, }).then(res => {
isArchivedLockedThread: () => false, if (res?.success) {
isDM: () => true, (target as HTMLInputElement).value = ""; // clear the input
roles: { "0": { permissions: 0n } }, refetch();
getRecipientId: () => "0", } else if (res?.message) {
hasFlag: () => false, showToast(res.message);
}; }
});
}
}
return ( return (
<> <textarea
<div onClick={() => { className={classes(Classes.inputDefault, "enter-comment", cl("input"))}
if (!token) { onKeyDownCapture={e => {
showToast("Opening authorization window..."); if (e.key === "Enter") {
authorize(); e.preventDefault(); // prevent newlines
} }
}}> }}
<InputComponent
className={cl("input")}
channel={channel}
placeholder={ placeholder={
!token !token
? "You need to authorize to review users!" ? "You need to authorize to review users!"
@ -155,43 +148,13 @@ export function ReviewsInputComponent({ discordId, isAuthor, refetch, name }: {
? `Update review for @${name}` ? `Update review for @${name}`
: `Review @${name}` : `Review @${name}`
} }
type={inputType} onKeyDown={onKeyPress}
disableThemedBackground={true} onClick={() => {
setEditorRef={ref => editorRef.current = ref} if (!token) {
textValue="" showToast("Opening authorization window...");
onSubmit={ authorize();
async res => {
const response = await addReview({
userid: discordId,
comment: res.value,
});
if (response?.success) {
refetch();
const slateEditor = editorRef.current.ref.current.getSlateEditor();
// clear editor
Transform.delete(slateEditor, {
at: {
anchor: Editor.start(slateEditor, []),
focus: Editor.end(slateEditor, []),
}
});
} else if (response?.message) {
showToast(response.message);
}
// even tho we need to return this, it doesnt do anything
return {
shouldClear: false,
shouldRefocus: true,
};
}
} }
}}
/> />
</div>
</>
); );
} }

View File

@ -29,13 +29,6 @@ export const enum ReviewType {
System = 3 System = 3
} }
export const enum NotificationType {
Info = 0,
Ban = 1,
Unban = 2,
Warning = 3
}
export interface Badge { export interface Badge {
name: string; name: string;
description: string; description: string;
@ -52,13 +45,6 @@ export interface BanInfo {
banEndDate: number; banEndDate: number;
} }
export interface Notification {
id: number;
title: string;
content: string;
type: NotificationType;
}
export interface ReviewDBUser { export interface ReviewDBUser {
ID: number; ID: number;
discordID: string; discordID: string;
@ -68,7 +54,6 @@ export interface ReviewDBUser {
warningCount: number; warningCount: number;
badges: any[]; badges: any[];
banInfo: BanInfo | null; banInfo: BanInfo | null;
notification: Notification | null;
lastReviewID: number; lastReviewID: number;
type: UserType; type: UserType;
} }

View File

@ -24,13 +24,13 @@ import ExpandableHeader from "@components/ExpandableHeader";
import { OpenExternalIcon } from "@components/Icons"; import { OpenExternalIcon } from "@components/Icons";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { Alerts, Menu, Parser, useState } from "@webpack/common"; import { Alerts, Menu, useState } from "@webpack/common";
import { Guild, User } from "discord-types/general"; import { Guild, User } from "discord-types/general";
import { openReviewsModal } from "./components/ReviewModal"; import { openReviewsModal } from "./components/ReviewModal";
import ReviewsView from "./components/ReviewsView"; import ReviewsView from "./components/ReviewsView";
import { NotificationType } from "./entities"; import { UserType } from "./entities";
import { getCurrentUserInfo, readNotification } from "./reviewDbApi"; import { getCurrentUserInfo } from "./reviewDbApi";
import { settings } from "./settings"; import { settings } from "./settings";
import { showToast } from "./utils"; import { showToast } from "./utils";
@ -78,8 +78,26 @@ export default definePlugin({
addContextMenuPatch("guild-header-popout", guildPopoutPatch); addContextMenuPatch("guild-header-popout", guildPopoutPatch);
if (user.notification) { if (user.banInfo) {
const props = user.notification.type === NotificationType.Ban ? { const endDate = new Date(user.banInfo.banEndDate);
if (endDate.getTime() > Date.now() && (s.user?.banInfo?.banEndDate ?? 0) < endDate.getTime()) {
Alerts.show({
title: "You have been banned from ReviewDB",
body: (
<>
<p>
You are banned from ReviewDB {
user.type === UserType.Banned
? "permanently"
: "until " + endDate.toLocaleString()
}
</p>
{user.banInfo.reviewContent && (
<p>Offending Review: {user.banInfo.reviewContent}</p>
)}
<p>Continued offenses will result in a permanent ban.</p>
</>
),
cancelText: "Appeal", cancelText: "Appeal",
confirmText: "Ok", confirmText: "Ok",
onCancel: () => onCancel: () =>
@ -90,21 +108,10 @@ export default definePlugin({
page: "dashboard/appeal" page: "dashboard/appeal"
}) })
) )
} : {};
Alerts.show({
title: user.notification.title,
body: (
Parser.parse(
user.notification.content,
false
)
),
...props
}); });
readNotification(user.notification.id);
} }
}
s.user = user; s.user = user;
}, 4000); }, 4000);
}, },

View File

@ -140,12 +140,3 @@ export function getCurrentUserInfo(token: string): Promise<ReviewDBUser> {
method: "POST", method: "POST",
}).then(r => r.json()); }).then(r => r.json());
} }
export function readNotification(id: number) {
return fetch(API_URL + `/api/reviewdb/notifications?id=${id}`, {
method: "PATCH",
headers: {
"Authorization": settings.store.token || "",
},
});
}

View File

@ -14,16 +14,7 @@
overflow: hidden; overflow: hidden;
background: transparent; background: transparent;
border: 1px solid var(--profile-message-input-border-color); border: 1px solid var(--profile-message-input-border-color);
} font-size: 14px;
.vc-rdb-modal-footer > div {
width: 100%;
margin: 6px 16px;
}
/* When input becomes disabled(while sending review), input adds unneccesary padding to left, this prevents it */
.vc-rdb-input > div > div {
padding-left: 0 !important;
} }
.vc-rdb-placeholder { .vc-rdb-placeholder {
@ -33,14 +24,15 @@
color: var(--text-muted); color: var(--text-muted);
} }
.vc-rdb-input * {
font-size: 14px;
}
.vc-rdb-modal-footer { .vc-rdb-modal-footer {
padding: 0; padding: 0;
} }
.vc-rdb-modal-footer > div {
width: 100%;
margin: 6px 16px;
}
.vc-rdb-modal-footer .vc-rdb-input { .vc-rdb-modal-footer .vc-rdb-input {
margin-bottom: 0; margin-bottom: 0;
background: var(--input-background); background: var(--input-background);
@ -57,20 +49,3 @@
.vc-rdb-modal-reviews { .vc-rdb-modal-reviews {
margin-top: 16px; margin-top: 16px;
} }
.vc-rdb-review {
margin-top: 8px;
margin-bottom: 8px;
}
.vc-rdb-review-comment img {
vertical-align: text-top;
}
.vc-rdb-review-comment {
overflow-y: hidden;
margin-top: 1px;
margin-bottom: 8px;
color: var(--text-normal);
font-size: 15px;
}

View File

@ -0,0 +1,23 @@
.vc-startuptimings-server-trace {
color: var(--header-primary);
user-select: text;
}
.vc-startuptimings-grid {
color: var(--header-primary);
display: grid;
gap: 2px 10px;
user-select: text;
}
.vc-startuptimings-4-cols {
grid-template-columns: repeat(3, auto) 1fr;
}
.vc-startuptimings-3-cols {
grid-template-columns: repeat(2, auto) 1fr;
}
.vc-startuptimings-2-cols {
grid-template-columns: auto 1fr;
}

View File

@ -16,11 +16,35 @@
* 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 "./StartupTimingPage.css";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex"; import { Flex } from "@components/Flex";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { Forms, React } from "@webpack/common"; import { Forms, React } from "@webpack/common";
export const cl = classNameFactory("vc-startuptimings-");
interface ITTITrackerEvent {
emoji: string;
name: string;
start: number;
end: number;
hasData(): boolean;
}
interface ITTITracker {
serializeTTITracker(): Record<string, string | number | boolean | null | undefined>;
[event: string]: ITTITrackerEvent | string | boolean | null | any;
}
/** Time-To-Interactive Tracker */
const TTITracker: ITTITracker = findByPropsLazy("serializeTTITracker");
interface AppStartPerformance { interface AppStartPerformance {
prefix: string; prefix: string;
logs: Log[]; logs: Log[];
@ -91,11 +115,11 @@ function TimingSection({ title, logs, traceEnd }: TimingSectionProps) {
<Forms.FormSection title={title} tag="h1"> <Forms.FormSection title={title} tag="h1">
<code> <code>
{traceEnd && ( {traceEnd && (
<div style={{ color: "var(--header-primary)", marginBottom: 5, userSelect: "text" }}> <div className={cl("server-trace")} style={{ marginBottom: 5 }}>
Trace ended at: {(new Date(traceEnd)).toTimeString()} Trace ended at: {(new Date(traceEnd)).toTimeString()}
</div> </div>
)} )}
<div style={{ color: "var(--header-primary)", display: "grid", gridTemplateColumns: "repeat(3, auto) 1fr", gap: "2px 10px", userSelect: "text" }}> <div className={classes(cl("grid"), cl("4-cols"))}>
<span>Start</span> <span>Start</span>
<span>Interval</span> <span>Interval</span>
<span>Delta</span> <span>Delta</span>
@ -119,7 +143,7 @@ function ServerTrace({ trace }: ServerTraceProps) {
return ( return (
<Forms.FormSection title="Server Trace" tag="h2"> <Forms.FormSection title="Server Trace" tag="h2">
<code> <code>
<Flex flexDirection="column" style={{ color: "var(--header-primary)", gap: 5, userSelect: "text" }}> <Flex flexDirection="column" className={cl("server-trace")} style={{ gap: 5 }}>
{lines.map(line => ( {lines.map(line => (
<span>{line}</span> <span>{line}</span>
))} ))}
@ -129,11 +153,70 @@ function ServerTrace({ trace }: ServerTraceProps) {
); );
} }
function TTIAnalytics() {
const analytics = TTITracker.serializeTTITracker();
const filteredAnalytics = Object.entries(analytics).filter(([key, value]) => value != null && !/_start$|_end$/.test(key));
return (
<ErrorBoundary>
<Forms.FormSection title="TTI Analytics" tag="h2">
<code>
<div className={classes(cl("grid"), cl("2-cols"))}>
{filteredAnalytics.map(([key, value]) => (
<React.Fragment>
<span><pre>{key}</pre></span>
<span><pre>{`${value}`}</pre></span>
</React.Fragment>
))}
</div>
</code>
</Forms.FormSection>
</ErrorBoundary>
);
}
interface TTITimingsProps {
records: [string, ITTITrackerEvent][];
title: string;
type: "registered" | "unregistered";
}
function TTITimings({ records, title, type }: TTITimingsProps) {
const isRegistered = type === "registered";
return (
<ErrorBoundary>
<Forms.FormSection title={title} tag="h2">
<code>
<div className={classes(cl("grid"), cl(isRegistered ? "3-cols" : "2-cols"))}>
{isRegistered && <span>Duration</span>}
<span>Key</span>
<span style={{ marginBottom: 5 }}>Event</span>
{records.map(([key, event]) => (
<React.Fragment key={key}>
{isRegistered && <span><pre>{event.end - event.start}ms</pre></span>}
<span><pre>{key}</pre></span>
<span><pre>{event.emoji} {event.name}</pre></span>
</React.Fragment>
))}
</div>
</code>
</Forms.FormSection>
</ErrorBoundary>
);
}
function StartupTimingPage() { function StartupTimingPage() {
if (!AppStartPerformance?.logs) return <div>Loading...</div>; if (!AppStartPerformance?.logs) return <div>Loading...</div>;
const serverTrace = AppStartPerformance.logGroups.find(g => g.serverTrace)?.serverTrace; const serverTrace = AppStartPerformance.logGroups.find(g => g.serverTrace)?.serverTrace;
const registeredTTITimings: [string, ITTITrackerEvent][] = (Object.entries(TTITracker))
.filter(([, value]) => value?.hasData?.());
const unregisteredTTITimings: [string, ITTITrackerEvent][] = (Object.entries(TTITracker))
.filter(([, value]) => value?.hasData && !value.hasData());
return ( return (
<React.Fragment> <React.Fragment>
<TimingSection <TimingSection
@ -141,9 +224,26 @@ function StartupTimingPage() {
logs={AppStartPerformance.logs} logs={AppStartPerformance.logs}
traceEnd={AppStartPerformance.endTime_} traceEnd={AppStartPerformance.endTime_}
/> />
{/* Lazy Divider */} <Forms.FormDivider className={classes(Margins.top16, Margins.bottom16)} />
<div style={{ marginTop: 5 }}>&nbsp;</div>
{serverTrace && <ServerTrace trace={serverTrace} />} {serverTrace && <ServerTrace trace={serverTrace} />}
<Forms.FormDivider className={classes(Margins.top16, Margins.bottom16)} />
<TTIAnalytics />
<Forms.FormDivider className={classes(Margins.top16, Margins.bottom16)} />
<TTITimings
title="Registered TTI Timings"
records={registeredTTITimings}
type="registered"
/>
<Forms.FormDivider className={classes(Margins.top16, Margins.bottom16)} />
<TTITimings
title="Unregistered TTI Timings"
records={unregisteredTTITimings}
type="unregistered"
/>
</React.Fragment> </React.Fragment>
); );
} }

View File

@ -17,7 +17,7 @@
*/ */
import { DataStore } from "@api/index"; import { DataStore } from "@api/index";
import { Devs, IsFirefox, SUPPORT_CHANNEL_ID } from "@utils/constants"; import { Devs, SUPPORT_CHANNEL_ID } from "@utils/constants";
import { isPluginDev } from "@utils/misc"; import { isPluginDev } from "@utils/misc";
import { makeCodeblock } from "@utils/text"; import { makeCodeblock } from "@utils/text";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
@ -27,10 +27,9 @@ import { Alerts, Forms, UserStore } from "@webpack/common";
import gitHash from "~git-hash"; import gitHash from "~git-hash";
import plugins from "~plugins"; import plugins from "~plugins";
import settings from "./settings"; import settings from "./_core/settings";
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss"; const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
const FIREFOX_DISMISS_KEY = "Vencord-Firefox-Warning-Dismiss";
const AllowedChannelIds = [ const AllowedChannelIds = [
SUPPORT_CHANNEL_ID, SUPPORT_CHANNEL_ID,
@ -116,22 +115,6 @@ ${makeCodeblock(enabledPlugins.join(", ") + "\n\n" + enabledApiPlugins.join(", "
onConfirm: rememberDismiss onConfirm: rememberDismiss
}); });
} }
if (IsFirefox) {
const rememberDismiss = () => DataStore.set(FIREFOX_DISMISS_KEY, true);
Alerts.show({
title: "Hold on!",
body: <div>
<Forms.FormText>You are using Firefox.</Forms.FormText>
<Forms.FormText>Due to Firefox's stupid extension guidelines, most themes and many plugins will not function correctly.</Forms.FormText>
<Forms.FormText>Do not report bugs. Do not ask for help with broken plugins.</Forms.FormText>
<Forms.FormText>Instead, use a chromium browser, Discord Desktop, or Vesktop.</Forms.FormText>
</div>,
onCancel: rememberDismiss,
onConfirm: rememberDismiss
});
}
} }
} }
}); });

View File

@ -213,12 +213,15 @@ function applyRules(content: string): string {
if (content.length === 0) if (content.length === 0)
return content; return content;
// pad so that rules can use " word " to only match whole "word"
content = " " + content + " ";
if (stringRules) { if (stringRules) {
for (const rule of stringRules) { for (const rule of stringRules) {
if (!rule.find || !rule.replace) continue; if (!rule.find || !rule.replace) continue;
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue; if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
content = ` ${content} `.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n")).replace(/^\s|\s$/g, ""); content = content.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n"));
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -265,7 +265,7 @@ export const Devs = /* #__PURE__*/ Object.freeze({
}, },
Dziurwa: { Dziurwa: {
name: "Dziurwa", name: "Dziurwa",
id: 1001086404203389018n id: 1034579679526526976n
}, },
AutumnVN: { AutumnVN: {
name: "AutumnVN", name: "AutumnVN",
@ -385,5 +385,3 @@ export const DevsById = /* #__PURE__*/ (() =>
.map(([_, v]) => [v.id, v] as const) .map(([_, v]) => [v.id, v] as const)
)) ))
)() as Record<string, Dev>; )() as Record<string, Dev>;
export const IsFirefox = IS_EXTENSION && navigator.userAgent.toLowerCase().includes("firefox");

View File

@ -17,15 +17,23 @@
*/ */
import { makeLazy } from "./lazy"; import { makeLazy } from "./lazy";
import { EXTENSION_BASE_URL } from "./web-metadata";
/* /*
Add dynamically loaded dependencies for plugins here. Add dynamically loaded dependencies for plugins here.
*/ */
// https://github.com/mattdesl/gifenc
// this lib is way better than gif.js and all other libs, they're all so terrible but this one is nice
// @ts-ignore ts mad
export const getGifEncoder = makeLazy(() => import("https://unpkg.com/gifenc@1.0.3/dist/gifenc.esm.js"));
// needed to parse APNGs in the nitroBypass plugin // needed to parse APNGs in the nitroBypass plugin
export const importApngJs = makeLazy(() => { export const importApngJs = makeLazy(async () => {
return require("./apng-canvas").APNG as { parseURL(url: string): Promise<ApngFrameData>; }; const exports = {};
const winProxy = new Proxy(window, { set: (_, k, v) => exports[k] = v });
Function("self", await fetch("https://cdnjs.cloudflare.com/ajax/libs/apng-canvas/2.1.1/apng-canvas.min.js").then(r => r.text()))(winProxy);
// @ts-ignore
return exports.APNG as { parseURL(url: string): Promise<ApngFrameData>; };
}); });
// https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk // https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
@ -67,20 +75,13 @@ export interface ApngFrameData {
playTime: number; playTime: number;
} }
// On web (extensions), use extension uri as basepath (load files from extension) const shikiWorkerDist = "https://unpkg.com/@vap/shiki-worker@0.0.8/dist";
// On desktop (electron), load from cdn export const shikiWorkerSrc = `${shikiWorkerDist}/${IS_DEV ? "index.js" : "index.min.js"}`;
export const rnnoiseDist = IS_EXTENSION export const shikiOnigasmSrc = "https://unpkg.com/@vap/shiki@0.10.3/dist/onig.wasm";
? new URL("/third-party/rnnoise", EXTENSION_BASE_URL).toString()
: "https://unpkg.com/@sapphi-red/web-noise-suppressor@0.3.3/dist"; export const rnnoiseDist = "https://unpkg.com/@sapphi-red/web-noise-suppressor@0.3.3/dist";
export const rnnoiseWasmSrc = (simd = false) => `${rnnoiseDist}/rnnoise${simd ? "_simd" : ""}.wasm`; export const rnnoiseWasmSrc = (simd = false) => `${rnnoiseDist}/rnnoise${simd ? "_simd" : ""}.wasm`;
export const rnnoiseWorkletSrc = `${rnnoiseDist}/rnnoise/workletProcessor.js`; export const rnnoiseWorkletSrc = `${rnnoiseDist}/rnnoise/workletProcessor.js`;
// @ts-expect-error SHUT UP
// The below code is only used on the Desktop (electron) build of Vencord. export const getStegCloak = makeLazy(() => import("https://unpkg.com/stegcloak-dist@1.0.0/index.js"));
// Browser (extension) builds do not contain these remote imports.
export const shikiWorkerSrc = `https://unpkg.com/@vap/shiki-worker@0.0.8/dist/${IS_DEV ? "index.js" : "index.min.js"}`;
export const shikiOnigasmSrc = "https://unpkg.com/@vap/shiki@0.10.3/dist/onig.wasm";
// @ts-expect-error
export const getStegCloak = /* #__PURE__*/ makeLazy(() => import("https://unpkg.com/stegcloak-dist@1.0.0/index.js"));

View File

@ -16,17 +16,9 @@
* 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 makeLazy<T>(factory: () => T, attempts = 5): () => T { export function makeLazy<T>(factory: () => T): () => T {
let tries = 0;
let cache: T; let cache: T;
return () => { return () => cache ?? (cache = factory());
if (!cache && attempts > tries++) {
cache = factory();
if (!cache && attempts === tries)
console.error("Lazy factory failed:", factory);
}
return cache;
};
} }
// Proxies demand that these properties be unmodified, so proxyLazy // Proxies demand that these properties be unmodified, so proxyLazy
@ -93,8 +85,6 @@ export function proxyLazy<T>(factory: () => T, attempts = 5): T {
[kGET]() { [kGET]() {
if (!proxyDummy[kCACHE] && attempts > tries++) { if (!proxyDummy[kCACHE] && attempts > tries++) {
proxyDummy[kCACHE] = factory(); proxyDummy[kCACHE] = factory();
if (!proxyDummy[kCACHE] && attempts === tries)
console.error("Lazy factory failed:", factory);
} }
return proxyDummy[kCACHE]; return proxyDummy[kCACHE];
} }

View File

@ -21,8 +21,6 @@ import { React, useEffect, useMemo, useReducer, useState } from "@webpack/common
import { makeLazy } from "./lazy"; import { makeLazy } from "./lazy";
import { checkIntersecting } from "./misc"; import { checkIntersecting } from "./misc";
export const NoopComponent = () => null;
/** /**
* Check if an element is on screen * Check if an element is on screen
* @param intersectOnly If `true`, will only update the state when the element comes into view * @param intersectOnly If `true`, will only update the state when the element comes into view
@ -127,14 +125,13 @@ export function useForceUpdater(withDep?: true) {
* A lazy component. The factory method is called on first render. For example useful * A lazy component. The factory method is called on first render. For example useful
* for const Component = LazyComponent(() => findByDisplayName("...").default) * for const Component = LazyComponent(() => findByDisplayName("...").default)
* @param factory Function returning a Component * @param factory Function returning a Component
* @param attempts How many times to try to get the component before giving up
* @returns Result of factory function * @returns Result of factory function
*/ */
export function LazyComponent<T extends object = any>(factory: () => React.ComponentType<T>, attempts = 5) { export function LazyComponent<T extends object = any>(factory: () => React.ComponentType<T>) {
const get = makeLazy(factory, attempts); const get = makeLazy(factory);
return (props: T) => { return (props: T) => {
const Component = get() ?? NoopComponent; const Component = get();
return <Component {...props} />; return <Component {...props} />;
}; };
} }

View File

@ -23,7 +23,6 @@ import { deflateSync, inflateSync } from "fflate";
import { getCloudAuth, getCloudUrl } from "./cloud"; import { getCloudAuth, getCloudUrl } from "./cloud";
import { Logger } from "./Logger"; import { Logger } from "./Logger";
import { relaunch } from "./native";
import { chooseFile, saveFile } from "./web"; import { chooseFile, saveFile } from "./web";
export async function importSettings(data: string) { export async function importSettings(data: string) {
@ -230,7 +229,7 @@ export async function getCloudSettings(shouldNotify = true, force = false) {
title: "Cloud Settings", title: "Cloud Settings",
body: "Your settings have been updated! Click here to restart to fully apply changes!", body: "Your settings have been updated! Click here to restart to fully apply changes!",
color: "var(--green-360)", color: "var(--green-360)",
onClick: IS_WEB ? () => location.reload() : relaunch, onClick: () => window.DiscordNative.app.relaunch(),
noPersist: true noPersist: true
}); });

View File

@ -1,14 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2023 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export let EXTENSION_BASE_URL: string;
export let EXTENSION_VERSION: string;
if (IS_EXTENSION) {
const script = document.querySelector("#vencord-script") as HTMLScriptElement;
EXTENSION_BASE_URL = script.dataset.extensionBaseUrl!;
EXTENSION_VERSION = script.dataset.version!;
}