On JanitorAI: grabs the bot name and opens DataCat. On DataCat: automatically pastes the name into the search bar.
// ==UserScript==
// @name JanitorAI → DataCat FINAL
// @namespace janitorai-datacat
// @version 1.2
// @description On JanitorAI: grabs the bot name and opens DataCat. On DataCat: automatically pastes the name into the search bar.
// @author You
// @match *://janitorai.com/*
// @match *://www.janitorai.com/*
// @match *://*.janitorai.com/*
// @match *://datacat.run/*
// @match *://www.datacat.run/*
// @run-at document-start
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
const host = location.hostname.toLowerCase();
const isJanitor = host === 'janitorai.com' || host === 'www.janitorai.com' || host.endsWith('.janitorai.com');
const isDataCat = host === 'datacat.run' || host === 'www.datacat.run' || host.endsWith('.datacat.run');
console.log('[JanitorAI → DataCat] Host:', host, '| Janitor:', isJanitor, '| DataCat:', isDataCat);
/* =========================
PART 1 — JANITOR AI
========================= */
if (isJanitor) {
const BUTTON_ID = 'jcc-datacat-button';
let currentBotName = null;
function cleanBotName(name) {
if (!name) return null;
return name
.replace(/\s+/g, ' ')
.replace(/\s*\|\s*JanitorAI.*$/i, '')
.replace(/\s*-\s*JanitorAI.*$/i, '')
.trim();
}
function findBotName() {
const title = cleanBotName(document.title);
if (
title &&
title.length > 1 &&
!/^(janitorai|chat|home|similar characters)$/i.test(title)
) {
return title;
}
const headings = document.querySelectorAll('h1, h2, [role="heading"]');
for (const el of headings) {
const text = cleanBotName(el.textContent);
if (!text) continue;
if (/^(chat|chats|characters|home|settings|search|similar characters|discover characters)$/i.test(text)) continue;
if (text.length >= 3) return text;
}
return null;
}
function detectBotName() {
const name = findBotName();
if (name && name !== currentBotName) {
currentBotName = name;
console.log('[JanitorAI → DataCat] Bot found:', currentBotName);
updateButton();
}
return currentBotName;
}
function updateButton() {
const button = document.getElementById(BUTTON_ID);
if (!button) return;
if (currentBotName) {
button.textContent = 'DataCat';
button.style.background = '#16a34a';
button.title = 'Open DataCat with: ' + currentBotName;
} else {
button.textContent = 'DataCat: searching...';
button.style.background = '#6b7280';
button.title = 'Waiting for bot name...';
}
}
function createButton() {
if (document.getElementById(BUTTON_ID)) return;
if (!document.body) return;
const button = document.createElement('button');
button.id = BUTTON_ID;
button.type = 'button';
button.textContent = 'DataCat: searching...';
button.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
z-index: 2147483647;
padding: 10px 16px;
border: 0;
border-radius: 8px;
background: #6b7280;
color: white;
font-family: system-ui, -apple-system, sans-serif;
font-size: 13px;
font-weight: 700;
cursor: pointer;
box-shadow: 0 3px 12px rgba(0,0,0,.35);
`;
button.addEventListener('click', function () {
const botName = detectBotName();
if (!botName) {
alert('Could not find the bot name yet.\nWait for the chat to load and try again.');
return;
}
const url = 'https://datacat.run/characters/recent#q=' + encodeURIComponent(botName);
window.open(url, '_blank');
});
document.body.appendChild(button);
console.log('[JanitorAI → DataCat] Button created');
updateButton();
}
// SPA navigation
let lastUrl = location.href;
function checkUrlChange() {
if (location.href === lastUrl) return;
lastUrl = location.href;
currentBotName = null;
updateButton();
setTimeout(detectBotName, 300);
setTimeout(detectBotName, 1000);
setTimeout(detectBotName, 2500);
}
const originalPushState = history.pushState;
history.pushState = function () {
const result = originalPushState.apply(this, arguments);
checkUrlChange();
return result;
};
const originalReplaceState = history.replaceState;
history.replaceState = function () {
const result = originalReplaceState.apply(this, arguments);
checkUrlChange();
return result;
};
window.addEventListener('popstate', checkUrlChange);
const observer = new MutationObserver(function () {
detectBotName();
createButton();
});
function startJanitor() {
if (!document.body) {
setTimeout(startJanitor, 50);
return;
}
createButton();
observer.observe(document.documentElement, { childList: true, subtree: true });
setTimeout(detectBotName, 100);
setTimeout(detectBotName, 500);
setTimeout(detectBotName, 1000);
setTimeout(detectBotName, 2000);
setTimeout(detectBotName, 4000);
}
startJanitor();
}
/* =========================
PART 2 — DATACAT
========================= */
if (isDataCat) {
function getQueryFromHash() {
const hash = location.hash || '';
const match = hash.match(/[#&]q=([^&]+)/);
if (!match) return null;
try {
return decodeURIComponent(match[1].replace(/\+/g, ' '));
} catch (e) {
return match[1];
}
}
function findSearchInput() {
const inputs = Array.from(document.querySelectorAll('input'));
const candidates = inputs.filter(function (el) {
const ph = (el.placeholder || '').toLowerCase();
const type = (el.type || '').toLowerCase();
return type === 'search' || type === 'text' ||
ph.indexOf('search') !== -1 ||
ph.indexOf('filter') !== -1 ||
ph.indexOf('character') !== -1;
});
return candidates.find(function (el) {
const ph = (el.placeholder || '').toLowerCase();
return ph.indexOf('search') !== -1 || ph.indexOf('character') !== -1;
}) || candidates[0] || null;
}
function setReactValue(input, value) {
const descriptor = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value');
if (descriptor && descriptor.set) {
descriptor.set.call(input, value);
} else {
input.value = value;
}
input.dispatchEvent(new Event('input', { bubbles: true }));
input.dispatchEvent(new Event('change', { bubbles: true }));
}
function tryPaste() {
const query = getQueryFromHash();
if (!query) return true;
const input = findSearchInput();
if (!input) return false;
input.focus();
setReactValue(input, query);
['keydown', 'keypress', 'keyup'].forEach(function (type) {
input.dispatchEvent(new KeyboardEvent(type, {
key: 'Enter',
code: 'Enter',
keyCode: 13,
which: 13,
bubbles: true
}));
});
history.replaceState(null, '', location.pathname + location.search);
console.log('[JanitorAI → DataCat] Pasted into DataCat:', query);
return true;
}
let attempts = 0;
const interval = setInterval(function () {
attempts++;
if (tryPaste() || attempts > 30) {
clearInterval(interval);
}
}, 400);
}
})();