Niconico - Better Nicoru

Improve issues such as Niconico's Nicoru only showing up to 9+.

// ==UserScript==
// @name            Niconico - Better Nicoru
// @name:ja         ニコニコ - ニコる表示の改善
// @description     Improve issues such as Niconico's Nicoru only showing up to 9+.
// @description:ja  ニコニコのニコるが9+までしか表示されないなどの問題を改善します。
// @version         1.0.1
// @match           https://www.nicovideo.jp/watch/sm*
// @namespace       https://github.com/sqrtox/userscript-niconico-better-nicoru
// @author          sqrtox
// @license         MIT
// @grant           none
// ==/UserScript==

// NOTE:
//   This file was built from https://github.com/sqrtox/userscript-niconico-better-nicoru
//
//   このスクリプトはこれらのスクリプトにインスパイアされました。先駆者の方々に感謝と敬意を表します。
//   This script was inspired by the following scripts. I express my gratitude and respect to the pioneers.
//
//      - https://github.com/takusan23/NicoruCountFix
//      - https://greasyfork.org/ja/scripts/482598

(async () => {
  "use strict";

  // src/color.ts
  var gradient = (r1, g1, b1, r2, g2, b2, d) => [
    (r2 - r1) * d + r1,
    (g2 - g1) * d + g1,
    (b2 - b1) * d + b1,
  ];

  // src/nicoru.ts
  var NICORU_BUTTON_ARIA_LABEL = "ニコるボタン";
  var LOWEST_NICORU_COLOR = [84, 73, 28];
  var NICORU_COLOR_DEFINES = [
    // mythology
    {
      start: 100,
      end: 200,
      color: [51, 153, 255],
    },
    // legendary
    {
      start: 50,
      end: 100,
      color: [136, 72, 152],
    },
    // top
    {
      start: 10,
      end: 50,
      color: [255, 0, 0],
    },
    // limit
    {
      start: 0,
      end: 10,
      color: [252, 216, 66],
    },
  ].sort((a, b) => b.start - a.start);
  var getDistance = (n, start, end) =>
    Math.min(Math.max(0, (n - start) / (end - start)), 1);
  var getNicoruColor = (n) => {
    for (const [i, { start, end, color }] of NICORU_COLOR_DEFINES.entries()) {
      const previousLevelColor =
        NICORU_COLOR_DEFINES[i + 1]?.color ?? LOWEST_NICORU_COLOR;
      if (n > start) {
        const distance = getDistance(n, start, end);
        return gradient(...previousLevelColor, ...color, distance);
      }
    }
  };

  // src/observer.ts
  var observeAddedHtmlElements = (target, callback) => {
    let observer = new MutationObserver((records) => {
      for (const record of records) {
        if (record.type !== "childList") {
          continue;
        }
        for (const node of record.addedNodes) {
          if (!(node instanceof HTMLElement)) {
            continue;
          }
          callback(node);
        }
      }
    });
    observer.observe(target, {
      childList: true,
      subtree: true,
    });
    return () => {
      if (observer) {
        observer.disconnect();
        observer = void 0;
      }
    };
  };

  // src/react.ts
  var getReactProps = (o) => {
    const reactPropsKey = Object.keys(o).filter((key) =>
      key.startsWith("__reactProps$"),
    )[0];
    if (!reactPropsKey) {
      return;
    }
    return o[reactPropsKey];
  };

  // src/comment.ts
  var COMMENT_LIST_ELEMENT_MARKER = "コメントリスト";
  var awaitCommentList = () => {
    const { promise, resolve } = Promise.withResolvers();
    const disconnect = observeAddedHtmlElements(document, (element) => {
      const commentHeader = [...element.getElementsByTagName("header")].filter(
        (element2) =>
          element2.textContent?.includes(COMMENT_LIST_ELEMENT_MARKER),
      )[0];
      if (
        !commentHeader ||
        !(commentHeader.nextElementSibling instanceof HTMLElement)
      ) {
        return;
      }
      resolve(commentHeader.nextElementSibling);
      disconnect();
    });
    return promise;
  };
  var fixNicoruCount = (commentElement, nicoruCount) => {
    const countElement = commentElement.querySelector(
      `button[aria-label="${NICORU_BUTTON_ARIA_LABEL}"] > p`,
    );
    if (!countElement) {
      return;
    }
    countElement.textContent = nicoruCount.toString();
  };
  var fixCommentContentStyle = (commentElement) => {
    const contentElement = commentElement.querySelector(
      `button[aria-label="${NICORU_BUTTON_ARIA_LABEL}"] + p`,
    );
    if (contentElement instanceof HTMLElement) {
      contentElement.style.flex = "1";
    }
  };
  var fixCommentBackgroundColor = (commentElement, nicoruCount) => {
    const color = getNicoruColor(nicoruCount);
    if (!color) {
      return;
    }
    const commentInnerElement = commentElement.children[0];
    if (!(commentInnerElement instanceof HTMLElement)) {
      return;
    }
    commentInnerElement.style.backgroundColor = `rgba(${color[0]}, ${color[1]}, ${color[2]}, 0.66)`;
  };
  var processComment = (commentElement) => {
    fixCommentContentStyle(commentElement);
    const count = commentElement.querySelector("button p");
    if (!count) {
      return;
    }
    const props = getReactProps(commentElement);
    if (!props) {
      return;
    }
    const comment = props.children.props.comment;
    const nicoruCount = comment.nicoruCount;
    fixNicoruCount(commentElement, nicoruCount);
    fixCommentBackgroundColor(commentElement, nicoruCount);
  };
  var processCommentPopper = (popperElement) => {
    const commentElement = popperElement.querySelector(
      `div:has(> div > button[aria-label="${NICORU_BUTTON_ARIA_LABEL}"])`,
    );
    if (!(commentElement instanceof HTMLElement)) {
      return;
    }
    fixCommentContentStyle(commentElement);
    const props = getReactProps(popperElement);
    if (!props) {
      return;
    }
    const comment = props.children.props.comment;
    fixNicoruCount(commentElement, comment.nicoruCount);
  };

  // src/index.ts
  var main = async () => {
    const commentList = await awaitCommentList();
    observeAddedHtmlElements(commentList, (element) => {
      if ("index" in element.dataset) {
        processComment(element);
      } else if (element.classList.contains("z_forward")) {
        processCommentPopper(element);
      }
    });
  };
  main();
})();