您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a save and download button with a format dropdown to character.AI, with widescreen support.
当前为
// ==UserScript== // @name c.AI Enhancements // @namespace http://tampermonkey.net/ // @version 1.6 // @description Adds a save and download button with a format dropdown to character.AI, with widescreen support. // @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 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 = '#ffffff'; 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 = '#000000'; 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'; 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'; 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, dropdown) { const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; saveChatButton.style.backgroundColor = isDarkMode ? '#333' : '#ff0000'; saveChatButton.style.color = isDarkMode ? '#fff' : '#ffffff'; dropdown.style.backgroundColor = isDarkMode ? '#333' : '#ffffff'; dropdown.style.color = isDarkMode ? '#ffffff' : '#000000'; } (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); })(); 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(); json['turns'].forEach(turn => { 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 getCharacterName(charid) { let json = await _fetchchats(charid); return json[0].character_name; } async function saveChat() { const chatElements = document.querySelectorAll('.prose.dark\\:prose-invert'); let chatContent = ''; chatElements.forEach(element => { chatContent += element.innerText + '\n'; }); const blob = new Blob([chatContent], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'chat.txt'; a.click(); URL.revokeObjectURL(url); } async function saveAndDownloadChat(format) { const charid = location.pathname.split("/")[2]; let chats = await getChats(charid); let turns = []; for(let i = 0; i < chats.length; i++) turns = turns.concat(await getMessages(chats[i], format)); let content = turns.map(turn => `${turn.author}: ${turn.message}`).join("\n\n"); let filename = (await getCharacterName(charid)).replace(/ /g, "_") + ".txt"; let blob = new Blob([content], { type: 'text/plain' }); let url = URL.createObjectURL(blob); let a = document.createElement('a'); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } function params(parameterName) { var result = null, tmp = []; location.search .substr(1) .split("&") .forEach(function (item) { tmp = item.split("="); if (tmp[0] === parameterName) result = decodeURIComponent(tmp[1]); }); return result; } function init() { const { saveChatButton, dropdown } = createSaveButton(); makeDraggable(saveChatButton); updateStyles(saveChatButton, dropdown); window.matchMedia('(prefers-color-scheme: dark)').addListener(() => updateStyles(saveChatButton, dropdown)); saveChatButton.addEventListener('click', () => toggleDropdown(dropdown)); } init(); })();