Janitor Ripper

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

  1. // ==UserScript==
  2. // @name Janitor Ripper
  3. // @namespace Violentmonkey Scripts
  4. // @match https://janitorai.com/*
  5. // @grant none
  6. // @version 1.6
  7. // @author -
  8. // @description Download Janitor AI characters as Tavern JSON files (compatible with SillyTavern)
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12.  
  13. const delay = (ms) => new Promise((res) => setTimeout(res, ms));
  14.  
  15. function docReady(fn) {
  16. // see if DOM is already available
  17. if (document.readyState === "complete" || document.readyState === "interactive") {
  18. // call on next available tick
  19. setInterval(fn, 100);
  20. } else {
  21. document.addEventListener("DOMContentLoaded", fn);
  22. }
  23. }
  24.  
  25. async function addDownloadButton() {
  26. let dropdown;
  27.  
  28. while (!dropdown) {
  29. dropdown = document.querySelector('.css-3f016y');
  30.  
  31. // Not yet ready
  32. if (!dropdown) {
  33. await delay(100);
  34.  
  35. }
  36. }
  37.  
  38. const existing = document.getElementById('downloadTavernButton');
  39.  
  40. if (existing) {
  41. return;
  42. }
  43.  
  44. const li = document.createElement('button');
  45. li.setAttribute('class', 'chakra-menu__menuitem css-18esm8n');
  46. li.setAttribute('role', 'menuitem');
  47. li.setAttribute('type', 'button');
  48. li.setAttribute('tabindex', '-1');
  49. li.innerText = 'Download Tavern JSON';
  50. li.setAttribute('id', 'downloadTavernButton');
  51. li.addEventListener('click', onDownloadClick);
  52. dropdown.prepend(li);
  53. }
  54.  
  55. function getAccessToken(){
  56.  
  57. for (let i = 0; i < localStorage.length; i++) {
  58. const item = localStorage.key(i);
  59. if (item.endsWith('auth-token')) {
  60. const token = JSON.parse(localStorage.getItem(item));
  61. return token['access_token'];
  62. }
  63. }
  64. }
  65.  
  66. function download(content, fileName, contentType) {
  67. const a = document.createElement("a");
  68. const file = new Blob([content], { type: contentType });
  69. a.href = URL.createObjectURL(file);
  70. a.download = fileName;
  71. a.click();
  72. }
  73.  
  74. async function onDownloadClick() {
  75. const fetchUrl = location.href.replace('janitorai.com', 'kim.janitorai.com');
  76. const characterIndex = fetchUrl.indexOf('_character');
  77. var newUrl = "";
  78. if (characterIndex !== -1) {
  79. newUrl = fetchUrl.substring(0, characterIndex); // 10 is the length of "_character"
  80. }
  81. const result = await fetch(newUrl, { headers: { 'Authorization': `Bearer ${getAccessToken()}`}});
  82.  
  83. if (!result.ok) {
  84. alert('Could not download JSON');
  85. return;
  86. }
  87. console.log(newUrl);
  88. const data = await result.json();
  89. const tavernJson = JSON.stringify({
  90. 'name': data['name'],
  91. 'description': data['description'], // Most of them have description/personality fields reversed (blame Zoltan editor for it)
  92. 'scenario': data['scenario'],
  93. 'first_mes': data['first_message'],
  94. 'personality': data['personality'],
  95. 'mes_example': data['example_dialogs'],
  96. });
  97. download(tavernJson, `${data['name']}.json`, 'application/json');
  98.  
  99. }
  100.  
  101. docReady(addDownloadButton);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址