Compare commits
23 commits
f5ac9304ae
...
76b4e72917
Author | SHA1 | Date | |
---|---|---|---|
76b4e72917 | |||
|
97c0face2f | ||
|
1a1d9b07e8 | ||
|
4a2def03e7 | ||
|
544edce9f9 | ||
|
e4485165d0 | ||
|
fada76ec81 | ||
|
f659c46031 | ||
|
ae1dc4eab0 | ||
|
fe60a72b80 | ||
|
5a0b2ee3f5 | ||
|
6c1b8b0d8a | ||
|
b2a1410a96 | ||
|
c25c95eecd | ||
|
d94418f42f | ||
|
da1a8cdd67 | ||
|
5d7ede34d8 | ||
|
cd61354998 | ||
|
a452945ac8 | ||
|
b577660800 | ||
|
4f57c7eded | ||
|
e3e5da10a9 | ||
|
998ce72f3b |
37 changed files with 1041 additions and 107 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "vencord",
|
"name": "vencord",
|
||||||
"private": "true",
|
"private": "true",
|
||||||
"version": "1.5.6",
|
"version": "1.5.8",
|
||||||
"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": {
|
||||||
|
|
|
@ -43,7 +43,7 @@ const nodeCommonOpts = {
|
||||||
format: "cjs",
|
format: "cjs",
|
||||||
platform: "node",
|
platform: "node",
|
||||||
target: ["esnext"],
|
target: ["esnext"],
|
||||||
external: ["electron", ...commonOpts.external],
|
external: ["electron", "original-fs", ...commonOpts.external],
|
||||||
define: defines,
|
define: defines,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,13 @@ const report = {
|
||||||
otherErrors: [] as string[]
|
otherErrors: [] as string[]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const IGNORED_DISCORD_ERRORS = [
|
||||||
|
"KeybindStore: Looking for callback action",
|
||||||
|
"Unable to process domain list delta: Client revision number is null",
|
||||||
|
"Downloading the full bad domains file",
|
||||||
|
/\[GatewaySocket\].{0,110}Cannot access '/
|
||||||
|
] as Array<string | RegExp>;
|
||||||
|
|
||||||
function toCodeBlock(s: string) {
|
function toCodeBlock(s: string) {
|
||||||
s = s.replace(/```/g, "`\u200B`\u200B`");
|
s = s.replace(/```/g, "`\u200B`\u200B`");
|
||||||
return "```" + s + " ```";
|
return "```" + s + " ```";
|
||||||
|
@ -86,6 +93,8 @@ async function printReport() {
|
||||||
console.log(` - Error: ${toCodeBlock(p.error)}`);
|
console.log(` - Error: ${toCodeBlock(p.error)}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
report.otherErrors = report.otherErrors.filter(e => !IGNORED_DISCORD_ERRORS.some(regex => e.match(regex)));
|
||||||
|
|
||||||
console.log("## Discord Errors");
|
console.log("## Discord Errors");
|
||||||
report.otherErrors.forEach(e => {
|
report.otherErrors.forEach(e => {
|
||||||
console.log(`- ${toCodeBlock(e)}`);
|
console.log(`- ${toCodeBlock(e)}`);
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
text-wrap: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-author-modal-name::before {
|
.vc-author-modal-name::before {
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { Margins } from "@utils/margins";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { showItemInFolder } from "@utils/native";
|
import { showItemInFolder } from "@utils/native";
|
||||||
import { useAwaiter } from "@utils/react";
|
import { useAwaiter } from "@utils/react";
|
||||||
import { findByCodeLazy, findByPropsLazy, findLazy } from "@webpack";
|
import { findByPropsLazy, findLazy } from "@webpack";
|
||||||
import { Button, Card, FluxDispatcher, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
import { Button, Card, FluxDispatcher, Forms, React, showToast, TabBar, TextArea, useEffect, useRef, useState } from "@webpack/common";
|
||||||
import { UserThemeHeader } from "main/themes";
|
import { UserThemeHeader } from "main/themes";
|
||||||
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
||||||
|
@ -41,7 +41,7 @@ type FileInput = ComponentType<{
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const InviteActions = findByPropsLazy("resolveInvite");
|
const InviteActions = findByPropsLazy("resolveInvite");
|
||||||
const FileInput: FileInput = findByCodeLazy("activateUploadDialogue=");
|
const FileInput: FileInput = findLazy(m => m.prototype?.activateUploadDialogue && m.prototype.setRef);
|
||||||
const TextAreaProps = findLazy(m => typeof m.textarea === "string");
|
const TextAreaProps = findLazy(m => typeof m.textarea === "string");
|
||||||
|
|
||||||
const cl = classNameFactory("vc-settings-theme-");
|
const cl = classNameFactory("vc-settings-theme-");
|
||||||
|
|
|
@ -62,6 +62,10 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
||||||
} catch { }
|
} catch { }
|
||||||
|
|
||||||
|
|
||||||
|
const findHeader = (headers: Record<string, string[]>, headerName: Lowercase<string>) => {
|
||||||
|
return Object.keys(headers).find(h => h.toLowerCase() === headerName);
|
||||||
|
};
|
||||||
|
|
||||||
// Remove CSP
|
// Remove CSP
|
||||||
type PolicyResult = Record<string, string[]>;
|
type PolicyResult = Record<string, string[]>;
|
||||||
|
|
||||||
|
@ -73,6 +77,7 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
||||||
result[directiveKey] = directiveValue;
|
result[directiveKey] = directiveValue;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
const stringifyPolicy = (policy: PolicyResult): string =>
|
const stringifyPolicy = (policy: PolicyResult): string =>
|
||||||
|
@ -81,31 +86,39 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
||||||
.map(directive => directive.flat().join(" "))
|
.map(directive => directive.flat().join(" "))
|
||||||
.join("; ");
|
.join("; ");
|
||||||
|
|
||||||
function patchCsp(headers: Record<string, string[]>, header: string) {
|
const patchCsp = (headers: Record<string, string[]>) => {
|
||||||
if (header in headers) {
|
const header = findHeader(headers, "content-security-policy");
|
||||||
|
|
||||||
|
if (header) {
|
||||||
const csp = parsePolicy(headers[header][0]);
|
const csp = parsePolicy(headers[header][0]);
|
||||||
|
|
||||||
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
|
for (const directive of ["style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"]) {
|
||||||
csp[directive] = ["*", "blob:", "data:", "vencord:", "'unsafe-inline'"];
|
csp[directive] ??= [];
|
||||||
|
csp[directive].push("*", "blob:", "data:", "vencord:", "'unsafe-inline'");
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Restrict this to only imported packages with fixed version.
|
// TODO: Restrict this to only imported packages with fixed version.
|
||||||
// Perhaps auto generate with esbuild
|
// Perhaps auto generate with esbuild
|
||||||
csp["script-src"] ??= [];
|
csp["script-src"] ??= [];
|
||||||
csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com");
|
csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com");
|
||||||
headers[header] = [stringifyPolicy(csp)];
|
headers[header] = [stringifyPolicy(csp)];
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => {
|
session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => {
|
||||||
if (responseHeaders) {
|
if (responseHeaders) {
|
||||||
if (resourceType === "mainFrame")
|
if (resourceType === "mainFrame")
|
||||||
patchCsp(responseHeaders, "content-security-policy");
|
patchCsp(responseHeaders);
|
||||||
|
|
||||||
// Fix hosts that don't properly set the css content type, such as
|
// Fix hosts that don't properly set the css content type, such as
|
||||||
// raw.githubusercontent.com
|
// raw.githubusercontent.com
|
||||||
if (resourceType === "stylesheet")
|
if (resourceType === "stylesheet") {
|
||||||
responseHeaders["content-type"] = ["text/css"];
|
const header = findHeader(responseHeaders, "content-type");
|
||||||
|
if (header)
|
||||||
|
responseHeaders[header] = ["text/css"];
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cb({ cancel: false, responseHeaders });
|
cb({ cancel: false, responseHeaders });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { app } from "electron";
|
import { app } from "electron";
|
||||||
import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "fs";
|
import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "original-fs";
|
||||||
import { basename, dirname, join } from "path";
|
import { basename, dirname, join } from "path";
|
||||||
|
|
||||||
function isNewer($new: string, old: string) {
|
function isNewer($new: string, old: string) {
|
||||||
|
|
|
@ -27,8 +27,8 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
find: ".withMentionPrefix",
|
find: ".withMentionPrefix",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(.roleDot.{10,50}{children:.{1,2})}\)/,
|
match: /currentUserIsPremium:.{0,70}{children:\i(?=}\))/,
|
||||||
replace: "$1.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))})"
|
replace: "$&.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -63,26 +63,26 @@ export default definePlugin({
|
||||||
replacement: {
|
replacement: {
|
||||||
get match() {
|
get match() {
|
||||||
switch (Settings.plugins.Settings.settingsLocation) {
|
switch (Settings.plugins.Settings.settingsLocation) {
|
||||||
case "top": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/;
|
case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/;
|
||||||
case "aboveNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/;
|
case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/;
|
||||||
case "belowNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/;
|
case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/;
|
||||||
case "belowActivity": return /(?<=\{section:(\i)\.ID\.DIVIDER},)\{section:"changelog"/;
|
case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/;
|
||||||
case "bottom": return /\{section:(\i)\.ID\.CUSTOM,\s*element:.+?}/;
|
case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/;
|
||||||
case "aboveActivity":
|
case "aboveActivity":
|
||||||
default:
|
default:
|
||||||
return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS\}/;
|
return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.ACTIVITY_SETTINGS\}/;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
replace: "...$self.makeSettingsCategories($1),$&"
|
replace: "...$self.makeSettingsCategories($1),$&"
|
||||||
}
|
}
|
||||||
}],
|
}],
|
||||||
|
|
||||||
customSections: [] as ((ID: Record<string, unknown>) => any)[],
|
customSections: [] as ((SectionTypes: Record<string, unknown>) => any)[],
|
||||||
|
|
||||||
makeSettingsCategories({ ID }: { ID: Record<string, unknown>; }) {
|
makeSettingsCategories(SectionTypes: Record<string, unknown>) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
section: ID.HEADER,
|
section: SectionTypes.HEADER,
|
||||||
label: "Vencord",
|
label: "Vencord",
|
||||||
className: "vc-settings-header"
|
className: "vc-settings-header"
|
||||||
},
|
},
|
||||||
|
@ -128,9 +128,9 @@ export default definePlugin({
|
||||||
element: require("@components/VencordSettings/PatchHelperTab").default,
|
element: require("@components/VencordSettings/PatchHelperTab").default,
|
||||||
className: "vc-patch-helper"
|
className: "vc-patch-helper"
|
||||||
},
|
},
|
||||||
...this.customSections.map(func => func(ID)),
|
...this.customSections.map(func => func(SectionTypes)),
|
||||||
{
|
{
|
||||||
section: ID.DIVIDER
|
section: SectionTypes.DIVIDER
|
||||||
}
|
}
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
},
|
},
|
||||||
|
|
43
src/plugins/badge.ts
Normal file
43
src/plugins/badge.ts
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/* eslint-disable header/header */
|
||||||
|
import { BadgePosition, ProfileBadge } from "@api/Badges";
|
||||||
|
import { Badges } from "@api/index";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
import { UserStore } from "@webpack/common";
|
||||||
|
|
||||||
|
const SHIGGY_BADGE = "https://cdn.discordapp.com/emojis/1101838344146665502.gif?size=240&quality=lossless";
|
||||||
|
const BLOBFOXBOX_BADGE = "https://cdn.discordapp.com/emojis/1036216552736952350.webp?size=240&quality=lossless";
|
||||||
|
|
||||||
|
const ShiggyBadge: ProfileBadge = {
|
||||||
|
description: "true shiggy fan",
|
||||||
|
image: SHIGGY_BADGE,
|
||||||
|
position: BadgePosition.START,
|
||||||
|
props: {
|
||||||
|
style: { transform: "scale(0.9)" }
|
||||||
|
},
|
||||||
|
shouldShow: ({ user }) => user.id === UserStore.getCurrentUser().id,
|
||||||
|
link: "https://ryanccn.dev/"
|
||||||
|
};
|
||||||
|
const BlobfoxBoxBadge: ProfileBadge = {
|
||||||
|
description: "blobfox",
|
||||||
|
image: BLOBFOXBOX_BADGE,
|
||||||
|
position: BadgePosition.START,
|
||||||
|
props: {
|
||||||
|
style: { transform: "scale(0.9)" }
|
||||||
|
},
|
||||||
|
shouldShow: ({ user }) => user.id === UserStore.getCurrentUser().id,
|
||||||
|
link: "https://ryanccn.dev/"
|
||||||
|
};
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "Ryan's Extra Badges",
|
||||||
|
description: "shiggy",
|
||||||
|
authors: [Devs.RyanCaoDev],
|
||||||
|
dependencies: ["BadgeAPI"],
|
||||||
|
|
||||||
|
|
||||||
|
start() {
|
||||||
|
Badges.addBadge(ShiggyBadge);
|
||||||
|
Badges.addBadge(BlobfoxBoxBadge);
|
||||||
|
},
|
||||||
|
});
|
|
@ -19,6 +19,9 @@
|
||||||
import { Settings } from "@api/Settings";
|
import { Settings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
|
||||||
|
const UserPopoutSectionCssClasses = findByPropsLazy("section", "lastSection");
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "BetterNotesBox",
|
name: "BetterNotesBox",
|
||||||
|
@ -34,12 +37,20 @@ export default definePlugin({
|
||||||
match: /hideNote:.+?(?=[,}])/g,
|
match: /hideNote:.+?(?=[,}])/g,
|
||||||
replace: "hideNote:true",
|
replace: "hideNote:true",
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
find: "Messages.NOTE_PLACEHOLDER",
|
find: "Messages.NOTE_PLACEHOLDER",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\.NOTE_PLACEHOLDER,/,
|
match: /\.NOTE_PLACEHOLDER,/,
|
||||||
replace: "$&spellCheck:!Vencord.Settings.plugins.BetterNotesBox.noSpellCheck,"
|
replace: "$&spellCheck:!Vencord.Settings.plugins.BetterNotesBox.noSpellCheck,"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: ".Messages.NOTE}",
|
||||||
|
replacement: {
|
||||||
|
match: /(\i)\.hideNote\?null/,
|
||||||
|
replace: "$1.hideNote?$self.patchPadding($1)"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@ -56,5 +67,12 @@ export default definePlugin({
|
||||||
disabled: () => Settings.plugins.BetterNotesBox.hide,
|
disabled: () => Settings.plugins.BetterNotesBox.hide,
|
||||||
default: false
|
default: false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
patchPadding(e: any) {
|
||||||
|
if (!e.lastSection) return;
|
||||||
|
return (
|
||||||
|
<div className={UserPopoutSectionCssClasses.lastSection}></div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
63
src/plugins/bottom/components/Indicator.tsx
Normal file
63
src/plugins/bottom/components/Indicator.tsx
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This plugin was modified from code licensed under the following license:
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021-present Sebastian Law
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { findByPropsLazy } from "@webpack";
|
||||||
|
import { Tooltip } from "@webpack/common";
|
||||||
|
|
||||||
|
export default function Indicator({ layers, bottom }: { layers: number; bottom: boolean; }) {
|
||||||
|
return (
|
||||||
|
<Tooltip color="black" position="top" text={layers <= 1 ? "🥺" : `Decoded from ${layers} nested bottom messages`}>
|
||||||
|
{({ onMouseLeave, onMouseEnter }) => (
|
||||||
|
<span
|
||||||
|
className={`power-bottom-indicator ${findByPropsLazy("edited").edited}`}
|
||||||
|
style={{ color: "var(--text-muted)" }}
|
||||||
|
onMouseEnter={onMouseEnter}
|
||||||
|
onMouseLeave={onMouseLeave}
|
||||||
|
>
|
||||||
|
{bottom ? "(bottom)" : "(original)"}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
73
src/plugins/bottom/encoding.ts
Normal file
73
src/plugins/bottom/encoding.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file was adapted from https://github.com/bottom-software-foundation/bottom-js
|
||||||
|
* Which is, hopefully, licensed under MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const CHARACTER_VALUES: [number, string][] = [
|
||||||
|
[200, "🫂"],
|
||||||
|
[50, "💖"],
|
||||||
|
[10, "✨"],
|
||||||
|
[5, "🥺"],
|
||||||
|
[1, ","],
|
||||||
|
[0, "❤️"],
|
||||||
|
];
|
||||||
|
const SECTION_SEPERATOR = "👉👈";
|
||||||
|
const FINAL_TERMINATOR = new RegExp(`(${SECTION_SEPERATOR})?$`);
|
||||||
|
|
||||||
|
function encodeChar(charValue: number): string {
|
||||||
|
if (charValue === 0) return "";
|
||||||
|
const [val, currentCase]: [number, string] =
|
||||||
|
CHARACTER_VALUES.find(([val]) => charValue >= val) || CHARACTER_VALUES[-1];
|
||||||
|
return `${currentCase}${encodeChar(charValue - val)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function encode(value: string): string {
|
||||||
|
return Array.from(new TextEncoder().encode(value))
|
||||||
|
.map((v: number) => encodeChar(v) + SECTION_SEPERATOR)
|
||||||
|
.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decode(value: string): string {
|
||||||
|
return new TextDecoder().decode(Uint8Array.from(
|
||||||
|
value
|
||||||
|
.trim()
|
||||||
|
.replace(FINAL_TERMINATOR, "")
|
||||||
|
.split(SECTION_SEPERATOR)
|
||||||
|
.map(letters => {
|
||||||
|
return Array.from(letters)
|
||||||
|
.map(character => {
|
||||||
|
const [value, emoji]: [number, string] = CHARACTER_VALUES.find(
|
||||||
|
([_, em]) => em === character
|
||||||
|
) || [-1, ""];
|
||||||
|
if (!emoji) {
|
||||||
|
throw new TypeError(`Invalid bottom text: '${character}'`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
})
|
||||||
|
.reduce((p, c) => p + c);
|
||||||
|
})
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
encode: encode,
|
||||||
|
decode: decode
|
||||||
|
};
|
158
src/plugins/bottom/handler.ts
Normal file
158
src/plugins/bottom/handler.ts
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This plugin was modified from code licensed under the following license:
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021-present Sebastian Law
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { FluxDispatcher, MessageStore } from "@webpack/common";
|
||||||
|
import type { Message } from "discord-types/general";
|
||||||
|
|
||||||
|
import Bottom from "./encoding";
|
||||||
|
|
||||||
|
class BottomHandler {
|
||||||
|
|
||||||
|
cache: Record<string, Record<string, { originalContent: string; top?: boolean; layers?: number; }>>;
|
||||||
|
re: RegExp;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.cache = {};
|
||||||
|
this.re = /((?:((?:\uD83E\uDEC2)?(?:💖)*(?:✨)*(?:🥺)*(?:,)*(❤️)?)(?:👉👈|\u200b))+)/gm;
|
||||||
|
}
|
||||||
|
|
||||||
|
isTranslated(message) {
|
||||||
|
if (
|
||||||
|
!this.cache[message.channel_id] ||
|
||||||
|
!this.cache[message.channel_id][message.id]
|
||||||
|
) { return false; }
|
||||||
|
|
||||||
|
return this.cache[message.channel_id][message.id].originalContent !== message.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
translate(text: string, notNested: boolean) {
|
||||||
|
var original = text;
|
||||||
|
var translated = text;
|
||||||
|
var layers = 0;
|
||||||
|
while (original.match(this.re)) {
|
||||||
|
translated = original.replace(this.re, (str, p1, offset, s) => Bottom.decode(p1) || p1);
|
||||||
|
|
||||||
|
// the regex can sometimes pick up invalid bottom in which case we want to return to avoid an infinite loop
|
||||||
|
if (translated === original || notNested) break;
|
||||||
|
else {
|
||||||
|
original = translated;
|
||||||
|
layers++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
translated: translated,
|
||||||
|
layers: layers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
translateMessage(message: Message, decodeLayers: boolean) {
|
||||||
|
if (!message.content || message.content.length === 0) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// Build cache if it doesn't exist
|
||||||
|
if (!this.cache[message.channel_id]) {
|
||||||
|
this.cache[message.channel_id] = {};
|
||||||
|
}
|
||||||
|
if (!this.cache[message.channel_id][message.id]) {
|
||||||
|
this.cache[message.channel_id][message.id] = {
|
||||||
|
originalContent: message.content,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const cached = this.cache[message.channel_id][message.id];
|
||||||
|
|
||||||
|
if (this.isTranslated(message)) {
|
||||||
|
// if we're reverting back to original, just set the content back to original
|
||||||
|
message.content = cached.originalContent;
|
||||||
|
this.updateMessage(message);
|
||||||
|
} else {
|
||||||
|
// the message hasn't been edited, let's try to decode it
|
||||||
|
const { translated, layers } = this.translate(message.content, !decodeLayers);
|
||||||
|
if (translated === message.content) {
|
||||||
|
// we don't want to do anything if there is no bottom
|
||||||
|
// since the translation fails, mark this message to not show the indicator
|
||||||
|
cached.top = true;
|
||||||
|
throw new Error("No Bottom detected 🥺");
|
||||||
|
} else {
|
||||||
|
// let the indicator show how many layers of decoding we did
|
||||||
|
cached.layers = layers;
|
||||||
|
message.content = translated;
|
||||||
|
this.updateMessage(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateMessage(message: Message) {
|
||||||
|
console.log({
|
||||||
|
bottomTranslation: true,
|
||||||
|
type: "MESSAGE_UPDATE",
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
FluxDispatcher.dispatch({
|
||||||
|
bottomTranslation: true,
|
||||||
|
type: "MESSAGE_UPDATE",
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearCache() {
|
||||||
|
for (const channelID in this.cache) {
|
||||||
|
for (const messageID in this.cache[channelID]) {
|
||||||
|
this.removeMessage(channelID, messageID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.cache = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
removeMessage(channelID: string, messageID: string, reset = true) {
|
||||||
|
const message = MessageStore.getMessage(channelID, messageID);
|
||||||
|
if (reset) {
|
||||||
|
message.content = this.cache[channelID][messageID].originalContent;
|
||||||
|
this.updateMessage(message);
|
||||||
|
}
|
||||||
|
delete this.cache[channelID][messageID];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BottomHandler;
|
262
src/plugins/bottom/index.tsx
Normal file
262
src/plugins/bottom/index.tsx
Normal file
|
@ -0,0 +1,262 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This plugin was modified from code licensed under the following license:
|
||||||
|
*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021-present Sebastian Law
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { findOption, RequiredMessageOption } from "@api/Commands";
|
||||||
|
import { addAccessory, removeAccessory } from "@api/MessageAccessories";
|
||||||
|
import { addPreSendListener, removePreSendListener } from "@api/MessageEvents";
|
||||||
|
import { addButton, removeButton } from "@api/MessagePopover";
|
||||||
|
import { definePluginSettings } from "@api/settings";
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
import { ChannelStore, Toasts } from "@webpack/common";
|
||||||
|
|
||||||
|
import Indicator from "./components/Indicator";
|
||||||
|
import Bottom from "./encoding";
|
||||||
|
import BottomHandler from "./handler";
|
||||||
|
|
||||||
|
const Handler = new BottomHandler();
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
"decode-layers": {
|
||||||
|
description: "Decode Layers",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
"auto-encode-send": {
|
||||||
|
description: "Automatically encode outgoing messages",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
"encode-send-type": {
|
||||||
|
description: "Automatic Encode Behavior",
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
options:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
label: "All",
|
||||||
|
default: true,
|
||||||
|
value: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Inline (Greedy)",
|
||||||
|
value: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Inline (Parsed)",
|
||||||
|
value: 2,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"inline-bottom-prefix": {
|
||||||
|
description: "Inline bottom prefix",
|
||||||
|
type: OptionType.STRING,
|
||||||
|
default: "👉",
|
||||||
|
},
|
||||||
|
"inline-bottom-suffix": {
|
||||||
|
description: "Inline bottom suffix",
|
||||||
|
type: OptionType.STRING,
|
||||||
|
default: "👈",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const escapeRegex: (string: string) => string = string => string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
|
||||||
|
function count(string: string, subString: string): number {
|
||||||
|
var n = 0;
|
||||||
|
var pos = 0;
|
||||||
|
const step = subString.length;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
pos = string.indexOf(subString, pos);
|
||||||
|
if (pos >= 0) {
|
||||||
|
n++;
|
||||||
|
pos += step;
|
||||||
|
} else break;
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
function inlineEncode(p: string, s: string, text: string): string {
|
||||||
|
var np = count(text, p);
|
||||||
|
var ns = count(text, s);
|
||||||
|
|
||||||
|
if (np === 0 || ns === 0) return text;
|
||||||
|
|
||||||
|
var pl = p.length;
|
||||||
|
var sl = s.length;
|
||||||
|
const result: string[] = [];
|
||||||
|
let idx = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
var startIndex = text.indexOf(p, idx);
|
||||||
|
|
||||||
|
if (startIndex < 0) {
|
||||||
|
result.push(text.slice(idx));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var endIndex = text.indexOf(s, startIndex + pl);
|
||||||
|
|
||||||
|
if (endIndex < 0) {
|
||||||
|
result.push(text.slice(idx));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(text.slice(idx, startIndex));
|
||||||
|
startIndex += pl;
|
||||||
|
result.push(Bottom.encode(text.slice(startIndex, endIndex)));
|
||||||
|
endIndex += sl;
|
||||||
|
idx = endIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "Bottom",
|
||||||
|
description: "The Vencord plugin for bottom 🥺",
|
||||||
|
authors: [
|
||||||
|
{
|
||||||
|
id: 1038096782963507210n,
|
||||||
|
name: "skyevg",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
dependencies: ["MessagePopoverAPI", "CommandsAPI", "MessageEventsAPI", "MessageAccessoriesAPI"],
|
||||||
|
|
||||||
|
settings,
|
||||||
|
|
||||||
|
start() {
|
||||||
|
addButton("bottom", msg => {
|
||||||
|
return {
|
||||||
|
label: "Translate Bottom",
|
||||||
|
icon: () => (
|
||||||
|
<svg x="0" y="0" aria-hidden="false" width="22" height="22" viewBox="0 0 36 36" fill="currentColor" className="icon">
|
||||||
|
<circle fill="#FFCC4D" cx="18" cy="18" r="18" />
|
||||||
|
<path fill="#65471B" d="M20.996 27c-.103 0-.206-.016-.309-.049-1.76-.571-3.615-.571-5.375 0-.524.169-1.089-.117-1.26-.642-.171-.525.117-1.089.643-1.26 2.162-.702 4.447-.702 6.609 0 .525.171.813.735.643 1.26-.137.421-.529.691-.951.691z" />
|
||||||
|
<path fill="#FFF" d="M30.335 12.068c-.903 2.745-3.485 4.715-6.494 4.715-.144 0-.289-.005-.435-.014-1.477-.093-2.842-.655-3.95-1.584.036.495.076.997.136 1.54.152 1.388.884 2.482 2.116 3.163.82.454 1.8.688 2.813.752 1.734.109 3.57-.28 4.873-.909 1.377-.665 2.272-1.862 2.456-3.285.183-1.415-.354-2.924-1.515-4.378z" />
|
||||||
|
<path fill="#65471B" d="M21.351 7.583c-1.297.55-1.947 2.301-1.977 5.289l.039.068c.897 1.319 2.373 2.224 4.088 2.332.114.007.228.011.341.011 2.634 0 4.849-1.937 5.253-4.524-.115-.105-.221-.212-.343-.316-3.715-3.17-6.467-3.257-7.401-2.86z" />
|
||||||
|
<path fill="#F4900C" d="M23.841 16.783c3.009 0 5.591-1.97 6.494-4.715-.354-.443-.771-.88-1.241-1.309-.404 2.587-2.619 4.524-5.253 4.524-.113 0-.227-.004-.341-.011-1.715-.108-3.191-1.013-4.088-2.332l-.039-.068c-.007.701.021 1.473.083 2.313 1.108.929 2.473 1.491 3.95 1.584.146.01.291.014.435.014z" />
|
||||||
|
<circle fill="#FFF" cx="21.413" cy="10.705" r="1.107" />
|
||||||
|
<path fill="#FFF" d="M12.159 16.783c-3.009 0-5.591-1.97-6.494-4.715-1.161 1.454-1.697 2.963-1.515 4.377.185 1.423 1.079 2.621 2.456 3.285 1.303.629 3.138 1.018 4.873.909 1.013-.064 1.993-.297 2.813-.752 1.231-.681 1.963-1.775 2.116-3.163.06-.542.1-1.042.136-1.536-1.103.923-2.47 1.487-3.95 1.58-.146.011-.291.015-.435.015z" />
|
||||||
|
<path fill="#65471B" d="M12.159 15.283c.113 0 .227-.004.341-.011 1.715-.108 3.191-1.013 4.088-2.332l.039-.068c-.031-2.988-.68-4.739-1.977-5.289-.934-.397-3.687-.31-7.401 2.859-.122.104-.227.211-.343.316.404 2.588 2.619 4.525 5.253 4.525z" />
|
||||||
|
<path fill="#F4900C" d="M16.626 12.872l-.039.068c-.897 1.319-2.373 2.224-4.088 2.332-.114.007-.228.011-.341.011-2.634 0-4.849-1.937-5.253-4.524-.47.429-.887.866-1.241 1.309.903 2.745 3.485 4.715 6.494 4.715.144 0 .289-.005.435-.014 1.48-.093 2.847-.657 3.95-1.58.062-.841.091-1.614.083-2.317z" />
|
||||||
|
<path fill="#FFF" d="M9.781 11.81c.61-.038 1.074-.564 1.035-1.174-.038-.61-.564-1.074-1.174-1.036-.61.038-1.074.564-1.036 1.174.039.61.565 1.074 1.175 1.036z" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
message: msg,
|
||||||
|
channel: ChannelStore.getChannel(msg.channel_id),
|
||||||
|
onClick: async () => {
|
||||||
|
try {
|
||||||
|
Handler.translateMessage(msg, settings.store["decode-layers"]);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
Toasts.show(
|
||||||
|
{
|
||||||
|
id: Toasts.genId(),
|
||||||
|
message: e.message,
|
||||||
|
type: Toasts.Type.MESSAGE
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
addAccessory("bottom", props => {
|
||||||
|
try {
|
||||||
|
if (!Handler.cache[props.message.channel_id][props.message.id].top) {
|
||||||
|
try {
|
||||||
|
return (
|
||||||
|
<ErrorBoundary>
|
||||||
|
<Indicator layers={Handler.cache[props.message.channel_id][props.message.id].layers ?? 0} bottom={!Handler.isTranslated(props.message)} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.preSend = addPreSendListener((_, msg) => {
|
||||||
|
if (settings.store["auto-encode-send"]) {
|
||||||
|
const sendType = settings.store["encode-send-type"];
|
||||||
|
var { content } = msg;
|
||||||
|
|
||||||
|
switch (sendType) {
|
||||||
|
case 0: // all
|
||||||
|
content = Bottom.encode(content);
|
||||||
|
break;
|
||||||
|
case 1: // inline greedy
|
||||||
|
var prefix = escapeRegex(settings.store["inline-bottom-prefix"]);
|
||||||
|
var suffix = escapeRegex(settings.store["inline-bottom-suffix"]);
|
||||||
|
var reg = new RegExp(`${prefix}(.+)${suffix}`, "gm");
|
||||||
|
content = content.replace(reg, (str, p1, o, s) => Bottom.encode(p1));
|
||||||
|
break;
|
||||||
|
case 2: // inline parsed
|
||||||
|
var prefix = settings.store["inline-bottom-prefix"];
|
||||||
|
var suffix = settings.store["inline-bottom-prefix"];
|
||||||
|
content = inlineEncode(prefix, suffix, content);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
msg.content = content;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
stop() {
|
||||||
|
removeButton("bottom");
|
||||||
|
removeAccessory("bottom");
|
||||||
|
removePreSendListener(this.preSend);
|
||||||
|
},
|
||||||
|
|
||||||
|
commands: [
|
||||||
|
{
|
||||||
|
name: "bottom",
|
||||||
|
description: "Translate and send text as bottom 🥺",
|
||||||
|
options: [RequiredMessageOption],
|
||||||
|
execute: opts => ({
|
||||||
|
content: Bottom.encode(findOption(opts, "message", "")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
|
@ -77,15 +77,6 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// Fix search history being disabled / broken with isStaff
|
|
||||||
{
|
|
||||||
find: 'get("disable_new_search")',
|
|
||||||
predicate: () => settings.store.enableIsStaff,
|
|
||||||
replacement: {
|
|
||||||
match: /(?<=showNewSearch"\);return)\s?!/,
|
|
||||||
replace: "!1&&!"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
find: 'H1,title:"Experiments"',
|
find: 'H1,title:"Experiments"',
|
||||||
replacement: {
|
replacement: {
|
||||||
|
|
|
@ -295,7 +295,7 @@ export default definePlugin({
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
predicate: () => settings.store.transformStickers,
|
predicate: () => settings.store.transformStickers,
|
||||||
match: /renderAttachments=function\(\i\){var (\i)=\i.attachments.+?;/,
|
match: /renderAttachments=function\(\i\){var \i=this,(\i)=\i.attachments.+?;/,
|
||||||
replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});`
|
replace: (m, attachments) => `${m}${attachments}=$self.filterAttachments(${attachments});`
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -329,6 +329,20 @@ export default definePlugin({
|
||||||
match: /(?<=\.Messages\.EMOJI_POPOUT_ADDED_PACK_DESCRIPTION.+?return ).{0,1200}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?(?=}\()/,
|
match: /(?<=\.Messages\.EMOJI_POPOUT_ADDED_PACK_DESCRIPTION.+?return ).{0,1200}\.Messages\.EMOJI_POPOUT_UNJOINED_DISCOVERABLE_GUILD_DESCRIPTION.+?(?=}\()/,
|
||||||
replace: reactNode => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!arguments[0]?.fakeNitroNode?.fake)`
|
replace: reactNode => `$self.addFakeNotice(${FakeNoticeType.Emoji},${reactNode},!!arguments[0]?.fakeNitroNode?.fake)`
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "canUsePremiumAppIcons:function",
|
||||||
|
replacement: {
|
||||||
|
match: /canUsePremiumAppIcons:function\(\i\){/,
|
||||||
|
replace: "$&return true;"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
find: "location:\"AppIconHome\"",
|
||||||
|
replacement: {
|
||||||
|
match: /\i\.\i\.isPremium\(\i\.\i\.getCurrentUser\(\)\)/,
|
||||||
|
replace: "true"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin from "@utils/types";
|
||||||
import { GuildStore } from "@webpack/common";
|
import { GuildStore } from "@webpack/common";
|
||||||
|
import { Channel, User } from "discord-types/general";
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "ForceOwnerCrown",
|
name: "ForceOwnerCrown",
|
||||||
|
@ -34,25 +35,15 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
isGuildOwner(props) {
|
isGuildOwner(props: { user: User, channel: Channel, guildId?: string; }) {
|
||||||
// Check if channel is a Group DM, if so return false
|
if (!props?.user?.id) return false;
|
||||||
if (props?.channel?.type === 3) {
|
if (props.channel?.type === 3 /* GROUP_DM */)
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
// guild id is in props twice, fallback if the first is undefined
|
// guild id is in props twice, fallback if the first is undefined
|
||||||
const guildId = props?.guildId ?? props?.channel?.guild_id;
|
const guildId = props.guildId ?? props.channel?.guild_id;
|
||||||
const userId = props?.user?.id;
|
const userId = props.user.id;
|
||||||
|
|
||||||
if (guildId && userId) {
|
return GuildStore.getGuild(guildId)?.ownerId === userId;
|
||||||
const guild = GuildStore.getGuild(guildId);
|
|
||||||
if (guild) {
|
|
||||||
return guild.ownerId === userId;
|
|
||||||
}
|
|
||||||
console.error("[ForceOwnerCrown] failed to get guild", { guildId, guild, props });
|
|
||||||
} else {
|
|
||||||
console.error("[ForceOwnerCrown] no guildId or userId", { guildId, userId, props });
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -106,7 +106,7 @@ export default definePlugin({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
find: ".Messages.EMBEDDED_ACTIVITIES_HAVE_PLAYED_ONE_KNOWN",
|
find: ".Messages.EMBEDDED_ACTIVITIES_DEVELOPER_SHELF_SUBTITLE",
|
||||||
replacement: [
|
replacement: [
|
||||||
{
|
{
|
||||||
match: /(?<=\(\)\.activityTitleText.+?children:(\i)\.name.*?}\),)/,
|
match: /(?<=\(\)\.activityTitleText.+?children:(\i)\.name.*?}\),)/,
|
||||||
|
|
|
@ -360,7 +360,7 @@ export default definePlugin({
|
||||||
{
|
{
|
||||||
// Render editHistory in the deepest div for message content
|
// Render editHistory in the deepest div for message content
|
||||||
match: /(\)\("div",\{id:.+?children:\[)/,
|
match: /(\)\("div",\{id:.+?children:\[)/,
|
||||||
replace: "$1 (arguments[0].message.editHistory.length > 0 ? arguments[0].message.editHistory.map(edit => $self.renderEdit(edit)) : null), "
|
replace: "$1 (arguments[0].message.editHistory?.length > 0 ? arguments[0].message.editHistory.map(edit => $self.renderEdit(edit)) : null), "
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
@ -84,8 +84,8 @@ export default definePlugin({
|
||||||
find: "showProgressBadge:",
|
find: "showProgressBadge:",
|
||||||
predicate: () => settings.store.hidePremiumOffersCount,
|
predicate: () => settings.store.hidePremiumOffersCount,
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /\(function\(\){return \i\.\i\.getUnacknowledgedOffers\(\i\)\.length}\)/,
|
match: /=\i\.unviewedTrialCount\+\i\.unviewedDiscountCount/,
|
||||||
replace: "(function(){return 0})"
|
replace: "=0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
21
src/plugins/noTypingAnimation/index.ts
Normal file
21
src/plugins/noTypingAnimation/index.ts
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 { Devs } from "@utils/constants";
|
||||||
|
import definePlugin from "@utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "NoTypingAnimation",
|
||||||
|
authors: [Devs.AutumnVN],
|
||||||
|
description: "Disables the CPU-intensive typing dots animation",
|
||||||
|
patches: [{
|
||||||
|
find: "dotCycle",
|
||||||
|
replacement: {
|
||||||
|
match: /document.hasFocus\(\)/,
|
||||||
|
replace: "false"
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
|
@ -4,20 +4,44 @@
|
||||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { ChannelStore, ReadStateStore } from "@webpack/common";
|
import { ChannelStore, ReadStateStore, UserStore } from "@webpack/common";
|
||||||
import { Message } from "discord-types/general";
|
import { MessageJSON } from "discord-types/general";
|
||||||
|
|
||||||
const enum ChannelType {
|
const enum ChannelType {
|
||||||
DM = 1,
|
DM = 1,
|
||||||
GROUP_DM = 3
|
GROUP_DM = 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
channelToAffect: {
|
||||||
|
type: OptionType.SELECT,
|
||||||
|
description: "Select the type of DM for the plugin to affect",
|
||||||
|
options: [
|
||||||
|
{ label: "Both", value: "both_dms", default: true },
|
||||||
|
{ label: "User DMs", value: "user_dm" },
|
||||||
|
{ label: "Group DMs", value: "group_dm" },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
allowMentions: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Receive audio pings for @mentions",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
allowEveryone: {
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
description: "Receive audio pings for @everyone and @here in group DMs",
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
name: "OnePingPerDM",
|
name: "OnePingPerDM",
|
||||||
description: "If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit",
|
description: "If unread messages are sent by a user in DMs multiple times, you'll only receive one audio ping. Read the messages to reset the limit",
|
||||||
authors: [Devs.ProffDea],
|
authors: [Devs.ProffDea],
|
||||||
|
settings,
|
||||||
patches: [{
|
patches: [{
|
||||||
find: ".getDesktopType()===",
|
find: ".getDesktopType()===",
|
||||||
replacement: [{
|
replacement: [{
|
||||||
|
@ -29,11 +53,19 @@ export default definePlugin({
|
||||||
replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1"
|
replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1"
|
||||||
}]
|
}]
|
||||||
}],
|
}],
|
||||||
isPrivateChannelRead(message: Message) {
|
isPrivateChannelRead(message: MessageJSON) {
|
||||||
const channelType = ChannelStore.getChannel(message.channel_id)?.type;
|
const channelType = ChannelStore.getChannel(message.channel_id)?.type;
|
||||||
if (channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) {
|
if (channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
(channelType === ChannelType.DM && settings.store.channelToAffect === "group_dm") ||
|
||||||
|
(channelType === ChannelType.GROUP_DM && settings.store.channelToAffect === "user_dm") ||
|
||||||
|
(settings.store.allowMentions && message.mentions.some(m => m.id === UserStore.getCurrentUser().id)) ||
|
||||||
|
(settings.store.allowEveryone && message.mention_everyone)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return ReadStateStore.getOldestUnreadMessageId(message.channel_id) === message.id;
|
return ReadStateStore.getOldestUnreadMessageId(message.channel_id) === message.id;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -81,7 +81,7 @@ export default definePlugin({
|
||||||
// array with the count, or an empty array. Due to spreading, only in the former
|
// array with the count, or an empty array. Due to spreading, only in the former
|
||||||
// case will an element be added to the outer array
|
// case will an element be added to the outer array
|
||||||
// Thanks for the fix, Strencher!
|
// Thanks for the fix, Strencher!
|
||||||
replace: "$&...$1.props.pinCount,"
|
replace: "$&...($1.props.pinCount ?? []),"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Patch renderSection (renders the header) to set the text to "Pinned DMs" instead of "Direct Messages"
|
// Patch renderSection (renders the header) to set the text to "Pinned DMs" instead of "Direct Messages"
|
||||||
|
|
|
@ -30,13 +30,13 @@ import { User } from "discord-types/general";
|
||||||
const SessionsStore = findStoreLazy("SessionsStore");
|
const SessionsStore = findStoreLazy("SessionsStore");
|
||||||
|
|
||||||
function Icon(path: string, opts?: { viewBox?: string; width?: number; height?: number; }) {
|
function Icon(path: string, opts?: { viewBox?: string; width?: number; height?: number; }) {
|
||||||
return ({ color, tooltip }: { color: string; tooltip: string; }) => (
|
return ({ color, tooltip, small }: { color: string; tooltip: string; small: boolean; }) => (
|
||||||
<Tooltip text={tooltip} >
|
<Tooltip text={tooltip} >
|
||||||
{(tooltipProps: any) => (
|
{(tooltipProps: any) => (
|
||||||
<svg
|
<svg
|
||||||
{...tooltipProps}
|
{...tooltipProps}
|
||||||
height={opts?.height ?? 20}
|
height={(opts?.height ?? 20) - (small ? 3 : 0)}
|
||||||
width={opts?.width ?? 20}
|
width={(opts?.width ?? 20) - (small ? 3 : 0)}
|
||||||
viewBox={opts?.viewBox ?? "0 0 24 24"}
|
viewBox={opts?.viewBox ?? "0 0 24 24"}
|
||||||
fill={color}
|
fill={color}
|
||||||
>
|
>
|
||||||
|
@ -57,16 +57,16 @@ type Platform = keyof typeof Icons;
|
||||||
|
|
||||||
const getStatusColor = findByCodeLazy(".TWITCH", ".STREAMING", ".INVISIBLE");
|
const getStatusColor = findByCodeLazy(".TWITCH", ".STREAMING", ".INVISIBLE");
|
||||||
|
|
||||||
const PlatformIcon = ({ platform, status }: { platform: Platform, status: string; }) => {
|
const PlatformIcon = ({ platform, status, small }: { platform: Platform, status: string; small: boolean; }) => {
|
||||||
const tooltip = platform[0].toUpperCase() + platform.slice(1);
|
const tooltip = platform[0].toUpperCase() + platform.slice(1);
|
||||||
const Icon = Icons[platform] ?? Icons.desktop;
|
const Icon = Icons[platform] ?? Icons.desktop;
|
||||||
|
|
||||||
return <Icon color={`var(--${getStatusColor(status)}`} tooltip={tooltip} />;
|
return <Icon color={`var(--${getStatusColor(status)}`} tooltip={tooltip} small={small} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStatus = (id: string): Record<Platform, string> => PresenceStore.getState()?.clientStatuses?.[id];
|
const getStatus = (id: string): Record<Platform, string> => PresenceStore.getState()?.clientStatuses?.[id];
|
||||||
|
|
||||||
const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; }) => {
|
const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => {
|
||||||
if (!user || user.bot) return null;
|
if (!user || user.bot) return null;
|
||||||
|
|
||||||
if (user.id === UserStore.getCurrentUser().id) {
|
if (user.id === UserStore.getCurrentUser().id) {
|
||||||
|
@ -99,6 +99,7 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false }: {
|
||||||
key={platform}
|
key={platform}
|
||||||
platform={platform as Platform}
|
platform={platform as Platform}
|
||||||
status={status}
|
status={status}
|
||||||
|
small={small}
|
||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -137,7 +138,7 @@ const indicatorLocations = {
|
||||||
description: "In the member list",
|
description: "In the member list",
|
||||||
onEnable: () => addDecorator("platform-indicator", props =>
|
onEnable: () => addDecorator("platform-indicator", props =>
|
||||||
<ErrorBoundary noop>
|
<ErrorBoundary noop>
|
||||||
<PlatformIndicator user={props.user} />
|
<PlatformIndicator user={props.user} small={true} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
),
|
),
|
||||||
onDisable: () => removeDecorator("platform-indicator")
|
onDisable: () => removeDecorator("platform-indicator")
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { classes } from "@utils/misc";
|
import { classes } from "@utils/misc";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { UserStore } from "@webpack/common";
|
import { UserStore } from "@webpack/common";
|
||||||
|
@ -39,17 +40,17 @@ function shouldShow(message: Message): boolean {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PronounsChatComponentWrapper({ message }: { message: Message; }) {
|
export const PronounsChatComponentWrapper = ErrorBoundary.wrap(({ message }: { message: Message; }) => {
|
||||||
return shouldShow(message)
|
return shouldShow(message)
|
||||||
? <PronounsChatComponent message={message} />
|
? <PronounsChatComponent message={message} />
|
||||||
: null;
|
: null;
|
||||||
}
|
}, { noop: true });
|
||||||
|
|
||||||
export function CompactPronounsChatComponentWrapper({ message }: { message: Message; }) {
|
export const CompactPronounsChatComponentWrapper = ErrorBoundary.wrap(({ message }: { message: Message; }) => {
|
||||||
return shouldShow(message)
|
return shouldShow(message)
|
||||||
? <CompactPronounsChatComponent message={message} />
|
? <CompactPronounsChatComponent message={message} />
|
||||||
: null;
|
: null;
|
||||||
}
|
}, { noop: true });
|
||||||
|
|
||||||
function PronounsChatComponent({ message }: { message: Message; }) {
|
function PronounsChatComponent({ message }: { message: Message; }) {
|
||||||
const [result] = useFormattedPronouns(message.author.id);
|
const [result] = useFormattedPronouns(message.author.id);
|
||||||
|
@ -63,7 +64,7 @@ function PronounsChatComponent({ message }: { message: Message; }) {
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CompactPronounsChatComponent({ message }: { message: Message; }) {
|
export const CompactPronounsChatComponent = ErrorBoundary.wrap(({ message }: { message: Message; }) => {
|
||||||
const [result] = useFormattedPronouns(message.author.id);
|
const [result] = useFormattedPronouns(message.author.id);
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@ -73,4 +74,4 @@ export function CompactPronounsChatComponent({ message }: { message: Message; })
|
||||||
>• {result}</span>
|
>• {result}</span>
|
||||||
)
|
)
|
||||||
: null;
|
: null;
|
||||||
}
|
}, { noop: true });
|
||||||
|
|
|
@ -41,7 +41,7 @@ export default definePlugin({
|
||||||
find: "showCommunicationDisabledStyles",
|
find: "showCommunicationDisabledStyles",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /("span",{id:\i,className:\i,children:\i}\))/,
|
match: /("span",{id:\i,className:\i,children:\i}\))/,
|
||||||
replace: "$1, $self.CompactPronounsChatComponentWrapper(e)"
|
replace: "$1, $self.CompactPronounsChatComponentWrapper(arguments[0])"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Patch the chat timestamp element (normal mode)
|
// Patch the chat timestamp element (normal mode)
|
||||||
|
@ -49,7 +49,7 @@ export default definePlugin({
|
||||||
find: "showCommunicationDisabledStyles",
|
find: "showCommunicationDisabledStyles",
|
||||||
replacement: {
|
replacement: {
|
||||||
match: /(?<=return\s*\(0,\i\.jsxs?\)\(.+!\i&&)(\(0,\i.jsxs?\)\(.+?\{.+?\}\))/,
|
match: /(?<=return\s*\(0,\i\.jsxs?\)\(.+!\i&&)(\(0,\i.jsxs?\)\(.+?\{.+?\}\))/,
|
||||||
replace: "[$1, $self.PronounsChatComponentWrapper(e)]"
|
replace: "[$1, $self.PronounsChatComponentWrapper(arguments[0])]"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// Patch the profile popout username header to use our pronoun hook instead of Discord's pronouns
|
// Patch the profile popout username header to use our pronoun hook instead of Discord's pronouns
|
||||||
|
|
46
src/plugins/useAltSearch.ts
Normal file
46
src/plugins/useAltSearch.ts
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2023 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
export default definePlugin({
|
||||||
|
name: "UseAlternativeSearch",
|
||||||
|
description: "Use alternative search engine in right click menu",
|
||||||
|
authors: [
|
||||||
|
{
|
||||||
|
id: 1038096782963507210n,
|
||||||
|
name: "skyevg",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
patches: [
|
||||||
|
{
|
||||||
|
find: "https://www.google.com/search?q=",
|
||||||
|
replacement: {
|
||||||
|
match: /"https:\/\/www.google.com\/search\?q=".concat\(encodeURIComponent\(e\)\)/,
|
||||||
|
replace: "Vencord.Settings.plugins.UseAlternativeSearch.source.replace(\"!QUERY!\", encodeURIComponent(e))"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
options: {
|
||||||
|
source: {
|
||||||
|
description: "Search engine's url (use !QUERY! as replacement for the search term)",
|
||||||
|
type: OptionType.STRING,
|
||||||
|
default: "https://duckduckgo.com/?q=!QUERY!",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
|
@ -21,6 +21,7 @@
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vc-uvs-popout-margin > [class^="section"] {
|
.vc-uvs-popout-margin-self>[class^="section"] {
|
||||||
margin-top: -12px;
|
padding-top: 0;
|
||||||
|
padding-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,14 +20,13 @@ import { definePluginSettings } from "@api/Settings";
|
||||||
import ErrorBoundary from "@components/ErrorBoundary";
|
import ErrorBoundary from "@components/ErrorBoundary";
|
||||||
import { Devs } from "@utils/constants";
|
import { Devs } from "@utils/constants";
|
||||||
import definePlugin, { OptionType } from "@utils/types";
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
import { findStoreLazy } from "@webpack";
|
||||||
import { ChannelStore, GuildStore, UserStore } from "@webpack/common";
|
import { ChannelStore, GuildStore, UserStore } from "@webpack/common";
|
||||||
import { User } from "discord-types/general";
|
import { User } from "discord-types/general";
|
||||||
|
|
||||||
import { VoiceChannelSection } from "./components/VoiceChannelSection";
|
import { VoiceChannelSection } from "./components/VoiceChannelSection";
|
||||||
|
|
||||||
const VoiceStateStore = findStoreLazy("VoiceStateStore");
|
const VoiceStateStore = findStoreLazy("VoiceStateStore");
|
||||||
const UserPopoutSectionCssClasses = findByPropsLazy("section", "lastSection");
|
|
||||||
|
|
||||||
const settings = definePluginSettings({
|
const settings = definePluginSettings({
|
||||||
showInUserProfileModal: {
|
showInUserProfileModal: {
|
||||||
|
@ -88,7 +87,7 @@ export default definePlugin({
|
||||||
patchPopout: ({ user }: UserProps) => {
|
patchPopout: ({ user }: UserProps) => {
|
||||||
const isSelfUser = user.id === UserStore.getCurrentUser().id;
|
const isSelfUser = user.id === UserStore.getCurrentUser().id;
|
||||||
return (
|
return (
|
||||||
<div className={isSelfUser ? `vc-uvs-popout-margin ${UserPopoutSectionCssClasses.lastSection}` : ""}>
|
<div className={isSelfUser ? "vc-uvs-popout-margin-self" : ""}>
|
||||||
<VoiceChannelField user={user} />
|
<VoiceChannelField user={user} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
143
src/plugins/uwuifier.ts
Normal file
143
src/plugins/uwuifier.ts
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
/*
|
||||||
|
* Vencord, a modification for Discord's desktop app
|
||||||
|
* Copyright (c) 2022 Vendicated and contributors
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { findOption, RequiredMessageOption } from "@api/Commands";
|
||||||
|
import { addPreEditListener, addPreSendListener, MessageObject, removePreEditListener, removePreSendListener } from "@api/MessageEvents";
|
||||||
|
import { definePluginSettings } from "@api/Settings";
|
||||||
|
import { Devs } from "@utils/constants";
|
||||||
|
import definePlugin, { OptionType } from "@utils/types";
|
||||||
|
|
||||||
|
const endings = [
|
||||||
|
"rawr x3",
|
||||||
|
"OwO",
|
||||||
|
"UwU",
|
||||||
|
"o.O",
|
||||||
|
"-.-",
|
||||||
|
">w<",
|
||||||
|
"(⑅˘꒳˘)",
|
||||||
|
"(ꈍᴗꈍ)",
|
||||||
|
"(˘ω˘)",
|
||||||
|
"(U ᵕ U❁)",
|
||||||
|
"σωσ",
|
||||||
|
"òωó",
|
||||||
|
"(///ˬ///✿)",
|
||||||
|
"(U ﹏ U)",
|
||||||
|
"( ͡o ω ͡o )",
|
||||||
|
"ʘwʘ",
|
||||||
|
":3",
|
||||||
|
":3", // important enough to have twice
|
||||||
|
"XD",
|
||||||
|
"nyaa~~",
|
||||||
|
"mya",
|
||||||
|
">_<",
|
||||||
|
"😳",
|
||||||
|
"🥺",
|
||||||
|
"😳😳😳",
|
||||||
|
"rawr",
|
||||||
|
"^^",
|
||||||
|
"^^;;",
|
||||||
|
"(ˆ ﻌ ˆ)♡",
|
||||||
|
"^•ﻌ•^",
|
||||||
|
"/(^•ω•^)",
|
||||||
|
"(✿oωo)"
|
||||||
|
];
|
||||||
|
|
||||||
|
const replacements = [
|
||||||
|
["small", "smol"],
|
||||||
|
["cute", "kawaii~"],
|
||||||
|
["fluff", "floof"],
|
||||||
|
["love", "luv"],
|
||||||
|
["stupid", "baka"],
|
||||||
|
["what", "nani"],
|
||||||
|
["meow", "nya~"],
|
||||||
|
["hello", "hewwo"],
|
||||||
|
];
|
||||||
|
|
||||||
|
const settings = definePluginSettings({
|
||||||
|
uwuEveryMessage: {
|
||||||
|
description: "Make every single message uwuified",
|
||||||
|
type: OptionType.BOOLEAN,
|
||||||
|
default: false,
|
||||||
|
restartNeeded: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function selectRandomElement(arr) {
|
||||||
|
// generate a random index based on the length of the array
|
||||||
|
const randomIndex = Math.floor(Math.random() * arr.length);
|
||||||
|
|
||||||
|
// return the element at the randomly generated index
|
||||||
|
return arr[randomIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function uwuify(message: string): string {
|
||||||
|
message = message.toLowerCase();
|
||||||
|
// words
|
||||||
|
for (const pair of replacements) {
|
||||||
|
message = message.replaceAll(pair[0], pair[1]);
|
||||||
|
}
|
||||||
|
message = message
|
||||||
|
.replaceAll(/([ \t\n])n/g, "$1ny") // nyaify
|
||||||
|
.replaceAll(/[lr]/g, "w") // [lr] > w
|
||||||
|
.replaceAll(/([ \t\n])([a-z])/g, (_, p1, p2) => Math.random() < .5 ? `${p1}${p2}-${p2}` : `${p1}${p2}`) // stutter
|
||||||
|
.replaceAll(/([^.,!][.,!])([ \t\n])/g, (_, p1, p2) => `${p1} ${selectRandomElement(endings)}${p2}`); // endings
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// actual command declaration
|
||||||
|
export default definePlugin({
|
||||||
|
name: "UwUifier",
|
||||||
|
description: "Simply uwuify commands",
|
||||||
|
authors: [Devs.echo, Devs.skyevg, Devs.PandaNinjas],
|
||||||
|
dependencies: ["CommandsAPI", "MessageEventsAPI"],
|
||||||
|
settings,
|
||||||
|
|
||||||
|
commands: [
|
||||||
|
{
|
||||||
|
name: "uwuify",
|
||||||
|
description: "uwuifies your messages",
|
||||||
|
options: [RequiredMessageOption],
|
||||||
|
|
||||||
|
execute: opts => ({
|
||||||
|
content: uwuify(findOption(opts, "message", "")),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
onSend(msg: MessageObject) {
|
||||||
|
// Only run when it's enabled
|
||||||
|
if (settings.store.uwuEveryMessage) {
|
||||||
|
msg.content = uwuify(msg.content);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.preSend = addPreSendListener((_, msg) => this.onSend(msg));
|
||||||
|
this.preEdit = addPreEditListener((_cid, _mid, msg) =>
|
||||||
|
this.onSend(msg)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
removePreSendListener(this.preSend);
|
||||||
|
removePreEditListener(this.preEdit);
|
||||||
|
},
|
||||||
|
});
|
|
@ -24,7 +24,7 @@ import { Margins } from "@utils/margins";
|
||||||
import { wordsToTitle } from "@utils/text";
|
import { wordsToTitle } from "@utils/text";
|
||||||
import definePlugin, { OptionType, PluginOptionsItem } from "@utils/types";
|
import definePlugin, { OptionType, PluginOptionsItem } from "@utils/types";
|
||||||
import { findByPropsLazy } from "@webpack";
|
import { findByPropsLazy } from "@webpack";
|
||||||
import { Button, ChannelStore, Forms, SelectedChannelStore, useMemo, UserStore } from "@webpack/common";
|
import { Button, ChannelStore, Forms, GuildMemberStore, SelectedChannelStore, SelectedGuildStore, useMemo, UserStore } from "@webpack/common";
|
||||||
|
|
||||||
interface VoiceState {
|
interface VoiceState {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
@ -70,11 +70,12 @@ function clean(str: string) {
|
||||||
.trim();
|
.trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatText(str: string, user: string, channel: string, displayName: string) {
|
function formatText(str: string, user: string, channel: string, displayName: string, nickname: string) {
|
||||||
return str
|
return str
|
||||||
.replaceAll("{{USER}}", clean(user) || (user ? "Someone" : ""))
|
.replaceAll("{{USER}}", clean(user) || (user ? "Someone" : ""))
|
||||||
.replaceAll("{{CHANNEL}}", clean(channel) || "channel")
|
.replaceAll("{{CHANNEL}}", clean(channel) || "channel")
|
||||||
.replaceAll("{{DISPLAY_NAME}}", clean(displayName) || (displayName ? "Someone" : ""));
|
.replaceAll("{{DISPLAY_NAME}}", clean(displayName) || (displayName ? "Someone" : ""))
|
||||||
|
.replaceAll("{{NICKNAME}}", clean(nickname) || (nickname ? "Someone" : ""));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -145,8 +146,9 @@ function updateStatuses(type: string, { deaf, mute, selfDeaf, selfMute, userId,
|
||||||
function playSample(tempSettings: any, type: string) {
|
function playSample(tempSettings: any, type: string) {
|
||||||
const settings = Object.assign({}, Settings.plugins.VcNarrator, tempSettings);
|
const settings = Object.assign({}, Settings.plugins.VcNarrator, tempSettings);
|
||||||
const currentUser = UserStore.getCurrentUser();
|
const currentUser = UserStore.getCurrentUser();
|
||||||
|
const myGuildId = SelectedGuildStore.getGuildId();
|
||||||
|
|
||||||
speak(formatText(settings[type + "Message"], currentUser.username, "general", (currentUser as any).globalName ?? currentUser.username), settings);
|
speak(formatText(settings[type + "Message"], currentUser.username, "general", (currentUser as any).globalName ?? currentUser.username, GuildMemberStore.getNick(myGuildId, currentUser.id) ?? currentUser.username), settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default definePlugin({
|
export default definePlugin({
|
||||||
|
@ -156,6 +158,7 @@ export default definePlugin({
|
||||||
|
|
||||||
flux: {
|
flux: {
|
||||||
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
|
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
|
||||||
|
const myGuildId = SelectedGuildStore.getGuildId();
|
||||||
const myChanId = SelectedChannelStore.getVoiceChannelId();
|
const myChanId = SelectedChannelStore.getVoiceChannelId();
|
||||||
const myId = UserStore.getCurrentUser().id;
|
const myId = UserStore.getCurrentUser().id;
|
||||||
|
|
||||||
|
@ -175,9 +178,10 @@ export default definePlugin({
|
||||||
const template = Settings.plugins.VcNarrator[type + "Message"];
|
const template = Settings.plugins.VcNarrator[type + "Message"];
|
||||||
const user = isMe && !Settings.plugins.VcNarrator.sayOwnName ? "" : UserStore.getUser(userId).username;
|
const user = isMe && !Settings.plugins.VcNarrator.sayOwnName ? "" : UserStore.getUser(userId).username;
|
||||||
const displayName = user && ((UserStore.getUser(userId) as any).globalName ?? user);
|
const displayName = user && ((UserStore.getUser(userId) as any).globalName ?? user);
|
||||||
|
const nickname = user && (GuildMemberStore.getNick(myGuildId, userId) ?? user);
|
||||||
const channel = ChannelStore.getChannel(id).name;
|
const channel = ChannelStore.getChannel(id).name;
|
||||||
|
|
||||||
speak(formatText(template, user, channel, displayName));
|
speak(formatText(template, user, channel, displayName, nickname));
|
||||||
|
|
||||||
// updateStatuses(type, state, isMe);
|
// updateStatuses(type, state, isMe);
|
||||||
}
|
}
|
||||||
|
@ -189,7 +193,7 @@ export default definePlugin({
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
|
|
||||||
const event = s.mute || s.selfMute ? "unmute" : "mute";
|
const event = s.mute || s.selfMute ? "unmute" : "mute";
|
||||||
speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name, ""));
|
speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name, "", ""));
|
||||||
},
|
},
|
||||||
|
|
||||||
AUDIO_TOGGLE_SELF_DEAF() {
|
AUDIO_TOGGLE_SELF_DEAF() {
|
||||||
|
@ -198,7 +202,7 @@ export default definePlugin({
|
||||||
if (!s) return;
|
if (!s) return;
|
||||||
|
|
||||||
const event = s.deaf || s.selfDeaf ? "undeafen" : "deafen";
|
const event = s.deaf || s.selfDeaf ? "undeafen" : "deafen";
|
||||||
speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name, ""));
|
speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name, "", ""));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -315,8 +319,8 @@ export default definePlugin({
|
||||||
You can customise the spoken messages below. You can disable specific messages by setting them to nothing
|
You can customise the spoken messages below. You can disable specific messages by setting them to nothing
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
<Forms.FormText>
|
<Forms.FormText>
|
||||||
The special placeholders <code>{"{{USER}}"}</code>, <code>{"{{DISPLAY_NAME}}"}</code> and <code>{"{{CHANNEL}}"}</code>{" "}
|
The special placeholders <code>{"{{USER}}"}</code>, <code>{"{{DISPLAY_NAME}}"}</code>, <code>{"{{NICKNAME}}"}</code> and <code>{"{{CHANNEL}}"}</code>{" "}
|
||||||
will be replaced with the user's name (nothing if it's yourself), the user's display name and the channel's name respectively
|
will be replaced with the user's name (nothing if it's yourself), the user's display name, the user's nickname on current server and the channel's name respectively
|
||||||
</Forms.FormText>
|
</Forms.FormText>
|
||||||
{hasEnglishVoices && (
|
{hasEnglishVoices && (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { filters, findByCode, mapMangledModuleLazy } from "@webpack";
|
import { filters, findByCode, findByPropsLazy, mapMangledModuleLazy } from "@webpack";
|
||||||
import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react";
|
import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react";
|
||||||
|
|
||||||
import { LazyComponent } from "./react";
|
import { LazyComponent } from "./react";
|
||||||
|
@ -132,12 +132,7 @@ export const ModalContent = LazyComponent(() => Modals.ModalContent);
|
||||||
export const ModalFooter = LazyComponent(() => Modals.ModalFooter);
|
export const ModalFooter = LazyComponent(() => Modals.ModalFooter);
|
||||||
export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton);
|
export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton);
|
||||||
|
|
||||||
const ModalAPI = mapMangledModuleLazy("onCloseRequest:null!=", {
|
const ModalAPI = findByPropsLazy("openModalLazy");
|
||||||
openModal: filters.byCode("onCloseRequest:null!="),
|
|
||||||
closeModal: filters.byCode("onCloseCallback&&"),
|
|
||||||
openModalLazy: m => m?.length === 1 && filters.byCode(".apply(this,arguments)")(m),
|
|
||||||
closeAllModals: filters.byCode(".value.key,")
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wait for the render promise to resolve, then open a modal with it.
|
* Wait for the render promise to resolve, then open a modal with it.
|
||||||
|
|
|
@ -16,10 +16,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { proxyLazy } from "@utils/lazy";
|
||||||
import type * as Stores from "discord-types/stores";
|
import type * as Stores from "discord-types/stores";
|
||||||
|
|
||||||
// eslint-disable-next-line path-alias/no-relative
|
// eslint-disable-next-line path-alias/no-relative
|
||||||
import { filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "../webpack";
|
import { filters, findByCode, findByProps, findByPropsLazy, mapMangledModuleLazy } from "../webpack";
|
||||||
import { waitForStore } from "./internal";
|
import { waitForStore } from "./internal";
|
||||||
import * as t from "./types/stores";
|
import * as t from "./types/stores";
|
||||||
|
|
||||||
|
@ -83,7 +84,14 @@ export const useStateFromStores: <T>(
|
||||||
idk?: any,
|
idk?: any,
|
||||||
isEqual?: (old: T, newer: T) => boolean
|
isEqual?: (old: T, newer: T) => boolean
|
||||||
) => T
|
) => T
|
||||||
= findByCodeLazy("useStateFromStores");
|
// FIXME: hack to support old stable and new canary
|
||||||
|
= proxyLazy(() => {
|
||||||
|
try {
|
||||||
|
return findByProps("useStateFromStores").useStateFromStores;
|
||||||
|
} catch {
|
||||||
|
return findByCode('("useStateFromStores")');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
waitForStore("DraftStore", s => DraftStore = s);
|
waitForStore("DraftStore", s => DraftStore = s);
|
||||||
waitForStore("UserStore", s => UserStore = s);
|
waitForStore("UserStore", s => UserStore = s);
|
||||||
|
|
|
@ -16,10 +16,11 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { proxyLazy } from "@utils/lazy";
|
||||||
import type { User } from "discord-types/general";
|
import type { User } from "discord-types/general";
|
||||||
|
|
||||||
// eslint-disable-next-line path-alias/no-relative
|
// eslint-disable-next-line path-alias/no-relative
|
||||||
import { _resolveReady, filters, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "../webpack";
|
import { _resolveReady, filters, find, findByCodeLazy, findByPropsLazy, findLazy, mapMangledModuleLazy, waitFor } from "../webpack";
|
||||||
import type * as t from "./types/utils";
|
import type * as t from "./types/utils";
|
||||||
|
|
||||||
export let FluxDispatcher: t.FluxDispatcher;
|
export let FluxDispatcher: t.FluxDispatcher;
|
||||||
|
@ -126,4 +127,11 @@ waitFor("parseTopic", m => Parser = m);
|
||||||
export let SettingsRouter: any;
|
export let SettingsRouter: any;
|
||||||
waitFor(["open", "saveAccountChanges"], m => SettingsRouter = m);
|
waitFor(["open", "saveAccountChanges"], m => SettingsRouter = m);
|
||||||
|
|
||||||
export const PermissionsBits: t.PermissionsBits = findLazy(m => typeof m.ADMINISTRATOR === "bigint");
|
// FIXME: hack to support old stable and new canary
|
||||||
|
export const PermissionsBits: t.PermissionsBits = proxyLazy(() => {
|
||||||
|
try {
|
||||||
|
return find(m => m.Permissions?.ADMINISTRATOR).Permissions;
|
||||||
|
} catch {
|
||||||
|
return find(m => typeof m.ADMINISTRATOR === "bigint");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -36,9 +36,8 @@ if (window[WEBPACK_CHUNK]) {
|
||||||
Object.defineProperty(window, WEBPACK_CHUNK, {
|
Object.defineProperty(window, WEBPACK_CHUNK, {
|
||||||
get: () => webpackChunk,
|
get: () => webpackChunk,
|
||||||
set: v => {
|
set: v => {
|
||||||
if (v?.push !== Array.prototype.push) {
|
if (v?.push !== Array.prototype.push && _initWebpack(v)) {
|
||||||
logger.info(`Patching ${WEBPACK_CHUNK}.push`);
|
logger.info(`Patching ${WEBPACK_CHUNK}.push`);
|
||||||
_initWebpack(v);
|
|
||||||
patchPush();
|
patchPush();
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
delete window[WEBPACK_CHUNK];
|
delete window[WEBPACK_CHUNK];
|
||||||
|
@ -85,10 +84,9 @@ function patchPush() {
|
||||||
logger.error("Error in patched chunk", err);
|
logger.error("Error in patched chunk", err);
|
||||||
return void originalMod(module, exports, require);
|
return void originalMod(module, exports, require);
|
||||||
}
|
}
|
||||||
|
|
||||||
// There are (at the time of writing) 11 modules exporting the window
|
// There are (at the time of writing) 11 modules exporting the window
|
||||||
// Make these non enumerable to improve webpack search performance
|
// Make these non enumerable to improve webpack search performance
|
||||||
if (module.exports === window) {
|
if (exports === window) {
|
||||||
Object.defineProperty(require.c, id, {
|
Object.defineProperty(require.c, id, {
|
||||||
value: require.c[id],
|
value: require.c[id],
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
|
|
|
@ -62,9 +62,50 @@ export type CallbackFn = (mod: any, id: number) => void;
|
||||||
export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {
|
export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {
|
||||||
if (cache !== void 0) throw "no.";
|
if (cache !== void 0) throw "no.";
|
||||||
|
|
||||||
wreq = instance.push([[Symbol("Vencord")], {}, r => r]);
|
instance.push([[Symbol("Vencord")], {}, r => wreq = r]);
|
||||||
|
if (!wreq) return false;
|
||||||
|
|
||||||
cache = wreq.c;
|
cache = wreq.c;
|
||||||
instance.pop();
|
instance.pop();
|
||||||
|
|
||||||
|
for (const id in cache) {
|
||||||
|
const { exports } = cache[id];
|
||||||
|
if (!exports) continue;
|
||||||
|
|
||||||
|
const numberId = Number(id);
|
||||||
|
|
||||||
|
for (const callback of listeners) {
|
||||||
|
try {
|
||||||
|
callback(exports, numberId);
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("Error in webpack listener", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [filter, callback] of subscriptions) {
|
||||||
|
try {
|
||||||
|
if (filter(exports)) {
|
||||||
|
subscriptions.delete(filter);
|
||||||
|
callback(exports, numberId);
|
||||||
|
} else if (typeof exports === "object") {
|
||||||
|
if (exports.default && filter(exports.default)) {
|
||||||
|
subscriptions.delete(filter);
|
||||||
|
callback(exports.default, numberId);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const nested in exports) if (nested.length <= 3) {
|
||||||
|
if (exports[nested] && filter(exports[nested])) {
|
||||||
|
subscriptions.delete(filter);
|
||||||
|
callback(exports[nested], numberId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error("Error while firing callback for webpack chunk", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IS_DEV && IS_DISCORD_DESKTOP) {
|
if (IS_DEV && IS_DISCORD_DESKTOP) {
|
||||||
|
|
Loading…
Reference in a new issue