diff --git a/api/src/processing/match-action.js b/api/src/processing/match-action.js index 5852b19d..e4c29397 100644 --- a/api/src/processing/match-action.js +++ b/api/src/processing/match-action.js @@ -191,6 +191,7 @@ export default function({ case "streamable": case "snapchat": case "twitch": + case "ntv": responseType = "redirect"; break; } diff --git a/api/src/processing/match.js b/api/src/processing/match.js index 1265297c..e252e09c 100644 --- a/api/src/processing/match.js +++ b/api/src/processing/match.js @@ -30,6 +30,7 @@ import facebook from "./services/facebook.js"; import bluesky from "./services/bluesky.js"; import xiaohongshu from "./services/xiaohongshu.js"; import newgrounds from "./services/newgrounds.js"; +import ntv from "./services/ntv.js"; let freebind; @@ -231,6 +232,15 @@ export default async function({ host, patternMatch, params, authType }) { r = await dailymotion(patternMatch); break; + case "ntv": + r = await ntv({ + name: patternMatch.name ?? null, + showid: patternMatch.showid ?? null, + videoid: patternMatch.videoid, + quality: params.videoQuality + }); + break; + case "snapchat": r = await snapchat({ ...patternMatch, diff --git a/api/src/processing/service-config.js b/api/src/processing/service-config.js index 906c23da..af0744f1 100644 --- a/api/src/processing/service-config.js +++ b/api/src/processing/service-config.js @@ -1,6 +1,6 @@ import UrlPattern from "url-pattern"; -export const audioIgnore = new Set(["vk", "ok", "loom"]); +export const audioIgnore = new Set(["vk", "ok", "loom", "ntv"]); export const hlsExceptions = new Set(["dailymotion", "vimeo", "rutube", "bsky", "youtube"]); export const services = { @@ -219,6 +219,13 @@ export const services = { "v/:id" ], subdomains: ["music", "m"], + }, + ntv: { + patterns: [ + "peredacha/:name/m:showid/o:videoid", + "video/:videoid", + ], + tld: "ru" } } diff --git a/api/src/processing/service-patterns.js b/api/src/processing/service-patterns.js index e68a20be..e9eaee18 100644 --- a/api/src/processing/service-patterns.js +++ b/api/src/processing/service-patterns.js @@ -82,4 +82,8 @@ export const testers = { "newgrounds": pattern => pattern.id?.length <= 12 || pattern.audioId?.length <= 12, + + "ntv": pattern => + pattern.videoid.length <= 32 || + (pattern.name.length <= 64 && pattern.showid.length <= 32 && pattern.videoid.length <= 32), } diff --git a/api/src/processing/services/ntv.js b/api/src/processing/services/ntv.js new file mode 100644 index 00000000..3ec3bc88 --- /dev/null +++ b/api/src/processing/services/ntv.js @@ -0,0 +1,101 @@ +import { env } from "../../config.js"; + +const ntvApi = 'https://www.ntv.ru/api/player/?id='; + +// the most generic user agent +export const clientAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0"; + +const getVideo = async (videoID) => { + const video = await fetch(`${ntvApi}${videoID}`, { + method: "GET", + headers: { + "user-agent": clientAgent, + } + }) + .then(r => { + if (r.status === 200) { + return r.json(); + } + }); + + return video; +} + +export default async function (obj) { + let video = null; + + if (obj.name && obj.name.length > 0) { + const fetchID = await fetch(`https://www.ntv.ru/peredacha/${obj.name}/m${obj.showid}/o${obj.videoid}`, { + method: "GET", + headers: { + "user-agent": clientAgent, + } + }) + .then(r => { + if (r.status === 200) { + return r.text(); + } + }); + + let figuredVideoId = null; + const match = String(fetchID).match(/ env.durationLimit) { + return { error: "content.too_long" }; + } + + const userQuality = obj.quality === "max" ? "1080" : obj.quality; + let url = null; + + // for hls there's 144p, 360p, 720p and 1080p available, but for mp4 only 360p and 1080p + // we'll use mp4 to simplify things + switch (userQuality) { + case "1080": + url = video.playback.hd_video; + break; + case "720": + case "480": + case "360": + url = video.playback.video; + break; + default: + // 360 by default + url = video.playback.video; + } + + if (!url) return { error: "fetch.fail" }; + + const fileMetadata = { + title: video.description.trim(), + // despite the param name, it actually contains the video title with the name of the show + } + + return { + urls: url, + fileMetadata, + filenameAttributes: { + service: "ntv", + id: `${obj.videoid}`, + title: fileMetadata.title, + extension: "mp4" + } + } +} diff --git a/api/src/util/tests/ntv.json b/api/src/util/tests/ntv.json new file mode 100644 index 00000000..2f98f38a --- /dev/null +++ b/api/src/util/tests/ntv.json @@ -0,0 +1,20 @@ +[ + { + "name": "tv show video", + "url": "https://www.ntv.ru/peredacha/dacha_otvet/m18960/o806491", + "params": {}, + "expected": { + "code": 200, + "status": "redirect" + } + }, + { + "name": "newsfeed video", + "url": "https://www.ntv.ru/video/2483380", + "params": {}, + "expected": { + "code": 200, + "status": "redirect" + } + } +] \ No newline at end of file