Compare commits
No commits in common. "f564567897f60885048cb11e3e06020eafe8afdf" and "ef6b82bb8b06d7e4fb74d80b7ad7e89b2acadc3d" have entirely different histories.
f564567897
...
ef6b82bb8b
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.6.18",
|
"version": "4.6.17",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.6.18",
|
"version": "4.6.17",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.0",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "twitch-vod-manager",
|
"name": "twitch-vod-manager",
|
||||||
"version": "4.6.18",
|
"version": "4.6.17",
|
||||||
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
"description": "Twitch VOD Manager - Download Twitch VODs easily",
|
||||||
"main": "dist/main.js",
|
"main": "dist/main.js",
|
||||||
"author": "xRangerDE",
|
"author": "xRangerDE",
|
||||||
|
|||||||
45
src/main.ts
45
src/main.ts
@ -2319,44 +2319,6 @@ const MAX_STREAMER_PROFILE_CACHE_ENTRIES = 512;
|
|||||||
const streamerProfileCache = new Map<string, CacheEntry<StreamerProfile>>();
|
const streamerProfileCache = new Map<string, CacheEntry<StreamerProfile>>();
|
||||||
const inFlightProfileRequests = new Map<string, Promise<StreamerProfile | null>>();
|
const inFlightProfileRequests = new Map<string, Promise<StreamerProfile | null>>();
|
||||||
|
|
||||||
// Avatar bytes get embedded as data URLs in the profile so the renderer
|
|
||||||
// doesn't have to do its own HTTPS fetch (Electron's renderer img loader
|
|
||||||
// has a habit of failing silently against the Twitch CDN — undocumented,
|
|
||||||
// but reproducibly: the same URL works in DevTools but not in the live
|
|
||||||
// page). Cached by source URL so a single avatar change across multiple
|
|
||||||
// streamer entries only downloads once.
|
|
||||||
const avatarDataUrlCache = new Map<string, string>();
|
|
||||||
const MAX_AVATAR_DATA_URL_CACHE = 256;
|
|
||||||
|
|
||||||
async function fetchAvatarAsDataUrl(url: string): Promise<string> {
|
|
||||||
if (!url) return '';
|
|
||||||
const cached = avatarDataUrlCache.get(url);
|
|
||||||
if (cached !== undefined) return cached;
|
|
||||||
try {
|
|
||||||
const response = await axios.get<ArrayBuffer>(url, {
|
|
||||||
responseType: 'arraybuffer',
|
|
||||||
timeout: 8000,
|
|
||||||
headers: { 'User-Agent': 'TwitchVODManager/1.0' }
|
|
||||||
});
|
|
||||||
const buf = Buffer.from(response.data);
|
|
||||||
// Twitch CDN almost always serves PNG or JPEG. Detect from the
|
|
||||||
// response content-type when available, fall back to PNG which is
|
|
||||||
// the default for profile_image_url.
|
|
||||||
const contentType = (response.headers['content-type'] as string | undefined)?.split(';')[0]?.trim() || 'image/png';
|
|
||||||
const dataUrl = `data:${contentType};base64,${buf.toString('base64')}`;
|
|
||||||
avatarDataUrlCache.set(url, dataUrl);
|
|
||||||
if (avatarDataUrlCache.size > MAX_AVATAR_DATA_URL_CACHE) {
|
|
||||||
// FIFO eviction — Map preserves insertion order.
|
|
||||||
const firstKey = avatarDataUrlCache.keys().next().value as string | undefined;
|
|
||||||
if (firstKey) avatarDataUrlCache.delete(firstKey);
|
|
||||||
}
|
|
||||||
return dataUrl;
|
|
||||||
} catch (e) {
|
|
||||||
appendDebugLog('avatar-fetch-failed', { url, error: String(e) });
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface HelixUser {
|
interface HelixUser {
|
||||||
id: string;
|
id: string;
|
||||||
login: string;
|
login: string;
|
||||||
@ -2515,15 +2477,10 @@ async function getStreamerProfile(login: string, forceRefresh = false): Promise<
|
|||||||
}
|
}
|
||||||
} catch (_) { /* best-effort */ }
|
} catch (_) { /* best-effort */ }
|
||||||
|
|
||||||
// Embed the avatar bytes as a data URL so the renderer doesn't
|
|
||||||
// have to make its own HTTPS request. Bypasses any CSP, CORS,
|
|
||||||
// referrer-policy, or Electron renderer image-loading quirks.
|
|
||||||
const avatarDataUrl = avatarUrl ? await fetchAvatarAsDataUrl(avatarUrl) : '';
|
|
||||||
|
|
||||||
const profile: StreamerProfile = {
|
const profile: StreamerProfile = {
|
||||||
login: normalized,
|
login: normalized,
|
||||||
displayName,
|
displayName,
|
||||||
avatarUrl: avatarDataUrl || avatarUrl,
|
avatarUrl,
|
||||||
description,
|
description,
|
||||||
broadcasterType,
|
broadcasterType,
|
||||||
followerCount,
|
followerCount,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user