Sleazy Fork is available in English.

HearYourWaifu | HYW

Let's you view censored messages.

// ==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...