fix(youtube): YouTube API fetch에 타임아웃 추가
업스트림 행 시 봇 동기화/요청 핸들러가 무한 대기하던 문제 방지. fetchWithTimeout(AbortController, 10s)으로 7개 fetch 호출 일괄 래핑. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
6e559a52b7
commit
b0a2e69711
1 changed files with 21 additions and 7 deletions
|
|
@ -3,6 +3,20 @@ import { formatDate, formatTime } from '../../utils/date.js';
|
||||||
|
|
||||||
const API_KEY = config.google.apiKey;
|
const API_KEY = config.google.apiKey;
|
||||||
const API_BASE = 'https://www.googleapis.com/youtube/v3';
|
const API_BASE = 'https://www.googleapis.com/youtube/v3';
|
||||||
|
const FETCH_TIMEOUT = 10000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 타임아웃이 있는 fetch (업스트림 행으로 봇/요청이 무한 대기하는 것 방지)
|
||||||
|
*/
|
||||||
|
async function fetchWithTimeout(url, options = {}) {
|
||||||
|
const controller = new AbortController();
|
||||||
|
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT);
|
||||||
|
try {
|
||||||
|
return await fetch(url, { ...options, signal: controller.signal });
|
||||||
|
} finally {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ISO 8601 duration (PT1M30S) → 초 변환
|
* ISO 8601 duration (PT1M30S) → 초 변환
|
||||||
|
|
@ -31,7 +45,7 @@ function getVideoUrl(videoId, isShorts) {
|
||||||
*/
|
*/
|
||||||
export async function getUploadsPlaylistId(channelId) {
|
export async function getUploadsPlaylistId(channelId) {
|
||||||
const url = `${API_BASE}/channels?part=contentDetails&id=${channelId}&key=${API_KEY}`;
|
const url = `${API_BASE}/channels?part=contentDetails&id=${channelId}&key=${API_KEY}`;
|
||||||
const res = await fetch(url);
|
const res = await fetchWithTimeout(url);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
|
|
@ -52,7 +66,7 @@ export async function getChannelByHandle(handle) {
|
||||||
// @ 제거
|
// @ 제거
|
||||||
const cleanHandle = handle.startsWith('@') ? handle.slice(1) : handle;
|
const cleanHandle = handle.startsWith('@') ? handle.slice(1) : handle;
|
||||||
const url = `${API_BASE}/channels?part=snippet,brandingSettings&forHandle=${cleanHandle}&key=${API_KEY}`;
|
const url = `${API_BASE}/channels?part=snippet,brandingSettings&forHandle=${cleanHandle}&key=${API_KEY}`;
|
||||||
const res = await fetch(url);
|
const res = await fetchWithTimeout(url);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
|
|
@ -84,7 +98,7 @@ export async function getChannelByHandle(handle) {
|
||||||
*/
|
*/
|
||||||
export async function getChannelInfo(channelId) {
|
export async function getChannelInfo(channelId) {
|
||||||
const url = `${API_BASE}/channels?part=snippet,brandingSettings&id=${channelId}&key=${API_KEY}`;
|
const url = `${API_BASE}/channels?part=snippet,brandingSettings&id=${channelId}&key=${API_KEY}`;
|
||||||
const res = await fetch(url);
|
const res = await fetchWithTimeout(url);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
|
|
@ -115,7 +129,7 @@ export async function getChannelInfo(channelId) {
|
||||||
*/
|
*/
|
||||||
async function getVideoDurations(videoIds) {
|
async function getVideoDurations(videoIds) {
|
||||||
const url = `${API_BASE}/videos?part=contentDetails&id=${videoIds.join(',')}&key=${API_KEY}`;
|
const url = `${API_BASE}/videos?part=contentDetails&id=${videoIds.join(',')}&key=${API_KEY}`;
|
||||||
const res = await fetch(url);
|
const res = await fetchWithTimeout(url);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
const durations = {};
|
const durations = {};
|
||||||
|
|
@ -136,7 +150,7 @@ async function getVideoDurations(videoIds) {
|
||||||
export async function fetchRecentVideoIds(channelId, maxResults = 10) {
|
export async function fetchRecentVideoIds(channelId, maxResults = 10) {
|
||||||
const fetchCount = Math.min(maxResults * 2, 50);
|
const fetchCount = Math.min(maxResults * 2, 50);
|
||||||
const url = `${API_BASE}/activities?part=snippet,contentDetails&channelId=${channelId}&type=upload&maxResults=${fetchCount}&key=${API_KEY}`;
|
const url = `${API_BASE}/activities?part=snippet,contentDetails&channelId=${channelId}&type=upload&maxResults=${fetchCount}&key=${API_KEY}`;
|
||||||
const res = await fetch(url);
|
const res = await fetchWithTimeout(url);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
|
|
@ -161,7 +175,7 @@ export async function fetchAllVideos(channelId, uploadsPlaylistId = null) {
|
||||||
|
|
||||||
do {
|
do {
|
||||||
const url = `${API_BASE}/playlistItems?part=snippet&playlistId=${uploadsId}&maxResults=50&key=${API_KEY}${pageToken ? `&pageToken=${pageToken}` : ''}`;
|
const url = `${API_BASE}/playlistItems?part=snippet&playlistId=${uploadsId}&maxResults=50&key=${API_KEY}${pageToken ? `&pageToken=${pageToken}` : ''}`;
|
||||||
const res = await fetch(url);
|
const res = await fetchWithTimeout(url);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
|
|
@ -204,7 +218,7 @@ export async function fetchAllVideos(channelId, uploadsPlaylistId = null) {
|
||||||
*/
|
*/
|
||||||
export async function fetchVideoInfo(videoId) {
|
export async function fetchVideoInfo(videoId) {
|
||||||
const url = `${API_BASE}/videos?part=snippet,contentDetails&id=${videoId}&key=${API_KEY}`;
|
const url = `${API_BASE}/videos?part=snippet,contentDetails&id=${videoId}&key=${API_KEY}`;
|
||||||
const res = await fetch(url);
|
const res = await fetchWithTimeout(url);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (!data.items?.length) {
|
if (!data.items?.length) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue