Edna.cz Future Schedule with Enhanced Countdown

Adds upcoming episodes to the main page summary

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Edna.cz Future Schedule with Enhanced Countdown
// @name:cs         Edna.cz Budoucí Epizody s Odpočtem
// @namespace    https://greasyfork.org/en/scripts/530726-edna-cz-future-schedule-with-enhanced-countdown
// @version      0.93
// @description  Adds upcoming episodes to the main page summary
// @description:cs  Přidá budoucí epizody do přehledu na hlavní stránce
// @author       Setcher
// @match        https://www.edna.cz/
// @match        https://www.edna.cz/?*
// @match        https://www.edna.cz/#
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_xmlhttpRequest
// @grant        GM_getResourceText
// @grant        GM_addStyle
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.2/jquery.modal.min.js
// @resource     modalCSS https://cdnjs.cloudflare.com/ajax/libs/jquery-modal/0.9.2/jquery.modal.min.css
// ==/UserScript==

(function() {
    'use strict';

    // Add modal CSS
    GM_addStyle(GM_getResourceText("modalCSS"));
    GM_addStyle(`
        .future-episode {
            opacity: 0.9;
        }
        .countdown-cell {
            text-align: right !important;
            white-space: nowrap;
            min-width: 120px;
        }
        .seen-episode {
            display: none;
        }
        #edna-settings-btn {
            margin-left: 0.5rem;
        }
        /* Dark mode for modal */
        #edna-settings-modal {
            color: #f8f9fa;
        }
        #edna-settings-modal > div {
            background-color: #343a40 !important;
            border: 1px solid #495057;
        }
        #edna-settings-modal h2 {
            color: #f8f9fa !important;
        }
        #edna-settings-modal label {
            color: #dee2e6 !important;
        }
        #edna-settings-modal input[type="number"],
        #edna-settings-modal input[type="checkbox"] {
            background-color: #495057;
            border-color: #6c757d;
            color: #f8f9fa;
        }
        #edna-settings-modal .btn-secondary {
            background-color: #6c757d;
            border-color: #6c757d;
        }
        #edna-settings-modal .btn-primary {
            background-color: #0d6efd;
            border-color: #0d6efd;
        }
        /* Combine rating and seen columns for countdown */
        .combined-countdown-column {
            display: flex;
            justify-content: space-between;
            align-items: center;
            min-width: 120px;
        }
        .combined-countdown-column .stars {
            margin-right: 8px;
        }
    `);

    // Settings
    const defaultSettings = {
        maxDaysInFuture: 7,
        maxFutureItems: 7,
        countdownFormat: 'DD:HH:MM:SS',
        hideSeenEpisodes: true
    };

    let settings = {
        ...defaultSettings,
        ...{
            maxDaysInFuture: GM_getValue('maxDaysInFuture', defaultSettings.maxDaysInFuture),
            maxFutureItems: GM_getValue('maxFutureItems', defaultSettings.maxFutureItems),
            countdownFormat: GM_getValue('countdownFormat', defaultSettings.countdownFormat),
            hideSeenEpisodes: GM_getValue('hideSeenEpisodes', defaultSettings.hideSeenEpisodes)
        }
    };

    // Create settings modal
    function createSettingsModal() {
        const modalHTML = `
            <div id="edna-settings-modal" class="modal" style="display: none;">
                <div style="padding: 20px; border-radius: 5px; max-width: 500px; margin: 0 auto;">
                    <h2 style="margin-top: 0;">Nastavení budoucích epizod</h2>

                    <div style="margin-bottom: 15px;">
                        <label style="display: block; margin-bottom: 5px; font-weight: bold;">Maximální dní dopředu:</label>
                        <input type="number" id="edna-max-days" value="${settings.maxDaysInFuture}" min="1" max="60"
                               style="width: 100%; padding: 8px; border-radius: 4px;">
                    </div>

                    <div style="margin-bottom: 15px;">
                        <label style="display: block; margin-bottom: 5px; font-weight: bold;">Maximálně epizod:</label>
                        <input type="number" id="edna-max-items" value="${settings.maxFutureItems}" min="1" max="100"
                               style="width: 100%; padding: 8px; border-radius: 4px;">
                    </div>

                    <div style="margin-bottom: 15px;">
                        <label style="display: block; margin-bottom: 8px; font-weight: bold;">Formát odpočtu:</label>
                        <div style="display: flex; flex-direction: column; gap: 8px;">
                            <label style="display: flex; align-items: center; gap: 8px;">
                                <input type="radio" name="countdownFormat" value="DD:HH" ${settings.countdownFormat === 'DD:HH' ? 'checked' : ''}>
                                <span>DD:HH (Dny a hodiny)</span>
                            </label>
                            <label style="display: flex; align-items: center; gap: 8px;">
                                <input type="radio" name="countdownFormat" value="DD:HH:MM" ${settings.countdownFormat === 'DD:HH:MM' ? 'checked' : ''}>
                                <span>DD:HH:MM (Dny, hodiny, minuty)</span>
                            </label>
                            <label style="display: flex; align-items: center; gap: 8px;">
                                <input type="radio" name="countdownFormat" value="DD:HH:MM:SS" ${settings.countdownFormat === 'DD:HH:MM:SS' ? 'checked' : ''}>
                                <span>DD:HH:MM:SS (Přesný odpočet)</span>
                            </label>
                        </div>
                    </div>

                    <div style="margin-bottom: 20px;">
                        <label style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
                            <input type="checkbox" id="edna-hide-seen" ${settings.hideSeenEpisodes ? 'checked' : ''}>
                            <span style="font-weight: bold;">Skrýt již zhlédnuté epizody</span>
                        </label>
                    </div>

                    <div style="display: flex; justify-content: flex-end; gap: 10px;">
                        <button id="edna-cancel-settings" class="btn btn-secondary">Zrušit</button>
                        <button id="edna-save-settings" class="btn btn-primary">Uložit</button>
                    </div>
                </div>
            </div>
        `;

        $('body').append(modalHTML);

        // Add settings button to controls
        const settingsBtn = $(`
            <a href="#" class="nav-link" id="edna-settings-btn">
                <i class="bi bi-gear-fill"></i> Nastavení
            </a>
        `);
        $('.custom-tabs').append(settingsBtn);

        // Modal handlers
        $('#edna-settings-btn').click(function(e) {
            e.preventDefault();
            $('#edna-settings-modal').modal({
                escapeClose: true,
                clickClose: false,
                showClose: false
            });
        });

        $('#edna-save-settings').click(function() {
            settings.maxDaysInFuture = parseInt($('#edna-max-days').val());
            settings.maxFutureItems = parseInt($('#edna-max-items').val());
            settings.countdownFormat = $('input[name="countdownFormat"]:checked').val();
            settings.hideSeenEpisodes = $('#edna-hide-seen').is(':checked');

            GM_setValue('maxDaysInFuture', settings.maxDaysInFuture);
            GM_setValue('maxFutureItems', settings.maxFutureItems);
            GM_setValue('countdownFormat', settings.countdownFormat);
            GM_setValue('hideSeenEpisodes', settings.hideSeenEpisodes);

            $.modal.close();
            location.reload();
        });

        $('#edna-cancel-settings').click(function() {
            $.modal.close();
        });
    }

    // Main function to load and process future schedule
    function processFutureSchedule() {
        // Wait for the main schedule table to load
        const checkTable = setInterval(function() {
            const $scheduleTable = $('table.table-responsive');
            if ($scheduleTable.length && $('#snippet--episodes').length) {
                clearInterval(checkTable);
                loadFutureSchedule();
            }
        }, 200);
    }

    // Fix episode images (replace placeholder with data-src)
    function fixImages($element) {
        $element.find('img[data-src]').each(function() {
            const $img = $(this);
            if ($img.attr('src') !== $img.attr('data-src')) {
                $img.attr('src', $img.attr('data-src'));
            }
        });
    }

    // Convert timestamp to countdown string
    function getCountdownString(timestamp) {
        const now = new Date().getTime();
        const diff = timestamp - now;

        if (diff < 0) return "Právě vysíláno";

        const days = Math.floor(diff / (1000 * 60 * 60 * 24));
        const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
        const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
        const seconds = Math.floor((diff % (1000 * 60)) / 1000);

        switch(settings.countdownFormat) {
            case 'DD:HH':
                return `${days}d ${hours}h`;
            case 'DD:HH:MM':
                return `${days}d ${hours}h ${minutes}m`;
            case 'DD:HH:MM:SS':
            default:
                return `${days}d ${hours}h ${minutes}m ${seconds}s`;
        }
    }

    // Update all countdowns
    function updateCountdowns() {
        $('.countdown-text').each(function() {
            const $cell = $(this);
            const timestamp = parseInt($cell.attr('data-countdown'));
            $cell.text(getCountdownString(timestamp));
        });
    }

    // Hide seen episodes if setting is enabled
    function handleSeenEpisodes() {
        if (settings.hideSeenEpisodes) {
            $('a[title^="Označit jako viděno"], a[title^="Označit jako neviděno"]').each(function() {
                const $link = $(this);
                const isWatched = $link.attr('title').startsWith("Označit jako neviděno");
                if (isWatched) {
                    $link.closest('tr').addClass('seen-episode');
                }
            });
        } else {
            $('.seen-episode').removeClass('seen-episode');
        }
    }

    // Load and merge future episodes
    function loadFutureSchedule() {
        GM_xmlhttpRequest({
            method: "GET",
            url: "https://www.edna.cz/vysilani/nadchazejici/",
            onload: function(response) {
                const $futureHtml = $($.parseHTML(response.responseText));
                const $scheduleTable = $('table.table-responsive');
                const $episodesContainer = $('#snippet--episodes');

                if (!$scheduleTable.length || !$episodesContainer.length) {
                    console.error('Required elements not found');
                    return;
                }

                const now = new Date();
                const maxDate = new Date(now.getTime() + (settings.maxDaysInFuture * 24 * 60 * 60 * 1000));
                let addedItems = 0;

                $futureHtml.find('#snippet--episodes table.episodes tr').each(function() {
                    if (addedItems >= settings.maxFutureItems) return false;

                    const $row = $(this);
                    const $timeCell = $row.find('td[data-countdown]');

                    if ($timeCell.length) {
                        const timestamp = parseInt($timeCell.attr('data-countdown'));
                        const itemDate = new Date(timestamp);

                        if (itemDate > now && itemDate <= maxDate) {
                            // Extract show information
                            const $showLink = $row.find('td:nth-child(3) a');
                            const showLink = $showLink.attr('href');
                            const showTitle = $showLink.text();
                            const episodeCode = $row.find('td:nth-child(4)').text();
                            const imgSrc = $row.find('img').attr('data-src');
                            const dateText = $timeCell.text().trim().split(' | ')[0];
                            const shortDate = dateText.split('.').slice(0, 2).join('.');

                            // Create new row matching the current layout
                            const $newRow = $(`
                                <tr class="future-episode">
                                    <td class="text-secondary text-nowrap ps-3 text-start" title="${dateText}">${shortDate}</td>
                                    <td onclick="location.href='${showLink}'" class="cursor-pointer" href="${showLink}">
                                        <div class="d-flex align-items-center">
                                            <div class="latest-series-image position-relative rounded-circle overflow-hidden">
                                                <img src="${imgSrc}" alt="${showTitle}" class="position-absolute top-0 start-0 w-100">
                                            </div>
                                            <div class="ms-3">
                                                <a class="text-reset text-decoration-none" href="${showLink}">
                                                    <p class="m-0 my-auto text-start text-white">${episodeCode} ${showTitle}</p>
                                                </a>
                                            </div>
                                        </div>
                                    </td>
                                    <td class="pe-3 combined-countdown-column">
                                        <span class="countdown-text" data-countdown="${timestamp}" title="${$timeCell.text().trim()}"></span>
                                    </td>
                                </tr>
                            `);

                            $episodesContainer.prepend($newRow);
                            fixImages($newRow);
                            addedItems++;
                        }
                    }
                });

                if (addedItems > 0) {
                    handleSeenEpisodes();
                    updateCountdowns();
                    const updateInterval = settings.countdownFormat === 'DD:HH:MM:SS' ? 1000 : 60000;
                    setInterval(updateCountdowns, updateInterval);
                }
            },
            onerror: function(error) {
                console.error('Error loading future schedule:', error);
            }
        });
    }

    // Initialize
    $(document).ready(function() {
        createSettingsModal();
        // Wait a bit longer to ensure all elements are loaded
        setTimeout(processFutureSchedule, 1500);
    });
})();