Compare commits

...

17 Commits

Author SHA1 Message Date
V
12fe367385 bump to v1.5.0 2023-09-19 04:12:54 +02:00
AutumnVN
fc0417eb80 moreUserTags: fix user tag in member list (#1730) 2023-09-19 04:11:30 +02:00
AutumnVN
1f87d14ab2 textReplace: pad space only in string rules (#1738) 2023-09-19 04:11:30 +02:00
XAlboX
7d8f3508a8 ClearURLs: Add si parameter on youtube.com (#1733)
Co-authored-by: V <vendicated@riseup.net>
2023-09-19 04:11:30 +02:00
V
41f5d71e38 Bundle dependencies with extensions for webstore rule compliance (#1740) 2023-09-19 04:11:27 +02:00
AutumnVN
efb88a4df8 favGifSearch: fix search bar (#1732) 2023-09-19 04:10:15 +02:00
Syncx
a73d09a2f0 PreviewMessage: Add attachments (& misc changes) (#1715) 2023-09-12 23:14:17 +02:00
AutumnVN
cf7c4d63b6 pictureInPicture: don't show PiP button on normal file (#1725) 2023-09-12 23:11:53 +02:00
Archer
a95311ef2c lastfm: Add setting for artist name and song title only (#1726)
Co-authored-by: V <vendicated@riseup.net>
2023-09-12 23:11:25 +02:00
lovenginx
dd23f9802c InvisibleChat: fixup decryption modal (#1720) 2023-09-12 23:04:50 +02:00
Nuckyz
f23ddf4cae oopsies 2023-09-12 05:10:44 -03:00
Nuckyz
4222c7fd9f Fix broken FakeNitro patch on canary 2023-09-12 05:01:18 -03:00
Nuckyz
09f65b401e Fix broken MessageLogger patches 2023-09-12 04:28:55 -03:00
V
7364776715 Dearrow: Fix button part 2 2023-09-09 19:51:30 +02:00
V
3a5b70d410 Dearrow: Fix button 2023-09-09 19:49:11 +02:00
V
e08d49edac New Plugin: Dearrow (#1723) 2023-09-09 19:17:50 +02:00
Mushrrom
17abbd3e3e LastFM: Add setting for using name + artist as activity name (#1713)
Co-authored-by: V <vendicated@riseup.net>
2023-09-09 04:22:41 +02:00
67 changed files with 2004 additions and 110 deletions

View File

@ -19,9 +19,11 @@
/// <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 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 * 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";
@ -80,6 +82,7 @@ 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 = () =>
@ -87,7 +90,7 @@ window.VencordNative = {
? "vs-light" ? "vs-light"
: "vs-dark"; : "vs-dark";
win.document.write(monacoHtml); win.document.write(IS_EXTENSION ? monacoHtmlLocal : monacoHtmlCdn);
}, },
}, },

View File

@ -4,6 +4,11 @@ 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/Vencord.js", "dist/Vencord.css"], "resources": ["dist/*", "third-party/*"],
"matches": ["*://*.discord.com/*"] "matches": ["*://*.discord.com/*"]
} }
], ],

43
browser/monaco.ts Normal file
View 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
View 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
View 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
View 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

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.4.7", "version": "1.5.0",
"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,10 +36,13 @@
"@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",
@ -64,7 +67,8 @@
"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,5 +1,9 @@
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
@ -24,6 +28,12 @@ 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
@ -32,6 +42,9 @@ 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
@ -107,6 +120,9 @@ 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:
@ -520,10 +536,31 @@ 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
@ -843,6 +880,10 @@ 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'}
@ -1867,6 +1908,10 @@ 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
@ -2184,6 +2229,12 @@ 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'}
@ -2354,6 +2405,10 @@ 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
@ -2511,6 +2566,10 @@ 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'}
@ -2662,6 +2721,11 @@ 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
@ -3377,6 +3441,17 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
dev: true dev: true
settings: /zip-local@0.3.5:
autoInstallPeers: true resolution: {integrity: sha512-GRV3D5TJY+/PqyeRm5CYBs7xVrKTKzljBoEXvocZu0HJ7tPEcgpSOYa2zFIsCZWgKWMuc4U3yMFgFkERGFIB9w==}
excludeLinksFromLockfile: false 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

View File

@ -78,6 +78,7 @@ await Promise.all([
define: { define: {
...defines, ...defines,
IS_WEB: false, IS_WEB: false,
IS_EXTENSION: false,
IS_DISCORD_DESKTOP: true, IS_DISCORD_DESKTOP: true,
IS_VESKTOP: false IS_VESKTOP: false
} }
@ -124,6 +125,7 @@ await Promise.all([
define: { define: {
...defines, ...defines,
IS_WEB: false, IS_WEB: false,
IS_EXTENSION: false,
IS_DISCORD_DESKTOP: false, IS_DISCORD_DESKTOP: false,
IS_VESKTOP: true IS_VESKTOP: true
} }

View File

@ -17,12 +17,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 esbuild from "esbuild"; import esbuild from "esbuild";
import { zip } from "fflate";
import { readFileSync } from "fs"; 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 { 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";
@ -42,6 +41,7 @@ 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,19 +52,51 @@ const commonOptions = {
} }
}; };
const MonacoWorkerEntryPoints = [
"vs/language/css/css.worker.js",
"vs/editor/editor.worker.js"
];
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: {
"window": "unsafeWindow", ...(commonOptions?.define),
...(commonOptions?.define) window: "unsafeWindow",
}, },
outfile: "dist/Vencord.user.js", outfile: "dist/Vencord.user.js",
banner: { 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 = { const entries = {
"dist/Vencord.js": await readFile("dist/browser.js"), "dist/Vencord.js": await readFile("dist/extension.js"),
"dist/Vencord.css": await readFile("dist/browser.css"), "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 => { ...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")) {
@ -100,21 +159,6 @@ 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 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);
@ -124,7 +168,6 @@ async function buildPluginZip(target, files, shouldZip) {
})); }));
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 => {
@ -142,8 +185,9 @@ const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content
await Promise.all([ await Promise.all([
appendCssRuntime, appendCssRuntime,
buildPluginZip("extension.zip", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"], true), buildExtension("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"]),
buildPluginZip("chromium-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"], false), buildExtension("firefox-unpacked", ["background.js", "content.js", "manifestv2.json", "icon.png"]),
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");

1
src/globals.d.ts vendored
View File

@ -33,6 +33,7 @@ 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/../components/monacoWin.html;base64"; import monacoHtml from "~fileContent/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

@ -27,7 +27,7 @@ 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 "./_core/settings"; import settings from "./settings";
const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss"; const REMEMBER_DISMISS_KEY = "Vencord-SupportHelper-Dismiss";

View File

@ -127,6 +127,8 @@ 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

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

View 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),"
}
]
}],
});

View 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;
}

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, getGifEncoder, importApngJs } from "@utils/dependencies"; import { ApngBlendOp, ApngDisposeOp, 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,6 +27,7 @@ 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;
@ -212,15 +213,15 @@ export default definePlugin({
} }
}, },
{ {
find: "canStreamHighQuality:function", find: "canUseHighVideoUploadQuality:function",
predicate: () => settings.store.enableStreamQualityBypass, predicate: () => settings.store.enableStreamQualityBypass,
replacement: [ replacement: [
"canUseHighVideoUploadQuality", "canUseHighVideoUploadQuality",
"canStreamHighQuality", // TODO: Remove the last two when they get removed from stable
"canStreamMidQuality" "(?:canStreamQuality|canStreamHighQuality|canStreamMidQuality)",
].map(func => { ].map(func => {
return { return {
match: new RegExp(`${func}:function\\(\\i\\){`), match: new RegExp(`${func}:function\\(\\i(?:,\\i)?\\){`, "g"),
replace: "$&return true;" replace: "$&return true;"
}; };
}) })
@ -650,15 +651,11 @@ export default definePlugin({
}, },
async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) { async sendAnimatedSticker(stickerLink: string, stickerId: string, channelId: string) {
const [{ parseURL }, { const { parseURL } = importApngJs();
GIFEncoder,
quantize,
applyPalette
}] = await Promise.all([importApngJs(), getGifEncoder()]);
const { frames, width, height } = await parseURL(stickerLink); const { frames, width, height } = await parseURL(stickerLink);
const gif = new GIFEncoder(); const gif = 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

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

View File

@ -0,0 +1,5 @@
# FavoriteGifSearch
Adds a search bar to favorite gifs.
![Screenshot](https://i.imgur.com/Bcgb7PD.png)

View File

@ -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({ export const settings = definePluginSettings({
searchOption: { searchOption: {
@ -87,7 +87,7 @@ export const settings = definePluginSettings({
export default definePlugin({ export default definePlugin({
name: "FavoriteGifSearch", name: "FavoriteGifSearch",
authors: [Devs.Aria], authors: [Devs.Aria],
description: "Adds a search bar for favorite gifs", description: "Adds a search bar to favorite gifs.",
patches: [ patches: [
{ {

View 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)

View File

@ -99,6 +99,15 @@ const imageContextMenuPatch: NavContextMenuPatchCallback = children => () => {
ContextMenu.close(); 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 <Menu.MenuControlItem
id="vc-zoom" id="vc-zoom"
label="Zoom" label="Zoom"

View File

@ -28,7 +28,7 @@ import { Button, Forms, React, TextInput } from "@webpack/common";
import { decrypt } from "../index"; import { decrypt } from "../index";
export function DecModal(props: any) { export function DecModal(props: any) {
const secret: string = props?.message?.content; const encryptedMessage: string = props?.message?.content;
const [password, setPassword] = React.useState("password"); const [password, setPassword] = React.useState("password");
return ( return (
@ -38,9 +38,9 @@ export function DecModal(props: any) {
</ModalHeader> </ModalHeader>
<ModalContent> <ModalContent>
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Secret</Forms.FormTitle> <Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Message with Encryption</Forms.FormTitle>
<TextInput defaultValue={secret} disabled={true}></TextInput> <TextInput defaultValue={encryptedMessage} disabled={true}></TextInput>
<Forms.FormTitle tag="h5">Password</Forms.FormTitle> <Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Password</Forms.FormTitle>
<TextInput <TextInput
style={{ marginBottom: "20px" }} style={{ marginBottom: "20px" }}
onChange={setPassword} onChange={setPassword}
@ -51,7 +51,7 @@ export function DecModal(props: any) {
<Button <Button
color={Button.Colors.GREEN} color={Button.Colors.GREEN}
onClick={() => { onClick={() => {
const toSend = decrypt(secret, password, true); const toSend = decrypt(encryptedMessage, password, true);
if (!toSend || !props?.message) return; if (!toSend || !props?.message) return;
// @ts-expect-error // @ts-expect-error
Vencord.Plugins.plugins.InvisibleChat.buildEmbed(props?.message, toSend); Vencord.Plugins.plugins.InvisibleChat.buildEmbed(props?.message, toSend);

View File

@ -225,8 +225,8 @@ export function encrypt(secret: string, password: string, cover: string): string
return steggo.hide(secret + "\u200b", password, cover); return steggo.hide(secret + "\u200b", password, cover);
} }
export function decrypt(secret: string, password: string, removeIndicator: boolean): string { export function decrypt(encrypted: string, password: string, removeIndicator: boolean): string {
const decrypted = steggo.reveal(secret, password); const decrypted = steggo.reveal(encrypted, password);
return removeIndicator ? decrypted.replace("\u200b", "") : decrypted; return removeIndicator ? decrypted.replace("\u200b", "") : decrypted;
} }

View File

@ -72,6 +72,14 @@ const enum ActivityFlag {
INSTANCE = 1 << 0, INSTANCE = 1 << 0,
} }
const enum NameFormat {
StatusName = "status-name",
ArtistFirst = "artist-first",
SongFirst = "song-first",
ArtistOnly = "artist",
SongOnly = "song"
}
const applicationId = "1108588077900898414"; const applicationId = "1108588077900898414";
const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f"; const placeholderId = "2a96cbd8b46e442fc41c2b86b821562f";
@ -117,10 +125,37 @@ const settings = definePluginSettings({
default: true, default: true,
}, },
statusName: { statusName: {
description: "text shown in status", description: "custom status text",
type: OptionType.STRING, type: OptionType.STRING,
default: "some music", 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: { useListeningStatus: {
description: 'show "Listening to" status instead of "Playing"', description: 'show "Listening to" status instead of "Playing"',
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
@ -140,13 +175,13 @@ const settings = definePluginSettings({
value: "placeholder" value: "placeholder"
} }
], ],
} },
}); });
export default definePlugin({ export default definePlugin({
name: "LastFMRichPresence", name: "LastFMRichPresence",
description: "Little plugin for Last.fm rich presence", description: "Little plugin for Last.fm rich presence",
authors: [Devs.dzshn, Devs.RuiNtD], authors: [Devs.dzshn, Devs.RuiNtD, Devs.blahajZip, Devs.archeruwu],
settingsAboutComponent: () => ( settingsAboutComponent: () => (
<> <>
@ -267,9 +302,24 @@ export default definePlugin({
url: `https://www.last.fm/user/${settings.store.username}`, 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 { return {
application_id: applicationId, application_id: applicationId,
name: settings.store.statusName, name: statusName,
details: trackData.name, details: trackData.name,
state: trackData.artist, state: trackData.artist,

View File

@ -326,14 +326,14 @@ export default definePlugin({
{ {
// Attachment renderer // Attachment renderer
// Module 96063 // Module 96063
find: "[\"className\",\"attachment\",\"inlineMedia\"", find: "().removeAttachmentHoverButton",
replacement: [ replacement: [
{ {
match: /((\w)\.className,\w=\2\.attachment),/, match: /((\w)\.className,\w=\2\.attachment),/,
replace: "$1,deleted=$2.attachment?.deleted," replace: "$1,deleted=$2.attachment?.deleted,"
}, },
{ {
match: /\["className","attachment","inlineMedia".+?className:/, match: /\["className","attachment".+?className:/,
replace: "$& (deleted ? 'messagelogger-deleted-attachment ' : '') +" replace: "$& (deleted ? 'messagelogger-deleted-attachment ' : '') +"
} }
] ]

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], authors: [Devs.Cyn, Devs.TheSun, Devs.RyanCaoDev, Devs.LordElias, Devs.AutumnVN],
settings, settings,
patches: [ patches: [
// add tags to the tag list // add tags to the tag list
@ -232,9 +232,8 @@ export default definePlugin({
{ {
find: ".renderBot=function(){", find: ".renderBot=function(){",
replacement: { replacement: {
match: /this.props.user;return null!=(\i)&&.{0,10}\?(.{0,50})\.botTag/, match: /\.BOT;return null!=(\i)&&.{0,10}\?(.{0,50})\.botTag,type:\i/,
replace: "this.props.user;var type=$self.getTag({...this.props,origType:$1.bot?0:null,location:'not-chat'});\ replace: ".BOT;var type=$self.getTag({...this.props,origType:$1.bot?0:null,location:'not-chat'});return type!==null?$2.botTag,type"
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,7 +124,6 @@ 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);
@ -143,7 +142,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 = new GIFEncoder(); const gif = GIFEncoder();
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
canvas.width = canvas.height = resolution; canvas.width = canvas.height = resolution;

View File

@ -29,8 +29,8 @@ export default definePlugin({
{ {
find: ".onRemoveAttachment,", find: ".onRemoveAttachment,",
replacement: { replacement: {
match: /\.nonMediaAttachment.{0,10}children:\[(\i),/, match: /\.nonMediaAttachment,!(\i).{0,7}children:\[(\i),/,
replace: "$&$1&&$self.renderPiPButton()," replace: "$&$1&&$2&&$self.renderPiPButton(),"
}, },
}, },
], ],

View File

@ -0,0 +1,5 @@
# PreviewMessage
Lets you preview your message before sending it.
![Example](https://i.imgur.com/etqbkzu.png)

View File

@ -16,37 +16,96 @@
* 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 { sendBotMessage } from "@api/Commands"; import { generateId, sendBotMessage } from "@api/Commands";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import definePlugin from "@utils/types"; import definePlugin from "@utils/types";
import { findByPropsLazy } from "@webpack";
import { Button, ButtonLooks, ButtonWrapperClasses, DraftStore, DraftType, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; import { Button, ButtonLooks, ButtonWrapperClasses, DraftStore, DraftType, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
import { MessageAttachment } from "discord-types/general";
interface Props { interface Props {
type: { type: {
analyticsName: string; analyticsName: string;
isEmpty: boolean;
attachments: boolean;
}; };
} }
const UploadStore = findByPropsLazy("getUploads");
const getDraft = (channelId: string) => DraftStore.getDraft(channelId, DraftType.ChannelMessage); 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) { export function PreviewButton(chatBoxProps: Props) {
const { isEmpty, attachments } = chatBoxProps.type;
const channelId = SelectedChannelStore.getChannelId(); const channelId = SelectedChannelStore.getChannelId();
const draft = useStateFromStores([DraftStore], () => getDraft(channelId)); const draft = useStateFromStores([DraftStore], () => getDraft(channelId));
if (chatBoxProps.type.analyticsName !== "normal") return null; 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 ( return (
<Tooltip text="Preview Message"> <Tooltip text="Preview Message">
{tooltipProps => ( {tooltipProps => (
<Button <Button
{...tooltipProps} {...tooltipProps}
onClick={() => onClick={async () =>
sendBotMessage( sendBotMessage(
channelId, channelId,
{ {
content: getDraft(channelId), content: getDraft(channelId),
author: UserStore.getCurrentUser() author: UserStore.getCurrentUser(),
attachments: hasAttachments ? await getAttachments(channelId) : undefined,
} }
)} )}
size="" size=""
@ -66,7 +125,7 @@ export function PreviewButton(chatBoxProps: Props) {
export default definePlugin({ export default definePlugin({
name: "PreviewMessage", name: "PreviewMessage",
description: "Lets you preview your message before sending it", description: "Lets you preview your message before sending it.",
authors: [Devs.Aria], authors: [Devs.Aria],
patches: [ patches: [
{ {

View File

@ -0,0 +1,5 @@
# SearchReply
Adds a reply button to search results.
![Screenshot](https://i.imgur.com/SjIEHpw.png)

View File

@ -213,15 +213,12 @@ 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")); content = ` ${content} `.replaceAll(rule.find, rule.replace.replaceAll("\\n", "\n")).replace(/^\s|\s$/g, "");
} }
} }

1168
src/utils/apng-canvas.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -366,6 +366,14 @@ export const Devs = /* #__PURE__*/ Object.freeze({
RuukuLada: { RuukuLada: {
name: "RuukuLada", name: "RuukuLada",
id: 119705748346241027n, id: 119705748346241027n,
},
blahajZip: {
name: "blahaj.zip",
id: 683954422241427471n,
},
archeruwu: {
name: "archer_uwu",
id: 160068695383736320n
} }
} satisfies Record<string, Dev>); } satisfies Record<string, Dev>);

View File

@ -17,23 +17,15 @@
*/ */
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(async () => { export const importApngJs = makeLazy(() => {
const exports = {}; return require("./apng-canvas").APNG as { parseURL(url: string): Promise<ApngFrameData>; };
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
@ -75,13 +67,20 @@ export interface ApngFrameData {
playTime: number; playTime: number;
} }
const shikiWorkerDist = "https://unpkg.com/@vap/shiki-worker@0.0.8/dist"; // On web (extensions), use extension uri as basepath (load files from extension)
export const shikiWorkerSrc = `${shikiWorkerDist}/${IS_DEV ? "index.js" : "index.min.js"}`; // On desktop (electron), load from cdn
export const shikiOnigasmSrc = "https://unpkg.com/@vap/shiki@0.10.3/dist/onig.wasm"; export const rnnoiseDist = IS_EXTENSION
? new URL("/third-party/rnnoise", EXTENSION_BASE_URL).toString()
export const rnnoiseDist = "https://unpkg.com/@sapphi-red/web-noise-suppressor@0.3.3/dist"; : "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
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"));

14
src/utils/web-metadata.ts Normal file
View 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!;
}

View File

@ -65,8 +65,13 @@ export interface Menu {
id: string; id: string;
interactive?: boolean; interactive?: boolean;
}>; }>;
// TODO: Type me MenuSliderControl: RC<{
MenuSliderControl: RC<any>; minValue: number,
maxValue: number,
value: number,
onChange(value: number): void,
renderValue?(value: number): string,
}>;
} }
export interface ContextMenuApi { export interface ContextMenuApi {