🎉 欢迎访问GreasyFork.Org 镜像站!本镜像站由公众号【爱吃馍】搭建,用于分享脚本。联系邮箱📮

Greasy fork 爱吃馍镜像

YouTube Timestamp Saver Ultimate

Save, organize, and navigate YouTube video timestamps with favorites | Made With 💖 by @AZXAD

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

🚀 安装遇到问题?关注公众号获取帮助

公众号二维码

扫码关注【爱吃馍】

回复【脚本】获取最新教程和防失联地址

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

🚀 安装遇到问题?关注公众号获取帮助

公众号二维码

扫码关注【爱吃馍】

回复【脚本】获取最新教程和防失联地址

// ==UserScript==
// @name         YouTube Timestamp Saver Ultimate
// @namespace    http://tampermonkey.net/
// @version      4.4
// @description  Save, organize, and navigate YouTube video timestamps with favorites | Made With 💖 by @AZXAD
// @author       @AZXAD
// @license      CC-BY-NC-ND-4.0
// @match        https://www.youtube.com/*
// @grant        GM_setClipboard
// @grant        GM_addStyle
// ==/UserScript==

/*
 * YouTube Timestamp Saver Ultimate
 * Copyright (c) 2025 @AZXAD
 * 
 * Licensed under CC BY-NC-ND 4.0
 * https://creativecommons.org/licenses/by-nc-nd/4.0/
 */

