Chat Enhancements

Adds a download button, hides edited tags, auto-clicks "Try Again," and enables widescreen mode.

当前为 2024-10-03 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Chat Enhancements
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.5
  5. // @description Adds a download button, hides edited tags, auto-clicks "Try Again," 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. saveChatButton.onclick = function() {
  86. dropdown.style.display = dropdown.style.display === 'none' ? 'block' : 'none';
  87. };
  88.  
  89. let offsetX, offsetY;
  90. let isDragging = false;
  91.  
  92. saveChatButton.addEventListener('mousedown', function(e) {
  93. isDragging = true;
  94. offsetX = e.clientX - saveChatButton.getBoundingClientRect().left;
  95. offsetY = e.clientY - saveChatButton.getBoundingClientRect().top;
  96. });
  97.  
  98. document.addEventListener('mousemove', function(e) {
  99. if (isDragging) {
  100. saveChatButton.style.left = e.clientX - offsetX + 'px';
  101. saveChatButton.style.top = e.clientY - offsetY + 'px';
  102. localStorage.setItem('buttonTop', saveChatButton.style.top);
  103. localStorage.setItem('buttonLeft', saveChatButton.style.left);
  104. }
  105. });
  106.  
  107. document.addEventListener('mouseup', function() {
  108. isDragging = false;
  109. });
  110.  
  111. return { saveChatButton, dropdown, downloadButton };
  112. }
  113.  
  114. async function fetchAndDownloadChat() {
  115. var token = JSON.parse(document.getElementById("__NEXT_DATA__").innerHTML).props.pageProps.token;
  116. var _cache;
  117.  
  118. async function _fetchchats(charid) {
  119. if (!_cache) {
  120. let url = 'https://neo.character.ai/chats/recent/' + charid;
  121. let response = await fetch(url, { headers: { "Authorization": `Token ${token}` } });
  122. let json = await response.json();
  123. _cache = json['chats'];
  124. }
  125. return _cache;
  126. }
  127.  
  128. async function getChats(charid) {
  129. let json = await _fetchchats(charid);
  130. return json.map(chat => chat.chat_id);
  131. }
  132.  
  133. async function getMessages(chat, format) {
  134. let url = 'https://neo.character.ai/turns/' + chat + '/';
  135. let next_token = null;
  136. let turns = [];
  137.  
  138. do {
  139. let url2 = url;
  140. if (next_token) url2 += "?next_token=" + encodeURIComponent(next_token);
  141. let response = await fetch(url2, { headers: { "Authorization": `Token ${token}` } });
  142. let json = await response.json();
  143. for (let turn of json['turns']) {
  144. let o = {};
  145. o.author = format === "definition" ? (turn.author.is_human ? "{{user}}" : "{{char}}") : turn.author.name;
  146. o.message = turn.candidates.find(x => x.candidate_id === turn.primary_candidate_id).raw_content || "";
  147. turns.push(o);
  148. }
  149. next_token = json['meta']['next_token'];
  150. } while (next_token);
  151. return turns.reverse();
  152. }
  153.  
  154. async function downloadChat(format) {
  155. let charid = prompt('Enter character ID:');
  156. let chats = await getChats(charid);
  157. let messages = await getMessages(chats[0], format);
  158. let content = messages.map(msg => `${msg.author}: ${msg.message}`).join('\n');
  159. let blob = new Blob([content], { type: 'text/plain' });
  160. let link = document.createElement('a');
  161. link.href = URL.createObjectURL(blob);
  162. link.download = `chat_${charid}.txt`;
  163. document.body.appendChild(link);
  164. link.click();
  165. document.body.removeChild(link);
  166. }
  167.  
  168. const dialog = document.createElement('dialog');
  169. dialog.innerHTML = `
  170. <form method="dialog">
  171. <p>Select format:</p>
  172. <label><input type="radio" name="format" value="definition" checked> Definition ({{user}}/{{char}})</label><br>
  173. <label><input type="radio" name="format" value="names"> Names (You/Bot)</label><br>
  174. <button type="submit">Download</button>
  175. </form>
  176. `;
  177. dialog.addEventListener('close', () => {
  178. const format = dialog.querySelector('input[name="format"]:checked').value;
  179. downloadChat(format);
  180. });
  181. document.body.appendChild(dialog);
  182.  
  183. let { downloadButton } = createSaveButton();
  184. downloadButton.onclick = function() {
  185. dialog.showModal();
  186. };
  187. }
  188.  
  189. fetchAndDownloadChat();
  190.  
  191. })();