您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Handling the logic of Rules and Lorebook
// ==UserScript== // @name Discord/Shapes - Main Logic // @namespace http://tampermonkey.net/ // @version 2.2 // @description Handling the logic of Rules and Lorebook // @author Vishanka // @match https://discord.com/channels/* // @grant unsafeWindow // @run-at document-idle // ==/UserScript== (function() { 'use strict'; // Function to check localStorage and reload if not ready function checkLocalStorageAndReload() { try { if (localStorage.length > 0) { console.log("LocalStorage has items. Proceeding with script..."); initializeScript(); } else { console.warn("LocalStorage is empty. Reloading page..."); setTimeout(() => { location.reload(); }, 5000); // Wait 5 seconds before reloading } } catch (error) { console.error("Error accessing localStorage:", error); setTimeout(() => { location.reload(); }, 5000); // Wait 5 seconds before reloading } } // Initial check for localStorage existence checkLocalStorageAndReload(); function initializeScript() { // Retrieve settings from localStorage or set default values let enterKeyDisabled = JSON.parse(localStorage.getItem('enterKeyDisabled')) || false; let customRuleEnabled = JSON.parse(localStorage.getItem('customRuleEnabled')) || true; let scanForKeywordsEnabled = JSON.parse(localStorage.getItem('scanForKeywordsEnabled')) || true; // Create the settings window unsafeWindow.settingsWindow = document.createElement('div'); // settingsWindow.style.position = 'fixed'; settingsWindow.style.bottom = '60px'; settingsWindow.style.right = '20px'; settingsWindow.style.width = '250px'; settingsWindow.style.padding = '15px'; // settingsWindow.style.backgroundColor = '#2f3136'; settingsWindow.style.color = 'white'; // settingsWindow.style.border = '1px solid #5865F2'; settingsWindow.style.borderRadius = '5px'; // settingsWindow.style.display = 'none'; settingsWindow.style.zIndex = '1001'; DCstoragePanel.appendChild(settingsWindow); // Custom Rule Checkbox const enableCustomRuleCheckbox = document.createElement('input'); enableCustomRuleCheckbox.type = 'checkbox'; enableCustomRuleCheckbox.checked = customRuleEnabled; enableCustomRuleCheckbox.id = 'enableCustomRuleCheckbox'; const enableCustomRuleLabel = document.createElement('label'); enableCustomRuleLabel.htmlFor = 'enableCustomRuleCheckbox'; enableCustomRuleLabel.innerText = ' Enable Custom Rules'; // Scan for Keywords Checkbox const enableScanForKeywordsCheckbox = document.createElement('input'); enableScanForKeywordsCheckbox.type = 'checkbox'; enableScanForKeywordsCheckbox.checked = scanForKeywordsEnabled; enableScanForKeywordsCheckbox.id = 'enableScanForKeywordsCheckbox'; const enableScanForKeywordsLabel = document.createElement('label'); enableScanForKeywordsLabel.htmlFor = 'enableScanForKeywordsCheckbox'; enableScanForKeywordsLabel.innerText = ' Enable Lorebook'; // Append elements to settings window settingsWindow.appendChild(enableCustomRuleCheckbox); settingsWindow.appendChild(enableCustomRuleLabel); settingsWindow.appendChild(document.createElement('br')); settingsWindow.appendChild(enableScanForKeywordsCheckbox); settingsWindow.appendChild(enableScanForKeywordsLabel); // document.body.appendChild(settingsWindow); // Update customRuleEnabled when checkbox is toggled, and save it in localStorage enableCustomRuleCheckbox.addEventListener('change', function() { customRuleEnabled = enableCustomRuleCheckbox.checked; localStorage.setItem('customRuleEnabled', JSON.stringify(customRuleEnabled)); }); // Update scanForKeywordsEnabled when checkbox is toggled, and save it in localStorage enableScanForKeywordsCheckbox.addEventListener('change', function() { scanForKeywordsEnabled = enableScanForKeywordsCheckbox.checked; localStorage.setItem('scanForKeywordsEnabled', JSON.stringify(scanForKeywordsEnabled)); }); // Function to get the correct input element based on the mode // Function to get the correct input element based on the mode // Function to get the correct input element based on the mode function getInputElement() { return document.querySelector('[data-slate-editor="true"]') || document.querySelector('textarea[class*="textArea_"]'); } // Add event listener to handle Enter key behavior window.addEventListener('keydown', function(event) { const inputElement = getInputElement(); if (event.key === 'Enter' && !event.shiftKey && !enterKeyDisabled) { if (inputElement && inputElement.nodeName === 'TEXTAREA') { // Mobile version: Only allow line break return; } event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); console.log('Enter key disabled'); enterKeyDisabled = true; // Execute main handler for Enter key handleEnterKey(); enterKeyDisabled = false; } }, true); // Use capture mode to intercept the event before Discord's handlers // Add event listener to the send button to execute handleEnterKey when clicked window.addEventListener('click', function(event) { const sendButton = document.querySelector('button[aria-label="Nachricht senden"]'); if (sendButton && sendButton.contains(event.target)) { // Execute main handler for Enter key handleEnterKey(); } }, true); // Main function that handles Enter key behavior function handleEnterKey() { let inputElement = getInputElement(); if (inputElement) { inputElement.focus(); setCursorToEnd(inputElement); if (customRuleEnabled) { applyCustomRule(inputElement); } setCursorToEnd(inputElement); if (scanForKeywordsEnabled) { scanForKeywords(inputElement); } getRandomEntry(inputElement); sendMessage(inputElement); anotherCustomFunction(); } } // Function to apply custom rules for the input field function applyCustomRule(inputElement) { const customRule = unsafeWindow.customRuleLogic ? unsafeWindow.customRuleLogic.getCurrentText() : ''; if (inputElement.nodeName === 'TEXTAREA') { // For mobile version where input is <textarea> const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; nativeInputValueSetter.call(inputElement, inputElement.value + customRule); const inputEvent = new Event('input', { bubbles: true, cancelable: true, }); inputElement.dispatchEvent(inputEvent); } else { // For desktop version where input is a Slate editor const inputEvent = new InputEvent('beforeinput', { bubbles: true, cancelable: true, inputType: 'insertText', data: customRule, }); inputElement.dispatchEvent(inputEvent); } } // Function to set the cursor position to the end after inserting the text function setCursorToEnd(inputElement) { if (inputElement.nodeName === 'TEXTAREA') { // For mobile version where input is <textarea> inputElement.selectionStart = inputElement.selectionEnd = inputElement.value.length; } else { // For desktop version where input is a Slate editor inputElement.focus(); // Simulate repeated Ctrl + ArrowRight key press events to move cursor to the end const repeatPresses = 150; // Number of times to simulate Ctrl + ArrowRight for (let i = 0; i < repeatPresses; i++) { const ctrlArrowRightEvent = new KeyboardEvent('keydown', { key: 'ArrowRight', code: 'ArrowRight', keyCode: 39, // ArrowRight key code charCode: 0, which: 39, bubbles: true, cancelable: true, ctrlKey: true // Set Ctrl key to true }); inputElement.dispatchEvent(ctrlArrowRightEvent); } } } // Function to send the message (either click send button or simulate Enter key) function sendMessage(inputElement) { if (inputElement.nodeName === 'TEXTAREA') { // Mobile version: Do not send message, just return to allow linebreak return; } let sendButton = document.querySelector('button[aria-label="Nachricht senden"]'); if (sendButton) { sendButton.click(); console.log('Send button clicked to send message'); } else { // For desktop version, simulate pressing Enter to send the message let enterEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true }); inputElement.dispatchEvent(enterEvent); console.log('Enter key simulated to send message'); } } // Example of adding another function function anotherCustomFunction() { console.log('Another custom function executed'); } // Function to scan for keywords and access local storage function scanForKeywords(inputElement) { const currentProfile = getCurrentProfile(); if (currentProfile) { // Retrieve all messages before iterating through storage keys const messageItems = document.querySelectorAll('div[class*="messageContent_"]'); let relevantMessages = Array.from(messageItems).slice(-15); // Last 15 messages const lastMessage = Array.from(messageItems).slice(-1); // Last message only // Iterate over the last 15 messages to extract hidden bracket content relevantMessages = relevantMessages.map(msg => { // Retrieve all span elements within the message const spans = msg.querySelectorAll('span'); // Filter out the spans based on both style conditions: opacity and position const hiddenSpans = Array.from(spans).filter(span => { const style = window.getComputedStyle(span); return style.opacity === '0' && style.position === 'absolute'; }); // Join the text content of all matching spans const bracketContent = hiddenSpans.map(span => span.textContent).join(''); // Extract content within square brackets, if any const match = bracketContent.match(/\[(.*?)\]/); return match ? match[1] : null; }).filter(content => content !== null); // Log the filtered messages for debugging purposes console.log("Filtered Relevant Messages (content in brackets, last 15):", relevantMessages); console.log("Last Message:", lastMessage.map(msg => msg.textContent)); // Track how many entries have been appended let appendedCount = 0; const maxAppends = 3; // Check if the last message contains a specific link pattern const mp3LinkPattern = /https:\/\/files\.shapes\.inc\/.*\.mp3/; let mp3LinkValue = null; if (lastMessage.length > 0) { const lastMessageText = lastMessage[0].textContent; const mp3LinkMatch = lastMessageText.match(mp3LinkPattern); if (mp3LinkMatch) { const mp3LinkKey = mp3LinkMatch[0]; mp3LinkValue = localStorage.getItem(mp3LinkKey); console.log(`MP3 Link detected: ${mp3LinkKey}. Retrieved value: ${mp3LinkValue}`); } } // Create an array to hold all entry keys that need to be checked let allEntryKeys = []; // Iterate through all localStorage keys that match the profile-lorebook prefix Object.keys(localStorage).forEach(storageKey => { if (storageKey.startsWith(`${currentProfile}-lorebook:`)) { const entryKeys = storageKey.replace(`${currentProfile}-lorebook:`, '').split(','); const entryValue = localStorage.getItem(storageKey); // Log the entry keys for debugging purposes console.log(`Entry Keys: `, entryKeys); entryKeys.forEach(entryKey => { allEntryKeys.push({ entryKey, entryValue }); }); } }); // If mp3LinkValue is present, parse it for keywords as well if (mp3LinkValue) { console.log(`Scanning MP3 link value for keywords: ${mp3LinkValue}`); const mp3Keywords = mp3LinkValue.split(','); mp3Keywords.forEach(keyword => { const trimmedKeyword = keyword.trim(); console.log(`Adding keyword from MP3 value: ${trimmedKeyword}`); // Add mp3 keywords but set entryValue to an empty string instead of null allEntryKeys.push({ entryKey: trimmedKeyword, entryValue: '' }); }); } // Iterate over all collected entry keys and perform the checks allEntryKeys.forEach(({ entryKey, entryValue }) => { if (appendedCount >= maxAppends) return; // Stop if max appends reached // Log each keyword being checked console.log(`Checking keyword: ${entryKey}`); // Check input element text for complete word match of keyword (case-insensitive) const inputText = inputElement.value || inputElement.textContent; // Combine check for keyword in input, in the last message, or in the mp3 link value const isKeywordInInput = inputText && new RegExp(`\\b${entryKey}\\b`, 'i').test(inputText); const isKeywordInLastMessage = lastMessage.some(message => { const lastMessageText = message.textContent; return new RegExp(`\\b${entryKey}\\b`, 'i').test(lastMessageText); }); const isKeywordInMp3LinkValue = mp3LinkValue && mp3LinkValue.includes(entryKey); console.log(`Keyword '${entryKey}' in input: ${isKeywordInInput}, in last message: ${isKeywordInLastMessage}, in MP3 value: ${isKeywordInMp3LinkValue}`); if ((isKeywordInInput || isKeywordInLastMessage || isKeywordInMp3LinkValue) && entryValue) { const keywordAlreadyUsed = relevantMessages.some(bracketContent => { return new RegExp(`\\b${entryKey}\\b`, 'i').test(bracketContent); }); if (!keywordAlreadyUsed) { // Append the entryValue to the input element only if entryValue is not null or empty if (inputElement.nodeName === 'TEXTAREA') { const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; nativeInputValueSetter.call(inputElement, inputElement.value + '\n' + entryValue); const inputEvent = new Event('input', { bubbles: true, cancelable: true, }); inputElement.dispatchEvent(inputEvent); } else { const inputEvent = new InputEvent('beforeinput', { bubbles: true, cancelable: true, inputType: 'insertText', data: '\n' + entryValue, }); inputElement.dispatchEvent(inputEvent); } appendedCount++; // Increment the count console.log(`Keyword '${entryKey}' detected. Appended lorebook entry to the input.`); } else { console.log(`Keyword '${entryKey}' already found in recent bracketed messages or entryValue is null/empty. Skipping append.`); } } }); // Log the total number of entries appended console.log(`Total lorebook entries appended: ${appendedCount}`); } } // Function to get the current profile from local storage function getCurrentProfile() { return localStorage.getItem('currentProfile'); } function getRandomEntry(inputElement) { const selectedProfile = localStorage.getItem('events.selectedProfile'); if (selectedProfile) { let profileEntries = []; const currentHour = new Date().getHours(); for (let key in localStorage) { if (key.startsWith(`events.${selectedProfile}:`)) { const entryData = JSON.parse(localStorage.getItem(key)); const [startHour, endHour] = entryData.timeRange.split('-').map(Number); // Check if current hour is within the time range if ( (startHour <= endHour && currentHour >= startHour && currentHour < endHour) || // Normal range (startHour > endHour && (currentHour >= startHour || currentHour < endHour)) // Crosses midnight ) { profileEntries.push({ key, ...entryData }); } } } if (profileEntries.length > 0) { const probability = parseInt(localStorage.getItem('events.probability') || '100', 10); let selectedEntry = null; while (profileEntries.length > 0) { // Randomly select an entry from the available entries const randomIndex = Math.floor(Math.random() * profileEntries.length); const randomEntry = profileEntries[randomIndex]; // Check if the entry passes the individual probability check if (Math.random() * 100 < randomEntry.probability) { selectedEntry = randomEntry; break; } else { // Remove the entry from the list if it fails the probability check profileEntries.splice(randomIndex, 1); } } if (selectedEntry && Math.random() * 100 < probability) { console.log(`Random Entry Selected: ${selectedEntry.value}`); appendToInput(inputElement, selectedEntry.value); // Append the random entry to the input element } } else { console.log('No entries available for the selected profile in the current time range.'); } } else { console.log('No profile selected. Please select a profile to retrieve a random entry.'); } } // Helper function to append text to the input element function appendToInput(inputElement, text) { const lineBreak = '\n'; if (!inputElement) { console.error('Input element not found.'); return; } if (inputElement.nodeName === 'TEXTAREA') { // Mobile: Append text to <textarea> const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; nativeInputValueSetter.call(inputElement, inputElement.value + `${lineBreak}${text}`); const inputEvent = new Event('input', { bubbles: true, cancelable: true, }); inputElement.dispatchEvent(inputEvent); } else if (inputElement.hasAttribute('data-slate-editor')) { // Desktop: Append text for Slate editor const inputEvent = new InputEvent('beforeinput', { bubbles: true, cancelable: true, inputType: 'insertText', data: `${lineBreak}${text}`, }); inputElement.dispatchEvent(inputEvent); } else { console.error('Unsupported input element type.'); } } // Expose the function to be accessible from other scripts unsafeWindow.getRandomEntry = getRandomEntry; } })();