From b99c2a59be334a19631508f83741befd7640f561 Mon Sep 17 00:00:00 2001 From: gatecrasher777 Date: Fri, 14 Apr 2023 18:04:31 +0200 Subject: [PATCH 1/3] Update package.json --- package.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 258e91f..09f7745 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ytcog", - "version": "2.4.3", + "version": "2.5", "description": "YouTube innertube class library for node-js; session, player, searches, channels, playlists, videos and downloads.", "main": "./lib/index.js", "repository": { @@ -25,7 +25,9 @@ "player", "session", "download", - "library" + "library", + "comments", + "subtitles" ], "author": "gatecrasher777", "license": "MIT", From 10ab2560c8688d75d36d1942afb3466012cfe6a7 Mon Sep 17 00:00:00 2001 From: gatecrasher777 Date: Fri, 14 Apr 2023 18:07:40 +0200 Subject: [PATCH 2/3] Add files via upload --- lib/channel.js | 53 ++++++++++++++++++++++++++++++++++++++++---------- lib/dl.js | 34 +++++++++++++++----------------- lib/model.js | 2 +- lib/search.js | 2 +- lib/ut.js | 2 +- lib/video.js | 15 ++++++++++++++ 6 files changed, 77 insertions(+), 31 deletions(-) diff --git a/lib/channel.js b/lib/channel.js index 7e167e6..564011a 100644 --- a/lib/channel.js +++ b/lib/channel.js @@ -25,6 +25,7 @@ class Channel extends Model { 'videos,new': 'EgZ2aWRlb3MYAyAAMAE%3D', 'videos,old': 'EgZ2aWRlb3MYAiAAMAE%3D', 'videos,views': 'EgZ2aWRlb3MYASAAMAE%3D', + 'shorts,new': 'EgZzaG9ydHPyBgUKA5oBAA%3D%3D', 'playlists,new': 'EglwbGF5bGlzdHMYAyABMAE%3D', 'playlists,updated': 'EglwbGF5bGlzdHMYBCABMAE%3D', 'channels,new': 'EghjaGFubmVscw%3D%3D', @@ -44,18 +45,17 @@ class Channel extends Model { this.thumbnail = ''; this.country = ''; this.tags = []; + this.tabs = []; this.options = { id: options.id, items: 'videos', order: 'new', quantity: 60, query: '', - period: 0, }; this.updateOptions(options); this.page = 0; this.more = ''; - this.periodExceeded = false; } // convert options into innertube parameters @@ -78,10 +78,13 @@ class Channel extends Model { let p = item.gridPlaylistRenderer; let c = item.gridChannelRenderer; let v = item.itemSectionRenderer; + let s = item.richItemRenderer; if (v) { - try { - v = v.contents[0].videoRenderer; - } catch (e) { this.debug(e); } + v = v.contents[0].videoRenderer; + } + if (s) { + s = s.content.reelItemRenderer; + if (!s) v = item.richItemRenderer.content.videoRenderer; } if (w && w.videoId) { try { @@ -98,7 +101,6 @@ class Channel extends Model { } if (w.viewCountText) video.views = ut.viewQ(w.viewCountText.simpleText); if (ts > this.latest) this.latest = ts; - if (this.options.period && (now - ts) > this.options.period) this.periodExceeded = true; video.author = this.author; video.channelId = this.id; video.channelThumb = this.thumbnail; @@ -155,7 +157,23 @@ class Channel extends Model { video.channelThumb = this.thumbnail; video.country = this.country; if (ts > this.latest) this.latest = ts; - if (this.options.period && (now - ts) > this.options.period) this.periodExceeded = true; + found.push(video); + } catch (e) { this.debug(e); } + } else if (s && s.videoId) { + try { + this.quantity++; + let ts = 0; + let video = new Video(this.session, { id: s.videoId, published: ts }); + if (s.headline) video.title = s.headline.simpleText; + if (s.viewCountText) video.views = ut.viewQ(s.viewCountText.simpleText); + let ac = s.accessibility.accessibilityData.label; + let ex = ac.indexOf(' seconds - play video'); + video.duration = parseInt(ac.substring(ex-2,ex)); + video.author = this.author; + video.channelId = this.id; + video.channelThumb = this.thumbnail; + video.country = this.country; + if (ts > this.latest) this.latest = ts; found.push(video); } catch (e) { this.debug(e); } } else if (m) { @@ -177,7 +195,6 @@ class Channel extends Model { this.page = 0; this.data = []; this.quantity = 0; - this.periodExceeded = false; this.results = []; let postData = { context: this.session.context, @@ -259,11 +276,27 @@ class Channel extends Model { if (jd && jd.text) this.joined = ut.dateTS(jd.text); let co = md.country; if (co && co.simpleText) this.country = co.simpleText; + if (tabs) { + this.tabs = []; + tabs.forEach(tb => { + let tr = tb.tabRenderer; + if (!tr) tr = tb.expandableTabRenderer; + if (tr) this.tabs.push(tr.title); + }); + } } catch (e) { this.debug(e); this.status = 'NOK'; this.reason = 'Could not find channel metadata'; } + } else if (this.options.items === 'videos' || this.options.items === 'shorts') { + try { + gi = tab.tabRenderer.content.richGridRenderer.contents; + } catch(e) { + this.debug(e); + this.status = 'OK'; + this.reason = 'No results'; + } } else { try { gi = tab.tabRenderer.content.sectionListRenderer.contents[0] @@ -279,7 +312,7 @@ class Channel extends Model { if (gi && gi.length) { try { this.process(gi, this.updated); - if (!this.periodExceeded && this.quantity < this.options.quantity) { + if (this.quantity < this.options.quantity) { await this.continued(); } else { this.status = 'OK'; @@ -326,7 +359,7 @@ class Channel extends Model { try { let gi = this.data[this.page].onResponseReceivedActions[0].appendContinuationItemsAction.continuationItems; this.process(gi, this.updated); - if (!this.periodExceeded && this.quantity < this.options.quantity && last < this.quantity) { + if (this.quantity < this.options.quantity && last < this.quantity) { await this.continued(); } } catch (e) { this.debug(e); } diff --git a/lib/dl.js b/lib/dl.js index 5cb2770..12afee0 100644 --- a/lib/dl.js +++ b/lib/dl.js @@ -225,18 +225,17 @@ class Download { // download the next section of video nextVideoSection() { if (!this.cancelled) { + let start; + let end; if (this.maxSection) { - let start = this.curSection * 1024 * 1024 * this.vSize; - if (this.curSection === this.maxSection) { - this.opts.headers.Range = `bytes=${start}-`; - } else { - let end = start + (1024 * 1024 * this.vSize) - 1; - this.opts.headers.Range = `bytes=${start}-${end}`; - } + start = this.curSection * 1024 * 1024 * this.vSize; + end = this.curSection === this.maxSection ? this.data.stream.vsize : start + (1024 * 1024 * this.vSize) - 1; } else { - delete this.opts.headers.Range; + start = 0; + end = this.data.stream.vsize - 1; } - this.vrs = miniget(this.data.stream.video_url, this.opts); + this.opts.headers.Range = `bytes=${start}-${end}`; + this.vrs = miniget(`${this.data.stream.video_url}&range=0-${this.data.stream.vsize}`, this.opts); this.tmo = setTimeout(this.timedOut.bind(this), this.timeout); this.vrs.pipe(this.vws, { end: this.curSection === this.maxSection }); this.vrs.on('data', this.dataChunk.bind(this)); @@ -261,18 +260,17 @@ class Download { // download the next section of audio nextAudioSection() { if (!this.cancelled) { + let start; + let end; if (this.maxSection) { - let start = this.curSection * 1024 * 1024 * this.aSize; - if (this.curSection === this.maxSection) { - this.opts.headers.Range = `bytes=${start}-`; - } else { - let end = start + (1024 * 1024 * this.aSize) - 1; - this.opts.headers.Range = `bytes=${start}-${end}`; - } + start = this.curSection * 1024 * 1024 * this.aSize; + end = this.curSection === this.maxSection ? this.data.stream.asize : start + (1024 * 1024 * this.aSize) - 1; } else { - delete this.opts.headers.Range; + start = 0; + end = this.data.stream.asize - 1; } - this.ars = miniget(this.data.stream.audio_url, this.opts); + this.opts.headers.Range = `bytes=${start}-${end}`; + this.ars = miniget(`${this.data.stream.audio_url}&range=0-${this.data.stream.asize}`, this.opts); this.tmo = setTimeout(this.timedOut.bind(this), this.timeout); this.ars.pipe(this.aws, { end: this.curSection === this.maxSection }); this.ars.on('data', this.dataChunk.bind(this)); diff --git a/lib/model.js b/lib/model.js index 49d7fad..3fcadde 100644 --- a/lib/model.js +++ b/lib/model.js @@ -116,7 +116,7 @@ class Model { } catch (e) { this.debug(e); this.status = 'NOK'; - this.reason = `Get request error. Status Code: ${e.statuscode}`; + this.reason = `Get request error. Status Code: ${e.statusCode}`; } return body; } diff --git a/lib/search.js b/lib/search.js index b82a96e..c399fc6 100644 --- a/lib/search.js +++ b/lib/search.js @@ -27,7 +27,7 @@ class Search extends Model { this.options = { query: 'video', order: 'relevance', - period: 'day', + period: 'any', items: 'videos', duration: 'any', features: '', diff --git a/lib/ut.js b/lib/ut.js index d21fdb3..7b7a512 100644 --- a/lib/ut.js +++ b/lib/ut.js @@ -201,7 +201,7 @@ class utClass { } // returns length of object to be ignored with start/end characters. - // objects muct be less than 1024 characters. + // objects must be less than 1024 characters. // If object is regex, will test if regex is valid. // if invalid regex or unterminated object, will report a 0 object length. ignore(body, spos, start, end) { diff --git a/lib/video.js b/lib/video.js index 75c3626..7c95414 100644 --- a/lib/video.js +++ b/lib/video.js @@ -1035,6 +1035,21 @@ class Video extends Model { return body; } + async fetchEmbed() { + let hdrs = { + referer: `https://www.youtube.com/watch?v=${this.options.id}`, + }; + let body = await this.httpsGet( + `https://www.youtube.com/oembed?format=json&url=https://www.youtube.com/watch?v=${this.options.id}`, + { + path: `/oembed?format=json&url=https://www.youtube.com/watch?v=${this.options.id}`, + headers: hdrs, + }, + true, + ); + return body; + } + // initiate comments request async fetchComments(options, proceed = true) { this.commentOptions = { ...this.commentOptions, ...options }; From 430e23243ea7011f9198f10307993598311a2879 Mon Sep 17 00:00:00 2001 From: gatecrasher777 Date: Fri, 14 Apr 2023 18:11:51 +0200 Subject: [PATCH 3/3] Add files via upload --- examples/channel_test.js | 245 ++++++++++++++++++++++----------------- examples/search_test.js | 121 ++++++++++--------- examples/session_test.js | 3 + examples/video_test.js | 139 +++++++++++++--------- 4 files changed, 295 insertions(+), 213 deletions(-) diff --git a/examples/channel_test.js b/examples/channel_test.js index 98b87cc..85bc383 100644 --- a/examples/channel_test.js +++ b/examples/channel_test.js @@ -16,21 +16,149 @@ let app = { // proxy agent string proxy: '', // info fields to ignore - ignore: ['cookie', 'userAgent', 'options', 'sapisid', 'status', 'reason', 'cancelled'], + ignore: ['cookie', 'userAgent', 'options', 'sapisid', 'status', 'reason', 'cancelled', 'debugOn','commentOptions','commentOrder','canEmbed','isLive'], test_options: { // any channel id id: 'UCG5qGWdu8nIRZqJ_GgDwQ-w', - // videos, playlists, channels, about, search + // videos, shorts, playlists, channels, about, search items: 'about', // new (videos/playlists), old (videos), views (videos), updated (playlists) order: 'new', // number of results to fetch. You can get further videos (if available) with subsequent channel.continued() calls - quantity: 60, + quantity: 30, // items=search only - query: '', + query: 'after:'+ new Date(ut.now()-1000*60*60*24*7).toISOString().substring(0,10), }, }; +async function runAbout(channel) { + console.log('\nFetch channel profile data (items: about)'); + await channel.fetch(); + console.log(`Channel status: ${channel.status} (${channel.reason})`); + console.log('Raw Channel json to ./examples/channelAbout.json'); + fs.writeFileSync('./examples/channelAbout.json', ut.jsp(channel.data), 'utf8'); + if (channel.status === 'OK') { + console.log(`\nChannel info for ${channel.author} to ./examples/channel_about.json`); + fs.writeFileSync('./examples/channel_about.json', ut.jsp(channel.info(app.ignore)), 'utf8'); + } +} + +async function runVideos(channel) { + console.log('\nFetch latest Channel videos'); + await channel.fetch({ items: 'videos' }); + console.log(`Channel status: ${channel.status} (${channel.reason})`); + console.log('Raw Channel json to ./examples/channelVideos.json'); + fs.writeFileSync('./examples/channelVideos.json', ut.jsp(channel.data), 'utf8'); + if (channel.status === 'OK') { + console.log(`\nFound ${channel.results.length} results for ${channel.author}`); + console.log('Want some more? Will continue...'); + await channel.continued(); + fs.writeFileSync('./examples/channelVideos.json', ut.jsp(channel.data), 'utf8'); + console.log(`Channel status: ${channel.status} (${channel.reason})`); + if (channel.status === 'OK') { + console.log(`\nFound ${channel.results.length} videos for ${channel.author}`); + console.log('\nChannel info/videos to ./examples/channel_videos.json'); + let output = { + channel: channel.info(app.ignore), + videos: [], + }; + channel.results.forEach(video => { + output.videos.push(video.info(app.ignore)); + }); + fs.writeFileSync('./examples/channel_videos.json', ut.jsp(output), 'utf8'); + } + } +} + +async function runShorts(channel) { + console.log('\nFetch latest Channel shorts'); + await channel.fetch({ items: 'shorts' }); + console.log(`Channel status: ${channel.status} (${channel.reason})`); + console.log('Raw Channel json to ./examples/channelShorts.json'); + fs.writeFileSync('./examples/channelShorts.json', ut.jsp(channel.data), 'utf8'); + if (channel.status === 'OK') { + console.log(`\nFound ${channel.results.length} results for ${channel.author}`); + console.log('Want some more? Will continue...'); + await channel.continued(); + fs.writeFileSync('./examples/channelShorts.json', ut.jsp(channel.data), 'utf8'); + console.log(`Channel status: ${channel.status} (${channel.reason})`); + if (channel.status === 'OK') { + console.log(`\nFound ${channel.results.length} shorts for ${channel.author}`); + console.log('\nChannel info/shorts to ./examples/channel_shorts.json'); + let output = { + channel: channel.info(app.ignore), + shorts: [], + }; + channel.results.forEach(video => { + output.shorts.push(video.info(app.ignore)); + }); + fs.writeFileSync('./examples/channel_shorts.json', ut.jsp(output), 'utf8'); + } + } +} + +async function runPlaylists(channel) { + console.log('\n\nFetch 30 channel playlists (most recently updated)...'); + await channel.fetch({ items: 'playlists', order: 'updated', quantity: 30 }); + console.log('Raw Channel json to ./examples/channelPlaylists.json'); + fs.writeFileSync('./examples/channelPlaylists.json', ut.jsp(channel.data), 'utf8'); + console.log(`Channel status: ${channel.status} (${channel.reason})`); + if (channel.status === 'OK') { + console.log(`\nFound ${channel.results.length} playlist results for ${channel.author}`); + console.log('\nChannel info/playlists to ./examples/channel_playlists.json'); + let output = { + channel: channel.info(app.ignore), + playlists: [], + }; + channel.results.forEach(item => { + output.playlists.push(item.info(app.ignore)); + }); + fs.writeFileSync('./examples/channel_playlists.json', ut.jsp(output), 'utf8'); + } +} + +async function runChannels(channel) { + console.log('\n\nFetch 20 related channels...'); + await channel.fetch({ items: 'channels', order: 'new', quantity: 20 }); + console.log('Raw Channel json to ./examples/channelChannels.json'); + fs.writeFileSync('./examples/channelChannels.json', ut.jsp(channel.data), 'utf8'); + console.log(`Channel status: ${channel.status} (${channel.reason})`); + if (channel.status === 'OK') { + console.log(`\nFound ${channel.results.length} related channels for ${channel.author}`); + console.log('\nChannel info/channels to ./examples/channel_channels.json'); + let output = { + channel: channel.info(app.ignore), + channels: [], + }; + channel.results.forEach(item => { + output.channels.push(item.info(app.ignore)); + }); + fs.writeFileSync('./examples/channel_channels.json', ut.jsp(output), 'utf8'); + + } +} + +async function runSearch(channel) { + console.log(`\n\nSearch for up to 100 videos/shorts in the channel with query: "${app.test_options.query}"`); + await channel.fetch({ items: 'search', quantity: 100 }); + console.log('Raw Channel json to ./examples/channelSearch.json'); + fs.writeFileSync('./examples/channelSearch.json', ut.jsp(channel.data), 'utf8'); + console.log(`Channel status: ${channel.status} (${channel.reason})`); + if (channel.status === 'OK') { + console.log(`\nFound ${channel.results.length} search results in ${channel + .author} for "${channel.options.query}"`); + console.log('\nChannel search info/videos to ./examples/channel_search.json'); + let output = { + channel: channel.info(app.ignore), + videos: [], + }; + channel.results.forEach(item => { + output.videos.push(item.info(app.ignore)); + }); + fs.writeFileSync('./examples/channel_search.json', ut.jsp(output), 'utf8'); + } +} + async function run() { let session = new ytcog.Session(app.cookie, app.userAgent, app.proxy); await session.fetch(); @@ -38,106 +166,15 @@ async function run() { if (session.status === 'OK') { let channel = new ytcog.Channel(session, app.test_options); channel.debugOn = true; - console.log('\nFetch channel profile data (items: about)'); - await channel.fetch(); - console.log('Raw Channel json to ./examples/channelA.json'); - fs.writeFileSync('./examples/channelA.json', ut.jsp(channel.data), 'utf8'); - console.log(`Channel status: ${channel.status} (${channel.reason})`); - if (channel.status === 'OK') { - console.log('\nFetch latest Channel videos'); - await channel.fetch({ items: 'videos' }); - console.log(`Channel status: ${channel.status} (${channel.reason})`); - if (channel.status === 'OK') { - console.log(`\nFound ${channel.results.length} results for ${channel.author}`); - console.log('Want some more? Will continue...'); - await channel.continued(); - console.log(`Channel status: ${channel.status} (${channel.reason})`); - if (channel.status === 'OK') { - console.log(`\nFound ${channel.results.length} video results for ${channel.author}`); - console.log('\nChannel info/results to ./examples/channel_results.json'); - let output1 = { - channel: channel.info(app.ignore), - results: [], - }; - channel.results.forEach(video => { - output1.results.push(video.info(app.ignore)); - }); - fs.writeFileSync('./examples/channel_results.json', ut.jsp(output1), 'utf8'); - console.log('Raw Channel json to ./examples/channel.json'); - fs.writeFileSync('./examples/channel.json', ut.jsp(channel.data), 'utf8'); - console.log('\n\nFetch 30 channel playlists (most recently updated)...'); - await channel.fetch({ items: 'playlists', order: 'updated', quantity: 30 }); - console.log(`Channel status: ${channel.status} (${channel.reason})`); - if (channel.status === 'OK') { - console.log(`\nFound ${channel.results.length} playlist results for ${channel.author}`); - console.log('\nChannel info/results to ./examples/channel_playlists.json'); - let output2 = { - channel: channel.info(app.ignore), - results: [], - }; - channel.results.forEach(item => { - output2.results.push(item.info(app.ignore)); - }); - fs.writeFileSync('./examples/channel_playlists.json', ut.jsp(output2), 'utf8'); - console.log('Raw Channel json to ./examples/channelP.json'); - fs.writeFileSync('./examples/channelP.json', ut.jsp(channel.data), 'utf8'); - console.log('\n\nFetch 20 related channels...'); - await channel.fetch({ items: 'channels', order: 'new', quantity: 20 }); - console.log(`Channel status: ${channel.status} (${channel.reason})`); - if (channel.status === 'OK') { - console.log(`\nFound ${channel.results.length} channel results for ${channel.author}`); - console.log('\nChannel info/results to ./examples/channel_channels.json'); - let output3 = { - channel: channel.info(app.ignore), - results: [], - }; - channel.results.forEach(item => { - output3.results.push(item.info(app.ignore)); - }); - fs.writeFileSync('./examples/channel_channels.json', ut.jsp(output3), 'utf8'); - console.log('Raw Channel json to ./examples/channelC.json'); - fs.writeFileSync('./examples/channelC.json', ut.jsp(channel.data), 'utf8'); - console.log('\n\nSearch for 20 videos in the channel ...'); - await channel.fetch({ items: 'search', query: 'Chelsea', quantity: 20 }); - console.log(`Channel status: ${channel.status} (${channel.reason})`); - if (channel.status === 'OK') { - console.log(`\nFound ${channel.results.length} search results in ${channel - .author} for ${channel.options.query}`); - console.log('\nChannel info/results to ./examples/channel_search.json'); - let output4 = { - channel: channel.info(app.ignore), - results: [], - }; - channel.results.forEach(item => { - output4.results.push(item.info(app.ignore)); - }); - fs.writeFileSync('./examples/channel_search.json', ut.jsp(output4), 'utf8'); - console.log('Raw Channel json to ./examples/channelS.json'); - fs.writeFileSync('./examples/channelS.json', ut.jsp(channel.data), 'utf8'); - } else { - console.log('Raw Channel json to ./examples/channelS.json'); - fs.writeFileSync('./examples/channelS.json', ut.jsp(channel.data), 'utf8'); - } - } else { - console.log('Raw Channel json to ./examples/channelC.json'); - fs.writeFileSync('./examples/channelC.json', ut.jsp(channel.data), 'utf8'); - } - } else { - console.log('Raw Channel json to ./examples/channelP.json'); - fs.writeFileSync('./examples/channelP.json', ut.jsp(channel.data), 'utf8'); - } - } else { - console.log('Raw Channel json to ./examples/channel.json'); - fs.writeFileSync('./examples/channel.json', ut.jsp(channel.data), 'utf8'); - } - } else { - console.log('Raw Channel json to ./examples/channel.json'); - fs.writeFileSync('./examples/channel.json', ut.jsp(channel.data), 'utf8'); - } - } else { - console.log('Raw Channel json to ./examples/channelA.json'); - fs.writeFileSync('./examples/channelA.json', ut.jsp(channel.data), 'utf8'); - } + await runAbout(channel); + await runVideos(channel); + await runShorts(channel); + await runPlaylists(channel); + await runChannels(channel); + await runSearch(channel); + console.log('Session complete.') + } else { + console.log('Session failed.') } } diff --git a/examples/search_test.js b/examples/search_test.js index acfd411..5d535c9 100644 --- a/examples/search_test.js +++ b/examples/search_test.js @@ -33,8 +33,70 @@ let app = { // live, 4k, hd, subtitles, cc, 360, vr180, 3d, hdr, location, purchased features: '', }, + ignore : ['cookie', 'userAgent', 'debugOn', 'options', 'sapisid','commentOptions','commentOrder', + 'status', 'reason', 'cancelled','periodExceeded'], }; +async function runVideos(search) { + console.log(`\n\nSearch for ${app.test_options.quantity} videos with query: "${app.test_options.query}"`); + await search.fetch(); + console.log('Raw search json saved to ./examples/searchVideos.json'); + fs.writeFileSync('./examples/searchVideos.json', ut.jsp(search.data), 'utf8'); + console.log(`\nSearch status: ${search.status} (${search.reason})`); + if (search.status === 'OK') { + console.log(`Found ${search.results.length} videos for "${search.options.query}"`); + console.log('\nSearch info/videos saved to ./examples/search_videos.json'); + let output = { + search: search.info(app.ignore), + videos: [], + }; + search.results.forEach(item => { + output.videos.push(item.info(app.ignore)); + }); + fs.writeFileSync('./examples/search_videos.json', ut.jsp(output), 'utf8'); + } +} + +async function runPlaylists(search) { + console.log('\n\nSearch for 30 playlists'); + await search.fetch({ items: 'playlists', quantity: 30 }); + console.log('Raw search json saved to ./examples/searchPlaylists.json'); + fs.writeFileSync('./examples/searchPlaylists.json', ut.jsp(search.data), 'utf8'); + console.log(`\nSearch status: ${search.status} (${search.reason})`); + if (search.status === 'OK') { + console.log(`Found ${search.results.length} results for "${search.options.query}"`); + console.log('\nSearch info/results saved to ./examples/search_playlists.json'); + let output = { + search: search.info(app.ignore), + playlists: [], + }; + search.playlists.forEach(item => { + output.playlists.push(item.info(app.ignore)); + }); + fs.writeFileSync('./examples/search_playlists.json', ut.jsp(output), 'utf8'); + } +} + +async function runChannels(search) { + console.log('\n\nSearch for 50 channels'); + await search.fetch({ items: 'channels', quantity: 50 }); + console.log('Raw search json saved to ./examples/searchChannels.json'); + fs.writeFileSync('./examples/searchChannels.json', ut.jsp(search.data), 'utf8'); + console.log(`\nSearch status: ${search.status} (${search.reason})`); + if (search.status === 'OK') { + console.log(`Found ${search.results.length} channels for "${search.options.query}"`); + console.log('\nSearch info/channels saved to ./examples/search_channels.json'); + let output = { + search: search.info(['cookie', 'userAgent', 'sapisid']), + channels: [], + }; + search.results.forEach(item => { + output.channels.push(item.info(app.ignore)); + }); + fs.writeFileSync('./examples/search_channels.json', ut.jsp(output), 'utf8'); + } +} + async function run() { let session = new ytcog.Session(app.cookie, app.userAgent, app.proxy); await session.fetch(); @@ -42,59 +104,12 @@ async function run() { if (session.status === 'OK') { let search = new ytcog.Search(session, app.test_options); search.debugOn = true; - await search.fetch(); - console.log(`\nSearch status: ${search.status} (${search.reason})`); - if (search.status === 'OK') { - console.log(`Found ${search.results.length} results for "${search.options.query}"`); - console.log('\nSearch info/results saved to ./examples/search_results.json'); - let output1 = { - search: search.info(['cookie', 'userAgent', 'sapisid']), - results: [], - }; - search.results.forEach(item => { - output1.results.push(item.info(['cookie', 'userAgent', 'options', 'sapisid', - 'status', 'reason', 'cancelled'])); - }); - fs.writeFileSync('./examples/search_results.json', ut.jsp(output1), 'utf8'); - console.log('Raw search json saved to ./examples/search.json'); - fs.writeFileSync('./examples/search.json', ut.jsp(search.data), 'utf8'); - console.log('\n\nSearch for 50 channels'); - await search.fetch({ items: 'channels', quantity: 50 }); - console.log(`\nSearch status: ${search.status} (${search.reason})`); - if (search.status === 'OK') { - console.log(`Found ${search.results.length} results for "${search.options.query}"`); - console.log('\nSearch info/results saved to ./examples/search_channels.json'); - let output2 = { - search: search.info(['cookie', 'userAgent', 'sapisid']), - results: [], - }; - search.results.forEach(item => { - output2.results.push(item.info(['cookie', 'userAgent', 'options', 'sapisid', - 'status', 'reason', 'cancelled'])); - }); - fs.writeFileSync('./examples/search_channels.json', ut.jsp(output2), 'utf8'); - console.log('Raw search json saved to ./examples/searchC.json'); - fs.writeFileSync('./examples/searchC.json', ut.jsp(search.data), 'utf8'); - console.log('\n\nSearch for 30 playlists'); - await search.fetch({ items: 'playlists', quantity: 30 }); - console.log(`\nSearch status: ${search.status} (${search.reason})`); - if (search.status === 'OK') { - console.log(`Found ${search.results.length} results for "${search.options.query}"`); - console.log('\nSearch info/results saved to ./examples/search_playlists.json'); - let output3 = { - search: search.info(['cookie', 'userAgent', 'sapisid']), - results: [], - }; - search.results.forEach(item => { - output3.results.push(item.info(['cookie', 'userAgent', 'options', 'sapisid', - 'status', 'reason', 'cancelled'])); - }); - fs.writeFileSync('./examples/search_playlists.json', ut.jsp(output3), 'utf8'); - console.log('Raw search json saved to ./examples/searchP.json'); - fs.writeFileSync('./examples/searchP.json', ut.jsp(search.data), 'utf8'); - } - } - } + await runVideos(search); + await runPlaylists(search); + await runChannels(search); + console.log('Session complete.') + } else { + console.log('Session failed.') } } diff --git a/examples/session_test.js b/examples/session_test.js index 4be9706..3ddddcb 100644 --- a/examples/session_test.js +++ b/examples/session_test.js @@ -26,6 +26,9 @@ async function run() { session.debugOn = true; await session.fetch(); console.log(`Session status: ${session.status} (${session.reason})`); + fs.writeFileSync('./examples/session_info.json', ut.jsp(session.info()), 'utf8'); + fs.writeFileSync('./examples/session.json', ut.jsp(session.data), 'utf8'); + fs.writeFileSync('./examples/player.js', session.player.data, 'utf8'); if (session.status === 'OK') { console.log(`\nSession key: ${session.key}`); console.log(`Visitor id: ${session.context.client.visitorData}`); diff --git a/examples/video_test.js b/examples/video_test.js index ec176a1..deb6bf1 100644 --- a/examples/video_test.js +++ b/examples/video_test.js @@ -17,7 +17,7 @@ let app = { // proxy agent string proxy: '', // info fields to ignore - ignore: ['cookie', 'userAgent', 'options', 'sapisid', 'status', 'reason', 'cancelled'], + ignore: ['cookie', 'userAgent', 'options', 'sapisid', 'status', 'reason', 'cancelled','debugOn','commentOptions','commentOrder','defaultCaptions','formnats'], // video options test_options: { // any video id @@ -29,7 +29,7 @@ let app = { // supply a optional filename, with placeholders, but do not include an extension. filename: '${author}_${datetime}_${title}_${id}_${videoQuality}_${videoCodec}_${audioCodec}', // any, mp4, webm, mkv - container: 'any', + container: 'mkv', // desired quality: highest, 1080p, 720p, 480p, medium, 360p, 240p, 144p, lowest videoQuality: '1080p', // desired audio quality: highest, medium, lowest @@ -42,8 +42,10 @@ let app = { audioFormat: -1, // Metadata to add to downloaded media metadata: 'author,title,published', - // make srt subtitles if captions are available, chose language codes, comma separated + // make subtitles if captions are available, chose language codes, comma separated subtitles: 'en,es,ja', + // chose the subtitle file format, one or more of srt, vtt or ttml + subtitleFormat: 'srt,vtt,ttml', // supply a callback for download progress; progress: (prg, siz, tot) => { app.downloaded += siz; @@ -60,70 +62,95 @@ let app = { }, }; -async function run() { - let session = new ytcog.Session(app.cookie, app.userAgent, app.proxy); - await session.fetch(); - console.log(`Session status: ${session.status} (${session.reason})`); - if (session.status === 'OK') { - let video = new ytcog.Video(session, app.test_options); - video.debugOn = true; - console.log('\nFetch video metadata and streams'); - await video.fetch(); - console.log(`Video status: ${video.status} (${video.reason})`); - if (video.status === 'OK') { - console.log('\nVideo info saved to ./examples/video_info.json'); - let output = video.info(app.ignore); - fs.writeFileSync('./examples/video_info.json', ut.jsp(output), 'utf8'); - console.log('Video json to ./examples/video.json'); - fs.writeFileSync('./examples/video.json', ut.jsp(video.data), 'utf8'); - console.log('\nAvailable media streams:'); - console.log(video.streamInfo); - console.log(`\nStreams expire in ${ut.secDur(video.timeToExpiry, 'hms')}`); - console.log(video.captions); - console.log('\nDownloading comments (if available)'); - if (await video.hasComments()) { - console.log(`Number of Comments: ${video.commentCount}`); - await video.commentsText(app.comment_options); - console.log(`\n\nVideo status: ${video.status} (${video.reason})`); - if (video.status === 'OK') console.log(`Success - comments saved to ${video.cfn}`); - } else { - console.log(`\n\nVideo status: ${video.status} (${video.reason})`); - } - console.log('\nVideo comments json saved to ./examples/videoComments.json'); - fs.writeFileSync('./examples/videoComments.json', ut.jsp(video.commentData), 'utf8'); - console.log(`\nDownloading test video using given test options`); - await video.download(); +async function runOnline(video) { + console.log('\nCheck online status via preview image'); + await video.imageOnline(); + console.log(`Video status: ${video.status} (${video.reason})`); +} + +async function runInfo(video) { + console.log('\nFetch video metadata and streams'); + await video.fetch(); + console.log(`Video status: ${video.status} (${video.reason})`); + console.log('\nVideo info saved to ./examples/video_info.json'); + let output = video.info(app.ignore); + fs.writeFileSync('./examples/video_info.json', ut.jsp(output), 'utf8'); + console.log('Video json to ./examples/video.json'); + fs.writeFileSync('./examples/video.json', ut.jsp(video.data), 'utf8'); + if (video.status === 'OK') { + console.log('\nAvailable media streams:'); + console.log(video.streamInfo); + console.log(`\nStreams expire in ${ut.secDur(video.timeToExpiry, 'hms')}`); + console.log(video.captions); + } +} + +async function runComments(video) { + console.log('\nDownloading comments (if available)'); + if (await video.hasComments()) { + console.log(`Number of Comments: ${video.commentCount}`); + await video.commentsText(app.comment_options); + console.log(`\n\nVideo status: ${video.status} (${video.reason})`); + if (video.status === 'OK') console.log(`Success - comments saved to ${video.cfn}`); + } else { + console.log(`\n\nVideo status: ${video.status} (${video.reason})`); + } + console.log('\nVideo comments json saved to ./examples/videoComments.json'); + fs.writeFileSync('./examples/videoComments.json', ut.jsp(video.commentData), 'utf8'); +} + +async function runDownloads(video) { + console.log(`\nDownloading test video using given test options`); + await video.download(); + console.log(`\n\nVideo status: ${video.status} (${video.reason})`); + if (video.downloaded) { + console.log(`Success - video saved to ${video.fn}`); + console.log(`\nDownloading video using video stream 3 and audio stream 1 (mp4 only)`); + app.downloaded = 0; + await video.download({ videoFormat: 3, audioFormat: 1 }); + console.log(`\n\nVideo status: ${video.status} (${video.reason})`); + if (video.downloaded) { + console.log(`Success - custom video saved to ${video.fn}`); + console.log(`\nDownloading video only`); + video.updateOptions(app.test_options); + app.downloaded = 0; + await video.download({ audioQuality: 'none' }); console.log(`\n\nVideo status: ${video.status} (${video.reason})`); if (video.downloaded) { - console.log(`Success - video saved to ${video.fn}`); - console.log(`\nDownloading video using video stream 3 and audio stream 1 (mp4 only)`); + console.log(`Success - video only saved to ${video.fn}`); + console.log(`\nDownloading audio only`); + video.updateOptions(app.test_options); app.downloaded = 0; - await video.download({ videoFormat: 3, audioFormat: 1 }); + await video.download({ videoQuality: 'none', container: 'mp3' }); console.log(`\n\nVideo status: ${video.status} (${video.reason})`); if (video.downloaded) { - console.log(`Success - custom video saved to ${video.fn}`); - console.log(`\nDownloading video only`); - video.updateOptions(app.test_options); - app.downloaded = 0; - await video.download({ audioQuality: 'none' }); - console.log(`\n\nVideo status: ${video.status} (${video.reason})`); - if (video.downloaded) { - console.log(`Success - video only saved to ${video.fn}`); - console.log(`\nDownloading audio only`); - video.updateOptions(app.test_options); - app.downloaded = 0; - await video.download({ videoQuality: 'none', container: 'mp3' }); - console.log(`\n\nVideo status: ${video.status} (${video.reason})`); - if (video.downloaded) { - console.log(`Success - audio only (mp3) saved to ${video.fn}`); - } - } + console.log(`Success - audio only (mp3) saved to ${video.fn}`); } } } } } +async function run() { + let session = new ytcog.Session(app.cookie, app.userAgent, app.proxy); + await session.fetch(); + console.log(`Session status: ${session.status} (${session.reason})`); + if (session.loggedIn) { + console.log('\nYour are logged into YouTube. Enjoy.'); + } else { + console.log('\nYou are not logged into YouTube. As a consequence:' + + ' \nSome downloads in ytcog may not work \nYou may experience rate limiting and age-restrictions.'); + } + if (session.status === 'OK') { + let video = new ytcog.Video(session, app.test_options); + video.debugOn = true; + runOnline(video); + runInfo(video); + runComments(video); + runDownloads(video); + } +} + if (process.argv.length === 2) { run(); } else if (process.argv.length === 3) {