Janitor Ripper

Download Janitor AI characters as Tavern JSON files (compatible with SillyTavern)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name        Janitor Ripper
// @namespace   Violentmonkey Scripts
// @match       https://janitorai.com/*
// @grant       none
// @version     1.6
// @author      -
// @description Download Janitor AI characters as Tavern JSON files (compatible with SillyTavern)
// @license MIT
// ==/UserScript==


const delay = (ms) => new Promise((res) => setTimeout(res, ms));

function docReady(fn) {
    // see if DOM is already available
    if (document.readyState === "complete" || document.readyState === "interactive") {
        // call on next available tick
        setInterval(fn, 100);
    } else {
        document.addEventListener("DOMContentLoaded", fn);
    }
}

async function addDownloadButton() {
  let dropdown;

  while (!dropdown) {
  dropdown = document.querySelector('.css-3f016y');

  // Not yet ready
    if (!dropdown) {
    await delay(100);

    }
  }

  const existing = document.getElementById('downloadTavernButton');

  if (existing) {
    return;
  }

  const li = document.createElement('button');
  li.setAttribute('class', 'chakra-menu__menuitem css-18esm8n');
  li.setAttribute('role', 'menuitem');
  li.setAttribute('type', 'button');
  li.setAttribute('tabindex', '-1');
  li.innerText = 'Download Tavern JSON';
  li.setAttribute('id', 'downloadTavernButton');
  li.addEventListener('click', onDownloadClick);
  dropdown.prepend(li);
}

function getAccessToken(){

  for (let i = 0; i < localStorage.length; i++) {
    const item = localStorage.key(i);
    if (item.endsWith('auth-token')) {
      const token = JSON.parse(localStorage.getItem(item));
      return token['access_token'];
    }
  }
}

function download(content, fileName, contentType) {
    const a = document.createElement("a");
    const file = new Blob([content], { type: contentType });
    a.href = URL.createObjectURL(file);
    a.download = fileName;
    a.click();
}

async function onDownloadClick() {
  const fetchUrl = location.href.replace('janitorai.com', 'kim.janitorai.com');
  const characterIndex = fetchUrl.indexOf('_character');
  var newUrl = "";
  if (characterIndex !== -1) {
    newUrl = fetchUrl.substring(0, characterIndex); // 10 is the length of "_character"
  }
  const result = await fetch(newUrl, { headers: { 'Authorization': `Bearer ${getAccessToken()}`}});

  if (!result.ok) {
    alert('Could not download JSON');
    return;
  }
  console.log(newUrl);
  const data = await result.json();
  const tavernJson = JSON.stringify({
      'name': data['name'],
      'description': data['description'], // Most of them have description/personality fields reversed (blame Zoltan editor for it)
      'scenario': data['scenario'],
      'first_mes': data['first_message'],
      'personality': data['personality'],
      'mes_example': data['example_dialogs'],
  });
  download(tavernJson, `${data['name']}.json`, 'application/json');

}

docReady(addDownloadButton);