您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Sort search so cards with public definition stays on top and marked with a star
// ==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 // ==/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 = '<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="#75FB4C"><path d="M371.01-324 480-390.22 589-324l-29-124 97-84-127-11-50-117-50 117-127 11 96.89 83.95L371.01-324ZM480-72 360-192H192v-168L72-480l120-120v-168h168l120-120 120 120h168v168l120 120-120 120v168H600L480-72Zm0-102 90-90h126v-126l90-90-90-90v-126H570l-90-90-90 90H264v126l-90 90 90 90v126h126l90 90Zm0-306Z"/></svg>'; var pendingIcon = '<svg xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px" fill="#5985E1"><path d="M288-420q25 0 42.5-17.5T348-480q0-25-17.5-42.5T288-540q-25 0-42.5 17.5T228-480q0 25 17.5 42.5T288-420Zm192 0q25 0 42.5-17.5T540-480q0-25-17.5-42.5T480-540q-25 0-42.5 17.5T420-480q0 25 17.5 42.5T480-420Zm192 0q25 0 42.5-17.5T732-480q0-25-17.5-42.5T672-540q-25 0-42.5 17.5T612-480q0 25 17.5 42.5T672-420ZM480.28-96Q401-96 331-126t-122.5-82.5Q156-261 126-330.96t-30-149.5Q96-560 126-629.5q30-69.5 82.5-122T330.96-834q69.96-30 149.5-30t149.04 30q69.5 30 122 82.5T834-629.28q30 69.73 30 149Q864-401 834-331t-82.5 122.5Q699-156 629.28-126q-69.73 30-149 30Zm-.28-72q130 0 221-91t91-221q0-130-91-221t-221-91q-130 0-221 91t-91 221q0 130 91 221t221 91Zm0-312Z"/></svg>'; // 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 = ` <div class="flex grow-0 shrink-0 justify-center"> ${starredIcon} </div> <div class="flex flex-row gap-1 tooltip-text"> <span class="tooltip-even">Description</span> <span class="tooltip-even tooltip-number">${descriptionLength}</span> </div> `; } __name(setStarredStatus, "setStarredStatus"); function setPendingStatus(card) { statusWrapper(card, "pending").innerHTML = ` <div class="flex flex-row grow-0 shrink-0 w-full items-center justify-center"> ${pendingIcon} </div> `; } __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)); }); })()