Last active
May 20, 2026 08:27
-
-
Save SMUsamaShah/0df1434d3e471f7cc83aaee32a0a61bc to your computer and use it in GitHub Desktop.
ytch schedule manager
This file contains hidden or 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
| // ==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