WayBackTube

Travel back in time on YouTube; simulate what it was like in any date.

当前为 2025-07-17 提交的版本,查看 最新版本

// ==UserScript==
// @name         WayBackTube
// @namespace    http://tampermonkey.net/
// @license MIT
// @version      24.3
// @description  Travel back in time on YouTube; simulate what it was like in any date.
// @author       You
// @match        https://www.youtube.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @connect      youtube.com
// @connect      googleapis.com
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';


    // === CONFIG ===
// Configuration settings for YouTube Time Machine
const CONFIG = {
    updateInterval: 50,
    debugMode: true,
    videosPerChannel: 30,
    maxHomepageVideos: 60,
    maxVideoPageVideos: 30,
    channelPageVideosPerMonth: 50,
    watchNextVideosCount: 30,
    seriesMatchVideosCount: 3,
    cacheExpiry: {
        videos: 2 * 60 * 60 * 1000, // 2 hours
        channelVideos: 1 * 60 * 60 * 1000, // 1 hour
        searchResults: 30 * 60 * 1000 // 30 minutes
    },
    maxConcurrentRequests: 2,
    batchSize: 3,
    apiCooldown: 200,
    autoLoadOnHomepage: true,
    autoLoadDelay: 1500,
    autoAdvanceDays: true,
    autoRefreshInterval: 20 * 60 * 1000, // 20 minutes in milliseconds
    refreshVideoPercentage: 0.5, // 50% new videos on each refresh
    homepageLoadMoreSize: 12,
    aggressiveNukingInterval: 10, // Nuke every 10ms
    viralVideoPercentage: 0.15, // 15% viral/popular videos
    viralVideosCount: 20, // Number of viral videos to fetch per timeframe

    // Recommendation system ratios
    RECOMMENDATION_COUNT: 20,
    SAME_CHANNEL_RATIO: 0.6, // 60% from same channel
    OTHER_CHANNELS_RATIO: 0.4, // 40% from other channels
    KEYWORD_RATIO: 0.5, // Half of same channel videos should use keywords

    // Watch next enhancements
    FRESH_VIDEOS_COUNT: 15, // Number of fresh videos to load from current channel
    KEYWORD_MATCH_RATIO: 0.5, // Half of fresh videos should match keywords

    // Shorts blocking selectors
    SHORTS_SELECTORS: [
        'ytd-reel-shelf-renderer',
        'ytd-rich-shelf-renderer[is-shorts]',
        '[aria-label*="Shorts"]',
        '[title*="Shorts"]',
        'ytd-video-renderer[is-shorts]',
        '.ytd-reel-shelf-renderer',
        '.shorts-shelf',
        '.reel-shelf-renderer',
        '.shortsLockupViewModelHost',
        '.ytGridShelfViewModelHost',
        '[overlay-style="SHORTS"]',
        '[href*="/shorts/"]'
    ],

    // Modern content detection patterns
    MODERN_CONTENT_INDICATORS: [
        'live', 'stream', 'streaming', 'premiere', 'premiering',
        'chat', 'superchat', 'donation', 'subscribe', 'notification',
        'bell icon', 'like and subscribe', 'smash that like',
        'comment below', 'let me know', 'what do you think',
        'thumbnail', 'clickbait', 'reaction', 'reacting to',
        'first time', 'blind reaction', 'compilation',
        'tiktok', 'instagram', 'twitter', 'social media',
        'influencer', 'content creator', 'youtuber',
        'monetization', 'demonetized', 'algorithm',
        'trending', 'viral', 'going viral', 'blew up'
    ],

    // Channel page selectors to hide
    CHANNEL_PAGE_SELECTORS: [
        'ytd-browse[page-subtype="channels"] ytd-video-renderer',
        'ytd-browse[page-subtype="channel"] ytd-video-renderer',
        'ytd-browse[page-subtype="channels"] ytd-grid-video-renderer',
        'ytd-browse[page-subtype="channel"] ytd-grid-video-renderer',
        'ytd-browse[page-subtype="channels"] ytd-rich-item-renderer',
        'ytd-browse[page-subtype="channel"] ytd-rich-item-renderer',
        'ytd-c4-tabbed-header-renderer ytd-video-renderer',
        'ytd-channel-video-player-renderer ytd-video-renderer',
        '#contents ytd-video-renderer',
        '#contents ytd-grid-video-renderer',
        '#contents ytd-rich-item-renderer',
        
        // Channel sections and shelves
        'ytd-browse[page-subtype="channel"] ytd-shelf-renderer',
        'ytd-browse[page-subtype="channel"] ytd-rich-shelf-renderer',
        'ytd-browse[page-subtype="channel"] ytd-item-section-renderer',
        'ytd-browse[page-subtype="channel"] ytd-section-list-renderer',
        'ytd-browse[page-subtype="channel"] ytd-horizontal-card-list-renderer',
        
        // Channel playlists and sections
        'ytd-browse[page-subtype="channel"] ytd-playlist-renderer',
        'ytd-browse[page-subtype="channel"] ytd-compact-playlist-renderer',
        'ytd-browse[page-subtype="channel"] ytd-grid-playlist-renderer',
        
        // For You, Popular Uploads, etc sections
        '#contents ytd-shelf-renderer',
        '#contents ytd-rich-shelf-renderer',
        '#contents ytd-item-section-renderer',
        '#contents ytd-section-list-renderer',
        '#contents ytd-horizontal-card-list-renderer',
        '#contents ytd-playlist-renderer',
        '#contents ytd-compact-playlist-renderer',
        '#contents ytd-grid-playlist-renderer',
        
        // Channel content containers
        'ytd-browse[page-subtype="channel"] #contents > *',
        'ytd-browse[page-subtype="channel"] #primary-inner > *:not(ytd-c4-tabbed-header-renderer)',
        
        // Specific section types
        '[data-target-id="browse-feed-tab"]',
        'ytd-browse[page-subtype="channel"] ytd-browse-feed-actions-renderer'
    ]
};

    // === API-MANAGER ===
// API Manager with unlimited rotation
class APIManager {
    constructor() {
        this.keys = GM_getValue('ytApiKeys', []);
        this.currentKeyIndex = GM_getValue('ytCurrentKeyIndex', 0);
        this.keyStats = GM_getValue('ytKeyStats', {});
        this.baseUrl = 'https://www.googleapis.com/youtube/v3';
        this.viralVideoCache = new Map();
        this.initializeKeys();
    }

    initializeKeys() {
        // Reset daily statistics
        const now = Date.now();
        const oneDayAgo = now - (24 * 60 * 60 * 1000);

        Object.keys(this.keyStats).forEach(key => {
            if (this.keyStats[key].lastFailed && this.keyStats[key].lastFailed < oneDayAgo) {
                this.keyStats[key].failed = false;
                this.keyStats[key].quotaExceeded = false;
            }
        });

        // Validate current key index
        if (this.currentKeyIndex >= this.keys.length) {
            this.currentKeyIndex = 0;
        }

        this.log('API Manager initialized with ' + this.keys.length + ' keys');
    }

    get currentKey() {
        if (this.keys.length === 0) return null;
        return this.keys[this.currentKeyIndex];
    }

    addKey(apiKey) {
        if (!apiKey || apiKey.length < 35) return false;

        if (!this.keys.includes(apiKey)) {
            this.keys.push(apiKey);
            this.keyStats[apiKey] = {
                failed: false,
                quotaExceeded: false,
                lastUsed: 0,
                requestCount: 0,
                successCount: 0
            };
            this.saveKeys();
            this.log('Added API key: ' + apiKey.substring(0, 8) + '...');
            return true;
        }
        return false;
    }

    removeKey(apiKey) {
        const index = this.keys.indexOf(apiKey);
        if (index > -1) {
            this.keys.splice(index, 1);
            delete this.keyStats[apiKey];

            // Adjust current index
            if (this.currentKeyIndex >= this.keys.length) {
                this.currentKeyIndex = Math.max(0, this.keys.length - 1);
            } else if (index <= this.currentKeyIndex && this.currentKeyIndex > 0) {
                this.currentKeyIndex--;
            }

            this.saveKeys();
            this.log('Removed API key: ' + apiKey.substring(0, 8) + '...');
            return true;
        }
        return false;
    }

    rotateToNextKey() {
        if (this.keys.length <= 1) return false;

        const startIndex = this.currentKeyIndex;
        let attempts = 0;

        while (attempts < this.keys.length) {
            this.currentKeyIndex = (this.currentKeyIndex + 1) % this.keys.length;
            attempts++;

            const currentKey = this.currentKey;
            const stats = this.keyStats[currentKey];

            if (!stats || (!stats.quotaExceeded && !stats.failed)) {
                this.saveKeys();
                this.log('Rotated to key ' + (this.currentKeyIndex + 1) + '/' + this.keys.length);
                return true;
            }
        }

        this.currentKeyIndex = 0;
        this.saveKeys();
        this.log('All keys have issues, reset to first key');
        return false;
    }

    markKeySuccess(apiKey) {
        if (!this.keyStats[apiKey]) {
            this.keyStats[apiKey] = {};
        }

        this.keyStats[apiKey].lastUsed = Date.now();
        this.keyStats[apiKey].requestCount = (this.keyStats[apiKey].requestCount || 0) + 1;
        this.keyStats[apiKey].successCount = (this.keyStats[apiKey].successCount || 0) + 1;
        this.keyStats[apiKey].failed = false;

        this.saveKeys();
    }

    markKeyFailed(apiKey, errorMessage) {
        if (!this.keyStats[apiKey]) {
            this.keyStats[apiKey] = {};
        }

        this.keyStats[apiKey].failed = true;
        this.keyStats[apiKey].lastFailed = Date.now();

        const quotaErrors = ['quota', 'exceeded', 'dailyLimitExceeded', 'rateLimitExceeded'];
        if (quotaErrors.some(error => errorMessage.toLowerCase().includes(error))) {
            this.keyStats[apiKey].quotaExceeded = true;
        }

        this.saveKeys();
        this.log('Key failed: ' + apiKey.substring(0, 8) + '... - ' + errorMessage);
    }

    async makeRequest(endpoint, params) {
        return new Promise((resolve, reject) => {
            if (this.keys.length === 0) {
                reject(new Error('No API keys available'));
                return;
            }

            const attemptRequest = (attemptCount = 0) => {
                const maxAttempts = Math.min(this.keys.length, 10);

                if (attemptCount >= maxAttempts) {
                    reject(new Error('All API keys exhausted'));
                    return;
                }

                const currentKey = this.currentKey;
                if (!currentKey) {
                    reject(new Error('No valid API key'));
                    return;
                }

                const urlParams = new URLSearchParams(Object.assign({}, params, { key: currentKey }));
                const url = endpoint + '?' + urlParams.toString();

                this.log('Request attempt ' + (attemptCount + 1) + ' with key ' + (this.currentKeyIndex + 1));

                GM_xmlhttpRequest({
                    method: 'GET',
                    url: url,
                    timeout: 10000,
                    headers: {
                        'Accept': 'application/json'
                    },
                    onload: (response) => {
                        if (response.status === 200) {
                            try {
                                const data = JSON.parse(response.responseText);
                                this.markKeySuccess(currentKey);
                                resolve(data);
                            } catch (e) {
                                reject(new Error('Invalid JSON response'));
                            }
                        } else {
                            let errorMessage = 'HTTP ' + response.status;
                            try {
                                const errorData = JSON.parse(response.responseText);
                                if (errorData.error && errorData.error.message) {
                                    errorMessage = errorData.error.message;
                                }
                            } catch (e) {
                                // Use default error message
                            }

                            this.markKeyFailed(currentKey, errorMessage);

                            if (this.rotateToNextKey()) {
                                setTimeout(() => attemptRequest(attemptCount + 1), 500);
                            } else {
                                reject(new Error('API Error: ' + errorMessage));
                            }
                        }
                    },
                    onerror: () => {
                        this.markKeyFailed(currentKey, 'Network error');
                        if (this.rotateToNextKey()) {
                            setTimeout(() => attemptRequest(attemptCount + 1), 1000);
                        } else {
                            reject(new Error('Network error'));
                        }
                    },
                    ontimeout: () => {
                        this.markKeyFailed(currentKey, 'Request timeout');
                        if (this.rotateToNextKey()) {
                            setTimeout(() => attemptRequest(attemptCount + 1), 500);
                        } else {
                            reject(new Error('Request timeout'));
                        }
                    }
                });
            };

            attemptRequest(0);
        });
    }

