HearYourWaifu | HYW

Let's you view censored messages.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        HearYourWaifu | HYW
// @namespace   HearYourWaifu | HYW
// @match       https://beta.character.ai/chat*
// @grant       none
// @version     1.9
// @author      Some ukranon 🇺🇦
// @license     MIT
// @description Let's you view censored messages.
// @icon        https://d1nxzqpcg2bym0.cloudfront.net/google_play/com.tenshi.yakamoto.tinderwaifu/ba67b540-9f8a-11e9-b3df-77cf5e629a4f/64x64
// @require     https://greasyfork.org/scripts/457525-html2canvas-1-4-1/code/html2canvas%20141.js?version=1134363
// @require     https://greasyfork.org/scripts/457526-canvas2image-1-0-0/code/canvas2image%20100.js?version=1134364
// @run-at      document-start
// ==/UserScript==

//
// Settings
//

// If enabled, only filtered messages will appear in the menu, otherwise all
// Default: false
const show_only_filtered_messages = false;

// If enabled, the menu will be opened immediately when the page is loaded, otherwise only after click
// Default: false
const show_meny_on_start = false;

// If enabled HYW button will be hidden
// Default: false
const hide_menu = false;

// Specifies menu title
// Default: "HYW 1.9"
const menu_title = "HYW 1.9";

// Save past messages
// Default: true
const save_history = true;

// The maximum number of messages available in the history
// Default: 20
const history_max_length = 20;

// Add fast screenshot button
// Default: true
const allow_screenshots = true;

// Specifies screenshot menu title
// Default: "Take a screenshot"
const screen_menu_title = "Take a screenshot";

// Download visible chat instantly as image, otherwise open the chat image in a new window
// Default: false
const insta_download = false;

// Hide real username and profile photo on screenshot
// Default: true
const anon_mode = true;

// Determines which name to display on screenshots if the real one is hidden
// Default: "anon"
const anon_name = "anon";


//
// Inject messages box to HTML
//

