Youtube exact upload

Adds exact upload time to youtube videos

当前为 2021-06-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Youtube exact upload
  3. // @name:de YouTube exakter Hochladezeitpunkt
  4. // @description Adds exact upload time to youtube videos
  5. // @description:de Fügt YouTube-Videos den exakten Hochladezeitpunkt mit Uhrzeit hinzu
  6. // @require https://cdn.jsdelivr.net/npm/hacktimer@1.1.3/HackTimer.min.js
  7. // @require http://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js
  8. // @version 0.9
  9. // @match https://www.youtube.com/*
  10. // @grant none
  11. // @namespace https://greasyfork.org/users/94906
  12. // @license WTFPL
  13. // ==/UserScript==
  14.  
  15. // HackTimer is for making setInterval work in background tabs
  16. // moment is for formatting and comparing dates and times
  17.  
  18. (function() {
  19. 'use strict';
  20. console.log("YT EXACT UPLOAD LOADED");
  21. var AGE_RESTRICTED = " - FSK 18";
  22. var SHOW_REFRESH = true;
  23. var REFRESH_TIMESTAMP = "⟳";
  24. var SHOW_UNDERLINE_ON_TIMESTAMP = false;
  25. var YT_API_KEY = "YouTube API-Key";
  26. var BASE_URL = "https://www.googleapis.com/youtube/v3/videos?part=snippet,liveStreamingDetails,contentDetails,localizations,player,statistics,status&key=" + YT_API_KEY;
  27. if (document.getElementsByTagName("html")[0].getAttribute("lang").startsWith("de")) {
  28. var DATE_PATTERN = "DD.MM.YYYY"; // https://momentjs.com/docs/#/parsing/string-format/
  29. var TIME_PATTERN = "HH:mm:ss [Uhr]"; // https://momentjs.com/docs/#/parsing/string-format/
  30. var DATETIME_COMBINE_PATTERN = " [um] "; // https://momentjs.com/docs/#/parsing/string-format/
  31. var SCHEDULED_LIVESTREAM_START = "Livestream geplant für: ";
  32. var SCHEDULED_PREMIERE_START = "Premiere geplant für: ";
  33. var ONGOING_LIVESTREAM_START = "Aktiver Livestream seit ";
  34. var ONGOING_PREMIERE_START = "Aktive Premiere seit ";
  35. var ENDED_LIVESTREAM_START = "Livestream von ";
  36. var ENDED_PREMIERE_START = "Premiere von ";
  37. var DATETIME_UNTIL_PATTERN = " bis ";
  38. var SINCE = "Seit";
  39. var TODAY_AT = "Heute um ";
  40. } else {
  41. var DATE_PATTERN = "DD.MM.YYYY"; // https://momentjs.com/docs/#/parsing/string-format/
  42. var TIME_PATTERN = "HH:mm:ss"; // https://momentjs.com/docs/#/parsing/string-format/
  43. var DATETIME_COMBINE_PATTERN = " [at] "; // https://momentjs.com/docs/#/parsing/string-format/
  44. var SCHEDULED_LIVESTREAM_START = "Livestream scheduled for: ";
  45. var SCHEDULED_PREMIERE_START = "Premiere scheduled for: ";
  46. var ONGOING_LIVESTREAM_START = "Active Livestream since ";
  47. var ONGOING_PREMIERE_START = "Active Premiere since ";
  48. var ENDED_LIVESTREAM_START = "Livestream from ";
  49. var ENDED_PREMIERE_START = "Premiere from ";
  50. var DATETIME_UNTIL_PATTERN = " until ";
  51. var SINCE = "Since";
  52. var TODAY_AT = "Today at ";
  53. }
  54. var interval = null;
  55. var changeCheckTimer = null;
  56. var currentVideoId = null;
  57. function genUrl(){
  58. const urlParams = new URLSearchParams(window.location.search);
  59.  
  60. if(urlParams.get("v") != null){
  61. return BASE_URL + "&id=" + urlParams.get("v");
  62. }else {
  63. return "";
  64. }
  65. }
  66. function isUndefinedOrNull(obj) {
  67. return obj == undefined || obj == null;
  68. }
  69. function sleep(milliseconds) {
  70. return new Promise(resolve => setTimeout(resolve, milliseconds));
  71. }
  72. function formatMilliseconds(milliseconds, joinString, showDays, showHours, showMinutes, showSeconds, showMilliseconds, pad, hideDaysOnNull) {
  73. let result = '';
  74. let days = Math.floor(milliseconds / (1000 * 60 * 60 * 24));
  75. let hours = Math.floor((milliseconds / (1000 * 60 * 60)) % 24);
  76. let minutes = Math.floor((milliseconds / (1000 * 60)) % 60);
  77. let seconds = Math.floor((milliseconds / 1000) % 60);
  78. milliseconds = milliseconds % 1000;
  79. if (showDays) {
  80. if (days < 1 && hideDaysOnNull) {
  81. } else {
  82. if (result != '')
  83. result += joinString;
  84. if (pad) {
  85. if (days < 10)
  86. result += '0' + days;
  87. else
  88. result += days;
  89. } else
  90. result += days;
  91. }
  92. }
  93. if (showHours) {
  94. if (result != '')
  95. result += joinString;
  96. if (pad)
  97. result += ('0' + hours).slice(-2);
  98. else
  99. result += hours;
  100. }
  101. if (showMinutes) {
  102. if (result != '')
  103. result += joinString;
  104. if (pad)
  105. result += ('0' + minutes).slice(-2);
  106. else
  107. result += minutes;
  108. }
  109. if (showSeconds) {
  110. if (result != '')
  111. result += joinString;
  112. if (pad)
  113. result += ('0' + seconds).slice(-2);
  114. else
  115. result += seconds;
  116. }
  117. if (showMilliseconds) {
  118. if (result != '')
  119. result += joinString;
  120. if (pad)
  121. result += ('00' + milliseconds).slice(-3);
  122. else
  123. result += milliseconds;
  124. }
  125. return result;
  126. }
  127. function updateOngoing(durationInMilliseconds) {
  128. if (!isUndefinedOrNull(interval)) {
  129. clearInterval(interval);
  130. interval = null;
  131. }
  132. let duration = durationInMilliseconds;
  133. interval = setInterval(function() {
  134. duration += 500;
  135. document.getElementById("ongoing-video-duration").innerHTML = formatMilliseconds(duration, ':', true, true, true, true, false, true, true);
  136. }, 500);
  137. }
  138. async function updateLiveContent(premiere, livestream, data, mom) {
  139. var element = null;
  140. while (isUndefinedOrNull(element)) {
  141. element = document.getElementById("date");
  142. await sleep(200);
  143. }
  144. var durationInMilliseconds = null;
  145. var ongoing = false;
  146. var innerHTML = "<span id=\"dot\" class=\"style-scope ytd-video-primary-info-renderer\">•</span>";
  147. if (!premiere && !livestream) { // normal video
  148. if (mom.isSame(moment(), 'day')) // today
  149. innerHTML += TODAY_AT + mom.format(TIME_PATTERN);
  150. else
  151. innerHTML += mom.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN);
  152. } else {
  153. if (isUndefinedOrNull(data.items[0].liveStreamingDetails.actualStartTime)) { // planned
  154. mom = moment(data.items[0].liveStreamingDetails.scheduledStartTime);
  155. if (mom.isSame(moment(), 'day')) { // today
  156. if (livestream)
  157. innerHTML += SCHEDULED_LIVESTREAM_START + mom.format(TIME_PATTERN);
  158. else if (premiere)
  159. innerHTML += SCHEDULED_PREMIERE_START + mom.format(TIME_PATTERN);
  160. else
  161. innerHTML += TODAY_AT + mom.format(TIME_PATTERN);
  162. } else {
  163. if (livestream)
  164. innerHTML += SCHEDULED_LIVESTREAM_START + mom.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN);
  165. else if (premiere)
  166. innerHTML += SCHEDULED_PREMIERE_START + mom.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN);
  167. else
  168. innerHTML += TODAY_AT + mom.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN);
  169. }
  170. } else { // ongoing / ended
  171. mom = moment(data.items[0].liveStreamingDetails.actualStartTime);
  172. var endTime = null;
  173. if (!isUndefinedOrNull(data.items[0].liveStreamingDetails.actualEndTime))
  174. endTime = moment(data.items[0].liveStreamingDetails.actualEndTime);
  175. if (endTime == null) { // ongoing
  176. ongoing = true;
  177. durationInMilliseconds = moment.duration(moment().diff(mom)).asMilliseconds();
  178. if (mom.isSame(moment(), 'day')) { // today
  179. if (livestream)
  180. innerHTML += ONGOING_LIVESTREAM_START + mom.format(TIME_PATTERN) + " (<span id=\"ongoing-video-duration\">" + formatMilliseconds(durationInMilliseconds, ':', true, true, true, true, false, true, true) + "</span>)";
  181. else if (premiere)
  182. innerHTML += ONGOING_PREMIERE_START + mom.format(TIME_PATTERN) + " (<span id=\"ongoing-video-duration\">" + formatMilliseconds(durationInMilliseconds, ':', true, true, true, true, false, true, true) + "</span>)";
  183. else
  184. innerHTML += SINCE + " " + mom.format(TIME_PATTERN) + " (<span id=\"ongoing-video-duration\">" + formatMilliseconds(durationInMilliseconds, ':', true, true, true, true, false, true, true) + "</span>)";
  185. } else {
  186. if (livestream)
  187. innerHTML += ONGOING_LIVESTREAM_START + mom.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN) + " (<span id=\"ongoing-video-duration\">" + formatMilliseconds(durationInMilliseconds, ':', true, true, true, true, false, true, true) + "</span>)";
  188. else if (premiere)
  189. innerHTML += ONGOING_PREMIERE_START + mom.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN) + " (<span id=\"ongoing-video-duration\">" + formatMilliseconds(durationInMilliseconds, ':', true, true, true, true, false, true, true) + "</span>)";
  190. else
  191. innerHTML += SINCE + " " + mom.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN) + " (<span id=\"ongoing-video-duration\">" + formatMilliseconds(durationInMilliseconds, ':', true, true, true, true, false, true, true) + "</span>)";
  192. }
  193. } else { // ended
  194. if (mom.isSame(endTime, 'day')) { // start date and end date are the same
  195. if (mom.isSame(moment(), 'day')) { // today
  196. if (livestream)
  197. innerHTML += ENDED_LIVESTREAM_START + mom.format(TIME_PATTERN) + DATETIME_UNTIL_PATTERN + endTime.format(TIME_PATTERN);
  198. else if (premiere)
  199. innerHTML += ENDED_PREMIERE_START + mom.format(TIME_PATTERN) + DATETIME_UNTIL_PATTERN + endTime.format(TIME_PATTERN);
  200. else
  201. innerHTML += TODAY_AT + mom.format(TIME_PATTERN);
  202. } else {
  203. if (livestream)
  204. innerHTML += ENDED_LIVESTREAM_START + mom.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN) + DATETIME_UNTIL_PATTERN + endTime.format(TIME_PATTERN);
  205. else if (premiere)
  206. innerHTML += ENDED_PREMIERE_START + mom.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN) + DATETIME_UNTIL_PATTERN + endTime.format(TIME_PATTERN);
  207. else
  208. innerHTML += mom.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN) + DATETIME_UNTIL_PATTERN + endTime.format(TIME_PATTERN);
  209. }
  210. } else {
  211. if (mom.isSame(moment(), 'day')) { // today
  212. if (livestream)
  213. innerHTML += ENDED_LIVESTREAM_START + mom.format(TIME_PATTERN) + DATETIME_UNTIL_PATTERN + endTime.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN);
  214. else if (premiere)
  215. innerHTML += ENDED_PREMIERE_START + mom.format(TIME_PATTERN) + DATETIME_UNTIL_PATTERN + endTime.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN);
  216. else
  217. innerHTML += TODAY_AT + mom.format(TIME_PATTERN) + DATETIME_UNTIL_PATTERN + endTime.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN);
  218. } else {
  219. if (livestream)
  220. innerHTML += ENDED_LIVESTREAM_START + mom.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN) + DATETIME_UNTIL_PATTERN + endTime.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN);
  221. else if (premiere)
  222. innerHTML += ENDED_PREMIERE_START + mom.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN) + DATETIME_UNTIL_PATTERN + endTime.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN);
  223. else
  224. innerHTML += mom.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN) + DATETIME_UNTIL_PATTERN + endTime.format(DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN);
  225. }
  226. }
  227. }
  228. }
  229. }
  230. var contentRating = data.items[0].contentDetails.contentRating;
  231. if (!isUndefinedOrNull(contentRating.ytRating) && contentRating.ytRating == 'ytAgeRestricted')
  232. innerHTML += AGE_RESTRICTED;
  233. if (SHOW_REFRESH) {
  234. if (SHOW_UNDERLINE_ON_TIMESTAMP)
  235. 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>";
  236. else
  237. 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>";
  238. }
  239. element.innerHTML = innerHTML;
  240. if (ongoing)
  241. updateOngoing(durationInMilliseconds);
  242. return ongoing;
  243. }
  244. function updateLiveContentWithChangeCheck(premiere, livestream, data, mom) {
  245. var ongoing = updateLiveContent(premiere, livestream, data, mom);
  246. /*document.getElementsByClassName('html5-main-video')[0].addEventListener('ended', function() {
  247. fetch('https://www.youtube.com/get_video_info?html5=1&video_id=' + currentVideoId).then(function(response) {
  248. return response.text();
  249. }).then(function(video_info) {
  250. try {
  251. let player_response = decodeURIComponent(video_info);
  252. let urlParams = new URLSearchParams(player_response);
  253. if (urlParams.get("player_response") != null) {
  254. player_response = urlParams.get("player_response");
  255. }
  256. player_response = JSON.parse(player_response);
  257. var premiere = !isUndefinedOrNull(player_response) && !player_response.videoDetails.isLiveContent;
  258. premiere = premiere && !isUndefinedOrNull(data.items[0].liveStreamingDetails);
  259. var livestream = !isUndefinedOrNull(player_response) && player_response.videoDetails.isLiveContent;
  260. var live = !isUndefinedOrNull(player_response) && player_response.videoDetails.isLive;
  261. if (ongoing)
  262. updateLiveContent(premiere, livestream, data, mom);
  263. } catch (ex) {
  264. console.error(ex);
  265. }
  266. }).catch(error => console.error("YOUTUBE EXACT UPLOAD ERROR: " + error, "\nget_video_info doesn't seem to work"));
  267. });*/
  268. }
  269. function getExactUploadDate(forceRefresh = false) {
  270. var abort = false;
  271. const processEvent = async () => {
  272. await sleep(500);
  273. const urlParams = new URLSearchParams(window.location.search);
  274. if (urlParams.get("v") != null){
  275. let videoId = urlParams.get("v");
  276. if (videoId == currentVideoId) {
  277. abort = true;
  278. } else {
  279. currentVideoId = videoId;
  280. }
  281. }
  282. if (forceRefresh)
  283. abort = false;
  284. if ((YT_API_KEY != "" || typeof YT_API_KEY != "undefined") && !abort) {
  285. var url = genUrl();
  286. if (url != "") {
  287. fetch(url).then(function(response) {
  288. return response.json();
  289. }).then(function(data) {
  290. if (data.pageInfo.totalResults > 0) {
  291. const addTime = async () => {
  292. var mom = moment(data.items[0].snippet.publishedAt);
  293. console.log(mom);
  294. let payload = {
  295. context: {
  296. client: {
  297. clientName: 'WEB',
  298. clientVersion: '2.20210614.06.00',
  299. originalUrl: window.location.href,
  300. platform: 'DESKTOP',
  301. clientFormFactor: 'UNKNOWN_FORM_FACTOR',
  302. mainAppWebInfo: {
  303. graftUrl: '/watch?v=' + currentVideoId,
  304. webDisplayMode: 'WEB_DISPLAY_MODE_BROWSER',
  305. isWebNativeShareAvailable: false
  306. }
  307. },
  308. user: {
  309. lockedSafetyMode: false
  310. },
  311. request: {
  312. useSsl: true
  313. }
  314. },
  315. videoId: currentVideoId,
  316. racyCheckOk: false,
  317. contentCheckOk: false
  318. };
  319. fetch('https://www.youtube.com/youtubei/v1/player?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'/*InnerTube-API-Key*/, {
  320. method: 'POST',
  321. headers: {
  322. 'Content-Type': 'application/json'
  323. },
  324. body: JSON.stringify(payload)
  325. }).then(function(response) {
  326. return response.text();
  327. }).then(function(video_info) {
  328. console.log('Video-Info:' + video_info);
  329. if (!isUndefinedOrNull(interval)) {
  330. clearInterval(interval);
  331. interval = null;
  332. }
  333. if (!isUndefinedOrNull(changeCheckTimer)) {
  334. clearInterval(changeCheckTimer);
  335. changeCheckTimer = null;
  336. }
  337. try {
  338. let player_response = decodeURIComponent(video_info);
  339. let urlParams = new URLSearchParams(player_response);
  340. if (urlParams.get("player_response") != null) {
  341. player_response = urlParams.get("player_response");
  342. }
  343. player_response = JSON.parse(player_response);// data.items[0].status.privacyStatus = "public" -> Öffentliches Video
  344. var premiere = !isUndefinedOrNull(player_response) && !player_response.videoDetails.isLiveContent;
  345. premiere = premiere && !isUndefinedOrNull(data.items[0].liveStreamingDetails);
  346. var livestream = !isUndefinedOrNull(player_response) && player_response.videoDetails.isLiveContent;
  347. var innerHTML = "<span id=\"dot\" class=\"style-scope ytd-video-primary-info-renderer\">•</span>";
  348. updateLiveContentWithChangeCheck(premiere, livestream, data, mom);
  349. } catch (ex) {
  350. console.error(ex);
  351. }
  352. }).catch(error => console.error("YOUTUBE EXACT UPLOAD ERROR: " + error, "\nget_video_info doesn't seem to work"));
  353. };
  354. addTime();
  355. };
  356. }).catch(error => console.error("YOUTUBE EXACT UPLOAD ERROR: " + error, "\nINVALID API KEY?"));
  357. }
  358. } else {
  359. if(!abort)
  360. console.error("YOUTUBE EXACT UPLOAD ERROR: Undefined api key");
  361. }
  362. }
  363. processEvent();
  364. }
  365. function refreshEventListener() {
  366. getExactUploadDate(true);
  367. }
  368. //getExactUploadDate();
  369. //document.addEventListener('click', getExactUploadDate);
  370. //document.addEventListener('yt-page-data-updated', getExactUploadDate);
  371. document.addEventListener('yt-navigate-finish', getExactUploadDate);
  372. document.addEventListener('refresh-clicked', refreshEventListener);
  373. })();