Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
12fe367385 | ||
|
fc0417eb80 | ||
|
1f87d14ab2 | ||
|
7d8f3508a8 | ||
|
41f5d71e38 | ||
|
efb88a4df8 | ||
|
a73d09a2f0 | ||
|
cf7c4d63b6 | ||
|
a95311ef2c | ||
|
dd23f9802c | ||
|
f23ddf4cae | ||
|
4222c7fd9f | ||
|
09f65b401e | ||
|
7364776715 | ||
|
3a5b70d410 | ||
|
e08d49edac | ||
|
17abbd3e3e | ||
|
25a1d934c6 | ||
|
620319a4bc | ||
|
4c9996d620 | ||
|
885c75fdaa | ||
|
f2a22c5e57 | ||
|
5e3a485edc | ||
|
452bf72e56 | ||
|
afa47addd7 | ||
|
8ecee3d09f | ||
|
d2aac5edc5 | ||
|
fc45510053 | ||
|
77bfaf38a5 | ||
|
a4cadc03d8 | ||
|
315fcf1972 | ||
|
f1b3b3c0a3 | ||
|
4c805d08be |
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -38,7 +38,7 @@ jobs:
|
||||
run: pnpm build --standalone
|
||||
|
||||
- name: Generate plugin list
|
||||
run: pnpm generatePluginJson dist/plugins.json
|
||||
run: pnpm generatePluginJson dist/plugins.json dist/plugin-readmes.json
|
||||
|
||||
- name: Clean up obsolete files
|
||||
run: |
|
||||
|
@ -19,9 +19,11 @@
|
||||
/// <reference path="../src/modules.d.ts" />
|
||||
/// <reference path="../src/globals.d.ts" />
|
||||
|
||||
import monacoHtml from "~fileContent/../src/components/monacoWin.html";
|
||||
import monacoHtmlLocal from "~fileContent/monacoWin.html";
|
||||
import monacoHtmlCdn from "~fileContent/../src/main/monacoWin.html";
|
||||
import * as DataStore from "../src/api/DataStore";
|
||||
import { debounce } from "../src/utils";
|
||||
import { EXTENSION_BASE_URL } from "../src/utils/web-metadata";
|
||||
import { getTheme, Theme } from "../src/utils/discord";
|
||||
import { getThemeInfo } from "../src/main/themes";
|
||||
|
||||
@ -80,6 +82,7 @@ window.VencordNative = {
|
||||
return;
|
||||
}
|
||||
|
||||
win.baseUrl = EXTENSION_BASE_URL;
|
||||
win.setCss = setCssDebounced;
|
||||
win.getCurrentCss = () => VencordNative.quickCss.get();
|
||||
win.getTheme = () =>
|
||||
@ -87,7 +90,7 @@ window.VencordNative = {
|
||||
? "vs-light"
|
||||
: "vs-dark";
|
||||
|
||||
win.document.write(monacoHtml);
|
||||
win.document.write(IS_EXTENSION ? monacoHtmlLocal : monacoHtmlCdn);
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -4,6 +4,11 @@ if (typeof browser === "undefined") {
|
||||
|
||||
const script = document.createElement("script");
|
||||
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");
|
||||
style.type = "text/css";
|
||||
|
@ -28,7 +28,7 @@
|
||||
|
||||
"web_accessible_resources": [
|
||||
{
|
||||
"resources": ["dist/Vencord.js", "dist/Vencord.css"],
|
||||
"resources": ["dist/*", "third-party/*"],
|
||||
"matches": ["*://*.discord.com/*"]
|
||||
}
|
||||
],
|
||||
|
43
browser/monaco.ts
Normal file
43
browser/monaco.ts
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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();
|
||||
});
|
||||
});
|
37
browser/monacoWin.html
Normal file
37
browser/monacoWin.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!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>
|
135
browser/patch-worker.js
Normal file
135
browser/patch-worker.js
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
})();
|
21
browser/third-party/rnnoise/LICENSE
vendored
Normal file
21
browser/third-party/rnnoise/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 翠 / green
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
BIN
browser/third-party/rnnoise/rnnoise.wasm
vendored
Normal file
BIN
browser/third-party/rnnoise/rnnoise.wasm
vendored
Normal file
Binary file not shown.
13
browser/third-party/rnnoise/rnnoise/workletProcessor.js
vendored
Normal file
13
browser/third-party/rnnoise/rnnoise/workletProcessor.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
browser/third-party/rnnoise/rnnoise_simd.wasm
vendored
Normal file
BIN
browser/third-party/rnnoise/rnnoise_simd.wasm
vendored
Normal file
Binary file not shown.
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "vencord",
|
||||
"private": "true",
|
||||
"version": "1.4.6",
|
||||
"version": "1.5.0",
|
||||
"description": "The cutest Discord client mod",
|
||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||
"bugs": {
|
||||
@ -36,10 +36,13 @@
|
||||
"@vap/shiki": "0.10.5",
|
||||
"eslint-plugin-simple-header": "^1.0.2",
|
||||
"fflate": "^0.7.4",
|
||||
"gifenc": "github:mattdesl/gifenc#64842fca317b112a8590f8fef2bf3825da8f6fe3",
|
||||
"monaco-editor": "^0.43.0",
|
||||
"nanoid": "^4.0.2",
|
||||
"virtual-merge": "^1.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chrome": "^0.0.246",
|
||||
"@types/diff": "^5.0.3",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"@types/node": "^18.16.3",
|
||||
@ -64,7 +67,8 @@
|
||||
"stylelint-config-standard": "^33.0.0",
|
||||
"tsx": "^3.12.7",
|
||||
"type-fest": "^3.9.0",
|
||||
"typescript": "^5.0.4"
|
||||
"typescript": "^5.0.4",
|
||||
"zip-local": "^0.3.5"
|
||||
},
|
||||
"packageManager": "pnpm@8.1.1",
|
||||
"pnpm": {
|
||||
|
81
pnpm-lock.yaml
generated
81
pnpm-lock.yaml
generated
@ -1,5 +1,9 @@
|
||||
lockfileVersion: '6.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
patchedDependencies:
|
||||
eslint-plugin-path-alias@1.0.0:
|
||||
hash: m6sma4g6bh67km3q6igf6uxaja
|
||||
@ -24,6 +28,12 @@ dependencies:
|
||||
fflate:
|
||||
specifier: ^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:
|
||||
specifier: ^4.0.2
|
||||
version: 4.0.2
|
||||
@ -32,6 +42,9 @@ dependencies:
|
||||
version: 1.0.1
|
||||
|
||||
devDependencies:
|
||||
'@types/chrome':
|
||||
specifier: ^0.0.246
|
||||
version: 0.0.246
|
||||
'@types/diff':
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3
|
||||
@ -107,6 +120,9 @@ devDependencies:
|
||||
typescript:
|
||||
specifier: ^5.0.4
|
||||
version: 5.0.4
|
||||
zip-local:
|
||||
specifier: ^0.3.5
|
||||
version: 0.3.5
|
||||
|
||||
packages:
|
||||
|
||||
@ -520,10 +536,31 @@ packages:
|
||||
resolution: {integrity: sha512-gAC33DCXYwNTI/k1PxOVHmbbzakUSMbb/DHpoV6rn4pKZtPI1dduULSmAAm/y1ipgIlArnk2JcnQzw4n2tCZHw==}
|
||||
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:
|
||||
resolution: {integrity: sha512-amrLbRqTU9bXMCc6uX0sWpxsQzRIo9z6MJPkH1pkez/qOxuqSZVuryJAWoBRq94CeG8JxY+VK4Le9HtjQR5T9A==}
|
||||
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:
|
||||
resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==}
|
||||
dev: true
|
||||
@ -843,6 +880,10 @@ packages:
|
||||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/async@1.5.2:
|
||||
resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==}
|
||||
dev: true
|
||||
|
||||
/atob@2.1.2:
|
||||
resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==}
|
||||
engines: {node: '>= 4.5.0'}
|
||||
@ -1867,6 +1908,10 @@ packages:
|
||||
resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==}
|
||||
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:
|
||||
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
|
||||
dev: true
|
||||
@ -2184,6 +2229,12 @@ packages:
|
||||
resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
|
||||
dev: false
|
||||
|
||||
/jszip@2.7.0:
|
||||
resolution: {integrity: sha512-JIsRKRVC3gTRo2vM4Wy9WBC3TRcfnIZU8k65Phi3izkvPH975FowRYtKGT6PxevA0XnJ/yO8b0QwV0ydVyQwfw==}
|
||||
dependencies:
|
||||
pako: 1.0.11
|
||||
dev: true
|
||||
|
||||
/kind-of@3.2.2:
|
||||
resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@ -2354,6 +2405,10 @@ packages:
|
||||
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
|
||||
dev: true
|
||||
|
||||
/monaco-editor@0.43.0:
|
||||
resolution: {integrity: sha512-cnoqwQi/9fml2Szamv1XbSJieGJ1Dc8tENVMD26Kcfl7xGQWp7OBKMjlwKVGYFJ3/AXJjSOGvcqK7Ry/j9BM1Q==}
|
||||
dev: false
|
||||
|
||||
/ms@2.0.0:
|
||||
resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==}
|
||||
dev: true
|
||||
@ -2511,6 +2566,10 @@ packages:
|
||||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/pako@1.0.11:
|
||||
resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==}
|
||||
dev: true
|
||||
|
||||
/parent-module@1.0.1:
|
||||
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||
engines: {node: '>=6'}
|
||||
@ -2662,6 +2721,11 @@ packages:
|
||||
- utf-8-validate
|
||||
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:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
dev: true
|
||||
@ -3377,6 +3441,17 @@ packages:
|
||||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
/zip-local@0.3.5:
|
||||
resolution: {integrity: sha512-GRV3D5TJY+/PqyeRm5CYBs7xVrKTKzljBoEXvocZu0HJ7tPEcgpSOYa2zFIsCZWgKWMuc4U3yMFgFkERGFIB9w==}
|
||||
dependencies:
|
||||
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
|
||||
|
@ -78,6 +78,7 @@ await Promise.all([
|
||||
define: {
|
||||
...defines,
|
||||
IS_WEB: false,
|
||||
IS_EXTENSION: false,
|
||||
IS_DISCORD_DESKTOP: true,
|
||||
IS_VESKTOP: false
|
||||
}
|
||||
@ -124,6 +125,7 @@ await Promise.all([
|
||||
define: {
|
||||
...defines,
|
||||
IS_WEB: false,
|
||||
IS_EXTENSION: false,
|
||||
IS_DISCORD_DESKTOP: false,
|
||||
IS_VESKTOP: true
|
||||
}
|
||||
|
@ -17,12 +17,11 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
import esbuild from "esbuild";
|
||||
import { zip } from "fflate";
|
||||
import { readFileSync } from "fs";
|
||||
import { appendFile, mkdir, readFile, rm, writeFile } from "fs/promises";
|
||||
import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
|
||||
import { join } from "path";
|
||||
import Zip from "zip-local";
|
||||
|
||||
import { BUILD_TIMESTAMP, commonOpts, globPlugins, VERSION, watch } from "./common.mjs";
|
||||
|
||||
@ -42,6 +41,7 @@ const commonOptions = {
|
||||
target: ["esnext"],
|
||||
define: {
|
||||
IS_WEB: "true",
|
||||
IS_EXTENSION: "false",
|
||||
IS_STANDALONE: "true",
|
||||
IS_DEV: JSON.stringify(watch),
|
||||
IS_DISCORD_DESKTOP: "false",
|
||||
@ -52,19 +52,51 @@ const commonOptions = {
|
||||
}
|
||||
};
|
||||
|
||||
const MonacoWorkerEntryPoints = [
|
||||
"vs/language/css/css.worker.js",
|
||||
"vs/editor/editor.worker.js"
|
||||
];
|
||||
|
||||
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({
|
||||
...commonOptions,
|
||||
outfile: "dist/browser.js",
|
||||
footer: { js: "//# sourceURL=VencordWeb" },
|
||||
}),
|
||||
esbuild.build({
|
||||
...commonOptions,
|
||||
outfile: "dist/extension.js",
|
||||
define: {
|
||||
...commonOptions?.define,
|
||||
IS_EXTENSION: "true",
|
||||
},
|
||||
footer: { js: "//# sourceURL=VencordWeb" },
|
||||
}),
|
||||
esbuild.build({
|
||||
...commonOptions,
|
||||
inject: ["browser/GMPolyfill.js", ...(commonOptions?.inject || [])],
|
||||
define: {
|
||||
"window": "unsafeWindow",
|
||||
...(commonOptions?.define)
|
||||
...(commonOptions?.define),
|
||||
window: "unsafeWindow",
|
||||
},
|
||||
outfile: "dist/Vencord.user.js",
|
||||
banner: {
|
||||
@ -79,12 +111,39 @@ await Promise.all(
|
||||
);
|
||||
|
||||
/**
|
||||
* @type {(target: string, files: string[], shouldZip: boolean) => Promise<void>}
|
||||
* @type {(dir: string) => Promise<string[]>}
|
||||
*/
|
||||
async function buildPluginZip(target, files, shouldZip) {
|
||||
async function globDir(dir) {
|
||||
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) {
|
||||
const entries = {
|
||||
"dist/Vencord.js": await readFile("dist/browser.js"),
|
||||
"dist/Vencord.css": await readFile("dist/browser.css"),
|
||||
"dist/Vencord.js": await readFile("dist/extension.js"),
|
||||
"dist/Vencord.css": await readFile("dist/extension.css"),
|
||||
...await loadDir("dist/monaco"),
|
||||
...await loadDir("browser/third-party", "browser/"),
|
||||
...Object.fromEntries(await Promise.all(files.map(async f => {
|
||||
let content = await readFile(join("browser", f));
|
||||
if (f.startsWith("manifest")) {
|
||||
@ -100,31 +159,15 @@ async function buildPluginZip(target, files, shouldZip) {
|
||||
}))),
|
||||
};
|
||||
|
||||
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 Promise.all(Object.entries(entries).map(async ([file, content]) => {
|
||||
const dest = join("dist", target, file);
|
||||
const parentDirectory = join(dest, "..");
|
||||
await mkdir(parentDirectory, { recursive: true });
|
||||
await writeFile(dest, content);
|
||||
}));
|
||||
await rm(target, { recursive: true, force: true });
|
||||
await Promise.all(Object.entries(entries).map(async ([file, content]) => {
|
||||
const dest = join("dist", target, file);
|
||||
const parentDirectory = join(dest, "..");
|
||||
await mkdir(parentDirectory, { recursive: true });
|
||||
await writeFile(dest, content);
|
||||
}));
|
||||
|
||||
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 => {
|
||||
@ -142,8 +185,9 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content
|
||||
|
||||
await Promise.all([
|
||||
appendCssRuntime,
|
||||
buildPluginZip("extension.zip", ["modifyResponseHeaders.json", "content.js", "manifest.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),
|
||||
buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
|
||||
buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
|
||||
]);
|
||||
|
||||
Zip.sync.zip("dist/chromium-unpacked").compress().save("dist/extension.zip");
|
||||
console.info("Packed Chromium Extension written to dist/extension.zip");
|
||||
|
@ -165,7 +165,11 @@ async function parseFile(fileName: string) {
|
||||
data.target = target as any;
|
||||
}
|
||||
|
||||
return data;
|
||||
let readme = "";
|
||||
try {
|
||||
readme = readFileSync(join(fileName, "..", "README.md"), "utf-8");
|
||||
} catch { }
|
||||
return [data, readme] as const;
|
||||
}
|
||||
|
||||
throw fail("no default export called 'definePlugin' found");
|
||||
@ -194,18 +198,24 @@ function isPluginFile({ name }: { name: string; }) {
|
||||
(async () => {
|
||||
parseDevs();
|
||||
|
||||
const plugins = ["src/plugins", "src/plugins/_core"].flatMap(dir =>
|
||||
const plugins = [] as PluginData[];
|
||||
const readmes = {} as Record<string, string>;
|
||||
|
||||
await Promise.all(["src/plugins", "src/plugins/_core"].flatMap(dir =>
|
||||
readdirSync(dir, { withFileTypes: true })
|
||||
.filter(isPluginFile)
|
||||
.map(async dirent =>
|
||||
parseFile(await getEntryPoint(dir, dirent))
|
||||
)
|
||||
);
|
||||
.map(async dirent => {
|
||||
const [data, readme] = await parseFile(await getEntryPoint(dir, dirent));
|
||||
plugins.push(data);
|
||||
if (readme) readmes[data.name] = readme;
|
||||
})
|
||||
));
|
||||
|
||||
const data = JSON.stringify(await Promise.all(plugins));
|
||||
const data = JSON.stringify(plugins);
|
||||
|
||||
if (process.argv.length > 2) {
|
||||
if (process.argv.length > 3) {
|
||||
writeFileSync(process.argv[2], data);
|
||||
writeFileSync(process.argv[3], JSON.stringify(readmes));
|
||||
} else {
|
||||
console.log(data);
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ page.on("console", async e => {
|
||||
plugin,
|
||||
type,
|
||||
id,
|
||||
match: regex,
|
||||
match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"),
|
||||
error: cause
|
||||
});
|
||||
break;
|
||||
|
21
src/components/CodeBlock.tsx
Normal file
21
src/components/CodeBlock.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Parser } from "@webpack/common";
|
||||
|
||||
const CodeContainerClasses = findByPropsLazy("markup", "codeContainer");
|
||||
|
||||
/**
|
||||
* Renders code in a Discord codeblock
|
||||
*/
|
||||
export function CodeBlock(props: { content?: string, lang: string; }) {
|
||||
return (
|
||||
<div className={CodeContainerClasses.markup}>
|
||||
{Parser.defaultRules.codeBlock.react(props, null, {})}
|
||||
</div>
|
||||
);
|
||||
}
|
113
src/components/PluginSettings/ContributorModal.tsx
Normal file
113
src/components/PluginSettings/ContributorModal.tsx
Normal file
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./contributorModal.css";
|
||||
|
||||
import { useSettings } from "@api/Settings";
|
||||
import { classNameFactory } from "@api/Styles";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { DevsById } from "@utils/constants";
|
||||
import { fetchUserProfile, getTheme, Theme } from "@utils/discord";
|
||||
import { ModalContent, ModalRoot, openModal } from "@utils/modal";
|
||||
import { Forms, MaskedLink, showToast, useEffect, useMemo, UserProfileStore, useStateFromStores } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
|
||||
import Plugins from "~plugins";
|
||||
|
||||
import { PluginCard } from ".";
|
||||
|
||||
const WebsiteIconDark = "/assets/e1e96d89e192de1997f73730db26e94f.svg";
|
||||
const WebsiteIconLight = "/assets/730f58bcfd5a57a5e22460c445a0c6cf.svg";
|
||||
const GithubIconLight = "/assets/3ff98ad75ac94fa883af5ed62d17c459.svg";
|
||||
const GithubIconDark = "/assets/6a853b4c87fce386cbfef4a2efbacb09.svg";
|
||||
|
||||
const cl = classNameFactory("vc-author-modal-");
|
||||
|
||||
export function openContributorModal(user: User) {
|
||||
openModal(modalProps =>
|
||||
<ModalRoot {...modalProps}>
|
||||
<ErrorBoundary>
|
||||
<ModalContent className={cl("root")}>
|
||||
<ContributorModal user={user} />
|
||||
</ModalContent>
|
||||
</ErrorBoundary>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
||||
|
||||
function GithubIcon() {
|
||||
const src = getTheme() === Theme.Light ? GithubIconLight : GithubIconDark;
|
||||
return <img src={src} alt="GitHub" />;
|
||||
}
|
||||
|
||||
function WebsiteIcon() {
|
||||
const src = getTheme() === Theme.Light ? WebsiteIconLight : WebsiteIconDark;
|
||||
return <img src={src} alt="Website" />;
|
||||
}
|
||||
|
||||
function ContributorModal({ user }: { user: User; }) {
|
||||
useSettings();
|
||||
|
||||
const profile = useStateFromStores([UserProfileStore], () => UserProfileStore.getUserProfile(user.id));
|
||||
|
||||
useEffect(() => {
|
||||
if (!profile && !user.bot && user.id)
|
||||
fetchUserProfile(user.id);
|
||||
}, [user.id]);
|
||||
|
||||
const githubName = profile?.connectedAccounts?.find(a => a.type === "github")?.name;
|
||||
const website = profile?.connectedAccounts?.find(a => a.type === "domain")?.name;
|
||||
|
||||
const plugins = useMemo(() => {
|
||||
const allPlugins = Object.values(Plugins);
|
||||
const pluginsByAuthor = DevsById[user.id]
|
||||
? allPlugins.filter(p => p.authors.includes(DevsById[user.id]))
|
||||
: allPlugins.filter(p => p.authors.some(a => a.name === user.username));
|
||||
|
||||
return pluginsByAuthor
|
||||
.filter(p => !p.name.endsWith("API"))
|
||||
.sort((a, b) => Number(a.required ?? false) - Number(b.required ?? false));
|
||||
}, [user.id, user.username]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={cl("header")}>
|
||||
<img
|
||||
className={cl("avatar")}
|
||||
src={user.getAvatarURL(void 0, 512, true)}
|
||||
alt=""
|
||||
/>
|
||||
<Forms.FormTitle tag="h2" className={cl("name")}>{user.username}</Forms.FormTitle>
|
||||
|
||||
<div className={cl("links")}>
|
||||
{website && (
|
||||
<MaskedLink
|
||||
href={"https://" + website}
|
||||
>
|
||||
<WebsiteIcon />
|
||||
</MaskedLink>
|
||||
)}
|
||||
{githubName && (
|
||||
<MaskedLink href={`https://github.com/${githubName}`}>
|
||||
<GithubIcon />
|
||||
</MaskedLink>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={cl("plugins")}>
|
||||
{plugins.map(p =>
|
||||
<PluginCard
|
||||
key={p.name}
|
||||
plugin={p}
|
||||
disabled={p.required ?? false}
|
||||
onRestartNeeded={() => showToast("Restart to apply changes!")}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
@ -18,7 +18,6 @@
|
||||
|
||||
import { generateId } from "@api/Commands";
|
||||
import { useSettings } from "@api/Settings";
|
||||
import { disableStyle, enableStyle } from "@api/Styles";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
@ -28,7 +27,7 @@ import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, M
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import { OptionType, Plugin } from "@utils/types";
|
||||
import { findByCode, findByPropsLazy } from "@webpack";
|
||||
import { Button, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
||||
import { Button, Clickable, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
import { Constructor } from "type-fest";
|
||||
|
||||
@ -41,7 +40,7 @@ import {
|
||||
SettingSliderComponent,
|
||||
SettingTextComponent
|
||||
} from "./components";
|
||||
import hideBotTagStyle from "./userPopoutHideBotTag.css?managed";
|
||||
import { openContributorModal } from "./ContributorModal";
|
||||
|
||||
const UserSummaryItem = LazyComponent(() => findByCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
|
||||
const AvatarStyles = findByPropsLazy("moreUsers", "emptyUser", "avatarContainer", "clickableAvatar");
|
||||
@ -92,27 +91,16 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||
const hasSettings = Boolean(pluginSettings && plugin.options && !isObjectEmpty(plugin.options));
|
||||
|
||||
React.useEffect(() => {
|
||||
enableStyle(hideBotTagStyle);
|
||||
|
||||
let originalUser: User;
|
||||
(async () => {
|
||||
for (const user of plugin.authors.slice(0, 6)) {
|
||||
const author = user.id
|
||||
? await UserUtils.fetchUser(`${user.id}`)
|
||||
// only show name & pfp and no actions so users cannot harass plugin devs for support (send dms, add as friend, etc)
|
||||
.then(u => (originalUser = u, makeDummyUser(u)))
|
||||
.catch(() => makeDummyUser({ username: user.name }))
|
||||
: makeDummyUser({ username: user.name });
|
||||
|
||||
setAuthors(a => [...a, author]);
|
||||
}
|
||||
})();
|
||||
|
||||
return () => {
|
||||
disableStyle(hideBotTagStyle);
|
||||
if (originalUser)
|
||||
FluxDispatcher.dispatch({ type: "USER_UPDATE", user: originalUser });
|
||||
};
|
||||
}, []);
|
||||
|
||||
async function saveAndClose() {
|
||||
@ -214,6 +202,19 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
|
||||
showDefaultAvatarsForNullUsers
|
||||
showUserPopout
|
||||
renderMoreUsers={renderMoreUsers}
|
||||
renderUser={(user: User) => (
|
||||
<Clickable
|
||||
className={AvatarStyles.clickableAvatar}
|
||||
onClick={() => openContributorModal(user)}
|
||||
>
|
||||
<img
|
||||
className={AvatarStyles.avatar}
|
||||
src={user.getAvatarURL(void 0, 80, true)}
|
||||
alt={user.username}
|
||||
title={user.username}
|
||||
/>
|
||||
</Clickable>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Forms.FormSection>
|
||||
|
57
src/components/PluginSettings/contributorModal.css
Normal file
57
src/components/PluginSettings/contributorModal.css
Normal file
@ -0,0 +1,57 @@
|
||||
.vc-author-modal-root {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.vc-author-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.vc-author-modal-name {
|
||||
text-transform: none;
|
||||
flex-grow: 0;
|
||||
background: var(--background-tertiary);
|
||||
border-radius: 0 9999px 9999px 0;
|
||||
padding: 6px 0.8em 6px 0.5em;
|
||||
font-size: 20px;
|
||||
height: 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.vc-author-modal-name::before {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 16px;
|
||||
background: var(--background-tertiary);
|
||||
z-index: -1;
|
||||
left: -16px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.vc-author-modal-avatar {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.vc-author-modal-links {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
gap: 0.2em;
|
||||
}
|
||||
|
||||
.vc-author-modal-links img {
|
||||
height: 32px;
|
||||
width: 32px;
|
||||
border-radius: 50%;
|
||||
border: 4px solid var(--background-tertiary);
|
||||
box-sizing: border-box
|
||||
}
|
||||
|
||||
.vc-author-modal-plugins {
|
||||
display: grid;
|
||||
gap: 0.5em;
|
||||
}
|
@ -91,7 +91,7 @@ interface PluginCardProps extends React.HTMLProps<HTMLDivElement> {
|
||||
isNew?: boolean;
|
||||
}
|
||||
|
||||
function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
|
||||
export function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) {
|
||||
const settings = Settings.plugins[plugin.name];
|
||||
|
||||
const isEnabled = () => settings.enabled ?? false;
|
||||
|
@ -1,3 +0,0 @@
|
||||
[class|="userPopoutOuter"] [class*="botTag"] {
|
||||
display: none;
|
||||
}
|
@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
import { CheckedTextInput } from "@components/CheckedTextInput";
|
||||
import { CodeBlock } from "@components/CodeBlock";
|
||||
import { debounce } from "@utils/debounce";
|
||||
import { Margins } from "@utils/margins";
|
||||
import { canonicalizeMatch, canonicalizeReplace } from "@utils/patches";
|
||||
@ -299,7 +300,7 @@ function PatchHelper() {
|
||||
{!!(find && match && replacement) && (
|
||||
<>
|
||||
<Forms.FormTitle className={Margins.top20}>Code</Forms.FormTitle>
|
||||
<div style={{ userSelect: "text" }}>{Parser.parse(makeCodeblock(code, "ts"))}</div>
|
||||
<CodeBlock lang="js" content={code} />
|
||||
<Button onClick={() => Clipboard.copy(code)}>Copy to Clipboard</Button>
|
||||
</>
|
||||
)}
|
||||
|
@ -8,6 +8,7 @@
|
||||
width: 100%;
|
||||
transition: 0.1s ease-out;
|
||||
transition-property: box-shadow, transform, background, opacity;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.vc-addon-card-disabled {
|
||||
|
1
src/globals.d.ts
vendored
1
src/globals.d.ts
vendored
@ -33,6 +33,7 @@ declare global {
|
||||
* replace: `${IS_WEB}?foo:bar`
|
||||
*/
|
||||
export var IS_WEB: boolean;
|
||||
export var IS_EXTENSION: boolean;
|
||||
export var IS_DEV: boolean;
|
||||
export var IS_STANDALONE: boolean;
|
||||
export var IS_UPDATER_DISABLED: boolean;
|
||||
|
@ -27,7 +27,7 @@ import { mkdirSync, readFileSync, watch } from "fs";
|
||||
import { open, readdir, readFile, writeFile } from "fs/promises";
|
||||
import { join, normalize } from "path";
|
||||
|
||||
import monacoHtml from "~fileContent/../components/monacoWin.html;base64";
|
||||
import monacoHtml from "~fileContent/monacoWin.html;base64";
|
||||
|
||||
import { getThemeInfo, stripBOM, UserThemeHeader } from "./themes";
|
||||
import { ALLOWED_PROTOCOLS, QUICKCSS_PATH, SETTINGS_DIR, SETTINGS_FILE, THEMES_DIR } from "./utils/constants";
|
||||
|
@ -27,7 +27,7 @@ import { Alerts, Forms, UserStore } from "@webpack/common";
|
||||
import gitHash from "~git-hash";
|
||||
import plugins from "~plugins";
|
||||
|
||||
import settings from "./_core/settings";
|
||||
import settings from "./settings";
|
||||
|
||||
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";
|
||||
|
||||
@ -48,7 +48,7 @@ export default definePlugin({
|
||||
name: "vencord-debug",
|
||||
description: "Send Vencord Debug info",
|
||||
predicate: ctx => AllowedChannelIds.includes(ctx.channel.id),
|
||||
execute() {
|
||||
async execute() {
|
||||
const { RELEASE_CHANNEL } = window.GLOBAL_ENV;
|
||||
|
||||
const client = (() => {
|
||||
@ -75,6 +75,10 @@ export default definePlugin({
|
||||
OpenAsar: "openasar" in window,
|
||||
};
|
||||
|
||||
if (IS_DISCORD_DESKTOP) {
|
||||
info["Last Crash Reason"] = (await DiscordNative.processUtils.getLastCrash())?.rendererCrashReason ?? "N/A";
|
||||
}
|
||||
|
||||
const debugInfo = `
|
||||
**Vencord Debug Info**
|
||||
>>> ${Object.entries(info).map(([k, v]) => `${k}: ${v}`).join("\n")}
|
@ -49,7 +49,7 @@ export default definePlugin({
|
||||
replace: "$1,vcProps=$2$3+(vcProps.channel.nsfw?' vc-nsfw-img':'')"
|
||||
}, {
|
||||
match: /(\.renderAttachments=.+?(.)=this\.props)(.+?\.embedWrapper)/g,
|
||||
replace: "$1,vcProps=$2$3+(vcProps.channel.nsfw?' vc-nsfw-img':'')"
|
||||
replace: "$1,vcProps=$2$3+(vcProps.nsfw?' vc-nsfw-img':'')"
|
||||
}]
|
||||
}
|
||||
],
|
||||
|
@ -127,6 +127,8 @@ export const defaultRules = [
|
||||
"redircnt@yandex.*",
|
||||
"feature@youtube.com",
|
||||
"kw@youtube.com",
|
||||
"si@youtube.com",
|
||||
"pp@youtube.com",
|
||||
"si@youtu.be",
|
||||
"wt_zmc",
|
||||
"utm_source",
|
||||
@ -136,4 +138,5 @@ export const defaultRules = [
|
||||
"utm_term",
|
||||
"si@open.spotify.com",
|
||||
"igshid",
|
||||
"share_id@reddit.com",
|
||||
];
|
||||
|
5
src/plugins/dearrow/README.md
Normal file
5
src/plugins/dearrow/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# Dearrow
|
||||
|
||||
Makes YouTube embed titles and thumbnails less sensationalist, powered by [Dearrow](https://dearrow.ajay.app/)
|
||||
|
||||
https://github.com/Vendicated/Vencord/assets/45497981/7bf81108-102d-47c5-8ba5-357db4db1283
|
161
src/plugins/dearrow/index.tsx
Normal file
161
src/plugins/dearrow/index.tsx
Normal file
@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import "./styles.css";
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Tooltip } from "@webpack/common";
|
||||
import type { Component } from "react";
|
||||
|
||||
interface Props {
|
||||
embed: {
|
||||
rawTitle: string;
|
||||
provider?: {
|
||||
name: string;
|
||||
};
|
||||
thumbnail: {
|
||||
proxyURL: string;
|
||||
};
|
||||
video: {
|
||||
url: string;
|
||||
};
|
||||
|
||||
dearrow: {
|
||||
enabled: boolean;
|
||||
oldTitle?: string;
|
||||
oldThumb?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const embedUrlRe = /https:\/\/www\.youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/;
|
||||
|
||||
async function embedDidMount(this: Component<Props>) {
|
||||
try {
|
||||
const { embed } = this.props;
|
||||
if (!embed || embed.dearrow || embed.provider?.name !== "YouTube" || !embed.video?.url) return;
|
||||
|
||||
const videoId = embedUrlRe.exec(embed.video.url)?.[1];
|
||||
if (!videoId) return;
|
||||
|
||||
const res = await fetch(`https://sponsor.ajay.app/api/branding?videoID=${videoId}`);
|
||||
if (!res.ok) return;
|
||||
|
||||
const { titles, thumbnails } = await res.json();
|
||||
|
||||
const hasTitle = titles[0]?.votes >= 0;
|
||||
const hasThumb = thumbnails[0]?.votes >= 0;
|
||||
|
||||
if (!hasTitle && !hasThumb) return;
|
||||
|
||||
embed.dearrow = {
|
||||
enabled: true
|
||||
};
|
||||
|
||||
if (titles[0]?.votes >= 0) {
|
||||
embed.dearrow.oldTitle = embed.rawTitle;
|
||||
embed.rawTitle = titles[0].title;
|
||||
}
|
||||
|
||||
if (thumbnails[0]?.votes >= 0) {
|
||||
embed.dearrow.oldThumb = embed.thumbnail.proxyURL;
|
||||
embed.thumbnail.proxyURL = `https://dearrow-thumb.ajay.app/api/v1/getThumbnail?videoID=${videoId}&time=${thumbnails[0].timestamp}`;
|
||||
}
|
||||
|
||||
this.forceUpdate();
|
||||
} catch (err) {
|
||||
new Logger("Dearrow").error("Failed to dearrow embed", err);
|
||||
}
|
||||
}
|
||||
|
||||
function DearrowButton({ component }: { component: Component<Props>; }) {
|
||||
const { embed } = component.props;
|
||||
if (!embed?.dearrow) return null;
|
||||
|
||||
return (
|
||||
<Tooltip text={embed.dearrow.enabled ? "This embed has been dearrowed, click to restore" : "Click to dearrow"}>
|
||||
{({ onMouseEnter, onMouseLeave }) => (
|
||||
<button
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
className={"vc-dearrow-toggle-" + (embed.dearrow.enabled ? "on" : "off")}
|
||||
onClick={() => {
|
||||
const { enabled, oldThumb, oldTitle } = embed.dearrow;
|
||||
embed.dearrow.enabled = !enabled;
|
||||
if (oldTitle) {
|
||||
embed.dearrow.oldTitle = embed.rawTitle;
|
||||
embed.rawTitle = oldTitle;
|
||||
}
|
||||
if (oldThumb) {
|
||||
embed.dearrow.oldThumb = embed.thumbnail.proxyURL;
|
||||
embed.thumbnail.proxyURL = oldThumb;
|
||||
}
|
||||
|
||||
component.forceUpdate();
|
||||
}}
|
||||
>
|
||||
{/* Dearrow Icon, taken from https://dearrow.ajay.app/logo.svg (and optimised) */}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24px"
|
||||
height="24px"
|
||||
viewBox="0 0 36 36"
|
||||
aria-label="Toggle Dearrow"
|
||||
>
|
||||
<path
|
||||
fill="#1213BD"
|
||||
d="M36 18.302c0 4.981-2.46 9.198-5.655 12.462s-7.323 5.152-12.199 5.152s-9.764-1.112-12.959-4.376S0 23.283 0 18.302s2.574-9.38 5.769-12.644S13.271 0 18.146 0s9.394 2.178 12.589 5.442C33.931 8.706 36 13.322 36 18.302z"
|
||||
/>
|
||||
<path
|
||||
fill="#88c9f9"
|
||||
d="m 30.394282,18.410186 c 0,3.468849 -1.143025,6.865475 -3.416513,9.137917 -2.273489,2.272442 -5.670115,2.92874 -9.137918,2.92874 -3.467803,0 -6.373515,-1.147212 -8.6470033,-3.419654 -2.2734888,-2.272442 -3.5871299,-5.178154 -3.5871299,-8.647003 0,-3.46885 0.9420533,-6.746149 3.2144954,-9.0196379 2.2724418,-2.2734888 5.5507878,-3.9513905 9.0196378,-3.9513905 3.46885,0 6.492841,1.9322561 8.76633,4.204698 2.273489,2.2724424 3.788101,5.2974804 3.788101,8.7663304 z"
|
||||
/>
|
||||
<path
|
||||
fill="#0a62a5"
|
||||
d="m 23.95823,17.818306 c 0,3.153748 -2.644888,5.808102 -5.798635,5.808102 -3.153748,0 -5.599825,-2.654354 -5.599825,-5.808102 0,-3.153747 2.446077,-5.721714 5.599825,-5.721714 3.153747,0 5.798635,2.567967 5.798635,5.721714 z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
</button>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "Dearrow",
|
||||
description: "Makes YouTube embed titles and thumbnails less sensationalist, powered by Dearrow",
|
||||
authors: [Devs.Ven],
|
||||
|
||||
embedDidMount,
|
||||
renderButton(component: Component<Props>) {
|
||||
return (
|
||||
<ErrorBoundary noop>
|
||||
<DearrowButton component={component} />
|
||||
</ErrorBoundary>
|
||||
);
|
||||
},
|
||||
|
||||
patches: [{
|
||||
find: "this.renderInlineMediaEmbed",
|
||||
replacement: [
|
||||
// patch componentDidMount to replace embed thumbnail and title
|
||||
{
|
||||
match: /(\i).render=function.{0,50}\i\.embed/,
|
||||
replace: "$1.componentDidMount=$self.embedDidMount,$&"
|
||||
},
|
||||
|
||||
// add dearrow button
|
||||
{
|
||||
match: /children:\[(?=null!=\i\?\i\.renderSuppressButton)/,
|
||||
replace: "children:[$self.renderButton(this),"
|
||||
}
|
||||
]
|
||||
}],
|
||||
});
|
12
src/plugins/dearrow/styles.css
Normal file
12
src/plugins/dearrow/styles.css
Normal file
@ -0,0 +1,12 @@
|
||||
.vc-dearrow-toggle-off svg {
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
.vc-dearrow-toggle-on, .vc-dearrow-toggle-off {
|
||||
all: unset;
|
||||
display: inline;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0.75rem;
|
||||
right: 0.75rem;
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
import { addPreEditListener, addPreSendListener, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
|
||||
import { definePluginSettings, Settings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { ApngBlendOp, ApngDisposeOp, getGifEncoder, importApngJs } from "@utils/dependencies";
|
||||
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
|
||||
import { getCurrentGuild } from "@utils/discord";
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import { Logger } from "@utils/Logger";
|
||||
@ -27,6 +27,7 @@ import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByCodeLazy, findByPropsLazy, findLazy, findStoreLazy } from "@webpack";
|
||||
import { ChannelStore, EmojiStore, FluxDispatcher, Parser, PermissionStore, UserStore } from "@webpack/common";
|
||||
import type { Message } from "discord-types/general";
|
||||
import { applyPalette, GIFEncoder, quantize } from "gifenc";
|
||||
import type { ReactElement, ReactNode } from "react";
|
||||
|
||||
const DRAFT_TYPE = 0;
|
||||
@ -212,15 +213,15 @@ export default definePlugin({
|
||||
}
|
||||
},
|
||||
{
|
||||
find: "canStreamHighQuality:function",
|
||||
find: "canUseHighVideoUploadQuality:function",
|
||||
predicate: () => settings.store.enableStreamQualityBypass,
|
||||
replacement: [
|
||||
"canUseHighVideoUploadQuality",
|
||||
"canStreamHighQuality",
|
||||
"canStreamMidQuality"
|
||||
// TODO: Remove the last two when they get removed from stable
|
||||
"(?:canStreamQuality|canStreamHighQuality|canStreamMidQuality)",
|
||||
].map(func => {
|
||||
return {
|
||||
match: new RegExp(`${func}:function\\(\\i\\){`),
|
||||
match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"),
|
||||
replace: "$&return true;"
|
||||
};
|
||||
})
|
||||
@ -650,15 +651,11 @@ export default definePlugin({
|
||||
},
|
||||
|
||||
async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) {
|
||||
const [{ parseURL }, {
|
||||
GIFEncoder,
|
||||
quantize,
|
||||
applyPalette
|
||||
}] = await Promise.all([importApngJs(), getGifEncoder()]);
|
||||
const { parseURL } = importApngJs();
|
||||
|
||||
const { frames, width, height } = await parseURL(stickerLink);
|
||||
|
||||
const gif = new GIFEncoder();
|
||||
const gif = GIFEncoder();
|
||||
const resolution = Settings.plugins.FakeNitro.stickerSize;
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
|
6
src/plugins/favEmojiFirst/README.md
Normal file
6
src/plugins/favEmojiFirst/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
# FavoriteEmojiFirst
|
||||
|
||||
Puts your favorite emoji first in the emoji autocomplete.
|
||||
|
||||
![FavEmojis](https://i.imgur.com/mEFCoZG.png)
|
||||
![Example](https://i.imgur.com/wY3Tc43.png)
|
5
src/plugins/favGifSearch/README.md
Normal file
5
src/plugins/favGifSearch/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# FavoriteGifSearch
|
||||
|
||||
Adds a search bar to favorite gifs.
|
||||
|
||||
![Screenshot](https://i.imgur.com/Bcgb7PD.png)
|
@ -60,7 +60,7 @@ interface Instance {
|
||||
}
|
||||
|
||||
|
||||
const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchHeader", "gutterSize");
|
||||
const containerClasses: { searchBar: string; } = findByPropsLazy("searchBar", "searchHeader", "searchInput");
|
||||
|
||||
export const settings = definePluginSettings({
|
||||
searchOption: {
|
||||
@ -87,7 +87,7 @@ export const settings = definePluginSettings({
|
||||
export default definePlugin({
|
||||
name: "FavoriteGifSearch",
|
||||
authors: [Devs.Aria],
|
||||
description: "Adds a search bar for favorite gifs",
|
||||
description: "Adds a search bar to favorite gifs.",
|
||||
|
||||
patches: [
|
||||
{
|
@ -72,7 +72,7 @@ export default definePlugin({
|
||||
async buildCss() {
|
||||
const elements = [...hiddenMessages].map(id => `#message-accessories-${id}`).join(",");
|
||||
style.textContent = `
|
||||
:is(${elements}) :is([class*="embedWrapper", [class*"clickableSticker"]) {
|
||||
:is(${elements}) :is([class*="embedWrapper"], [class*="clickableSticker"]) {
|
||||
/* important is not necessary, but add it to make sure bad themes won't break it */
|
||||
display: none !important;
|
||||
}
|
||||
|
6
src/plugins/imageZoom/README.md
Normal file
6
src/plugins/imageZoom/README.md
Normal file
@ -0,0 +1,6 @@
|
||||
# ImageZoom
|
||||
|
||||
Lets you zoom in to images and gifs. Use scroll wheel to zoom in and shift + scroll wheel to increase lens radius / size
|
||||
|
||||
![Example](https://i.imgur.com/VJdo4aq.png)
|
||||
![ContextMenu](https://i.imgur.com/0oaRM2s.png)
|
@ -99,6 +99,15 @@ const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => {
|
||||
ContextMenu.close();
|
||||
}}
|
||||
/>
|
||||
<Menu.MenuCheckboxItem
|
||||
id="vc-nearest-neighbour"
|
||||
label="Nearset Neighbour"
|
||||
checked={settings.store.nearestNeighbour}
|
||||
action={() => {
|
||||
settings.store.nearestNeighbour = !settings.store.nearestNeighbour;
|
||||
ContextMenu.close();
|
||||
}}
|
||||
/>
|
||||
<Menu.MenuControlItem
|
||||
id="vc-zoom"
|
||||
label="Zoom"
|
||||
|
@ -28,7 +28,7 @@ import { Button, Forms, React, TextInput } from "@webpack/common";
|
||||
import { decrypt } from "../index";
|
||||
|
||||
export function DecModal(props: any) {
|
||||
const secret: string = props?.message?.content;
|
||||
const encryptedMessage: string = props?.message?.content;
|
||||
const [password, setPassword] = React.useState("password");
|
||||
|
||||
return (
|
||||
@ -38,9 +38,9 @@ export function DecModal(props: any) {
|
||||
</ModalHeader>
|
||||
|
||||
<ModalContent>
|
||||
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Secret</Forms.FormTitle>
|
||||
<TextInput defaultValue={secret} disabled={true}></TextInput>
|
||||
<Forms.FormTitle tag="h5">Password</Forms.FormTitle>
|
||||
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Message with Encryption</Forms.FormTitle>
|
||||
<TextInput defaultValue={encryptedMessage} disabled={true}></TextInput>
|
||||
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Password</Forms.FormTitle>
|
||||
<TextInput
|
||||
style={{ marginBottom: "20px" }}
|
||||
onChange={setPassword}
|
||||
@ -51,7 +51,7 @@ export function DecModal(props: any) {
|
||||
<Button
|
||||
color={Button.Colors.GREEN}
|
||||
onClick={() => {
|
||||
const toSend = decrypt(secret, password, true);
|
||||
const toSend = decrypt(encryptedMessage, password, true);
|
||||
if (!toSend || !props?.message) return;
|
||||
// @ts-expect-error
|
||||
Vencord.Plugins.plugins.InvisibleChat.buildEmbed(props?.message, toSend);
|
@ -225,8 +225,8 @@ export function encrypt(secret: string, password: string, cover: string): string
|
||||
return steggo.hide(secret + "\u200b", password, cover);
|
||||
}
|
||||
|
||||
export function decrypt(secret: string, password: string, removeIndicator: boolean): string {
|
||||
const decrypted = steggo.reveal(secret, password);
|
||||
export function decrypt(encrypted: string, password: string, removeIndicator: boolean): string {
|
||||
const decrypted = steggo.reveal(encrypted, password);
|
||||
return removeIndicator ? decrypted.replace("\u200b", "") : decrypted;
|
||||
}
|
||||
|
@ -72,6 +72,14 @@ const enum ActivityFlag {
|
||||
INSTANCE = 1 << 0,
|
||||
}
|
||||
|
||||
const enum NameFormat {
|
||||
StatusName = "status-name",
|
||||
ArtistFirst = "artist-first",
|
||||
SongFirst = "song-first",
|
||||
ArtistOnly = "artist",
|
||||
SongOnly = "song"
|
||||
}
|
||||
|
||||
const applicationId = "1108588077900898414";
|
||||
const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f";
|
||||
|
||||
@ -117,10 +125,37 @@ const settings = definePluginSettings({
|
||||
default: true,
|
||||
},
|
||||
statusName: {
|
||||
description: "text shown in status",
|
||||
description: "custom status text",
|
||||
type: OptionType.STRING,
|
||||
default: "some music",
|
||||
},
|
||||
nameFormat: {
|
||||
description: "Show name of song and artist in status name",
|
||||
type: OptionType.SELECT,
|
||||
options: [
|
||||
{
|
||||
label: "Use custom status name",
|
||||
value: NameFormat.StatusName,
|
||||
default: true
|
||||
},
|
||||
{
|
||||
label: "Use format 'artist - song'",
|
||||
value: NameFormat.ArtistFirst
|
||||
},
|
||||
{
|
||||
label: "Use format 'song - artist'",
|
||||
value: NameFormat.SongFirst
|
||||
},
|
||||
{
|
||||
label: "Use artist name only",
|
||||
value: NameFormat.ArtistOnly
|
||||
},
|
||||
{
|
||||
label: "Use song name only",
|
||||
value: NameFormat.SongOnly
|
||||
}
|
||||
],
|
||||
},
|
||||
useListeningStatus: {
|
||||
description: 'show "Listening to" status instead of "Playing"',
|
||||
type: OptionType.BOOLEAN,
|
||||
@ -140,13 +175,13 @@ const settings = definePluginSettings({
|
||||
value: "placeholder"
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "LastFMRichPresence",
|
||||
description: "Little plugin for Last.fm rich presence",
|
||||
authors: [Devs.dzshn, Devs.RuiNtD],
|
||||
authors: [Devs.dzshn, Devs.RuiNtD, Devs.blahajZip, Devs.archeruwu],
|
||||
|
||||
settingsAboutComponent: () => (
|
||||
<>
|
||||
@ -267,9 +302,24 @@ export default definePlugin({
|
||||
url: `https://www.last.fm/user/${settings.store.username}`,
|
||||
});
|
||||
|
||||
const statusName = (() => {
|
||||
switch (settings.store.nameFormat) {
|
||||
case NameFormat.ArtistFirst:
|
||||
return trackData.artist + " - " + trackData.name;
|
||||
case NameFormat.SongFirst:
|
||||
return trackData.name + " - " + trackData.artist;
|
||||
case NameFormat.ArtistOnly:
|
||||
return trackData.artist;
|
||||
case NameFormat.SongOnly:
|
||||
return trackData.name;
|
||||
default:
|
||||
return settings.store.statusName;
|
||||
}
|
||||
})();
|
||||
|
||||
return {
|
||||
application_id: applicationId,
|
||||
name: settings.store.statusName,
|
||||
name: statusName,
|
||||
|
||||
details: trackData.name,
|
||||
state: trackData.artist,
|
||||
|
@ -56,7 +56,8 @@ const quotes = [
|
||||
"hd{b${",
|
||||
"<;vqkijbq33271:56<3799?24944:",
|
||||
"Thof$lu'ofdn,!qsefc'az*bnrcma+&Om{o+iu\"`khct$)bnrd\"bcdoi&",
|
||||
"snofplkb{)c'r\"lod'|f*aurv#cpno`abchijklmno"
|
||||
"snofplkb{)c'r\"lod'|f*aurv#cpno`abchijklmno",
|
||||
"Wdn`khc'|f*eghl{%"
|
||||
];
|
||||
|
||||
export default definePlugin({
|
||||
|
@ -206,6 +206,7 @@ export default definePlugin({
|
||||
ignoreSelf && message.author?.id === myId ||
|
||||
ignoreUsers.includes(message.author?.id) ||
|
||||
ignoreChannels.includes(message.channel_id) ||
|
||||
ignoreChannels.includes(ChannelStore.getChannel(message.channel_id)?.parent_id) ||
|
||||
ignoreGuilds.includes(ChannelStore.getChannel(message.channel_id)?.guild_id);
|
||||
},
|
||||
|
||||
@ -325,14 +326,14 @@ export default definePlugin({
|
||||
{
|
||||
// Attachment renderer
|
||||
// Module 96063
|
||||
find: "[\"className\",\"attachment\",\"inlineMedia\"",
|
||||
find: "().removeAttachmentHoverButton",
|
||||
replacement: [
|
||||
{
|
||||
match: /((\w)\.className,\w=\2\.attachment),/,
|
||||
replace: "$1,deleted=$2.attachment?.deleted,"
|
||||
},
|
||||
{
|
||||
match: /\["className","attachment","inlineMedia".+?className:/,
|
||||
match: /\["className","attachment".+?className:/,
|
||||
replace: "$& (deleted ? 'messagelogger-deleted-attachment ' : '') +"
|
||||
}
|
||||
]
|
||||
|
@ -185,7 +185,7 @@ const settings = definePluginSettings({
|
||||
export default definePlugin({
|
||||
name: "MoreUserTags",
|
||||
description: "Adds tags for webhooks and moderative roles (owner, admin, etc.)",
|
||||
authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias],
|
||||
authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN],
|
||||
settings,
|
||||
patches: [
|
||||
// add tags to the tag list
|
||||
@ -232,9 +232,8 @@ export default definePlugin({
|
||||
{
|
||||
find: ".renderBot=function(){",
|
||||
replacement: {
|
||||
match: /this.props.user;return null!=(\i)&&.{0,10}\?(.{0,50})\.botTag/,
|
||||
replace: "this.props.user;var type=$self.getTag({...this.props,origType:$1.bot?0:null,location:'not-chat'});\
|
||||
return type!==null?$2.botTag,type"
|
||||
match: /\.BOT;return null!=(\i)&&.{0,10}\?(.{0,50})\.botTag,type:\i/,
|
||||
replace: ".BOT;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
|
||||
|
@ -18,10 +18,10 @@
|
||||
|
||||
import { ApplicationCommandInputType, ApplicationCommandOptionType, Argument, CommandContext, findOption, sendBotMessage } from "@api/Commands";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getGifEncoder } from "@utils/dependencies";
|
||||
import { makeLazy } from "@utils/lazy";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByCodeLazy, findByPropsLazy } from "@webpack";
|
||||
import { applyPalette, GIFEncoder, quantize } from "gifenc";
|
||||
|
||||
const DRAFT_TYPE = 0;
|
||||
const DEFAULT_DELAY = 20;
|
||||
@ -124,7 +124,6 @@ export default definePlugin({
|
||||
}
|
||||
],
|
||||
execute: async (opts, cmdCtx) => {
|
||||
const { GIFEncoder, quantize, applyPalette } = await getGifEncoder();
|
||||
const frames = await getFrames();
|
||||
|
||||
const noServerPfp = findOption(opts, "no-server-pfp", false);
|
||||
@ -143,7 +142,7 @@ export default definePlugin({
|
||||
const delay = findOption(opts, "delay", DEFAULT_DELAY);
|
||||
const resolution = findOption(opts, "resolution", DEFAULT_RESOLUTION);
|
||||
|
||||
const gif = new GIFEncoder();
|
||||
const gif = GIFEncoder();
|
||||
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = canvas.height = resolution;
|
||||
|
83
src/plugins/pictureInPicture.tsx
Normal file
83
src/plugins/pictureInPicture.tsx
Normal file
@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2023 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { React, Tooltip } from "@webpack/common";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
loop: {
|
||||
description: "Whether to make the PiP video loop or not",
|
||||
type: OptionType.BOOLEAN,
|
||||
default: true,
|
||||
restartNeeded: false
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "PictureInPicture",
|
||||
description: "Adds picture in picture to videos (next to the Download button)",
|
||||
authors: [Devs.Lumap],
|
||||
settings,
|
||||
|
||||
patches: [
|
||||
{
|
||||
find: ".onRemoveAttachment,",
|
||||
replacement: {
|
||||
match: /\.nonMediaAttachment,!(\i).{0,7}children:\[(\i),/,
|
||||
replace: "$&$1&&$2&&$self.renderPiPButton(),"
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
renderPiPButton: ErrorBoundary.wrap(() => {
|
||||
return (
|
||||
<Tooltip text="Toggle Picture in Picture">
|
||||
{tooltipProps => (
|
||||
<div
|
||||
{...tooltipProps}
|
||||
role="button"
|
||||
style={{
|
||||
cursor: "pointer",
|
||||
paddingTop: "4px",
|
||||
paddingLeft: "4px",
|
||||
paddingRight: "4px",
|
||||
}}
|
||||
onClick={e => {
|
||||
const video = e.currentTarget.parentNode!.parentNode!.querySelector("video")!;
|
||||
const videoClone = document.body.appendChild(video.cloneNode(true)) as HTMLVideoElement;
|
||||
|
||||
videoClone.loop = settings.store.loop;
|
||||
videoClone.style.display = "none";
|
||||
videoClone.onleavepictureinpicture = () => videoClone.remove();
|
||||
|
||||
function launchPiP() {
|
||||
videoClone.currentTime = video.currentTime;
|
||||
videoClone.requestPictureInPicture();
|
||||
video.pause();
|
||||
videoClone.play();
|
||||
}
|
||||
|
||||
if (videoClone.readyState === 4 /* HAVE_ENOUGH_DATA */)
|
||||
launchPiP();
|
||||
else
|
||||
videoClone.onloadedmetadata = launchPiP;
|
||||
}}
|
||||
>
|
||||
<svg width="24px" height="24px" viewBox="0 0 24 24">
|
||||
<path
|
||||
fill="var(--interactive-normal)"
|
||||
d="M21 3a1 1 0 0 1 1 1v7h-2V5H4v14h6v2H3a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h18zm0 10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-8a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1h8zm-1 2h-6v4h6v-4z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</Tooltip>
|
||||
);
|
||||
}, { noop: true })
|
||||
});
|
5
src/plugins/previewMessage/README.md
Normal file
5
src/plugins/previewMessage/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# PreviewMessage
|
||||
|
||||
Lets you preview your message before sending it.
|
||||
|
||||
![Example](https://i.imgur.com/etqbkzu.png)
|
@ -16,37 +16,96 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { sendBotMessage } from "@api/Commands";
|
||||
import { generateId, sendBotMessage } from "@api/Commands";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, ButtonLooks, ButtonWrapperClasses, DraftStore, DraftType, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
|
||||
import { MessageAttachment } from "discord-types/general";
|
||||
|
||||
interface Props {
|
||||
type: {
|
||||
analyticsName: string;
|
||||
isEmpty: boolean;
|
||||
attachments: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
const UploadStore = findByPropsLazy("getUploads");
|
||||
|
||||
const getDraft = (channelId: string) => DraftStore.getDraft(channelId, DraftType.ChannelMessage);
|
||||
|
||||
|
||||
const getImageBox = (url: string): Promise<{ width: number, height: number; } | null> =>
|
||||
new Promise(res => {
|
||||
const img = new Image();
|
||||
img.onload = () =>
|
||||
res({ width: img.width, height: img.height });
|
||||
|
||||
img.onerror = () =>
|
||||
res(null);
|
||||
|
||||
img.src = url;
|
||||
});
|
||||
|
||||
|
||||
const getAttachments = async (channelId: string) =>
|
||||
await Promise.all(
|
||||
UploadStore.getUploads(channelId, DraftType.ChannelMessage)
|
||||
.map(async (upload: any) => {
|
||||
const { isImage, filename, spoiler, item: { file } } = upload;
|
||||
const url = URL.createObjectURL(file);
|
||||
const attachment: MessageAttachment = {
|
||||
id: generateId(),
|
||||
filename: spoiler ? "SPOILER_" + filename : filename,
|
||||
// weird eh? if i give it the normal content type the preview doenst work
|
||||
content_type: undefined,
|
||||
size: await upload.getSize(),
|
||||
spoiler,
|
||||
// discord adds query params to the url, so we need to add a hash to prevent that
|
||||
url: url + "#",
|
||||
proxy_url: url + "#",
|
||||
};
|
||||
|
||||
if (isImage) {
|
||||
const box = await getImageBox(url);
|
||||
if (!box) return attachment;
|
||||
|
||||
attachment.width = box.width;
|
||||
attachment.height = box.height;
|
||||
}
|
||||
|
||||
return attachment;
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
export function PreviewButton(chatBoxProps: Props) {
|
||||
const { isEmpty, attachments } = chatBoxProps.type;
|
||||
|
||||
const channelId = SelectedChannelStore.getChannelId();
|
||||
const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
|
||||
|
||||
if (chatBoxProps.type.analyticsName !== "normal") return null;
|
||||
if (!draft) return null;
|
||||
|
||||
const hasAttachments = attachments && UploadStore.getUploads(channelId, DraftType.ChannelMessage).length > 0;
|
||||
const hasContent = !isEmpty && draft?.length > 0;
|
||||
|
||||
if (!hasContent && !hasAttachments) return null;
|
||||
|
||||
return (
|
||||
<Tooltip text="Preview Message">
|
||||
{tooltipProps => (
|
||||
<Button
|
||||
{...tooltipProps}
|
||||
onClick={() =>
|
||||
onClick={async () =>
|
||||
sendBotMessage(
|
||||
channelId,
|
||||
{
|
||||
content: getDraft(channelId),
|
||||
author: UserStore.getCurrentUser()
|
||||
author: UserStore.getCurrentUser(),
|
||||
attachments: hasAttachments ? await getAttachments(channelId) : undefined,
|
||||
}
|
||||
)}
|
||||
size=""
|
||||
@ -66,7 +125,7 @@ export function PreviewButton(chatBoxProps: Props) {
|
||||
|
||||
export default definePlugin({
|
||||
name: "PreviewMessage",
|
||||
description: "Lets you preview your message before sending it",
|
||||
description: "Lets you preview your message before sending it.",
|
||||
authors: [Devs.Aria],
|
||||
patches: [
|
||||
{
|
@ -21,14 +21,11 @@ import { VENCORD_USER_AGENT } from "@utils/constants";
|
||||
import { debounce } from "@utils/debounce";
|
||||
import { getCurrentChannel } from "@utils/discord";
|
||||
import { useAwaiter } from "@utils/react";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { UserStore } from "@webpack/common";
|
||||
import { UserProfileStore, UserStore } from "@webpack/common";
|
||||
|
||||
import { settings } from "./settings";
|
||||
import { PronounCode, PronounMapping, PronounsResponse } from "./types";
|
||||
|
||||
const UserProfileStore = findStoreLazy("UserProfileStore");
|
||||
|
||||
type PronounsWithSource = [string | null, string];
|
||||
const EmptyPronouns: PronounsWithSource = [null, ""];
|
||||
|
||||
|
@ -145,7 +145,7 @@ export default definePlugin({
|
||||
find: "window.webkitAudioContext",
|
||||
replacement: {
|
||||
match: /(?<=\i\.acquire=function\((\i)\)\{return )navigator\.mediaDevices\.getUserMedia\(\1\)(?=\})/,
|
||||
replace: m => `${m}.then(stream => $self.connectRnnoise(stream))`
|
||||
replace: "$&.then(stream => $self.connectRnnoise(stream, $1.audio))"
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -182,7 +182,8 @@ export default definePlugin({
|
||||
|
||||
setEnabled,
|
||||
isEnabled: () => settings.store.isEnabled,
|
||||
async connectRnnoise(stream: MediaStream): Promise<MediaStream> {
|
||||
async connectRnnoise(stream: MediaStream, isAudio: boolean): Promise<MediaStream> {
|
||||
if (!isAudio) return stream;
|
||||
if (!settings.store.isEnabled) return stream;
|
||||
|
||||
const audioCtx = new AudioContext();
|
||||
|
5
src/plugins/searchReply/README.md
Normal file
5
src/plugins/searchReply/README.md
Normal file
@ -0,0 +1,5 @@
|
||||
# SearchReply
|
||||
|
||||
Adds a reply button to search results.
|
||||
|
||||
![Screenshot](https://i.imgur.com/SjIEHpw.png)
|
@ -27,13 +27,12 @@ import { copyWithToast } from "@utils/misc";
|
||||
import { LazyComponent } from "@utils/react";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByCode, findByCodeLazy, findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { Text, Tooltip } from "@webpack/common";
|
||||
import { Text, Tooltip, UserProfileStore } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
|
||||
import { VerifiedIcon } from "./VerifiedIcon";
|
||||
|
||||
const Section = LazyComponent(() => findByCode("().lastSection"));
|
||||
const UserProfileStore = findStoreLazy("UserProfileStore");
|
||||
const ThemeStore = findStoreLazy("ThemeStore");
|
||||
const platforms: { get(type: string): ConnectionPlatform; } = findByPropsLazy("isSupported", "getByUrl");
|
||||
const getTheme: (user: User, displayProfile: any) => any = findByCodeLazy(',"--profile-gradient-primary-color"');
|
||||
|
@ -213,15 +213,12 @@ function applyRules(content: string): string {
|
||||
if (content.length === 0)
|
||||
return content;
|
||||
|
||||
// pad so that rules can use " word " to only match whole "word"
|
||||
content = " " + content + " ";
|
||||
|
||||
if (stringRules) {
|
||||
for (const rule of stringRules) {
|
||||
if (!rule.find || !rule.replace) continue;
|
||||
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;
|
||||
|
||||
content = content.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n"));
|
||||
content = ` ${content} `.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n")).replace(/^\s|\s$/g, "");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,6 +80,9 @@ export default definePlugin({
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
data,
|
||||
|
||||
settingsAboutComponent: () => {
|
||||
return (
|
||||
<Link href="https://github.com/AutumnVN/usrbg#how-to-request-your-own-usrbg-banner">CLICK HERE TO GET YOUR OWN BANNER</Link>
|
||||
@ -116,7 +119,9 @@ export default definePlugin({
|
||||
enableStyle(style);
|
||||
|
||||
const res = await fetch(BASE_URL);
|
||||
if (res.ok)
|
||||
if (res.ok) {
|
||||
data = await res.json();
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -19,6 +19,7 @@
|
||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { addButton, removeButton } from "@api/MessagePopover";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { CodeBlock } from "@components/CodeBlock";
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Devs } from "@utils/constants";
|
||||
@ -26,14 +27,12 @@ import { Margins } from "@utils/margins";
|
||||
import { copyWithToast } from "@utils/misc";
|
||||
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { Button, ChannelStore, Forms, Menu, Parser, Text } from "@webpack/common";
|
||||
import { Button, ChannelStore, Forms, Menu, Text } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
const CodeContainerClasses = findByPropsLazy("markup", "codeContainer");
|
||||
|
||||
const CopyIcon = () => {
|
||||
return <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" width="22" height="22">
|
||||
return <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" width="18" height="18">
|
||||
<path d="M12.9297 3.25007C12.7343 3.05261 12.4154 3.05226 12.2196 3.24928L11.5746 3.89824C11.3811 4.09297 11.3808 4.40733 11.5739 4.60245L16.5685 9.64824C16.7614 9.84309 16.7614 10.1569 16.5685 10.3517L11.5739 15.3975C11.3808 15.5927 11.3811 15.907 11.5746 16.1017L12.2196 16.7507C12.4154 16.9477 12.7343 16.9474 12.9297 16.7499L19.2604 10.3517C19.4532 10.1568 19.4532 9.84314 19.2604 9.64832L12.9297 3.25007Z" />
|
||||
<path d="M8.42616 4.60245C8.6193 4.40733 8.61898 4.09297 8.42545 3.89824L7.78047 3.24928C7.58466 3.05226 7.26578 3.05261 7.07041 3.25007L0.739669 9.64832C0.5469 9.84314 0.546901 10.1568 0.739669 10.3517L7.07041 16.7499C7.26578 16.9474 7.58465 16.9477 7.78047 16.7507L8.42545 16.1017C8.61898 15.907 8.6193 15.5927 8.42616 15.3975L3.43155 10.3517C3.23869 10.1569 3.23869 9.84309 3.43155 9.64824L8.42616 4.60245Z" />
|
||||
</svg>;
|
||||
@ -61,14 +60,6 @@ function cleanMessage(msg: Message) {
|
||||
return clone;
|
||||
}
|
||||
|
||||
function CodeBlock(props: { content: string, lang: string; }) {
|
||||
return (
|
||||
<div className={CodeContainerClasses.markup}>
|
||||
{Parser.defaultRules.codeBlock.react(props, null, {})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function openViewRawModal(json: string, type: string, msgContent?: string) {
|
||||
const key = openModal(props => (
|
||||
<ErrorBoundary>
|
||||
@ -128,6 +119,7 @@ const settings = definePluginSettings({
|
||||
|
||||
function MakeContextCallback(name: string) {
|
||||
const callback: NavContextMenuPatchCallback = (children, props) => () => {
|
||||
if (name === "Guild" && !props.guild) return;
|
||||
const lastChild = children.at(-1);
|
||||
if (lastChild?.key === "developer-actions") {
|
||||
const p = lastChild.props;
|
||||
|
@ -19,7 +19,6 @@
|
||||
import "./styles.css";
|
||||
|
||||
import { addContextMenuPatch, NavContextMenuPatchCallback, removeContextMenuPatch } from "@api/ContextMenu";
|
||||
import { Flex } from "@components/Flex";
|
||||
import { Microphone } from "@components/Icons";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, openModal } from "@utils/modal";
|
||||
@ -39,6 +38,7 @@ import { VoiceRecorderWeb } from "./WebRecorder";
|
||||
const CloudUpload = findLazy(m => m.prototype?.uploadFileToCloud);
|
||||
const MessageCreator = findByPropsLazy("getSendMessageOptionsForReply", "sendMessage");
|
||||
const PendingReplyStore = findStoreLazy("PendingReplyStore");
|
||||
const OptionClasses = findByPropsLazy("optionName", "optionIcon", "optionLabel");
|
||||
|
||||
export type VoiceRecorder = ComponentType<{
|
||||
setAudioBlob(blob: Blob): void;
|
||||
@ -226,12 +226,10 @@ const ctxMenuPatch: NavContextMenuPatchCallback = (children, props) => () => {
|
||||
<Menu.MenuItem
|
||||
id="vc-send-vmsg"
|
||||
label={
|
||||
<>
|
||||
<Flex flexDirection="row" style={{ alignItems: "center", gap: 8 }}>
|
||||
<Microphone height={24} width={24} />
|
||||
Send voice message
|
||||
</Flex>
|
||||
</>
|
||||
<div className={OptionClasses.optionLabel}>
|
||||
<Microphone className={OptionClasses.optionIcon} height={24} width={24} />
|
||||
<div className={OptionClasses.optionName}>Send voice message</div>
|
||||
</div>
|
||||
}
|
||||
action={() => openModal(modalProps => <Modal modalProps={modalProps} />)}
|
||||
/>
|
||||
|
1168
src/utils/apng-canvas.js
Normal file
1168
src/utils/apng-canvas.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -355,6 +355,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||
name: "bb010g",
|
||||
id: 72791153467990016n,
|
||||
},
|
||||
Lumap: {
|
||||
name: "lumap",
|
||||
id: 635383782576357407n
|
||||
},
|
||||
Dolfies: {
|
||||
name: "Dolfies",
|
||||
id: 852892297661906993n,
|
||||
@ -363,6 +367,14 @@ export const Devs = /* #__PURE__*/ Object.freeze({
|
||||
name: "RuukuLada",
|
||||
id: 119705748346241027n,
|
||||
},
|
||||
blahajZip: {
|
||||
name: "blahaj.zip",
|
||||
id: 683954422241427471n,
|
||||
},
|
||||
archeruwu: {
|
||||
name: "archer_uwu",
|
||||
id: 160068695383736320n
|
||||
}
|
||||
} satisfies Record<string, Dev>);
|
||||
|
||||
// iife so #__PURE__ works correctly
|
||||
|
@ -17,23 +17,15 @@
|
||||
*/
|
||||
|
||||
import { makeLazy } from "./lazy";
|
||||
import { EXTENSION_BASE_URL } from "./web-metadata";
|
||||
|
||||
/*
|
||||
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
|
||||
export const importApngJs = makeLazy(async () => {
|
||||
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>; };
|
||||
export const importApngJs = makeLazy(() => {
|
||||
return require("./apng-canvas").APNG as { parseURL(url: string): Promise<ApngFrameData>; };
|
||||
});
|
||||
|
||||
// https://wiki.mozilla.org/APNG_Specification#.60fcTL.60:_The_Frame_Control_Chunk
|
||||
@ -75,13 +67,20 @@ export interface ApngFrameData {
|
||||
playTime: number;
|
||||
}
|
||||
|
||||
const shikiWorkerDist = "https://unpkg.com/@vap/shiki-worker@0.0.8/dist";
|
||||
export const shikiWorkerSrc = `${shikiWorkerDist}/${IS_DEV ? "index.js" : "index.min.js"}`;
|
||||
export const shikiOnigasmSrc = "https://unpkg.com/@vap/shiki@0.10.3/dist/onig.wasm";
|
||||
|
||||
export const rnnoiseDist = "https://unpkg.com/@sapphi-red/web-noise-suppressor@0.3.3/dist";
|
||||
// On web (extensions), use extension uri as basepath (load files from extension)
|
||||
// On desktop (electron), load from cdn
|
||||
export const rnnoiseDist = IS_EXTENSION
|
||||
? new URL("/third-party/rnnoise", EXTENSION_BASE_URL).toString()
|
||||
: "https://unpkg.com/@sapphi-red/web-noise-suppressor@0.3.3/dist";
|
||||
export const rnnoiseWasmSrc = (simd = false) => `${rnnoiseDist}/rnnoise${simd ? "_simd" : ""}.wasm`;
|
||||
export const rnnoiseWorkletSrc = `${rnnoiseDist}/rnnoise/workletProcessor.js`;
|
||||
|
||||
// @ts-expect-error SHUT UP
|
||||
export const getStegCloak = makeLazy(() => import("https://unpkg.com/stegcloak-dist@1.0.0/index.js"));
|
||||
|
||||
// The below code is only used on the Desktop (electron) build of Vencord.
|
||||
// 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"));
|
||||
|
@ -18,7 +18,7 @@
|
||||
|
||||
import { MessageObject } from "@api/MessageEvents";
|
||||
import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack";
|
||||
import { ChannelStore, ComponentDispatch, GuildStore, MaskedLink, ModalImageClasses, PrivateChannelsStore, SelectedChannelStore, SelectedGuildStore, UserUtils } from "@webpack/common";
|
||||
import { ChannelStore, ComponentDispatch, FluxDispatcher, GuildStore, MaskedLink, ModalImageClasses, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileStore, UserUtils } from "@webpack/common";
|
||||
import { Guild, Message, User } from "discord-types/general";
|
||||
|
||||
import { ImageModal, ModalRoot, ModalSize, openModal } from "./modal";
|
||||
@ -118,6 +118,41 @@ export async function openUserProfile(id: string) {
|
||||
});
|
||||
}
|
||||
|
||||
interface FetchUserProfileOptions {
|
||||
friend_token?: string;
|
||||
connections_role_id?: string;
|
||||
guild_id?: string;
|
||||
with_mutual_guilds?: boolean;
|
||||
with_mutual_friends_count?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a user's profile
|
||||
*/
|
||||
export async function fetchUserProfile(id: string, options?: FetchUserProfileOptions) {
|
||||
const cached = UserProfileStore.getUserProfile(id);
|
||||
if (cached) return cached;
|
||||
|
||||
FluxDispatcher.dispatch({ type: "USER_PROFILE_FETCH_START", userId: id });
|
||||
|
||||
const { body } = await RestAPI.get({
|
||||
url: `/users/${id}/profile`,
|
||||
query: {
|
||||
with_mutual_guilds: false,
|
||||
with_mutual_friends_count: false,
|
||||
...options
|
||||
},
|
||||
oldFormErrors: true,
|
||||
});
|
||||
|
||||
FluxDispatcher.dispatch({ type: "USER_UPDATE", user: body.user });
|
||||
await FluxDispatcher.dispatch({ type: "USER_PROFILE_FETCH_SUCCESS", ...body });
|
||||
if (options?.guild_id && body.guild_member)
|
||||
FluxDispatcher.dispatch({ type: "GUILD_MEMBER_PROFILE_UPDATE", guildId: options.guild_id, guildMember: body.guild_member });
|
||||
|
||||
return UserProfileStore.getUserProfile(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique username for a user. Returns user.username for pomelo people, user.tag otherwise
|
||||
*/
|
||||
|
14
src/utils/web-metadata.ts
Normal file
14
src/utils/web-metadata.ts
Normal file
@ -0,0 +1,14 @@
|
||||
/*
|
||||
* 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!;
|
||||
}
|
@ -48,6 +48,7 @@ export let PoggerModeSettingsStore: GenericStore;
|
||||
|
||||
export let GuildStore: Stores.GuildStore & t.FluxStore;
|
||||
export let UserStore: Stores.UserStore & t.FluxStore;
|
||||
export let UserProfileStore: GenericStore;
|
||||
export let SelectedChannelStore: Stores.SelectedChannelStore & t.FluxStore;
|
||||
export let SelectedGuildStore: t.FluxStore & Record<string, any>;
|
||||
export let ChannelStore: Stores.ChannelStore & t.FluxStore;
|
||||
@ -86,6 +87,7 @@ export const useStateFromStores: <T>(
|
||||
|
||||
waitForStore("DraftStore", s => DraftStore = s);
|
||||
waitForStore("UserStore", s => UserStore = s);
|
||||
waitForStore("UserProfileStore", m => UserProfileStore = m);
|
||||
waitForStore("ChannelStore", m => ChannelStore = m);
|
||||
waitForStore("SelectedChannelStore", m => SelectedChannelStore = m);
|
||||
waitForStore("SelectedGuildStore", m => SelectedGuildStore = m);
|
||||
|
16
src/webpack/common/types/components.d.ts
vendored
16
src/webpack/common/types/components.d.ts
vendored
@ -398,12 +398,18 @@ export type Paginator = ComponentType<{
|
||||
hideMaxPage?: boolean;
|
||||
}>;
|
||||
|
||||
export type MaskedLink = ComponentType<{
|
||||
onClick(): void;
|
||||
trusted: boolean;
|
||||
title: string,
|
||||
export type MaskedLink = ComponentType<PropsWithChildren<{
|
||||
href: string;
|
||||
}>;
|
||||
rel?: string;
|
||||
target?: string;
|
||||
title?: string,
|
||||
className?: string;
|
||||
tabIndex?: number;
|
||||
onClick?(): void;
|
||||
trusted?: boolean;
|
||||
messageId?: string;
|
||||
channelId?: string;
|
||||
}>>;
|
||||
|
||||
export type ScrollerThin = ComponentType<PropsWithChildren<{
|
||||
className?: string;
|
||||
|
9
src/webpack/common/types/menu.d.ts
vendored
9
src/webpack/common/types/menu.d.ts
vendored
@ -65,8 +65,13 @@ export interface Menu {
|
||||
id: string;
|
||||
interactive?: boolean;
|
||||
}>;
|
||||
// TODO: Type me
|
||||
MenuSliderControl: RC<any>;
|
||||
MenuSliderControl: RC<{
|
||||
minValue: number,
|
||||
maxValue: number,
|
||||
value: number,
|
||||
onChange(value: number): void,
|
||||
renderValue?(value: number): string,
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface ContextMenuApi {
|
||||
|
Reference in New Issue
Block a user