From f20f87bd1df335bdbf014e469e04a1cd73cce397 Mon Sep 17 00:00:00 2001 From: wukko Date: Mon, 29 Apr 2024 21:36:35 +0600 Subject: [PATCH] rutube: add support for shorts and yappy (#471) * rutube: add support for shorts and yappy * tests: add rutube yappy and shorts tests Closes #465 Closes #466 --- src/modules/processing/match.js | 1 + src/modules/processing/services/rutube.js | 44 ++++++++++++++++--- src/modules/processing/servicesConfig.json | 2 +- .../processing/servicesPatternTesters.js | 2 +- src/test/tests.json | 16 +++++++ 5 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/modules/processing/match.js b/src/modules/processing/match.js index 6f715ef7..d46c30d9 100644 --- a/src/modules/processing/match.js +++ b/src/modules/processing/match.js @@ -151,6 +151,7 @@ export default async function(host, patternMatch, url, lang, obj) { case "rutube": r = await rutube({ id: patternMatch.id, + yappyId: patternMatch.yappyId, quality: obj.vQuality, isAudioOnly: isAudioOnly }); diff --git a/src/modules/processing/services/rutube.js b/src/modules/processing/services/rutube.js index af99a31d..b26bfb19 100644 --- a/src/modules/processing/services/rutube.js +++ b/src/modules/processing/services/rutube.js @@ -1,18 +1,47 @@ import HLS from 'hls-parser'; + import { maxVideoDuration } from "../../config.js"; import { cleanString } from '../../sub/utils.js'; +async function requestJSON(url) { + try { + const r = await fetch(url); + return await r.json(); + } catch {} +} + export default async function(obj) { + if (obj.yappyId) { + let yappy = await requestJSON( + `https://rutube.ru/pangolin/api/web/yappy/yappypage/?client=wdp&videoId=${obj.yappyId}&page=1&page_size=15` + ) + let yappyURL = yappy?.results?.find(r => r.id === obj.yappyId)?.link; + if (!yappyURL) return { error: 'ErrorEmptyDownload' }; + + return { + urls: yappyURL, + filename: `rutube_yappy_${obj.yappyId}.mp4`, + audioFilename: `rutube_yappy_${obj.yappyId}_audio` + } + } + let quality = obj.quality === "max" ? "9000" : obj.quality; - let play = await fetch(`https://rutube.ru/api/play/options/${obj.id}/?no_404=true&referer&pver=v2`).then((r) => { return r.json() }).catch(() => { return false }); + + let play = await requestJSON( + `https://rutube.ru/api/play/options/${obj.id}/?no_404=true&referer&pver=v2` + ) if (!play) return { error: 'ErrorCouldntFetch' }; - if ("hls" in play.live_streams) return { error: 'ErrorLiveVideo' }; - if (!play.video_balancer || play.detail) return { error: 'ErrorEmptyDownload' }; + if (play.detail || !play.video_balancer) return { error: 'ErrorEmptyDownload' }; + if (play.live_streams?.hls) return { error: 'ErrorLiveVideo' }; + + if (play.duration > maxVideoDuration) + return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] }; + + let m3u8 = await fetch(play.video_balancer.m3u8) + .then(r => r.text()) + .catch(() => {}); - if (play.duration > maxVideoDuration) return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] }; - - let m3u8 = await fetch(play.video_balancer.m3u8).then((r) => { return r.text() }).catch(() => { return false }); if (!m3u8) return { error: 'ErrorCouldntFetch' }; m3u8 = HLS.parse(m3u8).variants.sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth)); @@ -21,6 +50,7 @@ export default async function(obj) { if (Number(quality) < bestQuality.resolution.height) { bestQuality = m3u8.find((i) => (Number(quality) === i["resolution"].height)); } + let fileMetadata = { title: cleanString(play.title.trim()), artist: cleanString(play.author.name.trim()), @@ -31,7 +61,7 @@ export default async function(obj) { isM3U8: true, filenameAttributes: { service: "rutube", - id: play.id, + id: obj.id, title: fileMetadata.title, author: fileMetadata.artist, resolution: `${bestQuality.resolution.width}x${bestQuality.resolution.height}`, diff --git a/src/modules/processing/servicesConfig.json b/src/modules/processing/servicesConfig.json index 1a51d17a..8972ac46 100644 --- a/src/modules/processing/servicesConfig.json +++ b/src/modules/processing/servicesConfig.json @@ -110,7 +110,7 @@ "rutube": { "alias": "rutube videos", "tld": "ru", - "patterns": ["video/:id", "play/embed/:id"], + "patterns": ["video/:id", "play/embed/:id", "shorts/:id", "yappy/:yappyId"], "enabled": true }, "dailymotion": { diff --git a/src/modules/processing/servicesPatternTesters.js b/src/modules/processing/servicesPatternTesters.js index f4dee15b..bdd691d5 100644 --- a/src/modules/processing/servicesPatternTesters.js +++ b/src/modules/processing/servicesPatternTesters.js @@ -19,7 +19,7 @@ export const testers = { patternMatch.sub?.length <= 22 && patternMatch.id?.length <= 10, "rutube": (patternMatch) => - patternMatch.id?.length === 32, + patternMatch.id?.length === 32 || patternMatch.yappyId?.length === 32, "soundcloud": (patternMatch) => (patternMatch.author?.length <= 255 && patternMatch.song?.length <= 255) diff --git a/src/test/tests.json b/src/test/tests.json index bd8b33c5..9fccaa7e 100644 --- a/src/test/tests.json +++ b/src/test/tests.json @@ -1061,6 +1061,22 @@ "code": 200, "status": "stream" } + }, { + "name": "yappy", + "url": "https://rutube.ru/yappy/f1771e86294748eea8ecb2ac88e55740/", + "params": {}, + "expected": { + "code": 200, + "status": "stream" + } + }, { + "name": "shorts", + "url": "https://rutube.ru/shorts/935c1afafd0e7d52836d671967d53dac/", + "params": {}, + "expected": { + "code": 200, + "status": "stream" + } }, { "name": "vertical video (isAudioOnly)", "url": "https://rutube.ru/video/18a281399b96f9184c647455a86f6724/",