Youtube exact upload

Adds exact upload time to youtube videos

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==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.5.0/luxon.min.js
// @version        0.19
// @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
  const YT_API_KEY = "YouTube API-Key";
  let DATE_PATTERN,
    TIME_PATTERN,
    DATETIME_COMBINE_PATTERN,
    SCHEDULED_LIVESTREAM_START,
    SCHEDULED_PREMIERE_START,
    ONGOING_LIVESTREAM_START;
  let ONGOING_PREMIERE_START,
    ENDED_LIVESTREAM_START,
    ENDED_PREMIERE_START,
    DATETIME_UNTIL_PATTERN,
    SINCE,
    TODAY_AT;
  const AGE_RESTRICTED = " - FSK 18";
  const SHOW_REFRESH = true;
  const REFRESH_TIMESTAMP = "⟳";
  const SHOW_UNDERLINE_ON_TIMESTAMP = false;
  const BASE_URL =
    "https://www.googleapis.com/youtube/v3/videos?part=snippet,liveStreamingDetails,contentDetails,localizations,player,statistics,status&key=" +
    YT_API_KEY;
  luxon.Settings.defaultLocale = document.documentElement.lang;
  if (document.documentElement.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 (document.documentElement.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 (document.documentElement.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 ";
  }
  let interval = null;
  let changeCheckTimer = null;
  let currentVideoId = null;
  function getVideoId() {
    return new URLSearchParams(globalThis.location.search).get("v");
  }
  function genUrl() {
    const videoId = getVideoId();
    if (videoId != null) {
      return BASE_URL + "&id=" + videoId;
    } else {
      return "";
    }
  }
  function sleep(milliseconds) {
    return new Promise((resolve) => setTimeout(resolve, milliseconds));
  }
  function formatMilliseconds(milliseconds) {
    const dur = luxon.Duration.fromMillis(milliseconds).shiftTo(
      "hours",
      "minutes",
      "seconds",
    );
    return [dur.hours, dur.minutes, dur.seconds].map((unit) =>
      String(unit).padStart(2, "0")
    ).join(":");
  }
  function updateOngoing(startTime) {
    if (interval) {
      clearInterval(interval);
      interval = null;
    }
    interval = setInterval(function () {
      const durationInMilliseconds = luxon.Interval.fromDateTimes(
        startTime,
        luxon.DateTime.now(),
      ).length("milliseconds");
      const ongoingVideoDuration = document.getElementById(
        "ongoing-video-duration",
      );
      ongoingVideoDuration.innerHTML = formatMilliseconds(
        durationInMilliseconds,
      );
      if (ongoingVideoDuration.parentNode) {
        ongoingVideoDuration.parentNode.title =
          ongoingVideoDuration.parentNode.innerText;
      }
    }, 500);
  }
  async function updateLiveContent(premiere, livestream, data, dt) {
    let element = null;
    while (!element) {
      element = document.getElementById("primary-inner");
      await sleep(200);
    }
    let durationInMilliseconds = null;
    let ongoing = false;
    let 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_PATTERH + 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
        const liveStreamingDetails = data.items[0].liveStreamingDetails;
        dt = luxon.DateTime.fromISO(liveStreamingDetails.actualStartTime);
        let endTime = null;
        if (liveStreamingDetails.actualEndTime) {
          endTime = luxon.DateTime.fromISO(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)
              }</span>)`;
            } else if (premiere) {
              innerHTML += `${ONGOING_PREMIERE_START}${
                dt.toFormat(TIME_PATTERN)
              } (<span id="ongoing-video-duration">${
                formatMilliseconds(durationInMilliseconds)
              }</span>)`;
            } else {
              innerHTML += `${SINCE} ${
                dt.toFormat(TIME_PATTERN)
              } (<span id="ongoing-video-duration">${
                formatMilliseconds(durationInMilliseconds)
              }</span>)`;
            }
          } else {
            if (livestream) {
              innerHTML += `${ONGOING_LIVESTREAM_START}${
                dt.toFormat(
                  DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
                )
              } (<span id="ongoing-video-duration">${
                formatMilliseconds(durationInMilliseconds)
              }</span>)`;
            } else if (premiere) {
              innerHTML += `${ONGOING_PREMIERE_START}${
                dt.toFormat(
                  DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
                )
              } (<span id="ongoing-video-duration">${
                formatMilliseconds(durationInMilliseconds)
              }</span>)`;
            } else {
              innerHTML += `${SINCE} ${
                dt.toFormat(
                  DATE_PATTERN + DATETIME_COMBINE_PATTERN + TIME_PATTERN,
                )
              } (<span id="ongoing-video-duration">${
                formatMilliseconds(durationInMilliseconds)
              }</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,
                  )
                }`;
              }
            }
          }
        }
      }
    }
    const 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);
    const 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) {
    let abort = false;
    const processEvent = async () => {
      await sleep(500);
      const videoId = getVideoId();
      if (videoId != null) {
        if (videoId == currentVideoId) {
          abort = true;
        } else {
          currentVideoId = videoId;
        }
      }
      if (forceRefresh) abort = false;
      if ((YT_API_KEY != "" || typeof YT_API_KEY != "undefined") && !abort) {
        const url = genUrl();
        if (url != "") {
          try {
            const data = await fetch(url).then((response) => response.json());
            if (data.pageInfo.totalResults > 0) {
              const addTime = async () => {
                const dt = luxon.DateTime.fromISO(
                  data.items[0].snippet.publishedAt,
                );
                console.log(dt);
                const payload = {
                  context: {
                    client: {
                      clientName: "WEB",
                      clientVersion: "2.20210614.06.00",
                      originalUrl: globalThis.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,
                };
                const video_info = await 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((response) => response.json());
                try {
                  if (interval) {
                    clearInterval(interval);
                    interval = null;
                  }
                  if (changeCheckTimer) {
                    clearInterval(changeCheckTimer);
                    changeCheckTimer = null;
                  }
                  try {
                    const premiere = !!(video_info &&
                      !video_info.videoDetails.isLiveContent) &&
                      !!data.items[0].liveStreamingDetails;
                    const livestream = !!(video_info &&
                      video_info.videoDetails.isLiveContent);
                    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);
  }
  document.addEventListener("refresh-clicked", refreshEventListener);
  function main() {
    const ytdPlayer = document.getElementById("ytd-player");
    if (!ytdPlayer) {
      setTimeout(() => main(), 500);
    } else {
      ytdPlayer.addEventListener(
        "yt-player-updated",
        () => getExactUploadDate(),
      );
    }
  }
  main();
  if (getVideoId() != null) {
    getExactUploadDate();
  }
})();