Sort search so cards with public definition stays on top and marked with a star
当前为
// ==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
// ==/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 += `
<div data-status="starred" class="relative" style="min-height: 90px;">
<svg style="margin-top: 10px; margin-right: 10px;" 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>
</div>
`
}
function setPendingStatus(card) {
card.innerHTML += `
<div data-status="pending" class="relative" style="min-height: 90px;">
<svg style="margin-top: 10px; margin-right: 10px;" 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>
</div>
`
}
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()
})();