Return YouTube Comment Username

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

当前为 2023-03-17 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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();
})();