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.
@@ -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[0].text.content;
31
- videoCountText = metadataRow.metadataParts[1].text.content;
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 response = yield this.http.get(`${constants_1.WATCH_END_POINT}`, {
79
- params: { v: videoId, pbj: "1" },
80
- });
81
- const data = Array.isArray(response.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.WATCH_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;
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 1:
168
+ case 5:
148
169
  response = _e.sent();
149
170
  return [4 /*yield*/, response.json()];
150
- case 2:
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 _j = data.header, c4TabbedHeaderRenderer = _j.c4TabbedHeaderRenderer, pageHeaderRenderer = _j.pageHeaderRenderer;
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 _k = pageHeaderRenderer.content.pageHeaderViewModel, metadata = _k.metadata, imageModel = _k.image, bannerModel = _k.banner;
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[0].text.content;
41
- videoCountText = metadataRow.metadataParts[1].text.content;
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, WATCH_END_POINT, } from "../constants";
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 response, data;
139
- return __generator(this, function (_c) {
140
- switch (_c.label) {
141
- case 0: return [4 /*yield*/, this.http.get("" + WATCH_END_POINT, {
142
- params: { v: videoId, pbj: "1" },
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
- response = _c.sent();
146
- data = Array.isArray(response.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.5.3",
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",