blastoise/routes/fetch.ts

136 lines
4.9 KiB
TypeScript

import { config, DEFAULT_CONFIG } from "../config";
import {
isAvailable as isYtdlpAvailable,
checkUrl,
addToFastQueue,
addToSlowQueue,
getQueues,
cancelSlowQueueItem,
} from "../ytdlp";
import { getOrCreateUser } from "./helpers";
import { createPlaylist, generateUniquePlaylistName } from "../db";
const ytdlpConfig = config.ytdlp || DEFAULT_CONFIG.ytdlp!;
// POST /api/fetch - fetch from URL (yt-dlp)
export async function handleFetch(req: Request, server: any): Promise<Response> {
const { user, headers } = getOrCreateUser(req, server);
if (!user) {
return Response.json({ error: "Authentication required" }, { status: 401 });
}
if (user.is_guest) {
return Response.json({ error: "Guests cannot fetch from URLs" }, { status: 403 });
}
if (!ytdlpConfig.enabled) {
return Response.json({ error: "Feature disabled" }, { status: 403 });
}
if (!isYtdlpAvailable()) {
return Response.json({ error: "yt-dlp not available" }, { status: 503 });
}
try {
const { url } = await req.json();
if (!url || typeof url !== "string") {
return Response.json({ error: "URL is required" }, { status: 400 });
}
console.log(`[Fetch] ${user.username} checking URL: ${url}`);
const info = await checkUrl(url);
if (info.type === "playlist") {
if (!ytdlpConfig.allowPlaylists) {
return Response.json({ error: "Playlist downloads are disabled" }, { status: 403 });
}
return Response.json(info, { headers });
} else {
const item = addToFastQueue(info.url, info.title, user.id);
console.log(`[Fetch] ${user.username} queued: ${info.title} (id=${item.id})`);
return Response.json({
type: "single",
id: item.id,
title: item.title,
queueType: "fast"
}, { headers });
}
} catch (e: any) {
console.error("[Fetch] Error:", e);
return Response.json({ error: e.message || "Invalid request" }, { status: 400 });
}
}
// POST /api/fetch/confirm - confirm playlist download
export async function handleFetchConfirm(req: Request, server: any): Promise<Response> {
const { user, headers } = getOrCreateUser(req, server);
if (!user) {
return Response.json({ error: "Authentication required" }, { status: 401 });
}
if (user.is_guest) {
return Response.json({ error: "Guests cannot fetch from URLs" }, { status: 403 });
}
if (!ytdlpConfig.enabled || !isYtdlpAvailable()) {
return Response.json({ error: "Feature not available" }, { status: 503 });
}
try {
const { items, playlistTitle } = await req.json();
if (!Array.isArray(items) || items.length === 0) {
return Response.json({ error: "Items required" }, { status: 400 });
}
// Auto-create playlist with unique name
const baseName = playlistTitle || "Imported Playlist";
const uniqueName = generateUniquePlaylistName(baseName, user.id);
const playlist = createPlaylist(uniqueName, user.id, `Imported from URL (${items.length} tracks)`);
console.log(`[Fetch] ${user.username} created playlist: ${uniqueName} (id=${playlist.id})`);
const queueItems = addToSlowQueue(items, user.id, { id: playlist.id, name: uniqueName });
const estimatedMinutes = Math.ceil(queueItems.length * ytdlpConfig.slowQueueInterval / 60);
const hours = Math.floor(estimatedMinutes / 60);
const mins = estimatedMinutes % 60;
const estimatedTime = hours > 0 ? `${hours}h ${mins}m` : `${mins}m`;
console.log(`[Fetch] ${user.username} confirmed playlist: ${queueItems.length} items`);
return Response.json({
message: `Added ${queueItems.length} items to queue`,
queueType: "slow",
estimatedTime,
playlistId: playlist.id,
playlistName: uniqueName,
items: queueItems.map(i => ({ id: i.id, title: i.title }))
}, { headers });
} catch (e) {
console.error("[Fetch] Confirm error:", e);
return Response.json({ error: "Invalid request" }, { status: 400 });
}
}
// GET /api/fetch - get fetch queue status (all users see all tasks)
export function handleGetFetchQueue(req: Request, server: any): Response {
const { user, headers } = getOrCreateUser(req, server);
if (!user) {
return Response.json({ error: "Authentication required" }, { status: 401 });
}
const queues = getQueues(); // Return all queues, not filtered by user
return Response.json(queues, { headers });
}
// DELETE /api/fetch/:id - cancel a slow queue item
export function handleCancelFetchItem(req: Request, server: any, itemId: string): Response {
const { user, headers } = getOrCreateUser(req, server);
if (!user) {
return Response.json({ error: "Authentication required" }, { status: 401 });
}
const success = cancelSlowQueueItem(itemId, user.id);
if (success) {
return Response.json({ message: "Item cancelled" }, { headers });
} else {
return Response.json({ error: "Cannot cancel item (not found, not owned, or already downloading)" }, { status: 400, headers });
}
}