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