您需要先安装一个扩展,例如 篡改猴、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();
- })();