Copy YouTube URL w/ Timestamp & Details

Adds two buttons to manage and copy current YouTube live or video URL with its timestamp and some other details to clipboard then turn into a shortened URL (youtu.be) style will auto switch color depending on your theme color, has a ~15 to 30 seconds delay for the time capture. (the Date and Time capture only works if this is an ongoing stream)

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Copy YouTube URL w/ Timestamp & Details
// @namespace    azb-copyurl
// @version      0.3.7
// @description  Adds two buttons to manage and copy current YouTube live or video URL with its timestamp and some other details to clipboard then turn into a shortened URL (youtu.be) style will auto switch color depending on your theme color, has a ~15 to 30 seconds delay for the time capture. (the Date and Time capture only works if this is an ongoing stream)
// @author       Azb
// @match        *://www.youtube.com/*
// @license MIT
// ==/UserScript==
(function () {
    'use strict';
    let initialDurationInSeconds = 0,
        selectedOptions = (localStorage.getItem("selectedOptions") || "1,2,3,4,5,6").split(","),
        startTime = new Date(),
        selectedTimezone = localStorage.getItem("selectedTimezone") || "UTC+0";
    let previousTitle = document.title;
    let maxChecks = 20;
    let titleChanged = false;
    let realReferenceTime = null;
    let videoReferenceTime = null;

    function getChannelName() {
        let channelNameElem = document.querySelector("#upload-info a.yt-simple-endpoint.style-scope.yt-formatted-string");
        return channelNameElem ? channelNameElem.textContent.trim() : "";
    }

    function detectVideoChange() {
        if (document.title !== previousTitle) {
            realReferenceTime = null;
            videoReferenceTime = null;
            initialDurationInSeconds = 0;
            startTime = new Date();
            previousTitle = document.title;
        }
    }

    function isStreamReady() {
        const videoElem = document.querySelector('video');
        return videoElem && videoElem.readyState > 3;
    }

    function isDarkMode() {
        return !['#fff', '#ffffff'].includes(getComputedStyle(document.documentElement).getPropertyValue('--yt-spec-general-background-a').trim());
    }

    function parseTime(time) {
        const timeParts = time.split(':').reverse();
        let seconds = 0;
        if (timeParts.length === 2) {
            seconds = (parseInt(timeParts[0]) || 0) + (parseInt(timeParts[1] || 0) * 60);
        } else if (timeParts.length === 3) {
            seconds = (parseInt(timeParts[0]) || 0) + (parseInt(timeParts[1] || 0) * 60) + (parseInt(timeParts[2] || 0) * 3600);
        } else if (timeParts.length === 4) {
            seconds = (parseInt(timeParts[0]) || 0) + (parseInt(timeParts[1] || 0) * 60) + (parseInt(timeParts[2] || 0) * 3600) + (parseInt(timeParts[3] || 0) * 86400);
        }
        return seconds;
    }

    function updateLiveStartTime() {
        const duration = document.querySelector('.ytp-time-duration');
        if (duration)
            initialDurationInSeconds = parseTime(duration.textContent);
    }

    function updateReferences() {
        const currentTimeElem = document.querySelector('.ytp-time-current');
        const timeParts = currentTimeElem.textContent.split(':').reverse();
        const videoCurrentTime = (parseInt(timeParts[0]) || 0) + (parseInt(timeParts[1] || 0) * 60) + (parseInt(timeParts[2] || 0) * 3600) + (parseInt(timeParts[3] || 0) * 86400);
        realReferenceTime = Date.now();
        videoReferenceTime = videoCurrentTime;
    }

    function convertToTimezone(date) {
        const timezoneOffset = parseInt(selectedTimezone.replace("UTC", "")) || 0;
        const year = date.getUTCFullYear();
        const month = date.getUTCMonth();
        const day = date.getUTCDate();
        const hours = date.getUTCHours();
        const minutes = date.getUTCMinutes();
        const seconds = date.getUTCSeconds();
        const adjustedDate = new Date(Date.UTC(year, month, day, hours + timezoneOffset, minutes, seconds));
        return adjustedDate;
    }

    function handleMinutesCheckboxChange() {
        if (!minutesCheckbox.checked) {
            secondsCheckbox.checked = false;
            secondsCheckbox.disabled = true;
            selectedOptions = selectedOptions.filter(option => option !== "6");
        } else {
            secondsCheckbox.disabled = false;
        }
        saveCheckboxState();
    }

    function handleHoursCheckboxChange() {
        if (!hoursCheckbox.checked) {
            minutesCheckbox.checked = false;
            minutesCheckbox.disabled = true;
            secondsCheckbox.checked = false;
            secondsCheckbox.disabled = true;
            selectedOptions = selectedOptions.filter(option => option !== "5" && option !== "6");
        } else {
            minutesCheckbox.disabled = false;
        }
        saveCheckboxState();
    }

    function getSimpleTimeInSeconds() {
        const currentTimeElem = document.querySelector('.ytp-time-current');
        if (!currentTimeElem)
            return null;

        return parseTime(currentTimeElem.textContent);
    }

    function getURLTimestampInSeconds() {
        const videoElem = document.querySelector('video');
        if (!videoElem)
            return null;

        const currentTimeElem = document.querySelector('.ytp-time-current');
        if (!currentTimeElem)
            return null;

        const timeParts = currentTimeElem.textContent.split(':').reverse();
        const videoCurrentTime = (parseInt(timeParts[0]) || 0) + (parseInt(timeParts[1] || 0) * 60) + (parseInt(timeParts[2] || 0) * 3600) + (parseInt(timeParts[3] || 0) * 86400);
        if (!realReferenceTime) {
            realReferenceTime = Date.now();
            videoReferenceTime = videoCurrentTime;
        }
        const realElapsedTime = (Date.now() - realReferenceTime) / 1000;
        return videoReferenceTime + realElapsedTime;
    }

    function getCurrentTimestampInSeconds() {
        const currentTimeElem = document.querySelector('.ytp-time-current');
        if (!currentTimeElem)
            return null;

        const currentInSeconds = parseTime(currentTimeElem.textContent);
        const liveStartTimeInSeconds = (initialDurationInSeconds + (new Date() - startTime) / 1000) - currentInSeconds;
        const date = new Date();
        const daysPassed = Math.floor(liveStartTimeInSeconds / 86400);
        date.setUTCDate(date.getUTCDate() + daysPassed);
        date.setUTCSeconds(date.getUTCSeconds() + 3600 - (liveStartTimeInSeconds % 86400));
        const adjustedDate = convertToTimezone(date);
        let formattedTime = "";
        if (selectedOptions.includes("4")) {
            formattedTime += `${
                String(adjustedDate.getUTCHours()).padStart(2, '0')
            }`;
        }
        if (selectedOptions.includes("5")) {
            if (formattedTime) {
                formattedTime += `:${
                    String(adjustedDate.getUTCMinutes()).padStart(2, '0')
                }`;
            } else {
                formattedTime += `${
                    String(adjustedDate.getUTCMinutes()).padStart(2, '0')
                }`;
            }
        }
        if (selectedOptions.includes("6")) {
            if (formattedTime) {
                formattedTime += `:${
                    String(adjustedDate.getUTCSeconds()).padStart(2, '0')
                }`;
            } else {
                formattedTime += `00:${
                    String(adjustedDate.getUTCSeconds()).padStart(2, '0')
                }`;
            }
        }
        if (formattedTime && !formattedTime.includes(":")) {
            formattedTime += "h";
        }
        return {
            timestamp: liveStartTimeInSeconds + currentInSeconds,
            formattedDate: `${
                adjustedDate.getUTCFullYear()
            }-${
                String(adjustedDate.getUTCMonth() + 1).padStart(2, '0')
            }-${
                String(adjustedDate.getUTCDate()).padStart(2, '0')
            }`,
            formattedTime: `${formattedTime} ${selectedTimezone}`
        };
    }

    function showCopyAlert(btn, message) {
        const alertDiv = document.createElement("div");
        alertDiv.innerHTML = message;
        alertDiv.style.position = 'fixed';
        alertDiv.style.background = isDarkMode() ? '#212121' : '#f8f8f8';
        alertDiv.style.color = isDarkMode() ? '#fff' : '#000';
        alertDiv.style.padding = '6px 10px';
        alertDiv.style.borderRadius = '5px';
        alertDiv.style.top = (btn.getBoundingClientRect().top - 40) + 'px';
        alertDiv.style.left = (btn.getBoundingClientRect().left + btn.offsetWidth / 2) + 'px';
        alertDiv.style.transform = 'translateX(-50%)';
        alertDiv.style.fontSize = '0.9rem';
        alertDiv.style.fontFamily = 'Roboto, sans-serif';
        alertDiv.style.zIndex = '1000';
        alertDiv.style.transition = 'opacity 0.3s';
        document.body.appendChild(alertDiv);
        setTimeout(() => {
            alertDiv.style.opacity = '0';
            setTimeout(() => {
                document.body.removeChild(alertDiv);
            }, 300);
        }, 2000);
    }

    function createStyledButton(text, handler) {
        const btn = document.createElement("a");
        btn.className = "yt-simple-endpoint style-scope ytd-toggle-button-renderer";
        btn.style.position = 'relative';
        btn.style.display = 'flex';
        btn.style.alignItems = 'center';
        btn.style.padding = "0 16px";
        btn.style.height = "36px";
        btn.style.borderRadius = "18px";
        btn.style.fontSize = "14px";
        btn.style.lineHeight = "2rem";
        btn.style.fontWeight = "500";
        btn.style.marginRight = "8px";
        btn.style.whiteSpace = "nowrap";
        btn.style.transition = "background 0.2s";
        btn.style.fontFamily = "Roboto, sans-serif";
        btn.style.flex = 'none';
        if (isDarkMode()) {
            btn.style.background = "#212121";
            btn.style.color = "#fff";
            btn.addEventListener("mouseenter", function () {
                btn.style.background = "#3e3e3e";
            });
            btn.addEventListener("mouseleave", function () {
                btn.style.background = "#212121";
            });
        } else {
            btn.style.background = "#f8f8f8";
            btn.style.color = "#000";
            btn.addEventListener("mouseenter", function () {
                btn.style.background = "#e0e0e0";
            });
            btn.addEventListener("mouseleave", function () {
                btn.style.background = "#f8f8f8";
            });
        }
        btn.appendChild(document.createTextNode(text));
        btn.addEventListener('click', handler);
        return btn;
    }

    function handleCopyButton() {
        let cursorTimeResult = getCurrentTimestampInSeconds();
        let urlTimeInSeconds = getSimpleTimeInSeconds();
        let channelName = getChannelName();
        if (cursorTimeResult) {
            let details = "";
            if (selectedOptions.includes("1")) {
                details += ` | ${cursorTimeResult.formattedDate}`;
            }
            if ((selectedOptions.includes("4") || selectedOptions.includes("5") || selectedOptions.includes("6")) && selectedOptions.includes("2")) {
                let timeDetail = cursorTimeResult.formattedTime.split(" ")[0];
                if (!selectedOptions.includes("6") && timeDetail.endsWith(":00")) {
                    timeDetail = timeDetail.substring(0, timeDetail.length - 3);
                }
                details += ` | ~${timeDetail} ${selectedTimezone}`;
            }
            if (selectedOptions.includes("3")) {
                details += ` | ${channelName}`;
            }
            const videoID = new URL(window.location.href).searchParams.get("v");
            const fullURL = `https://youtu.be/${videoID}?t=${Math.round(urlTimeInSeconds)}s${details}`;
            navigator.clipboard.writeText(fullURL).then(() => {
                showCopyAlert(document.getElementById('copyTimestampBtn'), "URL Copied!");
            });
        }
        titleChanged = false;
    }


    function toggleOptionsMenu() {
        let menu = document.getElementById("optionsMenu");
        if (menu) {
            menu.parentNode.removeChild(menu);
        } else {
            createOptionsMenu();
        }
    }
    document.body.addEventListener('click', function (event) {
        const menu = document.getElementById('optionsMenu');
        const optionsBtn = document.getElementById('optionsBtn');
        if (menu && event.target !== menu && event.target !== optionsBtn && !menu.contains(event.target)) {
            menu.remove();
        }
    }, true);

    function handleTimeOptionChange(checked) {
        if (checked) {
            if (localStorage.getItem('hoursCheckbox') === 'true') {
                hoursCheckbox.checked = true;
            }
            if (localStorage.getItem('minutesCheckbox') === 'true') {
                minutesCheckbox.checked = true;
            }
            if (localStorage.getItem('secondsCheckbox') === 'true') {
                secondsCheckbox.checked = true;
            }
        } else {
            hoursCheckbox.checked = false;
            minutesCheckbox.checked = false;
            secondsCheckbox.checked = false;
        }
        updateCheckboxStates();
        if (hoursCheckbox.parentNode && minutesCheckbox.parentNode && secondsCheckbox.parentNode) {
            const displayStyle = checked ? "" : "none";
            hoursCheckbox.parentNode.style.display = displayStyle;
            minutesCheckbox.parentNode.style.display = displayStyle;
            secondsCheckbox.parentNode.style.display = displayStyle;
            timezoneDiv.style.display = checked ? "" : "none";
        }
    }

    function updateSecondsCheckboxState() {
        if (!minutesCheckbox || !secondsCheckbox)
            return;

        const minutesChecked = minutesCheckbox.checked;
        if (!minutesChecked) {
            secondsCheckbox.checked = false;
            secondsCheckbox.disabled = true;
        } else {
            secondsCheckbox.disabled = false;
        }
    }

    let timezoneDiv;
    let hoursCheckbox;
    let minutesCheckbox;
    let secondsCheckbox;

    function updateCheckboxStates() {
        if (!hoursCheckbox || !minutesCheckbox || !secondsCheckbox)
            return;

        minutesCheckbox.disabled = false;
        secondsCheckbox.disabled = false;
        if (!hoursCheckbox.checked) {
            minutesCheckbox.checked = false;
            minutesCheckbox.disabled = true;
            secondsCheckbox.checked = false;
            secondsCheckbox.disabled = true;
        } else {
            if (!minutesCheckbox.checked) {
                secondsCheckbox.checked = false;
                secondsCheckbox.disabled = true;
            }
        }
    }

    function saveCheckboxState() {
        localStorage.setItem('hoursCheckbox', hoursCheckbox.checked);
        localStorage.setItem('minutesCheckbox', minutesCheckbox.checked);
        localStorage.setItem('secondsCheckbox', secondsCheckbox.checked);
    }

    function restoreCheckboxState() {
        if (localStorage.getItem('hoursCheckbox') !== null) {
            hoursCheckbox.checked = (localStorage.getItem('hoursCheckbox') === 'true');
        }
        if (localStorage.getItem('minutesCheckbox') !== null) {
            minutesCheckbox.checked = (localStorage.getItem('minutesCheckbox') === 'true');
        }
        if (localStorage.getItem('secondsCheckbox') !== null) {
            secondsCheckbox.checked = (localStorage.getItem('secondsCheckbox') === 'true');
        }
        handleHoursCheckboxChange
        handleMinutesCheckboxChange();
    }

    function bindCheckboxEvents() {
        hoursCheckbox = document.getElementById("option-4");
        minutesCheckbox = document.getElementById("option-5");
        secondsCheckbox = document.getElementById("option-6");
        if (hoursCheckbox) {
            hoursCheckbox.addEventListener("change", function () {
                saveCheckboxState();
                handleHoursCheckboxChange();
                updateCheckboxStates();
            });
        }
        if (minutesCheckbox) {
            minutesCheckbox.addEventListener("change", function () {
                saveCheckboxState();
                handleMinutesCheckboxChange();
                updateCheckboxStates();
            });
        }
        if (secondsCheckbox) {
            secondsCheckbox.addEventListener("change", function () {
                saveCheckboxState();
                updateCheckboxStates();
            });
        }
        restoreCheckboxState();
        updateCheckboxStates();
    }

    function refreshMenuState() {
        [
            "1",
            "2",
            "3",
            "4",
            "5",
            "6"
        ].forEach(id => {
            const elem = document.getElementById("option-" + id);
            elem.checked = selectedOptions.includes(id);
            if (selectedOptions.includes("2")) {
                timezoneDiv.style.display = "";
                if (["4", "5", "6"].includes(id)) {
                    elem.parentNode.style.display = "";
                }
            } else {
                timezoneDiv.style.display = "none";
                if (["4", "5", "6"].includes(id)) {
                    elem.parentNode.style.display = "none";
                }
            }
        });
        bindCheckboxEvents();
        updateCheckboxStates();
        updateSecondsCheckboxState();
    }

    function createOptionsMenu() {
        const optionsBtn = document.getElementById('optionsBtn');
        const rect = optionsBtn.getBoundingClientRect();
        const menu = document.createElement("div");
        menu.id = "optionsMenu";
        menu.style.position = "absolute";
        menu.style.top = rect.bottom + window.scrollY + "px";
        menu.style.left = rect.left + "px";
        menu.style.color = isDarkMode() ? '#fff' : '#000';
        menu.style.background = isDarkMode() ? "#212121" : "#f8f8f8";
        menu.style.border = "1px solid #ccc";
        menu.style.borderRadius = "5px";
        menu.style.zIndex = "1000";
        menu.style.padding = "5px";
        menu.style.marginTop = "10px";
        menu.style.fontFamily = "Roboto";
        menu.style.fontSize = "14px";
        menu.style.fontWeight = "500";

        const optionsData = [{
                id: "1",
                emoji: "📅",
                label: "Date"
            },
            {
                id: "2",
                emoji: "🕙",
                label: "Time",
                onChange: handleTimeOptionChange
            },
            {
                id: "3",
                emoji: "👤",
                label: "Channel"
            },
            {
                id: "4",
                emoji: "⏳",
                label: "Hours",
                requires: "2"
            }, {
                id: "5",
                emoji: "⌛",
                label: "Minutes",
                requires: "2"
            }, {
                id: "6",
                emoji: "⏲️",
                label: "Seconds",
                requires: "2"
            }
        ];
        optionsData.forEach(opt => {
            const optionElem = document.createElement("div");
            const checkbox = document.createElement("input");
            checkbox.type = "checkbox";
            checkbox.id = "option-" + opt.id;
            checkbox.checked = selectedOptions.includes(opt.id);
            checkbox.addEventListener("change", function () {
                if (this.checked) {
                    selectedOptions.push(opt.id);
                } else {
                    selectedOptions = selectedOptions.filter(option => option !== opt.id);
                }
                localStorage.setItem("selectedOptions", selectedOptions.join(","));
                if (opt.onChange) {
                    opt.onChange(this.checked);
                }
            });
            const label = document.createElement("label");
            label.htmlFor = "option-" + opt.id;
            label.innerHTML = `${
                opt.emoji
            } ${
                opt.label
            }`;
            optionElem.appendChild(checkbox);
            optionElem.appendChild(label);
            menu.appendChild(optionElem);
        });
        timezoneDiv = document.createElement("div");
        timezoneDiv.innerHTML = "🌍 Timezone: ";
        const timezoneSelect = document.createElement("select");
        if (isDarkMode()) {
            timezoneSelect.style.background = "#212121";
            timezoneSelect.style.color = "#fff";
            timezoneSelect.style.border = "1px solid #fff";
        } else {
            timezoneSelect.style.background = "#f8f8f8";
            timezoneSelect.style.color = "#000";
            timezoneSelect.style.border = "1px solid #000";
        }
        const timezones = [
            "UTC-11",
            "UTC-10",
            "UTC-9",
            "UTC-8",
            "UTC-7",
            "UTC-6",
            "UTC-5",
            "UTC-4",
            "UTC-3",
            "UTC-2",
            "UTC-1",
            "UTC+0",
            "UTC+1",
            "UTC+2",
            "UTC+3",
            "UTC+4",
            "UTC+5",
            "UTC+6",
            "UTC+7",
            "UTC+8",
            "UTC+9",
            "UTC+10",
            "UTC+11",
            "UTC+12",
            "UTC+13",
            "UTC+14"
        ];
        timezones.forEach(zone => {
            const option = document.createElement("option");
            option.value = zone;
            option.text = zone;
            if (zone === selectedTimezone) {
                option.selected = true;
            }
            timezoneSelect.appendChild(option);
        });
        timezoneSelect.onchange = function () {
            selectedTimezone = this.value;
            localStorage.setItem("selectedTimezone", selectedTimezone);
        };
        timezoneDiv.appendChild(timezoneSelect);
        menu.appendChild(timezoneDiv);
        document.body.appendChild(menu);
        bindCheckboxEvents();
        updateSecondsCheckboxState();
        refreshMenuState();
    }

    function handleOptionsButton() {
        toggleOptionsMenu();
        event.stopPropagation();
    }

    function addButton() {
        const actionsBar = document.querySelector('#actions');
        const innerActions = document.querySelector('ytd-watch-metadata[flex-menu-enabled] #actions.ytd-watch-metadata ytd-menu-renderer.ytd-watch-metadata');

        if (actionsBar && innerActions) {
            if (!document.getElementById('optionsBtn')) {
                const optionsBtn = createStyledButton("Settings", handleOptionsButton);
                optionsBtn.id = "optionsBtn";
                innerActions.insertBefore(optionsBtn, innerActions.firstChild);
            }
            if (isStreamReady() && !document.getElementById('copyTimestampBtn')) {
                const copyBtn = createStyledButton("Copy URL", handleCopyButton);
                copyBtn.id = "copyTimestampBtn";
                innerActions.insertBefore(copyBtn, innerActions.firstChild);
            }
            const videoElem = document.querySelector('video');
            videoElem.addEventListener('play', updateReferences);
            videoElem.addEventListener('pause', updateReferences);
            videoElem.addEventListener('seeked', updateReferences);
        }
    }
    const videoObserver = new MutationObserver(function () {
        if (isStreamReady() && !document.getElementById('copyTimestampBtn')) {
            addButton();
        }
    });

    const videoElem = document.querySelector('video');
    if (videoElem) {
        videoObserver.observe(videoElem, {
            attributes: true,
            attributeFilter: ['readyState']
        });
    }
    new MutationObserver(detectVideoChange).observe(document.querySelector('title'), {
        childList: true
    });
    const observer = new MutationObserver(addButton);
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
    setInterval(updateLiveStartTime, 1000);
})();