inital release
This commit is contained in:
commit
1d708c14cf
26 changed files with 34335 additions and 0 deletions
132
routes/avatar.js
Normal file
132
routes/avatar.js
Normal file
|
@ -0,0 +1,132 @@
|
|||
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;
|
Loading…
Add table
Add a link
Reference in a new issue