// ==UserScript== // @name c.AI Search Sort // @namespace http://tampermonkey.net/ // @version 1.0.0 // @description Sort search so cards with public definition stays on top and marked with a star // @author EnergoStalin // @license GPL-3.0-or-later // @match https://character.ai/search* // @icon https://www.google.com/s2/favicons?sz=64&domain=character.ai // @grant none // @downloadURL none // ==/UserScript== (async function() { 'use strict'; async function waitNotNull(func, timeout = 10000, interval = 1000) { 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) }) } const [pageProps, cardsContainer] = await Promise.all([ waitNotNull(() => document.querySelector('#__NEXT_DATA__')).then(e => JSON.parse(e.textContent).props.pageProps), waitNotNull(() => document.evaluate('/html/body/div[1]/div/main/div/div/div/main/div/div[2]', document).iterateNext()) ]); const token = pageProps.token async function isDefinitionPublic(id) { const character = 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) return !!character.definition } function clearStatus(card) { card.removeChild(card.querySelector('div[data-status]')) } function isStarred(card) { return !!card.querySelector('div[data-status="starred"]') } function setStarredStatus(card) { card.innerHTML += `
` } function setPendingStatus(card) { card.innerHTML += `
` } const cardsObserver = new MutationObserver(sortSearches) function sortSearches() { cardsObserver.disconnect() const nodes = Array.from(cardsContainer.childNodes) Promise.all(nodes.map(async card => { if(isStarred(card)) return setPendingStatus(card) const isPublic = await isDefinitionPublic(card.href.split('/').pop()) clearStatus(card) if(isPublic) { setStarredStatus(card) } else { cardsContainer.appendChild(card) } })).then(() => cardsObserver.observe(cardsContainer, { attributes: false, childList: true, subtree: false })) } sortSearches() })();