const axios = require('axios'); const cheerio = require('cheerio'); const avatarCache = new Map(); const pendingFetches = new Map(); const CACHE_TTL = 24 * 60 * 60 * 1000; async function fetchAvatarImage(channelId) { try { const channelUrl = `https://www.youtube.com/${channelId.startsWith('UC') ? 'channel/' : ''}${channelId}`; console.log("loading" + channelUrl); const htmlResponse = await axios.get(channelUrl, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } }); const $ = cheerio.load(htmlResponse.data); const imageUrl = $('meta[property="og:image"]').attr('content'); if (!imageUrl) { throw new Error('Profile picture URL not found in page'); } const imageResponse = await axios.get(imageUrl, { responseType: 'arraybuffer', headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' } }); const buffer = Buffer.from(imageResponse.data); const contentType = imageResponse.headers['content-type'] || 'image/jpeg'; avatarCache.set(channelId, { buffer, contentType, timestamp: Date.now() }); return { buffer, contentType }; } catch (error) { throw new Error(`Failed to fetch avatar: ${error.message}`); } } function avatarRouteHandler(app) { app.get("/api/avatar/:channelId.png", async (req, res) => { try { let channelId = req.params.channelId; if (!channelId) { return res.status(400).json({ error: "Invalid channel ID" }); } if (!channelId.startsWith('UC') && !channelId.startsWith('@')) { channelId = `@${channelId}`; } const cached = avatarCache.get(channelId); if (cached && (Date.now() - cached.timestamp) < CACHE_TTL) { console.log(`Cache hit for ${channelId}`); res.set('Content-Type', cached.contentType); return res.send(cached.buffer); } if (pendingFetches.has(channelId)) { console.log(`Awaiting pending fetch for ${channelId}`); const { buffer, contentType } = await pendingFetches.get(channelId); res.set('Content-Type', contentType); return res.send(buffer); } console.log(`Fetching avatar for ${channelId}`); const fetchPromise = fetchAvatarImage(channelId); pendingFetches.set(channelId, fetchPromise); try { const { buffer, contentType } = await fetchPromise; res.set('Content-Type', contentType); res.send(buffer); } finally { pendingFetches.delete(channelId); } } catch (err) { console.error(`Error fetching avatar: ${err.message} ${err.stack}`); res.status(500).json({ error: "Error fetching avatar" }); } }); app.get("/api/avatar/channel/:channelId.png", async (req, res) => { try { let channelId = req.params.channelId; if (!channelId && !channelId.startsWith('UC')) { return res.status(400).json({ error: "Invalid channel ID" }); } const cached = avatarCache.get(channelId); if (cached && (Date.now() - cached.timestamp) < CACHE_TTL) { console.log(`Cache hit for ${channelId}`); res.set('Content-Type', cached.contentType); return res.send(cached.buffer); } if (pendingFetches.has(channelId)) { console.log(`Awaiting pending fetch for ${channelId}`); const { buffer, contentType } = await pendingFetches.get(channelId); res.set('Content-Type', contentType); return res.send(buffer); } console.log(`Fetching avatar for ${channelId}`); const fetchPromise = fetchAvatarImage(channelId); pendingFetches.set(channelId, fetchPromise); try { const { buffer, contentType } = await fetchPromise; res.set('Content-Type', contentType); res.send(buffer); } finally { pendingFetches.delete(channelId); } } catch (err) { console.error(`Error fetching avatar: ${err.message} ${err.stack}`); res.status(500).json({ error: "Error fetching avatar" }); } }); } module.exports = avatarRouteHandler;