diff --git a/src/modules/processing/matchActionDecider.js b/src/modules/processing/matchActionDecider.js index f54bb3f5..389f2f30 100644 --- a/src/modules/processing/matchActionDecider.js +++ b/src/modules/processing/matchActionDecider.js @@ -85,6 +85,7 @@ export default function(r, host, userFormat, isAudioOnly, lang, isAudioMuted, di case "video": switch (host) { case "bilibili": + case "nicovideo": params = { type: "render" }; break; case "youtube": diff --git a/src/modules/processing/services/nicovideo.js b/src/modules/processing/services/nicovideo.js index 554912ac..d7179a98 100644 --- a/src/modules/processing/services/nicovideo.js +++ b/src/modules/processing/services/nicovideo.js @@ -2,9 +2,15 @@ import { genericUserAgent } from "../../config.js"; import HLS from "hls-parser"; import util from "node:util"; +const NICOVIDEO_EMBED_FRONTEND_HEADERS = { + "x-frontend-id": "70", + "x-frontend-version": "0", + "x-niconico-langauge": "ja-jp", + "x-request-with": "https://embed.nicovideo.jp", +}; + const NICOVIDEO_EMBED_URL = "https://embed.nicovideo.jp/watch/%s"; const NICOVIDEO_GUEST_API_URL = - // frontend is embed player "https://www.nicovideo.jp/api/watch/v3_guest/%s?_frontendId=70&_frontendVersion=0&actionTrackId=%s"; const NICOVIDEO_HLS_API_URL = "https://nvapi.nicovideo.jp/v1/watch/%s/access-rights/hls?actionTrackId=%s"; @@ -12,7 +18,6 @@ const NICOVIDEO_HLS_API_URL = const ACTION_TRACK_ID_REGEXP = /"actionTrackId":"[A-Za-z0-9]+_[0-9]+"/; -// working async function getActionTrackId(id) { const page = await fetch(util.format(NICOVIDEO_EMBED_URL, id), { headers: { "user-agent": genericUserAgent }, @@ -35,7 +40,6 @@ async function getActionTrackId(id) { return actionTrackId; } -// not tested async function fetchGuestData(id, actionTrackId) { const data = await fetch( util.format(NICOVIDEO_GUEST_API_URL, id, actionTrackId), @@ -45,7 +49,6 @@ async function fetchGuestData(id, actionTrackId) { ).then((response) => response.json()); if (data?.meta?.status !== 200) { - console.debug("fetchGuestData():", data) throw new Error(); } @@ -65,7 +68,6 @@ async function fetchGuestData(id, actionTrackId) { }; } -// not tested async function fetchContentURL(id, actionTrackId, accessRightKey, outputs) { const data = await fetch( util.format(NICOVIDEO_HLS_API_URL, id, actionTrackId), @@ -75,6 +77,7 @@ async function fetchContentURL(id, actionTrackId, accessRightKey, outputs) { "user-agent": genericUserAgent, "content-type": "application/json; charset=utf-8", "x-access-right-key": accessRightKey, + ...NICOVIDEO_EMBED_FRONTEND_HEADERS, }, body: JSON.stringify({ outputs }), } @@ -84,43 +87,37 @@ async function fetchContentURL(id, actionTrackId, accessRightKey, outputs) { throw new Error(); } - return data.data.contentURL; + return data.data.contentUrl; } -// not tested async function getHighestQualityHLS(contentURL) { const hls = await fetch(contentURL) .then((response) => response.text()) .then((response) => HLS.parse(response)); const highestQualityHLS = hls.variants - .sort((firstVariant, secondVariant) => { - const firstVariantPixels = - firstVariant.resolution.width * firstVariant.resolution.height; - const secondVariantPixels = - secondVariant.resolution.width * secondVariant.resolution.height; - - return firstVariantPixels - secondVariantPixels; - }) + .sort( + (firstVariant, secondVariant) => + firstVariant.bandwidth - secondVariant.bandwidth + ) .pop(); - return highestQualityHLS; + return [highestQualityHLS.uri, highestQualityHLS.audio.pop().uri]; } export default async function nicovideo({ id }) { try { const actionTrackId = await getActionTrackId(id); - const highestQualityHLS = await fetchGuestData(actionTrackId) + const [video, audio] = await fetchGuestData(id, actionTrackId) .then(({ accessRightKey, outputs }) => fetchContentURL(id, actionTrackId, accessRightKey, outputs) ) .then((contentURL) => getHighestQualityHLS(contentURL)); return { - urls: highestQualityHLS, - isM3U8: true, + urls: [video, audio], // TODO @synzr get video information from embed page props - filenameAttributes: { service: "niconico", id }, + filenameAttributes: { service: "nicovideo", id, extension: "mp4" }, }; } catch (error) { return { error: "ErrorEmptyDownload" }; diff --git a/src/modules/stream/manage.js b/src/modules/stream/manage.js index 6c9e9d3c..4702dbda 100644 --- a/src/modules/stream/manage.js +++ b/src/modules/stream/manage.js @@ -9,7 +9,7 @@ import { strict as assert } from "assert"; // optional dependency const freebind = env.freebindCIDR && await import('freebind').catch(() => {}); -const M3U_SERVICES = ['dailymotion', 'vimeo', 'rutube']; +const M3U_SERVICES = ['dailymotion', 'vimeo', 'rutube', 'nicovideo']; const streamCache = new NodeCache({ stdTTL: env.streamLifespan,