您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a save and download button with a format dropdown to character.AI.
当前为
// ==UserScript== // @name c.AI Save and Download // @namespace http://tampermonkey.net/ // @version 1.7 // @description Adds a save and download button with a format dropdown to character.AI. // @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 = '#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(); 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'; }); localStorage.setItem('savedChat', chatContent); alert('Chat saved!'); } async function saveAndDownloadChat(format) { let char = location.pathname.split("/")[2]; let history = params('hist'); if(history === null) { let chats = await getChats(char); history = chats[0]; } let msgs = await getMessages(history, format); let str = ""; for(let msg of msgs) { str += `${msg.author}: ${msg.message}\n`; } let date = new Date(); let date_str = `${date.getDate()}-${date.getMonth()+1}-${date.getFullYear()} ${date.getHours()}.${date.getMinutes()}`; download(`${await getCharacterName(char)} ${date_str}.txt`, str.trimEnd()); } function download(filename, text) { var element = document.createElement('a'); element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)); element.setAttribute('download', filename); element.style.display = 'none'; document.body.appendChild(element); element.click(); document.body.removeChild(element); } 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); window.matchMedia('(prefers-color-scheme: dark)').addListener(() => updateStyles(saveChatButton)); saveChatButton.addEventListener('click', () => toggleDropdown(dropdown)); } init(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址