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 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);

})();