@@ -20,7 +20,7 @@ const {
2020
2121const { ipcMain, app, dialog } = require ( 'electron' ) ;
2222const is = require ( 'electron-is' ) ;
23- const { Innertube, UniversalCache, Utils } = require ( 'youtubei.js' ) ;
23+ const { Innertube, UniversalCache, Utils, ClientType } = require ( 'youtubei.js' ) ;
2424const ytpl = require ( 'ytpl' ) ; // REPLACE with youtubei getplaylist https://github.com/LuanRT/YouTube.js#getplaylistid
2525
2626const filenamify = require ( 'filenamify' ) ;
@@ -48,20 +48,22 @@ let yt;
4848let win ;
4949let 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
107111async 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+ }
0 commit comments