// ==UserScript==
// @name Copy YouTube URL w/ Timestamp & Details
// @namespace azb-copyurl
// @version 0.2
// @description Adds two buttons to manage and copy current YouTube live or video URL with it's timestamp and some other details to clipboard then turn into a shortened url (youtu.be) and 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/watch*
// @license MIT
// ==/UserScript==
(function() {
'use strict';
let initialDurationInSeconds = 0,
selectedOptions = localStorage.getItem("selectedOptions") || "1,2,3,4,5,6",
startTime = new Date();
function isDarkMode() {
return !['#fff', '#ffffff'].includes(getComputedStyle(document.documentElement)
.getPropertyValue('--yt-spec-general-background-a')
.trim());
}
function parseTime(time) {
return time.split(':')
.reverse()
.reduce((acc, val, idx) => acc + (parseInt(val) || 0) * Math.pow(60, idx), 0);
}
function updateLiveStartTime() {
const duration = document.querySelector('.ytp-time-duration');
if (duration) initialDurationInSeconds = parseTime(duration.textContent);
}
let realReferenceTime = null;
let videoReferenceTime = null;
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();
date.setUTCSeconds(date.getUTCSeconds() + 3600 - (liveStartTimeInSeconds % 86400));
let formattedTime = "";
if (selectedOptions.includes("4")) {
formattedTime += `${String(date.getUTCHours()).padStart(2, '0')}:`;
}
if (selectedOptions.includes("5")) {
formattedTime += `${String(date.getUTCMinutes()).padStart(2, '0')}:`;
}
if (selectedOptions.includes("6")) {
formattedTime += `${String(date.getUTCSeconds()).padStart(2, '0')}`;
}
return {
timestamp: liveStartTimeInSeconds + currentInSeconds,
formattedDate: `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')}`,
formattedTime
};
}
function getChannelName() {
let channelNameElem = document.querySelector('yt-formatted-string.ytd-channel-name a');
return channelNameElem ? channelNameElem.textContent : "";
}
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.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() {
const cursorTimeResult = getCurrentTimestampInSeconds();
const urlTimeInSeconds = getURLTimestampInSeconds();
const channelName = getChannelName();
if (cursorTimeResult) {
let details = "";
if (selectedOptions.includes("1")) {
details += ` - ${cursorTimeResult.formattedDate}`;
}
if (selectedOptions.includes("2")) {
details += ` @ ~${cursorTimeResult.formattedTime} UTC`;
}
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!");
});
}
}
function toggleOptionsMenu() {
let menu = document.getElementById("optionsMenu");
if (menu) {
menu.parentNode.removeChild(menu);
} else {
createOptionsMenu();
}
}
function createOptionsMenu() {
const optionsBtn = document.getElementById('optionsBtn');
const rect = optionsBtn.getBoundingClientRect();
const menu = document.createElement("div");
menu.id = "optionsMenu";
menu.style.position = "fixed";
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"
}];
function handleTimeOptionChange(checked) {
["4", "5", "6"].forEach(id => {
const elem = document.getElementById("option-" + id)
.parentNode;
const checkbox = document.getElementById("option-" + id);
if (checked) {
elem.style.display = "";
checkbox.checked = true;
selectedOptions += id;
} else {
elem.style.display = "none";
checkbox.checked = false;
selectedOptions = selectedOptions.replace(id, "");
}
});
}
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 (opt.onChange) {
opt.onChange(this.checked);
}
if (this.checked) {
selectedOptions += opt.id;
} else {
selectedOptions = selectedOptions.replace(opt.id, "");
}
localStorage.setItem("selectedOptions", selectedOptions);
});
const label = document.createElement("label");
label.htmlFor = "option-" + opt.id;
label.innerHTML = `${opt.emoji} ${opt.label}`;
optionElem.appendChild(checkbox);
optionElem.appendChild(label);
if (opt.requires && !selectedOptions.includes(opt.requires)) {
optionElem.style.display = "none";
}
menu.appendChild(optionElem);
});
document.body.appendChild(menu);
}
function handleOptionsButton() {
toggleOptionsMenu();
}
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 (!document.getElementById('copyTimestampBtn')) {
const copyBtn = createStyledButton("Copy URL", handleCopyButton);
copyBtn.id = "copyTimestampBtn";
innerActions.insertBefore(copyBtn, innerActions.firstChild);
}
}
}
const observer = new MutationObserver(addButton);
observer.observe(document.body, {
childList: true,
subtree: true
});
setInterval(updateLiveStartTime, 1000);
})();