c.AI Search Sort

Sort search so cards with public definition stays on top and marked with a star

当前为 2024-10-05 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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()
})();