Return YouTube Comment Username

This is to change the handle in the YouTube comments section to a username.

Fra 17.03.2023. Se den seneste versjonen.

// ==UserScript==
// @name Return YouTube Comment Username
// @name:ja YouTubeコメント欄の名前を元に戻す
// @version 0.1.6
// @author yakisova41
// @license MIT
// @namespace https://yt-returnname-api.pages.dev/extension/
// @description This is to change the handle in the YouTube comments section to a username.
// @description:ja YouTubeのコメント欄の名前がハンドル(@...)表記になってしまった場合に、元のユーザーネームに上書きします。
// @match https://www.youtube.com/*
// @grant none
// ==/UserScript==

(() => {
  // src/lib/eventRoot.ts
  function pageChangeListener(eventElement) {
    let beforeHref = "";
    const observer = new MutationObserver(() => {
      const href = location.href;
      if (href !== beforeHref) {
        eventElement.dispatchEvent(
          new CustomEvent("pageChange", {
            detail: {
              beforeHref,
              newHref: href
            }
          })
        );
      }
      beforeHref = href;
    });
    observer.observe(document.querySelector("body"), {
      childList: true,
      subtree: true
    });
  }
  function createEventRoot() {
    const eventElement = document.createElement("div");
    pageChangeListener(eventElement);
    return {
      addEventListener: (eName, listener, options) => {
        eventElement.addEventListener(eName, listener, options);
        const pageChangeListener2 = () => {
          eventElement.removeEventListener(
            "pageChange",
            pageChangeListener2
          );
          eventElement.removeEventListener(eName, listener, options);
        };
        eventElement.addEventListener("pageChange", pageChangeListener2);
      },
      dispatchEvent: (e) => {
        eventElement.dispatchEvent(e);
      },
      removeEventListener: (eName, listener, options) => {
        eventElement.removeEventListener(eName, listener, options);
      },
      native: eventElement
    };
  }

  // src/lib/findElement.ts
  var findElement = (selector) => {
    return new Promise((resolve, reject) => {
      if (isNativeInterval()) {
        const interval = setInterval(() => {
          const elem = document.querySelector(selector);
          if (elem !== null) {
            clearInterval(interval);
            resolve(elem);
          }
        });
      } else {
        let search = function() {
          setTimeout(() => {
            const elem = document.querySelector(selector);
            if (elem !== null) {
              resolve(elem);
            } else {
              search();
            }
          });
        };
        search();
      }
    });
  };
  var findElementAll = (selector) => {
    return new Promise((resolve, reject) => {
      if (isNativeInterval()) {
        const interval = setInterval(() => {
          const elems = document.querySelectorAll(selector);
          if (elems.length !== 0) {
            clearInterval(interval);
            resolve(elems);
          }
        });
      } else {
        let search = function() {
          setTimeout(() => {
            const elems = document.querySelectorAll(selector);
            if (elems.length !== 0) {
              resolve(elems);
            } else {
              search();
            }
          });
        };
        search();
      }
    });
  };
  function isNativeInterval() {
    return window.setInterval.toString() === "function setInterval() { [native code] }";
  }

  // src/lib/getUserName.ts
  async function getUserName(href) {
    const id = href.split("/")[4];
    const data = await fetch(
      `https://www.youtube.com/youtubei/v1/browse?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8&prettyPrint=false`,
      {
        method: "POST",
        headers: {
          accept: "*/*",
          "accept-encoding": "gzip, deflate, br",
          "accept-language": "ja",
          "content-type": "application/json",
          cookie: `GPS=1; YSC=sHEZ9k4QSS0; DEVICE_INFO=DEVICE_INFO; VISITOR_INFO1_LIVE=LLIIVVEE; PREF=f6=40000000&tz=Asia.Tokyo; ST-o2eza2=itct=itct&endpoint=%7B%22clickTrackingParams%22%3A%22CBQQ8JMBGAciEwjNqtCN86H9AhXnm1YBHABY%3D%22%2C%22commandMetadata%22%3A%7B%22webCommandMetadata%22%3A%7B%22url%22%3A%22%2F%40FUCKYOUTUBE%2Fchannels%22%2C%22webPageType%22%3A%22WEB_PAGE_TYPE_CHANNEL%22%2C%22rootVe%22%3A3611%2C%22apiUrl%22%3A%22%2Fyoutubei%2Fv1%2Fbrowse%22%7D%7D%2C%22browseEndpoint%22%3A%7B%22browseId%22%3A%22${id}%22%2C%22params%22%3A%22EghjaGFubmVsc_IGBAoCUgA%253D%22%2C%22canonicalBaseUrl%22%3A%22%2F%40FUCK_YOUTUBE%22%7D%7D`,
          dnt: "1",
          referer: `https://www.youtube.com/channel/${id}`,
          "sec-ch-ua": `"Chromium";v="110", "Not A(Brand";v="24", "Google Chrome";v="110"`,
          "sec-ch-ua-arch": "x86",
          "sec-ch-ua-bitness": "64",
          "sec-ch-ua-full-version": "110.0.5481.104",
          "sec-ch-ua-full-version-list": `"Chromium";v="110.0.5481.104", "Not A(Brand";v="24.0.0.0", "Google Chrome";v="110.0.5481.104"`,
          "sec-ch-ua-mobile": "?0",
          "sec-ch-ua-platform": "Windows",
          "sec-ch-ua-platform-version": "15.0.0",
          "sec-ch-ua-wow64": "?0",
          "sec-fetch-dest": "empty",
          "sec-fetch-mode": "same-origin",
          "sec-fetch-site": "same-origin",
          "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
          "x-client-data": "x-client-data",
          "x-goog-authuser": "0",
          "x-goog-visitor-id": "visitorData",
          "x-origin": "https://www.youtube.com",
          "x-youtube-bootstrap-logged-in": "true",
          "x-youtube-client-name": "1",
          "x-youtube-client-version": "2.20230217.01.00"
        },
        body: JSON.stringify({
          context: {
            client: {
              hl: "ja",
              gl: "JP",
              remoteHost: "1919:8a10:1145:1419:e1c9:b81a:09db:ff3a",
              deviceMake: "",
              deviceModel: "",
              visitorData: "visitorData",
              userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36,gzip(gfe)",
              clientName: "WEB",
              clientVersion: "2.20230217.01.00",
              osName: "Windows",
              osVersion: "10.0",
              originalUrl: "https://www.youtube.com/@FUCK_YOUTUBE/channels",
              platform: "DESKTOP",
              clientFormFactor: "UNKNOWN_FORM_FACTOR",
              configInfo: {
                appInstallData: "appInstallData"
              },
              userInterfaceTheme: "USER_INTERFACE_THEME_DARK",
              timeZone: "Asia/Tokyo",
              browserName: "Chrome",
              browserVersion: "110.0.0.0",
              acceptHeader: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
              deviceExperimentId: "deviceExperimentId",
              screenWidthPoints: 599,
              screenHeightPoints: 937,
              screenPixelDensity: 1,
              screenDensityFloat: 1,
              utcOffsetMinutes: 540,
              memoryTotalKbytes: "8000000",
              mainAppWebInfo: {
                graftUrl: "/@FUCK_YOUTUBE/channels",
                pwaInstallabilityStatus: "PWA_INSTALLABILITY_STATUS_CAN_BE_INSTALLED",
                webDisplayMode: "WEB_DISPLAY_MODE_BROWSER",
                isWebNativeShareAvailable: true
              }
            },
            user: { lockedSafetyMode: false },
            request: {
              useSsl: true,
              internalExperimentFlags: [],
              consistencyTokenJars: []
            },
            clickTracking: {
              clickTrackingParams: "CCgQ8JMBGAgiEwiW5Pey8qH9AhVVSA8CHUHLAGc="
            },
            adSignalsInfo: {
              params: [
                { key: "dt", value: "1676820301790" },
                { key: "flash", value: "0" },
                { key: "frm", value: "0" },
                { key: "u_tz", value: "540" },
                { key: "u_his", value: "1" },
                { key: "u_h", value: "1080" },
                { key: "u_w", value: "1920" },
                { key: "u_ah", value: "1040" },
                { key: "u_aw", value: "1920" },
                { key: "u_cd", value: "24" },
                { key: "bc", value: "31" },
                { key: "bih", value: "937" },
                { key: "biw", value: "582" },
                {
                  key: "brdim",
                  value: "-1920,0,-1920,0,1920,0,1920,1040,599,937"
                },
                { key: "vis", value: "1" },
                { key: "wgl", value: "true" },
                { key: "ca_type", value: "image" }
              ]
            }
          },
          browseId: id,
          params: "EghjaGFubmVsc_IGBAoCUgA%3D"
        })
      }
    ).then((res) => res.text()).then((text) => {
      const data2 = JSON.parse(text);
      const name = data2["header"]["c4TabbedHeaderRenderer"]["title"];
      return name;
    });
    return data;
  }

  // src/watch/replaceComments.ts
  function replaceComments(comments, page, eventRoot) {
    const nameStore = [];
    comments.forEach(async (c, index) => {
      const nthChild = page * 20 + (index + 1);
      const commentElem = await findElement(
        `#comments > #sections > #contents > ytd-comment-thread-renderer:nth-child(${nthChild})`
      );
      eventRoot.addEventListener("pageChange", () => {
        commentElem.remove();
      });
      const channelHrefElem = commentElem.querySelector(
        "#comment > #body > #main > #header > #header-author > h3 > a "
      );
      let nameElem;
      nameElem = commentElem.querySelector(
        "#comment > #body > #main > #header  > #header-author > #author-comment-badge > ytd-author-comment-badge-renderer > #name > ytd-channel-name > #container > #text-container > #text "
      );
      if (nameElem === null) {
        nameElem = channelHrefElem.querySelector("span");
      }
      if (channelHrefElem.href in nameStore) {
        nameElem.innerHTML = nameStore[channelHrefElem.href];
      } else {
        getUserName(channelHrefElem.href).then((name) => {
          nameElem.innerHTML = name;
          nameStore[channelHrefElem.href] = name;
        });
      }
    });
  }

  // src/listeners/commentRenderingListener.ts
  async function renderingListener(eventRoot) {
    let renderingTrigger = false;
    const contents = await findElement("#comments > #sections");
    const observer = new MutationObserver(() => {
      if ("can-show-more" in contents.attributes) {
        renderingTrigger = true;
      } else if ("continuation-is-reloading" in contents.attributes) {
        renderingTrigger = true;
        eventRoot.dispatchEvent(
          new CustomEvent("commentsContinuationReloading")
        );
      } else {
        if (renderingTrigger) {
          renderingTrigger = false;
          eventRoot.dispatchEvent(
            new CustomEvent("commentsRenderingSuccess")
          );
        }
      }
    });
    observer.observe(contents, {
      attributes: true
    });
    return observer;
  }

  // src/watch/replaceReplies.ts
  async function replaceReplies(page, targetIndex) {
    const nthChild = page * 20 + (targetIndex + 1);
    const repliesElem = await findElementAll(
      `#comments > #sections > #contents > ytd-comment-thread-renderer:nth-child(${nthChild}) > #replies > ytd-comment-replies-renderer > #expander > #expander-contents > #contents > ytd-comment-renderer`
    );
    repliesElem.forEach((elem) => {
      const channelHrefElem = elem.querySelector(
        "#body > #main > #header > #header-author > h3 > a "
      );
      let nameElem;
      nameElem = elem.querySelector(
        "#body > #main > #header > #header-author >  #author-comment-badge > ytd-author-comment-badge-renderer > #name > ytd-channel-name > #container > #text-container > #text "
      );
      if (nameElem === null) {
        nameElem = channelHrefElem.querySelector("span");
      }
      getUserName(channelHrefElem.href).then((name) => {
        nameElem.innerHTML = name;
      });
      const textElems = elem.querySelectorAll(
        "#body > #main > #comment-content > #expander > #content > #content-text > a.yt-formatted-string"
      );
      textElems.forEach((textElem) => {
        const text = textElem.innerHTML;
        if (text.match("@.*")) {
          getUserName(textElem.href).then((name) => {
            textElem.innerHTML = "@" + name;
          });
        }
      });
    });
  }

  // src/watch/watch.ts
  async function watch(eventRoot) {
    const renderListener = await renderingListener(eventRoot);
    eventRoot.addEventListener("pageChange", () => {
      renderListener.disconnect();
    });
    const commentsTargetIdStore = [];
    let commentsPage = 0;
    eventRoot.addEventListener("commentsContinuationReloading", () => {
      commentsPage = 0;
    });
    eventRoot.addEventListener(
      "commentFetch",
      ({ detail: { comments, mode } }) => {
        comments.forEach((comment, index) => {
          if (comment["commentThreadRenderer"]["replies"] !== void 0) {
            commentsTargetIdStore.push({
              index,
              commentsPage,
              targetId: comment["commentThreadRenderer"]["replies"]["commentRepliesRenderer"]["targetId"]
            });
          }
        });
        if (mode === 1) {
          if (comments.length !== 20) {
            replaceComments(comments, commentsPage, eventRoot);
            commentsPage = commentsPage + 1;
          } else {
            const handleRenderingSucess = () => {
              replaceComments(comments, commentsPage, eventRoot);
              commentsPage = commentsPage + 1;
              eventRoot.native.removeEventListener(
                "commentsRenderingSuccess",
                handleRenderingSucess
              );
            };
            eventRoot.native.addEventListener(
              "commentsRenderingSuccess",
              handleRenderingSucess
            );
          }
        } else {
          replaceComments(comments, commentsPage, eventRoot);
          commentsPage = commentsPage + 1;
        }
      }
    );
    eventRoot.addEventListener(
      "repliesFetch",
      ({ detail: { replies, targetId } }) => {
        commentsTargetIdStore.forEach((targetData, index) => {
          if (targetData.targetId === targetId) {
            replaceReplies(targetData.commentsPage, targetData.index);
          }
        });
      }
    );
  }

  // src/lib/FetchIntercepter.ts
  function FetchIntercepter(originalFetch) {
    const actions = [];
    return {
      start: () => {
        window.fetch = (...arg) => {
          const [request, init] = arg;
          const response = originalFetch(request, init);
          response.then((res) => {
            actions.forEach((action) => {
              action({
                request,
                init,
                response: res
              });
            });
          });
          return response;
        };
      },
      addAction: (action) => {
        actions.push(action);
      },
      stop: () => {
        window.fetch = originalFetch;
      }
    };
  }

  // src/listeners/commentFetchListener.ts
  function commentFetchListener(eventRoot) {
    const intercepter = FetchIntercepter(window.fetch);
    intercepter.start();
    intercepter.addAction(async (data) => {
      if (typeof data.request["url"] === "string" && data.request["url"].match(
        "https://www.youtube.com/youtubei/v1/next.*"
      )) {
        const responseClone = data.response.clone();
        const text = await responseClone.text();
        const body = JSON.parse(text);
        const commentFetchMode = is_comments(body);
        if (commentFetchMode === 1) {
          const comments = body["onResponseReceivedEndpoints"][1]["reloadContinuationItemsCommand"]["continuationItems"];
          const data2 = removeContinuationItem(comments);
          eventRoot.dispatchEvent(
            new CustomEvent("commentFetch", {
              detail: {
                comments: data2,
                mode: commentFetchMode
              }
            })
          );
        }
        if (commentFetchMode === 2) {
          const comments = body["onResponseReceivedEndpoints"][0]["appendContinuationItemsAction"]["continuationItems"];
          const data2 = removeContinuationItem(comments);
          eventRoot.dispatchEvent(
            new CustomEvent("commentFetch", {
              detail: {
                comments: data2,
                mode: commentFetchMode
              }
            })
          );
        }
        if (commentFetchMode === 0) {
          if (body["onResponseReceivedEndpoints"][0]["appendContinuationItemsAction"]["targetId"].match("comment-replies.*")) {
            const replies = body["onResponseReceivedEndpoints"][0]["appendContinuationItemsAction"]["continuationItems"];
            const targetId = body["onResponseReceivedEndpoints"][0]["appendContinuationItemsAction"]["targetId"];
            eventRoot.dispatchEvent(
              new CustomEvent("repliesFetch", {
                detail: {
                  replies,
                  targetId
                }
              })
            );
          }
        }
      }
    });
    return {
      stop: intercepter.stop
    };
  }
  function is_comments(body) {
    const onResponseReceivedEndpoints = body["onResponseReceivedEndpoints"];
    if (onResponseReceivedEndpoints.length > 1 && "reloadContinuationItemsCommand" in onResponseReceivedEndpoints[1] && onResponseReceivedEndpoints[1]["reloadContinuationItemsCommand"]["targetId"] === "comments-section") {
      return 1;
    }
    if ("appendContinuationItemsAction" in onResponseReceivedEndpoints[0] && onResponseReceivedEndpoints[0]["appendContinuationItemsAction"]["targetId"] === "comments-section") {
      return 2;
    }
    return 0;
  }
  function removeContinuationItem(comments) {
    comments.forEach((comment, index) => {
      if ("continuationItemRenderer" in comment) {
        comments.splice(index, 1);
      }
    });
    return comments;
  }

  // src/index.ts
  function main() {
    const eventRoot = createEventRoot();
    commentFetchListener(eventRoot);
    eventRoot.native.addEventListener(
      "pageChange",
      async (e) => {
        const { newHref } = e.detail;
        const pageName = newHref.split("/")[3].split("?")[0];
        if (pageName === "watch") {
          watch(eventRoot);
        }
      }
    );
  }
  main();
})();