mirror of
https://github.com/imputnet/cobalt.git
synced 2025-07-10 07:18:30 +00:00
api: add initial draft support for nicovideo
This commit is contained in:
parent
40da8a46d6
commit
a7f6ea5d6f
@ -29,6 +29,7 @@ import loom from "./services/loom.js";
|
|||||||
import facebook from "./services/facebook.js";
|
import facebook from "./services/facebook.js";
|
||||||
import bluesky from "./services/bluesky.js";
|
import bluesky from "./services/bluesky.js";
|
||||||
import xiaohongshu from "./services/xiaohongshu.js";
|
import xiaohongshu from "./services/xiaohongshu.js";
|
||||||
|
import nicovideo from "./services/nicovideo.js";
|
||||||
|
|
||||||
let freebind;
|
let freebind;
|
||||||
|
|
||||||
@ -268,6 +269,14 @@ export default async function({ host, patternMatch, params, authType }) {
|
|||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "nicovideo":
|
||||||
|
r = await nicovideo({
|
||||||
|
...patternMatch,
|
||||||
|
dispatcher,
|
||||||
|
quality: params.videoQuality,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return createResponse("error", {
|
return createResponse("error", {
|
||||||
code: "error.api.service.unsupported"
|
code: "error.api.service.unsupported"
|
||||||
|
@ -60,6 +60,11 @@ export const services = {
|
|||||||
loom: {
|
loom: {
|
||||||
patterns: ["share/:id", "embed/:id"],
|
patterns: ["share/:id", "embed/:id"],
|
||||||
},
|
},
|
||||||
|
nicovideo: {
|
||||||
|
patterns: ["watch/:id"],
|
||||||
|
tld: "jp",
|
||||||
|
subdomains: ["sp"],
|
||||||
|
},
|
||||||
ok: {
|
ok: {
|
||||||
patterns: [
|
patterns: [
|
||||||
"video/:id",
|
"video/:id",
|
||||||
|
@ -79,4 +79,6 @@ export const testers = {
|
|||||||
"xiaohongshu": pattern =>
|
"xiaohongshu": pattern =>
|
||||||
pattern.id?.length <= 24 && pattern.token?.length <= 64
|
pattern.id?.length <= 24 && pattern.token?.length <= 64
|
||||||
|| pattern.shareId?.length <= 24,
|
|| pattern.shareId?.length <= 24,
|
||||||
|
|
||||||
|
"nicovideo": pattern => pattern.id?.length <= 12,
|
||||||
}
|
}
|
||||||
|
120
api/src/processing/services/nicovideo.js
Normal file
120
api/src/processing/services/nicovideo.js
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { genericUserAgent } from "../../config.js";
|
||||||
|
|
||||||
|
const genericHeaders = {
|
||||||
|
"user-agent": genericUserAgent,
|
||||||
|
"accept-language": "en-US,en;q=0.9",
|
||||||
|
}
|
||||||
|
|
||||||
|
const getVideoInfo = async (id, dispatcher, quality) => {
|
||||||
|
const html = await fetch(`https://www.nicovideo.jp/watch/${id}`, {
|
||||||
|
dispatcher,
|
||||||
|
headers: genericHeaders
|
||||||
|
}).then(r => r.text()).catch(() => {});
|
||||||
|
|
||||||
|
if (!(html.includes("accessRightKey")
|
||||||
|
|| !(html.includes('<meta name="server-response" content="')))) {
|
||||||
|
return { error: "fetch.fail" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawContent =
|
||||||
|
html.split('<meta name="server-response" content="')[1]
|
||||||
|
.split('" />')[0]
|
||||||
|
.replaceAll(""", '"');
|
||||||
|
|
||||||
|
const data = JSON.parse(rawContent)?.data?.response;
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
return { error: "fetch.fail" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const audio = data.media?.domand?.audios.find(audio => audio.isAvailable);
|
||||||
|
const bestVideo = data.media?.domand?.videos.find(video => video.isAvailable);
|
||||||
|
|
||||||
|
const preferredVideo = data.media?.domand?.videos.find(video =>
|
||||||
|
video.isAvailable && video.label.split('p')[0] === quality
|
||||||
|
);
|
||||||
|
|
||||||
|
const video = preferredVideo || bestVideo;
|
||||||
|
|
||||||
|
return {
|
||||||
|
watchTrackId: data.client?.watchTrackId,
|
||||||
|
accessRightKey: data.media?.domand?.accessRightKey,
|
||||||
|
|
||||||
|
video,
|
||||||
|
|
||||||
|
outputs: [[video.id, audio.id]],
|
||||||
|
|
||||||
|
author: data.owner?.nickname,
|
||||||
|
title: data.video?.title,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getHls = async (dispatcher, id, trackId, accessRightKey, outputs) => {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://nvapi.nicovideo.jp/v1/watch/${id}/access-rights/hls?actionTrackId=${trackId}`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
dispatcher,
|
||||||
|
headers: {
|
||||||
|
...genericHeaders,
|
||||||
|
"content-type": "application/json",
|
||||||
|
"x-access-right-key": accessRightKey,
|
||||||
|
"x-frontend-id": "6",
|
||||||
|
"x-frontend-version": "0",
|
||||||
|
"x-request-with": "nicovideo",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
outputs,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
).then(r => r.json()).catch(() => {});
|
||||||
|
|
||||||
|
if (!response?.data?.contentUrl) return;
|
||||||
|
return response.data.contentUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function ({ id, dispatcher, quality }) {
|
||||||
|
const {
|
||||||
|
watchTrackId,
|
||||||
|
accessRightKey,
|
||||||
|
video,
|
||||||
|
outputs,
|
||||||
|
author,
|
||||||
|
title,
|
||||||
|
error
|
||||||
|
} = await getVideoInfo(id, dispatcher, quality);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!watchTrackId || !accessRightKey || !outputs) {
|
||||||
|
return { error: "fetch.empty" };
|
||||||
|
}
|
||||||
|
|
||||||
|
const hlsUrl = await getHls(
|
||||||
|
dispatcher,
|
||||||
|
id,
|
||||||
|
watchTrackId,
|
||||||
|
accessRightKey,
|
||||||
|
outputs
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hlsUrl) {
|
||||||
|
return { error: "fetch.empty" };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
urls: hlsUrl,
|
||||||
|
isHLS: true,
|
||||||
|
filenameAttributes: {
|
||||||
|
service: "nicovideo",
|
||||||
|
id,
|
||||||
|
title,
|
||||||
|
author,
|
||||||
|
resolution: `${video.width}x${video.height}`,
|
||||||
|
qualityLabel: `${video.label}`,
|
||||||
|
extension: "mp4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user