Jelajahi Sumber

Merge branch 'main' of https://github.com/Anarios/return-youtube-dislike

Dmitrii Selivanov 3 tahun lalu
induk
melakukan
96f063838d

+ 51 - 376
Extensions/combined/ryd.content-script.js

@@ -1,387 +1,62 @@
-function RYD() {
-  const LIKED_STATE = "LIKED_STATE";
-  const DISLIKED_STATE = "DISLIKED_STATE";
-  const NEUTRAL_STATE = "NEUTRAL_STATE";
 
-  let storedData = {
-    likes: 0,
-    dislikes: 0,
-    previousState: NEUTRAL_STATE,
-  };
-
-  function cLog(message, writer) {
-    message = `[return youtube dislike]: ${message}`;
-    if (writer) {
-      writer(message);
-    } else {
-      console.log(message);
-    }
-  }
-
-  function isMobile() {
-    return location.hostname == "m.youtube.com";
-  }
-
-  function getButtons() {
-    if (isMobile()) {
-      return document.querySelector(".slim-video-action-bar-actions");
-    }
-    //   If Menu Element Is Displayed:
-    if (document.getElementById("menu-container")?.offsetParent === null) {
-      return document.querySelector(
-        "ytd-menu-renderer.ytd-watch-metadata > div"
-      );
-      //   If Menu Element Isnt Displayed:
-    } else {
-      return document
-        .getElementById("menu-container")
-        ?.querySelector("#top-level-buttons-computed");
-    }
-  }
-
-  function getLikeButton() {
-    return getButtons().children[0];
-  }
-
-  function getDislikeButton() {
-    return getButtons().children[1];
-  }
-
-  function isVideoLiked() {
-    if (isMobile()) {
-      return (
-        getLikeButton().querySelector("button").getAttribute("aria-label") ==
-        "true"
-      );
-    }
-    return getLikeButton().classList.contains("style-default-active");
-  }
-
-  function isVideoDisliked() {
-    if (isMobile()) {
-      return (
-        getDislikeButton().querySelector("button").getAttribute("aria-label") ==
-        "true"
-      );
-    }
-    return getDislikeButton().classList.contains("style-default-active");
-  }
-
-  function checkForSignInButton() {
-    if (
-      document.querySelector(
-        "a[href^='https://accounts.google.com/ServiceLogin']"
-      )
-    ) {
-      return true;
-    } else {
-      return false;
-    }
-  }
-
-  function getState() {
-    if (isVideoLiked()) {
-      return { current: LIKED_STATE, previous: storedData.previousState };
-    }
-    if (isVideoDisliked()) {
-      return { current: DISLIKED_STATE, previous: storedData.previousState };
-    }
-    return { current: NEUTRAL_STATE, previous: storedData.previousState };
-  }
-
-  //---   Sets The Likes And Dislikes Values   ---//
-  function setLikes(likesCount) {
-    (getButtons().children[0].querySelector("#text")).innerText = likesCount;
-  }
-
-  function setDislikes(dislikesCount) {
-    if (isMobile()) {
-      getButtons().children[1].querySelector(
-        ".button-renderer-text"
-      ).innerText = dislikesCount;
-      return;
-    }
-    (getButtons().children[1].querySelector("#text").innerText) = dislikesCount;
-  }
-
-  function getLikeCountFromButton() {
-    let likesStr = getLikeButton()
-      .querySelector("button")
-      .getAttribute("aria-label")
-      .replace(/\D/g, "");
-    return likesStr.length > 0 ? parseInt(likesStr) : false;
-  }
-
-  function processResponse(response) {
-    const formattedDislike = numberFormat(response.dislikes);
-    setDislikes(formattedDislike);
-    storedData.dislikes = parseInt(response.dislikes);
-    storedData.likes = getLikeCountFromButton() || parseInt(response.likes);
-    createRateBar(storedData.likes, storedData.dislikes);
-  }
-
-  function setState() {
-    storedData.previousState = isVideoDisliked()
-      ? DISLIKED_STATE
-      : isVideoLiked()
-      ? LIKED_STATE
-      : NEUTRAL_STATE;
-    let statsSet = false;
-
-    RYDTools.getBrowser().runtime.sendMessage(
-      {
-        message: "set_state",
-        videoId: getVideoId(window.location.href),
-        state: getState().current,
-        likeCount: getLikeCountFromButton() || null
-      },
-      function (response) {
-        cLog("response from api:");
-        cLog(JSON.stringify(response));
-        if (response !== undefined && !("traceId" in response) && !statsSet) {
-          processResponse(response);
-        } else {
-        }
-      }
-    );
-  }
-
-  function sendVote(vote) {
-    RYDTools.getBrowser().runtime.sendMessage({
-      message: "send_vote",
-      vote: vote,
-      videoId: getVideoId(window.location.href)
-    });
-  }
-
-  function likeClicked() {
-    if (checkForSignInButton() === false) {
-      if (storedData.previousState === DISLIKED_STATE) {
-        sendVote(1);
-        storedData.dislikes--;
-        storedData.likes++;
-        createRateBar(storedData.likes, storedData.dislikes);
-        setDislikes(numberFormat(storedData.dislikes));
-        storedData.previousState = LIKED_STATE;
-      } else if (storedData.previousState === NEUTRAL_STATE) {
-        sendVote(1);
-        storedData.likes++;
-        createRateBar(storedData.likes, storedData.dislikes);
-        storedData.previousState = LIKED_STATE;
-      } else if ((storedData.previousState = LIKED_STATE)) {
-        sendVote(0);
-        storedData.likes--;
-        createRateBar(storedData.likes, storedData.dislikes);
-        storedData.previousState = NEUTRAL_STATE;
-      }
-    }
-  }
-
-  function dislikeClicked() {
-    if (checkForSignInButton() == false) {
-      if (storedData.previousState === NEUTRAL_STATE) {
-        sendVote(-1);
-        storedData.dislikes++;
-        setDislikes(numberFormat(storedData.dislikes));
-        createRateBar(storedData.likes, storedData.dislikes);
-        storedData.previousState = DISLIKED_STATE;
-      } else if (storedData.previousState === DISLIKED_STATE) {
-        sendVote(0);
-        storedData.dislikes--;
-        setDislikes(numberFormat(storedData.dislikes));
-        createRateBar(storedData.likes, storedData.dislikes);
-        storedData.previousState = NEUTRAL_STATE;
-      } else if (storedData.previousState === LIKED_STATE) {
-        sendVote(-1);
-        storedData.likes--;
-        storedData.dislikes++;
-        setDislikes(numberFormat(storedData.dislikes));
-        createRateBar(storedData.likes, storedData.dislikes);
-        storedData.previousState = DISLIKED_STATE;
-      }
-    }
-  }
-
-  function setInitialState() {
-    setState();
-    setTimeout(() => {
-      sendVideoIds();
-    }, 1500);
-  }
-
-  function getVideoId(url) {
-    const urlObject = new URL(url);
-    const pathname = urlObject.pathname;
-    if (pathname.startsWith("/clip")) {
-      return document.querySelector("meta[itemprop='videoId']").content;
-    } else {
-      return urlObject.searchParams.get("v");
-    }
-  }
-
-  function isVideoLoaded() {
-    const videoId = getVideoId(window.location.href);
-    return (
-      document.querySelector(`ytd-watch-flexy[video-id='${videoId}']`) !==
-        null ||
-      // mobile: no video-id attribute
-      document.querySelector('#player[loading="false"]:not([hidden])') !== null
-    );
-  }
-
-  function roundDown(num) {
-    if (num < 1000) return num;
-    const int = Math.floor(Math.log10(num) - 2);
-    const decimal = int + (int % 3 ? 1 : 0);
-    const value = Math.floor(num / 10 ** decimal);
-    return value * 10 ** decimal;
-  }
-
-  function numberFormat(numberState) {
-    let userLocales;
-    try {
-      userLocales = new URL(
-        Array.from(document.querySelectorAll("head > link[rel='search']"))
-          ?.find((n) => n?.getAttribute("href")?.includes("?locale="))
-          ?.getAttribute("href")
-      )?.searchParams?.get("locale");
-    } catch {}
-    const formatter = Intl.NumberFormat(
-      document.documentElement.lang || userLocales || navigator.language,
-      {
-        notation: "compact",
-      }
-    );
-
-    return formatter.format(roundDown(numberState));
-  }
-
-  let jsInitChecktimer = null;
+import {
+  getButtons,
+  getLikeButton,
+  getDislikeButton,
+  checkForSignInButton,
+} from "./src/buttons";
+import {
+  isMobile,
+  isVideoDisliked,
+  isVideoLiked,
+  getState,
+  setState,
+  setInitialState,
+  setLikes,
+  setDislikes,
+  getLikeCountFromButton,
+  LIKED_STATE,
+  DISLIKED_STATE,
+  NEUTRAL_STATE,
+} from "./src/state";
+import { numberFormat, getBrowser, getVideoId, isVideoLoaded, cLog } from "./src/utils";
+import { createRateBar } from "./src/bar";
+import { sendVideoIds, sendVote, likeClicked, dislikeClicked } from "./src/events"
+
+let storedData = {
+  likes: 0,
+  dislikes: 0,
+  previousState: NEUTRAL_STATE,
+};
 
-  function setEventListeners(evt) {
-    function checkForJS_Finish() {
-      if (getButtons()?.offsetParent && isVideoLoaded()) {
-        clearInterval(jsInitChecktimer);
-        jsInitChecktimer = null;
-        const buttons = getButtons();
-        if (!window.returnDislikeButtonlistenersSet) {
-          buttons.children[0].addEventListener("click", likeClicked);
-          buttons.children[1].addEventListener("click", dislikeClicked);
-          window.returnDislikeButtonlistenersSet = true;
-        }
-        setInitialState();
+let jsInitChecktimer = null;
+
+function setEventListeners(evt) {
+  function checkForJS_Finish() {
+    if (getButtons()?.offsetParent && isVideoLoaded()) {
+      clearInterval(jsInitChecktimer);
+      jsInitChecktimer = null;
+      const buttons = getButtons();
+      if (!window.returnDislikeButtonlistenersSet) {
+        buttons.children[0].addEventListener("click", () => likeClicked(storedData));
+        buttons.children[1].addEventListener("click", () => dislikeClicked(storedData));
+        window.returnDislikeButtonlistenersSet = true;
       }
-    }
-
-    if (window.location.href.indexOf("watch?") >= 0) {
-      jsInitChecktimer = setInterval(checkForJS_Finish, 111);
-    }
-  }
-
-  function createRateBar(likes, dislikes) {
-    let rateBar = document.getElementById("ryd-bar-container");
-
-    const widthPx =
-      getButtons().children[0].clientWidth +
-      getButtons().children[1].clientWidth +
-      8;
-
-    const widthPercent =
-      likes + dislikes > 0 ? (likes / (likes + dislikes)) * 100 : 50;
-
-    if (!rateBar) {
-      (
-        document.getElementById("menu-container") ||
-        document.querySelector("ytm-slim-video-action-bar-renderer")
-      ).insertAdjacentHTML(
-        "beforeend",
-        `
-          <div class="ryd-tooltip" style="width: ${widthPx}px">
-          <div class="ryd-tooltip-bar-container">
-             <div
-                id="ryd-bar-container"
-                style="width: 100%; height: 2px;"
-                >
-                <div
-                   id="ryd-bar"
-                   style="width: ${widthPercent}%; height: 100%"
-                   ></div>
-             </div>
-          </div>
-          <tp-yt-paper-tooltip position="top" id="ryd-dislike-tooltip" class="style-scope ytd-sentiment-bar-renderer" role="tooltip" tabindex="-1">
-             <!--css-build:shady-->${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}
-          </tp-yt-paper-tooltip>
-          </div>
-  `
-      );
-    } else {
-      document.getElementById("ryd-bar-container").style.width = widthPx + "px";
-      document.getElementById("ryd-bar").style.width = widthPercent + "%";
-
-      document.querySelector(
-        "#ryd-dislike-tooltip > #tooltip"
-      ).innerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}`;
+      setInitialState(storedData);
     }
   }
 
-  function sendVideoIds() {
-    let links = Array.from(
-      document.getElementsByClassName(
-        "yt-simple-endpoint ytd-compact-video-renderer"
-      )
-    ).concat(
-      Array.from(
-        document.getElementsByClassName("yt-simple-endpoint ytd-thumbnail")
-      )
-    );
-    // Also try mobile
-    if (links.length < 1)
-      links = Array.from(
-        document.querySelectorAll(
-          ".large-media-item-metadata > a, a.large-media-item-thumbnail-container"
-        )
-      );
-    const ids = links
-      .filter((x) => x.href && x.href.indexOf("/watch?v=") > 0)
-      .map((x) => getVideoId(x.href));
-    RYDTools.getBrowser().runtime.sendMessage({
-      message: "send_links",
-      videoIds: ids,
-    });
+  if (window.location.href.indexOf("watch?") >= 0) {
+    jsInitChecktimer = setInterval(checkForJS_Finish, 111);
   }
-
-  setEventListeners();
-
-  document.addEventListener("yt-navigate-finish", function (event) {
-    if (jsInitChecktimer !== null) clearInterval(jsInitChecktimer);
-    window.returnDislikeButtonlistenersSet = false;
-    setEventListeners();
-  });
-
-  setTimeout(() => sendVideoIds(), 2500);
-
-  this.init = function () {};
 }
 
-RYD.getInstance = function () {
-  if (typeof RYD.instance == "undefined") RYD.instance = new RYD();
-  return RYD.instance;
-};
-
-RYDTools = {};
+setEventListeners();
 
-RYDTools.getBrowser = function () {
-  if (typeof chrome !== "undefined" && typeof chrome.runtime !== "undefined") {
-    return chrome;
-  } else if (
-    typeof browser !== "undefined" &&
-    typeof browser.runtime !== "undefined"
-  ) {
-    return browser;
-  } else {
-    console.log("browser is not supported");
-    return false;
-  }
-};
+document.addEventListener("yt-navigate-finish", function (event) {
+  if (jsInitChecktimer !== null) clearInterval(jsInitChecktimer);
+  window.returnDislikeButtonlistenersSet = false;
+  setEventListeners();
+});
 
-RYD.getInstance().init();
+setTimeout(() => sendVideoIds(), 2500);

+ 49 - 0
Extensions/combined/src/bar.js

@@ -0,0 +1,49 @@
+import { getButtons } from "./buttons";
+
+function createRateBar(likes, dislikes) {
+  let rateBar = document.getElementById("ryd-bar-container");
+
+  const widthPx =
+    getButtons().children[0].clientWidth +
+    getButtons().children[1].clientWidth +
+    8;
+
+  const widthPercent =
+    likes + dislikes > 0 ? (likes / (likes + dislikes)) * 100 : 50;
+
+  if (!rateBar) {
+    (
+      document.getElementById("menu-container") ||
+      document.querySelector("ytm-slim-video-action-bar-renderer")
+    ).insertAdjacentHTML(
+      "beforeend",
+      `
+          <div class="ryd-tooltip" style="width: ${widthPx}px">
+          <div class="ryd-tooltip-bar-container">
+             <div
+                id="ryd-bar-container"
+                style="width: 100%; height: 2px;"
+                >
+                <div
+                   id="ryd-bar"
+                   style="width: ${widthPercent}%; height: 100%"
+                   ></div>
+             </div>
+          </div>
+          <tp-yt-paper-tooltip position="top" id="ryd-dislike-tooltip" class="style-scope ytd-sentiment-bar-renderer" role="tooltip" tabindex="-1">
+             <!--css-build:shady-->${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}
+          </tp-yt-paper-tooltip>
+          </div>
+  `
+    );
+  } else {
+    document.getElementById("ryd-bar-container").style.width = widthPx + "px";
+    document.getElementById("ryd-bar").style.width = widthPercent + "%";
+
+    document.querySelector(
+      "#ryd-dislike-tooltip > #tooltip"
+    ).innerHTML = `${likes.toLocaleString()}&nbsp;/&nbsp;${dislikes.toLocaleString()}`;
+  }
+}
+
+export { createRateBar };

+ 43 - 0
Extensions/combined/src/buttons.js

@@ -0,0 +1,43 @@
+import { isMobile } from "./state";
+
+function getButtons() {
+  if (isMobile()) {
+    return document.querySelector(".slim-video-action-bar-actions");
+  }
+  //---   If Menu Element Is Displayed:   ---//
+  if (document.getElementById("menu-container")?.offsetParent === null) {
+    return document.querySelector("ytd-menu-renderer.ytd-watch-metadata > div");
+    //---   If Menu Element Isnt Displayed:   ---//
+  } else {
+    return document
+      .getElementById("menu-container")
+      ?.querySelector("#top-level-buttons-computed");
+  }
+}
+
+function getLikeButton() {
+  return getButtons().children[0];
+}
+
+function getDislikeButton() {
+  return getButtons().children[1];
+}
+
+function checkForSignInButton() {
+  if (
+    document.querySelector(
+      "a[href^='https://accounts.google.com/ServiceLogin']"
+    )
+  ) {
+    return true;
+  } else {
+    return false;
+  }
+}
+
+export {
+  getButtons,
+  getLikeButton,
+  getDislikeButton,
+  checkForSignInButton,
+};

+ 88 - 0
Extensions/combined/src/events.js

@@ -0,0 +1,88 @@
+import { getBrowser, getVideoId, numberFormat } from "./utils"
+import { checkForSignInButton } from "./buttons"
+import { NEUTRAL_STATE, LIKED_STATE, DISLIKED_STATE, setDislikes } from "./state"
+import { createRateBar } from "./bar"
+
+function sendVote(vote) {
+  getBrowser().runtime.sendMessage({
+    message: "send_vote",
+    vote: vote,
+    videoId: getVideoId(window.location.href),
+  });
+}
+
+function sendVideoIds() {
+  let links = Array.from(
+    document.getElementsByClassName(
+      "yt-simple-endpoint ytd-compact-video-renderer"
+    )
+  ).concat(
+    Array.from(
+      document.getElementsByClassName("yt-simple-endpoint ytd-thumbnail")
+    )
+  );
+  // Also try mobile
+  if (links.length < 1)
+    links = Array.from(
+      document.querySelectorAll(
+        ".large-media-item-metadata > a, a.large-media-item-thumbnail-container"
+      )
+    );
+  const ids = links
+    .filter((x) => x.href && x.href.indexOf("/watch?v=") > 0)
+    .map((x) => getVideoId(x.href));
+  getBrowser().runtime.sendMessage({
+    message: "send_links",
+    videoIds: ids,
+  });
+}
+
+function likeClicked(storedData) {
+  if (checkForSignInButton() === false) {
+    if (storedData.previousState === DISLIKED_STATE) {
+      sendVote(1);
+      storedData.dislikes--;
+      storedData.likes++;
+      createRateBar(storedData.likes, storedData.dislikes);
+      setDislikes(numberFormat(storedData.dislikes));
+      storedData.previousState = LIKED_STATE;
+    } else if (storedData.previousState === NEUTRAL_STATE) {
+      sendVote(1);
+      storedData.likes++;
+      createRateBar(storedData.likes, storedData.dislikes);
+      storedData.previousState = LIKED_STATE;
+    } else if ((storedData.previousState = LIKED_STATE)) {
+      sendVote(0);
+      storedData.likes--;
+      createRateBar(storedData.likes, storedData.dislikes);
+      storedData.previousState = NEUTRAL_STATE;
+    }
+  }
+}
+
+function dislikeClicked(storedData) {
+  if (checkForSignInButton() == false) {
+    if (storedData.previousState === NEUTRAL_STATE) {
+      sendVote(-1);
+      storedData.dislikes++;
+      setDislikes(numberFormat(storedData.dislikes));
+      createRateBar(storedData.likes, storedData.dislikes);
+      storedData.previousState = DISLIKED_STATE;
+    } else if (storedData.previousState === DISLIKED_STATE) {
+      sendVote(0);
+      storedData.dislikes--;
+      setDislikes(numberFormat(storedData.dislikes));
+      createRateBar(storedData.likes, storedData.dislikes);
+      storedData.previousState = NEUTRAL_STATE;
+    } else if (storedData.previousState === LIKED_STATE) {
+      sendVote(-1);
+      storedData.likes--;
+      storedData.dislikes++;
+      setDislikes(numberFormat(storedData.dislikes));
+      createRateBar(storedData.likes, storedData.dislikes);
+      storedData.previousState = DISLIKED_STATE;
+    }
+  }
+}
+
+export { sendVote, sendVideoIds, likeClicked, dislikeClicked }

+ 120 - 0
Extensions/combined/src/state.js

@@ -0,0 +1,120 @@
+import { getLikeButton, getDislikeButton, getButtons } from "./buttons";
+import { createRateBar } from "./bar";
+import { getBrowser, getVideoId, cLog, numberFormat } from "./utils";
+import { sendVideoIds } from "./events";
+
+const LIKED_STATE = "LIKED_STATE";
+const DISLIKED_STATE = "DISLIKED_STATE";
+const NEUTRAL_STATE = "NEUTRAL_STATE";
+
+function isMobile() {
+  return location.hostname == "m.youtube.com";
+}
+
+function isVideoLiked() {
+  if (isMobile()) {
+    return (
+      getLikeButton().querySelector("button").getAttribute("aria-label") ==
+      "true"
+    );
+  }
+  return getLikeButton().classList.contains("style-default-active");
+}
+
+function isVideoDisliked() {
+  if (isMobile()) {
+    return (
+      getDislikeButton().querySelector("button").getAttribute("aria-label") ==
+      "true"
+    );
+  }
+  return getDislikeButton().classList.contains("style-default-active");
+}
+
+function getState(storedData) {
+  if (isVideoLiked()) {
+    return { current: LIKED_STATE, previous: storedData.previousState };
+  }
+  if (isVideoDisliked()) {
+    return { current: DISLIKED_STATE, previous: storedData.previousState };
+  }
+  return { current: NEUTRAL_STATE, previous: storedData.previousState };
+}
+
+//---   Sets The Likes And Dislikes Values   ---//
+function setLikes(likesCount) {
+  getButtons().children[0].querySelector("#text").innerText = likesCount;
+}
+
+function setDislikes(dislikesCount) {
+  if (isMobile()) {
+    getButtons().children[1].querySelector(".button-renderer-text").innerText =
+      dislikesCount;
+    return;
+  }
+  getButtons().children[1].querySelector("#text").innerText = dislikesCount;
+}
+
+function getLikeCountFromButton() {
+  let likesStr = getLikeButton()
+    .querySelector("button")
+    .getAttribute("aria-label")
+    .replace(/\D/g, "");
+  return likesStr.length > 0 ? parseInt(likesStr) : false;
+}
+
+function processResponse(response, storedData) {
+  const formattedDislike = numberFormat(response.dislikes);
+  setDislikes(formattedDislike);
+  storedData.dislikes = parseInt(response.dislikes);
+  storedData.likes = getLikeCountFromButton() || parseInt(response.likes);
+  createRateBar(storedData.likes, storedData.dislikes);
+}
+
+function setState(storedData) {
+  storedData.previousState = isVideoDisliked()
+    ? DISLIKED_STATE
+    : isVideoLiked()
+    ? LIKED_STATE
+    : NEUTRAL_STATE;
+  let statsSet = false;
+
+  getBrowser().runtime.sendMessage(
+    {
+      message: "set_state",
+      videoId: getVideoId(window.location.href),
+      state: getState(storedData).current,
+      likeCount: getLikeCountFromButton() || null,
+    },
+    function (response) {
+      cLog("response from api:");
+      cLog(JSON.stringify(response));
+      if (response !== undefined && !("traceId" in response) && !statsSet) {
+        processResponse(response, storedData);
+      } else {
+      }
+    }
+  );
+}
+
+function setInitialState(storedData) {
+  setState(storedData);
+  setTimeout(() => {
+    sendVideoIds();
+  }, 1500);
+}
+
+export {
+  isMobile,
+  isVideoDisliked,
+  isVideoLiked,
+  getState,
+  setState,
+  setInitialState,
+  setLikes,
+  setDislikes,
+  getLikeCountFromButton,
+  LIKED_STATE,
+  DISLIKED_STATE,
+  NEUTRAL_STATE,
+};

+ 70 - 0
Extensions/combined/src/utils.js

@@ -0,0 +1,70 @@
+function roundDown(num) {
+  if (num < 1000) return num;
+  const int = Math.floor(Math.log10(num) - 2);
+  const decimal = int + (int % 3 ? 1 : 0);
+  const value = Math.floor(num / 10 ** decimal);
+  return value * 10 ** decimal;
+}
+
+function numberFormat(numberState) {
+  let userLocales;
+  try {
+    userLocales = new URL(
+      Array.from(document.querySelectorAll("head > link[rel='search']"))
+        ?.find((n) => n?.getAttribute("href")?.includes("?locale="))
+        ?.getAttribute("href")
+    )?.searchParams?.get("locale");
+  } catch {}
+  const formatter = Intl.NumberFormat(
+    document.documentElement.lang || userLocales || navigator.language,
+    {
+      notation: "compact",
+    }
+  );
+
+  return formatter.format(roundDown(numberState));
+}
+
+function getBrowser() {
+  if (typeof chrome !== "undefined" && typeof chrome.runtime !== "undefined") {
+    return chrome;
+  } else if (
+    typeof browser !== "undefined" &&
+    typeof browser.runtime !== "undefined"
+  ) {
+    return browser;
+  } else {
+    console.log("browser is not supported");
+    return false;
+  }
+}
+
+function getVideoId(url) {
+  const urlObject = new URL(url);
+  const pathname = urlObject.pathname;
+  if (pathname.startsWith("/clip")) {
+    return document.querySelector("meta[itemprop='videoId']").content;
+  } else {
+    return urlObject.searchParams.get("v");
+  }
+}
+
+function isVideoLoaded() {
+  const videoId = getVideoId(window.location.href);
+  return (
+    document.querySelector(`ytd-watch-flexy[video-id='${videoId}']`) !== null ||
+    // mobile: no video-id attribute
+    document.querySelector('#player[loading="false"]:not([hidden])') !== null
+  );
+}
+
+function cLog(message, writer) {
+  message = `[return youtube dislike]: ${message}`;
+  if (writer) {
+    writer(message);
+  } else {
+    console.log(message);
+  }
+}
+
+export { numberFormat, getBrowser, getVideoId, isVideoLoaded, cLog }