Compare commits

..

8 commits

Author SHA1 Message Date
Nuckyz
e95525429c
Merge branch 'dev' into discord-fixes 2025-01-31 16:33:31 -03:00
Nuckyz
a15efbb4b2
Merge branch 'dev' into discord-fixes 2025-01-29 01:06:20 -03:00
Nuckyz
d06789d963
Add permission check 2025-01-27 23:17:12 -03:00
Nuckyz
9ddeb2c67d
Merge branch 'dev' into discord-fixes 2025-01-27 23:02:39 -03:00
Nuckyz
42628f8bf9
Add more mod view fixes 2025-01-27 22:35:06 -03:00
Nuckyz
bb6d2f3d82
Move ShowHiddenThings fix 2025-01-27 20:21:28 -03:00
Nuckyz
aa64267ddb
Move CustomRPC fix 2025-01-27 20:15:43 -03:00
Nuckyz
76002fb2eb
DiscordFixes plugin 2025-01-27 20:13:01 -03:00
12 changed files with 131 additions and 239 deletions

View file

@ -1,24 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 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: "DynamicImageModalAPI",
authors: [Devs.sadan, Devs.Nuckyz],
description: "Allows you to omit either width or height when opening an image modal",
patches: [
{
find: "SCALE_DOWN:",
replacement: {
match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/,
replace: (_, width, height) => `!((null == ${width} || 0 === ${width}) && (null == ${height} || 0 === ${height}))`
}
}
]
});

View file

