// ==UserScript==
// @name Youtube exact upload
// @name:de YouTube exakter Hochladezeitpunkt
// @description Adds exact upload time to youtube videos
// @description:de Fügt YouTube-Videos den exakten Hochladezeitpunkt mit Uhrzeit hinzu
// @require https://cdnjs.cloudflare.com/ajax/libs/luxon/3.4.4/luxon.min.js
// @version 0.18
// @match https://www.youtube.com/*
// @grant none
// @namespace https://greasyfork.org/users/94906
// @license MIT
// ==/UserScript==
// luxon is for formatting and comparing dates and times
(function () {
"use strict";
console.log("YT EXACT UPLOAD LOADED");
//Pre-Define Variables to prevent warning of redaclaration of variables
var DATE_PATTERN,
TIME_PATTERN,
DATETIME_COMBINE_PATTERN,
SCHEDULED_LIVESTREAM_START,
SCHEDULED_PREMIERE_START,
ONGOING_LIVESTREAM_START;
var ONGOING_PREMIERE_START,
ENDED_LIVESTREAM_START,
ENDED_PREMIERE_START,
DATETIME_UNTIL_PATTERN,
SINCE,
TODAY_AT;
var YT_API_KEY = "YouTube API-Key";
var AGE_RESTRICTED = " - FSK 18";
var SHOW_REFRESH = true;
var REFRESH_TIMESTAMP = "⟳";
var SHOW_UNDERLINE_ON_TIMESTAMP = false;
var BASE_URL =
"https://www.googleapis.com/youtube/v3/videos?part=snippet,liveStreamingDetails,contentDetails,localizations,player,statistics,status&key=" +
YT_API_KEY;
var lang = document.getElementsByTagName("html")[0].getAttribute("lang");
luxon.Settings.defaultLocale = lang;
if (lang.startsWith("de")) {
DATE_PATTERN = "dd.MM.yyyy"; // https://moment.github.io/luxon/#/formatting?id=table-of-tokens
TIME_PATTERN = "HH:mm:ss 'Uhr'"; // https://moment.github.io/luxon/#/formatting?id=table-of-tokens
DATETIME_COMBINE_PATTERN = " 'um' "; // https://moment.github.io/luxon/#/formatting?id=table-of-tokens
SCHEDULED_LIVESTREAM_START = "Livestream geplant für: ";
SCHEDULED_PREMIERE_START = "Premiere geplant für: ";
ONGOING_LIVESTREAM_START = "Aktiver Livestream seit ";
ONGOING_PREMIERE_START = "Aktive Premiere seit ";
ENDED_LIVESTREAM_START = "Livestream von ";
ENDED_PREMIERE_START = "Premiere von ";
DATETIME_UNTIL_PATTERN = " bis ";
SINCE = "Seit";
TODAY_AT = "Heute um ";
} else if (lang.startsWith("fr")) {
DATE_PATTERN = "dd MMMM yyyy"; // https://moment.github.io/luxon/#/formatting?id=table-of-tokens
TIME_PATTERN = "HH:mm:ss"; // https://moment.github.io/luxon/#/formatting?id=table-of-tokens
DATETIME_COMBINE_PATTERN = " 'de' "; // https://moment.github.io/luxon/#/formatting?id=table-of-tokens
SCHEDULED_LIVESTREAM_START = "Direct planifié pour le ";
SCHEDULED_PREMIERE_START = "Première planifiée pour le ";
ONGOING_LIVESTREAM_START = "Direct en cours depuis ";
ONGOING_PREMIERE_START = "Première en cours depuis ";
ENDED_LIVESTREAM_START = "Direct diffusé le ";
ENDED_PREMIERE_START = "Première diffusée le ";
DATETIME_UNTIL_PATTERN = " à ";
SINCE = "Depuis";
TODAY_AT = "Aujourd'hui à ";
} else if (lang.startsWith("it")) {
DATE_PATTERN = "dd MMMM yyyy"; // https://moment.github.io/luxon/#/formatting?id=table-of-tokens
TIME_PATTERN = "HH:mm:ss"; // https://moment.github.io/luxon/#/formatting?id=table-of-tokens
DATETIME_COMBINE_PATTERN = " 'alle' "; // https://moment.github.io/luxon/#/formatting?id=table-of-tokens
SCHEDULED_LIVESTREAM_START = "Diretta pianificata per il: ";
SCHEDULED_PREMIERE_START = "Premiere pianificata per il: ";
ONGOING_LIVESTREAM_START = "Diretta attiva dalle ";
ONGOING_PREMIERE_START = "Premiere attiva dalle ";
ENDED_LIVESTREAM_START = "Diretta del ";
ENDED_PREMIERE_START = " Premiere del ";
DATETIME_UNTIL_PATTERN = " fino ";
SINCE = "Dalle";
TODAY_AT = "Oggi alle ";
} else {
DATE_PATTERN = "dd.MM.yyyy"; // https://moment.github.io/luxon/#/formatting?id=table-of-tokens
TIME_PATTERN = "HH:mm:ss"; // https://moment.github.io/luxon/#/formatting?id=table-of-tokens
DATETIME_COMBINE_PATTERN = " 'at' "; // https://moment.github.io/luxon/#/formatting?id=table-of-tokens
SCHEDULED_LIVESTREAM_START = "Livestream scheduled for: ";
SCHEDULED_PREMIERE_START = "Premiere scheduled for: ";
ONGOING_LIVESTREAM_START = "Active Livestream since ";
ONGOING_PREMIERE_START = "Active Premiere since ";
ENDED_LIVESTREAM_START = "Livestream from ";
ENDED_PREMIERE_START = "Premiere from ";
DATETIME_UNTIL_PATTERN = " until ";
SINCE = "Since";
TODAY_AT = "Today at ";
}
var interval = null;
var changeCheckTimer = null;
var currentVideoId = null;
function genUrl() {
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get("v") != null) {
return BASE_URL + "&id=" + urlParams.get("v");
} else {
return "";
}
}
function sleep(milliseconds) {
return new Promise((resolve) => setTimeout(resolve, milliseconds));
}
function formatMilliseconds(
milliseconds,
joinString,
showDays,
showHours,
showMinutes,
showSeconds,
showMilliseconds,
pad,
hideDaysOnNull,
) {
let result = "";
let days = Math.floor(milliseconds / (1000 * 60 * 60 * 24));
let hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
let minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
let seconds = Math.floor((milliseconds / 1000) % 60);
milliseconds = milliseconds % 1000;
if (showDays) {
if (days < 1 && hideDaysOnNull) {
} else {
if (result != "") result += joinString;
if (pad) {
if (days < 10) result += "0" + days;
else result += days;
} else result += days;
}
}
if (showHours) {
if (result != "") result += joinString;
if (pad) result += ("0" + hours).slice(-2);
else result += hours;
}
if (showMinutes) {
if (result != "") result += joinString;
if (pad) result += ("0" + minutes).slice(-2);
else result += minutes;
}
if (showSeconds) {
if (result != "") result += joinString;
if (pad) result += ("0" + seconds).slice(-2);
else result += seconds;
}
if (showMilliseconds) {
if (result != "") result += joinString;
if (pad) result += ("00" + milliseconds).slice(-3);
else result += milliseconds;
}
return result;
}
function updateOngoing(startTime) {
if (interval) {
clearInterval(interval);
interval = null;
}
interval = setInterval(function () {
let durationInMilliseconds = luxon.Interval.fromDateTimes(
startTime,
luxon.DateTime.now(),
).length("milliseconds");
var ongoingVideoDuration = document.getElementById(
"ongoing-video-duration",
);
ongoingVideoDuration.innerHTML = formatMilliseconds(
durationInMilliseconds,
":",
true,
true,
true,
true,
false,
true,
true,
);
if (ongoingVideoDuration.parentNode) {
ongoingVideoDuration.parentNode.title =
ongoingVideoDuration.parentNode.innerText;
}
}, 500);
}
async function updateLiveContent(premiere, livestream, data, dt) {
var element = null;
while (!element) {
element = document.getElementById("primary-inner");
await sleep(200);
}
var durationInMilliseconds = null;
var ongoing = false;
var innerHTML = "";
if (!premiere && !livestream) {
// normal video
if (dt.hasSame(luxon.DateTime.now(), "day"))
// today
innerHTML += TODAY_AT + dt.toFormat(TIME_PATTERN);
else
innerHTML += dt.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
);
} else {
if (!data.items[0].liveStreamingDetails.actualStartTime) {
// planned
dt = luxon.DateTime.fromISO(
data.items[0].liveStreamingDetails.scheduledStartTime,
);
if (dt.hasSame(luxon.DateTime.now(), "day")) {
// today
if (livestream)
innerHTML += SCHEDULED_LIVESTREAM_START + dt.toFormat(TIME_PATTERN);
else if (premiere)
innerHTML += SCHEDULED_PREMIERE_START + dt.toFormat(TIME_PATTERN);
else innerHTML += TODAY_AT + dt.toFormat(TIME_PATTERN);
} else {
if (livestream)
innerHTML +=
SCHEDULED_LIVESTREAM_START +
dt.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
);
else if (premiere)
innerHTML +=
SCHEDULED_PREMIERE_START +
dt.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
);
else
innerHTML +=
TODAY_AT +
dt.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
);
}
} else {
// ongoing / ended
dt = luxon.DateTime.fromISO(
data.items[0].liveStreamingDetails.actualStartTime,
);
var endTime = null;
if (data.items[0].liveStreamingDetails.actualEndTime)
endTime = luxon.DateTime.fromISO(
data.items[0].liveStreamingDetails.actualEndTime,
);
if (endTime == null) {
// ongoing
ongoing = true;
durationInMilliseconds = luxon.Interval.fromDateTimes(
dt,
luxon.DateTime.now(),
).length("milliseconds");
if (dt.hasSame(luxon.DateTime.now(), "day")) {
// today
if (livestream)
innerHTML +=
ONGOING_LIVESTREAM_START +
dt.toFormat(TIME_PATTERN) +
' (<span id="ongoing-video-duration">' +
formatMilliseconds(
durationInMilliseconds,
":",
true,
true,
true,
true,
false,
true,
true,
) +
"</span>)";
else if (premiere)
innerHTML +=
ONGOING_PREMIERE_START +
dt.toFormat(TIME_PATTERN) +
' (<span id="ongoing-video-duration">' +
formatMilliseconds(
durationInMilliseconds,
":",
true,
true,
true,
true,
false,
true,
true,
) +
"</span>)";
else
innerHTML +=
SINCE +
" " +
dt.toFormat(TIME_PATTERN) +
' (<span id="ongoing-video-duration">' +
formatMilliseconds(
durationInMilliseconds,
":",
true,
true,
true,
true,
false,
true,
true,
) +
"</span>)";
} else {
if (livestream)
innerHTML +=
ONGOING_LIVESTREAM_START +
dt.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
) +
' (<span id="ongoing-video-duration">' +
formatMilliseconds(
durationInMilliseconds,
":",
true,
true,
true,
true,
false,
true,
true,
) +
"</span>)";
else if (premiere)
innerHTML +=
ONGOING_PREMIERE_START +
dt.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
) +
' (<span id="ongoing-video-duration">' +
formatMilliseconds(
durationInMilliseconds,
":",
true,
true,
true,
true,
false,
true,
true,
) +
"</span>)";
else
innerHTML +=
SINCE +
" " +
dt.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
) +
' (<span id="ongoing-video-duration">' +
formatMilliseconds(
durationInMilliseconds,
":",
true,
true,
true,
true,
false,
true,
true,
) +
"</span>)";
}
} else {
// ended
if (dt.hasSame(endTime, "day")) {
// start date and end date are the same
if (dt.hasSame(luxon.DateTime.now(), "day")) {
// today
if (livestream)
innerHTML +=
ENDED_LIVESTREAM_START +
dt.toFormat(TIME_PATTERN) +
DATETIME_UNTIL_PATTERN +
endTime.toFormat(TIME_PATTERN);
else if (premiere)
innerHTML +=
ENDED_PREMIERE_START +
dt.toFormat(TIME_PATTERN) +
DATETIME_UNTIL_PATTERN +
endTime.toFormat(TIME_PATTERN);
else innerHTML += TODAY_AT + dt.toFormat(TIME_PATTERN);
} else {
if (livestream)
innerHTML +=
ENDED_LIVESTREAM_START +
dt.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
) +
DATETIME_UNTIL_PATTERN +
endTime.toFormat(TIME_PATTERN);
else if (premiere)
innerHTML +=
ENDED_PREMIERE_START +
dt.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
) +
DATETIME_UNTIL_PATTERN +
endTime.toFormat(TIME_PATTERN);
else
innerHTML +=
dt.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
) +
DATETIME_UNTIL_PATTERN +
endTime.toFormat(TIME_PATTERN);
}
} else {
if (dt.hasSame(luxon.DateTime.now(), "day")) {
// today
if (livestream)
innerHTML +=
ENDED_LIVESTREAM_START +
dt.toFormat(TIME_PATTERN) +
DATETIME_UNTIL_PATTERN +
endTime.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
);
else if (premiere)
innerHTML +=
ENDED_PREMIERE_START +
dt.toFormat(TIME_PATTERN) +
DATETIME_UNTIL_PATTERN +
endTime.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
);
else
innerHTML +=
TODAY_AT +
dt.toFormat(TIME_PATTERN) +
DATETIME_UNTIL_PATTERN +
endTime.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
);
} else {
if (livestream)
innerHTML +=
ENDED_LIVESTREAM_START +
dt.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
) +
DATETIME_UNTIL_PATTERN +
endTime.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
);
else if (premiere)
innerHTML +=
ENDED_PREMIERE_START +
dt.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
) +
DATETIME_UNTIL_PATTERN +
endTime.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
);
else
innerHTML +=
dt.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
) +
DATETIME_UNTIL_PATTERN +
endTime.toFormat(
DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
);
}
}
}
}
}
var contentRating = data.items[0].contentDetails.contentRating;
if (contentRating.ytRating && contentRating.ytRating == "ytAgeRestricted")
innerHTML += AGE_RESTRICTED;
if (SHOW_REFRESH) {
if (SHOW_UNDERLINE_ON_TIMESTAMP)
innerHTML +=
' <span id="dot" class="style-scope ytd-video-primary-info-renderer"></span> <span style="color: var(--yt-spec-text-secondary); text-decoration: underline var(--yt-spec-text-secondary); cursor: pointer;" onclick="document.dispatchEvent(new Event(\'refresh-clicked\'));">' +
REFRESH_TIMESTAMP +
"</span>";
else
innerHTML +=
' <span id="dot" class="style-scope ytd-video-primary-info-renderer"></span> <span style="color: var(--yt-spec-text-secondary); cursor: pointer;" onclick="document.dispatchEvent(new Event(\'refresh-clicked\'));">' +
REFRESH_TIMESTAMP +
"</span>";
}
if (ongoing) updateOngoing(dt);
let primaryInner = document.getElementById("primary-inner");
let dateTimeValueElem = document.getElementById("exact-date-time");
if (!dateTimeValueElem) {
dateTimeValueElem = document.createElement("span");
dateTimeValueElem.id = "exact-date-time";
primaryInner.insertBefore(dateTimeValueElem, primaryInner.firstChild);
}
dateTimeValueElem.style.color = "white";
dateTimeValueElem.style.position = "absolute";
dateTimeValueElem.style.zIndex = "999";
dateTimeValueElem.innerHTML = innerHTML;
dateTimeValueElem.title = dateTimeValueElem.innerText;
return ongoing;
}
function getExactUploadDate(forceRefresh = false) {
var abort = false;
const processEvent = async () => {
await sleep(500);
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get("v") != null) {
let videoId = urlParams.get("v");
if (videoId == currentVideoId) {
abort = true;
} else {
currentVideoId = videoId;
}
}
if (forceRefresh) abort = false;
if ((YT_API_KEY != "" || typeof YT_API_KEY != "undefined") && !abort) {
var url = genUrl();
if (url != "") {
fetch(url)
.then(function (response) {
return response.json();
})
.then(function (data) {
if (data.pageInfo.totalResults > 0) {
const addTime = async () => {
var dt = luxon.DateTime.fromISO(
data.items[0].snippet.publishedAt,
);
console.log(dt);
let payload = {
context: {
client: {
clientName: "WEB",
clientVersion: "2.20210614.06.00",
originalUrl: window.location.href,
platform: "DESKTOP",
clientFormFactor: "UNKNOWN_FORM_FACTOR",
mainAppWebInfo: {
graftUrl: "/watch?v=" + currentVideoId,
webDisplayMode: "WEB_DISPLAY_MODE_BROWSER",
isWebNativeShareAvailable: false,
},
},
user: {
lockedSafetyMode: false,
},
request: {
useSsl: true,
},
},
videoId: currentVideoId,
racyCheckOk: false,
contentCheckOk: false,
};
fetch(
"https://www.youtube.com/youtubei/v1/player?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8" /*InnerTube-API-Key*/,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
},
)
.then(function (response) {
return response.text();
})
.then(function (video_info) {
if (interval) {
clearInterval(interval);
interval = null;
}
if (changeCheckTimer) {
clearInterval(changeCheckTimer);
changeCheckTimer = null;
}
try {
/*let player_response = decodeURIComponent(video_info);
let urlParams = new URLSearchParams(player_response);
if (urlParams.get("player_response") != null) {
player_response = urlParams.get("player_response");
}
player_response = JSON.parse(player_response);// data.items[0].status.privacyStatus = "public" -> Öffentliches Video*/
let player_response = JSON.parse(video_info);
var premiere =
player_response &&
!player_response.videoDetails.isLiveContent;
premiere =
premiere && data.items[0].liveStreamingDetails;
var livestream =
player_response &&
player_response.videoDetails.isLiveContent;
var innerHTML =
'<span id="dot" class="style-scope ytd-video-primary-info-renderer">•</span>';
updateLiveContent(premiere, livestream, data, dt);
} catch (ex) {
console.error(ex);
}
})
.catch((error) =>
console.error(
"YOUTUBE EXACT UPLOAD ERROR: " + error,
"\nget_video_info doesn't seem to work",
),
);
};
addTime();
}
})
.catch((error) =>
console.error(
"YOUTUBE EXACT UPLOAD ERROR: " + error,
"\nINVALID API KEY?",
),
);
}
} else {
if (!abort)
console.error("YOUTUBE EXACT UPLOAD ERROR: Undefined api key");
}
};
processEvent();
}
function refreshEventListener() {
getExactUploadDate(true);
}
//getExactUploadDate();
//document.addEventListener('click', getExactUploadDate);
//document.addEventListener('yt-page-data-updated', getExactUploadDate);
//document.addEventListener('yt-navigate-finish', getExactUploadDate);
document.addEventListener("refresh-clicked", refreshEventListener);
//<video style="width: 853px; height: 480px; left: 0px; top: 0px;" tabindex="-1" class="video-stream html5-main-video" src="blob:https://www.youtube.com/0976da77-cfd4-4922-ad9e-383d88a12200"></video>
/*function main() {
let videoStream = document.getElementsByClassName('video-stream');
if (videoStream.length < 1) {
setTimeout(() => main(), 500);
} else {
console.log('video-stream:', videoStream[0]);
// videoStream[0].addEventListener('loadeddata', (event) => console.log(`Loaded ${event.target.src}`));
//videoStream[0].addEventListener('loadeddata', (event) => getExactUploadDate());
videoStream[0].addEventListener('durationchange', (event) => getExactUploadDate());
}
}*/
function main() {
let ytdPlayer = document.getElementById("ytd-player");
if (!ytdPlayer) {
setTimeout(() => main(), 500);
} else {
ytdPlayer.addEventListener("yt-player-updated", (event) =>
getExactUploadDate(),
);
}
}
main();
if (new URLSearchParams(window.location.search).get("v") != null) {
getExactUploadDate();
}
})();