Last active
March 17, 2024 16:28
-
-
Save artzub/02b1b91e5c16a76bdf6c4710dbc105f0 to your computer and use it in GitHub Desktop.
YT Subs to WL
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
})(); |
I-Jalal
commented
Mar 17, 2024
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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment