-
-
Save artzub/02b1b91e5c16a76bdf6c4710dbc105f0 to your computer and use it in GitHub Desktop.
| function findPlaylist(compare) { | |
| let pageToken; | |
| while(true) { | |
| const list = YouTube.Playlists.list(['snippet'], { | |
| "maxResults": 50, | |
| "mine": true, | |
| ...(pageToken && { pageToken }) | |
| }); | |
| const item = list.items.find((row) => compare && compare(row)); | |
| if (item) { | |
| return item; | |
| } | |
| if (!list.nextPageToken) { | |
| return null; | |
| } | |
| pageToken = list.nextPageToken; | |
| } | |
| } | |
| function addPlaylist() { | |
| const title = `wl__${(new Date().getMonth()) + 1}`; | |
| const item = findPlaylist(({ snippet }) => snippet.title === title); | |
| if (item) { | |
| return item; | |
| } | |
| return YouTube.Playlists.insert( | |
| { | |
| 'snippet': { | |
| title, | |
| }, | |
| 'status': { | |
| 'privacyStatus': 'private' | |
| } | |
| }, | |
| 'snippet,status' | |
| ); | |
| } | |
| function insertVideo(playlistId, videoId) { | |
| return YouTube.PlaylistItems.insert( | |
| { | |
| "snippet": { | |
| playlistId, | |
| "resourceId": { | |
| "kind": "youtube#video", | |
| videoId | |
| } | |
| } | |
| }, | |
| 'snippet' | |
| ); | |
| } | |
| function run() { | |
| const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); | |
| const sheet = spreadsheet.getSheets()[0]; | |
| const lastRow = sheet.getLastRow(); | |
| console.log({ lastRow }); | |
| if (lastRow < 1) { | |
| return; | |
| } | |
| // 199 because 1 request for insterting playlist | |
| const lastIndex = lastRow > 199 ? 199 : lastRow; | |
| console.log(`get range: A1:A${lastIndex}`); | |
| spreadsheet.toast(`get range: A1:A${lastIndex}`, "fill playlist"); | |
| let range = sheet.getRange(`A1:A${lastIndex}`); | |
| const rows = range.getValues().flat().filter(Boolean); | |
| console.log({ willProcessing: rows.length }); | |
| if (rows.length) { | |
| let lastAdded = 0; | |
| let error; | |
| let lastVideo; | |
| try { | |
| const pl = addPlaylist(); | |
| rows.forEach((videoId, index, arr) => { | |
| if (!videoId) { | |
| return; | |
| } | |
| try { | |
| insertVideo(pl.id, videoId, arr.length - index); | |
| lastVideo = videoId; | |
| } | |
| catch(err) { | |
| if (!err.message.includes('Video not found')) { | |
| throw err; | |
| } | |
| console.log('video not found', videoId); | |
| lastVideo = videoId; | |
| } | |
| lastAdded = index + 1; | |
| }); | |
| } catch(err) { | |
| error = err; | |
| } | |
| console.log({ lastAdded, lastVideo }); | |
| if (lastAdded > 0) { | |
| if (!lastVideo) { | |
| lastVideo = sheet.getRange('B1:B1').getValue(); | |
| } | |
| const row = sheet.getLastRow() || 1; | |
| if (lastAdded >= row) { | |
| lastAdded = row - 1; | |
| } | |
| console.log({ row, lastAdded }); | |
| if (lastAdded > 0) { | |
| sheet.deleteRows(1, lastAdded); | |
| } else { | |
| sheet.getRange('A1:A1').setValue(''); | |
| } | |
| sheet.getRange('B1:B1').setValue(lastVideo); | |
| } | |
| if (error) { | |
| throw error; | |
| } | |
| } | |
| } | |
| function init() { | |
| const spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); | |
| spreadsheet.removeMenu('YouTube'); | |
| spreadsheet.addMenu('YouTube', [ | |
| { | |
| name: 'fill playlist', | |
| functionName: 'run', | |
| } | |
| ]); | |
| } | |
| function onOpen() { | |
| init(); | |
| } |
| const targetRootId = 'contents'; | |
| const lastVideoSelector = 'ytd-rich-grid-row:last-of-type ytd-rich-item-renderer:last-child'; | |
| const allVideosSelector = `.ytd-rich-grid-renderer#${targetRootId} ytd-rich-grid-media`; | |
| const rootNodeSelector = `ytd-rich-grid-renderer > #${targetRootId}`; | |
| const delay = (ms = 100) => new Promise((resolve) => { | |
| setTimeout(() => { | |
| resolve(true); | |
| }, ms) | |
| }); | |
| (async function() { | |
| let neededVideoId = prompt('Enter id of the last video', ''); | |
| if (!neededVideoId) { | |
| alert('The last vidoe id can not be empty'); | |
| return; | |
| } | |
| const checkWatched = async () => { | |
| await delay(3e3); | |
| console.log('find video by id:', neededVideoId); | |
| const found = document.querySelector(`a[href^="/watch?v=${neededVideoId}"]`); | |
| if (found) { | |
| console.log('found, stop observer'); | |
| observer.disconnect(); | |
| stageSecond(); | |
| return; | |
| } | |
| const lastVideo = document.querySelector(lastVideoSelector); | |
| console.log('last video:', lastVideo); | |
| if (lastVideo) { | |
| lastVideo.scrollIntoView(); | |
| } | |
| }; | |
| let timer; | |
| const runWaiter = () => { | |
| if (timer) { | |
| clearTimeout(timer); | |
| } | |
| timer = setTimeout(checkWatched, 2e3); | |
| }; | |
| const stageSecond = async () => { | |
| let videos = Array.from(document.querySelectorAll(allVideosSelector)); | |
| if (neededVideoId) { | |
| const index = videos.findIndex(item => item.data.videoId === neededVideoId); | |
| videos.splice(index, videos.length); | |
| } | |
| videos = videos | |
| .filter(item => !item.data.isWatched) | |
| .reverse() | |
| .map(video => `<tr><td>${video.data.videoId}</td></tr>`) | |
| ; | |
| document.body.innerHTML = ` | |
| <pre style="background: white; color: black"> | |
| ${videos.join('\n')} | |
| </pre> | |
| `; | |
| }; | |
| const itemsContainer = document.querySelector(rootNodeSelector); | |
| const mutationListener = (mutationList, observer) => { | |
| const list = mutationList.filter(item => item.target.id === targetRootId); | |
| if (list.length > 0) { | |
| runWaiter(); | |
| } | |
| }; | |
| const observer = new MutationObserver(mutationListener); | |
| observer.observe(itemsContainer, { childList: true, subtree: true }); | |
| await checkWatched(); | |
| })(); |
not work
what specifically doesn't work?
Thanks man, for the work. I get an error in the console when I run the snippet, below is the message
Uncaught TypeError: Failed to execute 'observe' on 'MutationObserver': parameter 1 is not of type 'Node'.
at addAll (VM865 get list of videos:73:14)
at VM865 get list of videos:76:3
addAll @ VM865 get list of videos:73
(anonymous) @ VM865 get list of videos:76
VM27:2522
Oh, sorry, I forgot that I have this gist :D I've updated my private but that is missed.
So, I fixed!
Oh, sorry, I forgot that I have this gist :D I've updated my private but that is missed. So, I fixed!
Thank you so much, I didn't expect a comment this fast. It's working like a charm now, I can't appreciate your work enough.
All the best!
Oh, sorry, I forgot that I have this gist :D I've updated my private but that is missed. So, I fixed!
Thank you so much, I didn't expect a comment this fast. It's working like a charm now, I can't appreciate your work enough.
All the best!
Thanks! Enjoy!!!
@artzub Now I get this error GoogleJsonResponseException: API call to youtube.playlistItems.insert failed with error: Precondition check failed.
I also created a new spreadsheet and new script but when I click the fill playlist option I get this error.
@artzub Now I get this error GoogleJsonResponseException: API call to youtube.playlistItems.insert failed with error: Precondition check failed.
I also created a new spreadsheet and new script but when I click the fill playlist option I get this error.
Could you check if that is the message about reaching the limit of API calls?
Sometimes that happens.
@artzub Now I get this error GoogleJsonResponseException: API call to youtube.playlistItems.insert failed with error: Precondition check failed.
I also created a new spreadsheet and new script but when I click the fill playlist option I get this error.Could you check if that is the message about reaching the limit of API calls? Sometimes that happens.
I didn't notice anything about reaching the limit of API calls neither in the error message nor email that I got from Google, and I extracted around 5000 videos to my playlist from the subscription feed. I also run the script from another gmail and give it the remaining videos that I want to add them to the playlist but there I get the same error message.
I don't know what is the reason for that error, you might add console.log to the script and try to debug by yourself.


not work