Skip to content

Commit f722cf8

Browse files
committed
Allow downloading age restricted videos
* Bypass age restriction using `androidTvInfo` * Bump youtubei.js fix #1084 * Add more detailed error messages, including song name or url
1 parent 4364d3b commit f722cf8

File tree

3 files changed

+66
-16
lines changed

3 files changed

+66
-16
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@
134134
"node-fetch": "^2.6.8",
135135
"simple-youtube-age-restriction-bypass": "https://gitpkg.now.sh/api/pkg.tgz?url=zerodytrash/Simple-YouTube-Age-Restriction-Bypass&commit=v2.5.4",
136136
"vudio": "^2.1.1",
137-
"youtubei.js": "^3.1.1",
137+
"youtubei.js": "^4.1.0",
138138
"ytpl": "^2.3.0"
139139
},
140140
"devDependencies": {

plugins/downloader/back.js

Lines changed: 60 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const {
2020

2121
const { ipcMain, app, dialog } = require('electron');
2222
const is = require('electron-is');
23-
const { Innertube, UniversalCache, Utils } = require('youtubei.js');
23+
const { Innertube, UniversalCache, Utils, ClientType } = require('youtubei.js');
2424
const ytpl = require('ytpl'); // REPLACE with youtubei getplaylist https://github.com/LuanRT/YouTube.js#getplaylistid
2525

2626
const filenamify = require('filenamify');
@@ -48,20 +48,22 @@ let yt;
4848
let win;
4949
let playingUrl = undefined;
5050

51-
const sendError = (error) => {
51+
const sendError = (error, source) => {
5252
win.setProgressBar(-1); // close progress bar
5353
setBadge(0); // close badge
5454
sendFeedback_(win); // reset feedback
5555

56-
console.error(error);
56+
const songNameMessage = source ? `\nin ${source}` : '';
57+
const cause = error.cause ? `\n\n${error.cause.toString()}` : '';
58+
const message = `${error.toString()}${songNameMessage}${cause}`;
59+
60+
console.error(message);
5761
dialog.showMessageBox({
5862
type: 'info',
5963
buttons: ['OK'],
6064
title: 'Error in download!',
6165
message: 'Argh! Apologies, download failed…',
62-
detail: `${error.toString()} ${
63-
error.cause ? `\n\n${error.cause.toString()}` : ''
64-
}`,
66+
detail: message,
6567
});
6668
};
6769

@@ -92,20 +94,23 @@ async function downloadSong(
9294
trackId = undefined,
9395
increasePlaylistProgress = () => {},
9496
) {
97+
let resolvedName = undefined;
9598
try {
9699
await downloadSongUnsafe(
97100
url,
101+
name=>resolvedName=name,
98102
playlistFolder,
99103
trackId,
100104
increasePlaylistProgress,
101105
);
102106
} catch (error) {
103-
sendError(error);
107+
sendError(error, resolvedName || url);
104108
}
105109
}
106110

107111
async function downloadSongUnsafe(
108112
url,
113+
setName,
109114
playlistFolder = undefined,
110115
trackId = undefined,
111116
increasePlaylistProgress = () => {},
@@ -122,7 +127,11 @@ async function downloadSongUnsafe(
122127
sendFeedback('Downloading...', 2);
123128

124129
const id = getVideoId(url);
125-
const info = await yt.music.getInfo(id);
130+
let info = await yt.music.getInfo(id);
131+
132+
if (!info) {
133+
throw new Error('Video not found');
134+
}
126135

127136
const metadata = getMetadata(info);
128137
if (metadata.album === 'N/A') metadata.album = '';
@@ -133,6 +142,34 @@ async function downloadSongUnsafe(
133142
const name = `${metadata.artist ? `${metadata.artist} - ` : ''}${
134143
metadata.title
135144
}`;
145+
setName(name);
146+
147+
let playabilityStatus = info.playability_status;
148+
let bypassedResult = null;
149+
if (playabilityStatus.status === "LOGIN_REQUIRED") {
150+
// try to bypass the age restriction
151+
bypassedResult = await getAndroidTvInfo(id);
152+
playabilityStatus = bypassedResult.playability_status;
153+
154+
if (playabilityStatus.status === "LOGIN_REQUIRED") {
155+
throw new Error(
156+
`[${playabilityStatus.status}] ${playabilityStatus.reason}`,
157+
);
158+
}
159+
160+
info = bypassedResult;
161+
}
162+
163+
if (playabilityStatus.status === "UNPLAYABLE") {
164+
/**
165+
* @typedef {import('youtubei.js/dist/src/parser/classes/PlayerErrorMessage').default} PlayerErrorMessage
166+
* @type {PlayerErrorMessage}
167+
*/
168+
const errorScreen = playabilityStatus.error_screen;
169+
throw new Error(
170+
`[${playabilityStatus.status}] ${errorScreen.reason.text}: ${errorScreen.subreason.text}`,
171+
);
172+
}
136173

137174
const extension = presets[config.get('preset')]?.extension || 'mp3';
138175

@@ -252,7 +289,7 @@ async function iterableStreamToMP3(
252289

253290
return ffmpeg.FS('readFile', `${safeVideoName}.mp3`);
254291
} catch (e) {
255-
sendError(e);
292+
sendError(e, safeVideoName);
256293
} finally {
257294
releaseFFmpegMutex();
258295
}
@@ -307,7 +344,7 @@ async function writeID3(buffer, metadata, sendFeedback) {
307344
writer.addTag();
308345
return Buffer.from(writer.arrayBuffer);
309346
} catch (e) {
310-
sendError(e);
347+
sendError(e, `${metadata.artist} - ${metadata.title}`);
311348
}
312349
}
313350

@@ -482,3 +519,16 @@ const getMetadata = (info) => ({
482519
album: info.player_overlays?.browser_media_session?.album?.text,
483520
image: info.basic_info.thumbnail[0].url,
484521
});
522+
523+
// This is used to bypass age restrictions
524+
const getAndroidTvInfo = async (id) => {
525+
const innertube = await Innertube.create({
526+
clientType: ClientType.TV_EMBEDDED,
527+
generate_session_locally: true,
528+
retrieve_player: true,
529+
});
530+
const info = await innertube.getBasicInfo(id, 'TV_EMBEDDED');
531+
// getInfo 404s with the bypass, so we use getBasicInfo instead
532+
// that's fine as we only need the streaming data
533+
return info;
534+
}

yarn.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8910,19 +8910,19 @@ __metadata:
89108910
simple-youtube-age-restriction-bypass: "https://gitpkg.now.sh/api/pkg.tgz?url=zerodytrash/Simple-YouTube-Age-Restriction-Bypass&commit=v2.5.4"
89118911
vudio: ^2.1.1
89128912
xo: ^0.53.1
8913-
youtubei.js: ^3.1.1
8913+
youtubei.js: ^4.1.0
89148914
ytpl: ^2.3.0
89158915
languageName: unknown
89168916
linkType: soft
89178917

8918-
"youtubei.js@npm:^3.1.1":
8919-
version: 3.1.1
8920-
resolution: "youtubei.js@npm:3.1.1"
8918+
"youtubei.js@npm:^4.1.0":
8919+
version: 4.1.0
8920+
resolution: "youtubei.js@npm:4.1.0"
89218921
dependencies:
89228922
jintr: ^0.4.1
89238923
linkedom: ^0.14.12
89248924
undici: ^5.19.1
8925-
checksum: 1280e2ddacec3034ee8e1b398ba80662a6854e184416d3484119e7cf47b69ab2e58b4f1efdf468dcad3e50bdc7bd42b6ee66b95660ffb521efb5f0634ef60fb7
8925+
checksum: fa0090aa5b86c06a765757b0716ad9e5742c401b4fe662460db82495751e1fda3380b78f5fb916699f1707ab9b7c2783312dceac974afea3a5d101be62906bea
89268926
languageName: node
89278927
linkType: hard
89288928

0 commit comments

Comments
 (0)