fix: strip query params from asset URLs before filesystem lookup
All checks were successful
Deploy to Production / test (push) Successful in 36s

Icon URLs stored with cache-busting query params (e.g. ?v=123) caused
existsSync to fail, preventing Discord attachment fallback and leaving
unresolvable localhost URLs as thumbnails.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
syntaxbullet
2026-03-26 15:48:19 +01:00
parent 58d07a02fd
commit 782a138fd8
3 changed files with 13 additions and 7 deletions

View File

@@ -18,7 +18,7 @@ import { join } from "path";
import { existsSync } from "fs"; import { existsSync } from "fs";
import { LootType, EffectType } from "@shared/lib/constants"; import { LootType, EffectType } from "@shared/lib/constants";
import type { LootTableItem } from "@shared/lib/types"; import type { LootTableItem } from "@shared/lib/types";
import { getRarityConfig, defaultName } from "@shared/lib/rarity"; import { getRarityConfig, defaultName, stripQuery } from "@shared/lib/rarity";
export function getShopListingMessage( export function getShopListingMessage(
item: { item: {
@@ -40,7 +40,7 @@ export function getShopListingMessage(
// Handle local icon // Handle local icon
if (item.iconUrl && isLocalAssetUrl(item.iconUrl)) { if (item.iconUrl && isLocalAssetUrl(item.iconUrl)) {
const iconPath = join(process.cwd(), "bot/assets/graphics", item.iconUrl.replace(/^\/?assets\//, "")); const iconPath = join(process.cwd(), "bot/assets/graphics", stripQuery(item.iconUrl).replace(/^\/?assets\//, ""));
if (existsSync(iconPath)) { if (existsSync(iconPath)) {
const iconName = defaultName(item.iconUrl); const iconName = defaultName(item.iconUrl);
files.push(new AttachmentBuilder(iconPath, { name: iconName })); files.push(new AttachmentBuilder(iconPath, { name: iconName }));
@@ -53,7 +53,7 @@ export function getShopListingMessage(
if (item.imageUrl === item.iconUrl && thumbnailUrl?.startsWith("attachment://")) { if (item.imageUrl === item.iconUrl && thumbnailUrl?.startsWith("attachment://")) {
displayImageUrl = thumbnailUrl; displayImageUrl = thumbnailUrl;
} else { } else {
const imagePath = join(process.cwd(), "bot/assets/graphics", item.imageUrl.replace(/^\/?assets\//, "")); const imagePath = join(process.cwd(), "bot/assets/graphics", stripQuery(item.imageUrl).replace(/^\/?assets\//, ""));
if (existsSync(imagePath)) { if (existsSync(imagePath)) {
const imageName = defaultName(item.imageUrl); const imageName = defaultName(item.imageUrl);
if (!files.find(f => f.name === imageName)) { if (!files.find(f => f.name === imageName)) {

View File

@@ -12,7 +12,7 @@ import {
MessageFlags, MessageFlags,
} from "discord.js"; } from "discord.js";
import { resolveAssetUrl, isLocalAssetUrl } from "@shared/lib/assets"; import { resolveAssetUrl, isLocalAssetUrl } from "@shared/lib/assets";
import { getRarityConfig, defaultName } from "@shared/lib/rarity"; import { getRarityConfig, defaultName, stripQuery } from "@shared/lib/rarity";
import { join } from "path"; import { join } from "path";
import { existsSync } from "fs"; import { existsSync } from "fs";
@@ -126,7 +126,7 @@ export function getLootboxResultMessage(
const iconSource = lootResult.rewardType === "ITEM" ? lootResult.item?.iconUrl : item?.iconUrl; const iconSource = lootResult.rewardType === "ITEM" ? lootResult.item?.iconUrl : item?.iconUrl;
if (iconSource) { if (iconSource) {
if (isLocalAssetUrl(iconSource)) { if (isLocalAssetUrl(iconSource)) {
const iconPath = join(process.cwd(), "bot/assets/graphics", iconSource.replace(/^\/?assets\//, "")); const iconPath = join(process.cwd(), "bot/assets/graphics", stripQuery(iconSource).replace(/^\/?assets\//, ""));
if (existsSync(iconPath)) { if (existsSync(iconPath)) {
const iconName = defaultName(iconSource); const iconName = defaultName(iconSource);
files.push(new AttachmentBuilder(iconPath, { name: iconName })); files.push(new AttachmentBuilder(iconPath, { name: iconName }));
@@ -150,7 +150,7 @@ export function getLootboxResultMessage(
if (imgSource && imgSource !== iconSrc) { if (imgSource && imgSource !== iconSrc) {
let displayImageUrl: string | null = null; let displayImageUrl: string | null = null;
if (isLocalAssetUrl(imgSource)) { if (isLocalAssetUrl(imgSource)) {
const imagePath = join(process.cwd(), "bot/assets/graphics", imgSource.replace(/^\/?assets\//, "")); const imagePath = join(process.cwd(), "bot/assets/graphics", stripQuery(imgSource).replace(/^\/?assets\//, ""));
if (existsSync(imagePath)) { if (existsSync(imagePath)) {
const imageName = defaultName(imgSource); const imageName = defaultName(imgSource);
if (!files.find(f => f.name === imageName)) { if (!files.find(f => f.name === imageName)) {

View File

@@ -18,5 +18,11 @@ export function getRarityConfig(rarity: string): { color: number; emoji: string;
} }
export function defaultName(path: string): string { export function defaultName(path: string): string {
return path.split("/").pop() || "image.png"; return stripQuery(path).split("/").pop() || "image.png";
}
/** Strip query parameters from a URL/path (e.g. "foo.png?v=123" → "foo.png") */
export function stripQuery(url: string): string {
const idx = url.indexOf("?");
return idx === -1 ? url : url.slice(0, idx);
} }