diff --git a/docs/images/twitch-vod-manager-overview.png b/docs/images/twitch-vod-manager-overview.png index bc76a9f..bd377bd 100644 Binary files a/docs/images/twitch-vod-manager-overview.png and b/docs/images/twitch-vod-manager-overview.png differ diff --git a/scripts/capture-readme-screenshot.js b/scripts/capture-readme-screenshot.js index d9218cd..8805b67 100644 --- a/scripts/capture-readme-screenshot.js +++ b/scripts/capture-readme-screenshot.js @@ -9,6 +9,14 @@ const { cleanupE2eEnvironment } = require('./e2e-test-environment'); +async function fetchImageDataUrl(url) { + const response = await fetch(url); + if (!response.ok) throw new Error(`Image request failed with ${response.status}`); + const contentType = response.headers.get('content-type') || 'image/jpeg'; + const bytes = Buffer.from(await response.arrayBuffer()); + return `data:${contentType};base64,${bytes.toString('base64')}`; +} + async function run() { const environment = createE2eEnvironment('readme-screenshot', { language: 'en', @@ -19,71 +27,80 @@ async function run() { let app; try { + const publicImages = await Promise.all([ + 'https://static-cdn.jtvnw.net/jtv_user_pictures/xqc-profile_image-9298dca608632101-150x150.jpeg', + 'https://static-cdn.jtvnw.net/cf_vods/d1m7jfoe9zdc1j/73cded95867be2fe9ed5_xqc_320952179547_1786313143//thumb/thumb0-640x360.jpg', + 'https://static-cdn.jtvnw.net/cf_vods/d2vi6trrdongqn/a50a886595e03e3ef6ea_xqc_319796892764_1786132273//thumb/thumb0-640x360.jpg', + 'https://static-cdn.jtvnw.net/cf_vods/d2vi6trrdongqn/94030284cfbb93ede7b8_xqc_319780491228_1786042905//thumb/thumb0-640x360.jpg', + 'https://static-cdn.jtvnw.net/cf_vods/d1m7jfoe9zdc1j/f7d01c3a73dacdb116b3_xqc_320763288922_1785952851//thumb/thumb0-640x360.jpg', + 'https://static-cdn.jtvnw.net/cf_vods/d1m7jfoe9zdc1j/d0086ce0b50cded348bb_xqc_320745551322_1785869163//thumb/thumb0-640x360.jpg', + 'https://static-cdn.jtvnw.net/cf_vods/d2vi6trrdongqn/94d9a9b26ecc5891d8a0_xqc_319661776222_1785780125//thumb/thumb0-640x360.jpg' + ].map(fetchImageDataUrl)); app = await electron.launch(getElectronLaunchOptions(environment)); const win = await app.firstWindow(); await verifyE2eIsolation(app, win, environment); await installOfflineFixtures(app); - await app.evaluate(({ ipcMain }) => { + await app.evaluate(({ ipcMain }, images) => { const image = (label, from, to) => { const svg = `${label}`; return `data:image/svg+xml;base64,${Buffer.from(svg).toString('base64')}`; }; const vods = [ - ['Ranked highlights and community games', '5h13m20s', 155331, '#6441a5', '#1f7ae0'], - ['Late night challenge run', '7h39m0s', 241394, '#172554', '#7c3aed'], - ['Tournament watch party', '5h59m10s', 183557, '#0f766e', '#2563eb'], - ['Creative stream and Q&A', '3h40m3s', 94821, '#7c2d12', '#db2777'], - ['Weekend co-op session', '4h50m0s', 127644, '#1e3a8a', '#0891b2'], - ['Best moments from the week', '2h54m23s', 119663, '#581c87', '#be123c'] - ].map(([title, duration, viewCount, from, to], index) => ({ - id: `demo-vod-${index + 1}`, + ['🍝LIVE🍝DRAMA🍝NEWS🍝VIDEOS🍝GAMES🍝REACTS🍝VIDEOGAMES🍝FUN🍝COOL🍝THINGS🍝IMPORTANT🍝BROADCAST🍝SESSION🍝WICKED🍝', '2026-08-09T22:05:48Z', '9h3m44s', 627553], + ['🦴LIVE🦴DRAMA🦴NEWS🦴VIDEOS🦴IMPORTANT🦴COBBLEMON TOURNAMENT🦴WINNER🦴POV🦴WICKED🦴LETS GO YAY🦴IM HERE🦴', '2026-08-07T19:51:18Z', '5h26m23s', 464926], + ['🤒LIVE🤒DRAMA🤒NEWS🤒COBBLEMON🤒IS LIVE🤒DAY4 IMPORTANT🤒LOCK IN🤒QUICKLY🤒BIG THINGS TODAY🤒DONT SLEEP IN🤒', '2026-08-06T19:01:50Z', '11h49m41s', 878537], + ['👈LIVE👈CLICK👈HERE👈DRAMA👈NEWS👈VIDEOS👈GAMES👈COBBLEMON DAY3👈ALSO👈LONG WALK GAME👈FINISHSING?👈HOPEFULY👈LOCK IN👈', '2026-08-05T18:00:55Z', '12h11m17s', 877336], + ['👨‍🎨LIVE👨‍🎨CLICK👨‍🎨DRAMA👨‍🎨NEWS👨‍🎨GAMES👨‍🎨VIDEOS👨‍🎨IM LATE👨‍🎨BUT COBBLEMON IS BACK👨‍🎨LOCK IN👨‍🎨WOOO👨‍🎨PULL UP👨‍🎨', '2026-08-04T18:46:08Z', '12h17m10s', 868187], + ['🔮LIVE🔮CLICK🔮DRAMA🔮NEWS🔮VIDEOS🔮CLIPS🔮GAMES🔮VIDEOGAMES🔮THINGS🔮IMPORTANT🔮DAY🔮COBBLEMON SERVER🔮LETS GET IT🔮YIPEE🔮TEAM ROCKET POV', '2026-08-03T18:02:10Z', '10h59m24s', 950330] + ].map(([title, createdAt, duration, viewCount], index) => ({ + id: ['2841886176', '2839940123', '2839002231', '2838068542', '2837216764', '2836308921'][index], title, - created_at: `2026-08-${String(9 - index).padStart(2, '0')}T18:00:00Z`, + created_at: createdAt, duration, - thumbnail_url: image(`DEMO VOD ${index + 1}`, from, to), - url: `https://www.twitch.tv/videos/demo-${index + 1}`, + thumbnail_url: images[index + 1] || image(`xQc VOD ${index + 1}`, '#6441a5', '#1f7ae0'), + url: `https://www.twitch.tv/videos/${['2841886176', '2839940123', '2839002231', '2838068542', '2837216764', '2836308921'][index]}`, view_count: viewCount, - stream_id: `demo-stream-${index + 1}` + stream_id: '' })); ipcMain.removeHandler('get-user-id'); - ipcMain.handle('get-user-id', async () => 'demo-user'); + ipcMain.handle('get-user-id', async () => '71092938'); ipcMain.removeHandler('get-vods'); ipcMain.handle('get-vods', async () => vods); ipcMain.removeHandler('get-streamer-profile'); ipcMain.handle('get-streamer-profile', async () => ({ - login: 'demo_channel', - displayName: 'DemoChannel', - avatarUrl: image('TVM', '#7c3aed', '#0ea5e9'), + login: 'xqc', + displayName: 'xQc', + avatarUrl: images[0] || image('xQc', '#7c3aed', '#0ea5e9'), bannerUrl: '', - description: 'Example profile for the public product screenshot', + description: 'THE BEST AT ABSOLUTELY EVERYTHING. THE JUICER. LEADER OF THE JUICERS.', broadcasterType: 'partner', - followerCount: 1250000, + followerCount: 12534104, vodCount: vods.length, - lastStreamAt: '2026-08-09T18:00:00Z', + lastStreamAt: '2026-08-09T22:05:48Z', isLive: false, currentTitle: null, currentGame: null, currentStreamPreviewUrl: '', currentStreamViewers: null, - twitchUrl: 'https://www.twitch.tv/demo_channel', + twitchUrl: 'https://www.twitch.tv/xqc', fetchedAt: Date.now() })); ipcMain.removeHandler('get-streamer-display-names'); - ipcMain.handle('get-streamer-display-names', async () => ({ demo_channel: 'DemoChannel' })); - }); + ipcMain.handle('get-streamer-display-names', async () => ({ xqc: 'xQc' })); + }, publicImages); await win.waitForFunction(() => typeof window.showTab === 'function'); await win.setViewportSize({ width: 1600, height: 900 }); await win.evaluate(async () => { window.changeLanguage('en'); showTab('vods'); isConnected = true; - config.streamers = ['demo_channel']; - config.streamer_display_names = { demo_channel: 'DemoChannel' }; + config.streamers = ['xqc']; + config.streamer_display_names = { xqc: 'xQc' }; config.sidebar_split_view = true; streamerVodCache.clear(); streamerProfileCache.clear(); await window.hydrateStreamerDisplayNames(); - await selectStreamer('demo_channel'); + await selectStreamer('xqc'); }); await win.waitForTimeout(700); fs.mkdirSync(path.dirname(outputPath), { recursive: true });