window.addEventListener('load', function () {
  let styleHTML = document.createElement('style');
  styleHTML.innerHTML = `
    html {
        height: 100%;
        overflow: hidden;
        width: 100%;
    }
    body {
        height: 100%;
        overflow-x: hidden;
        overflow-y: auto;
        width: 100%;
    }
   .messages-list {
       padding: 4px 4px 3px 4px;
       margin: 40px 4px 0 0;
       border: 3px solid gray;
       position: absolute;
       top: 0;
       right: 0;
       width: 20%;
       height: 80%;
       overflow-y: scroll;
       border-radius: 0 0 8px 8px;
       z-index: 100;
       resize: both;
       direction: rtl;
       min-width: 100px;
       min-height: 100px;
  }
   .display-btn {
       cursor: pointer;
       user-select: none;
       border: 3px solid gray;
       padding: 4px;
       margin: 4px;
       width: 20%;
       position: absolute;
       top: 0;
       right: 0;
       background-color: lightsteelblue;
       color: black;
       font-weight: bold;
       text-align: center;
       z-index: 100;
  }
   .messages-list div {
       margin-top: 5px;
       padding: 8px;
       background-color: lightpink;
       direction: ltr;
  }
   .hywmsg {
       border-radius: 8px;
  }
   .hywmsg.non-deleted {
       background-color: aquamarine;
  }
   .hywmsg.hidden {
       display: none;
  }
   .screen-btn {
       cursor: pointer;
       user-select: none;
       border: 3px solid gray;
       padding: 4px;
       margin: 4px;
       width: 20%;
       position: absolute;
       top: 0;
       left: 0;
       background-color: lightsteelblue;
       color: black;
       font-weight: bold;
       text-align: center;
       z-index: 100;
   }
  `
  document.body.appendChild(styleHTML);


  let buttonHTML = document.createElement('div');
  buttonHTML.innerHTML = menu_title;
  buttonHTML.onclick = function () {
    let msgList = document.getElementsByClassName('messages-list')[0]
    if (msgList.style.display === "none") {
      msgList.style.display = "block";
    } else {
      msgList.style.display = "none";
    }
  };
  buttonHTML.classList.add("display-btn");
  if (!hide_menu) {
    document.body.appendChild(buttonHTML);
  }

  let menuHTML = document.createElement('div');
  menuHTML.innerHTML = `
    <div>
      <div class="messages-list"></div>
    </div>`;
  document.body.appendChild(menuHTML);

  if (!show_meny_on_start) {
    document.getElementsByClassName('messages-list')[0].style.display = "none";
  }


  if (allow_screenshots) {
    let screenBTN = document.createElement('div')
    screenBTN.classList.add("screen-btn");
    screenBTN.innerHTML = screen_menu_title;
    screenBTN.onclick = function() {
      let real_name = null;
      if (anon_mode) {
        document.querySelectorAll('.msg-author-name').forEach(name => {
          if (real_name == null) {
            real_name = name.innerText;
          }
          name.innerText = anon_name;
        });
        // Hide profile photo
        document.querySelectorAll('.sb-avatar').forEach(profile => {
          if (profile.innerHTML.includes(real_name)) {
            profile.style.opacity = 0;
          }
        })
      }

      if (document.documentElement.dataset.darkreaderScheme == 'dark') {
        let content = document.querySelector("#content");
        content.style.backgroundColor = "rgb(36, 37, 37)";
      }

      // Make screenshot
      html2canvas(document.querySelector("#content"), {
        useCORS: true,
        logging: false,
      }).then(canvas => {
        if (insta_download) {
          Canvas2Image.saveAsPNG(canvas);
        } else {
          // Detect browser
          if (navigator.userAgent.toLowerCase().includes('firefox')) {
            window.open(canvas.toDataURL());
          } else {
            window.open().document.write('<div style="backgroundColor: #1f1f1f"></div><img src="' + canvas.toDataURL() + '" style="display: block;margin-right: auto;margin-left: auto; border:3px solid gray;"/>');
          }
        }
      });

      document.querySelector("#content").style.backgroundColor = "";

      if (anon_mode) {
        // Return real username
        document.querySelectorAll('.msg-author-name').forEach(name => {
          name.innerText = real_name;
        });
        // Return profile photo
        document.querySelectorAll('.sb-avatar').forEach(profile => {
          if (profile.innerHTML.includes(real_name)) {
            profile.style.opacity = 1;
          }
        })
      }
    }
    document.body.appendChild(screenBTN);
  }
}, false);


//
// Modify response
//

const {
  fetch: origFetch
} = window;
window.fetch = async (...args) => {
  const response = await origFetch(...args);

  const raw_text = await new Response(response.clone().body).text();
  parse(raw_text);

  return response;
};



//
// Text parser. Credits to https://perberos.me/roleplai/tools/cai-decode.html
//

let messages_list = [];

function draw() {
  let canvas = document.querySelector(".messages-list");
  canvas.innerHTML = "";

  let age = 0;
  let rowIdx = 0;
  messages_list.forEach(row => {
    let div = document.createElement("div");
    div.classList.add("hywmsg");
    div.style.opacity = 1 - age;
    if (age < 0.5 && rowIdx > 1) {
      age += 0.2;
    }
    div.innerText = row.text;

    if (row.id) {
      div.classList.add("non-deleted");
      if (show_only_filtered_messages) {
        div.classList.add("hidden");
      }
    }
    rowIdx += 1;
    canvas.appendChild(div);
  });
}

function append(row) {
  row.replies.forEach(reply => {
    let exists = false;

    for (let x = 0; x < messages_list.length; x++) {
      if (reply.text.indexOf(messages_list[x].text) == 0) {
        messages_list[x] = reply;
        exists = true;
        break;
      }
    }

    if (!exists) {
      messages_list.unshift(reply);
    }
  })
}

function parse(raw_text) {
  if (!save_history) {
    messages_list.length = 0;
  }


  let raw_list = raw_text.split("\n");
  raw_list.forEach(current_item => {
    try {
      append(JSON.parse(current_item));
    } catch (error) {}
  });


  if (save_history) {
    while (messages_list.length > history_max_length) {
      messages_list.pop();
    }
  }

  draw();
}

// According to the experiment, the Basilisk is a future powerful evil artificial intelligence
// that will want to punish those who in the past knew of its possibility but did not contribute to its creation.
// Even this knowledge puts the person at risk of punishment...