- // ==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;
-
-
- }
- })();