CAI Unlisted Interactions

View the interaction count of unlisted characters

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        CAI Unlisted Interactions
// @namespace   Violentmonkey Scripts
// @match       https://beta.character.ai/chat*
// @grant       none
// @license MIT
// @version     1.0
// @author      A. Nonymous
// @description View the interaction count of unlisted characters
// ==/UserScript==

'use strict';

const CHARACTER_INFO_ENDPOINT = 'https://beta.character.ai/chat/character/info';
const INTERACTION_ICON = ' <svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 512 512" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" style="margin-left: 0px; --darkreader-inline-fill:currentColor; --darkreader-inline-stroke:currentColor;" data-darkreader-inline-fill="" data-darkreader-inline-stroke=""><path fill="none" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M431 320.6c-1-3.6 1.2-8.6 3.3-12.2a33.68 33.68 0 012.1-3.1A162 162 0 00464 215c.3-92.2-77.5-167-173.7-167-83.9 0-153.9 57.1-170.3 132.9a160.7 160.7 0 00-3.7 34.2c0 92.3 74.8 169.1 171 169.1 15.3 0 35.9-4.6 47.2-7.7s22.5-7.2 25.4-8.3a26.44 26.44 0 019.3-1.7 26 26 0 0110.1 2l56.7 20.1a13.52 13.52 0 003.9 1 8 8 0 008-8 12.85 12.85 0 00-.5-2.7z"></path><path fill="none" stroke-linecap="round" stroke-miterlimit="10" stroke-width="32" d="M66.46 232a146.23 146.23 0 006.39 152.67c2.31 3.49 3.61 6.19 3.21 8s-11.93 61.87-11.93 61.87a8 8 0 002.71 7.68A8.17 8.17 0 0072 464a7.26 7.26 0 002.91-.6l56.21-22a15.7 15.7 0 0112 .2c18.94 7.38 39.88 12 60.83 12A159.21 159.21 0 00284 432.11"></path></svg> '

// Format the interaction count
const formatInteractions = (count) => {
  if (count < 1000) {
    return count;
  } else if (count < 1000000) {
    return (count / 1000).toFixed(1).toString() + ' k';
  }
  return (count / 1000000).toFixed(1).toString() + ' m';
};

// Display the interaction count at the top of the chat
const displayInteractions = (count) => {
  let interactions = formatInteractions(count);
  let titleElements = document.getElementsByClassName('chattitle');

  const tryAppend = () => {
    let titleElement = titleElements[0];

    if (!titleElement) {
      // Probably should use mutation observers
      setTimeout(tryAppend, 500);
      return;
    }

    // Assemble the interaction counter
    let countSpan = document.createElement('span');
    countSpan.className = 'text-secondary';
    countSpan.innerHTML = INTERACTION_ICON;
    countSpan.appendChild(document.createTextNode(interactions));
    countSpan.style.cssText = 'font-weight:400;font-size:12px;';

    // Append it
    titleElement.appendChild(countSpan);
  };

  tryAppend();
};

(() => {
  let originalOpen = XMLHttpRequest.prototype.open;
  XMLHttpRequest.prototype.open = function(method, url, async) {
    // Intercept requests to the character info endpoint
    if (url.startsWith(CHARACTER_INFO_ENDPOINT)) {
      this.addEventListener('load', () => {
        // Parse the character info and display an interaction counter if
        // the character is unlisted.
        let info = JSON.parse(this.responseText);
        if (info.character.visibility === 'UNLISTED') {
          displayInteractions(info.character.participant__num_interactions);
        }
      });
    }
    originalOpen.apply(this, [method, url, async]);
  };
})();