Update api/src/processing/services/youtube.js
Browse files
api/src/processing/services/youtube.js
CHANGED
@@ -48,7 +48,7 @@ const transformSessionData = (cookie) => {
|
|
48 |
return;
|
49 |
|
50 |
const values = { ...cookie.values() };
|
51 |
-
const REQUIRED_VALUES = [
|
52 |
|
53 |
if (REQUIRED_VALUES.some(x => typeof values[x] !== 'string')) {
|
54 |
return;
|
@@ -66,10 +66,18 @@ const transformSessionData = (cookie) => {
|
|
66 |
|
67 |
const cloneInnertube = async (customFetch) => {
|
68 |
const shouldRefreshPlayer = lastRefreshedAt + PLAYER_REFRESH_PERIOD < new Date();
|
|
|
|
|
|
|
|
|
|
|
69 |
if (!innertube || shouldRefreshPlayer) {
|
70 |
innertube = await Innertube.create({
|
71 |
fetch: customFetch,
|
72 |
-
retrieve_player:
|
|
|
|
|
|
|
73 |
});
|
74 |
lastRefreshedAt = +new Date();
|
75 |
}
|
@@ -80,30 +88,30 @@ const cloneInnertube = async (customFetch) => {
|
|
80 |
innertube.session.api_version,
|
81 |
innertube.session.account_index,
|
82 |
innertube.session.player,
|
83 |
-
|
84 |
customFetch ?? innertube.session.http.fetch,
|
85 |
innertube.session.cache
|
86 |
);
|
87 |
|
88 |
-
const
|
89 |
-
const oauthData = transformSessionData(
|
90 |
|
91 |
if (!session.logged_in && oauthData) {
|
92 |
await session.oauth.init(oauthData);
|
93 |
session.logged_in = true;
|
94 |
}
|
95 |
|
96 |
-
if (session.logged_in) {
|
97 |
if (session.oauth.shouldRefreshToken()) {
|
98 |
await session.oauth.refreshAccessToken();
|
99 |
}
|
100 |
|
101 |
-
const cookieValues =
|
102 |
const oldExpiry = new Date(cookieValues.expiry_date);
|
103 |
const newExpiry = new Date(session.oauth.oauth2_tokens.expiry_date);
|
104 |
|
105 |
if (oldExpiry.getTime() !== newExpiry.getTime()) {
|
106 |
-
updateCookieValues(
|
107 |
...session.oauth.client_id,
|
108 |
...session.oauth.oauth2_tokens,
|
109 |
expiry_date: newExpiry.toISOString()
|
@@ -115,7 +123,7 @@ const cloneInnertube = async (customFetch) => {
|
|
115 |
return yt;
|
116 |
}
|
117 |
|
118 |
-
export default async function(o) {
|
119 |
let yt;
|
120 |
try {
|
121 |
yt = await cloneInnertube(
|
@@ -132,6 +140,8 @@ export default async function(o) {
|
|
132 |
} else throw e;
|
133 |
}
|
134 |
|
|
|
|
|
135 |
let useHLS = o.youtubeHLS;
|
136 |
|
137 |
// HLS playlists don't contain the av1 video format, at least with the iOS client
|
@@ -139,9 +149,20 @@ export default async function(o) {
|
|
139 |
useHLS = false;
|
140 |
}
|
141 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
142 |
let info;
|
143 |
try {
|
144 |
-
info = await yt.getBasicInfo(o.id,
|
145 |
} catch (e) {
|
146 |
if (e?.info) {
|
147 |
const errorInfo = JSON.parse(e?.info);
|
@@ -166,7 +187,7 @@ export default async function(o) {
|
|
166 |
const playability = info.playability_status;
|
167 |
const basicInfo = info.basic_info;
|
168 |
|
169 |
-
switch(playability.status) {
|
170 |
case "LOGIN_REQUIRED":
|
171 |
if (playability.reason.endsWith("bot")) {
|
172 |
return { error: "youtube.login" }
|
@@ -241,7 +262,7 @@ export default async function(o) {
|
|
241 |
} else {
|
242 |
throw new Error("couldn't fetch the HLS playlist");
|
243 |
}
|
244 |
-
}).catch(() => {});
|
245 |
|
246 |
if (!fetchedHlsManifest) {
|
247 |
return { error: "youtube.no_hls_streams" };
|
@@ -322,7 +343,7 @@ export default async function(o) {
|
|
322 |
}
|
323 |
|
324 |
const checkFormat = (format, pCodec) => format.content_length &&
|
325 |
-
|
326 |
|| format.mime_type.includes(codecList[pCodec].audioCodec));
|
327 |
|
328 |
// sort formats & weed out bad ones
|
@@ -436,6 +457,10 @@ export default async function(o) {
|
|
436 |
urls = audio.uri;
|
437 |
}
|
438 |
|
|
|
|
|
|
|
|
|
439 |
return {
|
440 |
type: "audio",
|
441 |
isAudioOnly: true,
|
@@ -462,11 +487,17 @@ export default async function(o) {
|
|
462 |
width: video.width,
|
463 |
height: video.height,
|
464 |
});
|
|
|
465 |
filenameAttributes.resolution = `${video.width}x${video.height}`;
|
466 |
filenameAttributes.extension = codecList[codec].container;
|
467 |
|
468 |
video = video.url;
|
469 |
audio = audio.url;
|
|
|
|
|
|
|
|
|
|
|
470 |
}
|
471 |
|
472 |
filenameAttributes.qualityLabel = `${resolution}p`;
|
@@ -485,4 +516,4 @@ export default async function(o) {
|
|
485 |
}
|
486 |
|
487 |
return { error: "youtube.no_matching_format" };
|
488 |
-
}
|
|
|
48 |
return;
|
49 |
|
50 |
const values = { ...cookie.values() };
|
51 |
+
const REQUIRED_VALUES = ['access_token', 'refresh_token'];
|
52 |
|
53 |
if (REQUIRED_VALUES.some(x => typeof values[x] !== 'string')) {
|
54 |
return;
|
|
|
66 |
|
67 |
const cloneInnertube = async (customFetch) => {
|
68 |
const shouldRefreshPlayer = lastRefreshedAt + PLAYER_REFRESH_PERIOD < new Date();
|
69 |
+
|
70 |
+
const rawCookie = getCookie('youtube');
|
71 |
+
const rawCookieValues = rawCookie?.values();
|
72 |
+
const cookie = rawCookie?.toString();
|
73 |
+
|
74 |
if (!innertube || shouldRefreshPlayer) {
|
75 |
innertube = await Innertube.create({
|
76 |
fetch: customFetch,
|
77 |
+
retrieve_player: !!cookie,
|
78 |
+
cookie,
|
79 |
+
po_token: rawCookieValues?.po_token,
|
80 |
+
visitor_data: rawCookieValues?.visitor_data,
|
81 |
});
|
82 |
lastRefreshedAt = +new Date();
|
83 |
}
|
|
|
88 |
innertube.session.api_version,
|
89 |
innertube.session.account_index,
|
90 |
innertube.session.player,
|
91 |
+
cookie,
|
92 |
customFetch ?? innertube.session.http.fetch,
|
93 |
innertube.session.cache
|
94 |
);
|
95 |
|
96 |
+
const oauthCookie = getCookie('youtube_oauth');
|
97 |
+
const oauthData = transformSessionData(oauthCookie);
|
98 |
|
99 |
if (!session.logged_in && oauthData) {
|
100 |
await session.oauth.init(oauthData);
|
101 |
session.logged_in = true;
|
102 |
}
|
103 |
|
104 |
+
if (session.logged_in && oauthData) {
|
105 |
if (session.oauth.shouldRefreshToken()) {
|
106 |
await session.oauth.refreshAccessToken();
|
107 |
}
|
108 |
|
109 |
+
const cookieValues = oauthCookie.values();
|
110 |
const oldExpiry = new Date(cookieValues.expiry_date);
|
111 |
const newExpiry = new Date(session.oauth.oauth2_tokens.expiry_date);
|
112 |
|
113 |
if (oldExpiry.getTime() !== newExpiry.getTime()) {
|
114 |
+
updateCookieValues(oauthCookie, {
|
115 |
...session.oauth.client_id,
|
116 |
...session.oauth.oauth2_tokens,
|
117 |
expiry_date: newExpiry.toISOString()
|
|
|
123 |
return yt;
|
124 |
}
|
125 |
|
126 |
+
export default async function (o) {
|
127 |
let yt;
|
128 |
try {
|
129 |
yt = await cloneInnertube(
|
|
|
140 |
} else throw e;
|
141 |
}
|
142 |
|
143 |
+
const cookie = getCookie('youtube')?.toString();
|
144 |
+
|
145 |
let useHLS = o.youtubeHLS;
|
146 |
|
147 |
// HLS playlists don't contain the av1 video format, at least with the iOS client
|
|
|
149 |
useHLS = false;
|
150 |
}
|
151 |
|
152 |
+
let innertubeClient = "ANDROID";
|
153 |
+
|
154 |
+
if (cookie) {
|
155 |
+
useHLS = false;
|
156 |
+
innertubeClient = "WEB";
|
157 |
+
}
|
158 |
+
|
159 |
+
if (useHLS) {
|
160 |
+
innertubeClient = "IOS";
|
161 |
+
}
|
162 |
+
|
163 |
let info;
|
164 |
try {
|
165 |
+
info = await yt.getBasicInfo(o.id, innertubeClient);
|
166 |
} catch (e) {
|
167 |
if (e?.info) {
|
168 |
const errorInfo = JSON.parse(e?.info);
|
|
|
187 |
const playability = info.playability_status;
|
188 |
const basicInfo = info.basic_info;
|
189 |
|
190 |
+
switch (playability.status) {
|
191 |
case "LOGIN_REQUIRED":
|
192 |
if (playability.reason.endsWith("bot")) {
|
193 |
return { error: "youtube.login" }
|
|
|
262 |
} else {
|
263 |
throw new Error("couldn't fetch the HLS playlist");
|
264 |
}
|
265 |
+
}).catch(() => { });
|
266 |
|
267 |
if (!fetchedHlsManifest) {
|
268 |
return { error: "youtube.no_hls_streams" };
|
|
|
343 |
}
|
344 |
|
345 |
const checkFormat = (format, pCodec) => format.content_length &&
|
346 |
+
(format.mime_type.includes(codecList[pCodec].videoCodec)
|
347 |
|| format.mime_type.includes(codecList[pCodec].audioCodec));
|
348 |
|
349 |
// sort formats & weed out bad ones
|
|
|
457 |
urls = audio.uri;
|
458 |
}
|
459 |
|
460 |
+
if (innertubeClient === "WEB" && innertube) {
|
461 |
+
urls = audio.decipher(innertube.session.player);
|
462 |
+
}
|
463 |
+
|
464 |
return {
|
465 |
type: "audio",
|
466 |
isAudioOnly: true,
|
|
|
487 |
width: video.width,
|
488 |
height: video.height,
|
489 |
});
|
490 |
+
|
491 |
filenameAttributes.resolution = `${video.width}x${video.height}`;
|
492 |
filenameAttributes.extension = codecList[codec].container;
|
493 |
|
494 |
video = video.url;
|
495 |
audio = audio.url;
|
496 |
+
|
497 |
+
if (innertubeClient === "WEB" && innertube) {
|
498 |
+
video = video.decipher(innertube.session.player);
|
499 |
+
audio = audio.decipher(innertube.session.player);
|
500 |
+
}
|
501 |
}
|
502 |
|
503 |
filenameAttributes.qualityLabel = `${resolution}p`;
|
|
|
516 |
}
|
517 |
|
518 |
return { error: "youtube.no_matching_format" };
|
519 |
+
}
|