// ==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 = `
${starredIcon}
Description ${descriptionLength}
`; } __name(setStarredStatus, "setStarredStatus"); function setPendingStatus(card) { statusWrapper(card, "pending").innerHTML = `
${pendingIcon}
`; } __name(setPendingStatus, "setPendingStatus"); // src/sorting/definition.ts async function _sort(container) { const nodes = Array.from(container.childNodes); const promises = nodes.map(async (card) => { if (isStarred(card)) return []; setPendingStatus(card); const info = await getCharacterInfo(card.href.split("/").pop()); clearStatus(card); if (info.description?.length > 0) { setStarredStatus(card, info.description.length); } else { container.append(card); } return [ card, info.description?.length ]; }); return Promise.all(promises); } __name(_sort, "_sort"); function sortByDefinitionLength(entries, container) { const sorted = entries.filter(([_, dl]) => dl).sort(([_c1, dl1], [_c2, dl2]) => dl1 > dl2 ? 1 : -1); for (const [c] of sorted) { container.insertBefore(c, container.firstChild); } } __name(sortByDefinitionLength, "sortByDefinitionLength"); async function sort(observer, container) { observer.disconnect(); const entries = await _sort(container); sortByDefinitionLength(entries, container); observer.observe(container, { attributes: false, childList: true, subtree: false }); } __name(sort, "sort"); // src/index.ts injectNavigationHook(async () => { console.log(unsafeWindow.location); if (unsafeWindow.location.pathname !== "/search") return; const cardsContainer = await waitNotNull(() => document.evaluate("/html/body/div[1]/div/main/div/div/div/main/div/div[2]", document).iterateNext()); const sortSearches = /* @__PURE__ */ __name((_, observer) => sort(observer, cardsContainer), "sortSearches"); sortSearches([], new MutationObserver(sortSearches)); }); })()