您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a download button, saves the chat to local storage, and enables widescreen mode.
// ==UserScript== // @name Chat Enhancements // @namespace http://tampermonkey.net/ // @version 1.1 // @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."); } }); } 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); let { downloadButton, saveLocalButton } = createSaveButton(); downloadButton.onclick = function() { dialog.showModal(); }; saveLocalButton.onclick = function() { saveChatToLocalStorage(); }; } fetchAndDownloadChat(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址