import { DATA_IMAGE_URL_PREFIX, IMAGE_FIELD_NAMES, MAX_IMAGE_URL_SEARCH_DEPTH, PROTOCOL_RELATIVE_IMAGE_URL_PATTERN, REMOTE_IMAGE_URL_PATTERN, SIDEBAR_GAME_ROW_CONTAINER_SELECTOR, SIDEBAR_GAME_ROW_IMAGE_SELECTOR, SIDEBAR_GAME_ROW_MORE_SELECTOR, SIDEBAR_GAME_ROW_TITLE_SELECTOR, SIDEBAR_GAME_ROW_TOOLTIP_ID_PATTERN, STEAM_APP_ID_FIELD_NAMES, STEAM_APP_ID_PATTERN, STEAM_CONTAINER_FIELD_PATTERN, STEAM_CONTAINER_ID_FIELD_NAMES, STEAM_PLATFORM, WEMOD_STEAM_COMMUNITY_CDN_BASE_URL, } from "./constants.js" import { isRecord, safeString, toStringId } from "./runtime.js" export function pickImageUrl(...values) { for (const value of values) { const imageUrl = normalizeImageUrl(value) if (imageUrl) { return imageUrl } } return null } export function normalizeImageUrl(value, depth = 0) { if (typeof value === "string") { const trimmed = value.trim() if ( REMOTE_IMAGE_URL_PATTERN.test(trimmed) || trimmed.startsWith(DATA_IMAGE_URL_PREFIX) ) { return trimmed } if (PROTOCOL_RELATIVE_IMAGE_URL_PATTERN.test(trimmed)) { return `https:${trimmed}` } return null } if (depth > MAX_IMAGE_URL_SEARCH_DEPTH || !value) { return null } if (Array.isArray(value)) { for (const entry of value) { const imageUrl = normalizeImageUrl(entry, depth + 1) if (imageUrl) { return imageUrl } } return null } if (!isRecord(value)) { return null } for (const key of IMAGE_FIELD_NAMES) { const imageUrl = normalizeImageUrl(value[key], depth + 1) if (imageUrl) { return imageUrl } } return null } export function getSteamClientIconUrl(...values) { for (const value of values) { const steamAppId = toStringId(value) if (steamAppId && STEAM_APP_ID_PATTERN.test(steamAppId)) { return `${WEMOD_STEAM_COMMUNITY_CDN_BASE_URL}/${steamAppId}/client_icon/96.webp` } } return null } export function getSidebarGameRowClientIcons() { const byTitleId = new Map() const byTitleName = new Map() if (typeof document === "undefined") { return { byTitleId, byTitleName } } const containers = document.querySelectorAll( SIDEBAR_GAME_ROW_CONTAINER_SELECTOR ) for (const container of containers) { const imageElement = container.querySelector( SIDEBAR_GAME_ROW_IMAGE_SELECTOR ) const titleElement = container.querySelector( SIDEBAR_GAME_ROW_TITLE_SELECTOR ) const moreButton = container.querySelector(SIDEBAR_GAME_ROW_MORE_SELECTOR) if (!imageElement || !titleElement) { continue } const backgroundImageUrl = getCssBackgroundImageUrl( safeString(imageElement.style?.backgroundImage) || getComputedStyle(imageElement).backgroundImage ) if (!backgroundImageUrl) { continue } const tooltipId = safeString( moreButton?.getAttribute?.("data-tooltip-trigger-for") ) const titleId = tooltipId.match(SIDEBAR_GAME_ROW_TOOLTIP_ID_PATTERN)?.[1] ?? null const titleName = normalizeTitleMatchKey(titleElement.textContent) if (titleId) { byTitleId.set(titleId, backgroundImageUrl) } if (titleName) { byTitleName.set(titleName, backgroundImageUrl) } } return { byTitleId, byTitleName } } export function getSidebarGameRowClientIconUrl( sidebarIcons, titleId, ...names ) { const normalizedTitleId = toStringId(titleId) if (normalizedTitleId && sidebarIcons.byTitleId.has(normalizedTitleId)) { return sidebarIcons.byTitleId.get(normalizedTitleId) ?? null } for (const name of names) { const normalizedName = normalizeTitleMatchKey(name) if (normalizedName && sidebarIcons.byTitleName.has(normalizedName)) { return sidebarIcons.byTitleName.get(normalizedName) ?? null } } return null } export function findSteamAppId(...roots) { const seen = new Set() const queue = roots.map((value) => ({ value, depth: 0, steamContext: false })) while (queue.length > 0) { const current = queue.shift() const value = current?.value const depth = current?.depth ?? 0 const steamContext = current?.steamContext ?? false if (!value || depth > MAX_IMAGE_URL_SEARCH_DEPTH || seen.has(value)) { continue } if (typeof value === "string" || typeof value === "number") { const steamAppId = steamContext ? toStringId(value) : null if (steamAppId && STEAM_APP_ID_PATTERN.test(steamAppId)) { return steamAppId } continue } seen.add(value) if (Array.isArray(value)) { for (const entry of value) { queue.push({ value: entry, depth: depth + 1, steamContext }) } continue } if (!isRecord(value)) { continue } for (const [key, entry] of Object.entries(value)) { const steamAppId = getSteamAppIdFromEntry(key, entry, steamContext) if (steamAppId) { return steamAppId } if (isRecord(entry) || Array.isArray(entry)) { queue.push({ value: entry, depth: depth + 1, steamContext: steamContext || STEAM_CONTAINER_FIELD_PATTERN.test(key), }) } } } return null } export function getInstalledAppSteamAppId(platform, sku) { if (safeString(platform).toLowerCase() !== STEAM_PLATFORM) { return null } return sku } function normalizeTitleMatchKey(value) { const normalized = safeString(value).trim().toLowerCase().replace(/\s+/g, " ") return normalized || null } function getCssBackgroundImageUrl(value) { const backgroundImage = safeString(value) if (!backgroundImage || backgroundImage === "none") { return null } const match = backgroundImage.match(/url\((['"]?)(.*?)\1\)/i) if (!match?.[2]) { return null } return normalizeImageUrl(match[2]) } function getSteamAppIdFromEntry(key, entry, steamContext) { if (STEAM_APP_ID_FIELD_NAMES.has(key)) { const steamAppId = toStringId(entry) if (steamAppId && STEAM_APP_ID_PATTERN.test(steamAppId)) { return steamAppId } } if (steamContext && STEAM_CONTAINER_ID_FIELD_NAMES.has(key)) { const steamAppId = toStringId(entry) if (steamAppId && STEAM_APP_ID_PATTERN.test(steamAppId)) { return steamAppId } } return null }