    async testAllKeys() {
        const results = [];

        if (this.keys.length === 0) {
            return ['No API keys configured'];
        }

        this.log('Testing all ' + this.keys.length + ' keys...');

        for (let i = 0; i < this.keys.length; i++) {
            const testKey = this.keys[i];

            try {
                const testParams = {
                    part: 'snippet',
                    q: 'test',
                    maxResults: 1,
                    type: 'video',
                    key: testKey,
                    videoDefinition: 'any',
                    videoSyndicated: 'true',
                };

                const url = this.baseUrl + '/search?' + new URLSearchParams(testParams);

                const response = await new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: url,
                        timeout: 8000,
                        onload: resolve,
                        onerror: reject,
                        ontimeout: reject
                    });
                });

                if (response.status === 200) {
                    const data = JSON.parse(response.responseText);
                    if (data.items && data.items.length > 0) {
                        results.push('Key ' + (i + 1) + ': Working perfectly');
                    } else {
                        results.push('Key ' + (i + 1) + ': Valid but no results');
                    }
                } else {
                    let errorMsg = 'HTTP ' + response.status;
                    try {
                        const errorData = JSON.parse(response.responseText);
                        if (errorData.error) {
                            errorMsg = errorData.error.message || errorMsg;
                        }
                    } catch (e) {
                        // Use default error
                    }
                    results.push('Key ' + (i + 1) + ': ' + errorMsg);
                }
            } catch (error) {
                results.push('Key ' + (i + 1) + ': ' + (error.message || 'Network error'));
            }

            await new Promise(resolve => setTimeout(resolve, 100));
        }

        return results;
    }

    saveKeys() {
        GM_setValue('ytApiKeys', this.keys);
        GM_setValue('ytCurrentKeyIndex', this.currentKeyIndex);
        GM_setValue('ytKeyStats', this.keyStats);
    }

    async getViralVideos(maxDate, forceRefresh = false) {
        const dateKey = maxDate.toISOString().split('T')[0];
        const cacheKey = 'viral_videos_' + dateKey;
        
        let viralVideos = this.getCache(cacheKey, forceRefresh);
        
        if (!viralVideos) {
            this.log('Fetching viral videos for ' + dateKey + '...');
            
            const viralQueries = [
                'viral meme',
                'funny viral video',
                'epic fail',
                'amazing viral',
                'internet meme',
                'viral compilation',
                'funny moments',
                'epic win',
                'viral trend',
                'popular meme',
                'viral challenge',
                'funny compilation',
                'viral clip',
                'internet famous',
                'viral sensation'
            ];
            
            const endDate = new Date(maxDate);
            endDate.setHours(23, 59, 59, 999);
            
            const startDate = new Date(maxDate);
            startDate.setFullYear(startDate.getFullYear() - 2); // 2 years before target date
            
            viralVideos = [];
            
            // Fetch from multiple viral queries
            for (let i = 0; i < Math.min(viralQueries.length, 8); i++) {
                try {
                    const query = viralQueries[Math.floor(Math.random() * viralQueries.length)];
                    
                    const response = await this.makeRequest(this.baseUrl + '/search', {
                        part: 'snippet',
                        q: query,
                        type: 'video',
                        order: 'relevance',
                        publishedBefore: endDate.toISOString(),
                        publishedAfter: startDate.toISOString(),
                        maxResults: 5,
                        videoDuration: 'short' // Prefer shorter viral content
                    });
                    
                    // Filter videos with minimum view count
                    const filteredVideos = [];
                    for (const video of videos) {
                        try {
                            // Get video statistics to check view count
                            const statsResponse = await this.makeRequest(this.baseUrl + '/videos', {
                                part: 'statistics',
                                id: video.id
                            });
                            
                            if (statsResponse.items && statsResponse.items[0]) {
                                const viewCount = parseInt(statsResponse.items[0].statistics.viewCount || '0');
                                if (viewCount >= 5000) {
                                    video.actualViewCount = viewCount;
                                    filteredVideos.push(video);
                                }
                            }
                            
                            // Small delay between stat requests
                            await new Promise(resolve => setTimeout(resolve, 100));
                        } catch (error) {
                            // If stats fail, include the video anyway
                            filteredVideos.push(video);
                        }
                    }
                    
                    viralVideos.push.apply(viralVideos, filteredVideos);
                    if (response.items) {
                        const videos = response.items.map(item => ({
                            id: item.id.videoId,
                            title: item.snippet.title,
                            channel: item.snippet.channelTitle,
                            channelId: item.snippet.channelId,
                            thumbnail: item.snippet.thumbnails && item.snippet.thumbnails.medium ? 
                                item.snippet.thumbnails.medium.url : 
                                (item.snippet.thumbnails && item.snippet.thumbnails.default ? 
                                    item.snippet.thumbnails.default.url : ''),
                            publishedAt: item.snippet.publishedAt,
                            description: item.snippet.description || '',
                            viewCount: this.generateViralViewCount(item.snippet.publishedAt, endDate),
                            relativeDate: this.formatRelativeDate(item.snippet.publishedAt, endDate),
                            isViral: true
                        }));
                        
                        viralVideos.push.apply(viralVideos, videos);
                    }
                    
                    // Small delay between requests
                    await new Promise(resolve => setTimeout(resolve, 300));
                    
                } catch (error) {
                    this.log('Failed to fetch viral videos for query ' + query + ':', error);
                }
            }
            
            // Remove duplicates and shuffle
            const uniqueVideos = viralVideos.filter((video, index, self) => 
                index === self.findIndex(v => v.id === video.id)
            );
            
            viralVideos = this.shuffleArray(uniqueVideos).slice(0, CONFIG.viralVideosCount);
            
            this.setCache(cacheKey, viralVideos, forceRefresh);
            this.log('Cached ' + viralVideos.length + ' viral videos for ' + dateKey);
        }
        
        return viralVideos || [];
    }
    
    generateViralViewCount(publishedAt, referenceDate) {
        const videoDate = new Date(publishedAt);
        const refDate = new Date(referenceDate);
        const daysSinceUpload = Math.floor((refDate - videoDate) / (1000 * 60 * 60 * 24));
        
        // Viral videos should have higher view counts
        let minViews, maxViews;
        
        if (daysSinceUpload <= 7) {
            minViews = 100000;
            maxViews = 5000000;
        } else if (daysSinceUpload <= 30) {
            minViews = 500000;
            maxViews = 20000000;
        } else if (daysSinceUpload <= 365) {
            minViews = 1000000;
            maxViews = 50000000;
        } else {
            minViews = 2000000;
            maxViews = 100000000;
        }
        
        const multiplier = Math.random() * 0.8 + 0.2;
        const viewCount = Math.floor(minViews + (maxViews - minViews) * multiplier);
        
        return video.actualViewCount ? this.formatViewCount(video.actualViewCount) : this.formatViewCount(viewCount);
    }
    
    async getChannelVideosForPage(channelId, channelName, endDate, startDate = null) {
        const cacheKey = 'channel_page_videos_' + channelId + '_' + endDate.toISOString().split('T')[0] + '_' + (startDate ? startDate.toISOString().split('T')[0] : 'latest');
        let videos = this.getCache(cacheKey);
        
        if (!videos) {
            try {
                const params = {
                    part: 'snippet',
                    channelId: channelId,
                    type: 'video',
                    order: 'date',
                    publishedBefore: endDate.toISOString(),
                    maxResults: CONFIG.channelPageVideosPerMonth
                };
                
                if (startDate) {
                    params.publishedAfter = startDate.toISOString();
                }
                
                const response = await this.makeRequest(this.baseUrl + '/search', params);
                
                videos = response.items ? response.items.map(item => ({
                    id: item.id.videoId,
                    title: item.snippet.title,
                    channel: item.snippet.channelTitle || channelName,
                    channelId: item.snippet.channelId,
                    thumbnail: item.snippet.thumbnails && item.snippet.thumbnails.medium ? 
                        item.snippet.thumbnails.medium.url : 
                        (item.snippet.thumbnails && item.snippet.thumbnails.default ? 
                            item.snippet.thumbnails.default.url : ''),
                    publishedAt: item.snippet.publishedAt,
                    description: item.snippet.description || '',
                    viewCount: this.generateRealisticViewCount(item.snippet.publishedAt, endDate),
                    relativeDate: this.formatRelativeDate(item.snippet.publishedAt, endDate)
                })) : [];
                
                this.setCache(cacheKey, videos);
                this.stats.apiCalls++;
            } catch (error) {
                this.log('Failed to get channel page videos for ' + channelName + ':', error);
                videos = [];
            }
        } else {
            this.stats.cacheHits++;
        }
        
        return videos;
    }
    
    generateRealisticViewCount(publishedAt, referenceDate) {
        const videoDate = new Date(publishedAt);
        const refDate = new Date(referenceDate);
        const daysSinceUpload = Math.floor((refDate - videoDate) / (1000 * 60 * 60 * 24));
        
        let minViews, maxViews;
        
        if (daysSinceUpload <= 1) {
            minViews = 50;
            maxViews = 10000;
        } else if (daysSinceUpload <= 7) {
            minViews = 500;
            maxViews = 100000;
        } else if (daysSinceUpload <= 30) {
            minViews = 2000;
            maxViews = 500000;
        } else if (daysSinceUpload <= 365) {
            minViews = 5000;
            maxViews = 2000000;
        } else {
            minViews = 10000;
            maxViews = 10000000;
        }
        
        const multiplier = Math.random() * 0.8 + 0.2;
        const viewCount = Math.floor(minViews + (maxViews - minViews) * multiplier);
        
        return this.formatViewCount(viewCount);
    }
    
    formatRelativeDate(publishedAt, referenceDate) {
        const videoDate = new Date(publishedAt);
        const refDate = new Date(referenceDate);
        const diffMs = refDate - videoDate;
        const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
        
        if (diffDays === 0) {
            const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
            if (diffHours === 0) {
                const diffMinutes = Math.floor(diffMs / (1000 * 60));
                return diffMinutes <= 1 ? '1 minute ago' : diffMinutes + ' minutes ago';
            }
            return diffHours === 1 ? '1 hour ago' : diffHours + ' hours ago';
        } else if (diffDays === 1) {
            return '1 day ago';
        } else if (diffDays < 7) {
            return diffDays + ' days ago';
        } else if (diffDays < 30) {
            const weeks = Math.floor(diffDays / 7);
            return weeks === 1 ? '1 week ago' : weeks + ' weeks ago';
        } else if (diffDays < 365) {
            const months = Math.floor(diffDays / 30);
            return months === 1 ? '1 month ago' : months + ' months ago';
        } else {
            const years = Math.floor(diffDays / 365);
            return years === 1 ? '1 year ago' : years + ' years ago';
        }
    }
    
    formatViewCount(count) {
        if (count >= 1000000) {
            return (count / 1000000).toFixed(1).replace('.0', '') + 'M';
        } else if (count >= 1000) {
            return (count / 1000).toFixed(1).replace('.0', '') + 'K';
        }
        return count.toLocaleString();
    }
    
    shuffleArray(array) {
        const shuffled = array.slice();
        for (let i = shuffled.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            const temp = shuffled[i];
            shuffled[i] = shuffled[j];
            shuffled[j] = temp;
        }
        return shuffled;
    }
    
    async searchVideos(query, maxResults = 10, endDate = null) {
        try {
            const params = {
                part: 'snippet',
                q: query,
                type: 'video',
                order: 'relevance',
                maxResults: maxResults
            };
            
            if (endDate) {
                params.publishedBefore = endDate.toISOString();
            }
            
            const response = await this.makeRequest(this.baseUrl + '/search', params);
            
            return response.items ? response.items.map(item => ({
                id: item.id.videoId,
                title: item.snippet.title,
                channel: item.snippet.channelTitle,
                channelId: item.snippet.channelId,
                thumbnail: item.snippet.thumbnails && item.snippet.thumbnails.medium ? 
                    item.snippet.thumbnails.medium.url : 
                    (item.snippet.thumbnails && item.snippet.thumbnails.default ? 
                        item.snippet.thumbnails.default.url : ''),
                publishedAt: item.snippet.publishedAt,
                description: item.snippet.description || '',
                viewCount: this.generateRealisticViewCount(item.snippet.publishedAt, endDate || new Date()),
                relativeDate: this.formatRelativeDate(item.snippet.publishedAt, endDate || new Date())
            })) : [];
            
        } catch (error) {
            this.log('Search failed for query "' + query + '":', error);
            return [];
        }
    }

    getCache(key, forceRefresh = false) {
        if (forceRefresh) return null;
        
        const cached = GM_getValue('cache_' + key, null);
        if (cached) {
            try {
                const data = JSON.parse(cached);
                if (Date.now() - data.timestamp < CONFIG.cacheExpiry.videos) {
                    return data.value;
                }
            } catch (e) {
                // Invalid cache entry
            }
        }
        return null;
    }

    setCache(key, value, forceRefresh = false) {
        const cacheData = {
            timestamp: Date.now(),
            value: value
        };
        GM_setValue('cache_' + key, JSON.stringify(cacheData));
    }

    clearCache() {
        const keys = GM_listValues();
        keys.forEach(key => {
            if (key.startsWith('cache_')) {
                GM_deleteValue(key);
            }
        });
    }

    log() {
        if (CONFIG.debugMode) {
            console.log.apply(console, ['[API Manager]'].concat(Array.prototype.slice.call(arguments)));
        }
    }
}

    // === SUBSCRIPTION-MANAGER ===
// Subscription Manager
class SubscriptionManager {
    constructor() {
        this.subscriptions = GM_getValue('ytSubscriptions', []);
    }

    addSubscription(channelName, channelId = null) {
        if (!channelName || channelName.trim() === '') return false;

        const subscription = {
            name: channelName.trim(),
            id: channelId,
            addedAt: Date.now()
        };

        // Check for duplicates
        const exists = this.subscriptions.some(sub =>
            sub.name.toLowerCase() === subscription.name.toLowerCase() ||
            (channelId && sub.id === channelId)
        );

        if (!exists) {
            this.subscriptions.push(subscription);
            this.save();
            return true;
        }
        return false;
    }

    removeSubscription(index) {
        if (index >= 0 && index < this.subscriptions.length) {
            this.subscriptions.splice(index, 1);
            this.save();
            return true;
        }
        return false;
    }

    getSubscriptions() {
        return this.subscriptions;
    }

    save() {
        GM_setValue('ytSubscriptions', this.subscriptions);
    }
}

    // === RECOMMENDATION-ENGINE ===
// Enhanced Recommendation Engine
class RecommendationEngine {
    constructor(apiManager) {
        this.apiManager = apiManager;
    }

