Greasy Fork

Greasy Fork is available in English.

KickSkip - Jump to Timestamps on Kick.com Videos

copy, paste, and jump to specific video timestamps effortlessly.

当前为 2024-12-26 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         KickSkip - Jump to Timestamps on Kick.com Videos
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  copy, paste, and jump to specific video timestamps effortlessly.
// @match        https://kick.com/*/videos/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    const styles = `
        .timestamp-overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            display: none;
            opacity: 0;
            justify-content: center;
            align-items: center;
            z-index: 10000;
            backdrop-filter: blur(2px);
        }

        .timestamp-overlay.visible {
            opacity: 1;
        }

        .timestamp-modal {
            background-color: #1a1a1a;
            border-radius: 12px;
            padding: 24px;
            width: 420px;
            box-shadow: 0 8px 32px rgba(0, 0, 0, 0.24);
            text-align: center;
            opacity: 0;
            transform: scale(0.95);
            transition: all 0.2s ease;
        }

        .timestamp-overlay.visible .timestamp-modal {
            opacity: 1;
            transform: scale(1);
        }

        .timestamp-header {
            position: relative;
            margin-bottom: 20px;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .timestamp-title {
            color: white;
            font-size: 18px;
            font-weight: 600;
            margin: 0;
            text-align: center;
        }

        .close-button {
            position: absolute;
            right: 0;
            top: 50%;
            transform: translateY(-50%);
            background: none;
            border: none;
            color: #777;
            font-size: 24px;
            cursor: pointer;
            padding: 0;
            line-height: 1;
        }

        .close-button:hover {
            color: #fff;
        }

        .timestamp-form {
            display: flex;
            flex-direction: column;
            gap: 16px;
            align-items: center;
        }

        .input-group {
            display: flex;
            gap: 8px;
            justify-content: center;
        }

        .time-input {
            background-color: #2a2a2a;
            border: 1px solid #3a3a3a;
            border-radius: 8px;
            color: white;
            padding: 12px;
            width: 70px;
            text-align: center;
            font-size: 16px;
        }

        .time-input:focus {
            outline: none;
            border-color: #666;
            background-color: #333;
        }

        .time-input::placeholder {
            color: #666;
        }

        .time-input:focus::placeholder {
            color: #888;
        }

        .action-buttons {
            display: flex;
            gap: 12px;
            margin-top: 8px;
            width: 100%;
        }

        .timestamp-button {
            flex: 1;
            padding: 12px;
            border-radius: 8px;
            border: none;
            font-size: 14px;
            font-weight: 500;
            cursor: pointer;
        }

        .primary-button {
            background-color: #1db954;
            color: black;
            width: 100%;
        }

        .primary-button:hover {
            background-color: #1ed760;
            transform: translateY(-1px);
        }

        .secondary-button {
            background-color: #2a2a2a;
            color: white;
        }

        .secondary-button:hover {
            background-color: #3a3a3a;
            transform: translateY(-1px);
        }

        .button-row {
            display: flex;
            gap: 8px;
            width: 100%;
        }

        .button-row .timestamp-button {
            flex: 1;
        }

        #custom-timestamp-button {
            background: none;
            border: none;
            width: 32px;
            height: 32px;
            padding: 0;
            cursor: pointer;
            fill: white;
            position: relative;
        }

        #custom-timestamp-button .tooltip {
            display: none;
            position: absolute;
            bottom: 40px;
            left: 50%;
            transform: translateX(-50%);
            background-color: white;
            color: black;
            padding: 5px 10px;
            border-radius: 4px;
            font-size: 14px;
            font-weight: 600;
            white-space: nowrap;
            z-index: 10001;
        }

        #custom-timestamp-button:hover .tooltip {
            display: block;
        }

        .tooltip::after {
            content: "";
            position: absolute;
            top: 100%;
            left: 50%;
            margin-left: -5px;
            border-width: 5px;
            border-style: solid;
            border-color: white transparent transparent transparent;
        }

        .toast-message {
            position: fixed;
            bottom: 20px;
            left: 50%;
            transform: translateX(-50%) translateY(20px);
            background-color: #1db954; /* Reverted back to original color */
            color: black;
            padding: 12px 24px;
            border-radius: 8px;
            font-size: 14px;
            z-index: 10001;
            opacity: 0;
            transition: all 0.2s ease;
        }

        .toast-message.visible {
            opacity: 1;
            transform: translateX(-50%) translateY(0);
        }
    `;

   let toastElement;
let toastTimeout;

function showToast(message, duration = 1200) {
    // If a toast is currently visible, clear the timeout and remove the toast
    if (toastTimeout) {
        clearTimeout(toastTimeout);
    }
    if (toastElement) {
        toastElement.remove();
    }

    // Create and display the new toast message
    toastElement = document.createElement('div');
    toastElement.className = 'toast-message';
    toastElement.textContent = message;
    document.body.appendChild(toastElement);

    // Trigger reflow to ensure the animation plays
    toastElement.offsetHeight;
    toastElement.classList.add('visible');

    // Set a timeout to hide and remove the toast
    toastTimeout = setTimeout(() => {
        toastElement.classList.remove('visible');
        // Wait for the fade-out animation to complete before removing
        setTimeout(() => {
            toastElement.remove();
            toastElement = null; // Reset the toast element reference
        }, 300);
    }, duration);
}


    function formatTime(seconds) {
        const d = Math.floor(seconds / (24 * 3600));
        const h = Math.floor((seconds % (24 * 3600)) / 3600);
        const m = Math.floor((seconds % 3600) / 60);
        const s = Math.floor(seconds % 60);
        return {
            days: String(d).padStart(2, '0'),
            hours: String(h).padStart(2, '0'),
            minutes: String(m).padStart(2, '0'),
            seconds: String(s).padStart(2, '0')
        };
    }

    function parseTimestamp(str) {
        const match = str.match(/^(\d+:)?(\d+:)?(\d+:)?(\d+)$/);
        if (!match) return null;

        const parts = str.split(':').map(Number);
        let seconds = 0;
        if (parts.length === 4) {
            seconds = parts[0] * 24 * 3600 + parts[1] * 3600 + parts[2] * 60 + parts[3];
        } else if (parts.length === 3) {
            seconds = parts[0] * 3600 + parts[1] * 60 + parts[2];
        } else if (parts.length === 2) {
            seconds = parts[0] * 60 + parts[1];
        } else {
            seconds = parts[0];
        }
        return seconds;
    }

    function createTimestampModal() {
        const modal = document.createElement('div');
        modal.innerHTML = `
            <div class="timestamp-overlay">
                <div class="timestamp-modal">
                    <div class="timestamp-header">
                        <h2 class="timestamp-title">Input Time</h2>
                        <button class="close-button">×</button>
                    </div>
                    <div class="timestamp-form">
                        <div class="input-group">
                            <input type="text" class="time-input" id="days" placeholder="DD" maxlength="2">
                            <input type="text" class="time-input" id="hours" placeholder="HH" maxlength="2">
                            <input type="text" class="time-input" id="minutes" placeholder="MM" maxlength="2">
                            <input type="text" class="time-input" id="seconds" placeholder="SS" maxlength="2">
                        </div>
                        <div class="button-row">
                            <button class="timestamp-button secondary-button" id="copy-current">Copy Current</button>
                            <button class="timestamp-button secondary-button" id="paste-timestamp">Paste</button>
                        </div>
                        <button class="timestamp-button primary-button" id="jump-to">Jump to Time</button>
                    </div>
                </div>
            </div>
        `;

        document.body.appendChild(modal);

        const overlay = document.querySelector('.timestamp-overlay');
        const closeButton = document.querySelector('.close-button');
        const jumpButton = document.getElementById('jump-to');
        const copyCurrentButton = document.getElementById('copy-current');
        const pasteButton = document.getElementById('paste-timestamp');
        const inputs = document.querySelectorAll('.time-input');

        function showModal() {
            overlay.style.display = 'flex';
            overlay.offsetHeight; // Trigger reflow
            overlay.classList.add('visible');
        }

        function hideModal() {
            overlay.classList.remove('visible');
            setTimeout(() => {
                overlay.style.display = 'none';
            }, 200);
        }

        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                hideModal();
            }
        });

        closeButton.addEventListener('click', () => {
            hideModal();
        });

        inputs.forEach((input, index) => {
            input.addEventListener('input', (e) => {
                if (e.target.value.length === 2 && index < inputs.length - 1) {
                    inputs[index + 1].focus();
                }
            });

            input.addEventListener('keypress', (e) => {
                if (!/[0-9]/.test(e.key)) {
                    e.preventDefault();
                }
            });
        });

        jumpButton.addEventListener('click', () => {
            const videoPlayer = document.querySelector('video');
            if (!videoPlayer) return;

            const days = parseInt(document.getElementById('days').value) || 0;
            const hours = parseInt(document.getElementById('hours').value) || 0;
            const minutes = parseInt(document.getElementById('minutes').value) || 0;
            const seconds = parseInt(document.getElementById('seconds').value) || 0;

            const totalSeconds = (days * 24 * 3600) + (hours * 3600) + (minutes * 60) + seconds;
            if (totalSeconds >= 0) {
                videoPlayer.currentTime = totalSeconds;
                hideModal();
            }
        });

        copyCurrentButton.addEventListener('click', async () => {
            const videoPlayer = document.querySelector('video');
            if (!videoPlayer) return;

            const time = formatTime(videoPlayer.currentTime);
            const timeString = `${time.days}:${time.hours}:${time.minutes}:${time.seconds}`;

            try {
                await navigator.clipboard.writeText(timeString);
                showToast('Timestamp copied to clipboard');
            } catch (err) {
                showToast('Failed to copy timestamp');
            }
        });

        pasteButton.addEventListener('click', async () => {
            try {
                const text = await navigator.clipboard.readText();
                const seconds = parseTimestamp(text);

                if (seconds !== null) {
                    const time = formatTime(seconds);
                    document.getElementById('days').value = time.days;
                    document.getElementById('hours').value = time.hours;
                    document.getElementById('minutes').value = time.minutes;
                    document.getElementById('seconds').value = time.seconds;
                    showToast('Timestamp pasted');
                } else {
                    showToast('Invalid timestamp format');
                }
            } catch (err) {
                showToast('Failed to paste timestamp');
            }
        });

        return { overlay, showModal, hideModal };
    }

    function addTimestampButton() {
        const controlsContainer = document.querySelector('.z-controls .flex.flex-row.items-center.gap-2');
        if (controlsContainer && !document.getElementById('custom-timestamp-button')) {
            const button = document.createElement('button');
            button.id = 'custom-timestamp-button';
            button.innerHTML =
                `<img src="https://i.ibb.co/nP8sLjf/kickship.png" alt="Logo" style="width: 36px; height: 36px; margin: 0 auto; display: block;" />` +
                `<div class="tooltip">KickSkip</div>`;

            // Add styles for the button to align it correctly
            button.style.display = "flex";
            button.style.alignItems = "center";
            button.style.justifyContent = "center";
            button.style.width = "36px"; // Set the width of the button
            button.style.height = "36px"; // Set the height of the button
            button.style.margin = "0 4px"; // Adjust margin as needed

            button.addEventListener('click', function() {
                const { showModal } = createTimestampModal();
                showModal();
            });

            // Find the clip button using its SVG path and place the new button before it
            const clipButton = Array.from(controlsContainer.querySelectorAll('button')).find(button =>
                button.querySelector('svg path[d="M1.82739 7.28856L27.0598 1.71777L28.2433 7.07867L3.01097 12.6495L1.82739 7.28856ZM3.03003 28.9699V13.6999H28.96V28.9699H3.03003ZM19.98 21.3299L13.47 16.7299V25.9299L19.98 21.3299Z"]')
            );

            if (clipButton) {
                controlsContainer.insertBefore(button, clipButton);
            } else {
                controlsContainer.appendChild(button); // Fallback if clip button not found
            }
        }
    }

    // Add styles
    const styleElement = document.createElement('style');
    styleElement.textContent = styles;
    document.head.appendChild(styleElement);

    // Observe changes in the controls container
    const observer = new MutationObserver((mutations, obs) => {
        addTimestampButton();
    });

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

    // Initial call to add the button if controls are already loaded
    addTimestampButton();

})();