avoid channel fetching problem when uploading

This commit is contained in:
localhost 2026-07-23 22:22:33 +02:00
parent f938346008
commit da0c9c23f5
3 changed files with 44 additions and 15 deletions

View File

@ -7,7 +7,7 @@ import { validateVideo, validateChannel } from '@/utils/regex'
import { checkCaptcha, createDatabaseVideo } from '@/utils/common'; import { checkCaptcha, createDatabaseVideo } from '@/utils/common';
import { downloadVideo } from '@/utils/download'; import { downloadVideo } from '@/utils/download';
import { uploadVideo } from '@/utils/upload'; import { uploadVideo } from '@/utils/upload';
import { getChannelVideos, getVideo } from '@/utils/metadata'; import { getChannel, getChannelVideos, getVideo } from '@/utils/metadata';
import { error } from '@/utils/html' import { error } from '@/utils/html'
import redis from '@/utils/redis'; import redis from '@/utils/redis';
import { parseSlop } from '@/utils/slop'; import { parseSlop } from '@/utils/slop';
@ -68,7 +68,7 @@ const cleanup = async (ws: any, videoId: string) => {
await redis.del(ws.id); await redis.del(ws.id);
}; };
const handleUpload = async (ws: any, videoId: string, isChannel: boolean = false) => { const handleUpload = async (ws: any, videoId: string, metadata: { data: any, channelData: any }, isChannel: boolean = false) => {
// the pattern of files that have finished downloading is [videoid].mp4, but some extensions are also possible due to // the pattern of files that have finished downloading is [videoid].mp4, but some extensions are also possible due to
// current youtube changes, so we need to make sure the other extensions are also covered // current youtube changes, so we need to make sure the other extensions are also covered
let filePath = fs.readdirSync('./videos/').find(f => f.includes(`${videoId}.`)) let filePath = fs.readdirSync('./videos/').find(f => f.includes(`${videoId}.`))
@ -84,7 +84,7 @@ const handleUpload = async (ws: any, videoId: string, isChannel: boolean = false
const videoUrl = await uploadVideo(filePath); const videoUrl = await uploadVideo(filePath);
fs.unlinkSync(filePath); fs.unlinkSync(filePath);
const uploaded = await createDatabaseVideo(videoId, videoUrl); const uploaded = await createDatabaseVideo(videoId, videoUrl, metadata);
if (uploaded !== 'success') { if (uploaded !== 'success') {
ws.send(`DATA - Error while uploading - ${JSON.stringify(uploaded)}`); ws.send(`DATA - Error while uploading - ${JSON.stringify(uploaded)}`);
return false; return false;
@ -185,6 +185,10 @@ app.ws('/save', {
if (data.error) { if (data.error) {
return sendError(ws, 'Unable to retrieve video info from YouTube. Please try again later.') return sendError(ws, 'Unable to retrieve video info from YouTube. Please try again later.')
} }
const channelData = await getChannel(data.videoDetails.channelId)
if (channelData.error) {
return sendError(ws, 'Unable to retrieve video info from YouTube. Please try again later.')
}
const isSlop = await parseSlop(videoId, data.videoDetails.title, const isSlop = await parseSlop(videoId, data.videoDetails.title,
(data.microformat.playerMicroformatRenderer.description?.simpleText || '').replaceAll('\n', '<br>'), (data.microformat.playerMicroformatRenderer.description?.simpleText || '').replaceAll('\n', '<br>'),
@ -217,7 +221,7 @@ app.ws('/save', {
} }
} }
const uploadSuccess = await handleUpload(ws, videoId); const uploadSuccess = await handleUpload(ws, videoId, { data, channelData });
if (!uploadSuccess) await redis.del(saveKey(videoId)); if (!uploadSuccess) await redis.del(saveKey(videoId));
await cleanup(ws, videoId); await cleanup(ws, videoId);
@ -273,8 +277,8 @@ app.ws('/savechannel', {
} }
videoIds[ws.id] = `downloading-${channelId}`; videoIds[ws.id] = `downloading-${channelId}`;
const videos = await getChannelVideos(channelId); const [videos, channelData] = await Promise.all([getChannelVideos(channelId), getChannel(channelId)]);
if (!Array.isArray(videos)) { if (!Array.isArray(videos) || channelData.error) {
await cleanup(ws, channelId); await cleanup(ws, channelId);
return sendError(ws, 'Unable to retrieve channel videos from YouTube. Please try again later.'); return sendError(ws, 'Unable to retrieve channel videos from YouTube. Please try again later.');
} }
@ -299,6 +303,11 @@ app.ws('/savechannel', {
} }
console.log(`saving (${subjects.map(subject => Bun.hash(subject).toString()).join(',')}) - ${ws.data.path} - ${video.videoId}`) console.log(`saving (${subjects.map(subject => Bun.hash(subject).toString()).join(',')}) - ${ws.data.path} - ${video.videoId}`)
const data = await getVideo(video.videoId)
if (data.error) {
ws.send(`DATA - Unable to retrieve video info for ${video.videoId}. Skipping.`)
continue
}
const isSlop = await parseSlop(video.videoId, video.title, const isSlop = await parseSlop(video.videoId, video.title,
video.description || video.description_snippet?.text || '', channelId) video.description || video.description_snippet?.text || '', channelId)
@ -323,11 +332,12 @@ app.ws('/savechannel', {
sendError(ws, limitStatus.isNewVisitorLimited ? NEW_VISITOR_STORAGE_LIMIT_MESSAGE : DEFAULT_STORAGE_LIMIT_MESSAGE, false); sendError(ws, limitStatus.isNewVisitorLimited ? NEW_VISITOR_STORAGE_LIMIT_MESSAGE : DEFAULT_STORAGE_LIMIT_MESSAGE, false);
break; break;
} }
await handleUpload(ws, video.videoId, true); const uploadSuccess = await handleUpload(ws, video.videoId, { data, channelData }, true);
if (uploadSuccess) ws.send(`DATA - Created video page for ${video.title}`)
} }
await redis.del(saveKey(video.videoId)); await redis.del(saveKey(video.videoId));
ws.send(`DATA - Created video page for ${video.title}`) if (downloadResult.fail) continue
} }
await cleanup(ws, channelId); await cleanup(ws, channelId);

View File

@ -1,4 +1,3 @@
import { getVideo, getChannel } from "@/utils/metadata";
import { uploadImage } from "@/utils/upload"; import { uploadImage } from "@/utils/upload";
import { db } from '@/utils/database' import { db } from '@/utils/database'
import crypto from 'node:crypto'; import crypto from 'node:crypto';
@ -52,9 +51,8 @@ async function checkCaptcha(response: string, remoteIp: string): Promise<any> {
return confirm return confirm
} }
async function createDatabaseVideo(id: string, videoUrl: string) { async function createDatabaseVideo(id: string, videoUrl: string, metadata: { data: any, channelData: any }) {
const data = await getVideo(id) const { data, channelData } = metadata
const channelData = await getChannel(data.videoDetails.channelId)
if (data.error) return data if (data.error) return data
if (channelData.error) return channelData if (channelData.error) return channelData

View File

@ -1,16 +1,37 @@
// metadata either returns innertube or { error: string } // metadata either returns innertube or { error: string }
import { getMetadataBackend } from '@/utils/health'; import { getMetadataBackend } from '@/utils/health';
import { sleep } from 'bun';
async function getMetadata(path: string, retries = 3, delay = 1000) {
let lastError = { error: 'ErrorUnknown' }
for (let attempt = 1; attempt <= retries; attempt++) {
try {
const response = await fetch(`${getMetadataBackend()}${path}`)
const json = await response.json()
if (response.ok && json.error !== 'ErrorUnknown' && json.error !== 'ErrorCantConnectToServiceAPI') return json
if (json.error) lastError = json
} catch {
lastError = { error: 'ErrorUnknown' }
}
if (attempt < retries) await sleep(delay * attempt)
}
return lastError
}
async function getVideo(id: string) { async function getVideo(id: string) {
return await (await fetch(`${getMetadataBackend()}/video/${id}`)).json() return await getMetadata(`/video/${id}`)
} }
async function getChannel(id: string) { async function getChannel(id: string) {
return await (await fetch(`${getMetadataBackend()}/channel/${id}`)).json() return await getMetadata(`/channel/${id}`)
} }
async function getChannelVideos(id: string) { async function getChannelVideos(id: string) {
return await (await fetch(`${getMetadataBackend()}/videos/${id}`)).json() return await getMetadata(`/videos/${id}`)
} }
export { getVideo, getChannel, getChannelVideos } export { getVideo, getChannel, getChannelVideos }