Chat Enhancements

Adds a save and download button with a format dropdown to Character.AI, hides edited tags, auto-clicks "Try Again," and enables widescreen mode.

当前为 2024-09-27 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Chat Enhancements
// @namespace    http://tampermonkey.net/
// @version      0.4
// @description  Adds a save and download button with a format dropdown to Character.AI, hides edited tags, auto-clicks "Try Again," and enables widescreen mode.
// @author       InariOkami
// @match        https://character.ai/*
// @grant        none
// @icon         https://www.google.com/s2/favicons?sz=64&domain=character.ai
// ==/UserScript==

(async function() {
    'use strict';

    (function() {
        function loop() {
            let elements = document.querySelectorAll("div, p");
            elements.forEach(function(val) {
                if (val.innerText === "(edited)" || val.innerText === "Edited") {
                    val.style.display = 'none';
                }
            });
            requestAnimationFrame(loop);
        }
        loop();
    })();

    (function() {
        let counter = 0;
        var interval = setInterval(function() {
            var toastify = document.querySelectorAll('[role="alert"]');
            if (toastify.length > 0) {
                for (var i = 0; i < toastify.length; i++) {
                    if (toastify[i].innerHTML.indexOf("Try Again") !== -1) {
                        toastify[i].querySelector(".btn-primary").click();
                        counter++;
                        console.log('Auto-clicked %s times', counter);
                        break;
                    }
                }
            }
        }, 50);
    })();

    (function() {
        function WideScreen() {
            if (document.URL.startsWith("https://old.character.ai/chat")) {
                if (document.URL.includes("/chat2") || document.URL.includes("/chat")) {
                    document.body.getElementsByClassName("apppage").item(0).firstElementChild.attributes.style.value = "height: 100%; display: flex; flex-direction: column; overflow-y: hidden; min-width: 300px; max-width: 7680; margin: 0px auto;";
                    document.getElementsByClassName("container-fluid chatbottom").item(0).attributes.item(1).value = "max-width: 7680;";
                }
                if (document.URL.includes("/chat")) {
                    document.getElementsByClassName("container-fluid chattop").item(0).attributes.item(1).value = "max-width: 7680;";
                }
            }
            if (document.URL.startsWith("https://character.ai/chat")) {
                var Chat = document.getElementsByClassName("overflow-x-hidden overflow-y-scroll px-1 flex flex-col-reverse min-w-full hide-scrollbar").item(0).children;
                for (var i = 0; i < Chat.length; i++) {
                    Chat.item(i).style = "min-width:100%";
                    document.getElementsByClassName("flex w-full flex-col max-w-2xl").item(0).style = "min-width:100%";
                }
            }
        }
        setTimeout(() => { setInterval(WideScreen, 100); }, 1000);
    })();

    function createSaveButton() {
        const saveChatButton = document.createElement('button');
        saveChatButton.innerHTML = 'Chat Options ▼';
        saveChatButton.style.position = 'fixed';
        saveChatButton.style.top = localStorage.getItem('buttonTop') || '10px';
        saveChatButton.style.left = localStorage.getItem('buttonLeft') || '10px';
        saveChatButton.style.backgroundColor = '#ff0000';
        saveChatButton.style.color = '#ffffff';
        saveChatButton.style.padding = '10px';
        saveChatButton.style.borderRadius = '5px';
        saveChatButton.style.cursor = 'pointer';
        saveChatButton.style.zIndex = '1000';
        saveChatButton.style.border = 'none';
        saveChatButton.style.boxShadow = '0px 2px 5px rgba(0,0,0,0.2)';
        document.body.appendChild(saveChatButton);

        const dropdown = document.createElement('div');
        dropdown.style.display = 'none';
        dropdown.style.position = 'absolute';
        dropdown.style.top = '100%';
        dropdown.style.left = '0';
        dropdown.style.backgroundColor = '#333';
        dropdown.style.border = '1px solid #ccc';
        dropdown.style.boxShadow = '0px 2px 5px rgba(0,0,0,0.2)';
        dropdown.style.zIndex = '1001';
        dropdown.style.color = '#ffffff';
        dropdown.style.fontFamily = 'sans-serif';
        dropdown.style.fontSize = '14px';
        dropdown.style.padding = '5px';
        saveChatButton.appendChild(dropdown);

        const saveButton = document.createElement('button');
        saveButton.innerHTML = 'Save Chat';
        saveButton.style.display = 'block';
        saveButton.style.width = '100%';
        saveButton.style.border = 'none';
        saveButton.style.padding = '10px';
        saveButton.style.cursor = 'pointer';
        saveButton.style.backgroundColor = '#444';
        saveButton.style.color = '#ffffff'; // White text
        saveButton.onclick = saveChat;
        dropdown.appendChild(saveButton);

        const downloadButton = document.createElement('button');
        downloadButton.innerHTML = 'Download Chat';
        downloadButton.style.display = 'block';
        downloadButton.style.width = '100%';
        downloadButton.style.border = 'none';
        downloadButton.style.padding = '10px';
        downloadButton.style.cursor = 'pointer';
        downloadButton.style.backgroundColor = '#444';
        downloadButton.style.color = '#ffffff'; // White text
        downloadButton.onclick = async function() {
            let format = prompt('Enter format (definition/names):', 'definition');
            if (format === 'definition' || format === 'names') {
                await saveAndDownloadChat(format);
            } else {
                alert('Invalid format. Please enter "definition" or "names".');
            }
        };
        dropdown.appendChild(downloadButton);

        return { saveChatButton, dropdown };
    }

    function toggleDropdown(dropdown) {
        dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
    }

    function makeDraggable(saveChatButton) {
        saveChatButton.onmousedown = function(event) {
            event.preventDefault();
            let shiftX = event.clientX - saveChatButton.getBoundingClientRect().left;
            let shiftY = event.clientY - saveChatButton.getBoundingClientRect().top;
            document.onmousemove = function(e) {
                saveChatButton.style.left = (e.clientX - shiftX) + 'px';
                saveChatButton.style.top = (e.clientY - shiftY) + 'px';
            };
            document.onmouseup = function() {
                localStorage.setItem('buttonTop', saveChatButton.style.top);
                localStorage.setItem('buttonLeft', saveChatButton.style.left);
                document.onmousemove = null;
                document.onmouseup = null;
            };
        };
    }

    function updateStyles(saveChatButton) {
        const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
        saveChatButton.style.backgroundColor = isDarkMode ? '#333' : '#ff0000';
        saveChatButton.style.color = isDarkMode ? '#fff' : '#ffffff';
    }

    var cai_version = -1;
    if (location.hostname === "old.character.ai") cai_version = 1;
    else if (location.pathname.startsWith("/chat/")) cai_version = 2;
    else return alert("Unsupported character.ai version");

    var token;
    if (cai_version === 1) token = JSON.parse(localStorage['char_token']).value;
    else if (cai_version === 2) token = JSON.parse(document.getElementById("__NEXT_DATA__").innerHTML).props.pageProps.token;

    async function _fetchchats(charid) {
        let url = 'https://neo.character.ai/chats/recent/' + charid;
        let response = await fetch(url, { headers: { "Authorization": `Token ${token}` } });
        let json = await response.json();
        return json['chats'];
    }

    async function getChats(charid) {
        let json = await _fetchchats(charid);
        return json.map(chat => chat.chat_id);
    }

    async function getMessages(chat, format) {
        let url = 'https://neo.character.ai/turns/' + chat + '/';
        let next_token = null;
        let turns = [];

        do {
            let url2 = url;
            if (next_token) url2 += "?next_token=" + encodeURIComponent(next_token);
            let response = await fetch(url2, { headers: { "Authorization": `Token ${token}` } });
            let json = await response.json();
            turns = turns.concat(json['turns']);
            next_token = json['next_token'];
        } while (next_token);

        return turns.map(turn => format === 'names' ? `User: ${turn['text']}\nBot: ${turn['response']}` : `${JSON.stringify(turn, null, 4)}`);
    }

    async function saveChat() {
        let charid = prompt('Enter character ID:');
        let chats = await getChats(charid);
        let messages = await getMessages(chats[0], 'definition');
        alert(messages.join("\n\n"));
    }

    async function saveAndDownloadChat(format) {
        let charid = prompt('Enter character ID:');
        let chats = await getChats(charid);
        let messages = await getMessages(chats[0], format);

        let filename = `chatlog_${charid}.txt`;
        let blob = new Blob([messages.join("\n\n")], { type: 'text/plain' });
        let link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = filename;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    }

    let { saveChatButton, dropdown } = createSaveButton();
    saveChatButton.onclick = function() { toggleDropdown(dropdown); };
    makeDraggable(saveChatButton);
    updateStyles(saveChatButton);

})();