Chat Enhancements

Adds a download button, saves the chat to local storage, and enables widescreen mode.

目前為 2024-11-08 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 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.9
// @description  Adds a download button, saves the chat to local storage, 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 WideScreen() {
            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';
        dropdown.style.cursor = 'pointer';
        dropdown.style.maxWidth = '200px';
        dropdown.style.borderRadius = '5px';
        saveChatButton.appendChild(dropdown);
        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';
        downloadButton.style.borderRadius = '5px';
        dropdown.appendChild(downloadButton);
        const saveLocalButton = document.createElement('button');
        saveLocalButton.innerHTML = 'Save to Local Storage';
        saveLocalButton.style.display = 'block';
        saveLocalButton.style.width = '100%';
        saveLocalButton.style.border = 'none';
        saveLocalButton.style.padding = '10px';
        saveLocalButton.style.cursor = 'pointer';
        saveLocalButton.style.backgroundColor = '#444';
        saveLocalButton.style.color = '#ffffff';
        saveLocalButton.style.borderRadius = '5px';
        dropdown.appendChild(saveLocalButton);
        saveChatButton.onclick = function() {
            dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
        };
        let offsetX, offsetY;
        let isDragging = false;
        saveChatButton.addEventListener('mousedown', function(e) {
            isDragging = true;
            offsetX = e.clientX - saveChatButton.getBoundingClientRect().left;
            offsetY = e.clientY - saveChatButton.getBoundingClientRect().top;
        });
        document.addEventListener('mousemove', function(e) {
            if (isDragging) {
                saveChatButton.style.left = e.clientX - offsetX + 'px';
                saveChatButton.style.top = e.clientY - offsetY + 'px';
                localStorage.setItem('buttonTop', saveChatButton.style.top);
                localStorage.setItem('buttonLeft', saveChatButton.style.left);
            }
        });
        document.addEventListener('mouseup', function() {
            isDragging = false;
        });
        return { saveChatButton, dropdown, downloadButton, saveLocalButton };
    }
    async function fetchAndDownloadChat() {
        var token = JSON.parse(document.getElementById("__NEXT_DATA__").innerHTML).props.pageProps.token;
        var _cache;
        async function _fetchchats(charid) {
            if (!_cache) {
                let url = 'https://neo.character.ai/chats/recent/' + charid;
                let response = await fetch(url, { headers: { "Authorization": `Token ${token}` } });
                let json = await response.json();
                _cache = json['chats'];
            }
            return _cache;
        }
        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();
                for (let turn of json['turns']) {
                    let o = {};
                    o.author = format === "definition" ? (turn.author.is_human ? "{{user}}" : "{{char}}") : turn.author.name;
                    o.message = turn.candidates.find(x => x.candidate_id === turn.primary_candidate_id).raw_content || "";
                    turns.push(o);
                }
                next_token = json['meta']['next_token'];
            } while (next_token);
            return turns.reverse();
        }
        async function downloadChat(format) {
            let charid = prompt('Enter character ID:');
            let chats = await getChats(charid);
            let messages = await getMessages(chats[0], format);
            let content = messages.map(msg => `${msg.author}: ${msg.message}`).join('\n');
            let blob = new Blob([content], { type: 'text/plain' });
            let link = document.createElement('a');
            link.href = URL.createObjectURL(blob);
            link.download = `chat_${charid}.txt`;
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
        function saveChatToLocalStorage() {
            let charid = prompt('Enter character ID:');
            getChats(charid).then(chats => {
                if (chats.length > 0) {
                    getMessages(chats[0], "definition").then(messages => {
                        const chatData = {
                            characterID: charid,
                            messages: messages
                        };
                        localStorage.setItem(`chat_${charid}`, JSON.stringify(chatData));
                        alert(`Chat saved to local storage as "chat_${charid}".`);
                    });
                } else {
                    alert("No chats found for this character ID.");
                }
            });
        }
        function showFormatDialog() {
            const dialog = document.createElement('dialog');
            dialog.innerHTML = `
                <form method="dialog">
                    <p>Select format:</p>
                    <label><input type="radio" name="format" value="definition" checked> Definition ({{user}}/{{char}})</label><br>
                    <label><input type="radio" name="format" value="names"> Names (You/Bot)</label><br>
                    <button type="submit">Download</button>
                </form>
            `;
            dialog.addEventListener('close', () => {
                const format = dialog.querySelector('input[name="format"]:checked').value;
                downloadChat(format);
            });
            document.body.appendChild(dialog);
            return dialog;
        }
        const dialog = showFormatDialog();
        let { downloadButton, saveLocalButton } = createSaveButton();
        downloadButton.onclick = function() {
            dialog.showModal();
        };
        saveLocalButton.onclick = function() {
            saveChatToLocalStorage();
        };
    }
    fetchAndDownloadChat();
})();