-
-
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(); | |
})(); |
@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.
Could you check if that is the message about reaching the limit of API calls?
Sometimes that happens.