    extractVideoKeywords(title) {
        const keywords = [];

        // Extract episode numbers with various formats
        const episodePatterns = [
            /episode\s*(\d+)/i,
            /ep\.?\s*(\d+)/i,
            /part\s*(\d+)/i,
            /#(\d+)/,
            /\b(\d+)\b/g
        ];

        episodePatterns.forEach(pattern => {
            const matches = title.match(pattern);
            if (matches) {
                if (pattern.global) {
                    const numbers = title.match(pattern);
                    if (numbers) {
                        numbers.forEach(match => {
                            const num = parseInt(match);
                            if (num > 0 && num < 1000) {
                                keywords.push('episode ' + num, 'ep ' + num, 'part ' + num, '#' + num);
                            }
                        });
                    }
                } else {
                    const episodeNum = matches[1];
                    keywords.push('episode ' + episodeNum, 'ep ' + episodeNum, 'part ' + episodeNum, '#' + episodeNum);
                }
            }
        });

        // Extract common series and gaming keywords
        const seriesKeywords = [
            'mod review', 'feed the beast', 'ftb', 'minecraft', 'tutorial',
            'let\'s play', 'lets play', 'playthrough', 'walkthrough', 'guide', 'tips',
            'tricks', 'build', 'showcase', 'series', 'season', 'modded', 'vanilla',
            'survival', 'creative', 'adventure', 'multiplayer', 'single player',
            'review', 'reaction', 'first time', 'blind', 'commentary', 'gameplay',
            'stream', 'live', 'vod', 'highlights', 'compilation', 'montage'
        ];

        seriesKeywords.forEach(keyword => {
            if (title.toLowerCase().includes(keyword.toLowerCase())) {
                keywords.push(keyword);
            }
        });

        // Extract words in quotes, brackets, or parentheses
        const quotedWords = title.match(/["'](.*?)["']|\[(.*?)\]|\((.*?)\)/g);
        if (quotedWords) {
            quotedWords.forEach(word => {
                const cleaned = word.replace(/["'\[\]()]/g, '').trim();
                if (cleaned.length > 2) {
                    keywords.push(cleaned);
                }
            });
        }

        // Extract potential game/mod names (capitalized words)
        const capitalizedWords = title.match(/\b[A-Z][a-z]+(?:\s+[A-Z][a-z]+)*\b/g);
        if (capitalizedWords) {
            capitalizedWords.forEach(word => {
                if (word.length > 3 && !['The', 'And', 'For', 'With', 'From'].includes(word)) {
                    keywords.push(word);
                }
            });
        }

        return keywords.filter((value, index, self) => self.indexOf(value) === index);
    }

    async generateEnhancedRecommendations(currentChannelId, currentVideoTitle, allVideos, maxDate, freshVideos = [], seriesVideos = []) {
        if (!allVideos || allVideos.length === 0) {
            console.log('[RecommendationEngine] No videos available for recommendations');
            return [];
        }

        console.log('[RecommendationEngine] Generating recommendations for channel: ' + currentChannelId + ', video: ' + currentVideoTitle);
        console.log('[RecommendationEngine] Total videos available: ' + allVideos.length);
        console.log('[RecommendationEngine] Fresh videos available: ' + freshVideos.length);
        console.log('[RecommendationEngine] Series videos available: ' + seriesVideos.length);

        const currentVideoKeywords = this.extractVideoKeywords(currentVideoTitle || '');
        console.log('[RecommendationEngine] Extracted keywords:', currentVideoKeywords);

        // Filter videos by date
        const validVideos = allVideos.filter(video =>
            new Date(video.publishedAt) <= maxDate
        );
        
        const validFreshVideos = freshVideos.filter(video =>
            new Date(video.publishedAt) <= maxDate
        );
        
        const validSeriesVideos = seriesVideos.filter(video =>
            new Date(video.publishedAt) <= maxDate && video.title !== currentVideoTitle
        );
        
        console.log('[RecommendationEngine] Valid videos after date filter: ' + validVideos.length);
        console.log('[RecommendationEngine] Valid fresh videos after date filter: ' + validFreshVideos.length);
        console.log('[RecommendationEngine] Valid series videos after date filter: ' + validSeriesVideos.length);

        // Separate current channel videos from others
        const currentChannelVideos = currentChannelId ?
            validVideos.filter(v => v.channelId === currentChannelId && v.title !== currentVideoTitle) :
            [];
        const otherChannelVideos = validVideos.filter(v =>
            !currentChannelId || v.channelId !== currentChannelId
        );

        console.log('[RecommendationEngine] Current channel videos: ' + currentChannelVideos.length);
        console.log('[RecommendationEngine] Other channel videos: ' + otherChannelVideos.length);

        // Calculate distribution - series videos will be interspersed later
        const freshVideosToUse = Math.min(CONFIG.FRESH_VIDEOS_COUNT, validFreshVideos.length);
        const keywordFreshCount = Math.floor(freshVideosToUse * CONFIG.KEYWORD_MATCH_RATIO);
        const regularFreshCount = freshVideosToUse - keywordFreshCount;
        
        const remainingSlots = CONFIG.RECOMMENDATION_COUNT - freshVideosToUse;
        const sameChannelCount = Math.floor(remainingSlots * CONFIG.SAME_CHANNEL_RATIO);
        const otherChannelsCount = remainingSlots - sameChannelCount;
        
        console.log('[RecommendationEngine] Distribution - Fresh: ' + freshVideosToUse + ' (' + keywordFreshCount + ' keyword, ' + regularFreshCount + ' regular), Same channel: ' + sameChannelCount + ', Other channels: ' + otherChannelsCount);

        const baseRecommendations = [];

        // Priority 1: Fresh keyword-matching videos from current channel
        if (keywordFreshCount > 0 && currentVideoKeywords.length > 0) {
            const keywordFreshVideos = this.findKeywordVideos(validFreshVideos, currentVideoKeywords);
            baseRecommendations.push.apply(baseRecommendations, this.shuffleArray(keywordFreshVideos).slice(0, keywordFreshCount));
        }
        console.log('[RecommendationEngine] Added ' + baseRecommendations.length + ' fresh keyword videos');

        // Priority 2: Other fresh videos from current channel
        if (regularFreshCount > 0) {
            const nonKeywordFreshVideos = validFreshVideos.filter(v => 
                baseRecommendations.indexOf(v) === -1 && v.title !== currentVideoTitle
            );
            baseRecommendations.push.apply(baseRecommendations, this.shuffleArray(nonKeywordFreshVideos).slice(0, regularFreshCount));
        }
        console.log('[RecommendationEngine] Added fresh videos, total: ' + baseRecommendations.length);

        // Priority 3: Cached videos from same channel
        if (sameChannelCount > 0) {
            const nonKeywordVideos = currentChannelVideos.filter(v =>
                baseRecommendations.indexOf(v) === -1 && 
                !validFreshVideos.some(fv => fv.id === v.id)
            );
            baseRecommendations.push.apply(baseRecommendations, this.shuffleArray(nonKeywordVideos).slice(0, sameChannelCount));
        }
        console.log('[RecommendationEngine] Added regular same channel videos, total: ' + baseRecommendations.length);

        // Priority 4: Videos from other channels
        if (otherChannelsCount > 0 && otherChannelVideos.length > 0) {
            baseRecommendations.push.apply(baseRecommendations, this.shuffleArray(otherChannelVideos).slice(0, otherChannelsCount));
        }
        console.log('[RecommendationEngine] Added other channel videos, total: ' + baseRecommendations.length);

        // Fill remaining slots if needed
        const allAvailableVideos = validVideos.concat(validFreshVideos).concat(validSeriesVideos);
        while (baseRecommendations.length < CONFIG.RECOMMENDATION_COUNT && allAvailableVideos.length > 0) {
            const remaining = allAvailableVideos.filter(v => 
                baseRecommendations.indexOf(v) === -1 && v.title !== currentVideoTitle
            );
            if (remaining.length === 0) break;
            baseRecommendations.push(remaining[Math.floor(Math.random() * remaining.length)]);
        }

        // Now intersperse series videos randomly into the base recommendations
        const finalRecommendations = this.intersperseSeriesVideos(baseRecommendations, validSeriesVideos);
        
        console.log('[RecommendationEngine] Final recommendations count: ' + finalRecommendations.length);
        return finalRecommendations.slice(0, CONFIG.RECOMMENDATION_COUNT);
    }

    intersperseSeriesVideos(baseRecommendations, seriesVideos) {
        if (!seriesVideos || seriesVideos.length === 0) {
            return baseRecommendations;
        }

        const maxSeriesToUse = Math.min(CONFIG.seriesMatchVideosCount || 3, seriesVideos.length);
        const seriesToUse = this.shuffleArray(seriesVideos).slice(0, maxSeriesToUse);
        
        console.log('[RecommendationEngine] Interspersing ' + seriesToUse.length + ' series videos randomly');

        // Create a copy of base recommendations
        const result = baseRecommendations.slice();
        
        // Generate random positions to insert series videos
        const positions = [];
        const maxPosition = Math.min(result.length, CONFIG.RECOMMENDATION_COUNT - seriesToUse.length);
        
        for (let i = 0; i < seriesToUse.length; i++) {
            let position;
            do {
                position = Math.floor(Math.random() * (maxPosition + i));
            } while (positions.includes(position));
            positions.push(position);
        }
        
        // Sort positions in descending order to insert from back to front
        positions.sort((a, b) => b - a);
        
        // Insert series videos at random positions
        positions.forEach((position, index) => {
            result.splice(position, 0, seriesToUse[index]);
        });
        
        console.log('[RecommendationEngine] Inserted series videos at positions:', positions.reverse());
        return result;
    }

    findKeywordVideos(videos, keywords) {
        return videos.filter(video => {
            const videoTitle = video.title.toLowerCase();
            return keywords.some(keyword =>
                videoTitle.includes(keyword.toLowerCase())
            );
        });
    }

    shuffleArray(array) {
        const shuffled = array.slice();
        for (let i = shuffled.length - 1; i > 0; i--) {
            const j = Math.floor(Math.random() * (i + 1));
            const temp = shuffled[i];
            shuffled[i] = shuffled[j];
            shuffled[j] = temp;
        }
        return shuffled;
    }
}

    // === SHORTS-BLOCKER ===
// Comprehensive Shorts Blocker
class ShortsBlocker {
    constructor() {
        this.processedElements = new WeakSet();
        this.modernContentCache = new Map();
    }

    blockShorts() {
        CONFIG.SHORTS_SELECTORS.forEach(selector => {
            try {
                const elements = document.querySelectorAll(selector);
                elements.forEach(element => {
                    if (element && element.style.display !== 'none') {
                        element.style.display = 'none';
                        element.style.visibility = 'hidden';
                        element.style.opacity = '0';
                        element.style.height = '0';
                        element.style.width = '0';
                        element.style.position = 'absolute';
                        element.style.left = '-9999px';
                        element.setAttribute('data-blocked-by-time-machine', 'true');
                    }
                });
            } catch (error) {
                // Ignore selector errors
            }
        });

        this.blockShortsByContent();
        this.blockModernContent();
        this.blockChannelPages();
    }
    
    nukeHomepage() {
        // ULTRA-AGGRESSIVELY hide ALL video content on homepage while loading
        const homepageSelectors = [
            // Primary content containers
            'ytd-browse[page-subtype="home"] ytd-video-renderer',
            'ytd-browse[page-subtype="home"] ytd-grid-video-renderer', 
            'ytd-browse[page-subtype="home"] ytd-rich-item-renderer',
            'ytd-browse[page-subtype="home"] ytd-compact-video-renderer',
            'ytd-browse[page-subtype="home"] ytd-movie-renderer',
            'ytd-browse[page-subtype="home"] ytd-playlist-renderer',
            
            // Shelf and section containers
            'ytd-browse[page-subtype="home"] ytd-rich-shelf-renderer',
            'ytd-browse[page-subtype="home"] ytd-shelf-renderer',
            'ytd-browse[page-subtype="home"] ytd-horizontal-card-list-renderer',
            'ytd-browse[page-subtype="home"] ytd-expanded-shelf-contents-renderer',
            
            // Content grids and lists
            'ytd-browse[page-subtype="home"] ytd-rich-grid-renderer #contents > *',
            'ytd-browse[page-subtype="home"] ytd-rich-grid-renderer ytd-rich-item-renderer',
            'ytd-browse[page-subtype="home"] ytd-rich-grid-renderer ytd-rich-section-renderer',
            'ytd-browse[page-subtype="home"] #contents > *',
            'ytd-browse[page-subtype="home"] #primary #contents > *',
            
            // Section renderers
            'ytd-browse[page-subtype="home"] ytd-item-section-renderer',
            'ytd-browse[page-subtype="home"] ytd-section-list-renderer',
            'ytd-browse[page-subtype="home"] ytd-continuation-item-renderer',
            
            // Specific modern content types
            'ytd-browse[page-subtype="home"] ytd-reel-shelf-renderer',
            'ytd-browse[page-subtype="home"] [is-shorts]',
            'ytd-browse[page-subtype="home"] [overlay-style="SHORTS"]',
            'ytd-browse[page-subtype="home"] [href*="/shorts/"]',
            
            // Trending and recommendation sections
            'ytd-browse[page-subtype="home"] ytd-rich-shelf-renderer[is-trending]',
            'ytd-browse[page-subtype="home"] ytd-rich-shelf-renderer[is-recommended]',
            'ytd-browse[page-subtype="home"] [aria-label*="Trending"]',
            'ytd-browse[page-subtype="home"] [aria-label*="Recommended"]',
            
            // Any video thumbnails
            'ytd-browse[page-subtype="home"] ytd-thumbnail',
            'ytd-browse[page-subtype="home"] .ytd-thumbnail',
            
            // Catch-all for any remaining video elements
            'ytd-browse[page-subtype="home"] [href*="/watch?v="]',
            'ytd-browse[page-subtype="home"] a[href*="/watch"]'
        ];
        
        homepageSelectors.forEach(selector => {
            try {
                const elements = document.querySelectorAll(selector);
                elements.forEach(element => {
                    if (!element.classList.contains('tm-homepage') && !element.classList.contains('tm-approved')) {
                        element.style.display = 'none !important';
                        element.style.visibility = 'hidden !important';
                        element.style.opacity = '0 !important';
                        element.style.height = '0 !important';
                        element.style.width = '0 !important';
                        element.style.maxHeight = '0 !important';
                        element.style.maxWidth = '0 !important';
                        element.style.overflow = 'hidden !important';
                        element.style.position = 'absolute !important';
                        element.style.left = '-9999px !important';
                        element.style.top = '-9999px !important';
                        element.style.zIndex = '-9999 !important';
                        element.style.pointerEvents = 'none !important';
                        element.setAttribute('data-nuked-by-time-machine', 'true');
                    }
                });
            } catch (error) {
                // Ignore selector errors
            }
        });
        
        // Also hide any video elements that might slip through
        const videoElements = document.querySelectorAll('ytd-browse[page-subtype="home"] *[href*="/watch"]');
        videoElements.forEach(element => {
            if (!element.classList.contains('tm-homepage') && !element.classList.contains('tm-approved')) {
                element.style.display = 'none !important';
                element.style.visibility = 'hidden !important';
                element.style.opacity = '0 !important';
                element.setAttribute('data-nuked-by-time-machine', 'video-link');
            }
        });
    }

    blockModernContent() {
        const videoElements = document.querySelectorAll('ytd-video-renderer, ytd-compact-video-renderer, ytd-grid-video-renderer, ytd-rich-item-renderer');

        videoElements.forEach(element => {
            if (this.processedElements.has(element)) return;

            if (this.isModernContent(element)) {
                element.classList.add('yt-time-machine-hidden');
                element.setAttribute('data-blocked-by-time-machine', 'modern-content');
                this.processedElements.add(element);
            }
        });
    }

    blockChannelPages() {
        // Hide only videos on channel pages, not the entire page
        if (this.isChannelPage()) {
            CONFIG.CHANNEL_PAGE_SELECTORS.forEach(selector => {
                try {
                    const elements = document.querySelectorAll(selector);
                    elements.forEach(element => {
                        if (element && element.style.display !== 'none') {
                            element.style.display = 'none';
                            element.style.visibility = 'hidden';
                            element.style.opacity = '0';
                            element.style.height = '0';
                            element.style.width = '0';
                            element.style.position = 'absolute';
                            element.style.left = '-9999px';
                            element.setAttribute('data-blocked-by-time-machine', 'channel-page-video');
                        }
                    });
                } catch (error) {
                    // Ignore selector errors
                }
            });
        }
    }

    isChannelPage() {
        return location.pathname.includes('/channel/') || 
               location.pathname.includes('/c/') || 
               location.pathname.includes('/user/') ||
               location.pathname.match(/\/@[\w-]+/);
    }


    isModernContent(videoElement) {
        // Cache results to avoid repeated processing
        if (this.modernContentCache.has(videoElement)) {
            return this.modernContentCache.get(videoElement);
        }

        const titleElement = videoElement.querySelector('a#video-title, h3 a, .ytd-video-meta-block a');
        const descElement = videoElement.querySelector('#description-text, .ytd-video-meta-block');
        
        let isModern = false;
        
        if (titleElement) {
            const title = titleElement.textContent.toLowerCase();
            
            // Check for modern content indicators
            isModern = CONFIG.MODERN_CONTENT_INDICATORS.some(indicator => 
                title.includes(indicator.toLowerCase())
            );
            
            // Additional modern patterns
            if (!isModern) {
                const modernPatterns = [
                    /\b(2019|2020|2021|2022|2023|2024|2025)\b/,
                    /\b(4k|1080p|60fps|hd|uhd)\b/i,
                    /\b(vlog|blog|daily|routine)\b/i,
                    /\b(unboxing|haul|review|reaction)\b/i,
                    /\b(challenge|prank|experiment)\b/i,
                    /\b(tik\s?tok|insta|snap|tweet)\b/i,
                    /\b(subscribe|notification|bell)\b/i,
                    /\b(like\s+and\s+subscribe|smash\s+that\s+like)\b/i,
                    /\b(what\s+do\s+you\s+think|let\s+me\s+know)\b/i,
                    /\b(comment\s+below|in\s+the\s+comments)\b/i
                ];
                
                isModern = modernPatterns.some(pattern => pattern.test(title));
            }
        }
        
        // Check description for modern indicators
        if (!isModern && descElement) {
            const desc = descElement.textContent.toLowerCase();
            isModern = CONFIG.MODERN_CONTENT_INDICATORS.slice(0, 10).some(indicator => 
                desc.includes(indicator.toLowerCase())
            );
        }
        
        // Check for modern UI elements
        if (!isModern) {
            const modernUIElements = [
                '[aria-label*="Subscribe"]',
                '[aria-label*="Notification"]',
                '.ytd-subscribe-button-renderer',
                '.ytd-notification-topbar-button-renderer',
                '[data-target-id*="subscribe"]'
            ];
            
            isModern = modernUIElements.some(selector => 
                videoElement.querySelector(selector)
            );
        }
        
        this.modernContentCache.set(videoElement, isModern);
        return isModern;
    }

    blockShortsByContent() {
        const videoElements = document.querySelectorAll('ytd-video-renderer, ytd-compact-video-renderer, ytd-grid-video-renderer, ytd-rich-item-renderer');

        videoElements.forEach(element => {
            if (this.processedElements.has(element)) return;

            const titleElement = element.querySelector('a#video-title, h3 a, .ytd-video-meta-block a');
            const durationElement = element.querySelector('.ytd-thumbnail-overlay-time-status-renderer span, .badge-style-type-simple');

            if (titleElement && durationElement) {
                const title = titleElement.textContent || '';
                const duration = durationElement.textContent || '';

                const isShort = duration.match(/^[0-5]?\d$/) || 
                              title.toLowerCase().includes('#shorts') ||
                              title.toLowerCase().includes('#short');

                if (isShort) {
                    element.classList.add('yt-time-machine-hidden');
                    element.setAttribute('data-blocked-by-time-machine', 'shorts');
                }
            }

            if (this.isShorts(element)) {
                element.classList.add('yt-time-machine-hidden');
                element.setAttribute('data-blocked-by-time-machine', 'shorts');
            }

            this.processedElements.add(element);
        });
    }

    isShorts(videoElement) {
        const shortsIndicators = [
            '[overlay-style="SHORTS"]',
            '[href*="/shorts/"]',
            '.shorts-thumbnail-overlay',
            '.shortsLockupViewModelHost',
            '[aria-label*="Shorts"]',
            '[title*="Shorts"]'
        ];

        return shortsIndicators.some(selector =>
            videoElement.querySelector(selector) ||
            videoElement.matches(selector)
        );
    }

    getShortsBlockingCSS() {
        return `
            /* Aggressive hiding of all video elements by default */
            ytd-video-renderer,
            ytd-grid-video-renderer,
            ytd-rich-item-renderer,
            ytd-compact-video-renderer,
            ytd-movie-renderer,
            ytd-playlist-renderer {
                visibility: hidden !important;
                opacity: 0 !important;
                height: 0 !important;
                overflow: hidden !important;
                transition: none !important;
            }

            /* Show approved videos */
            ytd-video-renderer.tm-approved,
            ytd-grid-video-renderer.tm-approved,
            ytd-rich-item-renderer.tm-approved,
            ytd-compact-video-renderer.tm-approved,
            ytd-movie-renderer.tm-approved,
            ytd-playlist-renderer.tm-approved {
                visibility: visible !important;
                opacity: 1 !important;
                height: auto !important;
                overflow: visible !important;
            }

            /* Ultra-aggressive hiding of modern content */
            ytd-rich-shelf-renderer[is-shorts],
            ytd-reel-shelf-renderer,
            ytd-shorts,
            [overlay-style="SHORTS"],
            ytd-thumbnail-overlay-time-status-renderer[overlay-style="SHORTS"],
            ytd-video-renderer:has([overlay-style="SHORTS"]),
            ytd-grid-video-renderer:has([overlay-style="SHORTS"]),
            ytd-rich-item-renderer:has([overlay-style="SHORTS"]),
            ytd-compact-video-renderer:has([overlay-style="SHORTS"]),
            #shorts-container,
            ytd-guide-entry-renderer a[title="Shorts"],
            ytd-mini-guide-entry-renderer[aria-label="Shorts"],
            a[href="/shorts"],
            [href*="/shorts/"],
            ytd-thumbnail[href*="/shorts/"],
            .shortsLockupViewModelHost,
            .ytGridShelfViewModelHost,
            [aria-label*="Shorts"],
            [title*="Shorts"],
            .shorts-shelf,
            .shorts-lockup {
                display: none !important;
                visibility: hidden !important;
                opacity: 0 !important;
                height: 0 !important;
                width: 0 !important;
                position: absolute !important;
                left: -9999px !important;
            }

            .yt-time-machine-hidden {
                display: none !important;
                visibility: hidden !important;
                opacity: 0 !important;
                height: 0 !important;
                width: 0 !important;
                position: absolute !important;
                left: -9999px !important;
            }
        `;
    }

    restoreBlockedContent() {
        const blockedElements = document.querySelectorAll('[data-blocked-by-time-machine]');
        blockedElements.forEach(element => {
            element.style.display = '';
            element.style.visibility = '';
            element.style.opacity = '';
            element.style.height = '';
            element.style.width = '';
            element.style.position = '';
            element.style.left = '';
            element.classList.remove('yt-time-machine-hidden');
            element.removeAttribute('data-blocked-by-time-machine');
        });
    }
}

    // === DATE-MODIFIER ===
// Enhanced Date Modifier
class DateModifier {
    constructor(maxDate) {
        this.maxDate = maxDate;
        this.processedElements = new WeakSet();
        this.originalDates = new Map();
    }

    setMaxDate(date) {
        this.maxDate = new Date(date);
    }

    formatRelativeDate(publishedAt, referenceDate) {
        const videoDate = new Date(publishedAt);
        const refDate = new Date(referenceDate);
        const diffMs = refDate - videoDate;
        const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));

        if (diffDays === 0) {
            const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
            if (diffHours === 0) {
                const diffMinutes = Math.floor(diffMs / (1000 * 60));
                return diffMinutes <= 1 ? '1 minute ago' : diffMinutes + ' minutes ago';
            }
            return diffHours === 1 ? '1 hour ago' : diffHours + ' hours ago';
        } else if (diffDays === 1) {
            return '1 day ago';
        } else if (diffDays < 7) {
            return diffDays + ' days ago';
        } else if (diffDays < 30) {
            const weeks = Math.floor(diffDays / 7);
            return weeks === 1 ? '1 week ago' : weeks + ' weeks ago';
        } else if (diffDays < 365) {
            const months = Math.floor(diffDays / 30);
            return months === 1 ? '1 month ago' : months + ' months ago';
        } else {
            const years = Math.floor(diffDays / 365);
            return years === 1 ? '1 year ago' : years + ' years ago';
        }
    }

    parseRelativeDate(dateText) {
        if (!dateText || !dateText.includes('ago')) return null;

        const now = new Date();
        const text = dateText.toLowerCase();

        const streamMatch = text.match(/streamed\s+(.+)/i);
        if (streamMatch) {
            text = streamMatch[1];
        }

        const premiereMatch = text.match(/premiered\s+(.+)/i);
        if (premiereMatch) {
            text = premiereMatch[1];
        }

        const patterns = [
            { regex: /(\d+)\s*second/i, multiplier: 1000 },
            { regex: /(\d+)\s*minute/i, multiplier: 60 * 1000 },
            { regex: /(\d+)\s*hour/i, multiplier: 60 * 60 * 1000 },
            { regex: /(\d+)\s*day/i, multiplier: 24 * 60 * 60 * 1000 },
            { regex: /(\d+)\s*week/i, multiplier: 7 * 24 * 60 * 60 * 1000 },
            { regex: /(\d+)\s*month/i, multiplier: 30 * 24 * 60 * 60 * 1000 },
            { regex: /(\d+)\s*year/i, multiplier: 365 * 24 * 60 * 60 * 1000 }
        ];

        for (const pattern of patterns) {
            const match = text.match(pattern.regex);
            if (match) {
                const amount = parseInt(match[1]);
                if (amount > 0) {
                    return new Date(now.getTime() - (amount * pattern.multiplier));
                }
            }
        }

        return null;
    }

    parseRelativeToTimeMachine(dateText, referenceDate) {
        const now = new Date(referenceDate);
        const text = dateText.toLowerCase();

        const streamMatch = text.match(/streamed\s+(.+)/i);
        if (streamMatch) {
            dateText = streamMatch[1];
        }

        const premiereMatch = text.match(/premiered\s+(.+)/i);
        if (premiereMatch) {
            dateText = premiereMatch[1];
        }

        const match = dateText.match(/(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago/i);

        if (!match) {
            const absoluteDate = this.parseAbsoluteDate(dateText);
            if (!isNaN(absoluteDate.getTime())) {
                return this.formatRelativeDate(absoluteDate.toISOString(), referenceDate);
            }
            return dateText;
        }

        const amount = parseInt(match[1]);
        const unit = match[2];

        switch (unit.toLowerCase()) {
            case 'second':
                now.setSeconds(now.getSeconds() - amount);
                break;
            case 'minute':
                now.setMinutes(now.getMinutes() - amount);
                break;
            case 'hour':
                now.setHours(now.getHours() - amount);
                break;
            case 'day':
                now.setDate(now.getDate() - amount);
                break;
            case 'week':
                now.setDate(now.getDate() - (amount * 7));
                break;
            case 'month':
                now.setMonth(now.getMonth() - amount);
                break;
            case 'year':
                now.setFullYear(now.getFullYear() - amount);
                break;
        }

        return this.formatRelativeDate(now.toISOString(), referenceDate);
    }

    parseAbsoluteDate(dateStr) {
        const formats = [
            /(\w+)\s+(\d+),\s+(\d+)/,
            /(\d+)\/(\d+)\/(\d+)/,
            /(\d+)-(\d+)-(\d+)/,
        ];

        const monthNames = {
            'jan': 0, 'january': 0, 'feb': 1, 'february': 1, 'mar': 2, 'march': 2,
            'apr': 3, 'april': 3, 'may': 4, 'jun': 5, 'june': 5,
            'jul': 6, 'july': 6, 'aug': 7, 'august': 7, 'sep': 8, 'september': 8,
            'oct': 9, 'october': 9, 'nov': 10, 'november': 10, 'dec': 11, 'december': 11
        };

        for (const format of formats) {
            const match = dateStr.match(format);
            if (match) {
                if (format === formats[0]) {
                    const month = monthNames[match[1].toLowerCase()];
                    return new Date(parseInt(match[3]), month, parseInt(match[2]));
                } else if (format === formats[1]) {
                    return new Date(parseInt(match[3]), parseInt(match[1]) - 1, parseInt(match[2]));
                } else if (format === formats[2]) {
                    return new Date(parseInt(match[1]), parseInt(match[2]) - 1, parseInt(match[3]));
                }
            }
        }

        return new Date(dateStr);
    }

    updateDates() {
        const dateSelectors = [
            '.ytd-video-meta-block #metadata-line span:last-child',
            '.ytd-video-meta-block .style-scope.ytd-video-meta-block',
            '#metadata-line span',
            '#metadata-line span:nth-child(2)', // Search results date
            '.ytd-video-renderer #metadata-line span:nth-child(2)',
            '.ytd-compact-video-renderer #metadata-line span:nth-child(2)',
            '.ytd-grid-video-renderer #metadata-line span:nth-child(2)',
            '.ytd-video-secondary-info-renderer #info span',
            '.ytd-video-renderer #metadata-line span',
            '.ytd-compact-video-renderer #metadata-line span',
            '.ytd-grid-video-renderer #metadata-line span',
            '.ytd-compact-video-renderer .style-scope.ytd-video-meta-block',
            '.ytd-watch-next-secondary-results-renderer #metadata-line span',
            '.ytd-comment-renderer #header-author .published-time-text',
            '.ytd-comment-thread-renderer .published-time-text',
            '.ytd-playlist-video-renderer #metadata span',
            '.ytd-grid-video-renderer #metadata-line span',
            '.ytd-rich-item-renderer #metadata-line span',
            // Search results specific selectors
            'ytd-video-renderer[data-context-menu-target] #metadata-line span:nth-child(2)',
            'ytd-video-renderer #metadata-line span[aria-label*="ago"]',
            '.ytd-video-renderer .ytd-video-meta-block span:nth-child(2)',
            '.ytd-video-renderer #metadata span:nth-child(2)'
        ];

        dateSelectors.forEach(selector => {
            try {
                const elements = document.querySelectorAll(selector);
                elements.forEach(element => {
                    if (this.processedElements.has(element)) return;

                    const originalText = element.textContent && element.textContent.trim();
                    if (!originalText) return;

                    // Skip if this is a view count (contains "views")
                    if (originalText.toLowerCase().includes('view')) return;

                    if (!originalText.includes('ago') &&
                        !originalText.match(/\d+/) &&
                        !originalText.includes('Streamed') &&
                        !originalText.includes('Premiered')) {
                        return;
                    }

                    if (!this.originalDates.has(element)) {
                        this.originalDates.set(element, originalText);
                    }

                    const modifiedDate = this.parseRelativeToTimeMachine(originalText, this.maxDate);
                    if (modifiedDate !== originalText) {
                        element.textContent = modifiedDate;
                        element.setAttribute('data-original-date', originalText);
                        element.setAttribute('data-modified-by-time-machine', 'true');
                        this.processedElements.add(element);
                    }
                });
            } catch (error) {
                // Ignore selector errors
            }
        });

        // Add view counts to search results that are missing them
        this.addMissingViewCounts();
        this.hideBeforeTags();
    }

    addMissingViewCounts() {
        const videoElements = document.querySelectorAll('ytd-video-renderer, ytd-compact-video-renderer, ytd-grid-video-renderer');
        
        videoElements.forEach(videoElement => {
            if (videoElement.dataset.tmViewsAdded) return;
            
            const metadataLine = videoElement.querySelector('#metadata-line');
            if (!metadataLine) return;
            
            const spans = metadataLine.querySelectorAll('span');
            let hasViews = false;
            
            spans.forEach(span => {
                if (span.textContent && span.textContent.toLowerCase().includes('view')) {
                    hasViews = true;
                }
            });
            
            if (!hasViews && spans.length > 0) {
                // Generate a realistic view count
                const viewCount = this.generateRealisticViewCount();
                
                // Create view count element
                const viewSpan = document.createElement('span');
                viewSpan.textContent = viewCount + ' views';
                viewSpan.style.color = 'var(--yt-spec-text-secondary)';
                viewSpan.setAttribute('data-added-by-time-machine', 'true');
                
                // Add separator if there are other elements
                if (spans.length > 0) {
                    const separator = document.createElement('span');
                    separator.textContent = ' • ';
                    separator.style.color = 'var(--yt-spec-text-secondary)';
                    separator.setAttribute('data-added-by-time-machine', 'true');
                    
                    // Insert before the date (usually the last span)
                    const lastSpan = spans[spans.length - 1];
                    metadataLine.insertBefore(viewSpan, lastSpan);
                    metadataLine.insertBefore(separator, lastSpan);
                } else {
                    metadataLine.appendChild(viewSpan);
                }
                
                videoElement.dataset.tmViewsAdded = 'true';
            }
        });
    }

    generateRealisticViewCount() {
        // Generate view counts that feel authentic for the time period
        const ranges = [
            { min: 100, max: 5000, weight: 30 },      // Small videos
            { min: 5000, max: 50000, weight: 25 },    // Medium videos  
            { min: 50000, max: 500000, weight: 20 },  // Popular videos
            { min: 500000, max: 2000000, weight: 15 }, // Very popular
            { min: 2000000, max: 10000000, weight: 8 }, // Viral
            { min: 10000000, max: 50000000, weight: 2 } // Mega viral
        ];
        
        // Weighted random selection
        const totalWeight = ranges.reduce((sum, range) => sum + range.weight, 0);
        let random = Math.random() * totalWeight;
        
        for (const range of ranges) {
            random -= range.weight;
            if (random <= 0) {
                const viewCount = Math.floor(Math.random() * (range.max - range.min) + range.min);
                return this.formatViewCount(viewCount);
            }
        }
        
        // Fallback
        return this.formatViewCount(Math.floor(Math.random() * 100000) + 1000);
    }

    formatViewCount(count) {
        if (count >= 1000000) {
            return (count / 1000000).toFixed(1).replace('.0', '') + 'M';
        } else if (count >= 1000) {
            return (count / 1000).toFixed(1).replace('.0', '') + 'K';
        }
        return count.toLocaleString();
    }
    hideBeforeTags() {
        const beforeTags = document.querySelectorAll('span, div');
        beforeTags.forEach(element => {
            const text = element.textContent;
            if (text && text.includes('before:') && text.includes(this.maxDate.toISOString().split('T')[0])) {
                element.style.display = 'none';
                element.setAttribute('data-hidden-by-time-machine', 'true');
            }
        });

        const filterChips = document.querySelectorAll('ytd-search-filter-renderer');
        filterChips.forEach(chip => {
            const text = chip.textContent;
            if (text && text.includes('before:')) {
                chip.style.display = 'none';
                chip.setAttribute('data-hidden-by-time-machine', 'true');
            }
        });
    }

    restoreOriginalDates() {
        this.originalDates.forEach((originalText, element) => {
            if (element && element.textContent !== originalText) {
                element.textContent = originalText;
                element.removeAttribute('data-original-date');
                element.removeAttribute('data-modified-by-time-machine');
            }
        });

        const hiddenElements = document.querySelectorAll('[data-hidden-by-time-machine]');
        hiddenElements.forEach(element => {
            element.style.display = '';
            element.removeAttribute('data-hidden-by-time-machine');
        });

        this.processedElements = new WeakSet();
        this.originalDates.clear();
    }
}

    // === SEARCH-INTERCEPTOR ===
// Enhanced Search Interceptor
class SearchInterceptor {
    constructor(maxDate, shortsBlocker) {
        this.maxDate = maxDate;
        this.shortsBlocker = shortsBlocker;
        this.interceptedElements = new WeakSet();
        this.originalSubmitHandler = null;
        this.isIntercepting = false;
    }

    setMaxDate(date) {
        this.maxDate = new Date(date);
    }

    setupSearchInterception() {
        this.log('Setting up search interception...');
        
        // Intercept search form submissions
        this.interceptSearchForms();
        
        // Intercept search input enter key
        this.interceptSearchInputs();
        
        // Intercept search button clicks
        this.interceptSearchButtons();
        
        // Clean up search results
        this.cleanupSearchResults();
        
        // Monitor for new search elements
        this.monitorForNewElements();
    }

    interceptSearchForms() {
        const interceptForm = (form) => {
            if (this.interceptedElements.has(form)) return;
            this.interceptedElements.add(form);

            const originalSubmit = form.onsubmit;
            form.onsubmit = (e) => {
                e.preventDefault();
                const input = form.querySelector('input#search');
                if (input && input.value.trim()) {
                    this.performSearch(input.value.trim(), input);
                }
                return false;
            };

            form.addEventListener('submit', (e) => {
                e.preventDefault();
                const input = form.querySelector('input#search');
                if (input && input.value.trim()) {
                    this.performSearch(input.value.trim(), input);
                }
            });
        };

        // Find all search forms
        const searchForms = document.querySelectorAll('form[role="search"], form#search-form, ytd-searchbox form');
        searchForms.forEach(interceptForm);
    }

    interceptSearchInputs() {
        const interceptInput = (input) => {
            if (this.interceptedElements.has(input)) return;
            this.interceptedElements.add(input);

            input.addEventListener('keydown', (e) => {
                if (e.key === 'Enter' && input.value.trim()) {
                    e.preventDefault();
                    e.stopPropagation();
                    this.performSearch(input.value.trim(), input);
                }
            });
        };

        // Find all search inputs
        const searchInputs = document.querySelectorAll('input#search, input[name="search_query"]');
        searchInputs.forEach(interceptInput);
    }

    interceptSearchButtons() {
        const interceptButton = (btn) => {
            if (this.interceptedElements.has(btn)) return;
            this.interceptedElements.add(btn);

            btn.addEventListener('click', (e) => {
                const input = document.querySelector('input#search, input[name="search_query"]');
                if (input && input.value.trim()) {
                    e.preventDefault();
                    e.stopPropagation();
                    this.performSearch(input.value.trim(), input);
                }
            });
        };

        // Find all search buttons
        const searchButtons = document.querySelectorAll(
            '#search-icon-legacy button, ' +
            'button[aria-label="Search"], ' +
            'ytd-searchbox button, ' +
            '.search-button'
        );
        searchButtons.forEach(interceptButton);
    }

    performSearch(query, inputElement) {
        this.log('Intercepting search for:', query);
        
        // Don't add before: if it already exists
        if (query.includes('before:')) {
            this.log('Search already contains before: filter, proceeding normally');
            this.executeSearch(query, inputElement);
            return;
        }

        // Add the before: filter to the actual search
        const beforeDate = this.maxDate.toISOString().split('T')[0];
        const modifiedQuery = query + ' before:' + beforeDate;
        
        this.log('Modified search query:', modifiedQuery);
        
        // Execute the search with the modified query
        this.executeSearch(modifiedQuery, inputElement);
        
        // Keep the original query visible in the input - multiple attempts
        const restoreOriginalQuery = () => {
            const searchInputs = document.querySelectorAll('input#search, input[name="search_query"]');
            searchInputs.forEach(input => {
                if (input.value.includes('before:')) {
                    input.value = query;
                }
            });
        };
        
        // Restore immediately and with delays
        restoreOriginalQuery();
        setTimeout(restoreOriginalQuery, 50);
        setTimeout(restoreOriginalQuery, 100);
        setTimeout(restoreOriginalQuery, 200);
        setTimeout(restoreOriginalQuery, 500);
        setTimeout(restoreOriginalQuery, 1000);
    }

    executeSearch(searchQuery, inputElement) {
        // Method 1: Try to use YouTube's search API directly
        if (this.tryYouTubeSearch(searchQuery)) {
            return;
        }

        // Method 2: Modify the URL and navigate
        const searchParams = new URLSearchParams();
        searchParams.set('search_query', searchQuery);
        
        const searchUrl = '/results?' + searchParams.toString();
        this.log('Navigating to search URL:', searchUrl);
        
        // Show loading indicator to prevent "offline" message
        this.showSearchLoading();
        
        window.location.href = searchUrl;
    }

    showSearchLoading() {
        // Create a temporary loading overlay to prevent offline message
        const loadingOverlay = document.createElement('div');
        loadingOverlay.id = 'tm-search-loading';
        loadingOverlay.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(15, 15, 15, 0.95);
            z-index: 999999;
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-family: Roboto, Arial, sans-serif;
            font-size: 16px;
        `;
        
        loadingOverlay.innerHTML = `
            <div style="text-align: center;">
                <div style="border: 2px solid #333; border-top: 2px solid #ff0000; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto 16px;"></div>
                <div>Searching through time...</div>
            </div>
            <style>
                @keyframes spin {
                    0% { transform: rotate(0deg); }
                    100% { transform: rotate(360deg); }
                }
            </style>
        `;
        
        document.body.appendChild(loadingOverlay);
        
        // Remove loading overlay after navigation starts
        setTimeout(() => {
            const overlay = document.getElementById('tm-search-loading');
            if (overlay) {
                overlay.remove();
            }
        }, 100);
    }
    tryYouTubeSearch(query) {
        try {
            // Try to find YouTube's search function
            if (window.ytInitialData && window.ytInitialData.contents) {
                // Use YouTube's internal search if available
                const searchParams = new URLSearchParams(window.location.search);
                searchParams.set('search_query', query);
                
                const newUrl = window.location.pathname + '?' + searchParams.toString();
                window.history.pushState({}, '', newUrl);
                
                // Show loading for internal search too
                this.showSearchLoading();
                
                // Trigger a page reload to execute the search
                window.location.reload();
                return true;
            }
        } catch (error) {
            this.log('YouTube internal search failed:', error);
        }
        
        return false;
    }

    monitorForNewElements() {
        // Set up a mutation observer to catch dynamically added search elements
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        // Hide offline messages
                        if (node.textContent && node.textContent.includes("You're offline")) {
                            node.style.display = 'none';
                            node.style.visibility = 'hidden';
                        }
                        
                        // Check for offline messages in child elements
                        const offlineElements = node.querySelectorAll('*');
                        offlineElements.forEach(el => {
                            if (el.textContent && el.textContent.includes("You're offline")) {
                                el.style.display = 'none';
                                el.style.visibility = 'hidden';
                            }
                        });
                        
                        // Check for new search forms
                        const newForms = node.querySelectorAll('form[role="search"], form#search-form, ytd-searchbox form');
                        newForms.forEach((form) => this.interceptSearchForms());
                        
                        // Check for new search inputs
                        const newInputs = node.querySelectorAll('input#search, input[name="search_query"]');
                        newInputs.forEach((input) => this.interceptSearchInputs());
                        
                        // Check for new search buttons
                        const newButtons = node.querySelectorAll('#search-icon-legacy button, button[aria-label="Search"], ytd-searchbox button');
                        newButtons.forEach((btn) => this.interceptSearchButtons());
                    }
                });
            });
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // Also run the setup periodically to catch any missed elements
        setInterval(() => {
            this.interceptSearchForms();
            this.interceptSearchInputs();
            this.interceptSearchButtons();
            
            // Hide any offline messages that appear
            const offlineMessages = document.querySelectorAll('*');
            offlineMessages.forEach(el => {
                if (el.textContent && el.textContent.includes("You're offline")) {
                    el.style.display = 'none';
                    el.style.visibility = 'hidden';
                }
            });
        }, 2000);
    }

    cleanupSearchResults() {
        // Hide search filter chips that show the before: date
        const hideBeforeChips = () => {
            // Hide all elements that contain "before:" text
            const allElements = document.querySelectorAll('*');
            allElements.forEach(element => {
                if (element.dataset.tmProcessedForBefore) return;
                
                const text = element.textContent || element.innerText || '';
                const ariaLabel = element.getAttribute('aria-label') || '';
                const title = element.getAttribute('title') || '';
                
                if (text.includes('before:') || ariaLabel.includes('before:') || title.includes('before:')) {
                    // Check if it's a search filter chip or similar element
                    if (element.matches('ytd-search-filter-renderer, .search-filter-chip, ytd-chip-cloud-chip-renderer, ytd-search-sub-menu-renderer *')) {
                        element.style.display = 'none !important';
                        element.style.visibility = 'hidden !important';
                        element.style.opacity = '0 !important';
                        element.style.height = '0 !important';
                        element.style.width = '0 !important';
                        element.style.position = 'absolute !important';
                        element.style.left = '-9999px !important';
                        element.setAttribute('data-hidden-by-time-machine', 'true');
                    }
                    // Also hide parent containers that might show the filter
                    let parent = element.parentElement;
                    while (parent && parent !== document.body) {
                        if (parent.matches('ytd-search-filter-renderer, ytd-chip-cloud-chip-renderer, ytd-search-sub-menu-renderer')) {
                            parent.style.display = 'none !important';
                            parent.setAttribute('data-hidden-by-time-machine', 'true');
                            break;
                        }
                        parent = parent.parentElement;
                    }
                }
                
                element.dataset.tmProcessedForBefore = 'true';
            });
            
            // Also hide specific YouTube search filter elements
            const specificSelectors = [
                'ytd-search-filter-renderer',
                'ytd-chip-cloud-chip-renderer', 
                'ytd-search-sub-menu-renderer',
                '.search-filter-chip',
                '[data-text*="before:"]',
                '[aria-label*="before:"]',
                '[title*="before:"]'
            ];
            
            specificSelectors.forEach(selector => {
                try {
                    const elements = document.querySelectorAll(selector);
                    elements.forEach(element => {
                        const text = element.textContent || element.getAttribute('aria-label') || element.getAttribute('title') || '';
                        if (text.includes('before:')) {
                            element.style.display = 'none !important';
                            element.style.visibility = 'hidden !important';
                            element.setAttribute('data-hidden-by-time-machine', 'true');
                        }
                    });
                } catch (e) {
                    // Ignore selector errors
                }
            });
        };

        // Run cleanup immediately and periodically
        hideBeforeChips();
        setInterval(hideBeforeChips, 200); // More frequent cleanup
        
        // Also run cleanup on page mutations
        const observer = new MutationObserver(() => {
            setTimeout(hideBeforeChips, 50);
            // Also restore search input if it shows before:
            setTimeout(() => {
                const searchInputs = document.querySelectorAll('input#search, input[name="search_query"]');
                searchInputs.forEach(input => {
                    if (input.value.includes('before:')) {
                        const originalQuery = input.value.replace(/\s*before:\d{4}-\d{2}-\d{2}/, '').trim();
                        if (originalQuery) {
                            input.value = originalQuery;
                        }
                    }
                });
            }, 10);
        });
        
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // Continuous monitoring of search input
        setInterval(() => {
            const searchInputs = document.querySelectorAll('input#search, input[name="search_query"]');
            searchInputs.forEach(input => {
                if (input.value.includes('before:')) {
                    const originalQuery = input.value.replace(/\s*before:\d{4}-\d{2}-\d{2}/, '').trim();
                    if (originalQuery) {
                        input.value = originalQuery;
                    }
                }
            });
        }, 100);
        // Block shorts in search results
        if (this.shortsBlocker) {
            setInterval(() => {
                this.shortsBlocker.blockShorts();
            }, 500);
        }
    }

    log() {
        if (CONFIG.debugMode) {
            console.log.apply(console, ['[Search Interceptor]'].concat(Array.prototype.slice.call(arguments)));
        }
    }
}

    // === UI-MANAGER ===
// UI Manager
class UIManager {
    constructor(timeMachine) {
        this.timeMachine = timeMachine;
    }

    getUIHTML() {
        const subscriptions = this.timeMachine.subscriptionManager.getSubscriptions();

        return '<div style="display: flex; justify-content: between; align-items: center; margin-bottom: 15px;">' +
            '<h2 style="margin: 0; font-size: 16px; color: #ff6b6b;">Time Machine</h2>' +
            '<button id="tmHideBtn" style="background: none; border: none; color: white; cursor: pointer; font-size: 18px;">×</button>' +
            '</div>' +

            '<div class="tm-section">' +
            '<h3>Target Date</h3>' +
            '<div class="tm-input-group">' +
            '<input type="date" id="tmDateInput" class="tm-input" value="' + this.timeMachine.settings.date + '" max="' + new Date().toISOString().split('T')[0] + '">' +
            '<button id="tmSetDate" class="tm-button">Set Date</button>' +
            '</div>' +
            '<div style="font-size: 11px; color: #aaa; margin-top: 5px;">' +
            'Currently traveling to: ' + this.timeMachine.maxDate.toLocaleDateString() +
            (CONFIG.autoAdvanceDays ? ' (Auto-advancing daily)' : '') +
            '</div>' +
            '</div>' +

            '<div class="tm-section">' +
            '<h3>API Keys (' + this.timeMachine.apiManager.keys.length + ')</h3>' +
            '<div class="tm-input-group">' +
            '<input type="password" id="tmApiInput" class="tm-input" placeholder="Enter YouTube API key">' +
            '<button id="tmAddApi" class="tm-button">Add</button>' +
            '</div>' +
            '<div class="tm-list" id="tmApiList">' + this.getApiKeyListHTML() + '</div>' +
            '<div style="margin-top: 8px;">' +
            '<button id="tmTestAll" class="tm-button" style="width: 100%;">Test All Keys</button>' +
            '</div>' +
            '</div>' +

            '<div class="tm-section">' +
            '<h3>Subscriptions (' + subscriptions.length + ')</h3>' +
            '<div class="tm-input-group">' +
            '<input type="text" id="tmSubInput" class="tm-input" placeholder="Enter channel name">' +
            '<button id="tmAddSub" class="tm-button">Add</button>' +
            '</div>' +
            '<div class="tm-list" id="tmSubList">' + this.getSubscriptionListHTML() + '</div>' +
            '<div style="margin-top: 8px;">' +
            '<button id="tmLoadVideos" class="tm-button" style="width: 100%;">Load Videos</button>' +
            '</div>' +
            '</div>' +

            '<div class="tm-section">' +
            '<h3>Statistics</h3>' +
            '<div class="tm-stats">' +
            '<div class="tm-stat">' +
            '<div class="tm-stat-value">' + this.timeMachine.stats.processed + '</div>' +
            '<div>Processed</div>' +
            '</div>' +
            '<div class="tm-stat">' +
            '<div class="tm-stat-value">' + this.timeMachine.stats.filtered + '</div>' +
            '<div>Filtered</div>' +
            '</div>' +
            '<div class="tm-stat">' +
            '<div class="tm-stat-value">' + this.timeMachine.stats.apiCalls + '</div>' +
            '<div>API Calls</div>' +
            '</div>' +
            '<div class="tm-stat">' +
            '<div class="tm-stat-value">' + this.timeMachine.stats.cacheHits + '</div>' +
            '<div>Cache Hits</div>' +
            '</div>' +
            '</div>' +
            '</div>' +

            '<div class="tm-section">' +
            '<h3>Controls</h3>' +
            '<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px;">' +
            '<button id="tmToggle" class="tm-button">' + (this.timeMachine.settings.active ? 'Disable' : 'Enable') + '</button>' +
            '<button id="tmClearCache" class="tm-button">Clear Cache</button>' +
            '</div>' +
            '<div style="margin-top: 8px;">' +
            '<button id="tmRefreshVideosBtn" class="tm-button" style="width: 100%;">Refresh Videos</button>' +
            '</div>' +
            '</div>' +

            '<div style="font-size: 10px; color: #666; text-align: center; margin-top: 15px;">' +
            'Press Ctrl+Shift+T to toggle UI' +
            '</div>';
    }

    getApiKeyListHTML() {
        if (this.timeMachine.apiManager.keys.length === 0) {
            return '<div style="text-align: center; color: #666; font-style: italic;">No API keys added</div>';
        }

        return this.timeMachine.apiManager.keys.map((key, index) => {
            const stats = this.timeMachine.apiManager.keyStats[key] || {};
            const isCurrent = index === this.timeMachine.apiManager.currentKeyIndex;
            let status = 'Unused';
            let statusColor = '#666';

            if (isCurrent) {
                status = 'Active';
                statusColor = '#4caf50';
            } else if (stats.quotaExceeded) {
                status = 'Quota Exceeded';
                statusColor = '#ff9800';
            } else if (stats.failed) {
                status = 'Failed';
                statusColor = '#f44336';
            } else if (stats.successCount > 0) {
                status = 'Standby';
                statusColor = '#2196f3';
            }

            return '<div class="tm-list-item">' +
                '<div>' +
                '<div style="font-weight: bold;">' + key.substring(0, 8) + '...' + key.substring(key.length - 4) + '</div>' +
                '<div style="font-size: 10px; color: ' + statusColor + ';">' + status + '</div>' +
                '</div>' +
                '<button class="tm-remove-btn" onclick="timeMachine.apiManager.removeKey(\'' + key + '\'); timeMachine.updateUI();">Remove</button>' +
                '</div>';
        }).join('');
    }

    getSubscriptionListHTML() {
        const subscriptions = this.timeMachine.subscriptionManager.getSubscriptions();

        if (subscriptions.length === 0) {
            return '<div style="text-align: center; color: #666; font-style: italic;">No subscriptions added</div>';
        }

        return subscriptions.map((sub, index) => 
            '<div class="tm-list-item">' +
            '<div>' +
            '<div style="font-weight: bold;">' + sub.name + '</div>' +
            '<div style="font-size: 10px; color: #666;">Added ' + new Date(sub.addedAt).toLocaleDateString() + '</div>' +
            '</div>' +
            '<button class="tm-remove-btn" onclick="timeMachine.removeSubscription(' + index + ')">Remove</button>' +
            '</div>'
        ).join('');
    }

    getStyles() {
        return `
            /* UI Styles */
            #timeMachineUI {
                position: fixed;
                top: 80px;
                right: 20px;
                width: 400px;
                max-height: 80vh;
                overflow-y: auto;
                background: linear-gradient(135deg, #0f0f0f, #1a1a1a);
                border: 1px solid #333;
                border-radius: 12px;
                padding: 20px;
                color: white;
                font-family: Roboto, Arial, sans-serif;
                z-index: 999999;
                box-shadow: 0 8px 32px rgba(0,0,0,0.8);
                backdrop-filter: blur(10px);
            }

            #timeMachineUI.hidden {
                display: none;
            }

            #timeMachineToggle {
                position: fixed;
                top: 80px;
                right: 20px;
                width: 50px;
                height: 50px;
                background: #ff0000;
                border: none;
                border-radius: 50%;
                color: white;
                font-size: 24px;
                cursor: pointer;
                z-index: 999998;
                box-shadow: 0 4px 16px rgba(255,0,0,0.4);
                display: none;
            }

            #timeMachineToggle.visible {
                display: block;
            }

            .tm-section {
                margin-bottom: 20px;
                padding: 15px;
                background: rgba(255,255,255,0.05);
                border-radius: 8px;
                border-left: 3px solid #ff0000;
            }

            .tm-section h3 {
                margin: 0 0 10px 0;
                font-size: 14px;
                color: #ff6b6b;
            }

            .tm-input-group {
                display: flex;
                gap: 8px;
                margin-bottom: 10px;
            }

            .tm-input {
                flex: 1;
                padding: 8px;
                background: rgba(255,255,255,0.1);
                border: 1px solid #333;
                border-radius: 4px;
                color: white;
                font-size: 12px;
            }

            .tm-button {
                padding: 8px 12px;
                background: #ff0000;
                border: none;
                border-radius: 4px;
                color: white;
                cursor: pointer;
                font-size: 12px;
                transition: background 0.2s;
            }

            .tm-button:hover {
                background: #cc0000;
            }

            .tm-button:disabled {
                background: #666;
                cursor: not-allowed;
            }

            .tm-list {
                max-height: 150px;
                overflow-y: auto;
                font-size: 12px;
            }

            .tm-list-item {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 6px 0;
                border-bottom: 1px solid rgba(255,255,255,0.1);
            }

            .tm-remove-btn {
                background: #444;
                border: none;
                color: white;
                padding: 2px 6px;
                border-radius: 3px;
                cursor: pointer;
                font-size: 10px;
            }

            .tm-stats {
                display: grid;
                grid-template-columns: 1fr 1fr;
                gap: 10px;
                font-size: 12px;
            }

            .tm-stat {
                background: rgba(255,255,255,0.05);
                padding: 8px;
                border-radius: 4px;
                text-align: center;
            }

            .tm-stat-value {
                font-size: 16px;
                font-weight: bold;
                color: #ff6b6b;
            }

            /* Homepage replacement - 4 columns */
            .tm-channel-page {
                padding: 20px;
                max-width: 1200px;
                margin: 0 auto;
            }
            
            .tm-channel-header {
                margin-bottom: 20px;
                padding: 15px;
                background: var(--yt-spec-raised-background);
                border-radius: 8px;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
            
            .tm-channel-title {
                font-size: 18px;
                font-weight: 500;
                color: var(--yt-spec-text-primary);
            }
            
            .tm-load-older-btn {
                padding: 8px 16px;
                background: #ff0000;
                border: none;
                border-radius: 4px;
                color: white;
                cursor: pointer;
                font-size: 12px;
                transition: background 0.2s;
            }
            
            .tm-load-older-btn:hover {
                background: #cc0000;
            }
            
            .tm-load-older-btn:disabled {
                background: #666;
                cursor: not-allowed;
            }

            .tm-homepage {
                padding: 20px;
                max-width: 1200px;
                margin: 0 auto;
            }

            .tm-video-grid {
                display: grid;
                grid-template-columns: repeat(4, 1fr);
                gap: 20px;
                margin-top: 20px;
            }

            .tm-video-card {
                background: var(--yt-spec-raised-background);
                border-radius: 8px;
                overflow: hidden;
                cursor: pointer;
                transition: transform 0.2s;
            }

            .tm-video-card:hover {
                transform: translateY(-4px);
            }
            
            .tm-viral-video {
                border: 2px solid #ff6b6b;
                box-shadow: 0 0 10px rgba(255, 107, 107, 0.3);
            }
            
            .tm-viral-video:hover {
                box-shadow: 0 4px 20px rgba(255, 107, 107, 0.5);
            }

            .tm-video-thumbnail {
                width: 100%;
                aspect-ratio: 16/9;
                object-fit: cover;
                position: relative;
            }

            .tm-video-info {
                padding: 12px;
            }

            .tm-video-title {
                font-size: 14px;
                font-weight: 500;
                line-height: 1.3;
                margin-bottom: 8px;
                color: var(--yt-spec-text-primary);
                display: -webkit-box;
                -webkit-line-clamp: 2;
                -webkit-box-orient: vertical;
                overflow: hidden;
            }

            .tm-video-channel {
                font-size: 12px;
                color: var(--yt-spec-text-secondary);
                margin-bottom: 4px;
            }

            .tm-video-meta {
                display: flex;
                justify-content: space-between;
                font-size: 11px;
                color: var(--yt-spec-text-secondary);
            }

            .tm-video-views {
                font-weight: 500;
            }

            .tm-video-date {
                font-size: 11px;
                color: var(--yt-spec-text-secondary);
            }

            /* Load More Button */
            .tm-load-more-btn {
                display: block;
                margin: 30px auto;
                padding: 12px 24px;
                background: linear-gradient(to bottom, #f8f8f8, #e0e0e0);
                border: 1px solid #ccc;
                border-radius: 3px;
                color: #333;
                font-family: Arial, sans-serif;
                font-size: 13px;
                font-weight: bold;
                text-shadow: 0 1px 0 rgba(255,255,255,0.8);
                cursor: pointer;
                box-shadow: 0 1px 3px rgba(0,0,0,0.2), inset 0 1px 0 rgba(255,255,255,0.8);
                transition: all 0.2s;
            }

            .tm-load-more-btn:hover {
                background: linear-gradient(to bottom, #ffffff, #e8e8e8);
                box-shadow: 0 2px 4px rgba(0,0,0,0.3), inset 0 1px 0 rgba(255,255,255,0.9);
            }

            .tm-load-more-btn:active {
                background: linear-gradient(to bottom, #e0e0e0, #f0f0f0);
                box-shadow: inset 0 1px 3px rgba(0,0,0,0.2);
            }

            .tm-load-more-btn:disabled {
                background: #f0f0f0;
                color: #999;
                cursor: not-allowed;
                box-shadow: none;
            }

            /* Video page enhancement */
            .tm-video-page-section {
                margin-top: 20px;
                padding: 16px;
                background: var(--yt-spec-raised-background);
                border-radius: 8px;
            }

            .tm-video-page-title {
                font-size: 16px;
                font-weight: 500;
                margin-bottom: 16px;
                color: var(--yt-spec-text-primary);
            }

            .tm-video-page-grid {
                display: grid;
                grid-template-columns: 1fr;
                gap: 12px;
            }

            .tm-video-page-card {
                display: flex;
                gap: 12px;
                cursor: pointer;
                transition: background 0.2s;
                padding: 8px;
                border-radius: 8px;
            }

            .tm-video-page-card:hover {
                background: rgba(255,255,255,0.05);
            }

            .tm-video-page-thumbnail {
                width: 168px;
                height: 94px;
                object-fit: cover;
                border-radius: 4px;
                flex-shrink: 0;
            }

            .tm-video-page-info {
                flex: 1;
                display: flex;
                flex-direction: column;
                justify-content: space-between;
            }

            .tm-video-page-video-title {
                font-size: 14px;
                font-weight: 500;
                line-height: 1.3;
                color: var(--yt-spec-text-primary);
                margin-bottom: 4px;
                display: -webkit-box;
                -webkit-line-clamp: 2;
                -webkit-box-orient: vertical;
                overflow: hidden;
            }

            .tm-video-page-channel {
                font-size: 12px;
                color: var(--yt-spec-text-secondary);
                margin-bottom: 4px;
            }

            .tm-video-page-meta {
                display: flex;
                gap: 8px;
                font-size: 11px;
                color: var(--yt-spec-text-secondary);
            }

            .tm-loading {
                text-align: center;
                padding: 40px;
                color: var(--yt-spec-text-secondary);
            }

            .tm-spinner {
                border: 2px solid #333;
                border-top: 2px solid #ff0000;
                border-radius: 50%;
                width: 30px;
                height: 30px;
                animation: spin 1s linear infinite;
                margin: 0 auto 16px;
            }

            @keyframes spin {
                0% { transform: rotate(0deg); }
                100% { transform: rotate(360deg); }
            }

            /* Responsive design */
            @media (max-width: 1200px) {
                .tm-video-grid {
                    grid-template-columns: repeat(3, 1fr);
                }
            }

            @media (max-width: 768px) {
                .tm-video-grid {
                    grid-template-columns: repeat(2, 1fr);
                }

                .tm-video-page-grid {
                    grid-template-columns: 1fr;
                }
            }

            @media (max-width: 480px) {
                .tm-video-grid {
                    grid-template-columns: 1fr;
                }
            }

            .yt-time-machine-hidden {
                display: none !important;
                visibility: hidden !important;
                opacity: 0 !important;
                height: 0 !important;
                width: 0 !important;
                position: absolute !important;
                left: -9999px !important;
            }

            /* ULTRA-AGGRESSIVE homepage nuking */
            ytd-browse[page-subtype="home"] ytd-video-renderer:not(.tm-approved),
            ytd-browse[page-subtype="home"] ytd-grid-video-renderer:not(.tm-approved),
            ytd-browse[page-subtype="home"] ytd-rich-item-renderer:not(.tm-approved),
            ytd-browse[page-subtype="home"] ytd-compact-video-renderer:not(.tm-approved),
            ytd-browse[page-subtype="home"] ytd-movie-renderer:not(.tm-approved),
            ytd-browse[page-subtype="home"] ytd-playlist-renderer:not(.tm-approved),
            ytd-browse[page-subtype="home"] ytd-rich-shelf-renderer:not(.tm-approved),
            ytd-browse[page-subtype="home"] ytd-shelf-renderer:not(.tm-approved),
            ytd-browse[page-subtype="home"] ytd-item-section-renderer:not(.tm-approved),
            ytd-browse[page-subtype="home"] ytd-continuation-item-renderer:not(.tm-approved),
            ytd-browse[page-subtype="home"] ytd-rich-grid-renderer #contents > *:not(.tm-homepage):not(.tm-approved),
            ytd-browse[page-subtype="home"] #contents > *:not(.tm-homepage):not(.tm-approved),
            ytd-browse[page-subtype="home"] ytd-thumbnail:not(.tm-approved),
            ytd-browse[page-subtype="home"] [href*="/watch?v="]:not(.tm-approved) {
                display: none !important;
                visibility: hidden !important;
                opacity: 0 !important;
                height: 0 !important;
                width: 0 !important;
                max-height: 0 !important;
                max-width: 0 !important;
                overflow: hidden !important;
                position: absolute !important;
                left: -9999px !important;
                top: -9999px !important;
                z-index: -9999 !important;
                pointer-events: none !important;
            }
        `;
    }
}

    // === MAIN TIME MACHINE CLASS ===
    class YouTubeTimeMachine {
        constructor() {
            this.apiManager = new APIManager();
            this.subscriptionManager = new SubscriptionManager();
            this.recommendationEngine = new RecommendationEngine(this.apiManager);
            this.shortsBlocker = new ShortsBlocker();
            this.uiManager = new UIManager(this);

            this.settings = {
                date: GM_getValue('ytTimeMachineDate', '2014-06-14'),
                active: GM_getValue('ytTimeMachineActive', true),
                uiVisible: GM_getValue('ytTimeMachineUIVisible', true),
                lastAdvancedDate: GM_getValue('ytLastAdvancedDate', new Date().toDateString())
            };

            this.checkAndAdvanceDate();

            this.maxDate = new Date(this.settings.date);
            this.dateModifier = new DateModifier(this.maxDate);
            this.searchInterceptor = new SearchInterceptor(this.maxDate, this.shortsBlocker);

            this.isProcessing = false;
            this.videoCache = new Map();
            this.homepageLoadedCount = 0;

            this.stats = {
                filtered: 0,
                processed: 0,
                apiCalls: 0,
                cacheHits: 0
            };

            this.init();
        }

        setupAutoRefresh() {
            if (CONFIG.autoRefreshInterval) {
                this.log('Setting up aggressive auto-refresh every ' + (CONFIG.autoRefreshInterval / 60000) + ' minutes with ' + (CONFIG.refreshVideoPercentage * 100) + '% new videos');
                
                setInterval(() => {
                    if (this.isHomePage()) {
                        this.log('Auto-refresh triggered - loading fresh videos...');
                        this.loadVideosFromSubscriptions(true).then(() => {
                            const container = document.querySelector('ytd-browse[page-subtype="home"] ytd-rich-grid-renderer');
                            if (container) {
                                this.replaceHomepage(container, true);
                            }
                            this.log('Auto-refresh completed successfully');
                        }).catch(error => {
                            this.log('Auto-refresh failed:', error);
                        });
                    }
                }, CONFIG.autoRefreshInterval);
            }
        }

        checkAndAdvanceDate() {
            if (!CONFIG.autoAdvanceDays) return;

            const today = new Date().toDateString();
            const lastAdvanced = this.settings.lastAdvancedDate;

            if (today !== lastAdvanced) {
                const currentDate = new Date(this.settings.date);
                currentDate.setDate(currentDate.getDate() + 1);

                if (currentDate <= new Date()) {
                    this.settings.date = currentDate.toISOString().split('T')[0];
                    this.settings.lastAdvancedDate = today;

                    GM_setValue('ytTimeMachineDate', this.settings.date);
                    GM_setValue('ytLastAdvancedDate', today);

                    this.apiManager.clearCache();

                    this.log('Date auto-advanced to:', this.settings.date);
                }
            }
        }

        generateViewCount(publishedAt, referenceDate) {
            const videoDate = new Date(publishedAt);
            const refDate = new Date(referenceDate);
            const daysSinceUpload = Math.floor((refDate - videoDate) / (1000 * 60 * 60 * 24));

            let minViews, maxViews;

            if (daysSinceUpload <= 1) {
                minViews = 100;
                maxViews = 50000;
            } else if (daysSinceUpload <= 7) {
                minViews = 1000;
                maxViews = 500000;
            } else if (daysSinceUpload <= 30) {
                minViews = 5000;
                maxViews = 2000000;
            } else if (daysSinceUpload <= 365) {
                minViews = 10000;
                maxViews = 10000000;
            } else {
                minViews = 50000;
                maxViews = 50000000;
            }

            const multiplier = Math.random() * 0.8 + 0.2;
            const viewCount = Math.floor(minViews + (maxViews - minViews) * multiplier);

            return this.formatViewCount(viewCount);
        }

        formatViewCount(count) {
            if (count >= 1000000) {
                return (count / 1000000).toFixed(1) + 'M';
            } else if (count >= 1000) {
                return (count / 1000).toFixed(1) + 'K';
            }
            return count.toString();
        }

        init() {
            this.log('YouTube Time Machine initializing...');

            // Immediately nuke homepage if we're on it
            if (this.isHomePage()) {
                this.shortsBlocker.nukeHomepage();
            }

            this.addStyles();
            this.setupUI();

            if (this.settings.active) {
                this.startFiltering();
                this.searchInterceptor.setupSearchInterception();
                this.startHomepageReplacement();
                this.startVideoPageEnhancement();
                this.setupAutoRefresh();
            }

            this.log('YouTube Time Machine ready!');
        }

        addStyles() {
            const shortsCSS = this.shortsBlocker.getShortsBlockingCSS();
            const uiCSS = this.uiManager.getStyles();
            GM_addStyle(shortsCSS + uiCSS);
        }

        setupUI() {
            const ui = document.createElement('div');
            ui.id = 'timeMachineUI';
            if (!this.settings.uiVisible) {
                ui.classList.add('hidden');
            }

            ui.innerHTML = this.uiManager.getUIHTML();

            const toggle = document.createElement('button');
            toggle.id = 'timeMachineToggle';
            toggle.innerHTML = '🕰️';
            toggle.title = 'Show Time Machine';
            if (this.settings.uiVisible) {
                toggle.classList.remove('visible');
            } else {
                toggle.classList.add('visible');
            }

            const addToPage = () => {
                if (document.body) {
                    document.body.appendChild(ui);
                    document.body.appendChild(toggle);
                    this.attachEventListeners();
                } else {
                    setTimeout(addToPage, 100);
                }
            };

            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', addToPage);
            } else {
                addToPage();
            }
        }

        attachEventListeners() {
            window.timeMachine = this;

            document.getElementById('tmHideBtn').addEventListener('click', () => {
                this.toggleUI();
            });

            document.getElementById('timeMachineToggle').addEventListener('click', () => {
                this.toggleUI();
            });

            document.getElementById('tmSetDate').addEventListener('click', () => {
                const newDate = document.getElementById('tmDateInput').value;
                if (newDate) {
                    this.settings.date = newDate;
                    this.maxDate = new Date(newDate);
                    this.dateModifier.setMaxDate(newDate);
                    this.searchInterceptor.setMaxDate(newDate);
                    GM_setValue('ytTimeMachineDate', newDate);
                    this.apiManager.clearCache();
                    this.homepageLoadedCount = 0;
                    this.updateUI();
                    this.log('Date updated to:', newDate);
                }
            });

            document.getElementById('tmAddApi').addEventListener('click', () => {
                const key = document.getElementById('tmApiInput').value.trim();
                if (this.apiManager.addKey(key)) {
                    document.getElementById('tmApiInput').value = '';
                    this.updateUI();
                }
            });

            document.getElementById('tmTestAll').addEventListener('click', () => {
                const btn = document.getElementById('tmTestAll');
                btn.disabled = true;
                btn.textContent = 'Testing...';

                this.apiManager.testAllKeys().then(results => {
                    alert(results.join('\n'));
                }).finally(() => {
                    btn.disabled = false;
                    btn.textContent = 'Test All Keys';
                });
            });

            document.getElementById('tmAddSub').addEventListener('click', () => {
                const name = document.getElementById('tmSubInput').value.trim();
                if (this.subscriptionManager.addSubscription(name)) {
                    document.getElementById('tmSubInput').value = '';
                    this.updateUI();
                }
            });

            document.getElementById('tmLoadVideos').addEventListener('click', () => {
                const btn = document.getElementById('tmLoadVideos');
                btn.disabled = true;
                btn.textContent = 'Loading...';

                this.loadVideosFromSubscriptions().then(() => {
                    btn.textContent = 'Videos Loaded!';
                }).catch(error => {
                    btn.textContent = 'Load Failed';
                    this.log('Failed to load videos:', error);
                }).finally(() => {
                    setTimeout(() => {
                        btn.disabled = false;
                        btn.textContent = 'Load Videos';
                    }, 2000);
                });
            });

            document.getElementById('tmToggle').addEventListener('click', () => {
                this.settings.active = !this.settings.active;
                GM_setValue('ytTimeMachineActive', this.settings.active);
                location.reload();
            });

            document.getElementById('tmClearCache').addEventListener('click', () => {
                this.apiManager.clearCache();
                this.videoCache.clear();
                this.homepageLoadedCount = 0;
                this.updateUI();
            });

            document.addEventListener('keydown', (e) => {
                if (e.ctrlKey && e.shiftKey && e.key === 'T') {
                    e.preventDefault();
                    this.toggleUI();
                }
            });

            const refreshBtn = document.getElementById('tmRefreshVideosBtn');
            if (refreshBtn) {
                refreshBtn.addEventListener('click', () => {
                    refreshBtn.disabled = true;
                    refreshBtn.textContent = 'Refreshing...';

                    this.loadVideosFromSubscriptions().then(() => {
                        const container = document.querySelector('ytd-browse[page-subtype="home"] ytd-rich-grid-renderer');
                        if (container) {
                            this.replaceHomepage(container);
                        }
                        refreshBtn.textContent = 'Refreshed!';
                    }).catch(error => {
                        refreshBtn.textContent = 'Refresh Failed';
                        this.log('Manual refresh failed:', error);
                    }).finally(() => {
                        setTimeout(() => {
                            refreshBtn.disabled = false;
                            refreshBtn.textContent = 'Refresh Videos';
                        }, 2000);
                    });
                });
            }
        }

        toggleUI() {
            this.settings.uiVisible = !this.settings.uiVisible;
            GM_setValue('ytTimeMachineUIVisible', this.settings.uiVisible);

            const ui = document.getElementById('timeMachineUI');
            const toggle = document.getElementById('timeMachineToggle');

            if (this.settings.uiVisible) {
                ui.classList.remove('hidden');
                toggle.classList.remove('visible');
            } else {
                ui.classList.add('hidden');
                toggle.classList.add('visible');
            }
        }

        updateUI() {
            const ui = document.getElementById('timeMachineUI');
            if (ui) {
                ui.innerHTML = this.uiManager.getUIHTML();
                this.attachEventListeners();
            }
        }

        removeApiKey(index) {
            if (this.apiManager.removeKey(this.apiManager.keys[index])) {
                this.updateUI();
            }
        }

        removeSubscription(index) {
            if (this.subscriptionManager.removeSubscription(index)) {
                this.updateUI();
            }
        }

        async loadVideosFromSubscriptions(isRefresh = false) {
            const subscriptions = this.subscriptionManager.getSubscriptions();

            if (subscriptions.length === 0) {
                throw new Error('No subscriptions to load from');
            }

            this.log('Loading videos from subscriptions' + (isRefresh ? ' (refresh mode)' : '') + '...');

            // Get existing videos for refresh mixing
            const existingVideos = this.videoCache.get('subscription_videos') || [];
            
            const allVideos = [];
            const endDate = new Date(this.maxDate);
            endDate.setHours(23, 59, 59, 999);

            // Load viral videos alongside subscription videos
            const viralVideos = await this.apiManager.getViralVideos(endDate, isRefresh);
            this.log('Loaded ' + viralVideos.length + ' viral videos');

            for (let i = 0; i < subscriptions.length; i += CONFIG.batchSize) {
                const batch = subscriptions.slice(i, i + CONFIG.batchSize);

                const batchPromises = batch.map(async (sub) => {
                    try {
                        let channelId = sub.id;
                        if (!channelId) {
                            channelId = await this.getChannelIdByName(sub.name);
                        }

                        if (channelId) {
                            const videos = await this.getChannelVideos(channelId, sub.name, endDate, isRefresh);
                            return videos;
                        }
                        return [];
                    } catch (error) {
                        this.log('Failed to load videos for ' + sub.name + ':', error);
                        return [];
                    }
                });

                const batchResults = await Promise.all(batchPromises);
                batchResults.forEach(videos => allVideos.push.apply(allVideos, videos));

                if (i + CONFIG.batchSize < subscriptions.length) {
                    await new Promise(resolve => setTimeout(resolve, CONFIG.apiCooldown));
                }
            }

            // Mix with existing videos if this is a refresh
            let finalVideos = allVideos;
            if (isRefresh && existingVideos.length > 0) {
                const existingViralVideos = this.videoCache.get('viral_videos') || [];
                finalVideos = this.mixVideosForRefresh(allVideos, existingVideos, viralVideos, existingViralVideos);
                this.log('Mixed ' + allVideos.length + ' new videos with ' + existingVideos.length + ' existing videos + ' + viralVideos.length + ' viral videos');
            } else {
                // For initial load, just add viral videos to the mix
                finalVideos = allVideos.concat(viralVideos);
            }

            // Store viral videos separately for mixing
            this.videoCache.set('viral_videos', viralVideos);
            this.videoCache.set('subscription_videos', finalVideos);
            this.log('Loaded ' + finalVideos.length + ' total videos from subscriptions');

            return finalVideos;
        }

        mixVideosForRefresh(newVideos, existingVideos, newViralVideos = [], existingViralVideos = []) {
            // Calculate how many videos to keep from each set
            const totalDesired = Math.max(CONFIG.maxHomepageVideos, existingVideos.length);
            const viralVideoCount = Math.floor(totalDesired * CONFIG.viralVideoPercentage);
            const remainingSlots = totalDesired - viralVideoCount;
            const newVideoCount = Math.floor(remainingSlots * CONFIG.refreshVideoPercentage);
            const existingVideoCount = remainingSlots - newVideoCount;

            this.log('Mixing videos: ' + newVideoCount + ' new + ' + existingVideoCount + ' existing + ' + viralVideoCount + ' viral = ' + totalDesired + ' total');

            // Shuffle and select videos
            const selectedNew = this.shuffleArray(newVideos).slice(0, newVideoCount);
            const selectedExisting = this.shuffleArray(existingVideos).slice(0, existingVideoCount);
            
            // Mix viral videos (prefer new viral over existing)
            const allViralVideos = newViralVideos.concat(existingViralVideos);
            const selectedViral = this.shuffleArray(allViralVideos).slice(0, viralVideoCount);

            // Combine and shuffle the final mix
            const mixedVideos = selectedNew.concat(selectedExisting).concat(selectedViral);
            return this.shuffleArray(mixedVideos);
        }

        async getChannelIdByName(channelName) {
            const cacheKey = 'channel_id_' + channelName;
            let channelId = this.apiManager.getCache(cacheKey);

            if (!channelId) {
                try {
                    const response = await this.apiManager.makeRequest(this.apiManager.baseUrl + '/search', {
                        part: 'snippet',
                        q: channelName,
                        type: 'channel',
                        maxResults: 1
                    });

                    if (response.items && response.items.length > 0) {
                        channelId = response.items[0].snippet.channelId;
                        this.apiManager.setCache(cacheKey, channelId);
                        this.stats.apiCalls++;
                    }
                } catch (error) {
                    this.log('Failed to find channel ID for ' + channelName + ':', error);
                }
            } else {
                this.stats.cacheHits++;
            }

            return channelId;
        }

        async getChannelVideos(channelId, channelName, endDate, forceRefresh = false) {
            const cacheKey = 'channel_videos_' + channelId + '_' + this.settings.date;
            let videos = this.apiManager.getCache(cacheKey, forceRefresh);

            if (!videos) {
                try {
                    const response = await this.apiManager.makeRequest(this.apiManager.baseUrl + '/search', {
                        part: 'snippet',
                        channelId: channelId,
                        type: 'video',
                        order: 'date',
                        publishedBefore: endDate.toISOString(),
                        maxResults: CONFIG.videosPerChannel
                    });

                    videos = response.items ? response.items.map(item => ({
                        id: item.id.videoId,
                        title: item.snippet.title,
                        channel: item.snippet.channelTitle || channelName,
                        channelId: item.snippet.channelId,
                        thumbnail: item.snippet.thumbnails && item.snippet.thumbnails.medium ? item.snippet.thumbnails.medium.url : (item.snippet.thumbnails && item.snippet.thumbnails.default ? item.snippet.thumbnails.default.url : ''),
                        publishedAt: item.snippet.publishedAt,
                        description: item.snippet.description || '',
                        viewCount: this.generateViewCount(item.snippet.publishedAt, endDate),
                        relativeDate: this.dateModifier.formatRelativeDate(item.snippet.publishedAt, endDate)
                    })) : [];

                    this.apiManager.setCache(cacheKey, videos, forceRefresh);
                    this.stats.apiCalls++;
                } catch (error) {
                    this.log('Failed to get videos for channel ' + channelName + ':', error);
                    videos = [];
                }
            } else {
                this.stats.cacheHits++;
            }

            return videos;
        }

        startFiltering() {
            this.log('Starting ultra-aggressive video filtering...');

            const filterVideos = () => {
                if (this.isProcessing) return;
                this.isProcessing = true;

                const videoSelectors = [
                    'ytd-video-renderer',
                    'ytd-grid-video-renderer',
                    'ytd-rich-item-renderer',
                    'ytd-compact-video-renderer',
                    'ytd-movie-renderer',
                    'ytd-playlist-renderer',
                    'ytd-channel-renderer',
                    'ytd-shelf-renderer',
                    'ytd-rich-shelf-renderer',
                    'ytd-horizontal-card-list-renderer',
                    'ytd-compact-movie-renderer',
                    'ytd-compact-playlist-renderer',
                    'ytd-compact-station-renderer',
                    'ytd-compact-radio-renderer'
                ];

                this.cleanupWatchNext();
                this.shortsBlocker.blockShorts();
                this.dateModifier.updateDates();

                let processed = 0;
                let filtered = 0;

                videoSelectors.forEach(selector => {
                    const videos = document.querySelectorAll(selector);
                    videos.forEach(video => {
                        if (video.dataset.tmProcessed) return;

                        video.dataset.tmProcessed = 'true';
                        processed++;

                        if (this.shouldHideVideo(video)) {
                            video.classList.add('yt-time-machine-hidden');
                            filtered++;
                        } else {
                            video.classList.add('tm-approved');
                            video.classList.remove('yt-time-machine-hidden');
                        }
                    });
                });

                if (processed > 0) {
                    this.stats.processed += processed;
                    this.stats.filtered += filtered;
                    this.log('Processed ' + processed + ', filtered ' + filtered);
                }

                this.isProcessing = false;
            };

            filterVideos();

            setInterval(filterVideos, CONFIG.updateInterval);

            const observer = new MutationObserver(() => {
                setTimeout(filterVideos, 10);
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });
        }

        cleanupWatchNext() {
            // Don't aggressively hide watch next content - let our enhancement handle it
            const autoplayRenderer = document.querySelector('ytd-compact-autoplay-renderer');
            if (autoplayRenderer && !autoplayRenderer.dataset.tmHidden) {
                autoplayRenderer.dataset.tmHidden = 'true';
                autoplayRenderer.style.display = 'none';
            }
        }

        shouldHideVideo(videoElement) {
            if (this.shortsBlocker.isShorts(videoElement)) {
                return true;
            }

            const videoDate = this.extractVideoDate(videoElement);
            if (videoDate && videoDate > this.maxDate) {
                return true;
            }

            return false;
        }

        extractVideoDate(videoElement) {
            const dateSelectors = [
                '#metadata-line span:last-child',
                '.ytd-video-meta-block span:last-child',
                '#published-time-text',
                '[aria-label*="ago"]'
            ];

            for (const selector of dateSelectors) {
                const element = videoElement.querySelector(selector);
                if (element) {
                    const dateText = element.textContent && element.textContent.trim();
                    if (dateText) {
                        return this.dateModifier.parseRelativeDate(dateText);
                    }
                }
            }

            return null;
        }

        startHomepageReplacement() {
            const replaceHomepage = () => {
                if (this.isHomePage()) {
                    const container = document.querySelector('ytd-browse[page-subtype="home"] ytd-rich-grid-renderer');
                    if (container && !container.dataset.tmReplaced) {
                        container.dataset.tmReplaced = 'true';
                        this.replaceHomepage(container);
                    }
                }
            };

            setInterval(replaceHomepage, 1000);
        }

        isHomePage() {
            return location.pathname === '/' || location.pathname === '';
        }

        async replaceHomepage(container, isRefresh = false) {
            this.log('Replacing homepage' + (isRefresh ? ' (refresh)' : '') + '...');

            container.innerHTML = '<div class="tm-homepage">' +
                '<div class="tm-loading">' +
                '<div class="tm-spinner"></div>' +
                '<div>' + (isRefresh ? 'Refreshing' : 'Loading') + ' your time capsule from ' + this.maxDate.toLocaleDateString() + '...</div>' +
                '</div>' +
                '</div>';

            try {
                let videos = this.videoCache.get('subscription_videos');
                if (!videos || videos.length === 0) {
                    videos = await this.loadVideosFromSubscriptions(isRefresh);
                }

                if (videos.length > 0) {
                    const homepageHTML = this.createHomepageHTML(videos, isRefresh);
                    container.innerHTML = homepageHTML;
                    this.attachVideoClickHandlers();
                    this.attachLoadMoreHandler();
                } else {
                    container.innerHTML = '<div class="tm-homepage">' +
                        '<div class="tm-loading">' +
                        '<div>No videos found from ' + this.maxDate.toLocaleDateString() + '</div>' +
                        '</div>' +
                        '</div>';
                }
            } catch (error) {
                this.log('Homepage replacement failed:', error);
                container.innerHTML = '<div class="tm-homepage">' +
                    '<div class="tm-loading">' +
                    '<div>Failed to ' + (isRefresh ? 'refresh' : 'load') + ' time capsule</div>' +
                    '<div style="margin-top: 10px; font-size: 12px; opacity: 0.7;">' +
                    error.message +
                    '</div>' +
                    '</div>' +
                    '</div>';
            }
        }

        createHomepageHTML(videos, isRefresh = false) {
            const targetDate = new Date(this.maxDate);
            
            // Separate viral videos from regular videos
            const viralVideos = videos.filter(video => video.isViral);
            const regularVideos = videos.filter(video => !video.isViral);
            
            // Filter by date
            const validViralVideos = viralVideos.filter(video => new Date(video.publishedAt) <= this.maxDate);
            const validRegularVideos = regularVideos.filter(video => new Date(video.publishedAt) <= this.maxDate);
            
            // Combine and shuffle for final display
            const finalVideos = this.shuffleArray(validRegularVideos.concat(validViralVideos));

            const videoCards = finalVideos.map(video => 
                '<div class="tm-video-card' + (video.isViral ? ' tm-viral-video' : '') + '" data-video-id="' + video.id + '">' +
                '<img class="tm-video-thumbnail" src="' + video.thumbnail + '" alt="' + video.title + '" loading="lazy">' +
                '<div class="tm-video-info">' +
                '<div class="tm-video-title">' + video.title + '</div>' +
                '<div class="tm-video-channel">' + video.channel + '</div>' +
                '<div class="tm-video-meta">' +
                '<span class="tm-video-views">' + video.viewCount + ' views</span>' +
                '<span class="tm-video-date">' + video.relativeDate + '</span>' +
                '</div>' +
                '</div>' +
                '</div>'
            ).join('');

            const viralCount = validViralVideos.length;
            const regularCount = validRegularVideos.length;
            const refreshIndicator = isRefresh ? ' (refreshed with ' + (CONFIG.refreshVideoPercentage * 100) + '% new content + ' + (CONFIG.viralVideoPercentage * 100) + '% viral)' : '';

            return '<div class="tm-homepage">' +
                '<div style="font-size: 14px; color: var(--yt-spec-text-secondary); margin-bottom: 20px;">' +
                regularCount + ' subscription videos + ' + viralCount + ' viral videos' + refreshIndicator +
                '</div>' +
                '<div class="tm-video-grid">' +
                videoCards +
                '</div>' +
                '<div style="text-align: center; margin-top: 30px;">' +
                '<button id="tmLoadMoreBtn" class="tm-load-more-btn">Load more videos</button>' +
                '</div>' +
                '</div>';
        }

        shuffleArray(array) {
            const shuffled = array.slice();
            for (let i = shuffled.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                const temp = shuffled[i];
                shuffled[i] = shuffled[j];
                shuffled[j] = temp;
            }
            return shuffled;
        }

        attachVideoClickHandlers() {
            document.querySelectorAll('.tm-video-card').forEach(card => {
                card.addEventListener('click', () => {
                    const videoId = card.dataset.videoId;
                    if (videoId) {
                        window.location.href = '/watch?v=' + videoId;
                    }
                });
            });
        }

        attachLoadMoreHandler() {
            const loadMoreBtn = document.getElementById('tmLoadMoreBtn');
            if (loadMoreBtn) {
                loadMoreBtn.addEventListener('click', () => {
                    this.homepageLoadedCount += CONFIG.homepageLoadMoreSize;

                    const container = document.querySelector('ytd-browse[page-subtype="home"] ytd-rich-grid-renderer');
                    if (container) {
                        const videos = this.videoCache.get('subscription_videos');
                        if (videos) {
                            const homepageHTML = this.createHomepageHTML(videos);
                            container.innerHTML = homepageHTML;
                            this.attachVideoClickHandlers();
                            this.attachLoadMoreHandler();
                        }
                    }
                });
            }
        }

        startVideoPageEnhancement() {
            const enhanceVideoPage = () => {
                if (this.isVideoPage()) {
                    const sidebar = document.querySelector('#secondary');
                    if (sidebar && !sidebar.dataset.tmEnhanced) {
                        sidebar.dataset.tmEnhanced = 'true';
                        this.enhanceVideoPage(sidebar);
                    }
                }
            };

            setInterval(enhanceVideoPage, 2000);
        }

        isVideoPage() {
            return location.pathname === '/watch';
        }

        async enhanceVideoPage(sidebar) {
            this.log('Enhancing video page...');

            // Only hide specific YouTube elements, not all content
            const elementsToHide = [
                'ytd-watch-next-secondary-results-renderer',
                'ytd-compact-autoplay-renderer'
            ];
            
            elementsToHide.forEach(selector => {
                const elements = sidebar.querySelectorAll(selector);
                elements.forEach(el => {
                    if (el && typeof el === 'object' && el.style) {
                        el.style.display = 'none';
                    }
                });
            });

            const currentChannelId = this.getCurrentChannelId();
            const currentVideoTitle = this.getCurrentVideoTitle();
            const currentChannelName = this.getCurrentChannelName();

            this.log('Current video context:', {
                channelId: currentChannelId,
                channelName: currentChannelName,
                videoTitle: currentVideoTitle
            });

            try {
                let videos = this.videoCache.get('subscription_videos');
                if (!videos || videos.length === 0) {
                    this.log('Loading subscription videos for watch next...');
                    videos = await this.loadVideosFromSubscriptions();
                }

                // Get more videos from current channel if we have channel ID
                let freshVideos = [];
                if (currentChannelId) {
                    try {
                        const endDate = new Date(this.maxDate);
                        endDate.setHours(23, 59, 59, 999);
                        
                        const startDate = new Date(endDate);
                        startDate.setMonth(startDate.getMonth() - 3); // 3 months back for stability
                        
                        freshVideos = await this.apiManager.getChannelVideosForPage(currentChannelId, currentChannelName, endDate, startDate);
                        this.log('Loaded ' + freshVideos.length + ' fresh videos from current channel');
                    } catch (error) {
                        this.log('Failed to load fresh videos:', error);
                    }
                }

                // Get series matching videos
                let seriesVideos = [];
                if (currentVideoTitle && currentChannelName) {
                    try {
                        const enhancedQuery = this.createNextEpisodeQuery(currentVideoTitle, currentChannelName);
                        console.log('[TimeMachine] Enhanced series search query:', enhancedQuery);
                        seriesVideos = await this.apiManager.searchVideos(enhancedQuery, 10, this.maxDate);
                        this.log('Loaded ' + seriesVideos.length + ' series matching videos');
                    } catch (error) {
                        this.log('Failed to load series videos:', error);
                    }
                }

                if (videos.length > 0) {
                    const recommendations = await this.recommendationEngine.generateEnhancedRecommendations(
                        currentChannelId,
                        currentVideoTitle,
                        videos,
                        this.maxDate,
                        freshVideos,
                        seriesVideos
                    );

                    this.log('Generated ' + recommendations.length + ' total recommendations');
                    this.createWatchNextSection(sidebar, recommendations);
                } else {
                    this.log('No videos available for recommendations');
                    // Create empty watch next section
                    this.createWatchNextSection(sidebar, []);
                }
            } catch (error) {
                this.log('Video page enhancement failed:', error);
                // Create fallback watch next section
                this.createWatchNextSection(sidebar, []);
            }
        }

        getCurrentChannelName() {
            const channelElement = document.querySelector('ytd-video-owner-renderer #channel-name a, #owner-name a');
            return channelElement ? channelElement.textContent.trim() : '';
        }

        getCurrentVideoTitle() {
            const titleElement = document.querySelector('h1.ytd-video-primary-info-renderer, h1.ytd-watch-metadata');
            return titleElement ? titleElement.textContent.trim() : '';
        }

        getCurrentChannelId() {
            const channelLink = document.querySelector('ytd-video-owner-renderer a, #owner-name a');
            if (channelLink) {
                const href = channelLink.getAttribute('href');
                if (href) {
                    const match = href.match(/\/(channel|c|user)\/([^\/]+)/);
                    if (match) {
                        return match[2];
                    }
                }
            }
            return null;
        }

        createWatchNextSection(secondary, recommendations) {
            // Remove any existing time machine sections first
            const existingSections = secondary.querySelectorAll('.tm-video-page-section');
            existingSections.forEach(section => {
                if (section && section.parentNode) {
                    section.parentNode.removeChild(section);
                }
            });

            const videoPageHTML = this.createVideoPageHTML(recommendations);
            const tmSection = document.createElement('div');
            tmSection.innerHTML = videoPageHTML;
            
            // Insert at the top of secondary content
            if (secondary.firstChild) {
                secondary.insertBefore(tmSection, secondary.firstChild);
            } else {
                secondary.appendChild(tmSection);
            }
            
            this.attachVideoPageClickHandlers();
        }

        createNextEpisodeQuery(videoTitle, channelName) {
            // Create a query that looks for the next episode by incrementing numbers
            let enhancedTitle = videoTitle;
            
            // Find numbers in the title and increment them
            enhancedTitle = enhancedTitle.replace(/(d+)/g, (match, number) => {
                const nextNumber = parseInt(number) + 1;
                console.log('[TimeMachine] Incrementing episode number: ' + number + ' -> ' + nextNumber);
                return nextNumber.toString();
            });
            
            // Also search for the original title to get related videos
            const queries = [
                enhancedTitle + ' ' + channelName,  // Next episode search
                videoTitle + ' ' + channelName       // Original series search
            ];
            
            // Return the enhanced query (we'll use the first one for now)
            return queries[0];
        }

        createVideoPageHTML(videos) {
            if (!videos || videos.length === 0) {
                return '<div class="tm-video-page-section">' +
                    '<h3 class="tm-video-page-title">Watch next</h3>' +
                    '<div class="tm-loading">' +
                    '<div>Loading recommendations from ' + this.maxDate.toLocaleDateString() + '...</div>' +
                    '</div>' +
                    '</div>';
            }
            
            const videoCards = videos.slice(0, Math.min(CONFIG.watchNextVideosCount, videos.length)).map(video => 
                '<div class="tm-video-page-card" data-video-id="' + video.id + '">' +
                '<img class="tm-video-page-thumbnail" src="' + video.thumbnail + '" alt="' + video.title + '" loading="lazy">' +
                '<div class="tm-video-page-info">' +
                '<div class="tm-video-page-video-title">' + video.title + '</div>' +
                '<div class="tm-video-page-channel">' + video.channel + '</div>' +
                '<div class="tm-video-page-meta">' +
                '<span class="tm-video-views">' + video.viewCount + ' views</span>' +
                '<span>•</span>' +
                '<span class="tm-video-date">' + video.relativeDate + '</span>' +
                '</div>' +
                '</div>' +
                '</div>'
            ).join('');

            return '<div class="tm-video-page-section">' +
                '<h3 class="tm-video-page-title">Watch next</h3>' +
                '<div class="tm-video-page-grid">' +
                videoCards +
                '</div>' +
                '</div>';
        }

        attachVideoPageClickHandlers() {
            document.querySelectorAll('.tm-video-page-card').forEach(card => {
                card.addEventListener('click', () => {
                    const videoId = card.dataset.videoId;
                    if (videoId) {
                        window.location.href = '/watch?v=' + videoId;
                    }
                });
            });
        }

        log() {
            if (CONFIG.debugMode) {
                console.log.apply(console, ['[Time Machine]'].concat(Array.prototype.slice.call(arguments)));
            }
        }
    }

    function init() {
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                new YouTubeTimeMachine();
            });
        } else {
            new YouTubeTimeMachine();
        }
    }

    init();

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址