Chat Enhancements

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

目前为 2024-10-15 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Chat Enhancements
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.7
  5. // @description Adds a download button, saves the chat to local storage, hides edited tags, and enables widescreen mode.
  6. // @author InariOkami
  7. // @match https://character.ai/*
  8. // @grant none
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=character.ai
  10. // ==/UserScript==
  11.  
  12. (async function() {
  13. 'use strict';
  14.  
  15. (function() {
  16. function loop() {
  17. let elements = document.querySelectorAll("div, p");
  18. elements.forEach(function(val) {
  19. if (val.innerText === "(edited)" || val.innerText === "Edited") {
  20. val.style.display = 'none';
  21. }
  22. });
  23. requestAnimationFrame(loop);
  24. }
  25. loop();
  26. })();
  27.  
  28. (function() {
  29. function WideScreen() {
  30. var Chat = document.getElementsByClassName("overflow-x-hidden overflow-y-scroll px-1 flex flex-col-reverse min-w-full hide-scrollbar").item(0).children;
  31. for (var i = 0; i < Chat.length; i++) {
  32. Chat.item(i).style = "min-width:100%";
  33. document.getElementsByClassName("flex w-full flex-col max-w-2xl").item(0).style = "min-width:100%";
  34. }
  35. }
  36. setTimeout(() => { setInterval(WideScreen, 100); }, 1000);
  37. })();
  38.  
  39. function createSaveButton() {
  40. const saveChatButton = document.createElement('button');
  41. saveChatButton.innerHTML = 'Chat Options ▼';
  42. saveChatButton.style.position = 'fixed';
  43. saveChatButton.style.top = localStorage.getItem('buttonTop') || '10px';
  44. saveChatButton.style.left = localStorage.getItem('buttonLeft') || '10px';
  45. saveChatButton.style.backgroundColor = '#ff0000';
  46. saveChatButton.style.color = '#ffffff';
  47. saveChatButton.style.padding = '10px';
  48. saveChatButton.style.borderRadius = '5px';
  49. saveChatButton.style.cursor = 'pointer';
  50. saveChatButton.style.zIndex = '1000';
  51. saveChatButton.style.border = 'none';
  52. saveChatButton.style.boxShadow = '0px 2px 5px rgba(0,0,0,0.2)';
  53. document.body.appendChild(saveChatButton);
  54.  
  55. const dropdown = document.createElement('div');
  56. dropdown.style.display = 'none';
  57. dropdown.style.position = 'absolute';
  58. dropdown.style.top = '100%';
  59. dropdown.style.left = '0';
  60. dropdown.style.backgroundColor = '#333';
  61. dropdown.style.border = '1px solid #ccc';
  62. dropdown.style.boxShadow = '0px 2px 5px rgba(0,0,0,0.2)';
  63. dropdown.style.zIndex = '1001';
  64. dropdown.style.color = '#ffffff';
  65. dropdown.style.fontFamily = 'sans-serif';
  66. dropdown.style.fontSize = '14px';
  67. dropdown.style.padding = '5px';
  68. dropdown.style.cursor = 'pointer';
  69. dropdown.style.maxWidth = '200px';
  70. dropdown.style.borderRadius = '5px';
  71. saveChatButton.appendChild(dropdown);
  72.  
  73. const downloadButton = document.createElement('button');
  74. downloadButton.innerHTML = 'Download Chat';
  75. downloadButton.style.display = 'block';
  76. downloadButton.style.width = '100%';
  77. downloadButton.style.border = 'none';
  78. downloadButton.style.padding = '10px';
  79. downloadButton.style.cursor = 'pointer';
  80. downloadButton.style.backgroundColor = '#444';
  81. downloadButton.style.color = '#ffffff';
  82. downloadButton.style.borderRadius = '5px';
  83. dropdown.appendChild(downloadButton);
  84.  
  85. const saveLocalButton = document.createElement('button');
  86. saveLocalButton.innerHTML = 'Save to Local Storage';
  87. saveLocalButton.style.display = 'block';
  88. saveLocalButton.style.width = '100%';
  89. saveLocalButton.style.border = 'none';
  90. saveLocalButton.style.padding = '10px';
  91. saveLocalButton.style.cursor = 'pointer';
  92. saveLocalButton.style.backgroundColor = '#444';
  93. saveLocalButton.style.color = '#ffffff';
  94. saveLocalButton.style.borderRadius = '5px';
  95. dropdown.appendChild(saveLocalButton);
  96.  
  97. saveChatButton.onclick = function() {
  98. dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
  99. };
  100.  
  101. let offsetX, offsetY;
  102. let isDragging = false;
  103.  
  104. saveChatButton.addEventListener('mousedown', function(e) {
  105. isDragging = true;
  106. offsetX = e.clientX - saveChatButton.getBoundingClientRect().left;
  107. offsetY = e.clientY - saveChatButton.getBoundingClientRect().top;
  108. });
  109.  
  110. document.addEventListener('mousemove', function(e) {
  111. if (isDragging) {
  112. saveChatButton.style.left = e.clientX - offsetX + 'px';
  113. saveChatButton.style.top = e.clientY - offsetY + 'px';
  114. localStorage.setItem('buttonTop', saveChatButton.style.top);
  115. localStorage.setItem('buttonLeft', saveChatButton.style.left);
  116. }
  117. });
  118.  
  119. document.addEventListener('mouseup', function() {
  120. isDragging = false;
  121. });
  122.  
  123. return { saveChatButton, dropdown, downloadButton, saveLocalButton };
  124. }
  125.  
  126. async function fetchAndDownloadChat() {
  127. var token = JSON.parse(document.getElementById("__NEXT_DATA__").innerHTML).props.pageProps.token;
  128. var _cache;
  129.  
  130. async function _fetchchats(charid) {
  131. if (!_cache) {
  132. let url = 'https://neo.character.ai/chats/recent/' + charid;
  133. let response = await fetch(url, { headers: { "Authorization": `Token ${token}` } });
  134. let json = await response.json();
  135. _cache = json['chats'];
  136. }
  137. return _cache;
  138. }
  139.  
  140. async function getChats(charid) {
  141. let json = await _fetchchats(charid);
  142. return json.map(chat => chat.chat_id);
  143. }
  144.  
  145. async function getMessages(chat, format) {
  146. let url = 'https://neo.character.ai/turns/' + chat + '/';
  147. let next_token = null;
  148. let turns = [];
  149.  
  150. do {
  151. let url2 = url;
  152. if (next_token) url2 += "?next_token=" + encodeURIComponent(next_token);
  153. let response = await fetch(url2, { headers: { "Authorization": `Token ${token}` } });
  154. let json = await response.json();
  155. for (let turn of json['turns']) {
  156. let o = {};
  157. o.author = format === "definition" ? (turn.author.is_human ? "{{user}}" : "{{char}}") : turn.author.name;
  158. o.message = turn.candidates.find(x => x.candidate_id === turn.primary_candidate_id).raw_content || "";
  159. turns.push(o);
  160. }
  161. next_token = json['meta']['next_token'];
  162. } while (next_token);
  163. return turns.reverse();
  164. }
  165.  
  166. async function downloadChat(format) {
  167. let charid = prompt('Enter character ID:');
  168. let chats = await getChats(charid);
  169. let messages = await getMessages(chats[0], format);
  170. let content = messages.map(msg => `${msg.author}: ${msg.message}`).join('\n');
  171. let blob = new Blob([content], { type: 'text/plain' });
  172. let link = document.createElement('a');
  173. link.href = URL.createObjectURL(blob);
  174. link.download = `chat_${charid}.txt`;
  175. document.body.appendChild(link);
  176. link.click();
  177. document.body.removeChild(link);
  178. }
  179.  
  180. function saveChatToLocalStorage() {
  181. let charid = prompt('Enter character ID:');
  182. getChats(charid).then(chats => {
  183. if (chats.length > 0) {
  184. getMessages(chats[0], "definition").then(messages => {
  185. const chatData = {
  186. characterID: charid,
  187. messages: messages
  188. };
  189. localStorage.setItem(`chat_${charid}`, JSON.stringify(chatData));
  190. alert(`Chat saved to local storage as "chat_${charid}".`);
  191. });
  192. } else {
  193. alert("No chats found for this character ID.");
  194. }
  195. });
  196. }
  197.  
  198. const dialog = document.createElement('dialog');
  199. dialog.innerHTML = `
  200. <form method="dialog">
  201. <p>Select format:</p>
  202. <label><input type="radio" name="format" value="definition" checked> Definition ({{user}}/{{char}})</label><br>
  203. <label><input type="radio" name="format" value="names"> Names (You/Bot)</label><br>
  204. <button type="submit">Download</button>
  205. </form>
  206. `;
  207. dialog.addEventListener('close', () => {
  208. const format = dialog.querySelector('input[name="format"]:checked').value;
  209. downloadChat(format);
  210. });
  211. document.body.appendChild(dialog);
  212.  
  213. let { downloadButton, saveLocalButton } = createSaveButton();
  214. downloadButton.onclick = function() {
  215. dialog.showModal();
  216. };
  217.  
  218. saveLocalButton.onclick = function() {
  219. saveChatToLocalStorage();
  220. };
  221. }
  222.  
  223. fetchAndDownloadChat();
  224. })();