From c0309cfcb6266263320be96a92cc21c2ec31b079 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:04:52 -0500 Subject: [PATCH 1/3] Add more equibop checks --- src/debug/runReporter.ts | 2 +- src/utils/Logger.ts | 2 +- src/utils/native.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts index 679c40bc3..339edbbee 100644 --- a/src/debug/runReporter.ts +++ b/src/debug/runReporter.ts @@ -39,7 +39,7 @@ async function runReporter() { await loadLazyChunksDone; - if (IS_REPORTER && IS_WEB && !IS_VESKTOP) { + if (IS_REPORTER && IS_WEB && !IS_VESKTOP && !IS_EQUIBOP) { console.log("[REPORTER_META]", { buildNumber: getBuildNumber(), buildHash: window.GLOBAL_ENV.SENTRY_TAGS.buildId diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts index 50c74bf46..75b79c299 100644 --- a/src/utils/Logger.ts +++ b/src/utils/Logger.ts @@ -32,7 +32,7 @@ export class Logger { constructor(public name: string, public color: string = "white") { } private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") { - if (IS_REPORTER && IS_WEB && !IS_VESKTOP) { + if (IS_REPORTER && IS_WEB && !IS_VESKTOP && !IS_EQUIBOP) { console[level]("[Equicord]", this.name + ":", ...args); return; } diff --git a/src/utils/native.ts b/src/utils/native.ts index 3dd46b432..8636b4ffe 100644 --- a/src/utils/native.ts +++ b/src/utils/native.ts @@ -19,7 +19,7 @@ export function relaunch() { if (IS_DISCORD_DESKTOP) window.DiscordNative.app.relaunch(); - else if (IS_VESKTOP) + else if (IS_VESKTOP || IS_EQUIBOP) window.VesktopNative.app.relaunch(); else location.reload(); From c4a01001baa3ec4765c1282ab53bfa6cc2bd0287 Mon Sep 17 00:00:00 2001 From: Key Date: Thu, 19 Feb 2026 20:34:17 +0000 Subject: [PATCH 2/3] Quoter: render custom emojis inline (#807) * Quoter: render custom emojis inline * fix: use getEmojiURL --- src/equicordplugins/quoter/utils.tsx | 151 +++++++++++++++++++++++++-- 1 file changed, 141 insertions(+), 10 deletions(-) diff --git a/src/equicordplugins/quoter/utils.tsx b/src/equicordplugins/quoter/utils.tsx index 44fcf7cba..d963967ff 100644 --- a/src/equicordplugins/quoter/utils.tsx +++ b/src/equicordplugins/quoter/utils.tsx @@ -5,11 +5,22 @@ */ import { User } from "@vencord/discord-types"; -import { UserStore } from "@webpack/common"; +import { IconUtils, UserStore } from "@webpack/common"; import { applyPalette, GIFEncoder, quantize } from "gifenc"; import { CANVAS_CONFIG, CanvasConfig, FONT_SIZES, FontSizeCalculation, QuoteFont, QuoteImageOptions, SPACING } from "./types"; +const CUSTOM_EMOJI_REGEX = //g; +const CUSTOM_EMOJI_PLACEHOLDER = "\uFFFC"; +const CUSTOM_EMOJI_SIZE_MULTIPLIER = 1.15; +const CUSTOM_EMOJI_BASELINE_OFFSET = 0.85; + +interface CustomEmojiToken { + id: string; + name: string; + animated: boolean; +} + export function sizeUpgrade(url: string): string { const u = new URL(url); u.searchParams.set("size", "512"); @@ -32,7 +43,7 @@ export async function fetchImageAsBlob(url: string): Promise { } export function fixUpQuote(quote: string): string { - let result = quote.replace(//g, ""); + let result = quote; const mentionMatches = result.match(/<@!?\d+>/g); if (mentionMatches) { mentionMatches.forEach(match => { @@ -141,6 +152,89 @@ function drawGradientOverlay(ctx: CanvasRenderingContext2D, config: CanvasConfig ctx.fillRect(config.height - SPACING.gradientWidth, 0, SPACING.gradientWidth, config.height); } +function extractCustomEmojis(text: string): { text: string; emojis: CustomEmojiToken[]; } { + const emojis: CustomEmojiToken[] = []; + const cleanText = text.replace(CUSTOM_EMOJI_REGEX, (match, name: string, id: string) => { + emojis.push({ + id, + name, + animated: match.startsWith(" { + const image = new Image(); + const blobUrl = URL.createObjectURL(blob); + + try { + await new Promise((resolve, reject) => { + image.onload = () => resolve(); + image.onerror = () => reject(new Error("Failed to load image")); + image.src = blobUrl; + }); + return image; + } finally { + URL.revokeObjectURL(blobUrl); + } +} + +async function loadCustomEmojiImage(emoji: CustomEmojiToken): Promise { + const animatedVariants = emoji.animated ? [true, false] : [false]; + for (const animated of animatedVariants) { + try { + const blob = await fetchImageAsBlob(IconUtils.getEmojiURL({ + id: emoji.id, + animated, + size: 96 + })); + return await loadImageFromBlob(blob); + } catch { + continue; + } + } + + return null; +} + +async function loadCustomEmojiImages(emojis: CustomEmojiToken[]): Promise> { + if (!emojis.length) return new Map(); + + const unique = new Map(); + emojis.forEach(emoji => { + const key = `${emoji.id}:${emoji.animated ? "a" : "s"}`; + if (!unique.has(key)) unique.set(key, emoji); + }); + + const entries = await Promise.all( + Array.from(unique.entries()).map(async ([key, emoji]) => [key, await loadCustomEmojiImage(emoji)] as const) + ); + + return entries.reduce((acc, [key, image]) => { + if (image) acc.set(key, image); + return acc; + }, new Map()); +} + +function measureTextWithCustomEmojis(ctx: CanvasRenderingContext2D, text: string, fontSize: number): number { + const emojiSize = fontSize * CUSTOM_EMOJI_SIZE_MULTIPLIER; + let width = 0; + let start = 0; + + for (let i = 0; i < text.length; i++) { + if (text[i] !== CUSTOM_EMOJI_PLACEHOLDER) continue; + width += ctx.measureText(text.slice(start, i)).width; + width += emojiSize; + start = i + 1; + } + + width += ctx.measureText(text.slice(start)).width; + return width; +} + function calculateTextLines( ctx: CanvasRenderingContext2D, text: string, @@ -154,7 +248,7 @@ function calculateTextLines( let currentLine: string[] = []; words.forEach(word => { - if (ctx.measureText(word).width > maxWidth) { + if (measureTextWithCustomEmojis(ctx, word, fontSize) > maxWidth) { if (currentLine.length) { lines.push(currentLine.join(" ")); currentLine = []; @@ -163,7 +257,7 @@ function calculateTextLines( let chunk = ""; for (const char of word) { const testChunk = chunk + char; - if (ctx.measureText(testChunk).width > maxWidth) { + if (measureTextWithCustomEmojis(ctx, testChunk, fontSize) > maxWidth) { if (chunk) lines.push(chunk); chunk = char; } else { @@ -173,7 +267,7 @@ function calculateTextLines( if (chunk) lines.push(chunk); } else { const testLine = [...currentLine, word].join(" "); - if (ctx.measureText(testLine).width > maxWidth && currentLine.length) { + if (measureTextWithCustomEmojis(ctx, testLine, fontSize) > maxWidth && currentLine.length) { lines.push(currentLine.join(" ")); currentLine = [word]; } else { @@ -224,17 +318,52 @@ function drawQuoteText( ctx: CanvasRenderingContext2D, calculation: FontSizeCalculation, font: QuoteFont, - config: CanvasConfig + config: CanvasConfig, + emojis: CustomEmojiToken[], + emojiImages: Map ): number { ctx.fillStyle = "#fff"; ctx.font = `300 ${calculation.fontSize}px '${font}', sans-serif`; + const emojiSize = calculation.fontSize * CUSTOM_EMOJI_SIZE_MULTIPLIER; let quoteY = (config.height - calculation.totalHeight) / 2; + let emojiIndex = 0; calculation.lines.forEach(line => { - const xOffset = (config.quoteAreaWidth - ctx.measureText(line).width) / 2; + const xOffset = (config.quoteAreaWidth - measureTextWithCustomEmojis(ctx, line, calculation.fontSize)) / 2; + let x = config.quoteAreaX + xOffset; + let segmentStart = 0; quoteY += calculation.lineHeight; - ctx.fillText(line, config.quoteAreaX + xOffset, quoteY); + + for (let i = 0; i < line.length; i++) { + if (line[i] !== CUSTOM_EMOJI_PLACEHOLDER) continue; + + const segment = line.slice(segmentStart, i); + if (segment) { + ctx.fillText(segment, x, quoteY); + x += ctx.measureText(segment).width; + } + + const emoji = emojis[emojiIndex++]; + if (emoji) { + const key = `${emoji.id}:${emoji.animated ? "a" : "s"}`; + const image = emojiImages.get(key); + if (image) { + const y = quoteY - emojiSize * CUSTOM_EMOJI_BASELINE_OFFSET; + ctx.drawImage(image, x, y, emojiSize, emojiSize); + } else { + ctx.fillText("□", x, quoteY); + } + } + + x += emojiSize; + segmentStart = i + 1; + } + + const tail = line.slice(segmentStart); + if (tail) { + ctx.fillText(tail, x, quoteY); + } }); return quoteY; @@ -283,6 +412,8 @@ export async function createQuoteImage(options: QuoteImageOptions): Promise Date: Thu, 19 Feb 2026 15:35:22 -0500 Subject: [PATCH 3/3] bump --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e1d7f371e..d3d234ab7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "equicord", "private": "true", - "version": "1.14.2.0", + "version": "1.14.2.1", "description": "The other cutest Discord client mod", "homepage": "https://github.com/Equicord/Equicord#readme", "bugs": { @@ -122,4 +122,4 @@ "engines": { "node": ">=18" } -} \ No newline at end of file +}