// ==UserScript== // @name c.AI Search Sort // @author EnergoStalin // @description Sort search so cards with public definition stays on top and marked with a star // @license AGPL-3.0-only // @version 1.1.1 // @namespace https://c.ai // @match https://character.ai/* // @run-at document-body // @icon https://www.google.com/s2/favicons?sz=64&domain=character.ai // @grant GM.addStyle // @downloadURL none // ==/UserScript== (async () => { var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); // src/util.ts async function waitNotNull(func, timeout = 1e4, interval = 1e3) { return new Promise((res, rej) => { let time = timeout; const i = setInterval(async () => { const c = await func(); time -= interval; if (time <= 0) { clearInterval(i); rej(); } if (!c) return; clearInterval(i); res(c); }, interval); }); } __name(waitNotNull, "waitNotNull"); function injectNavigationHook(callback) { let old = unsafeWindow.location.href; new MutationObserver(() => { if (old === unsafeWindow.location.href) return; old = unsafeWindow.location.href; callback(old); }).observe(unsafeWindow.document.body, { subtree: true, childList: true }); callback(old); } __name(injectNavigationHook, "injectNavigationHook"); // src/api.ts var pageProps = await waitNotNull(() => document.querySelector("#__NEXT_DATA__")?.textContent).then((e) => JSON.parse(e).props.pageProps); var token = pageProps.token; async function getCharacterInfo(id) { return await fetch(`https://plus.character.ai/chat/character/info/`, { headers: { Authorization: `Token ${token}`, Origin: "https://character.ai/", Referer: "https://character.ai/", "Content-Type": "application/json", Accept: "application/json" }, method: "POST", body: JSON.stringify({ external_id: id }) }).then((e) => e.json()).then((e) => e.character); } __name(getCharacterInfo, "getCharacterInfo"); // src/styles/tooltip.css GM.addStyle(` .tooltip { position: relative; cursor: pointer; } .tooltip .tooltip-text { visibility: hidden; text-align: left; z-index: 1; opacity: 0; transition: opacity 0.3s; font-size: 0.7em; color: #a6a6a6; text-wrap: nowrap; } .tooltip .tooltip-head { text-align: center; font-size: 1.3em; color: #a6a6a6; } .tooltip .tooltip-even { flex-basis: 50%; } .tooltip .tooltip-number { color: #b0a676; text-align: right; } .tooltip:hover .tooltip-text { visibility: visible; opacity: 1; } `); // src/styles/bootstrap.css GM.addStyle(` .align-items-start { align-items: flex-start; } `); // src/icons.ts var starredIcon = ''; var pendingIcon = ''; // src/statuses.ts function clearStatus(card) { card.querySelector("div[data-status]")?.remove(); } __name(clearStatus, "clearStatus"); function statusWrapper(card, status) { const d = document.createElement("div"); d.dataset.status = status; d.classList.add("flex", "flex-col", "tooltip"); card.classList.add("align-items-start"); card.append(d); return d; } __name(statusWrapper, "statusWrapper"); function isStarred(card) { return Boolean(card.querySelector('div[data-status="starred"]')); } __name(isStarred, "isStarred"); function setStarredStatus(card, descriptionLength) { statusWrapper(card, "starred").innerHTML = `