Discord/Shapes - Main Logic

Handling the logic of Rules and Lorebook

当前为 2024-11-24 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Discord/Shapes - Main Logic
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.5
  5. // @description Handling the logic of Rules and Lorebook
  6. // @author Vishanka
  7. // @match https://discord.com/channels/*
  8. // @grant unsafeWindow
  9. // @run-at document-idle
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Function to check localStorage and reload if not ready
  16. function checkLocalStorageAndReload() {
  17. try {
  18. if (localStorage.length > 0) {
  19. console.log("LocalStorage has items. Proceeding with script...");
  20. initializeScript();
  21. } else {
  22. console.warn("LocalStorage is empty. Reloading page...");
  23. setTimeout(() => {
  24. location.reload();
  25. }, 5000); // Wait 5 seconds before reloading
  26. }
  27. } catch (error) {
  28. console.error("Error accessing localStorage:", error);
  29. setTimeout(() => {
  30. location.reload();
  31. }, 5000); // Wait 5 seconds before reloading
  32. }
  33. }
  34.  
  35. // Initial check for localStorage existence
  36. checkLocalStorageAndReload();
  37.  
  38. function initializeScript() {
  39. // Retrieve settings from localStorage or set default values
  40. let enterKeyDisabled = JSON.parse(localStorage.getItem('enterKeyDisabled')) || false;
  41. let customRuleEnabled = JSON.parse(localStorage.getItem('customRuleEnabled')) || true;
  42. let scanForKeywordsEnabled = JSON.parse(localStorage.getItem('scanForKeywordsEnabled')) || true;
  43.  
  44. // Create and add the UI button
  45. /* window.uiButton = document.createElement('button');
  46. uiButton.innerHTML = 'Toggle Settings';
  47. // uiButton.style.position = 'fixed';
  48. uiButton.style.bottom = '20px';
  49. uiButton.style.right = '20px';
  50. uiButton.style.zIndex = '1000';
  51. uiButton.style.padding = '10px';
  52. uiButton.style.backgroundColor = '#5865F2';
  53. uiButton.style.color = 'white';
  54. uiButton.style.border = 'none';
  55. uiButton.style.borderRadius = '5px';
  56. uiButton.style.cursor = 'pointer';
  57.  
  58. // document.body.appendChild(uiButton);
  59. */
  60. // Create the settings window
  61. unsafeWindow.settingsWindow = document.createElement('div');
  62. // settingsWindow.style.position = 'fixed';
  63. settingsWindow.style.bottom = '60px';
  64. settingsWindow.style.right = '20px';
  65. settingsWindow.style.width = '250px';
  66. settingsWindow.style.padding = '15px';
  67. // settingsWindow.style.backgroundColor = '#2f3136';
  68. settingsWindow.style.color = 'white';
  69. // settingsWindow.style.border = '1px solid #5865F2';
  70. settingsWindow.style.borderRadius = '5px';
  71. // settingsWindow.style.display = 'none';
  72. settingsWindow.style.zIndex = '1001';
  73. DCstoragePanel.appendChild(settingsWindow);
  74. // Custom Rule Checkbox
  75. const enableCustomRuleCheckbox = document.createElement('input');
  76. enableCustomRuleCheckbox.type = 'checkbox';
  77. enableCustomRuleCheckbox.checked = customRuleEnabled;
  78. enableCustomRuleCheckbox.id = 'enableCustomRuleCheckbox';
  79.  
  80. const enableCustomRuleLabel = document.createElement('label');
  81. enableCustomRuleLabel.htmlFor = 'enableCustomRuleCheckbox';
  82. enableCustomRuleLabel.innerText = ' Enable Custom Rules';
  83.  
  84. // Scan for Keywords Checkbox
  85. const enableScanForKeywordsCheckbox = document.createElement('input');
  86. enableScanForKeywordsCheckbox.type = 'checkbox';
  87. enableScanForKeywordsCheckbox.checked = scanForKeywordsEnabled;
  88. enableScanForKeywordsCheckbox.id = 'enableScanForKeywordsCheckbox';
  89.  
  90. const enableScanForKeywordsLabel = document.createElement('label');
  91. enableScanForKeywordsLabel.htmlFor = 'enableScanForKeywordsCheckbox';
  92. enableScanForKeywordsLabel.innerText = ' Enable Lorebook';
  93.  
  94. // Append elements to settings window
  95. settingsWindow.appendChild(enableCustomRuleCheckbox);
  96. settingsWindow.appendChild(enableCustomRuleLabel);
  97. settingsWindow.appendChild(document.createElement('br'));
  98. settingsWindow.appendChild(enableScanForKeywordsCheckbox);
  99. settingsWindow.appendChild(enableScanForKeywordsLabel);
  100. // document.body.appendChild(settingsWindow);
  101.  
  102. // Event listener to open/close settings window
  103. /* uiButton.addEventListener('click', function() {
  104. settingsWindow.style.display = settingsWindow.style.display === 'none' ? 'block' : 'none';
  105. });
  106. */
  107. // Update customRuleEnabled when checkbox is toggled, and save it in localStorage
  108. enableCustomRuleCheckbox.addEventListener('change', function() {
  109. customRuleEnabled = enableCustomRuleCheckbox.checked;
  110. localStorage.setItem('customRuleEnabled', JSON.stringify(customRuleEnabled));
  111. });
  112.  
  113. // Update scanForKeywordsEnabled when checkbox is toggled, and save it in localStorage
  114. enableScanForKeywordsCheckbox.addEventListener('change', function() {
  115. scanForKeywordsEnabled = enableScanForKeywordsCheckbox.checked;
  116. localStorage.setItem('scanForKeywordsEnabled', JSON.stringify(scanForKeywordsEnabled));
  117. });
  118.  
  119. // Add event listener to handle Enter key behavior
  120. window.addEventListener('keydown', function(event) {
  121. if (event.key === 'Enter' && !event.shiftKey && !enterKeyDisabled) {
  122. event.preventDefault();
  123. event.stopPropagation();
  124. event.stopImmediatePropagation();
  125. console.log('Enter key disabled');
  126. enterKeyDisabled = true;
  127.  
  128. // Execute main handler for Enter key
  129. handleEnterKey();
  130.  
  131. enterKeyDisabled = false;
  132. }
  133. }, true); // Use capture mode to intercept the event before Discord's handlers
  134.  
  135. // Main function that handles Enter key behavior
  136. function handleEnterKey() {
  137. let inputElement = getInputElement();
  138. if (inputElement) {
  139. inputElement.focus();
  140. if (customRuleEnabled) {
  141. applyCustomRule(inputElement);
  142. }
  143. setCursorToEnd(inputElement);
  144. if (scanForKeywordsEnabled) {
  145. scanForKeywords(inputElement);
  146. }
  147. sendMessage(inputElement);
  148. anotherCustomFunction();
  149. }
  150. }
  151.  
  152.  
  153.  
  154. // Function to get the correct input element based on the mode
  155. function getInputElement() {
  156. return document.querySelector('[data-slate-editor="true"]') || document.querySelector('textarea[class*="textArea_"]');
  157. }
  158.  
  159. // Function to apply custom rules for the input field
  160. function applyCustomRule(inputElement) {
  161. const customRule = unsafeWindow.customRuleLogic ? unsafeWindow.customRuleLogic.getCurrentText() : '';
  162.  
  163. if (inputElement.nodeName === 'TEXTAREA') {
  164. // For mobile version where input is <textarea>
  165. const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
  166. nativeInputValueSetter.call(inputElement, inputElement.value + customRule);
  167.  
  168. const inputEvent = new Event('input', {
  169. bubbles: true,
  170. cancelable: true,
  171. });
  172. inputElement.dispatchEvent(inputEvent);
  173. } else {
  174. // For desktop version where input is a Slate editor
  175. const inputEvent = new InputEvent('beforeinput', {
  176. bubbles: true,
  177. cancelable: true,
  178. inputType: 'insertText',
  179. data: customRule,
  180. });
  181. inputElement.dispatchEvent(inputEvent);
  182. }
  183. }
  184.  
  185. // Function to set the cursor position to the end after inserting the text
  186. function setCursorToEnd(inputElement) {
  187. if (inputElement.nodeName === 'TEXTAREA') {
  188. inputElement.selectionStart = inputElement.selectionEnd = inputElement.value.length;
  189. } else {
  190. const range = document.createRange();
  191. range.selectNodeContents(inputElement);
  192. range.collapse(false);
  193. const selection = window.getSelection();
  194. selection.removeAllRanges();
  195. selection.addRange(range);
  196. }
  197. }
  198.  
  199. // Function to send the message (either click send button or simulate Enter key)
  200. function sendMessage(inputElement) {
  201. let sendButton = document.querySelector('button[aria-label="Nachricht senden"]');
  202. if (sendButton) {
  203. sendButton.click();
  204. console.log('Send button clicked to send message');
  205. } else {
  206. // For desktop version, simulate pressing Enter to send the message
  207. let enterEvent = new KeyboardEvent('keydown', {
  208. key: 'Enter',
  209. code: 'Enter',
  210. keyCode: 13,
  211. which: 13,
  212. bubbles: true,
  213. cancelable: true
  214. });
  215. inputElement.dispatchEvent(enterEvent);
  216. console.log('Enter key simulated to send message');
  217. }
  218. }
  219.  
  220. // Example of adding another function
  221. function anotherCustomFunction() {
  222. console.log('Another custom function executed');
  223. }
  224.  
  225.  
  226. // Function to scan for keywords and access local storage
  227. function scanForKeywords(inputElement) {
  228. const currentProfile = getCurrentProfile();
  229. if (currentProfile) {
  230. // Retrieve all messages before iterating through storage keys
  231. const messageItems = document.querySelectorAll('div[class*="messageContent_"]');
  232. const relevantMessages = Array.from(messageItems).slice(-15); // Messages -5 to -1
  233. const lastMessage = Array.from(messageItems).slice(-1); // Last message only
  234.  
  235. // Log the messages for debugging purposes even if no keyword is matched
  236. console.log("Relevant Messages (last 5):", relevantMessages.map(msg => msg.textContent));
  237. console.log("Last Message:", lastMessage.map(msg => msg.textContent));
  238.  
  239. // Iterate through all localStorage keys that match the profile-lorebook prefix
  240. Object.keys(localStorage).forEach(storageKey => {
  241. if (storageKey.startsWith(`${currentProfile}-lorebook:`)) {
  242. const entryKeys = storageKey.replace(`${currentProfile}-lorebook:`, '').split(',');
  243. const entryValue = localStorage.getItem(storageKey);
  244.  
  245. // Log the entry keys for debugging purposes
  246. console.log(`Entry Keys: `, entryKeys);
  247.  
  248. entryKeys.forEach(entryKey => {
  249. // Check input element text for complete word match of keyword
  250. const inputText = inputElement.value || inputElement.textContent;
  251.  
  252. // Combine check for keyword in input or in the last message
  253. const isKeywordInInput = inputText && new RegExp(`\\b${entryKey}\\b`).test(inputText);
  254. const isKeywordInLastMessage = lastMessage.some(message => {
  255. const lastMessageText = message.textContent;
  256. return new RegExp(`\\b${entryKey}\\b`).test(lastMessageText);
  257. });
  258.  
  259. if (isKeywordInInput || isKeywordInLastMessage) {
  260. const keywordAlreadyUsed = relevantMessages.some(message => {
  261. const messageText = message.textContent;
  262. const bracketContent = messageText.match(/\[(.*?)\]/);
  263.  
  264. return bracketContent ? new RegExp(`\\b${entryKey}\\b`).test(bracketContent[1]) : false;
  265. });
  266.  
  267. if (!keywordAlreadyUsed) {
  268. // Append the entryValue to the input element
  269. if (inputElement.nodeName === 'TEXTAREA') {
  270. const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
  271. nativeInputValueSetter.call(inputElement, inputElement.value + '\n' + entryValue);
  272.  
  273. const inputEvent = new Event('input', {
  274. bubbles: true,
  275. cancelable: true,
  276. });
  277. inputElement.dispatchEvent(inputEvent);
  278. } else {
  279. const inputEvent = new InputEvent('beforeinput', {
  280. bubbles: true,
  281. cancelable: true,
  282. inputType: 'insertText',
  283. data: '\n' + entryValue,
  284. });
  285. inputElement.dispatchEvent(inputEvent);
  286. }
  287. console.log(`Keyword '${entryKey}' detected. Appended lorebook entry to the input.`);
  288. } else {
  289. console.log(`Keyword '${entryKey}' already found in recent bracketed messages. Skipping append.`);
  290. }
  291. }
  292. });
  293. }
  294. });
  295. }
  296. }
  297.  
  298. // Function to get the current profile from local storage
  299. function getCurrentProfile() {
  300. return localStorage.getItem('currentProfile');
  301. }
  302.  
  303.  
  304.  
  305. }
  306. })();