From 93d53773c5a9665322b628f2a221559be1b9b197 Mon Sep 17 00:00:00 2001 From: syeopite Date: Thu, 26 Dec 2024 22:24:10 -0800 Subject: [PATCH] Add Invidious's download endpoint --- src/routes/index.ts | 3 ++ src/routes/invidious_routes/download.ts | 64 +++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 src/routes/invidious_routes/download.ts diff --git a/src/routes/index.ts b/src/routes/index.ts index 5ad9258..f4ec8c1 100644 --- a/src/routes/index.ts +++ b/src/routes/index.ts @@ -6,6 +6,7 @@ import { bearerAuth } from "hono/bearer-auth"; import youtubeApiPlayer from "./youtube_api_routes/player.ts"; import invidiousRouteLatestVersion from "./invidious_routes/latestVersion.ts"; import invidiousRouteDashManifest from "./invidious_routes/dashManifest.ts"; +import {getDownloadHandler} from "./invidious_routes/download.ts"; import videoPlaybackProxy from "./videoPlaybackProxy.ts"; import health from "./health.ts"; @@ -21,6 +22,8 @@ export const routes = (app: Hono, konfigStore: Store>) = app.route("/youtubei/v1", youtubeApiPlayer); app.route("/latest_version", invidiousRouteLatestVersion); + // Needs app for app.request in order to call /latest_version endpoint + app.post("/download", getDownloadHandler(app)); app.route("/api/manifest/dash/id", invidiousRouteDashManifest); app.route("/videoplayback", videoPlaybackProxy); app.route("/healthz", health); diff --git a/src/routes/invidious_routes/download.ts b/src/routes/invidious_routes/download.ts new file mode 100644 index 0000000..50a86db --- /dev/null +++ b/src/routes/invidious_routes/download.ts @@ -0,0 +1,64 @@ +import { HTTPException } from "hono/http-exception"; +import { Store } from "@willsoto/node-konfig-core"; +import { + verifyRequest +} from "../../lib/helpers/verifyRequest.ts"; + +export function getDownloadHandler(app) { + async function handler(c) { + const body = await c.req.formData(); + + const videoId = body.get("id"); + + const konfigStore = await c.get("konfigStore") as Store< + Record + >; + + const check = c.req.query("check"); + + if (konfigStore.get("server.verify_requests") && check == undefined) { + throw new HTTPException(400, { + res: new Response("No check ID."), + }); + } else if (konfigStore.get("server.verify_requests") && check) { + if (verifyRequest(check, videoId, konfigStore) === false) { + throw new HTTPException(400, { + res: new Response("ID incorrect."), + }); + } + } + + const title = body.get("title"); + + let downloadWidgetData : { itag: number; ext: string; label: string}; + + try { + downloadWidgetData = JSON.parse(body.get("download_widget")); + } catch (error) { + throw new HTTPException(400, {res: new Response("Invalid download_widget json"), }); + } + + if (!(title || videoId || (downloadWidgetData.itag && downloadWidgetData.ext))) { + throw new HTTPException(400, {res: new Response("Invalid form data"), }); + } + const itag = Number(downloadWidgetData.itag); + const {ext, label} = downloadWidgetData; + + const filename = `${title}-${videoId}.${ext || ''}`; + + if (label) { + } else if (itag) { + const urlQueriesForLatestVersion = new URLSearchParams(); + urlQueriesForLatestVersion.set("id", videoId); + urlQueriesForLatestVersion.set("itag", itag.toString()); + urlQueriesForLatestVersion.set("title", filename); + urlQueriesForLatestVersion.set("local", "true"); + + return await app.request(`/latest_version?${urlQueriesForLatestVersion.toString()}`); + } else { + throw new HTTPException(400, {res: new Response("Invalid label or itag"), }); + } + } + + return handler +}