Skip to content

Instantly share code, notes, and snippets.

@SMUsamaShah
Last active May 20, 2026 08:27
Show Gist options
  • Select an option

  • Save SMUsamaShah/0df1434d3e471f7cc83aaee32a0a61bc to your computer and use it in GitHub Desktop.

Select an option

Save SMUsamaShah/0df1434d3e471f7cc83aaee32a0a61bc to your computer and use it in GitHub Desktop.
ytch schedule manager
// ==UserScript==
// @name YouTube Video Collector with Grid View
// @namespace http://tampermonkey.net/
// @version 0.2
// @description Collect YouTube videos and display in a grid view
// @match https://www.youtube.com/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Helper functions
const $ = (selector, context = document) => context.querySelector(selector);
const $$ = (selector, context = document) => Array.from(context.querySelectorAll(selector));
const createElement = (tag, attributes = {}, children = []) => {
const element = document.createElement(tag);
Object.entries(attributes).forEach(([key, value]) => element.setAttribute(key, value));
children.forEach(child => {
if (typeof child === 'string') {
element.appendChild(document.createTextNode(child));
} else if (child instanceof Node) {
element.appendChild(child);
}
});
return element;
};
// Styles
const styles = `
#video-collector-modal {
display: none;
position: fixed;
z-index: 10000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
#video-collector-content {
background-color: #fefefe;
margin: 5% auto;
padding: 20px;
border: 1px solid #888;
width: 90%;
max-width: 1200px;
border-radius: 5px;
}
#video-collector-close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
cursor: pointer;
}
#video-collector-toggle {
position: fixed;
top: 70px;
right: 10px;
z-index: 9999;
background-color: #cc0000;
color: white;
border: none;
padding: 10px;
cursor: pointer;
border-radius: 5px;
}
#video-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
padding: 20px 0;
}
.video-item {
display: flex;
flex-direction: column;
border: 1px solid #ddd;
border-radius: 5px;
overflow: hidden;
}
.video-thumbnail {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
}
.video-info {
padding: 10px;
}
.video-title {
font-weight: bold;
margin-bottom: 5px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.video-duration {
font-size: 12px;
color: #666;
}
.delete-video-btn {
background-color: #f44336;
color: white;
border: none;
padding: 5px 10px;
cursor: pointer;
border-radius: 3px;
margin-top: 5px;
}
.add-to-tv-btn {
background-color: #cc0000;
color: white;
border: none;
padding: 5px 10px;
margin: 5px;
border-radius: 3px;
cursor: pointer;
}
#export-btn {
display: block;
margin: 20px auto;
padding: 10px 20px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
`;
// Create and add elements
document.head.appendChild(createElement('style', {}, [styles]));
const modal = createElement('div', { id: 'video-collector-modal' });
const modalContent = createElement('div', { id: 'video-collector-content' });
const closeBtn = createElement('span', { id: 'video-collector-close' }, ['×']);
const title = createElement('h2', {}, ['Collected Videos']);
const videoGrid = createElement('div', { id: 'video-grid' });
const exportBtn = createElement('button', { id: 'export-btn' }, ['Copy List to Clipboard']);
modalContent.appendChild(closeBtn);
modalContent.appendChild(title);
modalContent.appendChild(videoGrid);
modalContent.appendChild(exportBtn);
modal.appendChild(modalContent);
document.body.appendChild(modal);
const toggleBtn = createElement('button', { id: 'video-collector-toggle' }, ['Video Collector']);
document.body.appendChild(toggleBtn);
// Event listeners
closeBtn.onclick = () => { modal.style.display = "none"; };
window.onclick = (event) => { if (event.target == modal) modal.style.display = "none"; };
toggleBtn.onclick = toggleCollector;
exportBtn.onclick = exportToClipboard;
function toggleCollector() {
modal.style.display = "block";
loadVideoGrid();
}
function loadVideoGrid() {
videoGrid.textContent = ''; // Clear existing content
const savedVideos = JSON.parse(localStorage.getItem('collectedVideos') || '[]');
savedVideos.forEach((video, index) => {
videoGrid.appendChild(createVideoElement(video, index));
});
}
function createVideoElement(video, index) {
const videoItem = createElement('div', { class: 'video-item' });
const thumbnail = createElement('img', { src: video.thumbnail, alt: video.title, class: 'video-thumbnail' });
const infoDiv = createElement('div', { class: 'video-info' });
infoDiv.appendChild(createElement('div', { class: 'video-title' }, [video.title]));
infoDiv.appendChild(createElement('div', { class: 'video-duration' }, [formatDuration(video.duration)]));
const deleteBtn = createElement('button', { class: 'delete-video-btn' }, ['Delete']);
videoItem.appendChild(thumbnail);
videoItem.appendChild(infoDiv);
videoItem.appendChild(deleteBtn);
deleteBtn.onclick = () => deleteVideo(index);
return videoItem;
}
function formatDuration(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
const remainingSeconds = seconds % 60;
return hours > 0
? `${hours}:${padZero(minutes)}:${padZero(remainingSeconds)}`
: `${minutes}:${padZero(remainingSeconds)}`;
}
function padZero(num) {
return num.toString().padStart(2, '0');
}
function deleteVideo(index) {
let savedVideos = JSON.parse(localStorage.getItem('collectedVideos') || '[]');
savedVideos.splice(index, 1);
localStorage.setItem('collectedVideos', JSON.stringify(savedVideos));
loadVideoGrid();
}
function exportToClipboard() {
const savedVideos = localStorage.getItem('collectedVideos') || '[]';
navigator.clipboard.writeText(savedVideos).then(() => {
// alert('Video list copied to clipboard!');
}).catch(err => {
console.error('Failed to copy: ', err);
});
}
function addButton(container, videoId, title, duration) {
if ($('.add-to-tv-btn', container)) return;
const button = createElement('button', { class: 'add-to-tv-btn' }, ['Add to TV']);
button.addEventListener('click', function(e) {
e.stopPropagation();
const videoData = {
id: videoId,
title: title,
duration: duration,
thumbnail: `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`
};
let savedVideos = JSON.parse(localStorage.getItem('collectedVideos') || '[]');
// Check if the video already exists in the list
const videoExists = savedVideos.some(video => video.id === videoId);
if (!videoExists) {
savedVideos.push(videoData);
localStorage.setItem('collectedVideos', JSON.stringify(savedVideos));
console.log(`Added to TV: ${title}`);
} else {
console.log(`This video is already in your TV list.`);
}
});
container.appendChild(button);
}
function extractDuration(container) {
const durationElement = $('ytd-thumbnail-overlay-time-status-renderer yt-formatted-string, ytd-thumbnail-overlay-time-status-renderer span#text', container);
if (durationElement) {
const durationText = durationElement.textContent.trim();
const parts = durationText.split(':').map(Number);
return parts.length === 3
? parts[0] * 3600 + parts[1] * 60 + parts[2]
: parts[0] * 60 + parts[1];
}
return null;
}
function handleVideoItem(item) {
const thumbnailLink = $('a#thumbnail', item);
if (!thumbnailLink) return;
const videoId = new URLSearchParams(thumbnailLink.href.split('?')[1]).get('v');
const titleElement = $('#video-title', item);
const title = titleElement ? titleElement.textContent.trim() : 'Unknown Title';
const duration = extractDuration(item);
addButton(item, videoId, title, duration);
}
function handleAllVideoItems() {
$$('ytd-rich-item-renderer').forEach(handleVideoItem);
}
function observePageChanges() {
const observer = new MutationObserver((mutations) => {
mutations.forEach(mutation => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1 && node.matches('ytd-rich-item-renderer')) {
handleVideoItem(node);
}
});
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
}
handleAllVideoItems();
observePageChanges();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment