Chat Enhancements

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

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