@ -0,0 +1,114 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import "./styles.css";
import { classNameFactory } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs } from "@utils/constants";
import { classes } from "@utils/misc";
import definePlugin from "@utils/types";
import { findByCodeLazy } from "@webpack";
import { GuildStore, PermissionsBits, PermissionStore, useEffect } from "@webpack/common";
import { PropsWithChildren } from "react";
const cl = classNameFactory("vc-discord-fixes-");
const fetchMemberSupplemental = findByCodeLazy('type:"FETCH_GUILD_MEMBER_SUPPLEMENTAL_SUCCESS"');
type UsernameWrapperProps = PropsWithChildren<Record<string, any> & {
className?: string;
}>;
const UsernameWrapper = ErrorBoundary.wrap((props: UsernameWrapperProps) => {
return <div {...props} className={classes(cl("username-wrapper"), props.className)} />;
}, { noop: true });
type MemberSupplementalCache = Record<string, number>;
let memberSupplementalCache = {};
function setMemberSupplementalCache(cache: MemberSupplementalCache) {
memberSupplementalCache = cache;
}
function useFetchMemberSupplemental(guildId: string, userId: string) {
useEffect(() => {
if (!PermissionStore.can(PermissionsBits.MANAGE_GUILD, GuildStore.getGuild(guildId))) {
return;
}
// Set this member as unfetched in the member supplemental cache
memberSupplementalCache[`${guildId}-${userId}`] ??= 1;
fetchMemberSupplemental(guildId, [userId]);
}, [guildId, userId]);
}
export default definePlugin({
name: "DiscordFixes",
description: "Fixes Discord issues required or not for Vencord plugins to work properly",
authors: [Devs.Nuckyz],
required: true,
patches: [
// Make username wrapper a div instead of a span, to align items to center easier with flex
{
find: '"Message Username"',
replacement: {
match: /"span"(?=,{id:)/,
replace: "$self.UsernameWrapper"
}
},
// Make MediaModal use the Discord media component instead of a simple "img" component,
// if any of height or width exists and is not 0. Default behavior is to only use the component if both exist.
{
find: "SCALE_DOWN:",
replacement: {
match: /!\(null==(\i)\|\|0===\i\|\|null==(\i)\|\|0===\i\)/,
replace: (_, width, height) => `!((null==${width}||0===${width})&&(null==${height}||0===${height}))`
}
},
// Make buttons show up for your own activities
{
find: ".party?(0",
all: true,
replacement: {
match: /\i\.id===\i\.id\?null:/,
replace: ""
}
},
// Fixes mod view depending on Members page being loaded to acquire the member highest role
{
find: "#{intl::GUILD_MEMBER_MOD_VIEW_PERMISSION_GRANTED_BY_ARIA_LABEL}),allowOverflow:",
replacement: {
match: /(role:)\i(?=,guildId.{0,100}role:(\i\[))/,
replace: "$1$2arguments[0].member.highestRoleId]"
}
},
// Fix mod view join method depending on loading the member in the Members page
{
find: ".MANUAL_MEMBER_VERIFICATION]:",
replacement: {
match: /useEffect\(.{0,50}\.requestMembersById\(.+?\[\i,\i\]\);(?<=userId:(\i),guildId:(\i).+?)/,
replace: (m, userId, guildId) => `${m}$self.useFetchMemberSupplemental(${guildId},${userId});`
}
},
// Fix member supplemental caching code and export cache object to use within the plugin code,
// there is no other way around it besides patching again
{
find: ".MEMBER_SAFETY_SUPPLEMENTAL(",
replacement: [
{
match: /(let (\i)={};)(function \i\(\i,\i\){return \i\+)(\i})/,
replace: (_, rest1, cache, rest2, rest3) => `${rest1}$self.setMemberSupplementalCache(${cache});${rest2}"-"+${rest3}`
}
]
}
],
UsernameWrapper,
setMemberSupplementalCache,
useFetchMemberSupplemental
});

View file

@ -0,0 +1,11 @@
.vc-discord-fixes-username-wrapper {
display: inline-flex;
align-items: center;
justify-content: center;
}
/* Remove top gap from chat tags, because the plugin makes it use a div to center instead */
[class*="botTag"][class*="rem"] {
margin-top: unset;
top: unset;
}

View file

@ -1,179 +0,0 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2025 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import { MessageExtra, MessageObject } from "@api/MessageEvents";
import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin from "@utils/types";
import { findLazy } from "@webpack";
const TesseractLogger = new Logger("Tesseract", "#ff9e64");
let worker!: Tesseract.Worker;
function reduceBboxGreatest(acc: Tesseract.Bbox, cur: Tesseract.Bbox): Tesseract.Bbox {
return {
x0: Math.min(acc.x0, cur.x0),
x1: Math.max(acc.x1, cur.x1),
y0: Math.min(acc.y0, cur.y0),
y1: Math.max(acc.y1, cur.y1)
};
}
function findWordLocation(text: Tesseract.Block[], regex: RegExp): Tesseract.Bbox[] {
const locs: Tesseract.Bbox[] = [];
for (let i = 0; i < text.length; i++) {
const block = text[i];
if (block.text.match(regex)) {
const bl = locs.length;
for (let j = 0; j < block.paragraphs.length; j++) {
const paragraph = block.paragraphs[j];
if (paragraph.text.match(regex)) {
const bl = locs.length;
for (let k = 0; k < paragraph.lines.length; k++) {
const line = paragraph.lines[k];
if (line.text.match(regex)) {
const bl = locs.length;
for (let l = 0; l < line.words.length; l++) {
const word = line.words[l];
let matches: RegExpExecArray[];
if ((matches = [...word.text.matchAll(new RegExp(regex, `${regex.flags.replace("g", "")}g`))]).length) {
for (const match of matches) {
const syms = word.symbols
.slice(match.index, match.index + match[0].length)
.map(x => x.bbox)
.reduce(reduceBboxGreatest);
locs.push(syms);
}
}
}
if (locs.length === bl) {
locs.push(line.bbox);
}
}
}
if (locs.length === bl) {
locs.push(paragraph.bbox);
}
}
}
if (locs.length === bl) {
locs.push(block.bbox);
}
}
}
return locs;
}
interface CloudUpload {
new(file: { file: File; isThumbnail: boolean; platform: number; }, channelId: string, showDiaglog: boolean, numCurAttachments: number): CloudUpload;
upload(): void;
}
const CloudUpload: CloudUpload = findLazy(m => m.prototype?.trackUploadFinished);
function getImage(file: File): Promise<HTMLImageElement> {
return new Promise((resolve, reject) => {
const img = new Image();
img.src = URL.createObjectURL(file);
img.onload = () => resolve(img);
img.onerror = reject;
});
}
function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {
return new Promise<Blob>(resolve => {
canvas.toBlob(blob => {
if (blob) {
resolve(blob);
} else {
throw new Error("Failed to create Blob");
}
}, "image/png");
});
}
const badRegex = /nix(?:os)?|This ?content ?is|blocked ?by ?this ?server/i;
export default definePlugin({
name: "AntiTessie",
authors: [Devs.sadan],
description: "Scans your messages with ocr for anything that matches the selected regex, and if found, blurs it",
start() {
if (!window?.Tesseract) {
fetch(
"https://unpkg.com/tesseract.js@6.0.0/dist/tesseract.min.js"
)
.then(async r => void (0, eval)(await r.text()))
.then(async () => {
worker = await Tesseract.createWorker("eng", Tesseract.OEM.TESSERACT_LSTM_COMBINED, {
corePath: "https://unpkg.com/tesseract.js-core@6.0.0/tesseract-core-simd-lstm.wasm.js",
workerPath: "https://unpkg.com/tesseract.js@6.0.0/dist/worker.min.js",
});
})
.then(() => {
worker.setParameters({
tessedit_pageseg_mode: Tesseract.PSM.AUTO
});
});
}
},
stop() {
worker.terminate();
},
async onBeforeMessageSend(channelId: string, message: MessageObject, extra: MessageExtra): Promise<void | { cancel: boolean; }> {
if (extra.channel.guild_id !== "1015060230222131221" && extra.channel.guild_id !== "1041012073603289109") {
return;
}
const uploads = extra?.uploads ?? [];
for (let i = 0; i < uploads.length; i++) {
async function convertToFile(canvas: HTMLCanvasElement): Promise<File> {
const blob = await canvasToBlob(canvas);
return new File([blob], `${upload.filename.substring(0, upload.filename.lastIndexOf("."))}.png`, {
type: "image/png"
});
}
const upload = uploads[i];
if (!upload.isImage) continue;
console.log(upload);
const ret = await worker.recognize(upload.item.file, {
}, {
text: true,
blocks: true,
});
if (ret.data.text.match(badRegex)) {
const toBlur = findWordLocation(ret.data.blocks!, badRegex);
const sourceImage = await getImage(upload.item.file);
const width = sourceImage.naturalWidth;
const height = sourceImage.naturalHeight;
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d")!;
ctx.canvas.width = width;
ctx.canvas.height = height;
ctx.drawImage(sourceImage, 0, 0, width, height);
for (const { x0, x1, y0, y1 } of toBlur) {
ctx.fillStyle = "black";
ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
}
const newFile = await convertToFile(canvas);
const attachment = new CloudUpload({
file: newFile,
isThumbnail: false,
platform: 1
}, channelId, false, uploads.length);
attachment.upload();
extra.uploads![i] = attachment as any;
}
}
}
});

View file

@ -399,17 +399,6 @@ export default definePlugin({
stop: () => setRpc(true),
settings,
patches: [
{
find: ".party?(0",
all: true,
replacement: {
match: /\i\.id===\i\.id\?null:/,
replace: ""
}
}
],
settingsAboutComponent: () => {
const activity = useAwaiter(createActivity);
const gameActivityEnabled = ShowCurrentGame.useSetting();

View file

@ -133,7 +133,7 @@ function getBadges({ userId }: BadgeUserArgs): ProfileBadge[] {
}));
}
const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, small = false }: { user: User; wantMargin?: boolean; wantTopMargin?: boolean; small?: boolean; }) => {
const PlatformIndicator = ({ user, wantMargin = true, small = false }: { user: User; wantMargin?: boolean; small?: boolean; }) => {
if (!user || user.bot) return null;
ensureOwnStatus(user);
@ -157,7 +157,6 @@ const PlatformIndicator = ({ user, wantMargin = true, wantTopMargin = false, sma
className="vc-platform-indicator"
style={{
marginLeft: wantMargin ? 4 : 0,
top: wantTopMargin ? 2 : 0,
gap: 2
}}
>
@ -190,7 +189,7 @@ const indicatorLocations = {
description: "Inside messages",
onEnable: () => addMessageDecoration("platform-indicator", props =>
<ErrorBoundary noop>
<PlatformIndicator user={props.message?.author} wantTopMargin={true} />
<PlatformIndicator user={props.message?.author} />
</ErrorBoundary>
),
onDisable: () => removeMessageDecoration("platform-indicator")

View file

@ -28,7 +28,6 @@ export default definePlugin({
name: "ServerInfo",
description: "Allows you to view info about a server",
authors: [Devs.Ven, Devs.Nuckyz],
dependencies: ["DynamicImageModalAPI"],
tags: ["guild", "info", "ServerProfile"],
contextMenus: {

View file

@ -65,16 +65,7 @@ export default definePlugin({
replace: "return true",
}
},
// fixes a bug where Members page must be loaded to see highest role, why is Discord depending on MemberSafetyStore.getEnhancedMember for something that can be obtained here?
{
find: "#{intl::GUILD_MEMBER_MOD_VIEW_PERMISSION_GRANTED_BY_ARIA_LABEL}),allowOverflow:",
predicate: () => settings.store.showModView,
replacement: {
match: /(role:)\i(?=,guildId.{0,100}role:(\i\[))/,
replace: "$1$2arguments[0].member.highestRoleId]",
}
},
// allows you to open mod view on yourself
// Allows you to open mod view on yourself
{
find: 'action:"PRESS_MOD_VIEW",icon:',
predicate: () => settings.store.showModView,

View file

@ -130,7 +130,6 @@ function VoiceChannelTooltip({ channel, isLocked }: VoiceChannelTooltipProps) {
interface VoiceChannelIndicatorProps {
userId: string;
isMessageIndicator?: boolean;
isProfile?: boolean;
isActionButton?: boolean;
shouldHighlight?: boolean;
@ -138,7 +137,7 @@ interface VoiceChannelIndicatorProps {
const clickTimers = {} as Record<string, any>;
export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isMessageIndicator, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => {
export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isProfile, isActionButton, shouldHighlight }: VoiceChannelIndicatorProps) => {
const channelId = useStateFromStores([VoiceStateStore], () => VoiceStateStore.getVoiceStateForUser(userId)?.channelId as string | undefined);
const channel = channelId == null ? undefined : ChannelStore.getChannel(channelId);
@ -182,7 +181,7 @@ export const VoiceChannelIndicator = ErrorBoundary.wrap(({ userId, isMessageIndi
{props => {
const iconProps: IconProps = {
...props,
className: classes(isMessageIndicator && cl("message-indicator"), (!isProfile && !isActionButton) && cl("speaker-margin"), isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight),
className: classes((!isProfile && !isActionButton) && cl("speaker-margin"), isActionButton && ActionButtonClasses.actionButton, shouldHighlight && ActionButtonClasses.highlight),
size: isActionButton ? 20 : undefined,
onClick
};

View file

@ -99,7 +99,7 @@ export default definePlugin({
addMemberListDecorator("UserVoiceShow", ({ user }) => user == null ? null : <VoiceChannelIndicator userId={user.id} />);
}
if (settings.store.showInMessages) {
addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} isMessageIndicator />);
addMessageDecoration("UserVoiceShow", ({ message }) => message?.author == null ? null : <VoiceChannelIndicator userId={message.author.id} />);
}
},

View file

@ -17,12 +17,6 @@
margin-left: 4px;
}
.vc-uvs-message-indicator {
display: inline-flex;
top: 2.5px;
position: relative;
}
.vc-uvs-tooltip-container {
max-width: 300px;
}

View file

@ -176,7 +176,6 @@ export default definePlugin({
authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz, Devs.nyx],
description: "Makes avatars and banners in user profiles clickable, adds View Icon/Banner entries in the user, server and group channel context menu.",
tags: ["ImageUtilities"],
dependencies: ["DynamicImageModalAPI"],
settings,