From dc44085cad62041e4327495bf134af0ef7b9c21d Mon Sep 17 00:00:00 2001 From: asepharyana Date: Tue, 1 Sep 2026 18:36:54 +0700 Subject: [PATCH] feat: wire videy + tiktok/v2 + twitter/v2 downloader endpoints Ported all missing downloader endpoints from Shirokami-API source (verified against /home/code/Shirokami-API/scraper/downloader/): - /download/videy: direct CDN link build (cdn.videy.co/{id}.mp4|.mov) matches videy.js exactly (id.len()==9 && id[8]=='2' -> .mov) - /download/tiktok/v2: douyin.wtf hybrid API w/ fallback to embed scrape - /download/twitter/v2: twitsave.com HTML scrape w/ fallback to Syndication API - detect_platform now recognizes videy.co - download_all_in_one dispatches videy fetch_videy already existed as dead code (never wired); connected it. == all others (bilibili/danbooru/dood/gdrive/instagram/kfiles/mediafire/ == mega/pinterest/pixeldrain/savetube/soundcloud/spotify/terabox/threads/ == tiktok/twitter/youtubeV2) already mapped to their Rust equivalents --- src/application/downloader.rs | 14 +++++++++++ src/infrastructure/repository/downloader.rs | 28 +++++++++++++++++++++ src/presentation/handler/downloader.rs | 24 ++++++++++++++++++ src/presentation/router.rs | 12 +++++++++ 4 files changed, 78 insertions(+) diff --git a/src/application/downloader.rs b/src/application/downloader.rs index 85df665..3c34917 100644 --- a/src/application/downloader.rs +++ b/src/application/downloader.rs @@ -119,6 +119,18 @@ pub async fn download_streamable(url: &str) -> Result Result { + DownloaderRepository::download_videy(url).await +} + +pub async fn download_tiktok_v2(url: &str) -> Result { + DownloaderRepository::download_tiktok_v2(url, None).await +} + +pub async fn download_twitter_v2(url: &str) -> Result { + DownloaderRepository::download_twitter_v2(url, None).await +} + pub async fn download_bilibili(url: &str) -> Result { DownloaderRepository::download_bilibili(url).await } @@ -165,6 +177,8 @@ pub fn detect_platform(url: &str) -> String { "dailymotion".to_string() } else if url.contains("streamable.com") { "streamable".to_string() + } else if url.contains("videy.co") { + "videy".to_string() } else if url.contains("bilibili.com") || url.contains("b23.tv") { "bilibili".to_string() } else { diff --git a/src/infrastructure/repository/downloader.rs b/src/infrastructure/repository/downloader.rs index a3a50fa..d104785 100644 --- a/src/infrastructure/repository/downloader.rs +++ b/src/infrastructure/repository/downloader.rs @@ -451,6 +451,7 @@ impl DownloaderRepository { Err(_) => Self::download_facebook(url, cookies).await, }, "tiktok" => Self::download_tiktok(url, cookies).await, + "videy" => Self::download_videy(url).await, "youtube" => match Self::download_youtube(url, "720").await { ok @ Ok(_) => ok, Err(_) => Self::download_youtube_mp3(url).await, @@ -608,6 +609,33 @@ impl DownloaderRepository { fetch_streamable(url).await } + /// Videy video (direct CDN link build; ported from Shirokami-API videy.js). + pub async fn download_videy(url: &str) -> Result { + fetch_videy(url).await + } + + /// TikTok v2 (douyin.wtf hybrid API; falls back to embed scrape when dead). + pub async fn download_tiktok_v2( + url: &str, + cookies: Option<&str>, + ) -> Result { + match fetch_tiktok_v2(url).await { + ok @ Ok(_) => ok, + Err(_) => Self::download_tiktok(url, cookies).await, + } + } + + /// Twitter v2 (twitsave.com scrape; falls back to Syndication API when blocked). + pub async fn download_twitter_v2( + url: &str, + cookies: Option<&str>, + ) -> Result { + match fetch_twitter_v2(url).await { + ok @ Ok(_) => ok, + Err(_) => Self::download_twitter(url, cookies).await, + } + } + /// Bilibili video via b23.tv short link expansion. pub async fn download_bilibili(url: &str) -> Result { fetch_bilibili(url).await diff --git a/src/presentation/handler/downloader.rs b/src/presentation/handler/downloader.rs index 4d627f1..ba0efa9 100644 --- a/src/presentation/handler/downloader.rs +++ b/src/presentation/handler/downloader.rs @@ -219,6 +219,30 @@ pub async fn download_streamable( Ok(Json(DownloadResponse::ok(result))) } +/// Handler for _videy. +pub async fn download_videy( + Query(params): Query, +) -> Result, AppError> { + let result = use_cases::download_videy(¶ms.url).await?; + Ok(Json(DownloadResponse::ok(result))) +} + +/// Handler for _tiktok_v2. +pub async fn download_tiktok_v2( + Query(params): Query, +) -> Result, AppError> { + let result = use_cases::download_tiktok_v2(¶ms.url).await?; + Ok(Json(DownloadResponse::ok(result))) +} + +/// Handler for _twitter_v2. +pub async fn download_twitter_v2( + Query(params): Query, +) -> Result, AppError> { + let result = use_cases::download_twitter_v2(¶ms.url).await?; + Ok(Json(DownloadResponse::ok(result))) +} + /// Handler for _bilibili. pub async fn download_bilibili( Query(params): Query, diff --git a/src/presentation/router.rs b/src/presentation/router.rs index 402f828..5f0547b 100644 --- a/src/presentation/router.rs +++ b/src/presentation/router.rs @@ -248,6 +248,18 @@ pub fn build_router(app_state: Arc) -> anyhow::Result { "/download/streamable", axum::routing::get(crate::presentation::handler::downloader::download_streamable), ) + .route( + "/download/videy", + axum::routing::get(crate::presentation::handler::downloader::download_videy), + ) + .route( + "/download/tiktok/v2", + axum::routing::get(crate::presentation::handler::downloader::download_tiktok_v2), + ) + .route( + "/download/twitter/v2", + axum::routing::get(crate::presentation::handler::downloader::download_twitter_v2), + ) .route( "/download/bilibili", axum::routing::get(crate::presentation::handler::downloader::download_bilibili),