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.8
  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. getRandomEntry(inputElement);
  148. sendMessage(inputElement);
  149. anotherCustomFunction();
  150.  
  151. }
  152. }
  153.  
  154.  
  155.  
  156. // Function to get the correct input element based on the mode
  157. function getInputElement() {
  158. return document.querySelector('[data-slate-editor="true"]') || document.querySelector('textarea[class*="textArea_"]');
  159. }
  160.  
  161. // Function to apply custom rules for the input field
  162. function applyCustomRule(inputElement) {
  163. const customRule = unsafeWindow.customRuleLogic ? unsafeWindow.customRuleLogic.getCurrentText() : '';
  164.  
  165. if (inputElement.nodeName === 'TEXTAREA') {
  166. // For mobile version where input is <textarea>
  167. const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
  168. nativeInputValueSetter.call(inputElement, inputElement.value + customRule);
  169.  
  170. const inputEvent = new Event('input', {
  171. bubbles: true,
  172. cancelable: true,
  173. });
  174. inputElement.dispatchEvent(inputEvent);
  175. } else {
  176. // For desktop version where input is a Slate editor
  177. const inputEvent = new InputEvent('beforeinput', {
  178. bubbles: true,
  179. cancelable: true,
  180. inputType: 'insertText',
  181. data: customRule,
  182. });
  183. inputElement.dispatchEvent(inputEvent);
  184. }
  185. }
  186.  
  187. // Function to set the cursor position to the end after inserting the text
  188. function setCursorToEnd(inputElement) {
  189. if (inputElement.nodeName === 'TEXTAREA') {
  190. inputElement.selectionStart = inputElement.selectionEnd = inputElement.value.length;
  191. } else {
  192. const range = document.createRange();
  193. range.selectNodeContents(inputElement);
  194. range.collapse(false);
  195. const selection = window.getSelection();
  196. selection.removeAllRanges();
  197. selection.addRange(range);
  198. }
  199. }
  200.  
  201. // Function to send the message (either click send button or simulate Enter key)
  202. function sendMessage(inputElement) {
  203. let sendButton = document.querySelector('button[aria-label="Nachricht senden"]');
  204. if (sendButton) {
  205. sendButton.click();
  206. console.log('Send button clicked to send message');
  207. } else {
  208. // For desktop version, simulate pressing Enter to send the message
  209. let enterEvent = new KeyboardEvent('keydown', {
  210. key: 'Enter',
  211. code: 'Enter',
  212. keyCode: 13,
  213. which: 13,
  214. bubbles: true,
  215. cancelable: true
  216. });
  217. inputElement.dispatchEvent(enterEvent);
  218. console.log('Enter key simulated to send message');
  219. }
  220. }
  221.  
  222. // Example of adding another function
  223. function anotherCustomFunction() {
  224. console.log('Another custom function executed');
  225. }
  226.  
  227.  
  228. // Function to scan for keywords and access local storage
  229. function scanForKeywords(inputElement) {
  230. const currentProfile = getCurrentProfile();
  231. if (currentProfile) {
  232. // Retrieve all messages before iterating through storage keys
  233. const messageItems = document.querySelectorAll('div[class*="messageContent_"]');
  234. const relevantMessages = Array.from(messageItems).slice(-15); // Messages -15 to -1
  235. const lastMessage = Array.from(messageItems).slice(-1); // Last message only
  236.  
  237. // Log the messages for debugging purposes even if no keyword is matched
  238. console.log("Relevant Messages (last 15):", relevantMessages.map(msg => msg.textContent));
  239. console.log("Last Message:", lastMessage.map(msg => msg.textContent));
  240.  
  241. // Track how many entries have been appended
  242. let appendedCount = 0;
  243. const maxAppends = 3;
  244.  
  245. // Iterate through all localStorage keys that match the profile-lorebook prefix
  246. Object.keys(localStorage).forEach(storageKey => {
  247. if (storageKey.startsWith(`${currentProfile}-lorebook:`)) {
  248. const entryKeys = storageKey.replace(`${currentProfile}-lorebook:`, '').split(',');
  249. const entryValue = localStorage.getItem(storageKey);
  250.  
  251. // Log the entry keys for debugging purposes
  252. console.log(`Entry Keys: `, entryKeys);
  253.  
  254. entryKeys.forEach(entryKey => {
  255. if (appendedCount >= maxAppends) return; // Stop if max appends reached
  256.  
  257. // Check input element text for complete word match of keyword
  258. const inputText = inputElement.value || inputElement.textContent;
  259.  
  260. // Combine check for keyword in input or in the last message
  261. const isKeywordInInput = inputText && new RegExp(`\\b${entryKey}\\b`).test(inputText);
  262. const isKeywordInLastMessage = lastMessage.some(message => {
  263. const lastMessageText = message.textContent;
  264. return new RegExp(`\\b${entryKey}\\b`).test(lastMessageText);
  265. });
  266.  
  267. if (isKeywordInInput || isKeywordInLastMessage) {
  268. const keywordAlreadyUsed = relevantMessages.some(message => {
  269. const messageText = message.textContent;
  270. const bracketContent = messageText.match(/\[(.*?)\]/);
  271.  
  272. return bracketContent ? new RegExp(`\\b${entryKey}\\b`).test(bracketContent[1]) : false;
  273. });
  274.  
  275. if (!keywordAlreadyUsed) {
  276. // Append the entryValue to the input element
  277. if (inputElement.nodeName === 'TEXTAREA') {
  278. const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
  279. nativeInputValueSetter.call(inputElement, inputElement.value + '\n' + entryValue);
  280.  
  281. const inputEvent = new Event('input', {
  282. bubbles: true,
  283. cancelable: true,
  284. });
  285. inputElement.dispatchEvent(inputEvent);
  286. } else {
  287. const inputEvent = new InputEvent('beforeinput', {
  288. bubbles: true,
  289. cancelable: true,
  290. inputType: 'insertText',
  291. data: '\n' + entryValue,
  292. });
  293. inputElement.dispatchEvent(inputEvent);
  294. }
  295. appendedCount++; // Increment the count
  296. console.log(`Keyword '${entryKey}' detected. Appended lorebook entry to the input.`);
  297. } else {
  298. console.log(`Keyword '${entryKey}' already found in recent bracketed messages. Skipping append.`);
  299. }
  300. }
  301. });
  302. }
  303. });
  304.  
  305. // Log the total number of entries appended
  306. console.log(`Total lorebook entries appended: ${appendedCount}`);
  307. }
  308. }
  309.  
  310. // Function to get the current profile from local storage
  311. function getCurrentProfile() {
  312. return localStorage.getItem('currentProfile');
  313. }
  314.  
  315.  
  316. function getRandomEntry(inputElement) {
  317. const selectedProfile = localStorage.getItem('events.selectedProfile');
  318. if (selectedProfile) {
  319. let profileEntries = [];
  320. const currentHour = new Date().getHours();
  321. for (let key in localStorage) {
  322. if (key.startsWith(`events.${selectedProfile}:`)) {
  323. const entryData = JSON.parse(localStorage.getItem(key));
  324. const [startHour, endHour] = entryData.timeRange.split('-').map(Number);
  325. // Check if current hour is within the specified time range
  326. if (currentHour >= startHour && currentHour < endHour) {
  327. profileEntries.push({ key, ...entryData });
  328. }
  329. }
  330. }
  331. if (profileEntries.length > 0) {
  332. const probability = parseInt(localStorage.getItem('events.probability') || '100', 10);
  333. let selectedEntry = null;
  334. while (profileEntries.length > 0) {
  335. // Randomly select an entry from the available entries
  336. const randomIndex = Math.floor(Math.random() * profileEntries.length);
  337. const randomEntry = profileEntries[randomIndex];
  338. // Check if the entry passes the individual probability check
  339. if (Math.random() * 100 < randomEntry.probability) {
  340. selectedEntry = randomEntry;
  341. break;
  342. } else {
  343. // Remove the entry from the list if it fails the probability check
  344. profileEntries.splice(randomIndex, 1);
  345. }
  346. }
  347. if (selectedEntry && Math.random() * 100 < probability) {
  348. console.log(`Random Entry Selected: ${selectedEntry.value}`);
  349. appendToInput(inputElement, selectedEntry.value); // Append the random entry to the input element
  350. }
  351. } else {
  352. console.log('No entries available for the selected profile in the current time range.');
  353. }
  354. } else {
  355. console.log('No profile selected. Please select a profile to retrieve a random entry.');
  356. }
  357. }
  358.  
  359. // Helper function to append text to the input element
  360. function appendToInput(inputElement, text) {
  361. const lineBreak = '\n';
  362. if (!inputElement) {
  363. console.error('Input element not found.');
  364. return;
  365. }
  366.  
  367. if (inputElement.nodeName === 'TEXTAREA') {
  368. // Mobile: Append text to <textarea>
  369. const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set;
  370. nativeInputValueSetter.call(inputElement, inputElement.value + `${lineBreak}${text}`);
  371.  
  372. const inputEvent = new Event('input', {
  373. bubbles: true,
  374. cancelable: true,
  375. });
  376. inputElement.dispatchEvent(inputEvent);
  377. } else if (inputElement.hasAttribute('data-slate-editor')) {
  378. // Desktop: Append text for Slate editor
  379. const inputEvent = new InputEvent('beforeinput', {
  380. bubbles: true,
  381. cancelable: true,
  382. inputType: 'insertText',
  383. data: `${lineBreak}${text}`,
  384. });
  385. inputElement.dispatchEvent(inputEvent);
  386. } else {
  387. console.error('Unsupported input element type.');
  388. }
  389. }
  390.  
  391.  
  392.  
  393. // Expose the function to be accessible from other scripts
  394. unsafeWindow.getRandomEntry = getRandomEntry;
  395.  
  396.  
  397. }
  398. })();