youtubei 0.0.1-rc.3 → 0.0.1-rc.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/dist/classes/Base.d.ts +10 -0
- package/dist/classes/{client/types.js → Base.js} +3 -0
- package/dist/classes/BaseVideo.d.ts +59 -0
- package/dist/classes/BaseVideo.js +121 -0
- package/dist/classes/Channel.d.ts +27 -31
- package/dist/classes/Channel.js +59 -135
- package/dist/classes/ChannelCompact.d.ts +74 -23
- package/dist/classes/ChannelCompact.js +114 -103
- package/dist/classes/Chat.d.ts +29 -0
- package/dist/classes/Chat.js +31 -0
- package/dist/classes/Client.d.ts +49 -0
- package/dist/classes/Client.js +97 -0
- package/dist/classes/Comment.d.ts +50 -0
- package/dist/classes/Comment.js +84 -0
- package/dist/classes/LiveVideo.d.ts +47 -0
- package/dist/classes/LiveVideo.js +94 -0
- package/dist/classes/MixPlaylist.d.ts +32 -0
- package/dist/classes/MixPlaylist.js +44 -0
- package/dist/classes/Playlist.d.ts +35 -18
- package/dist/classes/Playlist.js +80 -116
- package/dist/classes/PlaylistCompact.d.ts +27 -15
- package/dist/classes/PlaylistCompact.js +42 -46
- package/dist/classes/Reply.d.ts +38 -0
- package/dist/classes/Reply.js +35 -0
- package/dist/classes/SearchResult.d.ts +52 -8
- package/dist/classes/SearchResult.js +101 -122
- package/dist/classes/Thumbnails.d.ts +42 -0
- package/dist/classes/Thumbnails.js +66 -0
- package/dist/classes/Video.d.ts +44 -37
- package/dist/classes/Video.js +74 -83
- package/dist/classes/VideoCompact.d.ts +38 -19
- package/dist/classes/VideoCompact.js +53 -53
- package/dist/classes/index.d.ts +10 -2
- package/dist/classes/index.js +20 -4
- package/dist/common/HTTP.d.ts +26 -0
- package/dist/common/HTTP.js +85 -0
- package/dist/common/decorators.js +6 -26
- package/dist/common/helper.d.ts +4 -0
- package/dist/common/helper.js +37 -18
- package/dist/common/index.d.ts +2 -1
- package/dist/common/index.js +5 -3
- package/dist/common/mixins.d.ts +2 -0
- package/dist/common/mixins.js +12 -0
- package/dist/common/types.d.ts +0 -5
- package/dist/constants.d.ts +5 -2
- package/dist/constants.js +6 -3
- package/package.json +23 -20
- package/.prettierrc +0 -8
- package/.vscode/settings.json +0 -4
- package/CHANGELOG.md +0 -6
- package/debug.log +0 -47
- package/dist/classes/BaseCompact.d.ts +0 -10
- package/dist/classes/BaseCompact.js +0 -27
- package/dist/classes/client/Client.d.ts +0 -23
- package/dist/classes/client/Client.js +0 -128
- package/dist/classes/client/index.d.ts +0 -2
- package/dist/classes/client/index.js +0 -19
- package/dist/classes/client/types.d.ts +0 -12
- package/dist/common/axios.d.ts +0 -4
- package/dist/common/axios.js +0 -44
- package/jest.config.js +0 -5
|
@@ -8,121 +8,132 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
8
8
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
13
|
-
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
-
function step(op) {
|
|
16
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
-
while (_) try {
|
|
18
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
-
switch (op[0]) {
|
|
21
|
-
case 0: case 1: t = op; break;
|
|
22
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
-
default:
|
|
26
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
-
if (t[2]) _.ops.pop();
|
|
31
|
-
_.trys.pop(); continue;
|
|
32
|
-
}
|
|
33
|
-
op = body.call(thisArg, _);
|
|
34
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
12
|
+
const common_1 = require("../common");
|
|
13
|
+
const _1 = require(".");
|
|
14
|
+
const constants_1 = require("../constants");
|
|
15
|
+
/** Represents a Youtube Channel */
|
|
16
|
+
class ChannelCompact extends _1.Base {
|
|
17
|
+
/** @hidden */
|
|
18
|
+
constructor(channel = {}) {
|
|
19
|
+
super();
|
|
20
|
+
/** Loaded videos on the channel, fetched from `channel.nextVideos()` */
|
|
21
|
+
this.videos = [];
|
|
22
|
+
/** Loaded playlists on the channel, fetched from `channel.nextPlaylists()` */
|
|
23
|
+
this.playlists = [];
|
|
24
|
+
/** Current continuation token to load next videos */
|
|
25
|
+
this.videoContinuation = null;
|
|
26
|
+
/** Current continuation token to load next playlists */
|
|
27
|
+
this.playlistContinuation = null;
|
|
48
28
|
Object.assign(this, channel);
|
|
49
29
|
}
|
|
30
|
+
/** The URL of the channel page */
|
|
31
|
+
get url() {
|
|
32
|
+
return `https://www.youtube.com/channel/${this.id}`;
|
|
33
|
+
}
|
|
50
34
|
/**
|
|
51
|
-
*
|
|
35
|
+
* Load this instance with raw data from Youtube
|
|
52
36
|
*
|
|
53
|
-
*
|
|
37
|
+
* @hidden
|
|
54
38
|
*/
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
return [2 /*return*/, response.data.contents.twoColumnBrowseResultsRenderer.tabs[1].tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer.contents[0].gridRenderer.items
|
|
67
|
-
.filter(function (i) { return i.gridVideoRenderer; })
|
|
68
|
-
.map(function (i) { return new _1.VideoCompact().load(i.gridVideoRenderer); })];
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
};
|
|
39
|
+
load(data) {
|
|
40
|
+
const { channelId, title, thumbnail, videoCountText, subscriberCountText } = data;
|
|
41
|
+
this.id = channelId;
|
|
42
|
+
this.name = title.simpleText;
|
|
43
|
+
this.thumbnails = new _1.Thumbnails().load(thumbnail.thumbnails);
|
|
44
|
+
this.videoCount = common_1.stripToInt(videoCountText === null || videoCountText === void 0 ? void 0 : videoCountText.runs[0].text) || 0;
|
|
45
|
+
this.subscriberCount = subscriberCountText === null || subscriberCountText === void 0 ? void 0 : subscriberCountText.simpleText;
|
|
46
|
+
this.videos = [];
|
|
47
|
+
this.playlists = [];
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
73
50
|
/**
|
|
74
|
-
*
|
|
51
|
+
* Load next 30 videos made by the channel, and push the loaded videos to {@link Channel.videos}
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```js
|
|
55
|
+
* const channel = await youtube.findOne(CHANNEL_NAME, {type: "channel"});
|
|
56
|
+
* await channel.nextVideos();
|
|
57
|
+
* console.log(channel.videos) // first 30 videos
|
|
58
|
+
*
|
|
59
|
+
* let newVideos = await channel.nextVideos();
|
|
60
|
+
* console.log(newVideos) // 30 loaded videos
|
|
61
|
+
* console.log(channel.videos) // first 60 videos
|
|
75
62
|
*
|
|
76
|
-
*
|
|
63
|
+
* await channel.nextVideos(0); // load the rest of the videos in the channel
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* @param count How many time to load the next videos, pass `0` to load all
|
|
67
|
+
*
|
|
68
|
+
* @return New loaded videos
|
|
77
69
|
*/
|
|
78
|
-
|
|
79
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// Has category
|
|
92
|
-
if ("shelfRenderer" in section.contents[0].itemSectionRenderer.contents[0]) {
|
|
93
|
-
gridPlaylistRenderer = section.contents
|
|
94
|
-
.map(function (c) {
|
|
95
|
-
return c.itemSectionRenderer.contents[0].shelfRenderer.content
|
|
96
|
-
.horizontalListRenderer.items;
|
|
97
|
-
})
|
|
98
|
-
.flat();
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
gridPlaylistRenderer =
|
|
102
|
-
section.contents[0].itemSectionRenderer.contents[0].gridRenderer.items;
|
|
103
|
-
}
|
|
104
|
-
return [2 /*return*/, gridPlaylistRenderer.map(function (i) {
|
|
105
|
-
return new _1.PlaylistCompact().load(i.gridPlaylistRenderer);
|
|
106
|
-
})];
|
|
107
|
-
}
|
|
108
|
-
});
|
|
70
|
+
nextVideos(count = 1) {
|
|
71
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
const newVideos = [];
|
|
73
|
+
for (let i = 0; i < count || count == 0; i++) {
|
|
74
|
+
if (this.videoContinuation === undefined)
|
|
75
|
+
break;
|
|
76
|
+
const items = yield this.getTabData("videos");
|
|
77
|
+
this.videoContinuation = common_1.getContinuationFromItems(items);
|
|
78
|
+
const videos = common_1.mapFilter(items, "gridVideoRenderer");
|
|
79
|
+
newVideos.push(...videos.map((i) => new _1.VideoCompact({ client: this.client }).load(i)));
|
|
80
|
+
}
|
|
81
|
+
this.videos.push(...newVideos);
|
|
82
|
+
return newVideos;
|
|
109
83
|
});
|
|
110
|
-
}
|
|
84
|
+
}
|
|
111
85
|
/**
|
|
112
|
-
* Load
|
|
86
|
+
* Load next 30 playlists made by the channel, and push the loaded playlists to {@link Channel.playlists}
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```js
|
|
90
|
+
* const channel = await youtube.findOne(CHANNEL_NAME, {type: "channel"});
|
|
91
|
+
* await channel.nextPlaylists();
|
|
92
|
+
* console.log(channel.playlists) // first 30 playlists
|
|
93
|
+
*
|
|
94
|
+
* let newPlaylists = await channel.nextPlaylists();
|
|
95
|
+
* console.log(newPlaylists) // 30 loaded playlists
|
|
96
|
+
* console.log(channel.playlists) // first 60 playlists
|
|
97
|
+
*
|
|
98
|
+
* await channel.nextPlaylists(0); // load the rest of the playlists in the channel
|
|
99
|
+
* ```
|
|
113
100
|
*
|
|
114
|
-
* @param
|
|
101
|
+
* @param count How many time to load the next playlists, pass `0` to load all
|
|
102
|
+
*
|
|
103
|
+
* @return New loaded playlists
|
|
115
104
|
*/
|
|
116
|
-
|
|
105
|
+
nextPlaylists(count = 1) {
|
|
106
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
107
|
+
const newPlaylists = [];
|
|
108
|
+
for (let i = 0; i < count || count == 0; i++) {
|
|
109
|
+
if (this.playlistContinuation === undefined)
|
|
110
|
+
break;
|
|
111
|
+
const items = yield this.getTabData("playlists");
|
|
112
|
+
this.playlistContinuation = common_1.getContinuationFromItems(items);
|
|
113
|
+
const playlists = common_1.mapFilter(items, "gridPlaylistRenderer");
|
|
114
|
+
newPlaylists.push(...playlists.map((i) => new _1.PlaylistCompact({ client: this.client }).load(i)));
|
|
115
|
+
}
|
|
116
|
+
this.playlists.push(...newPlaylists);
|
|
117
|
+
return newPlaylists;
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
/** Get tab data from youtube */
|
|
121
|
+
getTabData(name) {
|
|
122
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
123
|
+
const params = name === "videos" ? "EgZ2aWRlb3M%3D" : "EglwbGF5bGlzdHMgAQ%3D%3D";
|
|
124
|
+
const continuation = name === "videos" ? this.videoContinuation : this.playlistContinuation;
|
|
125
|
+
const response = yield this.client.http.post(`${constants_1.I_END_POINT}/browse`, {
|
|
126
|
+
data: { browseId: this.id, params, continuation },
|
|
127
|
+
});
|
|
128
|
+
return ChannelCompact.parseTabData(name, response.data);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
/** Parse tab data from request, tab name is ignored if it's a continuation data */
|
|
132
|
+
static parseTabData(name, data) {
|
|
117
133
|
var _a;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
this.videoCount = (_a = +(videoCountText === null || videoCountText === void 0 ? void 0 : videoCountText.runs[0].text.replace(/[^0-9]/g, ""))) !== null && _a !== void 0 ? _a : 0;
|
|
124
|
-
return this;
|
|
125
|
-
};
|
|
126
|
-
return ChannelCompact;
|
|
127
|
-
}());
|
|
134
|
+
const index = name === "videos" ? 1 : 2;
|
|
135
|
+
return (((_a = data.contents) === null || _a === void 0 ? void 0 : _a.twoColumnBrowseResultsRenderer.tabs[index].tabRenderer.content.sectionListRenderer.contents[0].itemSectionRenderer.contents[0].gridRenderer.items) ||
|
|
136
|
+
data.onResponseReceivedActions[0].appendContinuationItemsAction.continuationItems);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
128
139
|
exports.default = ChannelCompact;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Base, ChannelCompact, Video, BaseAttributes } from ".";
|
|
2
|
+
import { YoutubeRawData } from "../common";
|
|
3
|
+
/** @hidden */
|
|
4
|
+
interface ChatAttributes extends BaseAttributes {
|
|
5
|
+
video: Video;
|
|
6
|
+
author: ChannelCompact;
|
|
7
|
+
message: string;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
}
|
|
10
|
+
/** Represents a chat in a live stream */
|
|
11
|
+
export default class Chat extends Base implements ChatAttributes {
|
|
12
|
+
/** The video this chat belongs to */
|
|
13
|
+
video: Video;
|
|
14
|
+
/** The chat's author */
|
|
15
|
+
author: ChannelCompact;
|
|
16
|
+
/** The message of this chat */
|
|
17
|
+
message: string;
|
|
18
|
+
/** Timestamp in usec / microsecond */
|
|
19
|
+
timestamp: number;
|
|
20
|
+
/** @hidden */
|
|
21
|
+
constructor(chat?: Partial<ChatAttributes>);
|
|
22
|
+
/**
|
|
23
|
+
* Load this instance with raw data from Youtube
|
|
24
|
+
*
|
|
25
|
+
* @hidden
|
|
26
|
+
*/
|
|
27
|
+
load(data: YoutubeRawData): Chat;
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const _1 = require(".");
|
|
4
|
+
/** Represents a chat in a live stream */
|
|
5
|
+
class Chat extends _1.Base {
|
|
6
|
+
/** @hidden */
|
|
7
|
+
constructor(chat = {}) {
|
|
8
|
+
super();
|
|
9
|
+
Object.assign(this, chat);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Load this instance with raw data from Youtube
|
|
13
|
+
*
|
|
14
|
+
* @hidden
|
|
15
|
+
*/
|
|
16
|
+
load(data) {
|
|
17
|
+
const { id, message, authorName, authorPhoto, timestampUsec, authorExternalChannelId, } = data;
|
|
18
|
+
// Basic information
|
|
19
|
+
this.id = id;
|
|
20
|
+
this.message = message.runs.map((r) => r.text).join("");
|
|
21
|
+
this.author = new _1.ChannelCompact({
|
|
22
|
+
id: authorExternalChannelId,
|
|
23
|
+
name: authorName.simpleText,
|
|
24
|
+
thumbnails: authorPhoto.thumbnails,
|
|
25
|
+
client: this.client,
|
|
26
|
+
});
|
|
27
|
+
this.timestamp = +timestampUsec;
|
|
28
|
+
return this;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.default = Chat;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { HTTP } from "../common";
|
|
3
|
+
import { Playlist, Video, SearchResult, LiveVideo, Channel, MixPlaylist } from ".";
|
|
4
|
+
import { SearchResultType } from "./SearchResult";
|
|
5
|
+
import { RequestOptions } from "https";
|
|
6
|
+
export declare namespace Client {
|
|
7
|
+
type SearchType = "video" | "channel" | "playlist" | "all";
|
|
8
|
+
type SearchOptions = {
|
|
9
|
+
/** Search type, can be `"video"`, `"channel"`, `"playlist"`, or `"all"` */
|
|
10
|
+
type?: SearchType;
|
|
11
|
+
/** Raw search params to be passed on the request, ignores `type` value if this is provided */
|
|
12
|
+
params?: string;
|
|
13
|
+
};
|
|
14
|
+
type ClientOptions = {
|
|
15
|
+
cookie: string;
|
|
16
|
+
/** Optional options for http client */
|
|
17
|
+
requestOptions: Partial<RequestOptions>;
|
|
18
|
+
/** Optional options passed when sending a request to youtube (context.client) */
|
|
19
|
+
youtubeClientOptions: Record<string, unknown>;
|
|
20
|
+
/** Use Node `https` module, set false to use `http` */
|
|
21
|
+
https: boolean;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Youtube Client */
|
|
25
|
+
export default class Client {
|
|
26
|
+
/** @hidden */
|
|
27
|
+
http: HTTP;
|
|
28
|
+
constructor(options?: Partial<Client.ClientOptions>);
|
|
29
|
+
/**
|
|
30
|
+
* Searches for videos / playlists / channels
|
|
31
|
+
*
|
|
32
|
+
* @param query The search query
|
|
33
|
+
* @param searchOptions Search options
|
|
34
|
+
*
|
|
35
|
+
*/
|
|
36
|
+
search<T extends Client.SearchOptions>(query: string, searchOptions?: T): Promise<SearchResult<T["type"]>>;
|
|
37
|
+
/**
|
|
38
|
+
* Search for videos / playlists / channels and returns the first result
|
|
39
|
+
*
|
|
40
|
+
* @return Can be {@link VideoCompact} | {@link PlaylistCompact} | {@link Channel} | `undefined`
|
|
41
|
+
*/
|
|
42
|
+
findOne<T extends Client.SearchOptions>(query: string, searchOptions?: Partial<T>): Promise<SearchResultType<T["type"]> | undefined>;
|
|
43
|
+
/** Get playlist information and its videos by playlist id or URL */
|
|
44
|
+
getPlaylist<T extends Playlist | MixPlaylist | undefined>(playlistIdOrUrl: string): Promise<T>;
|
|
45
|
+
/** Get video information by video id or URL */
|
|
46
|
+
getVideo<T extends Video | LiveVideo | undefined>(videoIdOrUrl: string): Promise<T>;
|
|
47
|
+
/** Get channel information by channel id+ */
|
|
48
|
+
getChannel(channelId: string): Promise<Channel | undefined>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const constants_1 = require("../constants");
|
|
13
|
+
const common_1 = require("../common");
|
|
14
|
+
const _1 = require(".");
|
|
15
|
+
/** Youtube Client */
|
|
16
|
+
class Client {
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
const fullOptions = Object.assign(Object.assign({ cookie: "", https: true, requestOptions: {} }, options), { youtubeClientOptions: Object.assign({ hl: "en", gl: "US" }, options.youtubeClientOptions) });
|
|
19
|
+
this.http = new common_1.HTTP(fullOptions);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Searches for videos / playlists / channels
|
|
23
|
+
*
|
|
24
|
+
* @param query The search query
|
|
25
|
+
* @param searchOptions Search options
|
|
26
|
+
*
|
|
27
|
+
*/
|
|
28
|
+
search(query, searchOptions) {
|
|
29
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
const options = Object.assign({ type: "all", params: "" }, searchOptions);
|
|
31
|
+
const result = new _1.SearchResult().load(this);
|
|
32
|
+
yield result.init(query, options);
|
|
33
|
+
return result;
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Search for videos / playlists / channels and returns the first result
|
|
38
|
+
*
|
|
39
|
+
* @return Can be {@link VideoCompact} | {@link PlaylistCompact} | {@link Channel} | `undefined`
|
|
40
|
+
*/
|
|
41
|
+
findOne(query, searchOptions) {
|
|
42
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
return (yield this.search(query, searchOptions)).shift();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/** Get playlist information and its videos by playlist id or URL */
|
|
47
|
+
getPlaylist(playlistIdOrUrl) {
|
|
48
|
+
var _a, _b, _c;
|
|
49
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
50
|
+
const playlistId = common_1.getQueryParameter(playlistIdOrUrl, "list");
|
|
51
|
+
if (playlistId.startsWith("RD")) {
|
|
52
|
+
const response = yield this.http.post(`${constants_1.I_END_POINT}/next`, {
|
|
53
|
+
data: { playlistId },
|
|
54
|
+
});
|
|
55
|
+
if (response.data.error) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return new _1.MixPlaylist({ client: this }).load(response.data);
|
|
59
|
+
}
|
|
60
|
+
const response = yield this.http.post(`${constants_1.I_END_POINT}/browse`, {
|
|
61
|
+
data: { browseId: `VL${playlistId}` },
|
|
62
|
+
});
|
|
63
|
+
if (response.data.error || ((_c = (_b = (_a = response.data.alerts) === null || _a === void 0 ? void 0 : _a.shift()) === null || _b === void 0 ? void 0 : _b.alertRenderer) === null || _c === void 0 ? void 0 : _c.type) === "ERROR") {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
return new _1.Playlist({ client: this }).load(response.data);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** Get video information by video id or URL */
|
|
70
|
+
getVideo(videoIdOrUrl) {
|
|
71
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
72
|
+
const videoId = common_1.getQueryParameter(videoIdOrUrl, "v");
|
|
73
|
+
const response = yield this.http.get(`${constants_1.WATCH_END_POINT}`, {
|
|
74
|
+
params: { v: videoId, pbj: "1" },
|
|
75
|
+
});
|
|
76
|
+
if (!response.data[3].response.contents)
|
|
77
|
+
return undefined;
|
|
78
|
+
return (!response.data[2].playerResponse.playabilityStatus.liveStreamability
|
|
79
|
+
? new _1.Video({ client: this }).load(response.data)
|
|
80
|
+
: new _1.LiveVideo({ client: this }).load(response.data));
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** Get channel information by channel id+ */
|
|
84
|
+
getChannel(channelId) {
|
|
85
|
+
var _a, _b, _c;
|
|
86
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
87
|
+
const response = yield this.http.post(`${constants_1.I_END_POINT}/browse`, {
|
|
88
|
+
data: { browseId: channelId },
|
|
89
|
+
});
|
|
90
|
+
if (response.data.error || ((_c = (_b = (_a = response.data.alerts) === null || _a === void 0 ? void 0 : _a.shift()) === null || _b === void 0 ? void 0 : _b.alertRenderer) === null || _c === void 0 ? void 0 : _c.type) === "ERROR") {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
return new _1.Channel({ client: this }).load(response.data);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
exports.default = Client;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Base, ChannelCompact, Video, BaseAttributes, Reply } from ".";
|
|
2
|
+
import { YoutubeRawData } from "../common";
|
|
3
|
+
/** @hidden */
|
|
4
|
+
interface CommentAttributes extends BaseAttributes {
|
|
5
|
+
video: Video;
|
|
6
|
+
author: ChannelCompact;
|
|
7
|
+
content: string;
|
|
8
|
+
publishDate: string;
|
|
9
|
+
likeCount: number;
|
|
10
|
+
isAuthorChannelOwner: boolean;
|
|
11
|
+
isPinnedComment: boolean;
|
|
12
|
+
replyCount: number;
|
|
13
|
+
replyContinuation?: string;
|
|
14
|
+
}
|
|
15
|
+
/** Represents a Comment / Reply */
|
|
16
|
+
export default class Comment extends Base implements CommentAttributes {
|
|
17
|
+
/** The video this comment belongs to */
|
|
18
|
+
video: Video;
|
|
19
|
+
/** The comment's author */
|
|
20
|
+
author: ChannelCompact;
|
|
21
|
+
/** The content of this comment */
|
|
22
|
+
content: string;
|
|
23
|
+
/** The publish date of the comment */
|
|
24
|
+
publishDate: string;
|
|
25
|
+
/** How many likes does this comment have */
|
|
26
|
+
likeCount: number;
|
|
27
|
+
/** Whether the comment is posted by the video uploader / owner */
|
|
28
|
+
isAuthorChannelOwner: boolean;
|
|
29
|
+
/** Whether the comment is pinned */
|
|
30
|
+
isPinnedComment: boolean;
|
|
31
|
+
/** Comment's reply count */
|
|
32
|
+
replyCount: number;
|
|
33
|
+
/** Comment's loaded replies */
|
|
34
|
+
replies: Reply[];
|
|
35
|
+
/** Current continuation token to load next replies */
|
|
36
|
+
replyContinuation?: string;
|
|
37
|
+
/** @hidden */
|
|
38
|
+
constructor(comment?: Partial<CommentAttributes>);
|
|
39
|
+
/**
|
|
40
|
+
* Load this instance with raw data from Youtube
|
|
41
|
+
*
|
|
42
|
+
* @hidden
|
|
43
|
+
*/
|
|
44
|
+
load(data: YoutubeRawData): Comment;
|
|
45
|
+
/** URL to the video with this comment being highlighted (appears on top of the comment section) */
|
|
46
|
+
get url(): string;
|
|
47
|
+
/** Load next replies of the comment */
|
|
48
|
+
nextReplies(count?: number): Promise<Reply[]>;
|
|
49
|
+
}
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const _1 = require(".");
|
|
13
|
+
const common_1 = require("../common");
|
|
14
|
+
const constants_1 = require("../constants");
|
|
15
|
+
/** Represents a Comment / Reply */
|
|
16
|
+
class Comment extends _1.Base {
|
|
17
|
+
/** @hidden */
|
|
18
|
+
constructor(comment = {}) {
|
|
19
|
+
super();
|
|
20
|
+
/** Comment's loaded replies */
|
|
21
|
+
this.replies = [];
|
|
22
|
+
Object.assign(this, comment);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Load this instance with raw data from Youtube
|
|
26
|
+
*
|
|
27
|
+
* @hidden
|
|
28
|
+
*/
|
|
29
|
+
load(data) {
|
|
30
|
+
const { authorText, authorThumbnail, authorEndpoint, contentText, publishedTimeText, commentId, voteCount, authorIsChannelOwner, pinnedCommentBadge, replyCount, } = data.comment.commentRenderer;
|
|
31
|
+
// Basic information
|
|
32
|
+
this.id = commentId;
|
|
33
|
+
this.content = contentText.runs.map((r) => r.text).join("");
|
|
34
|
+
this.publishDate = publishedTimeText.runs.shift().text;
|
|
35
|
+
this.likeCount = +((voteCount === null || voteCount === void 0 ? void 0 : voteCount.simpleText) || 0);
|
|
36
|
+
this.isAuthorChannelOwner = authorIsChannelOwner;
|
|
37
|
+
this.isPinnedComment = !!pinnedCommentBadge;
|
|
38
|
+
this.replyCount = replyCount;
|
|
39
|
+
// Reply Continuation
|
|
40
|
+
this.replies = [];
|
|
41
|
+
this.replyContinuation = data.replies
|
|
42
|
+
? common_1.getContinuationFromItems(data.replies.commentRepliesRenderer.contents)
|
|
43
|
+
: undefined;
|
|
44
|
+
// Author
|
|
45
|
+
const { browseId } = authorEndpoint.browseEndpoint;
|
|
46
|
+
this.author = new _1.ChannelCompact({
|
|
47
|
+
id: browseId,
|
|
48
|
+
name: authorText.simpleText,
|
|
49
|
+
thumbnails: new _1.Thumbnails().load(authorThumbnail.thumbnails),
|
|
50
|
+
client: this.client,
|
|
51
|
+
});
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
/** URL to the video with this comment being highlighted (appears on top of the comment section) */
|
|
55
|
+
get url() {
|
|
56
|
+
return `https://www.youtube.com/watch?v=${this.video.id}&lc=${this.id}`;
|
|
57
|
+
}
|
|
58
|
+
/** Load next replies of the comment */
|
|
59
|
+
nextReplies(count = 1) {
|
|
60
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
const newReplies = [];
|
|
62
|
+
for (let i = 0; i < count || count == 0; i++) {
|
|
63
|
+
if (!this.replyContinuation)
|
|
64
|
+
break;
|
|
65
|
+
// Send request
|
|
66
|
+
const response = yield this.client.http.post(`${constants_1.I_END_POINT}/next`, {
|
|
67
|
+
data: { continuation: this.replyContinuation },
|
|
68
|
+
});
|
|
69
|
+
const continuationItems = response.data.onResponseReceivedEndpoints[0].appendContinuationItemsAction
|
|
70
|
+
.continuationItems;
|
|
71
|
+
this.replyContinuation = common_1.getContinuationFromItems(continuationItems, [
|
|
72
|
+
"button",
|
|
73
|
+
"buttonRenderer",
|
|
74
|
+
"command",
|
|
75
|
+
]);
|
|
76
|
+
const replies = common_1.mapFilter(continuationItems, "commentRenderer");
|
|
77
|
+
newReplies.push(...replies.map((i) => new _1.Reply({ video: this.video, comment: this, client: this.client }).load(i)));
|
|
78
|
+
}
|
|
79
|
+
this.replies.push(...newReplies);
|
|
80
|
+
return newReplies;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.default = Comment;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { YoutubeRawData } from "../common";
|
|
2
|
+
import { Chat, BaseVideo, BaseVideoAttributes } from ".";
|
|
3
|
+
/** @hidden */
|
|
4
|
+
interface LiveVideoAttributes extends BaseVideoAttributes {
|
|
5
|
+
watchingCount: number;
|
|
6
|
+
chatContinuation?: string;
|
|
7
|
+
}
|
|
8
|
+
interface LiveVideoEvents {
|
|
9
|
+
chat: (chat: Chat) => void;
|
|
10
|
+
}
|
|
11
|
+
declare interface LiveVideo {
|
|
12
|
+
on<T extends keyof LiveVideoEvents>(event: T, listener: LiveVideoEvents[T]): AsyncIterableIterator<any>;
|
|
13
|
+
emit<T extends keyof LiveVideoEvents>(event: T, ...args: Parameters<LiveVideoEvents[T]>): boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Represents a video that's currently live, usually returned from `client.getVideo()` */
|
|
16
|
+
declare class LiveVideo extends BaseVideo implements LiveVideoAttributes {
|
|
17
|
+
/** Number of people who's watching the live stream right now */
|
|
18
|
+
watchingCount: number;
|
|
19
|
+
/** Current continuation token to load next chat */
|
|
20
|
+
chatContinuation: string;
|
|
21
|
+
private _delay;
|
|
22
|
+
private _chatRequestPoolingTimeout;
|
|
23
|
+
private _timeoutMs;
|
|
24
|
+
private _isChatPlaying;
|
|
25
|
+
private _chatQueue;
|
|
26
|
+
/** @hidden */
|
|
27
|
+
constructor(video?: Partial<LiveVideoAttributes>);
|
|
28
|
+
/**
|
|
29
|
+
* Load this instance with raw data from Youtube
|
|
30
|
+
*
|
|
31
|
+
* @hidden
|
|
32
|
+
*/
|
|
33
|
+
load(data: YoutubeRawData): LiveVideo;
|
|
34
|
+
/**
|
|
35
|
+
* Start polling for get live chat request
|
|
36
|
+
*
|
|
37
|
+
* @param delay chat delay in millisecond
|
|
38
|
+
*/
|
|
39
|
+
playChat(delay?: number): void;
|
|
40
|
+
/** Stop request polling for live chat */
|
|
41
|
+
stopChat(): void;
|
|
42
|
+
/** Start request polling */
|
|
43
|
+
private pollChatContinuation;
|
|
44
|
+
/** Parse chat data from Youtube and add to chatQueue */
|
|
45
|
+
private parseChat;
|
|
46
|
+
}
|
|
47
|
+
export default LiveVideo;
|