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",
|
||||
"private": "true",
|
||||
"version": "1.5.6",
|
||||
"version": "1.5.8",
|
||||
"description": "The cutest Discord client mod",
|
||||
"homepage": "https://github.com/Vendicated/Vencord#readme",
|
||||
"bugs": {
|
||||
|
|
|
@ -43,7 +43,7 @@ const nodeCommonOpts = {
|
|||
format: "cjs",
|
||||
platform: "node",
|
||||
target: ["esnext"],
|
||||
external: ["electron", ...commonOpts.external],
|
||||
external: ["electron", "original-fs", ...commonOpts.external],
|
||||
define: defines,
|
||||
};
|
||||
|
||||
|
|
|
@ -61,6 +61,13 @@ const report = {
|
|||
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) {
|
||||
s = s.replace(/```/g, "`\u200B`\u200B`");
|
||||
return "```" + s + " ```";
|
||||
|
@ -86,6 +93,8 @@ async function printReport() {
|
|||
console.log(` - Error: ${toCodeBlock(p.error)}`);
|
||||
});
|
||||
|
||||
report.otherErrors = report.otherErrors.filter(e => !IGNORED_DISCORD_ERRORS.some(regex => e.match(regex)));
|
||||
|
||||
console.log("## Discord Errors");
|
||||
report.otherErrors.forEach(e => {
|
||||
console.log(`- ${toCodeBlock(e)}`);
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
font-size: 20px;
|
||||
height: 20px;
|
||||
position: relative;
|
||||
text-wrap: nowrap;
|
||||
}
|
||||
|
||||
.vc-author-modal-name::before {
|
||||
|
|
|
@ -25,7 +25,7 @@ import { Margins } from "@utils/margins";
|
|||
import { classes } from "@utils/misc";
|
||||
import { showItemInFolder } from "@utils/native";
|
||||
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 { UserThemeHeader } from "main/themes";
|
||||
import type { ComponentType, Ref, SyntheticEvent } from "react";
|
||||
|
@ -41,7 +41,7 @@ type FileInput = ComponentType<{
|
|||
}>;
|
||||
|
||||
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 cl = classNameFactory("vc-settings-theme-");
|
||||
|
|
|
@ -62,6 +62,10 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
|||
} catch { }
|
||||
|
||||
|
||||
const findHeader = (headers: Record<string, string[]>, headerName: Lowercase<string>) => {
|
||||
return Object.keys(headers).find(h => h.toLowerCase() === headerName);
|
||||
};
|
||||
|
||||
// Remove CSP
|
||||
type PolicyResult = Record<string, string[]>;
|
||||
|
||||
|
@ -73,6 +77,7 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
|||
result[directiveKey] = directiveValue;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
const stringifyPolicy = (policy: PolicyResult): string =>
|
||||
|
@ -81,31 +86,39 @@ if (IS_VESKTOP || !IS_VANILLA) {
|
|||
.map(directive => directive.flat().join(" "))
|
||||
.join("; ");
|
||||
|
||||
function patchCsp(headers: Record<string, string[]>, header: string) {
|
||||
if (header in headers) {
|
||||
const patchCsp = (headers: Record<string, string[]>) => {
|
||||
const header = findHeader(headers, "content-security-policy");
|
||||
|
||||
if (header) {
|
||||
const csp = parsePolicy(headers[header][0]);
|
||||
|
||||
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.
|
||||
// Perhaps auto generate with esbuild
|
||||
csp["script-src"] ??= [];
|
||||
csp["script-src"].push("'unsafe-eval'", "https://unpkg.com", "https://cdnjs.cloudflare.com");
|
||||
headers[header] = [stringifyPolicy(csp)];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
session.defaultSession.webRequest.onHeadersReceived(({ responseHeaders, resourceType }, cb) => {
|
||||
if (responseHeaders) {
|
||||
if (resourceType === "mainFrame")
|
||||
patchCsp(responseHeaders, "content-security-policy");
|
||||
patchCsp(responseHeaders);
|
||||
|
||||
// Fix hosts that don't properly set the css content type, such as
|
||||
// raw.githubusercontent.com
|
||||
if (resourceType === "stylesheet")
|
||||
responseHeaders["content-type"] = ["text/css"];
|
||||
if (resourceType === "stylesheet") {
|
||||
const header = findHeader(responseHeaders, "content-type");
|
||||
if (header)
|
||||
responseHeaders[header] = ["text/css"];
|
||||
}
|
||||
}
|
||||
|
||||
cb({ cancel: false, responseHeaders });
|
||||
});
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
*/
|
||||
|
||||
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";
|
||||
|
||||
function isNewer($new: string, old: string) {
|
||||
|
|
|
@ -27,8 +27,8 @@ export default definePlugin({
|
|||
{
|
||||
find: ".withMentionPrefix",
|
||||
replacement: {
|
||||
match: /(.roleDot.{10,50}{children:.{1,2})}\)/,
|
||||
replace: "$1.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))})"
|
||||
match: /currentUserIsPremium:.{0,70}{children:\i(?=}\))/,
|
||||
replace: "$&.concat(Vencord.Api.MessageDecorations.__addDecorationsToMessage(arguments[0]))"
|
||||
}
|
||||
}
|
||||
],
|
||||
|
|
|
@ -63,26 +63,26 @@ export default definePlugin({
|
|||
replacement: {
|
||||
get match() {
|
||||
switch (Settings.plugins.Settings.settingsLocation) {
|
||||
case "top": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/;
|
||||
case "aboveNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/;
|
||||
case "belowNitro": return /\{section:(\i)\.ID\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/;
|
||||
case "belowActivity": return /(?<=\{section:(\i)\.ID\.DIVIDER},)\{section:"changelog"/;
|
||||
case "bottom": return /\{section:(\i)\.ID\.CUSTOM,\s*element:.+?}/;
|
||||
case "top": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.USER_SETTINGS\}/;
|
||||
case "aboveNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.BILLING_SETTINGS\}/;
|
||||
case "belowNitro": return /\{section:(\i\.\i)\.HEADER,\s*label:(\i)\.\i\.Messages\.APP_SETTINGS\}/;
|
||||
case "belowActivity": return /(?<=\{section:(\i\.\i)\.DIVIDER},)\{section:"changelog"/;
|
||||
case "bottom": return /\{section:(\i\.\i)\.CUSTOM,\s*element:.+?}/;
|
||||
case "aboveActivity":
|
||||
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),$&"
|
||||
}
|
||||
}],
|
||||
|
||||
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 [
|
||||
{
|
||||
section: ID.HEADER,
|
||||
section: SectionTypes.HEADER,
|
||||
label: "Vencord",
|
||||
className: "vc-settings-header"
|
||||
},
|
||||
|
@ -128,9 +128,9 @@ export default definePlugin({
|
|||
element: require("@components/VencordSettings/PatchHelperTab").default,
|
||||
className: "vc-patch-helper"
|
||||
},
|
||||
...this.customSections.map(func => func(ID)),
|
||||
...this.customSections.map(func => func(SectionTypes)),
|
||||
{
|
||||
section: ID.DIVIDER
|
||||
section: SectionTypes.DIVIDER
|
||||
}
|
||||
].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 { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
|
||||
const UserPopoutSectionCssClasses = findByPropsLazy("section", "lastSection");
|
||||
|
||||
export default definePlugin({
|
||||
name: "BetterNotesBox",
|
||||
|
@ -34,12 +37,20 @@ export default definePlugin({
|
|||
match: /hideNote:.+?(?=[,}])/g,
|
||||
replace: "hideNote:true",
|
||||
}
|
||||
}, {
|
||||
},
|
||||
{
|
||||
find: "Messages.NOTE_PLACEHOLDER",
|
||||
replacement: {
|
||||
match: /\.NOTE_PLACEHOLDER,/,
|
||||
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,
|
||||
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"',
|
||||
replacement: {
|
||||
|
|
|
@ -295,7 +295,7 @@ export default definePlugin({
|
|||
},
|
||||
{
|
||||
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});`
|
||||
}
|
||||
]
|
||||
|
@ -329,6 +329,20 @@ export default definePlugin({
|
|||
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)`
|
||||
}
|
||||
},
|
||||
{
|
||||
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 definePlugin from "@utils/types";
|
||||
import { GuildStore } from "@webpack/common";
|
||||
import { Channel, User } from "discord-types/general";
|
||||
|
||||
export default definePlugin({
|
||||
name: "ForceOwnerCrown",
|
||||
|
@ -34,25 +35,15 @@ export default definePlugin({
|
|||
}
|
||||
}
|
||||
],
|
||||
isGuildOwner(props) {
|
||||
// Check if channel is a Group DM, if so return false
|
||||
if (props?.channel?.type === 3) {
|
||||
isGuildOwner(props: { user: User, channel: Channel, guildId?: string; }) {
|
||||
if (!props?.user?.id) return false;
|
||||
if (props.channel?.type === 3 /* GROUP_DM */)
|
||||
return false;
|
||||
}
|
||||
|
||||
// guild id is in props twice, fallback if the first is undefined
|
||||
const guildId = props?.guildId ?? props?.channel?.guild_id;
|
||||
const userId = props?.user?.id;
|
||||
const guildId = props.guildId ?? props.channel?.guild_id;
|
||||
const userId = props.user.id;
|
||||
|
||||
if (guildId && 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;
|
||||
return GuildStore.getGuild(guildId)?.ownerId === userId;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -106,7 +106,7 @@ export default definePlugin({
|
|||
}
|
||||
},
|
||||
{
|
||||
find: ".Messages.EMBEDDED_ACTIVITIES_HAVE_PLAYED_ONE_KNOWN",
|
||||
find: ".Messages.EMBEDDED_ACTIVITIES_DEVELOPER_SHELF_SUBTITLE",
|
||||
replacement: [
|
||||
{
|
||||
match: /(?<=\(\)\.activityTitleText.+?children:(\i)\.name.*?}\),)/,
|
||||
|
|
|
@ -360,7 +360,7 @@ export default definePlugin({
|
|||
{
|
||||
// Render editHistory in the deepest div for message content
|
||||
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:",
|
||||
predicate: () => settings.store.hidePremiumOffersCount,
|
||||
replacement: {
|
||||
match: /\(function\(\){return \i\.\i\.getUnacknowledgedOffers\(\i\)\.length}\)/,
|
||||
replace: "(function(){return 0})"
|
||||
match: /=\i\.unviewedTrialCount\+\i\.unviewedDiscountCount/,
|
||||
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
|
||||
*/
|
||||
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin from "@utils/types";
|
||||
import { ChannelStore, ReadStateStore } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { ChannelStore, ReadStateStore, UserStore } from "@webpack/common";
|
||||
import { MessageJSON } from "discord-types/general";
|
||||
|
||||
const enum ChannelType {
|
||||
DM = 1,
|
||||
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({
|
||||
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",
|
||||
authors: [Devs.ProffDea],
|
||||
settings,
|
||||
patches: [{
|
||||
find: ".getDesktopType()===",
|
||||
replacement: [{
|
||||
|
@ -29,11 +53,19 @@ export default definePlugin({
|
|||
replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1"
|
||||
}]
|
||||
}],
|
||||
isPrivateChannelRead(message: Message) {
|
||||
isPrivateChannelRead(message: MessageJSON) {
|
||||
const channelType = ChannelStore.getChannel(message.channel_id)?.type;
|
||||
if (channelType !== ChannelType.DM && channelType !== ChannelType.GROUP_DM) {
|
||||
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;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -81,7 +81,7 @@ export default definePlugin({
|
|||
// 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
|
||||
// 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"
|
||||
|
|
|
@ -30,13 +30,13 @@ import { User } from "discord-types/general";
|
|||
const SessionsStore = findStoreLazy("SessionsStore");
|
||||
|
||||
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} >
|
||||
{(tooltipProps: any) => (
|
||||
<svg
|
||||
{...tooltipProps}
|
||||
height={opts?.height ?? 20}
|
||||
width={opts?.width ?? 20}
|
||||
height={(opts?.height ?? 20) - (small ? 3 : 0)}
|
||||
width={(opts?.width ?? 20) - (small ? 3 : 0)}
|
||||
viewBox={opts?.viewBox ?? "0 0 24 24"}
|
||||
fill={color}
|
||||
>
|
||||
|
@ -57,16 +57,16 @@ type Platform = keyof typeof Icons;
|
|||
|
||||
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 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 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.id === UserStore.getCurrentUser().id) {
|
||||
|
@ -99,6 +99,7 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false }: {
|
|||
key={platform}
|
||||
platform={platform as Platform}
|
||||
status={status}
|
||||
small={small}
|
||||
/>
|
||||
));
|
||||
|
||||
|
@ -137,7 +138,7 @@ const indicatorLocations = {
|
|||
description: "In the member list",
|
||||
onEnable: () => addDecorator("platform-indicator", props =>
|
||||
<ErrorBoundary noop>
|
||||
<PlatformIndicator user={props.user} />
|
||||
<PlatformIndicator user={props.user} small={true} />
|
||||
</ErrorBoundary>
|
||||
),
|
||||
onDisable: () => removeDecorator("platform-indicator")
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { classes } from "@utils/misc";
|
||||
import { findByPropsLazy } from "@webpack";
|
||||
import { UserStore } from "@webpack/common";
|
||||
|
@ -39,17 +40,17 @@ function shouldShow(message: Message): boolean {
|
|||
return true;
|
||||
}
|
||||
|
||||
export function PronounsChatComponentWrapper({ message }: { message: Message; }) {
|
||||
export const PronounsChatComponentWrapper = ErrorBoundary.wrap(({ message }: { message: Message; }) => {
|
||||
return shouldShow(message)
|
||||
? <PronounsChatComponent message={message} />
|
||||
: null;
|
||||
}
|
||||
}, { noop: true });
|
||||
|
||||
export function CompactPronounsChatComponentWrapper({ message }: { message: Message; }) {
|
||||
export const CompactPronounsChatComponentWrapper = ErrorBoundary.wrap(({ message }: { message: Message; }) => {
|
||||
return shouldShow(message)
|
||||
? <CompactPronounsChatComponent message={message} />
|
||||
: null;
|
||||
}
|
||||
}, { noop: true });
|
||||
|
||||
function PronounsChatComponent({ message }: { message: Message; }) {
|
||||
const [result] = useFormattedPronouns(message.author.id);
|
||||
|
@ -63,7 +64,7 @@ function PronounsChatComponent({ message }: { message: Message; }) {
|
|||
: null;
|
||||
}
|
||||
|
||||
export function CompactPronounsChatComponent({ message }: { message: Message; }) {
|
||||
export const CompactPronounsChatComponent = ErrorBoundary.wrap(({ message }: { message: Message; }) => {
|
||||
const [result] = useFormattedPronouns(message.author.id);
|
||||
|
||||
return result
|
||||
|
@ -73,4 +74,4 @@ export function CompactPronounsChatComponent({ message }: { message: Message; })
|
|||
>• {result}</span>
|
||||
)
|
||||
: null;
|
||||
}
|
||||
}, { noop: true });
|
||||
|
|
|
@ -41,7 +41,7 @@ export default definePlugin({
|
|||
find: "showCommunicationDisabledStyles",
|
||||
replacement: {
|
||||
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)
|
||||
|
@ -49,7 +49,7 @@ export default definePlugin({
|
|||
find: "showCommunicationDisabledStyles",
|
||||
replacement: {
|
||||
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
|
||||
|
|
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;
|
||||
}
|
||||
|
||||
.vc-uvs-popout-margin > [class^="section"] {
|
||||
margin-top: -12px;
|
||||
.vc-uvs-popout-margin-self>[class^="section"] {
|
||||
padding-top: 0;
|
||||
padding-bottom: 12px;
|
||||
}
|
||||
|
|
|
@ -20,14 +20,13 @@ import { definePluginSettings } from "@api/Settings";
|
|||
import ErrorBoundary from "@components/ErrorBoundary";
|
||||
import { Devs } from "@utils/constants";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { findByPropsLazy, findStoreLazy } from "@webpack";
|
||||
import { findStoreLazy } from "@webpack";
|
||||
import { ChannelStore, GuildStore, UserStore } from "@webpack/common";
|
||||
import { User } from "discord-types/general";
|
||||
|
||||
import { VoiceChannelSection } from "./components/VoiceChannelSection";
|
||||
|
||||
const VoiceStateStore = findStoreLazy("VoiceStateStore");
|
||||
const UserPopoutSectionCssClasses = findByPropsLazy("section", "lastSection");
|
||||
|
||||
const settings = definePluginSettings({
|
||||
showInUserProfileModal: {
|
||||
|
@ -88,7 +87,7 @@ export default definePlugin({
|
|||
patchPopout: ({ user }: UserProps) => {
|
||||
const isSelfUser = user.id === UserStore.getCurrentUser().id;
|
||||
return (
|
||||
<div className={isSelfUser ? `vc-uvs-popout-margin ${UserPopoutSectionCssClasses.lastSection}` : ""}>
|
||||
<div className={isSelfUser ? "vc-uvs-popout-margin-self" : ""}>
|
||||
<VoiceChannelField user={user} />
|
||||
</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 definePlugin, { OptionType, PluginOptionsItem } from "@utils/types";
|
||||
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 {
|
||||
userId: string;
|
||||
|
@ -70,11 +70,12 @@ function clean(str: string) {
|
|||
.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
|
||||
.replaceAll("{{USER}}", clean(user) || (user ? "Someone" : ""))
|
||||
.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) {
|
||||
const settings = Object.assign({}, Settings.plugins.VcNarrator, tempSettings);
|
||||
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({
|
||||
|
@ -156,6 +158,7 @@ export default definePlugin({
|
|||
|
||||
flux: {
|
||||
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: VoiceState[]; }) {
|
||||
const myGuildId = SelectedGuildStore.getGuildId();
|
||||
const myChanId = SelectedChannelStore.getVoiceChannelId();
|
||||
const myId = UserStore.getCurrentUser().id;
|
||||
|
||||
|
@ -175,9 +178,10 @@ export default definePlugin({
|
|||
const template = Settings.plugins.VcNarrator[type + "Message"];
|
||||
const user = isMe && !Settings.plugins.VcNarrator.sayOwnName ? "" : UserStore.getUser(userId).username;
|
||||
const displayName = user && ((UserStore.getUser(userId) as any).globalName ?? user);
|
||||
const nickname = user && (GuildMemberStore.getNick(myGuildId, userId) ?? user);
|
||||
const channel = ChannelStore.getChannel(id).name;
|
||||
|
||||
speak(formatText(template, user, channel, displayName));
|
||||
speak(formatText(template, user, channel, displayName, nickname));
|
||||
|
||||
// updateStatuses(type, state, isMe);
|
||||
}
|
||||
|
@ -189,7 +193,7 @@ export default definePlugin({
|
|||
if (!s) return;
|
||||
|
||||
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() {
|
||||
|
@ -198,7 +202,7 @@ export default definePlugin({
|
|||
if (!s) return;
|
||||
|
||||
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
|
||||
</Forms.FormText>
|
||||
<Forms.FormText>
|
||||
The special placeholders <code>{"{{USER}}"}</code>, <code>{"{{DISPLAY_NAME}}"}</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
|
||||
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, the user's nickname on current server and the channel's name respectively
|
||||
</Forms.FormText>
|
||||
{hasEnglishVoices && (
|
||||
<>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* 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 { LazyComponent } from "./react";
|
||||
|
@ -132,12 +132,7 @@ export const ModalContent = LazyComponent(() => Modals.ModalContent);
|
|||
export const ModalFooter = LazyComponent(() => Modals.ModalFooter);
|
||||
export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton);
|
||||
|
||||
const ModalAPI = mapMangledModuleLazy("onCloseRequest:null!=", {
|
||||
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,")
|
||||
});
|
||||
const ModalAPI = findByPropsLazy("openModalLazy");
|
||||
|
||||
/**
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import type * as Stores from "discord-types/stores";
|
||||
|
||||
// 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 * as t from "./types/stores";
|
||||
|
||||
|
@ -83,7 +84,14 @@ export const useStateFromStores: <T>(
|
|||
idk?: any,
|
||||
isEqual?: (old: T, newer: T) => boolean
|
||||
) => 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("UserStore", s => UserStore = s);
|
||||
|
|
|
@ -16,10 +16,11 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { proxyLazy } from "@utils/lazy";
|
||||
import type { User } from "discord-types/general";
|
||||
|
||||
// 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";
|
||||
|
||||
export let FluxDispatcher: t.FluxDispatcher;
|
||||
|
@ -126,4 +127,11 @@ waitFor("parseTopic", m => Parser = m);
|
|||
export let SettingsRouter: any;
|
||||
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, {
|
||||
get: () => webpackChunk,
|
||||
set: v => {
|
||||
if (v?.push !== Array.prototype.push) {
|
||||
if (v?.push !== Array.prototype.push && _initWebpack(v)) {
|
||||
logger.info(`Patching ${WEBPACK_CHUNK}.push`);
|
||||
_initWebpack(v);
|
||||
patchPush();
|
||||
// @ts-ignore
|
||||
delete window[WEBPACK_CHUNK];
|
||||
|
@ -85,10 +84,9 @@ function patchPush() {
|
|||
logger.error("Error in patched chunk", err);
|
||||
return void originalMod(module, exports, require);
|
||||
}
|
||||
|
||||
// There are (at the time of writing) 11 modules exporting the window
|
||||
// Make these non enumerable to improve webpack search performance
|
||||
if (module.exports === window) {
|
||||
if (exports === window) {
|
||||
Object.defineProperty(require.c, id, {
|
||||
value: require.c[id],
|
||||
enumerable: false,
|
||||
|
|
|
@ -62,9 +62,50 @@ export type CallbackFn = (mod: any, id: number) => void;
|
|||
export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {
|
||||
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;
|
||||
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) {
|
||||
|
|
Loading…
Reference in a new issue