(function() {
    'use strict';

    const AUTHOR = '@AZXAD';
    const WATERMARK = 'Made With 💖 by @AZXAD';
    const SCRIPT_URL = 'https://greasyfork.org/en/scripts/555619-youtube-timestamp-saver-ultimate';

    let timestamps = [];
    let currentVideoId = '';
    let markerContainer = null;
    let previewTooltip = null;
    let urlObserver = null;
    let markerUpdateInterval = null;
    let videoElement = null;
    let showOnlyFavorites = false;

    const storage = {
        getItem(key) {
            try {
                return localStorage.getItem(key);
            } catch (e) {
                console.error('localStorage get error:', e);
                return null;
            }
        },
        setItem(key, value) {
            try {
                localStorage.setItem(key, value);
                return true;
            } catch (e) {
                if (e.name === 'QuotaExceededError' || e.code === 22 || e.code === 1014) {
                    showNotification('⚠️ Storage full! Clear old timestamps or browser data.');
                } else {
                    showNotification('⚠️ Storage error. Check browser settings.');
                }
                console.error('localStorage set error:', e);
                return false;
            }
        },
        removeItem(key) {
            try {
                localStorage.removeItem(key);
            } catch (e) {
                console.error('localStorage remove error:', e);
            }
        }
    };

    GM_addStyle(`
        #timestamp-panel {
            position: fixed;
            top: 80px;
            right: 20px;
            width: 350px;
            max-height: 500px;
            background: #1f1f1f;
            border: 2px solid #3ea6ff;
            border-radius: 8px;
            color: white;
            font-family: Arial, sans-serif;
            z-index: 10000;
            display: none;
            overflow: hidden;
        }
        #timestamp-header {
            background: #3ea6ff;
            padding: 10px;
            font-weight: bold;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        #timestamp-list {
            max-height: 350px;
            overflow-y: auto;
            padding: 10px;
        }
        .timestamp-item {
            background: #2d2d2d;
            margin: 8px 0;
            padding: 8px;
            border-radius: 5px;
            font-size: 12px;
            position: relative;
            border: 2px solid transparent;
            transition: border-color 0.2s;
        }
        .timestamp-item.favorite {
            background: #3a2d1f;
            border: 2px solid #ffd700;
        }
        .timestamp-time {
            color: #3ea6ff;
            font-weight: bold;
            cursor: pointer;
        }
        .timestamp-time:hover {
            text-decoration: underline;
        }
        .timestamp-note {
            color: #aaa;
            margin-top: 5px;
            word-wrap: break-word;
        }
        .timestamp-actions {
            float: right;
            display: flex;
            gap: 5px;
        }
        .timestamp-favorite {
            color: #888;
            cursor: pointer;
            font-size: 16px;
            transition: color 0.2s;
        }
        .timestamp-favorite.active {
            color: #ffd700;
        }
        .timestamp-favorite:hover {
            color: #ffd700;
        }
        .timestamp-delete {
            color: #ff4444;
            cursor: pointer;
            font-size: 16px;
        }
        .timestamp-edit {
            color: #ffaa00;
            cursor: pointer;
            font-size: 14px;
        }
        #timestamp-controls {
            padding: 10px;
            background: #282828;
            display: flex;
            gap: 5px;
            justify-content: space-between;
            flex-wrap: wrap;
        }
        .control-btn {
            padding: 6px 12px;
            background: #3ea6ff;
            border: none;
            border-radius: 4px;
            color: white;
            cursor: pointer;
            font-size: 11px;
            flex: 1;
            min-width: 70px;
        }
        .control-btn:hover {
            background: #2d8fd8;
        }
        .control-btn.active {
            background: #ffd700;
            color: #000;
        }
        #close-panel {
            cursor: pointer;
            font-size: 20px;
        }
        #timestamp-footer {
            padding: 8px 10px;
            background: #1a1a1a;
            text-align: center;
            font-size: 10px;
            color: #888;
            border-top: 1px solid #333;
        }
        #timestamp-footer a {
            color: #3ea6ff;
            text-decoration: none;
        }
        #timestamp-counter {
            position: fixed;
            top: 70px;
            left: 20px;
            background: rgba(255, 0, 0, 0.9);
            color: white;
            padding: 8px 12px;
            border-radius: 20px;
            font-size: 13px;
            font-weight: bold;
            z-index: 9999;
            font-family: Arial, sans-serif;
            box-shadow: 0 2px 8px rgba(0,0,0,0.3);
            display: none;
        }
        #timestamp-counter.show {
            display: block;
        }
        .timestamp-marker-container {
            position: absolute;
            bottom: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 31;
        }
        .timestamp-marker {
            position: absolute;
            bottom: 0;
            width: 8px;
            height: 8px;
            background: #ff0000;
            border-radius: 50%;
            transform: translateX(-50%);
            cursor: pointer;
            pointer-events: all;
            box-shadow: 0 0 4px rgba(255, 0, 0, 0.8);
            transition: all 0.2s ease;
        }
        .timestamp-marker.favorite {
            background: #ffd700;
            box-shadow: 0 0 6px rgba(255, 215, 0, 0.9);
        }
        .timestamp-marker:hover {
            width: 12px;
            height: 12px;
            background: #ff3333;
            box-shadow: 0 0 8px rgba(255, 0, 0, 1);
        }
        .timestamp-marker.favorite:hover {
            background: #ffed4e;
            box-shadow: 0 0 10px rgba(255, 215, 0, 1);
        }
        .timestamp-marker-tooltip {
            position: absolute;
            bottom: 12px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0, 0, 0, 0.9);
            color: white;
            padding: 4px 8px;
            border-radius: 4px;
            font-size: 11px;
            white-space: nowrap;
            display: none;
            z-index: 2000;
        }
        .timestamp-marker:hover .timestamp-marker-tooltip {
            display: block;
        }
        #seekbar-preview {
            position: absolute;
            bottom: 15px;
            background: rgba(0, 0, 0, 0.95);
            color: white;
            padding: 8px 12px;
            border-radius: 6px;
            font-size: 12px;
            white-space: nowrap;
            z-index: 2100;
            pointer-events: none;
            display: none;
            border: 1px solid #3ea6ff;
            max-width: 250px;
        }
        #seekbar-preview.show {
            display: block;
        }
        .preview-time {
            color: #3ea6ff;
            font-weight: bold;
        }
        .preview-note {
            color: #ccc;
            margin-top: 3px;
            font-size: 11px;
            white-space: normal;
        }
    `);

    function cleanup() {
        document.querySelectorAll('.timestamp-marker-container').forEach(el => el.remove());
        
        if (markerContainer && markerContainer.parentNode) {
            markerContainer.remove();
        }
        markerContainer = null;
        
        if (markerUpdateInterval) {
            clearInterval(markerUpdateInterval);
            markerUpdateInterval = null;
        }
        if (videoElement) {
            videoElement.removeEventListener('loadedmetadata', onVideoMetadataLoaded);
            videoElement = null;
        }
    }

    function formatTime(seconds) {
        if (!isFinite(seconds)) return '00:00:00';
        const hours = Math.floor(seconds / 3600);
        const minutes = Math.floor((seconds % 3600) / 60);
        const secs = Math.floor(seconds % 60);
        return [hours, minutes, secs].map(val => val.toString().padStart(2, '0')).join(':');
    }

    function getVideo() {
        return document.querySelector('video');
    }

    function getVideoId() {
        const urlParams = new URLSearchParams(window.location.search);
        const videoId = urlParams.get('v');
        if (!videoId && window.location.pathname.includes('/embed/')) {
            return window.location.pathname.split('/embed/')[1]?.split('?')[0];
        }
        return videoId;
    }

    function getVideoTitle() {
        const titleElement = document.querySelector('h1.ytd-video-primary-info-renderer') || 
                           document.querySelector('yt-formatted-string.style-scope.ytd-watch-metadata') ||
                           document.querySelector('#container h1');
        const title = titleElement ? titleElement.textContent.trim() : 'Unknown Video';
        return title.substring(0, 200);
    }

    function sanitizeHTML(text) {
        const div = document.createElement('div');
        div.textContent = text;
        return div.innerHTML;
    }

    function checkDuplicate(newTime) {
        const tolerance = 5;
        for (let item of timestamps) {
            if (Math.abs(item.time - newTime) < tolerance) {
                return item;
            }
        }
        return null;
    }

    function updateCounter() {
        let counter = document.getElementById('timestamp-counter');
        if (!counter) {
            counter = document.createElement('div');
            counter.id = 'timestamp-counter';
            counter.innerHTML = '📌 <span id="counter-value">0</span>';
            document.body.appendChild(counter);
        }
        
        const value = counter.querySelector('#counter-value');
        if (value) {
            const favCount = timestamps.filter(t => t.favorite).length;
            if (favCount > 0) {
                value.textContent = `${timestamps.length} (⭐${favCount})`;
            } else {
                value.textContent = timestamps.length;
            }
        }
        
        if (timestamps.length > 0) {
            counter.classList.add('show');
        } else {
            counter.classList.remove('show');
        }
    }

    function loadTimestamps() {
        const videoId = getVideoId();
        if (!videoId) {
            timestamps = [];
            updateCounter();
            return;
        }
        
        const saved = storage.getItem(`yt_timestamps_${videoId}`);
        if (saved) {
            try {
                timestamps = JSON.parse(saved);
                timestamps.sort((a, b) => a.time - b.time);
            } catch (e) {
                console.error('Error parsing timestamps:', e);
                timestamps = [];
            }
        } else {
            timestamps = [];
        }
        updateCounter();
    }

    function saveTimestamps() {
        const videoId = getVideoId();
        if (!videoId) return false;
        
        timestamps.sort((a, b) => a.time - b.time);
        
        const success = storage.setItem(`yt_timestamps_${videoId}`, JSON.stringify(timestamps));
        if (success) {
            updateCounter();
        }
        return success;
    }

    // Toggle favorite status
    function toggleFavorite(index) {
        if (index < 0 || index >= timestamps.length) return;
        
        timestamps[index].favorite = !timestamps[index].favorite;
        saveTimestamps();
        updatePanel();
        updateSeekbarMarkers();
        
        const status = timestamps[index].favorite ? '⭐ Favorited' : 'Removed from favorites';
        showNotification(status);
    }

    // Navigate to next timestamp
    function jumpToNextTimestamp() {
        const video = getVideo();
        if (!video || timestamps.length === 0) {
            showNotification('No timestamps available');
            return;
        }

        const currentTime = video.currentTime;
        const nextTimestamp = timestamps.find(t => t.time > currentTime + 1);
        
        if (nextTimestamp) {
            video.currentTime = nextTimestamp.time;
            const favIcon = nextTimestamp.favorite ? '⭐ ' : '';
            showNotification(`${favIcon}Next: ${formatTime(nextTimestamp.time)}`);
        } else {
            showNotification('Already at last timestamp');
        }
    }

    // Navigate to previous timestamp
    function jumpToPreviousTimestamp() {
        const video = getVideo();
        if (!video || timestamps.length === 0) {
            showNotification('No timestamps available');
            return;
        }

        const currentTime = video.currentTime;
        const reversedTimestamps = [...timestamps].reverse();
        const prevTimestamp = reversedTimestamps.find(t => t.time < currentTime - 1);
        
        if (prevTimestamp) {
            video.currentTime = prevTimestamp.time;
            const favIcon = prevTimestamp.favorite ? '⭐ ' : '';
            showNotification(`${favIcon}Previous: ${formatTime(prevTimestamp.time)}`);
        } else {
            showNotification('Already at first timestamp');
        }
    }

    function exportBackup() {
        const allData = {};
        let count = 0;
        
        try {
            for (let i = 0; i < localStorage.length; i++) {
                const key = localStorage.key(i);
                if (key && key.startsWith('yt_timestamps_')) {
                    const data = storage.getItem(key);
                    if (data) {
                        allData[key] = JSON.parse(data);
                        count++;
                    }
                }
            }
            
            allData['_metadata'] = {
                version: '4.3',
                author: AUTHOR,
                exported: new Date().toISOString(),
                credit: WATERMARK,
                scriptUrl: SCRIPT_URL
            };
        } catch (e) {
            showNotification('Error creating backup: ' + e.message);
            return;
        }
        
        const dataStr = JSON.stringify(allData, null, 2);
        const blob = new Blob([dataStr], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        
        const link = document.createElement('a');
        link.href = url;
        link.download = `youtube_timestamps_backup_${Date.now()}.json`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        
        URL.revokeObjectURL(url);
        showNotification(`Backup downloaded! (${count} videos)`);
    }

    function importBackup() {
        const input = document.createElement('input');
        input.type = 'file';
        input.accept = '.json';
        
        input.onchange = (e) => {
            const file = e.target.files[0];
            if (!file) return;
            
            if (file.size > 10 * 1024 * 1024) {
                showNotification('⚠️ File too large (max 10MB)');
                return;
            }
            
            const reader = new FileReader();
            reader.onload = (event) => {
                try {
                    const data = JSON.parse(event.target.result);
                    let count = 0;
                    let failed = 0;
                    
                    for (let key in data) {
                        if (key.startsWith('yt_timestamps_')) {
                            const success = storage.setItem(key, JSON.stringify(data[key]));
                            if (success) {
                                count++;
                            } else {
                                failed++;
                            }
                        }
                    }
                    
                    loadTimestamps();
                    updatePanel();
                    updateSeekbarMarkers();
                    
                    if (failed > 0) {
                        showNotification(`Imported ${count} videos. ${failed} failed (storage full?)`);
                    } else {
                        showNotification(`Imported ${count} video(s) successfully!`);
                    }
                } catch (err) {
                    showNotification('⚠️ Invalid backup file format');
                    console.error('Import error:', err);
                }
            };
            reader.readAsText(file);
        };
        
        input.click();
    }

    function createSeekbarPreview() {
        if (document.getElementById('seekbar-preview')) return;
        
        const preview = document.createElement('div');
        preview.id = 'seekbar-preview';
        document.body.appendChild(preview);
        previewTooltip = preview;
    }

    function showSeekbarPreview(mouseX, progressBar) {
        const video = getVideo();
        if (!video || !previewTooltip || timestamps.length === 0 || !isFinite(video.duration)) return;
        
        const rect = progressBar.getBoundingClientRect();
        const percentage = Math.max(0, Math.min(1, (mouseX - rect.left) / rect.width));
        const hoverTime = percentage * video.duration;
        
        const nearby = timestamps.filter(item => Math.abs(item.time - hoverTime) < 30)
            .sort((a, b) => Math.abs(a.time - hoverTime) - Math.abs(b.time - hoverTime))
            .slice(0, 3);
        
        if (nearby.length > 0) {
            let html = nearby.map(item => {
                const sanitizedNote = item.note ? sanitizeHTML(item.note) : '';
                const favIcon = item.favorite ? '⭐ ' : '';
                return `
                    <div class="preview-time">${favIcon}${formatTime(item.time)}</div>
                    ${sanitizedNote ? `<div class="preview-note">${sanitizedNote}</div>` : ''}
                `;
            }).join('<hr style="border: 1px solid #444; margin: 5px 0;">');
            
            previewTooltip.innerHTML = html;
            previewTooltip.style.left = Math.min(mouseX, window.innerWidth - 270) + 'px';
            previewTooltip.classList.add('show');
        } else {
            previewTooltip.classList.remove('show');
        }
    }

    function setupSeekbarHover() {
        const progressBar = document.querySelector('.ytp-progress-bar-container') ||
                          document.querySelector('.ytp-progress-bar');
        if (!progressBar) return;
        
        progressBar.addEventListener('mousemove', (e) => {
            showSeekbarPreview(e.clientX, progressBar);
        });
        
        progressBar.addEventListener('mouseleave', () => {
            if (previewTooltip) {
                previewTooltip.classList.remove('show');
            }
        });
    }

    function onVideoMetadataLoaded() {
        updateSeekbarMarkers();
    }

    function updateSeekbarMarkers() {
        const video = getVideo();
        if (!video) return;
        
        if (!isFinite(video.duration) || video.duration === 0) {
            if (videoElement !== video) {
                videoElement = video;
                video.addEventListener('loadedmetadata', onVideoMetadataLoaded, { once: true });
            }
            return;
        }

        const progressBar = document.querySelector('.ytp-progress-bar-container') || 
                          document.querySelector('.ytp-progress-bar') ||
                          document.querySelector('.ytp-progress-bar-padding');
        
        if (!progressBar) {
            setTimeout(updateSeekbarMarkers, 500);
            return;
        }

        if (markerContainer && markerContainer.parentNode) {
            markerContainer.remove();
            markerContainer = null;
        }

        markerContainer = document.createElement('div');
        markerContainer.className = 'timestamp-marker-container';
        progressBar.appendChild(markerContainer);

        timestamps.forEach((item, index) => {
            const percentage = (item.time / video.duration) * 100;
            if (percentage < 0 || percentage > 100) return;
            
            const marker = document.createElement('div');
            marker.className = 'timestamp-marker';
            if (item.favorite) {
                marker.classList.add('favorite');
            }
            marker.style.left = `${percentage}%`;
            marker.dataset.time = item.time;
            marker.dataset.index = index;

            const tooltip = document.createElement('div');
            tooltip.className = 'timestamp-marker-tooltip';
            const sanitizedNote = item.note ? sanitizeHTML(item.note) : '';
            const favIcon = item.favorite ? '⭐ ' : '';
            tooltip.textContent = sanitizedNote ? `${favIcon}${formatTime(item.time)} - ${item.note}` : `${favIcon}${formatTime(item.time)}`;
            marker.appendChild(tooltip);

            marker.addEventListener('click', (e) => {
                e.stopPropagation();
                video.currentTime = parseFloat(marker.dataset.time);
            });

            markerContainer.appendChild(marker);
        });
    }

    function createPanel() {
        const existingPanel = document.getElementById('timestamp-panel');
        if (existingPanel) {
            existingPanel.remove();
        }

        const panel = document.createElement('div');
        panel.id = 'timestamp-panel';
        panel.innerHTML = `
            <div id="timestamp-header">
                <span>Timestamps (${timestamps.length})</span>
                <span id="close-panel">×</span>
            </div>
            <div id="timestamp-list"></div>
            <div id="timestamp-controls">
                <button class="control-btn" id="filter-favorites-btn">⭐ Favorites</button>
                <button class="control-btn" id="clear-all">Clear All</button>
                <button class="control-btn" id="export-btn">Export</button>
                <button class="control-btn" id="backup-btn">💾 Backup</button>
                <button class="control-btn" id="import-btn">📁 Import</button>
            </div>
            <div id="timestamp-footer">${WATERMARK}</div>
        `;
        document.body.appendChild(panel);

        document.getElementById('close-panel').addEventListener('click', () => {
            panel.style.display = 'none';
        });

        document.getElementById('filter-favorites-btn').addEventListener('click', () => {
            showOnlyFavorites = !showOnlyFavorites;
            updatePanel();
            const btn = document.getElementById('filter-favorites-btn');
            if (showOnlyFavorites) {
                btn.classList.add('active');
            } else {
                btn.classList.remove('active');
            }
        });

        document.getElementById('clear-all').addEventListener('click', () => {
            if (confirm('Clear all timestamps for this video?')) {
                timestamps = [];
                saveTimestamps();
                updatePanel();
                updateSeekbarMarkers();
                showNotification('All timestamps cleared');
            }
        });

        document.getElementById('export-btn').addEventListener('click', exportTimestamps);
        document.getElementById('backup-btn').addEventListener('click', exportBackup);
        document.getElementById('import-btn').addEventListener('click', importBackup);
    }

    function updatePanel() {
        const list = document.getElementById('timestamp-list');
        if (!list) return;

        const header = document.querySelector('#timestamp-header span');
        if (header) {
            const favCount = timestamps.filter(t => t.favorite).length;
            if (showOnlyFavorites) {
                header.textContent = `Favorites (${favCount})`;
            } else {
                header.textContent = `Timestamps (${timestamps.length})`;
            }
        }

        const displayTimestamps = showOnlyFavorites ? 
            timestamps.filter(t => t.favorite) : 
            timestamps;

        if (displayTimestamps.length === 0) {
            const message = showOnlyFavorites ? 
                'No favorite timestamps' : 
                'No timestamps saved';
            list.innerHTML = `<div style="text-align: center; color: #aaa; padding: 20px;">${message}</div>`;
            return;
        }

        list.innerHTML = displayTimestamps.map((item, displayIndex) => {
            const realIndex = timestamps.indexOf(item);
            const sanitizedNote = item.note ? sanitizeHTML(item.note) : '';
            const favoriteClass = item.favorite ? 'favorite' : '';
            const favoriteIcon = item.favorite ? 'active' : '';
            
            return `
                <div class="timestamp-item ${favoriteClass}">
                    <div class="timestamp-actions">
                        <span class="timestamp-favorite ${favoriteIcon}" data-index="${realIndex}">★</span>
                        <span class="timestamp-edit" data-index="${realIndex}">✎</span>
                        <span class="timestamp-delete" data-index="${realIndex}">×</span>
                    </div>
                    <div class="timestamp-time" data-time="${item.time}">${formatTime(item.time)}</div>
                    ${sanitizedNote ? `<div class="timestamp-note">${sanitizedNote}</div>` : ''}
                </div>
            `;
        }).join('');

        list.querySelectorAll('.timestamp-time').forEach(el => {
            el.addEventListener('click', () => {
                const video = getVideo();
                if (video) {
                    video.currentTime = parseFloat(el.dataset.time);
                }
            });
        });

        list.querySelectorAll('.timestamp-favorite').forEach(el => {
            el.addEventListener('click', () => {
                const index = parseInt(el.dataset.index);
                toggleFavorite(index);
            });
        });

        list.querySelectorAll('.timestamp-delete').forEach(el => {
            el.addEventListener('click', () => {
                const index = parseInt(el.dataset.index);
                timestamps.splice(index, 1);
                saveTimestamps();
                updatePanel();
                updateSeekbarMarkers();
                showNotification('Timestamp deleted');
            });
        });

        list.querySelectorAll('.timestamp-edit').forEach(el => {
            el.addEventListener('click', () => {
                const index = parseInt(el.dataset.index);
                editTimestamp(index);
            });
        });
    }

    function editTimestamp(index) {
        if (index < 0 || index >= timestamps.length) return;
        
        const item = timestamps[index];
        const newNote = prompt(`Edit note for ${formatTime(item.time)}:`, item.note || '');
        
        if (newNote !== null) {
            timestamps[index].note = newNote.trim().substring(0, 500);
            saveTimestamps();
            updatePanel();
            updateSeekbarMarkers();
            showNotification('Timestamp updated!');
        }
    }

    function exportTimestamps() {
        if (timestamps.length === 0) {
            showNotification('No timestamps to export');
            return;
        }

        const videoTitle = getVideoTitle();
        const videoUrl = window.location.href.split('&')[0];
        
        let output = `Video: ${videoTitle}\nURL: ${videoUrl}\n\nTimestamps:\n`;
        output += timestamps.map(item => {
            const time = formatTime(item.time);
            const favIcon = item.favorite ? '⭐ ' : '';
            return item.note ? `${favIcon}${time} - ${item.note}` : `${favIcon}${time}`;
        }).join('\n');
        
        const favCount = timestamps.filter(t => t.favorite).length;
        if (favCount > 0) {
            output += `\n\nFavorites: ${favCount}`;
        }
        
        output += `\n\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`;
        output += `${WATERMARK}\n`;
        output += `\n📥 Get this script: ${SCRIPT_URL}\n`;
        output += `⭐ If you find this useful, please leave a review!\n`;
        output += `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`;

        GM_setClipboard(output);
        showNotification(`✅ Exported ${timestamps.length} timestamps!`);
    }

    function showNotification(message) {
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background-color: rgba(0, 0, 0, 0.9);
            color: white;
            padding: 15px 20px;
            border-radius: 5px;
            z-index: 10001;
            font-size: 14px;
            font-family: Arial, sans-serif;
            border: 2px solid #3ea6ff;
        `;
        document.body.appendChild(notification);
        setTimeout(() => {
            if (notification.parentNode) {
                notification.remove();
            }
        }, 2500);
    }

    document.addEventListener('keydown', function(event) {
        const videoId = getVideoId();
        if (!videoId) return;

        if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA') return;

        // Alt+X: Save timestamp
        if (event.altKey && event.key.toLowerCase() === 'x') {
            event.preventDefault();
            const video = getVideo();
            if (!video || !isFinite(video.currentTime)) return;

            const currentTime = video.currentTime;
            const duplicate = checkDuplicate(currentTime);
            
            if (duplicate) {
                const proceed = confirm(`Similar timestamp exists at ${formatTime(duplicate.time)}.\nAdd anyway?`);
                if (!proceed) return;
            }
            
            const note = prompt('Add a note (optional):');
            
            if (note !== null) {
                timestamps.push({
                    time: currentTime,
                    note: note.trim().substring(0, 500),
                    videoTitle: getVideoTitle(),
                    favorite: false
                });
                if (saveTimestamps()) {
                    updatePanel();
                    updateSeekbarMarkers();
                    showNotification(`Saved: ${formatTime(currentTime)}`);
                }
            }
        }

        // Alt+C: Export timestamps
        if (event.altKey && event.key.toLowerCase() === 'c') {
            event.preventDefault();
            exportTimestamps();
        }

        // Alt+Z: Toggle panel
        if (event.altKey && event.key.toLowerCase() === 'z') {
            event.preventDefault();
            const panel = document.getElementById('timestamp-panel');
            if (panel) {
                panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
                if (panel.style.display === 'block') {
                    updatePanel();
                }
            }
        }

        // Alt+D: Delete last timestamp
        if (event.altKey && event.key.toLowerCase() === 'd') {
            event.preventDefault();
            if (timestamps.length > 0) {
                const deleted = timestamps.pop();
                saveTimestamps();
                updatePanel();
                updateSeekbarMarkers();
                showNotification(`Deleted: ${formatTime(deleted.time)}`);
            }
        }

        // Alt+E: Edit last timestamp
        if (event.altKey && event.key.toLowerCase() === 'e') {
            event.preventDefault();
            if (timestamps.length > 0) {
                editTimestamp(timestamps.length - 1);
            } else {
                showNotification('No timestamps to edit');
            }
        }

        // Alt+N: Jump to next timestamp
        if (event.altKey && event.key.toLowerCase() === 'n') {
            event.preventDefault();
            jumpToNextTimestamp();
        }

        // Alt+M: Jump to previous timestamp
        if (event.altKey && event.key.toLowerCase() === 'm') {
            event.preventDefault();
            jumpToPreviousTimestamp();
        }
    });

    function init() {
        const videoId = getVideoId();
        
        if (videoId !== currentVideoId) {
            cleanup();
            currentVideoId = videoId;
            
            if (videoId) {
                loadTimestamps();
                createPanel();
                createSeekbarPreview();
                setupSeekbarHover();
                
                if (markerUpdateInterval) {
                    clearInterval(markerUpdateInterval);
                }
                markerUpdateInterval = setInterval(() => {
                    if (markerContainer && !document.contains(markerContainer)) {
                        updateSeekbarMarkers();
                    }
                }, 3000);
                
                setTimeout(() => {
                    const video = getVideo();
                    if (video) {
                        if (isFinite(video.duration) && video.duration > 0) {
                            updateSeekbarMarkers();
                        } else {
                            video.addEventListener('loadedmetadata', onVideoMetadataLoaded, { once: true });
                        }
                    }
                }, 500);
            }
        }
    }

    console.log('%c' + WATERMARK, 'color: #ff0000; font-size: 14px; font-weight: bold;');
    console.log('%cScript URL: ' + SCRIPT_URL, 'color: #3ea6ff; font-size: 12px;');

    let lastUrl = location.href;
    urlObserver = new MutationObserver(() => {
        const url = location.href;
        if (url !== lastUrl) {
            lastUrl = url;
            init();
        }
    });
    
    urlObserver.observe(document, {
        subtree: true,
        childList: true
    });

    setTimeout(init, 2000);
    window.addEventListener('beforeunload', cleanup);
})();