diff --git a/src/types/innerTubeApi.model.ts b/src/types/innerTubeApi.model.ts new file mode 100644 index 0000000..3fad700 --- /dev/null +++ b/src/types/innerTubeApi.model.ts @@ -0,0 +1,23 @@ +export interface innerTubeVideoDetails { + "videoId": string, + "title": string, + "lengthSeconds": string, // yes, don't ask. + "channelId": string, + "isOwnerViewing": boolean, + "shortDescription": string, + "isCrawlable": boolean, + "thumbnail": { + "thumbnails": [{ + "url": string, + "width": number, + "height": number + } + ] + }, + "allowRatings": boolean, + "viewCount": string, // yes, don't ask + "author": string, + "isPrivate": boolean, + "isUnpluggedCorpus": boolean, + "isLiveContent": boolean +} \ No newline at end of file diff --git a/src/utils/innerTubeAPI.ts b/src/utils/innerTubeAPI.ts new file mode 100644 index 0000000..4ff5797 --- /dev/null +++ b/src/utils/innerTubeAPI.ts @@ -0,0 +1,29 @@ +import axios from "axios"; +import { innerTubeVideoDetails } from "../types/innerTubeApi.model"; + +export async function getPlayerData(videoID: string): Promise { + // start subrequest + const url = "https://www.youtube.com/youtubei/v1/player"; + const data = { + context: { + client: { + clientName: "WEB", + clientVersion: "2.20211129.09.00" + } + }, + videoId: videoID + }; + const result = await axios.post(url, data, { + timeout: 3500 + }); + if (result.status === 200) { + return result.data.videoDetails; + } else { + return Promise.reject(result.status); + } +} + +export const getLength = (videoID: string): Promise => + getPlayerData(videoID) + .then(pData => Number(pData.lengthSeconds)) + .catch(err => err); \ No newline at end of file diff --git a/test/cases/innerTubeApi.ts b/test/cases/innerTubeApi.ts new file mode 100644 index 0000000..6ef3eb1 --- /dev/null +++ b/test/cases/innerTubeApi.ts @@ -0,0 +1,51 @@ +import { config } from "../../src/config"; +import assert from "assert"; +// import { innerTubeVideoDetails } from "../../src/types/innerTubeApi.model"; +import { YouTubeAPI } from "../../src/utils/youtubeApi"; +import * as innerTube from "../../src/utils/innerTubeAPI"; +import { partialDeepEquals } from "../utils/partialDeepEquals"; + + +const videoID = "dQw4w9WgXcQ"; +const expected = { // partial type of innerTubeVideoDetails + videoId: videoID, + title: "Rick Astley - Never Gonna Give You Up (Official Music Video)", + lengthSeconds: "212", + channelId: "UCuAXFkgsw1L7xaCfnd5JJOw", + isOwnerViewing: false, + isCrawlable: true, + allowRatings: true, + author: "Rick Astley", + isPrivate: false, + isUnpluggedCorpus: false, + isLiveContent: false +}; +const currentViews = 1284257550; + +describe("innertube API test", function() { + it("should be able to get innerTube details", async () => { + const result = await innerTube.getPlayerData(videoID); + assert.ok(partialDeepEquals(result, expected)); + }); + it("Should have more views than current", async () => { + const result = await innerTube.getPlayerData(videoID); + assert.ok(Number(result.viewCount) >= currentViews); + }); + it("Should have the same video duration from both endpoints", async () => { + const playerData = await innerTube.getPlayerData(videoID); + const length = await innerTube.getLength(videoID); + assert.equal(Number(playerData.lengthSeconds), length); + }); + it("Should have equivalent response from NewLeaf", async function () { + if (!config.newLeafURLs || config.newLeafURLs.length <= 0 || config.newLeafURLs[0] == "placeholder") this.skip(); + const itResponse = await innerTube.getPlayerData(videoID); + const newLeafResponse = await YouTubeAPI.listVideos(videoID, true); + // validate videoID + assert.strictEqual(itResponse.videoId, videoID); + assert.strictEqual(newLeafResponse.data?.videoId, videoID); + // validate description + assert.strictEqual(itResponse.shortDescription, newLeafResponse.data?.description); + // validate authorId + assert.strictEqual(itResponse.channelId, newLeafResponse.data?.authorId); + }); +}); \ No newline at end of file