youtubei 1.5.3 → 1.6.0
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/dist/cjs/common/shared/HTTP/HTTP.js +30 -0
- package/dist/cjs/common/shared/HTTP/OAuth.js +133 -0
- package/dist/cjs/youtube/Channel/ChannelParser.js +3 -3
- package/dist/cjs/youtube/Client/Client.js +5 -7
- package/dist/cjs/youtube/constants.js +1 -2
- package/dist/esm/common/shared/HTTP/HTTP.js +51 -2
- package/dist/esm/common/shared/HTTP/OAuth.js +193 -0
- package/dist/esm/youtube/Channel/ChannelParser.js +5 -5
- package/dist/esm/youtube/Client/Client.js +27 -12
- package/dist/esm/youtube/constants.js +0 -1
- package/dist/typings/common/shared/HTTP/HTTP.d.ts +12 -0
- package/dist/typings/common/shared/HTTP/OAuth.d.ts +21 -0
- package/dist/typings/youtube/Client/Client.d.ts +2 -1
- package/dist/typings/youtube/constants.d.ts +0 -1
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
15
15
|
exports.HTTP = void 0;
|
|
16
16
|
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
17
17
|
const url_1 = require("url");
|
|
18
|
+
const OAuth_1 = require("./OAuth");
|
|
18
19
|
/**
|
|
19
20
|
* @hidden
|
|
20
21
|
*/
|
|
@@ -31,6 +32,8 @@ class HTTP {
|
|
|
31
32
|
"content-type": "application/json",
|
|
32
33
|
"accept-encoding": "gzip, deflate, br",
|
|
33
34
|
};
|
|
35
|
+
this.oauth = Object.assign({ enabled: false, token: null, expiresAt: null }, options.oauth);
|
|
36
|
+
this.authorizationPromise = null;
|
|
34
37
|
this.defaultFetchOptions = options.fetchOptions || {};
|
|
35
38
|
this.defaultClientOptions = options.youtubeClientOptions || {};
|
|
36
39
|
}
|
|
@@ -48,7 +51,18 @@ class HTTP {
|
|
|
48
51
|
}
|
|
49
52
|
request(path, partialOptions) {
|
|
50
53
|
return __awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
if (this.authorizationPromise)
|
|
55
|
+
yield this.authorizationPromise;
|
|
51
56
|
const options = Object.assign(Object.assign(Object.assign({}, partialOptions), this.defaultFetchOptions), { headers: Object.assign(Object.assign(Object.assign(Object.assign({}, this.defaultHeaders), { cookie: this.cookie, referer: `https://${this.baseUrl}/` }), partialOptions.headers), this.defaultFetchOptions.headers), body: partialOptions.data ? JSON.stringify(partialOptions.data) : undefined });
|
|
57
|
+
if (this.oauth.enabled) {
|
|
58
|
+
this.authorizationPromise = this.authorize();
|
|
59
|
+
yield this.authorizationPromise;
|
|
60
|
+
if (this.oauth.token) {
|
|
61
|
+
options.headers = {
|
|
62
|
+
Authorization: `Bearer ${this.oauth.token}`,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
52
66
|
// if URL is a full URL, ignore baseUrl
|
|
53
67
|
let urlString;
|
|
54
68
|
if (path.startsWith("http")) {
|
|
@@ -72,5 +86,21 @@ class HTTP {
|
|
|
72
86
|
if (cookie)
|
|
73
87
|
this.cookie = cookie;
|
|
74
88
|
}
|
|
89
|
+
authorize() {
|
|
90
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
91
|
+
const isExpired = !this.oauth.expiresAt || this.oauth.expiresAt.getTime() - 5 * 60 * 1000 < Date.now();
|
|
92
|
+
if (this.oauth.refreshToken && (isExpired || !this.oauth.token)) {
|
|
93
|
+
const response = yield OAuth_1.OAuth.refreshToken(this.oauth.refreshToken);
|
|
94
|
+
this.oauth.token = response.accessToken;
|
|
95
|
+
this.oauth.expiresAt = new Date(Date.now() + response.expiresIn * 1000);
|
|
96
|
+
}
|
|
97
|
+
else if (isExpired || !this.oauth.token) {
|
|
98
|
+
const response = yield OAuth_1.OAuth.authorize();
|
|
99
|
+
this.oauth.token = response.accessToken;
|
|
100
|
+
this.oauth.refreshToken = response.refreshToken;
|
|
101
|
+
this.oauth.expiresAt = new Date(Date.now() + response.expiresIn * 1000);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
75
105
|
}
|
|
76
106
|
exports.HTTP = HTTP;
|
|
@@ -0,0 +1,133 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.OAuth = void 0;
|
|
16
|
+
const crypto_1 = require("crypto");
|
|
17
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
18
|
+
class OAuth {
|
|
19
|
+
static authorize() {
|
|
20
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
21
|
+
const body = {
|
|
22
|
+
client_id: this.CLIENT_ID,
|
|
23
|
+
scope: this.SCOPE,
|
|
24
|
+
device_id: crypto_1.randomBytes(20).toString("hex"),
|
|
25
|
+
device_model: "ytlr::",
|
|
26
|
+
};
|
|
27
|
+
const response = yield node_fetch_1.default("https://www.youtube.com/o/oauth2/device/code", {
|
|
28
|
+
method: "POST",
|
|
29
|
+
headers: {
|
|
30
|
+
"Content-Type": "application/json",
|
|
31
|
+
__youtube_oauth__: "True",
|
|
32
|
+
},
|
|
33
|
+
body: JSON.stringify(body),
|
|
34
|
+
});
|
|
35
|
+
if (response.ok) {
|
|
36
|
+
const data = yield response.json();
|
|
37
|
+
console.log(`[youtubei] Open ${data.verification_url} and enter ${data.user_code}`);
|
|
38
|
+
let authenticateResponse = null;
|
|
39
|
+
while (!authenticateResponse) {
|
|
40
|
+
try {
|
|
41
|
+
authenticateResponse = yield this.authenticate(data.device_code);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const message = err.message;
|
|
45
|
+
if (message === "authorization_pending") {
|
|
46
|
+
yield new Promise((r) => setTimeout(r, data.interval * 1000));
|
|
47
|
+
}
|
|
48
|
+
else if (message === "expired_token") {
|
|
49
|
+
return this.authorize();
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
throw err;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return authenticateResponse;
|
|
57
|
+
}
|
|
58
|
+
throw new Error("Authorization failed");
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
static authenticate(code) {
|
|
62
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
63
|
+
const body = {
|
|
64
|
+
client_id: this.CLIENT_ID,
|
|
65
|
+
client_secret: this.CLIENT_SECRET,
|
|
66
|
+
code,
|
|
67
|
+
grant_type: "http://oauth.net/grant_type/device/1.0",
|
|
68
|
+
};
|
|
69
|
+
const response = yield node_fetch_1.default("https://www.youtube.com/o/oauth2/token", {
|
|
70
|
+
method: "POST",
|
|
71
|
+
headers: {
|
|
72
|
+
"Content-Type": "application/json",
|
|
73
|
+
__youtube_oauth__: "True",
|
|
74
|
+
},
|
|
75
|
+
body: JSON.stringify(body),
|
|
76
|
+
});
|
|
77
|
+
if (response.ok) {
|
|
78
|
+
const data = yield response.json();
|
|
79
|
+
if ("error" in data) {
|
|
80
|
+
throw new Error(data.error);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
return {
|
|
84
|
+
accessToken: data.access_token,
|
|
85
|
+
expiresIn: data.expires_in,
|
|
86
|
+
refreshToken: data.refresh_token,
|
|
87
|
+
scope: data.scope,
|
|
88
|
+
tokenType: data.token_type,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
throw new Error("Authentication failed");
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
static refreshToken(refreshToken) {
|
|
98
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
99
|
+
const body = {
|
|
100
|
+
client_id: this.CLIENT_ID,
|
|
101
|
+
client_secret: this.CLIENT_SECRET,
|
|
102
|
+
refresh_token: refreshToken,
|
|
103
|
+
grant_type: "refresh_token",
|
|
104
|
+
};
|
|
105
|
+
const response = yield node_fetch_1.default("https://www.youtube.com/o/oauth2/token", {
|
|
106
|
+
method: "POST",
|
|
107
|
+
headers: {
|
|
108
|
+
"Content-Type": "application/json",
|
|
109
|
+
__youtube_oauth__: "True",
|
|
110
|
+
},
|
|
111
|
+
body: JSON.stringify(body),
|
|
112
|
+
});
|
|
113
|
+
if (response.ok) {
|
|
114
|
+
const data = yield response.json();
|
|
115
|
+
if ("error" in data)
|
|
116
|
+
return this.authorize();
|
|
117
|
+
return {
|
|
118
|
+
accessToken: data.access_token,
|
|
119
|
+
expiresIn: data.expires_in,
|
|
120
|
+
scope: data.scope,
|
|
121
|
+
tokenType: data.token_type,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
return this.authorize();
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
exports.OAuth = OAuth;
|
|
131
|
+
OAuth.CLIENT_ID = "861556708454-d6dlm3lh05idd8npek18k6be8ba3oc68.apps.googleusercontent.com";
|
|
132
|
+
OAuth.CLIENT_SECRET = "SboVhoG9s0rNafixCSGGKXAT";
|
|
133
|
+
OAuth.SCOPE = "http://gdata.youtube.com https://www.googleapis.com/auth/youtube";
|
|
@@ -7,7 +7,7 @@ const PlaylistCompact_1 = require("../PlaylistCompact");
|
|
|
7
7
|
const VideoCompact_1 = require("../VideoCompact");
|
|
8
8
|
class ChannelParser {
|
|
9
9
|
static loadChannel(target, data) {
|
|
10
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
10
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
11
11
|
let channelId, title, avatar, subscriberCountText, videoCountText, tvBanner, mobileBanner, banner;
|
|
12
12
|
const { c4TabbedHeaderRenderer, pageHeaderRenderer } = data.header;
|
|
13
13
|
if (c4TabbedHeaderRenderer) {
|
|
@@ -27,8 +27,8 @@ class ChannelParser {
|
|
|
27
27
|
title = pageHeaderRenderer.pageTitle;
|
|
28
28
|
const { metadata, image: imageModel, banner: bannerModel, } = pageHeaderRenderer.content.pageHeaderViewModel;
|
|
29
29
|
const metadataRow = metadata.contentMetadataViewModel.metadataRows[1];
|
|
30
|
-
subscriberCountText = metadataRow.metadataParts
|
|
31
|
-
videoCountText = metadataRow.metadataParts
|
|
30
|
+
subscriberCountText = metadataRow.metadataParts.find((m) => !m.text.styeRuns).text.content;
|
|
31
|
+
videoCountText = (_j = metadataRow.metadataParts.find((m) => m.text.styeRuns)) === null || _j === void 0 ? void 0 : _j.text.content;
|
|
32
32
|
avatar = imageModel.decoratedAvatarViewModel.avatar.avatarViewModel.image.sources;
|
|
33
33
|
banner = bannerModel === null || bannerModel === void 0 ? void 0 : bannerModel.imageBannerViewModel.image.sources;
|
|
34
34
|
}
|
|
@@ -21,7 +21,7 @@ const constants_1 = require("../constants");
|
|
|
21
21
|
/** Youtube Client */
|
|
22
22
|
class Client {
|
|
23
23
|
constructor(options = {}) {
|
|
24
|
-
this.options = Object.assign(Object.assign({ initialCookie: "", fetchOptions: {} }, options), { youtubeClientOptions: Object.assign({ hl: "en", gl: "US" }, options.youtubeClientOptions) });
|
|
24
|
+
this.options = Object.assign(Object.assign({ initialCookie: "", oauth: { enabled: false }, fetchOptions: {} }, options), { youtubeClientOptions: Object.assign({ hl: "en", gl: "US" }, options.youtubeClientOptions) });
|
|
25
25
|
this.http = new common_1.HTTP(Object.assign({ apiKey: constants_1.INNERTUBE_API_KEY, baseUrl: constants_1.BASE_URL, clientName: constants_1.INNERTUBE_CLIENT_NAME, clientVersion: constants_1.INNERTUBE_CLIENT_VERSION }, this.options));
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
@@ -75,12 +75,10 @@ class Client {
|
|
|
75
75
|
getVideo(videoId) {
|
|
76
76
|
var _a, _b;
|
|
77
77
|
return __awaiter(this, void 0, void 0, function* () {
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const data =
|
|
82
|
-
? response.data.reduce((prev, curr) => (Object.assign(Object.assign({}, prev), curr)), {})
|
|
83
|
-
: response.data;
|
|
78
|
+
const nextPromise = this.http.post(`${constants_1.I_END_POINT}/next`, { data: { videoId } });
|
|
79
|
+
const playerPromise = this.http.post(`${constants_1.I_END_POINT}/player`, { data: { videoId } });
|
|
80
|
+
const [nextResponse, playerResponse] = yield Promise.all([nextPromise, playerPromise]);
|
|
81
|
+
const data = { response: nextResponse.data, playerResponse: playerResponse.data };
|
|
84
82
|
if (!((_b = (_a = data.response) === null || _a === void 0 ? void 0 : _a.contents) === null || _b === void 0 ? void 0 : _b.twoColumnWatchNextResults.results.results.contents) ||
|
|
85
83
|
data.playerResponse.playabilityStatus.status === "ERROR") {
|
|
86
84
|
return undefined;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.COMMENT_END_POINT = exports.
|
|
3
|
+
exports.COMMENT_END_POINT = exports.LIVE_CHAT_END_POINT = exports.I_END_POINT = exports.BASE_URL = exports.INNERTUBE_API_KEY = exports.INNERTUBE_CLIENT_VERSION = exports.INNERTUBE_CLIENT_NAME = void 0;
|
|
4
4
|
exports.INNERTUBE_CLIENT_NAME = "WEB";
|
|
5
5
|
exports.INNERTUBE_CLIENT_VERSION = "2.20201209.01.00";
|
|
6
6
|
exports.INNERTUBE_API_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8";
|
|
7
7
|
exports.BASE_URL = "www.youtube.com";
|
|
8
8
|
exports.I_END_POINT = "/youtubei/v1";
|
|
9
9
|
exports.LIVE_CHAT_END_POINT = `${exports.I_END_POINT}/live_chat/get_live_chat`;
|
|
10
|
-
exports.WATCH_END_POINT = "/watch";
|
|
11
10
|
exports.COMMENT_END_POINT = "/comment_service_ajax";
|
|
@@ -74,6 +74,7 @@ var __read = (this && this.__read) || function (o, n) {
|
|
|
74
74
|
};
|
|
75
75
|
import fetch from "node-fetch";
|
|
76
76
|
import { URLSearchParams } from "url";
|
|
77
|
+
import { OAuth } from "./OAuth";
|
|
77
78
|
/**
|
|
78
79
|
* @hidden
|
|
79
80
|
*/
|
|
@@ -90,6 +91,8 @@ var HTTP = /** @class */ (function () {
|
|
|
90
91
|
"content-type": "application/json",
|
|
91
92
|
"accept-encoding": "gzip, deflate, br",
|
|
92
93
|
};
|
|
94
|
+
this.oauth = __assign({ enabled: false, token: null, expiresAt: null }, options.oauth);
|
|
95
|
+
this.authorizationPromise = null;
|
|
93
96
|
this.defaultFetchOptions = options.fetchOptions || {};
|
|
94
97
|
this.defaultClientOptions = options.youtubeClientOptions || {};
|
|
95
98
|
}
|
|
@@ -122,7 +125,25 @@ var HTTP = /** @class */ (function () {
|
|
|
122
125
|
return __generator(this, function (_e) {
|
|
123
126
|
switch (_e.label) {
|
|
124
127
|
case 0:
|
|
128
|
+
if (!this.authorizationPromise) return [3 /*break*/, 2];
|
|
129
|
+
return [4 /*yield*/, this.authorizationPromise];
|
|
130
|
+
case 1:
|
|
131
|
+
_e.sent();
|
|
132
|
+
_e.label = 2;
|
|
133
|
+
case 2:
|
|
125
134
|
options = __assign(__assign(__assign({}, partialOptions), this.defaultFetchOptions), { headers: __assign(__assign(__assign(__assign({}, this.defaultHeaders), { cookie: this.cookie, referer: "https://" + this.baseUrl + "/" }), partialOptions.headers), this.defaultFetchOptions.headers), body: partialOptions.data ? JSON.stringify(partialOptions.data) : undefined });
|
|
135
|
+
if (!this.oauth.enabled) return [3 /*break*/, 4];
|
|
136
|
+
this.authorizationPromise = this.authorize();
|
|
137
|
+
return [4 /*yield*/, this.authorizationPromise];
|
|
138
|
+
case 3:
|
|
139
|
+
_e.sent();
|
|
140
|
+
if (this.oauth.token) {
|
|
141
|
+
options.headers = {
|
|
142
|
+
Authorization: "Bearer " + this.oauth.token,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
_e.label = 4;
|
|
146
|
+
case 4:
|
|
126
147
|
if (path.startsWith("http")) {
|
|
127
148
|
url = new URL(path);
|
|
128
149
|
try {
|
|
@@ -144,10 +165,10 @@ var HTTP = /** @class */ (function () {
|
|
|
144
165
|
urlString = "https://" + this.baseUrl + "/" + path + "?" + new URLSearchParams(partialOptions.params);
|
|
145
166
|
}
|
|
146
167
|
return [4 /*yield*/, fetch(urlString, options)];
|
|
147
|
-
case
|
|
168
|
+
case 5:
|
|
148
169
|
response = _e.sent();
|
|
149
170
|
return [4 /*yield*/, response.json()];
|
|
150
|
-
case
|
|
171
|
+
case 6:
|
|
151
172
|
data = _e.sent();
|
|
152
173
|
this.parseCookie(response);
|
|
153
174
|
return [2 /*return*/, { data: data }];
|
|
@@ -160,6 +181,34 @@ var HTTP = /** @class */ (function () {
|
|
|
160
181
|
if (cookie)
|
|
161
182
|
this.cookie = cookie;
|
|
162
183
|
};
|
|
184
|
+
HTTP.prototype.authorize = function () {
|
|
185
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
186
|
+
var isExpired, response, response;
|
|
187
|
+
return __generator(this, function (_a) {
|
|
188
|
+
switch (_a.label) {
|
|
189
|
+
case 0:
|
|
190
|
+
isExpired = !this.oauth.expiresAt || this.oauth.expiresAt.getTime() - 5 * 60 * 1000 < Date.now();
|
|
191
|
+
if (!(this.oauth.refreshToken && (isExpired || !this.oauth.token))) return [3 /*break*/, 2];
|
|
192
|
+
return [4 /*yield*/, OAuth.refreshToken(this.oauth.refreshToken)];
|
|
193
|
+
case 1:
|
|
194
|
+
response = _a.sent();
|
|
195
|
+
this.oauth.token = response.accessToken;
|
|
196
|
+
this.oauth.expiresAt = new Date(Date.now() + response.expiresIn * 1000);
|
|
197
|
+
return [3 /*break*/, 4];
|
|
198
|
+
case 2:
|
|
199
|
+
if (!(isExpired || !this.oauth.token)) return [3 /*break*/, 4];
|
|
200
|
+
return [4 /*yield*/, OAuth.authorize()];
|
|
201
|
+
case 3:
|
|
202
|
+
response = _a.sent();
|
|
203
|
+
this.oauth.token = response.accessToken;
|
|
204
|
+
this.oauth.refreshToken = response.refreshToken;
|
|
205
|
+
this.oauth.expiresAt = new Date(Date.now() + response.expiresIn * 1000);
|
|
206
|
+
_a.label = 4;
|
|
207
|
+
case 4: return [2 /*return*/];
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
};
|
|
163
212
|
return HTTP;
|
|
164
213
|
}());
|
|
165
214
|
export { HTTP };
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
11
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
12
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
13
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
14
|
+
function step(op) {
|
|
15
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
16
|
+
while (_) try {
|
|
17
|
+
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;
|
|
18
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
19
|
+
switch (op[0]) {
|
|
20
|
+
case 0: case 1: t = op; break;
|
|
21
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
22
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
23
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
24
|
+
default:
|
|
25
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
26
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
27
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
28
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
29
|
+
if (t[2]) _.ops.pop();
|
|
30
|
+
_.trys.pop(); continue;
|
|
31
|
+
}
|
|
32
|
+
op = body.call(thisArg, _);
|
|
33
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
34
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
import { randomBytes } from "crypto";
|
|
38
|
+
import fetch from "node-fetch";
|
|
39
|
+
var OAuth = /** @class */ (function () {
|
|
40
|
+
function OAuth() {
|
|
41
|
+
}
|
|
42
|
+
OAuth.authorize = function () {
|
|
43
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
44
|
+
var body, response, data_1, authenticateResponse, err_1, message;
|
|
45
|
+
return __generator(this, function (_a) {
|
|
46
|
+
switch (_a.label) {
|
|
47
|
+
case 0:
|
|
48
|
+
body = {
|
|
49
|
+
client_id: this.CLIENT_ID,
|
|
50
|
+
scope: this.SCOPE,
|
|
51
|
+
device_id: randomBytes(20).toString("hex"),
|
|
52
|
+
device_model: "ytlr::",
|
|
53
|
+
};
|
|
54
|
+
return [4 /*yield*/, fetch("https://www.youtube.com/o/oauth2/device/code", {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
__youtube_oauth__: "True",
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify(body),
|
|
61
|
+
})];
|
|
62
|
+
case 1:
|
|
63
|
+
response = _a.sent();
|
|
64
|
+
if (!response.ok) return [3 /*break*/, 12];
|
|
65
|
+
return [4 /*yield*/, response.json()];
|
|
66
|
+
case 2:
|
|
67
|
+
data_1 = _a.sent();
|
|
68
|
+
console.log("[youtubei] Open " + data_1.verification_url + " and enter " + data_1.user_code);
|
|
69
|
+
authenticateResponse = null;
|
|
70
|
+
_a.label = 3;
|
|
71
|
+
case 3:
|
|
72
|
+
if (!!authenticateResponse) return [3 /*break*/, 11];
|
|
73
|
+
_a.label = 4;
|
|
74
|
+
case 4:
|
|
75
|
+
_a.trys.push([4, 6, , 10]);
|
|
76
|
+
return [4 /*yield*/, this.authenticate(data_1.device_code)];
|
|
77
|
+
case 5:
|
|
78
|
+
authenticateResponse = _a.sent();
|
|
79
|
+
return [3 /*break*/, 10];
|
|
80
|
+
case 6:
|
|
81
|
+
err_1 = _a.sent();
|
|
82
|
+
message = err_1.message;
|
|
83
|
+
if (!(message === "authorization_pending")) return [3 /*break*/, 8];
|
|
84
|
+
return [4 /*yield*/, new Promise(function (r) { return setTimeout(r, data_1.interval * 1000); })];
|
|
85
|
+
case 7:
|
|
86
|
+
_a.sent();
|
|
87
|
+
return [3 /*break*/, 9];
|
|
88
|
+
case 8:
|
|
89
|
+
if (message === "expired_token") {
|
|
90
|
+
return [2 /*return*/, this.authorize()];
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
throw err_1;
|
|
94
|
+
}
|
|
95
|
+
_a.label = 9;
|
|
96
|
+
case 9: return [3 /*break*/, 10];
|
|
97
|
+
case 10: return [3 /*break*/, 3];
|
|
98
|
+
case 11: return [2 /*return*/, authenticateResponse];
|
|
99
|
+
case 12: throw new Error("Authorization failed");
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
};
|
|
104
|
+
OAuth.authenticate = function (code) {
|
|
105
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
106
|
+
var body, response, data;
|
|
107
|
+
return __generator(this, function (_a) {
|
|
108
|
+
switch (_a.label) {
|
|
109
|
+
case 0:
|
|
110
|
+
body = {
|
|
111
|
+
client_id: this.CLIENT_ID,
|
|
112
|
+
client_secret: this.CLIENT_SECRET,
|
|
113
|
+
code: code,
|
|
114
|
+
grant_type: "http://oauth.net/grant_type/device/1.0",
|
|
115
|
+
};
|
|
116
|
+
return [4 /*yield*/, fetch("https://www.youtube.com/o/oauth2/token", {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: {
|
|
119
|
+
"Content-Type": "application/json",
|
|
120
|
+
__youtube_oauth__: "True",
|
|
121
|
+
},
|
|
122
|
+
body: JSON.stringify(body),
|
|
123
|
+
})];
|
|
124
|
+
case 1:
|
|
125
|
+
response = _a.sent();
|
|
126
|
+
if (!response.ok) return [3 /*break*/, 3];
|
|
127
|
+
return [4 /*yield*/, response.json()];
|
|
128
|
+
case 2:
|
|
129
|
+
data = _a.sent();
|
|
130
|
+
if ("error" in data) {
|
|
131
|
+
throw new Error(data.error);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
return [2 /*return*/, {
|
|
135
|
+
accessToken: data.access_token,
|
|
136
|
+
expiresIn: data.expires_in,
|
|
137
|
+
refreshToken: data.refresh_token,
|
|
138
|
+
scope: data.scope,
|
|
139
|
+
tokenType: data.token_type,
|
|
140
|
+
}];
|
|
141
|
+
}
|
|
142
|
+
return [3 /*break*/, 4];
|
|
143
|
+
case 3: throw new Error("Authentication failed");
|
|
144
|
+
case 4: return [2 /*return*/];
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
OAuth.refreshToken = function (refreshToken) {
|
|
150
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
151
|
+
var body, response, data;
|
|
152
|
+
return __generator(this, function (_a) {
|
|
153
|
+
switch (_a.label) {
|
|
154
|
+
case 0:
|
|
155
|
+
body = {
|
|
156
|
+
client_id: this.CLIENT_ID,
|
|
157
|
+
client_secret: this.CLIENT_SECRET,
|
|
158
|
+
refresh_token: refreshToken,
|
|
159
|
+
grant_type: "refresh_token",
|
|
160
|
+
};
|
|
161
|
+
return [4 /*yield*/, fetch("https://www.youtube.com/o/oauth2/token", {
|
|
162
|
+
method: "POST",
|
|
163
|
+
headers: {
|
|
164
|
+
"Content-Type": "application/json",
|
|
165
|
+
__youtube_oauth__: "True",
|
|
166
|
+
},
|
|
167
|
+
body: JSON.stringify(body),
|
|
168
|
+
})];
|
|
169
|
+
case 1:
|
|
170
|
+
response = _a.sent();
|
|
171
|
+
if (!response.ok) return [3 /*break*/, 3];
|
|
172
|
+
return [4 /*yield*/, response.json()];
|
|
173
|
+
case 2:
|
|
174
|
+
data = _a.sent();
|
|
175
|
+
if ("error" in data)
|
|
176
|
+
return [2 /*return*/, this.authorize()];
|
|
177
|
+
return [2 /*return*/, {
|
|
178
|
+
accessToken: data.access_token,
|
|
179
|
+
expiresIn: data.expires_in,
|
|
180
|
+
scope: data.scope,
|
|
181
|
+
tokenType: data.token_type,
|
|
182
|
+
}];
|
|
183
|
+
case 3: return [2 /*return*/, this.authorize()];
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
OAuth.CLIENT_ID = "861556708454-d6dlm3lh05idd8npek18k6be8ba3oc68.apps.googleusercontent.com";
|
|
189
|
+
OAuth.CLIENT_SECRET = "SboVhoG9s0rNafixCSGGKXAT";
|
|
190
|
+
OAuth.SCOPE = "http://gdata.youtube.com https://www.googleapis.com/auth/youtube";
|
|
191
|
+
return OAuth;
|
|
192
|
+
}());
|
|
193
|
+
export { OAuth };
|
|
@@ -17,9 +17,9 @@ var ChannelParser = /** @class */ (function () {
|
|
|
17
17
|
function ChannelParser() {
|
|
18
18
|
}
|
|
19
19
|
ChannelParser.loadChannel = function (target, data) {
|
|
20
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
20
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
21
21
|
var channelId, title, avatar, subscriberCountText, videoCountText, tvBanner, mobileBanner, banner;
|
|
22
|
-
var
|
|
22
|
+
var _k = data.header, c4TabbedHeaderRenderer = _k.c4TabbedHeaderRenderer, pageHeaderRenderer = _k.pageHeaderRenderer;
|
|
23
23
|
if (c4TabbedHeaderRenderer) {
|
|
24
24
|
channelId = c4TabbedHeaderRenderer.channelId;
|
|
25
25
|
title = c4TabbedHeaderRenderer.title;
|
|
@@ -35,10 +35,10 @@ var ChannelParser = /** @class */ (function () {
|
|
|
35
35
|
data.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.endpoint
|
|
36
36
|
.browseEndpoint.browseId;
|
|
37
37
|
title = pageHeaderRenderer.pageTitle;
|
|
38
|
-
var
|
|
38
|
+
var _l = pageHeaderRenderer.content.pageHeaderViewModel, metadata = _l.metadata, imageModel = _l.image, bannerModel = _l.banner;
|
|
39
39
|
var metadataRow = metadata.contentMetadataViewModel.metadataRows[1];
|
|
40
|
-
subscriberCountText = metadataRow.metadataParts
|
|
41
|
-
videoCountText = metadataRow.metadataParts
|
|
40
|
+
subscriberCountText = metadataRow.metadataParts.find(function (m) { return !m.text.styeRuns; }).text.content;
|
|
41
|
+
videoCountText = (_j = metadataRow.metadataParts.find(function (m) { return m.text.styeRuns; })) === null || _j === void 0 ? void 0 : _j.text.content;
|
|
42
42
|
avatar = imageModel.decoratedAvatarViewModel.avatar.avatarViewModel.image.sources;
|
|
43
43
|
banner = bannerModel === null || bannerModel === void 0 ? void 0 : bannerModel.imageBannerViewModel.image.sources;
|
|
44
44
|
}
|
|
@@ -45,6 +45,22 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
45
45
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
|
+
var __read = (this && this.__read) || function (o, n) {
|
|
49
|
+
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
|
50
|
+
if (!m) return o;
|
|
51
|
+
var i = m.call(o), r, ar = [], e;
|
|
52
|
+
try {
|
|
53
|
+
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
|
54
|
+
}
|
|
55
|
+
catch (error) { e = { error: error }; }
|
|
56
|
+
finally {
|
|
57
|
+
try {
|
|
58
|
+
if (r && !r.done && (m = i["return"])) m.call(i);
|
|
59
|
+
}
|
|
60
|
+
finally { if (e) throw e.error; }
|
|
61
|
+
}
|
|
62
|
+
return ar;
|
|
63
|
+
};
|
|
48
64
|
import { HTTP } from "../../common";
|
|
49
65
|
import { Channel } from "../Channel";
|
|
50
66
|
import { LiveVideo } from "../LiveVideo";
|
|
@@ -52,12 +68,12 @@ import { MixPlaylist } from "../MixPlaylist";
|
|
|
52
68
|
import { Playlist } from "../Playlist";
|
|
53
69
|
import { SearchResult } from "../SearchResult";
|
|
54
70
|
import { Video } from "../Video";
|
|
55
|
-
import { BASE_URL, INNERTUBE_API_KEY, INNERTUBE_CLIENT_NAME, INNERTUBE_CLIENT_VERSION, I_END_POINT,
|
|
71
|
+
import { BASE_URL, INNERTUBE_API_KEY, INNERTUBE_CLIENT_NAME, INNERTUBE_CLIENT_VERSION, I_END_POINT, } from "../constants";
|
|
56
72
|
/** Youtube Client */
|
|
57
73
|
var Client = /** @class */ (function () {
|
|
58
74
|
function Client(options) {
|
|
59
75
|
if (options === void 0) { options = {}; }
|
|
60
|
-
this.options = __assign(__assign({ initialCookie: "", fetchOptions: {} }, options), { youtubeClientOptions: __assign({ hl: "en", gl: "US" }, options.youtubeClientOptions) });
|
|
76
|
+
this.options = __assign(__assign({ initialCookie: "", oauth: { enabled: false }, fetchOptions: {} }, options), { youtubeClientOptions: __assign({ hl: "en", gl: "US" }, options.youtubeClientOptions) });
|
|
61
77
|
this.http = new HTTP(__assign({ apiKey: INNERTUBE_API_KEY, baseUrl: BASE_URL, clientName: INNERTUBE_CLIENT_NAME, clientVersion: INNERTUBE_CLIENT_VERSION }, this.options));
|
|
62
78
|
}
|
|
63
79
|
/**
|
|
@@ -135,17 +151,16 @@ var Client = /** @class */ (function () {
|
|
|
135
151
|
Client.prototype.getVideo = function (videoId) {
|
|
136
152
|
var _a, _b;
|
|
137
153
|
return __awaiter(this, void 0, void 0, function () {
|
|
138
|
-
var
|
|
139
|
-
return __generator(this, function (
|
|
140
|
-
switch (
|
|
141
|
-
case 0:
|
|
142
|
-
|
|
143
|
-
})
|
|
154
|
+
var nextPromise, playerPromise, _c, nextResponse, playerResponse, data;
|
|
155
|
+
return __generator(this, function (_d) {
|
|
156
|
+
switch (_d.label) {
|
|
157
|
+
case 0:
|
|
158
|
+
nextPromise = this.http.post(I_END_POINT + "/next", { data: { videoId: videoId } });
|
|
159
|
+
playerPromise = this.http.post(I_END_POINT + "/player", { data: { videoId: videoId } });
|
|
160
|
+
return [4 /*yield*/, Promise.all([nextPromise, playerPromise])];
|
|
144
161
|
case 1:
|
|
145
|
-
|
|
146
|
-
data =
|
|
147
|
-
? response.data.reduce(function (prev, curr) { return (__assign(__assign({}, prev), curr)); }, {})
|
|
148
|
-
: response.data;
|
|
162
|
+
_c = __read.apply(void 0, [_d.sent(), 2]), nextResponse = _c[0], playerResponse = _c[1];
|
|
163
|
+
data = { response: nextResponse.data, playerResponse: playerResponse.data };
|
|
149
164
|
if (!((_b = (_a = data.response) === null || _a === void 0 ? void 0 : _a.contents) === null || _b === void 0 ? void 0 : _b.twoColumnWatchNextResults.results.results.contents) ||
|
|
150
165
|
data.playerResponse.playabilityStatus.status === "ERROR") {
|
|
151
166
|
return [2 /*return*/, undefined];
|
|
@@ -4,5 +4,4 @@ export var INNERTUBE_API_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8";
|
|
|
4
4
|
export var BASE_URL = "www.youtube.com";
|
|
5
5
|
export var I_END_POINT = "/youtubei/v1";
|
|
6
6
|
export var LIVE_CHAT_END_POINT = I_END_POINT + "/live_chat/get_live_chat";
|
|
7
|
-
export var WATCH_END_POINT = "/watch";
|
|
8
7
|
export var COMMENT_END_POINT = "/comment_service_ajax";
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
import { RequestInit } from "node-fetch";
|
|
2
|
+
export declare type OAuthOptions = {
|
|
3
|
+
enabled: boolean;
|
|
4
|
+
refreshToken?: string;
|
|
5
|
+
};
|
|
6
|
+
declare type OAuthProps = {
|
|
7
|
+
token: string | null;
|
|
8
|
+
expiresAt: Date | null;
|
|
9
|
+
};
|
|
2
10
|
declare type HTTPOptions = {
|
|
3
11
|
apiKey: string;
|
|
4
12
|
baseUrl: string;
|
|
@@ -7,6 +15,7 @@ declare type HTTPOptions = {
|
|
|
7
15
|
fetchOptions?: Partial<RequestInit>;
|
|
8
16
|
youtubeClientOptions?: Record<string, unknown>;
|
|
9
17
|
initialCookie?: string;
|
|
18
|
+
oauth?: OAuthOptions;
|
|
10
19
|
};
|
|
11
20
|
declare type Response<T = any> = {
|
|
12
21
|
data: T;
|
|
@@ -27,10 +36,13 @@ export declare class HTTP {
|
|
|
27
36
|
private defaultHeaders;
|
|
28
37
|
private defaultFetchOptions;
|
|
29
38
|
private defaultClientOptions;
|
|
39
|
+
private authorizationPromise;
|
|
40
|
+
oauth: OAuthOptions & OAuthProps;
|
|
30
41
|
constructor(options: HTTPOptions);
|
|
31
42
|
get(path: string, options?: Partial<Options>): Promise<Response>;
|
|
32
43
|
post(path: string, options?: Partial<Options>): Promise<Response>;
|
|
33
44
|
private request;
|
|
34
45
|
private parseCookie;
|
|
46
|
+
private authorize;
|
|
35
47
|
}
|
|
36
48
|
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface RefreshResponse {
|
|
2
|
+
accessToken: string;
|
|
3
|
+
expiresIn: number;
|
|
4
|
+
scope: string;
|
|
5
|
+
tokenType: string;
|
|
6
|
+
}
|
|
7
|
+
export interface AuthenticateResponse {
|
|
8
|
+
accessToken: string;
|
|
9
|
+
expiresIn: number;
|
|
10
|
+
refreshToken: string;
|
|
11
|
+
scope: string;
|
|
12
|
+
tokenType: string;
|
|
13
|
+
}
|
|
14
|
+
export declare class OAuth {
|
|
15
|
+
private static CLIENT_ID;
|
|
16
|
+
private static CLIENT_SECRET;
|
|
17
|
+
private static SCOPE;
|
|
18
|
+
static authorize(): Promise<AuthenticateResponse>;
|
|
19
|
+
private static authenticate;
|
|
20
|
+
static refreshToken(refreshToken: string): Promise<RefreshResponse>;
|
|
21
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RequestInit } from "node-fetch";
|
|
2
|
-
import { HTTP } from "../../common";
|
|
2
|
+
import { HTTP, OAuthOptions } from "../../common";
|
|
3
3
|
import { Caption } from "../Caption";
|
|
4
4
|
import { Channel } from "../Channel";
|
|
5
5
|
import { LiveVideo } from "../LiveVideo";
|
|
@@ -9,6 +9,7 @@ import { SearchOptions, SearchResult, SearchResultItem } from "../SearchResult";
|
|
|
9
9
|
import { Video } from "../Video";
|
|
10
10
|
export declare type ClientOptions = {
|
|
11
11
|
initialCookie: string;
|
|
12
|
+
oauth: OAuthOptions;
|
|
12
13
|
/** Optional options for http client */
|
|
13
14
|
fetchOptions: Partial<RequestInit>;
|
|
14
15
|
/** Optional options passed when sending a request to youtube (context.client) */
|
|
@@ -4,5 +4,4 @@ export declare const INNERTUBE_API_KEY = "AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW
|
|
|
4
4
|
export declare const BASE_URL = "www.youtube.com";
|
|
5
5
|
export declare const I_END_POINT = "/youtubei/v1";
|
|
6
6
|
export declare const LIVE_CHAT_END_POINT: string;
|
|
7
|
-
export declare const WATCH_END_POINT = "/watch";
|
|
8
7
|
export declare const COMMENT_END_POINT = "/comment_service_ajax";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "youtubei",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Simple package to get information from youtube such as videos, playlists, channels, video information & comments, related videos, up next video, and more!",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|