Skip to content

Instantly share code, notes, and snippets.

@artzub
Last active March 17, 2024 16:28
Show Gist options
  • Save artzub/02b1b91e5c16a76bdf6c4710dbc105f0 to your computer and use it in GitHub Desktop.
Save artzub/02b1b91e5c16a76bdf6c4710dbc105f0 to your computer and use it in GitHub Desktop.
YT Subs to WL
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
Copy link
Author

artzub commented Mar 17, 2024

To see that happened in the script need to visit to execution and open an execution with an error to see a reason.
image

@I-Jalal
Copy link

I-Jalal commented Mar 17, 2024

image

@artzub
Copy link
Author

artzub 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