您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Various Functions for Shapes.inc for Discord
当前为
// ==UserScript== // @name Discord/Shapes Tools // @namespace https://vishanka.com // @version 1.4 // @description Various Functions for Shapes.inc for Discord // @author Vishanka // @match https://discord.com/channels/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @license Proprietary // @run-at document-idle // ==/UserScript== (function() { 'use strict'; let notifierActive = false; // ================================================================== LOREBOOK ================================================================== function LorebookScript(){ // Create and add the arrow button to open the storage panel const arrowButton = document.createElement('div'); arrowButton.innerHTML = '〈'; arrowButton.style.position = 'fixed'; arrowButton.style.bottom = '50%'; arrowButton.style.right = '0'; arrowButton.style.padding = '10px'; arrowButton.style.fontSize = '24px'; arrowButton.style.zIndex = '1000'; arrowButton.style.cursor = 'pointer'; arrowButton.style.color = '#B4B4B4'; arrowButton.style.borderRadius = '5px 0 0 5px'; arrowButton.style.transition = 'transform 0.3s ease, right 0.3s ease, background-color 0.1s'; // Toggle panel visibility arrowButton.addEventListener('click', () => { if (DCstoragePanel.style.right === '-250px') { DCstoragePanel.style.right = '0'; arrowButton.style.right = '250px'; arrowButton.style.transform = 'rotate(180deg)'; } else { DCstoragePanel.style.right = '-250px'; arrowButton.style.right = '0'; arrowButton.style.transform = 'rotate(0deg)'; } }); // Create the fancy sliding panel window.DCstoragePanel = document.createElement('div'); DCstoragePanel.style.position = 'fixed'; DCstoragePanel.style.top = '0'; DCstoragePanel.style.right = '-250px'; // Initially hidden DCstoragePanel.style.height = '100%'; DCstoragePanel.style.width = '250px'; DCstoragePanel.style.backgroundColor = '#171717'; DCstoragePanel.style.transition = 'right 0.3s ease'; DCstoragePanel.style.zIndex = '999'; // Create the header above the button const storagePanelHeader = document.createElement('div'); storagePanelHeader.innerText = 'Shapes Tools'; storagePanelHeader.style.margin = '20px'; storagePanelHeader.style.padding = '10px'; storagePanelHeader.style.fontSize = '19px'; storagePanelHeader.style.fontWeight = '550'; storagePanelHeader.style.color = '#ECECEC'; storagePanelHeader.style.textAlign = 'center'; // Create a divider line const dividerLine = document.createElement('div'); dividerLine.style.height = '1px'; dividerLine.style.backgroundColor = '#212121'; dividerLine.style.margin = '10px 20px'; // Manage Lorebook Button window.openLorebookButton = document.createElement('div'); window.openLorebookButton.innerHTML = ` <button id="toggle-lorebook-panel" style=" display: flex; align-items: center; gap: 8px; position: relative; top: 10px; right: 0px; left: 10px; padding: 7px 15px; background: transparent; color: #b0b0b0; border: none; border-radius: 8px; font-size: 16px; text-align: left; cursor: pointer; width: 90%; transition: background-color 0.1s, color 0.1s; z-index: 1001;"> <svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg" class="mb-[-1px]"> <path d="M6 3C4.89543 3 4 3.89543 4 5V13C4 14.1046 4.89543 15 6 15L6 3Z" fill="currentColor"></path> <path d="M7 3V15H8.18037L8.4899 13.4523C8.54798 13.1619 8.69071 12.8952 8.90012 12.6858L12.2931 9.29289C12.7644 8.82153 13.3822 8.58583 14 8.58578V3.5C14 3.22386 13.7761 3 13.5 3H7Z" fill="currentColor"></path> <path d="M11.3512 15.5297L9.73505 15.8529C9.38519 15.9229 9.07673 15.6144 9.14671 15.2646L9.46993 13.6484C9.48929 13.5517 9.53687 13.4628 9.60667 13.393L12.9996 10C13.5519 9.44771 14.4473 9.44771 14.9996 10C15.5519 10.5523 15.5519 11.4477 14.9996 12L11.6067 15.393C11.5369 15.4628 11.448 15.5103 11.3512 15.5297Z" fill="currentColor"></path> </svg> Manage Lorebook </button> `; const lorebookButtonElement = openLorebookButton.querySelector('button'); lorebookButtonElement.onmouseover = () => { lorebookButtonElement.style.backgroundColor = '#212121'; lorebookButtonElement.style.color = '#ffffff'; }; lorebookButtonElement.onmouseout = () => { lorebookButtonElement.style.backgroundColor = 'transparent'; lorebookButtonElement.style.color = '#b0b0b0'; }; // Create the main panel container const lorebookPanel = document.createElement('div'); lorebookPanel.id = 'lorebookManagerPanel'; lorebookPanel.style.position = 'fixed'; lorebookPanel.style.top = '50%'; lorebookPanel.style.left = '50%'; lorebookPanel.style.transform = 'translate(-50%, -50%)'; // Size different for Mobile and Desktop if (window.innerWidth <= 768) { lorebookPanel.style.width = '90%'; lorebookPanel.style.height = '90%'; } else { lorebookPanel.style.width = '800px'; lorebookPanel.style.height = '700px'; } lorebookPanel.style.backgroundColor = '#2F2F2F'; lorebookPanel.style.borderRadius = '20px'; lorebookPanel.style.padding = '10px'; lorebookPanel.style.display = 'none'; lorebookPanel.style.zIndex = '1000'; // Add close button for the panel const closeButton = document.createElement('button'); closeButton.innerText = '✕'; closeButton.style.position = 'absolute'; closeButton.style.borderRadius = '50%'; closeButton.style.color = '#ffffff'; closeButton.style.top = '20px'; closeButton.style.right = '20px'; closeButton.style.backgroundColor = 'transparent'; closeButton.style.cursor = 'pointer'; closeButton.addEventListener('mouseenter', () => { closeButton.style.backgroundColor = '#676767'; }); closeButton.addEventListener('mouseleave', () => { closeButton.style.backgroundColor = 'transparent'; }); // Hide the Lorebook Panel closeButton.addEventListener('click', () => { lorebookPanel.style.display = 'none'; }); // Open the Lorebook Panel openLorebookButton.addEventListener('click', () => { lorebookPanel.style.display = lorebookPanel.style.display === 'none' ? 'block' : 'none'; loadProfileEntries(); }); // Profiles Title const profileslabel = document.createElement('div'); profileslabel.textContent = 'Profiles'; profileslabel.style.color = '#dddddd'; profileslabel.style.fontSize = '14px'; profileslabel.style.marginBottom = '5px'; profileslabel.style.marginLeft = '3px'; profileslabel.style.marginTop = '5px'; profileslabel.style.fontSize = '20px'; profileslabel.style.fontWeight = '550'; // Profile Management Panel const lorebookProfilePanel = document.createElement('div'); lorebookProfilePanel.id = 'lorebookProfilePanel'; lorebookProfilePanel.style.float = 'left'; lorebookProfilePanel.style.width = '20%'; lorebookProfilePanel.style.borderRight = '0.5px solid #444444'; lorebookProfilePanel.style.height = '93%'; // Create the profile list container const profileList = document.createElement('div'); profileList.id = 'profileList'; profileList.style.height = '95%'; profileList.style.color = 'white'; profileList.style.overflowY = 'auto'; // Add Profiles Button const addProfileButton = document.createElement('button'); addProfileButton.innerText = 'Add Profile'; addProfileButton.style.padding = '8px'; addProfileButton.style.border = '0.2px solid #4E4E4E'; addProfileButton.style.backgroundColor = 'transparent'; addProfileButton.style.color = '#fff'; addProfileButton.style.borderRadius = '20px'; addProfileButton.style.width = '90%'; addProfileButton.style.cursor = 'pointer'; // Mouseover Effect for Add Profiles Button addProfileButton.onmouseover = () => { addProfileButton.style.backgroundColor = '#424242'; }; addProfileButton.onmouseout = () => { addProfileButton.style.backgroundColor = 'transparent'; }; addProfileButton.addEventListener('click', () => { const profileName = prompt('Enter profile name:'); if (profileName) { const profileKey = `lorebook.profile:${profileName}`; // Create a list of keys from localStorage that match the prefix 'lorebook.profile:' const existingKeys = Object.keys(localStorage) .filter(key => key.startsWith('lorebook.profile:')) .map(key => key.toLowerCase()); // Convert all keys to lowercase for case-insensitive check if (!existingKeys.includes(profileKey.toLowerCase())) { localStorage.setItem(profileKey, JSON.stringify({})); loadProfiles(); } else { alert('Profile already exists.'); } } }); // Profile Selection Functionality function loadProfiles() { profileList.innerHTML = ''; Object.keys(localStorage).forEach(profileKey => { if (profileKey.startsWith('lorebook.profile:')) { const profileName = profileKey.replace('lorebook.profile:', ''); const profileItem = document.createElement('div'); profileItem.innerText = profileName; profileItem.style.padding = '5px'; profileItem.style.marginBottom = '5px'; profileItem.style.cursor = 'pointer'; profileItem.style.backgroundColor = profileName === getCurrentProfile() ? '#424242' : '#2F2F2F'; profileItem.style.borderRadius = '5px'; profileItem.style.width = '90%'; profileItem.style.position = 'relative'; profileItem.addEventListener('click', () => { setCurrentProfile(profileName); loadProfiles(); loadProfileEntries(); }); const removeButton = document.createElement('button'); removeButton.innerText = '✕'; removeButton.style.position = 'absolute'; removeButton.style.top = '3px'; removeButton.style.right = '10px'; removeButton.style.cursor = 'pointer'; removeButton.style.backgroundColor = 'transparent'; removeButton.style.color = 'white'; removeButton.addEventListener('click', (e) => { e.stopPropagation(); localStorage.removeItem(profileKey); if (profileName === getCurrentProfile()) { setCurrentProfile(null); } loadProfiles(); loadProfileEntries(); }); profileItem.appendChild(removeButton); profileList.appendChild(profileItem); } }); } // Lorebook Entries Title const lorebookEntriesTitle = document.createElement('h3'); lorebookEntriesTitle.innerText = 'Manage Lorebook Entries'; lorebookEntriesTitle.style.fontWeight = 'normal'; lorebookEntriesTitle.style.color = '#ffffff'; lorebookEntriesTitle.style.textAlign = 'left'; lorebookEntriesTitle.style.fontSize = '24px'; lorebookEntriesTitle.style.marginTop = '20px'; lorebookEntriesTitle.style.position = 'relative'; lorebookEntriesTitle.style.marginLeft = '23%'; lorebookEntriesTitle.style.marginBottom = '15px'; lorebookEntriesTitle.style.fontWeight = '550'; // Profile Entries List const profileEntriesList = document.createElement('div'); profileEntriesList.id = 'profileEntriesList'; profileEntriesList.style.marginTop = '20px'; profileEntriesList.style.height = '48%'; // Check if the device is mobile if (window.innerWidth <= 768) { profileEntriesList.style.height = '30%'; } else { profileEntriesList.style.height = '48%'; } profileEntriesList.style.overflowY = 'auto'; // Header for Inputs const entrieslabel = document.createElement('div'); entrieslabel.textContent = 'Enter keys and description:'; entrieslabel.style.color = '#dddddd'; entrieslabel.style.fontSize = '14px'; entrieslabel.style.marginBottom = '5px'; entrieslabel.style.marginTop = '5px'; entrieslabel.style.marginLeft = '23%'; // Create key-value input fields const inputContainer = document.createElement('div'); inputContainer.id = 'inputContainer'; inputContainer.style.marginTop = '10px'; inputContainer.style.display = 'flex'; inputContainer.style.flexDirection = 'column'; inputContainer.style.alignItems = 'center'; inputContainer.style.margin = '0 auto'; const lorebookKeyInput = document.createElement('input'); lorebookKeyInput.type = 'text'; lorebookKeyInput.placeholder = 'Entry Keywords (comma-separated)'; lorebookKeyInput.style.width = '90%'; lorebookKeyInput.style.marginBottom = '5px'; lorebookKeyInput.style.padding = '10px'; lorebookKeyInput.style.border = '1px solid #444444'; lorebookKeyInput.style.borderRadius = '8px'; lorebookKeyInput.style.backgroundColor = '#1e1e1e'; lorebookKeyInput.style.color = '#dddddd'; const lorebookValueInput = document.createElement('textarea'); lorebookValueInput.placeholder = ' '; lorebookValueInput.style.width = '90%'; lorebookValueInput.style.marginBottom = '5px'; lorebookValueInput.style.padding = '10px'; lorebookValueInput.style.border = '1px solid #444444'; lorebookValueInput.style.borderRadius = '8px'; lorebookValueInput.style.backgroundColor = '#1e1e1e'; lorebookValueInput.style.color = '#dddddd'; lorebookValueInput.style.height = '100px'; lorebookValueInput.style.resize = 'vertical'; lorebookValueInput.maxLength = 1000; lorebookValueInput.style.overflow = 'auto'; const charCounter = document.createElement('div'); charCounter.style.color = '#dddddd'; charCounter.style.fontSize = '12px'; charCounter.style.marginTop = '0px'; charCounter.style.marginBottom = '15px'; charCounter.style.textAlign = 'right'; charCounter.style.marginRight = '-87%'; charCounter.style.color = 'grey'; charCounter.textContent = `0/${lorebookValueInput.maxLength}`; // Update the counter as the user types lorebookValueInput.addEventListener('input', () => { charCounter.textContent = `${lorebookValueInput.value.length}/${lorebookValueInput.maxLength}`; }); // Save Entry button, also important for editing const lorebookSaveButton = document.createElement('button'); lorebookSaveButton.innerText = 'Add Entry'; lorebookSaveButton.style.padding = '10px 20px'; lorebookSaveButton.style.border = '0.2px solid #4E4E4E'; lorebookSaveButton.style.backgroundColor = '#2F2F2F'; lorebookSaveButton.style.color = '#fff'; lorebookSaveButton.style.borderRadius = '50px'; lorebookSaveButton.style.cursor = 'pointer'; lorebookSaveButton.style.width = '95%'; // Append all Elements document.body.appendChild(arrowButton); document.body.appendChild(DCstoragePanel); DCstoragePanel.appendChild(storagePanelHeader); DCstoragePanel.appendChild(dividerLine); document.body.appendChild(lorebookPanel); lorebookPanel.appendChild(closeButton); lorebookPanel.appendChild(profileslabel); lorebookPanel.appendChild(lorebookProfilePanel); lorebookProfilePanel.appendChild(profileList); lorebookProfilePanel.appendChild(addProfileButton); lorebookPanel.appendChild(lorebookEntriesTitle); lorebookPanel.appendChild(profileEntriesList); lorebookPanel.appendChild(entrieslabel); lorebookPanel.appendChild(inputContainer); inputContainer.appendChild(lorebookKeyInput); inputContainer.appendChild(lorebookValueInput); inputContainer.appendChild(charCounter); inputContainer.appendChild(lorebookSaveButton); let isEditing = false; let editingKey = ''; lorebookSaveButton.addEventListener('click', () => { const key = lorebookKeyInput.value.trim().toLowerCase(); // Ensure key is always saved as lowercase const value = lorebookValueInput.value; const currentProfile = getCurrentProfile(); if (key && currentProfile) { const profileKey = `${currentProfile}.lorebook:${key}`; const formattedValue = `<[Lorebook: ${key}] ${value}>`; // Check for duplicate keys (case-insensitive) and prevent keys that partially match an existing key const isDuplicateKey = Object.keys(localStorage).some(storageKey => { const normalizedStorageKey = storageKey.toLowerCase(); const normalizedCurrentProfile = currentProfile.toLowerCase(); const currentKey = normalizedStorageKey.replace(`${normalizedCurrentProfile}.lorebook:`.toLowerCase(), ''); return ( (currentKey === key || currentKey.split(',').includes(key)) && (!isEditing || editingKey.toLowerCase() !== currentKey) ); }); // Enforce uniqueness for edits as well if (isDuplicateKey) { alert('The key is already used in an existing entry (case-insensitive). Please use a different key.'); return; } // Remove the old key if editing and key has changed if (isEditing && editingKey.toLowerCase() !== key) { const oldProfileKey = `${currentProfile}.lorebook:${editingKey.toLowerCase()}`; localStorage.removeItem(oldProfileKey); } localStorage.setItem(profileKey, formattedValue); lorebookKeyInput.value = ''; lorebookValueInput.value = ''; isEditing = false; editingKey = ''; loadProfileEntries(); } else { alert('Please select a profile and enter a key.'); } }); function loadProfileEntries() { profileEntriesList.innerHTML = ''; profileEntriesList.style.display = 'flex'; profileEntriesList.style.flexDirection = 'column'; profileEntriesList.style.alignItems = 'center'; profileEntriesList.style.margin = '0 auto'; const currentProfile = getCurrentProfile(); if (currentProfile) { Object.keys(localStorage).forEach(storageKey => { // Normalize both the profile and the storage key for case-insensitive comparison if (storageKey.toLowerCase().startsWith(`${currentProfile.toLowerCase()}.lorebook:`)) { const entryKey = storageKey.replace(new RegExp(`^${currentProfile}\.lorebook:`, 'i'), ''); const entryValue = localStorage.getItem(storageKey); const displayedValue = entryValue.replace(/^<\[Lorebook:.*?\]\s*/, '').replace(/>$/, ''); const entryItem = document.createElement('div'); entryItem.style.padding = '10px'; entryItem.style.marginBottom = '12px'; entryItem.style.borderRadius = '8px'; entryItem.style.backgroundColor = '#424242'; entryItem.style.position = 'relative'; entryItem.style.color = 'white'; entryItem.style.flexDirection = 'column'; entryItem.style.width = '90%'; const keyElement = document.createElement('div'); keyElement.innerText = entryKey; keyElement.style.fontWeight = 'bold'; keyElement.style.marginBottom = '10px'; entryItem.appendChild(keyElement); const valueElement = document.createElement('div'); valueElement.innerText = displayedValue; entryItem.appendChild(valueElement); entryItem.addEventListener('click', () => { lorebookKeyInput.value = entryKey; lorebookValueInput.value = entryValue.replace(/^<\[Lorebook:.*?\]\s*/, '').replace(/>$/, ''); isEditing = true; editingKey = entryKey; }); const removeButton = document.createElement('button'); removeButton.innerText = '✕'; removeButton.style.position = 'absolute'; removeButton.style.top = '10px'; removeButton.style.right = '10px'; removeButton.style.cursor = 'pointer'; removeButton.style.backgroundColor = 'transparent'; removeButton.style.color = 'white'; removeButton.addEventListener('click', (event) => { event.stopPropagation(); localStorage.removeItem(storageKey); loadProfileEntries(); }); entryItem.appendChild(removeButton); profileEntriesList.appendChild(entryItem); } }); } } // Utility functions to manage profiles and local storage function getCurrentProfile() { // Return the current profile name in its original case, but convert to lower case for comparisons const selectedProfile = localStorage.getItem('selectedProfile.lorebook'); return selectedProfile ? selectedProfile : null; } function setCurrentProfile(profileName) { localStorage.setItem('selectedProfile.lorebook', profileName); } loadProfiles(); } // ================================================================ IMPORT/EXPORT =============================================================== function ImportExportScript() { // Create buttons to trigger export and import window.exportButton = document.createElement('div'); exportButton.innerHTML = ` <button id="toggle-export-button" style=" position: relative; top: 10px; right: 0px; left: 10px; padding: 7px 15px; background: transparent; color: #b0b0b0; border: none; border-radius: 8px; font-size: 16px; text-align: left; cursor: pointer; width: 90%; transition: background-color 0.1s, color 0.1s; z-index: 1001; display: flex; align-items: center;"> <svg width="20px" height="20px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" style="margin-right: 8px;"> <path d="M12 5L11.2929 4.29289L12 3.58579L12.7071 4.29289L12 5ZM13 14C13 14.5523 12.5523 15 12 15C11.4477 15 11 14.5523 11 14L13 14ZM6.29289 9.29289L11.2929 4.29289L12.7071 5.70711L7.70711 10.7071L6.29289 9.29289ZM12.7071 4.29289L17.7071 9.29289L16.2929 10.7071L11.2929 5.70711L12.7071 4.29289ZM13 5L13 14L11 14L11 5L13 5Z" fill="#B0B0B0"></path> <path d="M5 16L5 17C5 18.1046 5.89543 19 7 19L17 19C18.1046 19 19 18.1046 19 17V16" stroke="#B0B0B0" stroke-width="2"></path> </svg> Export Data </button> `; window.exportButton.onmouseover = () => { exportButton.querySelector('button').style.backgroundColor = '#212121'; exportButton.querySelector('button').style.color = '#ffffff'; }; exportButton.onmouseout = () => { exportButton.querySelector('button').style.backgroundColor = 'transparent'; exportButton.querySelector('button').style.color = '#b0b0b0'; }; //DCstoragePanel.appendChild(exportButton); window.importButton = document.createElement('div'); importButton.innerHTML = ` <button id="toggle-import-button" style=" position: relative; top: 10px; right: 0px; left: 10px; padding: 7px 15px; background: transparent; color: #b0b0b0; border: none; border-radius: 8px; font-size: 16px; text-align: left; cursor: pointer; width: 90%; transition: background-color 0.1s, color 0.1s; z-index: 1001; display: flex; align-items: center; gap: 8px;"> <svg width="20px" height="20px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <path d="M12 14L11.2929 14.7071L12 15.4142L12.7071 14.7071L12 14ZM13 5C13 4.44772 12.5523 4 12 4C11.4477 4 11 4.44771 11 5L13 5ZM6.29289 9.70711L11.2929 14.7071L12.7071 13.2929L7.70711 8.29289L6.29289 9.70711ZM12.7071 14.7071L17.7071 9.70711L16.2929 8.29289L11.2929 13.2929L12.7071 14.7071ZM13 14L13 5L11 5L11 14L13 14Z" fill="#B0B0B0"></path> <path d="M5 16L5 17C5 18.1046 5.89543 19 7 19L17 19C18.1046 19 19 18.1046 19 17V16" stroke="#B0B0B0" stroke-width="2"></path> </svg> Import Data </button> `; importButton.onmouseover = () => { importButton.querySelector('button').style.backgroundColor = '#212121'; importButton.querySelector('button').style.color = '#ffffff'; }; importButton.onmouseout = () => { importButton.querySelector('button').style.backgroundColor = 'transparent'; importButton.querySelector('button').style.color = '#b0b0b0'; }; //DCstoragePanel.appendChild(importButton); // Export specific localStorage entries exportButton.addEventListener('click', () => { const filteredData = {}; for (const key in localStorage) { if (localStorage.hasOwnProperty(key)) { if (key.startsWith('events') || key.includes('lorebook') || key.includes('rules') || key.includes('notifier')) { filteredData[key] = localStorage.getItem(key); } } } const data = JSON.stringify(filteredData); const blob = new Blob([data], {type: 'application/json'}); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'localStorage_filtered.json'; document.body.appendChild(a); a.click(); document.body.removeChild(a); }); // Import localStorage importButton.addEventListener('click', () => { const input = document.createElement('input'); input.type = 'file'; input.accept = 'application/json'; input.addEventListener('change', (event) => { const file = event.target.files[0]; if (!file) { return; } const reader = new FileReader(); reader.onload = (e) => { try { const importedData = JSON.parse(e.target.result); for (const key in importedData) { localStorage.setItem(key, importedData[key]); } alert('localStorage has been successfully imported.'); } catch (err) { alert('Failed to import localStorage: ' + err.message); } }; reader.readAsText(file); }); input.click(); }); } // =================================================================== EVENTS =================================================================== function EventsScript() { window.eventsButton = document.createElement('div'); window.eventsButton.innerHTML = ` <button id="toggle-events-panel" style=" position: relative; top: 10px; right: 0px; left: 10px; padding: 7px 15px; display: flex; align-items: center; text-align: left; background: transparent; color: #b0b0b0; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; width: 90%; transition: background-color 0.1s, color 0.1s; z-index: 1001;"> <svg fill="#B0B0B0" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24" xml:space="preserve" width="20px" height="20px" style="padding-right: 5px; margin-left: 1px;"> <g id="SVGRepo_bgCarrier" stroke-width="0"></g> <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g> <g id="SVGRepo_iconCarrier"> <path d="M5,2v2H4C2.9,4,2,4.9,2,6v11c0,1.1,0.9,2,2,2h6.8c1.8-1.8,0,0,2-2H4V8h12v5.9c1.6-1.6,0.2-0.2,2-2V6c0-1.1-0.9-2-2-2h-1V2 h-2v2H7V2H5z M10,9.2l-0.8,2L7,11.4l1.6,1.4l-0.5,2.1l1.8-1.1l1.8,1.1l-0.5-2.1l1.6-1.4l-2.2-0.2L10,9.2z M20.5,12 c-0.1,0-0.3,0.1-0.4,0.2L19.3,13l2,2l0.8-0.8c0.2-0.2,0.2-0.6,0-0.7l-1.3-1.3C20.8,12,20.6,12,20.5,12z M18.8,13.5L12.3,20v2h2 l6.5-6.5L18.8,13.5"></path> </g> </svg> Manage Events </button> `; let manageEventsButton = window.eventsButton.querySelector('#toggle-events-panel'); manageEventsButton.onclick = openProfilePanel; const eventsbuttonElement = eventsButton.querySelector('button'); eventsbuttonElement.onmouseover = () => { eventsbuttonElement.style.backgroundColor = '#212121'; eventsbuttonElement.style.color = '#ffffff'; }; eventsbuttonElement.onmouseout = () => { eventsbuttonElement.style.backgroundColor = 'transparent'; eventsbuttonElement.style.color = '#b0b0b0'; }; function openProfilePanel() { if (document.querySelector('#eventsProfilePanel')) { return; } // Create profile management panel const eventsProfilePanel = document.createElement('div'); eventsProfilePanel.id = 'eventsProfilePanel'; eventsProfilePanel.style.position = 'fixed'; eventsProfilePanel.style.top = '50%'; eventsProfilePanel.style.left = '50%'; eventsProfilePanel.style.transform = 'translate(-50%, -50%)'; // Size different for Mobile and Desktop if (window.innerWidth <= 768) { eventsProfilePanel.style.width = '90%'; eventsProfilePanel.style.height = '90%'; } else { eventsProfilePanel.style.width = '800px'; eventsProfilePanel.style.height = '700px'; } //eventsProfilePanel.style.width = '800px'; //eventsProfilePanel.style.height = '700px'; eventsProfilePanel.style.backgroundColor = '#2F2F2F'; eventsProfilePanel.style.color = 'white'; eventsProfilePanel.style.borderRadius = '20px'; eventsProfilePanel.style.padding = '20px'; eventsProfilePanel.style.zIndex = '1000'; eventsProfilePanel.style.display = 'flex'; eventsProfilePanel.style.flexDirection = 'row'; // Create close button for profilePanel const closeButton = document.createElement('button'); closeButton.style.position = 'absolute'; closeButton.style.top = '15px'; closeButton.style.right = '15px'; closeButton.style.width = '30px'; closeButton.style.height = '30px'; closeButton.style.backgroundColor = 'transparent'; closeButton.style.color = '#ffffff'; closeButton.style.border = 'none'; closeButton.style.borderRadius = '50%'; closeButton.style.cursor = 'pointer'; closeButton.style.display = 'flex'; closeButton.style.alignItems = 'center'; closeButton.style.zIndex = '1001'; closeButton.style.justifyContent = 'center'; closeButton.style.transition = 'background-color 0.2s ease'; closeButton.style.boxSizing = 'border-box'; // Create span for the '✕' character const closeIcon = document.createElement('span'); closeIcon.innerText = '✕'; closeIcon.style.fontSize = '16px'; closeIcon.style.position = 'relative'; closeIcon.style.top = '-1px'; // Hover effect closeButton.addEventListener('mouseenter', () => { closeButton.style.backgroundColor = '#676767'; }); closeButton.addEventListener('mouseleave', () => { closeButton.style.backgroundColor = 'transparent'; }); // Close button action closeButton.onclick = function() { document.body.removeChild(eventsProfilePanel); }; // Create profile list container const profileListContainer = document.createElement('div'); profileListContainer.style.flex = '0.50'; profileListContainer.style.marginRight = '20px'; profileListContainer.style.paddingRight = '20px'; profileListContainer.style.display = 'flex'; profileListContainer.style.flexDirection = 'column'; profileListContainer.style.borderRight = '1px solid #444444'; profileListContainer.style.overflowY = 'auto'; profileListContainer.style.maxHeight = '660px'; // Create header for profile list const profileListHeader = document.createElement('h4'); profileListHeader.innerText = 'Profiles'; profileListHeader.style.marginBottom = '10px'; // Create profile list const profileList = document.createElement('ul'); profileList.style.overflowY = 'auto'; profileList.style.height = '600px'; // Create button to add profile const addProfileButton = document.createElement('button'); addProfileButton.innerText = 'Add Profile'; addProfileButton.style.padding = '8px'; addProfileButton.style.border = '0.2px solid #4E4E4E'; addProfileButton.style.backgroundColor = 'transparent'; addProfileButton.style.color = '#fff'; addProfileButton.style.borderRadius = '20px'; addProfileButton.style.cursor = 'pointer'; addProfileButton.onmouseover = () => { addProfileButton.style.backgroundColor = '#424242'; }; addProfileButton.onmouseout = () => { addProfileButton.style.backgroundColor = 'transparent'; }; addProfileButton.onclick = function() { openAddProfileDialog(); }; // Create key-value input container const keyValueContainer = document.createElement('div'); keyValueContainer.style.flex = '2.3'; keyValueContainer.style.display = 'flex'; keyValueContainer.style.flexDirection = 'column'; keyValueContainer.style.gap = '10px'; // Create entries list const entriesList = document.createElement('div'); entriesList.style.overflowY = 'auto'; entriesList.style.height = '340px'; entriesList.style.width = '100%'; entriesList.style.paddingRight = '20px'; entriesList.style.borderCollapse = 'collapse'; entriesList.style.display = 'block'; entriesList.style.overflowY = 'auto'; // Create a header above the headerRow const manageEventsHeader = document.createElement('h2'); manageEventsHeader.innerText = 'Manage Event List'; manageEventsHeader.style.marginBottom = '-10px'; closeButton.appendChild(closeIcon); eventsProfilePanel.appendChild(closeButton); profileListContainer.appendChild(profileListHeader); profileListContainer.appendChild(profileList); profileListContainer.appendChild(addProfileButton); keyValueContainer.appendChild(manageEventsHeader); // Create table header const headerRow = document.createElement('tr'); const headers = ['Key', 'Value', '%', 'Time', '']; headers.forEach(headerText => { const header = document.createElement('th'); header.innerText = headerText; header.style.padding = '5px'; header.style.textAlign = 'left'; if (headerText === 'Value') { header.style.width = '60%'; } else if (headerText === 'Key') { header.style.width = '15%'; } else if (headerText === '%' || headerText === 'Time') { header.style.width = '10%'; } else if (headerText === '') { header.style.width = '5%'; } headerRow.appendChild(header); }); const headerContainer = document.createElement('div'); headerContainer.style.position = 'sticky'; headerContainer.style.top = '0'; headerContainer.style.backgroundColor = '#2F2F2F'; headerContainer.style.zIndex = '1'; headerContainer.appendChild(headerRow); entriesList.appendChild(headerContainer); entriesList.style.position = 'sticky'; entriesList.style.top = '0'; entriesList.style.backgroundColor = '#2F2F2F'; // Create a separate header for the entries list const entriesHeaderContainer = document.createElement('div'); entriesHeaderContainer.style.position = 'sticky'; entriesHeaderContainer.style.top = '0'; entriesHeaderContainer.style.backgroundColor = '#2F2F2F'; entriesHeaderContainer.style.zIndex = '1'; const entriesHeader = document.createElement('div'); entriesHeader.style.display = 'flex'; entriesHeader.style.padding = '5px 0'; entriesHeader.style.borderBottom = '1px solid #444444'; headers.forEach(headerText => { const header = document.createElement('div'); header.innerText = headerText; header.style.padding = '5px'; header.style.textAlign = 'left'; // Align headers to the left if (headerText === 'Value') { header.style.width = '57%'; } else if (headerText === 'Key') { header.style.width = '15%'; } else if (headerText === '%' || headerText === 'Time') { header.style.width = '7%'; } else if (headerText === '') { header.style.width = '5%'; } entriesHeader.appendChild(header); }); entriesHeaderContainer.appendChild(entriesHeader); keyValueContainer.appendChild(entriesHeaderContainer); keyValueContainer.appendChild(entriesList); // Create container for probability and time range inputs const probTimeContainer = document.createElement('div'); probTimeContainer.style.display = 'flex'; probTimeContainer.style.gap = '10px'; probTimeContainer.style.marginBottom = '-4px'; // Create probability label and input const probabilityContainer = document.createElement('div'); probabilityContainer.style.display = 'flex'; probabilityContainer.style.flexDirection = 'column'; probabilityContainer.style.width = '30%'; // Adjust width to fit the row better const probabilityLabel = document.createElement('div'); probabilityLabel.innerText = 'Event Probability'; probabilityLabel.style.color = 'white'; probabilityLabel.style.marginBottom = '0px'; probabilityContainer.appendChild(probabilityLabel); const probabilityInputContainer = document.createElement('div'); probabilityInputContainer.style.display = 'flex'; probabilityInputContainer.style.alignItems = 'center'; const probabilityInput = document.createElement('input'); probabilityInput.type = 'number'; probabilityInput.placeholder = '0-100'; probabilityInput.style.backgroundColor = '#1E1E1E'; probabilityInput.style.color = 'white'; probabilityInput.style.border = '1px solid #444444'; probabilityInput.style.borderRadius = '5px'; probabilityInput.style.padding = '5px'; probabilityInput.style.width = '85%'; probabilityInput.style.marginRight = '5px'; probabilityInputContainer.appendChild(probabilityInput); const probabilityPercentLabel = document.createElement('span'); probabilityPercentLabel.innerText = '%'; probabilityPercentLabel.style.color = 'white'; probabilityInputContainer.appendChild(probabilityPercentLabel); probabilityContainer.appendChild(probabilityInputContainer); probTimeContainer.appendChild(probabilityContainer); // Create time range label and input const timeRangeContainer = document.createElement('div'); timeRangeContainer.style.display = 'flex'; timeRangeContainer.style.flexDirection = 'column'; timeRangeContainer.style.width = '30%'; // Adjust width to fit the row better const timeRangeLabel = document.createElement('div'); timeRangeLabel.innerText = 'Time Range'; timeRangeLabel.style.color = 'white'; timeRangeLabel.style.marginBottom = '0px'; timeRangeContainer.appendChild(timeRangeLabel); const timeRangeInputContainer = document.createElement('div'); timeRangeInputContainer.style.display = 'flex'; timeRangeInputContainer.style.alignItems = 'center'; const timeRangeInput = document.createElement('input'); timeRangeInput.type = 'text'; timeRangeInput.placeholder = '0-24'; timeRangeInput.value = '0-24'; // Set default value timeRangeInput.style.backgroundColor = '#1E1E1E'; timeRangeInput.style.color = 'white'; timeRangeInput.style.border = '1px solid #444444'; timeRangeInput.style.borderRadius = '5px'; timeRangeInput.style.padding = '5px'; timeRangeInput.style.width = '85%'; timeRangeInput.style.marginRight = '5px'; timeRangeInput.addEventListener('blur', () => { const timeValue = timeRangeInput.value.trim(); const timeRegex = /^([0-9]|1[0-9]|2[0-3])-(?:[0-9]|1[0-9]|2[0-3])$/; if (!timeRegex.test(timeValue)) { alert('Please enter a valid time range between 0-23, e.g., "8-16" or "0-24". Defaulting to "0-24".'); timeRangeInput.value = '0-24'; } }); timeRangeInputContainer.appendChild(timeRangeInput); const timeRangeUnitLabel = document.createElement('span'); timeRangeUnitLabel.innerText = 'h'; timeRangeUnitLabel.style.color = 'white'; timeRangeInputContainer.appendChild(timeRangeUnitLabel); timeRangeContainer.appendChild(timeRangeInputContainer); probTimeContainer.appendChild(timeRangeContainer); // Create overall probability label and input const overallProbabilityContainer = document.createElement('div'); overallProbabilityContainer.style.display = 'flex'; overallProbabilityContainer.style.flexDirection = 'column'; overallProbabilityContainer.style.width = '30%'; // Adjust width to fit the row better const overallProbabilityLabel = document.createElement('div'); overallProbabilityLabel.innerText = 'Overall Probability'; overallProbabilityLabel.style.color = 'white'; overallProbabilityLabel.style.marginBottom = '0px'; overallProbabilityContainer.appendChild(overallProbabilityLabel); const overallProbabilityInputContainer = document.createElement('div'); overallProbabilityInputContainer.style.display = 'flex'; overallProbabilityInputContainer.style.alignItems = 'center'; const overallProbabilityInput = document.createElement('input'); overallProbabilityInput.type = 'number'; overallProbabilityInput.placeholder = '0-100'; overallProbabilityInput.style.backgroundColor = '#202530'; overallProbabilityInput.style.color = 'white'; overallProbabilityInput.style.border = '1px solid #444444'; overallProbabilityInput.style.borderRadius = '5px'; overallProbabilityInput.style.padding = '5px'; overallProbabilityInput.style.marginRight = '5px'; overallProbabilityInput.style.width = '85%'; // Load the existing overall probability value if set const savedProbability = localStorage.getItem('events.probability'); if (savedProbability) { overallProbabilityInput.value = savedProbability; } // Add event listener to save the value to localStorage when changed overallProbabilityInput.addEventListener('input', () => { const probabilityValue = overallProbabilityInput.value.trim(); if (probabilityValue !== '' && probabilityValue >= 0 && probabilityValue <= 100) { localStorage.setItem('events.probability', probabilityValue); } else { alert('Please enter a valid probability between 0 and 100.'); } }); overallProbabilityInputContainer.appendChild(overallProbabilityInput); const overallProbabilityPercentLabel = document.createElement('span'); overallProbabilityPercentLabel.innerText = '%'; overallProbabilityPercentLabel.style.color = 'white'; overallProbabilityInputContainer.appendChild(overallProbabilityPercentLabel); overallProbabilityContainer.appendChild(overallProbabilityInputContainer); probTimeContainer.appendChild(overallProbabilityContainer); // Append probability and time range container to the main keyValue container keyValueContainer.appendChild(probTimeContainer); // Create input for key const keyInput = document.createElement('input'); keyInput.type = 'text'; keyInput.placeholder = 'Enter key'; keyInput.style.backgroundColor = '#1E1E1E'; keyInput.style.color = 'white'; keyInput.style.border = '1px solid #444444'; keyInput.style.borderRadius = '5px'; keyInput.style.padding = '5px'; keyInput.style.marginBottom = '-4px'; keyValueContainer.appendChild(keyInput); // Create input for value const valueInput = document.createElement('textarea'); valueInput.placeholder = 'Enter value'; valueInput.style.backgroundColor = '#1E1E1E'; valueInput.style.color = 'white'; valueInput.style.border = '1px solid #444444'; valueInput.style.borderRadius = '5px'; valueInput.style.padding = '5px'; valueInput.style.height = '80px'; valueInput.style.overflowWrap = 'break-word'; valueInput.style.overflow = 'auto'; valueInput.style.marginBottom = '-4px'; keyValueContainer.appendChild(valueInput); // Create button to add key-value pair const addEntryButton = document.createElement('button'); addEntryButton.innerText = 'Add Entry'; addEntryButton.innerText = 'Add Entry'; addEntryButton.style.padding = '10px 20px'; addEntryButton.style.border = '0.2px solid #4E4E4E'; addEntryButton.style.backgroundColor = '#2F2F2F'; addEntryButton.style.color = '#fff'; addEntryButton.style.borderRadius = '50px'; addEntryButton.style.cursor = 'pointer'; addEntryButton.onmouseover = () => { addEntryButton.style.backgroundColor = '#424242'; }; addEntryButton.onmouseout = () => { addEntryButton.style.backgroundColor = 'transparent'; }; // Adding Entries let currentEditingKey = null; // This keeps track of the current key being edited // Add or Edit Entry Button addEntryButton.onclick = function () { if (!selectedProfile) { alert('Please select a profile before adding entries.'); return; } const key = keyInput.value.trim(); const value = `<Event: ${valueInput.value.trim()}>`; const probability = probabilityInput.value.trim(); const timeRange = timeRangeInput.value.trim(); const fullKey = `${selectedProfile}.events:${key}`; if (key && value) { // Check if we are editing an existing entry if (currentEditingKey) { // Check if we are attempting to edit to a key that already exists if (currentEditingKey !== fullKey && localStorage.getItem(fullKey)) { alert('An entry with this key already exists. Please use a different key.'); return; } // Remove the old entry if the key has changed if (currentEditingKey !== fullKey) { localStorage.removeItem(currentEditingKey); } // Update or add the new entry const entryData = { value: value, probability: probability || '100', timeRange: timeRange || '0-24' }; localStorage.setItem(fullKey, JSON.stringify(entryData)); currentEditingKey = null; // Reset the editing key } else { // If adding a new entry if (!localStorage.getItem(fullKey)) { const entryData = { value: value, probability: probability || '100', timeRange: timeRange || '0-24' }; localStorage.setItem(fullKey, JSON.stringify(entryData)); } else { alert('Entry with this key already exists. Please use a different key.'); return; } } loadEntries(); // Clear the input fields after adding/editing the entry keyInput.value = ''; valueInput.value = ''; probabilityInput.value = '100'; timeRangeInput.value = '0-24'; } }; keyValueContainer.appendChild(addEntryButton); // Append containers to profilePanel eventsProfilePanel.appendChild(profileListContainer); eventsProfilePanel.appendChild(keyValueContainer); // Load saved profiles and entries let selectedProfile = localStorage.getItem('selectedProfile.events'); function loadProfiles() { profileList.innerHTML = ''; for (let key in localStorage) { if (key.startsWith('events.profile:')) { const profileName = key.replace('events.profile:', ''); const listItem = document.createElement('li'); listItem.style.display = 'flex'; listItem.style.alignItems = 'center'; listItem.style.cursor = 'pointer'; const nameSpan = document.createElement('span'); nameSpan.innerText = profileName; nameSpan.style.flex = '1'; listItem.appendChild(nameSpan); listItem.onclick = function() { if (selectedProfile === profileName) { selectedProfile = null; localStorage.setItem('selectedProfile.events', ''); listItem.style.backgroundColor = ''; loadEntries(); } else { selectedProfile = profileName; localStorage.setItem('selectedProfile.events', selectedProfile); loadEntries(); highlightSelectedProfile(listItem); } }; const deleteButton = document.createElement('button'); // deleteButton.innerText = 'x'; deleteButton.style.backgroundColor = 'transparent'; deleteButton.style.borderRadius = '50%'; deleteButton.style.marginLeft = '10px'; deleteButton.style.border = 'none'; deleteButton.style.color = '#ffffff'; deleteButton.style.cursor = 'pointer'; deleteButton.style.padding = '14px'; deleteButton.style.width = '15px'; deleteButton.style.height = '15px'; deleteButton.style.display = 'flex'; deleteButton.style.alignItems = 'center'; deleteButton.style.justifyContent = 'center'; deleteButton.style.transition = 'background-color 0.1s'; // Hover effect deleteButton.addEventListener('mouseenter', () => { // closeButton.style.transform = 'scale(1.1)'; deleteButton.style.backgroundColor = '#676767'; }); deleteButton.addEventListener('mouseleave', () => { // closeButton.style.transform = 'scale(1)'; deleteButton.style.backgroundColor = 'transparent'; }); const closeIcon2 = document.createElement('span'); closeIcon2.innerText = '✕'; closeIcon2.style.fontSize = '16px'; closeIcon2.style.color = '#ffffff'; closeIcon2.style.position = 'relative'; closeIcon2.style.top = '-1px'; // Adjust this value to move the character up deleteButton.appendChild(closeIcon2); deleteButton.onclick = function(event) { event.stopPropagation(); localStorage.removeItem(`events.profile:${profileName}`); if (selectedProfile === profileName) { selectedProfile = null; localStorage.setItem('selectedProfile.events', ''); } loadProfiles(); loadEntries(); }; listItem.appendChild(deleteButton); if (selectedProfile === profileName) { highlightSelectedProfile(listItem); } profileList.appendChild(listItem); } } } function openAddProfileDialog() { const dialog = document.createElement('div'); dialog.style.position = 'fixed'; dialog.style.top = '50%'; dialog.style.left = '50%'; dialog.style.transform = 'translate(-50%, -50%)'; dialog.style.backgroundColor = '#424242'; dialog.style.padding = '20px'; dialog.style.boxShadow = '0 0 10px rgba(0,0,0,0.5)'; dialog.style.zIndex = '1100'; const input = document.createElement('input'); input.type = 'text'; input.placeholder = 'Enter profile name'; dialog.appendChild(input); const addButton = document.createElement('button'); addButton.innerText = 'Add'; addButton.style.marginLeft = '10px'; addButton.onclick = function() { const profileName = input.value.trim(); if (profileName) { // Perform a case-insensitive check for existing profiles const existingProfiles = Object.keys(localStorage).filter(key => key.startsWith('events.profile:')); const profileExists = existingProfiles.some(key => key.toLowerCase() === `events.profile:${profileName.toLowerCase()}`); if (!profileExists) { localStorage.setItem(`events.profile:${profileName}`, JSON.stringify({})); loadProfiles(); document.body.removeChild(dialog); } else { alert('Profile name already exists. Please choose a different name.'); } } }; dialog.appendChild(addButton); const cancelButton = document.createElement('button'); cancelButton.innerText = 'Cancel'; cancelButton.style.marginLeft = '10px'; cancelButton.onclick = function() { document.body.removeChild(dialog); }; dialog.appendChild(cancelButton); document.body.appendChild(dialog); } function loadEntries() { entriesList.innerHTML = ''; // Add header back after clearing if (selectedProfile) { const prefix = `${selectedProfile}.events:`.toLowerCase(); for (let key in localStorage) { if (key.toLowerCase().startsWith(prefix)) { const entryData = JSON.parse(localStorage.getItem(key)); const row = document.createElement('div'); row.style.padding = '10px'; row.style.margin = '5px 0'; row.style.borderRadius = '10px'; row.style.marginBottom = '12px'; row.style.backgroundColor = '#424242'; row.style.display = 'flex'; row.style.alignItems = 'center'; // Create cells for key, value, probability, and time range const keyCell = document.createElement('div'); keyCell.innerText = key.split(':')[1]; keyCell.style.padding = '5px'; keyCell.style.width = '15%'; row.appendChild(keyCell); const valueCell = document.createElement('div'); valueCell.innerText = entryData.value.replace(/^<Event:\s*/, '').slice(0, -1); // Remove surrounding brackets valueCell.style.padding = '5px'; valueCell.style.width = '60%'; row.appendChild(valueCell); const probabilityCell = document.createElement('div'); probabilityCell.innerText = `${entryData.probability}%`; probabilityCell.style.padding = '5px'; probabilityCell.style.width = '10%'; row.appendChild(probabilityCell); const timeRangeCell = document.createElement('div'); timeRangeCell.innerText = entryData.timeRange; timeRangeCell.style.padding = '5px'; timeRangeCell.style.width = '10%'; row.appendChild(timeRangeCell); // Add remove button for each entry const actionCell = document.createElement('div'); actionCell.style.padding = '5px'; actionCell.style.width = '5%'; const removeButton = document.createElement('button'); // removeButton.innerText = '✕'; removeButton.style.backgroundColor = 'transparent'; removeButton.style.border = 'none'; removeButton.style.cursor = 'pointer'; removeButton.style.display = 'flex'; removeButton.style.alignItems = 'center'; removeButton.style.justifyContent = 'center'; removeButton.style.width = '100%'; removeButton.style.height = '100%'; removeButton.style.transition = 'background-color 0.2s ease'; removeButton.style.borderRadius = '50%'; removeButton.style.width = '28px'; removeButton.style.height = '28px'; removeButton.style.marginLeft = '-3px'; removeButton.style.boxSizing = 'border-box'; removeButton.onclick = function() { localStorage.removeItem(key); loadEntries(); }; const closeIcon1 = document.createElement('span'); closeIcon1.innerText = '✕'; closeIcon1.style.fontSize = '16px'; closeIcon1.style.position = 'relative'; closeIcon1.style.color = '#ffffff'; closeIcon1.style.top = '-1px'; // Adjust this value to move the character up // Append the span to the button removeButton.appendChild(closeIcon1); actionCell.appendChild(removeButton); row.appendChild(actionCell); // Hover effect removeButton.addEventListener('mouseenter', () => { removeButton.style.backgroundColor = '#676767'; }); removeButton.addEventListener('mouseleave', () => { removeButton.style.backgroundColor = 'transparent'; }); // Make the row editable when clicked // Make the row editable when clicked row.onclick = function() { keyInput.value = key.split(':')[1]; // If key processing remains as is valueInput.value = entryData.value.replace(/^<Event:\s*/, '').slice(0, -1); // Remove <Event: > prefix and surrounding brackets probabilityInput.value = entryData.probability; timeRangeInput.value = entryData.timeRange; currentEditingKey = key; // Set the current key for editing }; entriesList.appendChild(row); } } } } function highlightSelectedProfile(selectedItem) { // Remove highlight from all items Array.from(profileList.children).forEach(item => { item.style.backgroundColor = ''; }); // Highlight the selected item selectedItem.style.backgroundColor = '#444444'; selectedItem.style.borderRadius = '10px'; selectedItem.style.padding = '10px'; } loadProfiles(); loadEntries(); // Append profilePanel to body document.body.appendChild(eventsProfilePanel); } } // ==================================================================== RULES =================================================================== function RulesScript() { // Custom texts to cycle through let customRules = []; let currentIndex = 0; // Function to determine the current rule index by scanning last two messages function determineCurrentIndex() { const messageItems = document.querySelectorAll('li[class^="messageListItem_"]'); if (messageItems.length >= 1) { // Check the last message first const lastMessage = Array.from(messageItems[messageItems.length - 1].querySelectorAll('span')).map(span => span.innerText).join('') || messageItems[messageItems.length - 1].innerText; for (let i = 0; i < customRules.length; i++) { if (lastMessage.includes(`<Rule${i + 1}:`)) { currentIndex = (i + 1) % customRules.length; return; } } } // If not found in the last message, check the second to last message if (messageItems.length >= 2) { const secondLastMessage = Array.from(messageItems[messageItems.length - 2].querySelectorAll('span')).map(span => span.innerText).join('') || messageItems[messageItems.length - 2].innerText; for (let i = 0; i < customRules.length; i++) { if (secondLastMessage.includes(`<Rule${i + 1}:`)) { currentIndex = (i + 1) % customRules.length; return; } } } } // Expose necessary elements to be used by the second script window.customRuleLogic = { customRules, determineCurrentIndex, getCurrentText: function() { determineCurrentIndex(); const customRule = '\n' + customRules[currentIndex]; currentIndex = (currentIndex + 1) % customRules.length; return customRule; } }; // Create Button and Panel UI for Local Storage Key Management window.manageRulesButton = document.createElement('div'); window.manageRulesButton.innerHTML = ` <button id="toggle-rules-panel" style=" position: relative; top: 10px; right: 0px; left: 10px; padding: 7px 15px; background: transparent; color: #b0b0b0; border: none; border-radius: 8px; font-size: 16px; text-align: left; cursor: pointer; width: 90%; display: flex; align-items: center; gap: 10px; transition: background-color 0.1s, color 0.1s; z-index: 1001;"> <svg fill="#B0B0B0" version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 492.508 492.508" xml:space="preserve" style="padding-right: 0px; margin-left: 1px;"> <g id="SVGRepo_bgCarrier" stroke-width="0"></g> <g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g> <g id="SVGRepo_iconCarrier"> <g> <g> <path d="M199.493,402.145c0-10.141-8.221-18.361-18.36-18.361H42.475c-10.139,0-18.36,8.221-18.36,18.361 c0,3.195,0.818,6.199,2.255,8.816H0v38.067h223.607v-38.067h-26.369C198.674,408.343,199.493,405.34,199.493,402.145z"></path> <path d="M175.898,88.224l117.157,74.396c9.111,4.643,20.43,1.678,26.021-7.129l5.622-8.85c5.938-9.354,3.171-21.75-6.182-27.69 L204.592,46.608c-9.352-5.939-21.748-3.172-27.688,6.182l-5.622,8.851C165.692,70.447,167.82,81.952,175.898,88.224z"></path> <path d="M492.456,372.433l-0.082-1.771l-0.146-1.672c-0.075-1.143-0.235-2.159-0.375-3.204c-0.562-4.177-1.521-7.731-2.693-10.946 c-2.377-6.386-5.738-11.222-9.866-14.845c-1.027-0.913-2.126-1.714-3.218-2.528l-3.271-2.443 c-2.172-1.643-4.387-3.218-6.587-4.815c-2.196-1.606-4.419-3.169-6.644-4.729c-2.218-1.571-4.445-3.125-6.691-4.651 c-4.468-3.089-8.983-6.101-13.51-9.103l-6.812-4.464l-6.85-4.405c-4.58-2.911-9.167-5.813-13.785-8.667 c-4.611-2.865-9.24-5.703-13.896-8.496l-13.979-8.363l-14.072-8.22l-14.149-8.096l-14.219-7.987l-14.287-7.882l-14.354-7.773 c-4.802-2.566-9.599-5.137-14.433-7.653c-4.822-2.529-9.641-5.071-14.498-7.548l-4.398,6.928l-22.17-10.449l24.781-39.026 l-117.156-74.395l-60.944,95.974l117.157,74.395l24.781-39.026l18.887,15.622l-4.399,6.929c4.309,3.343,8.657,6.619,12.998,9.91 c4.331,3.305,8.698,6.553,13.062,9.808l13.14,9.686l13.211,9.577l13.275,9.474l13.346,9.361l13.422,9.242l13.514,9.095 c4.51,3.026,9.045,6.009,13.602,8.964c4.547,2.967,9.123,5.882,13.707,8.792l6.898,4.324l6.936,4.266 c4.643,2.818,9.289,5.625,13.985,8.357c2.337,1.383,4.689,2.739,7.055,4.078c2.358,1.349,4.719,2.697,7.106,4 c2.383,1.312,4.75,2.646,7.159,3.912l3.603,1.922c1.201,0.64,2.394,1.296,3.657,1.837c5.036,2.194,10.841,3.18,17.63,2.614 c3.409-0.305,7.034-0.949,11.054-2.216c1.006-0.317,1.992-0.606,3.061-1.023l1.574-0.58l1.639-0.68 c2.185-0.91,4.523-2.063,7.059-3.522C492.513,377.405,492.561,374.799,492.456,372.433z"></path> <path d="M67.897,261.877l113.922,72.341c9.354,5.938,21.75,3.172,27.689-6.181l5.621-8.852c5.592-8.808,3.462-20.311-4.615-26.583 L93.358,218.207c-9.111-4.642-20.43-1.678-26.022,7.13l-5.62,8.85C55.775,243.541,58.543,255.938,67.897,261.877z"></path> </g> </g> </g> </svg> Manage Rules </button> `; const rulesButtonElement = manageRulesButton.querySelector('button'); rulesButtonElement.onmouseover = () => { rulesButtonElement.style.backgroundColor = '#212121'; rulesButtonElement.style.color = '#ffffff'; }; rulesButtonElement.onmouseout = () => { rulesButtonElement.style.backgroundColor = 'transparent'; rulesButtonElement.style.color = '#b0b0b0'; }; const rulesPanel = document.createElement('div'); rulesPanel.style.position = 'fixed'; rulesPanel.style.top = '50%'; rulesPanel.style.left = '50%'; rulesPanel.style.transform = 'translate(-50%, -50%)'; // Size different for Mobile and Desktop if (window.innerWidth <= 768) { rulesPanel.style.width = '90%'; rulesPanel.style.height = '90%'; } else { rulesPanel.style.width = '800px'; rulesPanel.style.height = '700px'; } rulesPanel.style.backgroundColor = '#2f2f2f'; rulesPanel.style.padding = '20px'; rulesPanel.style.overflow = 'hidden'; // Prevent full panel scrolling rulesPanel.style.display = 'none'; rulesPanel.style.zIndex = 1000; rulesPanel.style.borderRadius = '10px'; document.body.appendChild(rulesPanel); const title = document.createElement('h2'); title.innerText = 'Manage Rules'; title.style.textAlign = 'center'; title.style.color = '#ffffff'; title.style.fontSize = '24px'; title.style.fontWeight = '550'; title.style.marginBottom = '20px'; rulesPanel.appendChild(title); const closeButton = document.createElement('button'); closeButton.innerText = '✕'; closeButton.style.position = 'absolute'; closeButton.style.borderRadius = '50%'; closeButton.style.color = '#ffffff'; closeButton.style.top = '20px'; closeButton.style.right = '20px'; closeButton.style.backgroundColor = 'transparent'; closeButton.style.cursor = 'pointer'; closeButton.addEventListener('mouseenter', () => { closeButton.style.backgroundColor = '#676767'; }); closeButton.addEventListener('mouseleave', () => { closeButton.style.backgroundColor = 'transparent'; }); closeButton.addEventListener('click', () => { rulesPanel.style.display = 'none'; }); rulesPanel.appendChild(closeButton); const rulesListContainer = document.createElement('div'); rulesListContainer.style.height = 'calc(100% - 180px)'; // Adjust height to leave space for header and button rulesListContainer.style.overflowY = 'auto'; // Allow only the rules list to scroll rulesPanel.appendChild(rulesListContainer); manageRulesButton.addEventListener('click', () => { rulesPanel.style.display = rulesPanel.style.display === 'none' ? 'block' : 'none'; renderPanel(); }); function renderPanel() { rulesListContainer.innerHTML = ''; customRules.forEach((rule, index) => { const ruleContainer = document.createElement('div'); ruleContainer.style.marginBottom = '15px'; ruleContainer.style.display = 'flex'; ruleContainer.style.alignItems = 'center'; ruleContainer.style.width = '95%'; ruleContainer.style.gap = '5px'; // Reduced gap to bring elements closer const ruleLabel = document.createElement('label'); ruleLabel.textContent = `Rule ${index + 1}:`; ruleLabel.style.color = 'white'; ruleLabel.style.marginLeft = '5%'; ruleLabel.style.flex = '0.5'; // Reduced flex to bring the label closer to the input ruleContainer.appendChild(ruleLabel); // Set the textarea value to the rule text rather than using it as a placeholder const ruleInput = document.createElement('textarea'); ruleInput.value = rule.replace(/<Rule\d+: (.*?)>/, '$1'); // Set the inner text as the value for editing ruleInput.style.flex = '2'; ruleInput.style.padding = '5px'; // Added more padding for easier tapping ruleInput.style.borderRadius = '5px'; ruleInput.style.border = '0.5px solid #444444'; ruleInput.style.backgroundColor = '#1E1E1E'; ruleInput.style.color = 'gray'; ruleInput.style.overflowY = 'hidden'; // Handle vertical overflow automatically ruleInput.style.overflowX = 'hidden'; // Prevent horizontal overflow ruleInput.style.maxWidth = '100%'; // Prevent textarea from extending beyond container width ruleInput.style.boxSizing = 'border-box'; // Ensure padding is included in width calculation ruleInput.style.resize = 'none'; // Disable manual resizing to avoid visual clutter // Adjust height based on input content function adjustHeight(element) { element.style.height = 'auto'; // Reset height to calculate the new height element.style.height = `${element.scrollHeight}px`; // Set height to match content } // Adjust height initially to fit content on render adjustHeight(ruleInput); ruleInput.addEventListener('input', () => { adjustHeight(ruleInput); ruleInput.style.color = '#D16262'; // Indicate unsaved changes }); ruleContainer.appendChild(ruleInput); const updateButton = document.createElement('button'); updateButton.textContent = 'Update'; updateButton.style.padding = '5px 10px'; updateButton.style.border = 'none'; updateButton.style.backgroundColor = '#1E1E1E'; updateButton.style.color = 'white'; updateButton.style.borderRadius = '5px'; updateButton.style.cursor = 'pointer'; updateButton.addEventListener('click', () => { customRules[index] = `<Rule${index + 1}: ${ruleInput.value}>`; updateLocalStorageKeys(); ruleInput.style.color = 'black'; // Change color to indicate saved state }); ruleContainer.appendChild(updateButton); const deleteButton = document.createElement('button'); deleteButton.textContent = 'Delete'; deleteButton.style.padding = '5px 10px'; deleteButton.style.border = 'none'; deleteButton.style.backgroundColor = '#1E1E1E'; deleteButton.style.color = 'white'; deleteButton.style.marginLeft = '5px'; deleteButton.style.borderRadius = '5px'; deleteButton.style.cursor = 'pointer'; deleteButton.addEventListener('click', () => { customRules.splice(index, 1); updateLocalStorageKeys(); renderPanel(); }); ruleContainer.appendChild(deleteButton); // Move rule up/down container const moveContainer = document.createElement('div'); moveContainer.style.display = 'flex'; moveContainer.style.flexDirection = 'column'; moveContainer.style.gap = '3px'; // Move rule up if (index > 0) { const upButton = document.createElement('button'); upButton.textContent = '▲'; upButton.style.padding = '3px'; upButton.style.border = 'none'; upButton.style.backgroundColor = 'transparent'; upButton.style.color = 'white'; upButton.style.borderRadius = '5px'; upButton.style.cursor = 'pointer'; upButton.addEventListener('click', () => { [customRules[index - 1], customRules[index]] = [customRules[index], customRules[index - 1]]; updateLocalStorageKeys(); renderPanel(); }); moveContainer.appendChild(upButton); } // Move rule down if (index < customRules.length - 1) { const downButton = document.createElement('button'); downButton.textContent = '▼'; downButton.style.padding = '3px'; downButton.style.border = 'none'; downButton.style.backgroundColor = 'transparent'; downButton.style.color = 'white'; downButton.style.borderRadius = '5px'; downButton.style.cursor = 'pointer'; downButton.addEventListener('click', () => { [customRules[index], customRules[index + 1]] = [customRules[index + 1], customRules[index]]; updateLocalStorageKeys(); renderPanel(); }); moveContainer.appendChild(downButton); } ruleContainer.appendChild(moveContainer); rulesListContainer.appendChild(ruleContainer); }); const addButton = document.createElement('button'); addButton.textContent = 'Add Rule'; addButton.style.margin = '15px auto'; addButton.style.display = 'block'; addButton.style.padding = '10px'; addButton.style.border = 'none'; addButton.style.width = '90%'; addButton.style.backgroundColor = '#e0e0e0'; addButton.style.borderRadius = '20px'; addButton.style.cursor = 'pointer'; addButton.addEventListener('click', () => { const newRule = `<Rule${customRules.length + 1}: Define your rule here>`; customRules.push(newRule); updateLocalStorageKeys(); renderPanel(); }); rulesPanel.appendChild(addButton); // Adjust height for all textareas after rendering the panel adjustAllHeights(); } // Initial adjustment when loading the panel function adjustAllHeights() { const textareas = rulesListContainer.querySelectorAll('textarea'); textareas.forEach((textarea) => { textarea.style.height = 'auto'; // Reset height to calculate the new height textarea.style.height = `${textarea.scrollHeight}px`; // Set height to match content }); } function updateLocalStorageKeys() { customRules.forEach((_, index) => localStorage.removeItem(`Rule${index + 1}`)); customRules.forEach((rule, index) => { localStorage.setItem(`Rule${index + 1}`, rule); }); } // Load rules from localStorage for (let i = 0; i < localStorage.length; i++) { const savedRule = localStorage.getItem(`Rule${i + 1}`); if (savedRule) { customRules.push(savedRule); } } } // ===================================================================== MP3 ==================================================================== function mp3Script() { // Function to get API key from local storage or prompt user to input it function getApiKey() { let apiKey = localStorage.getItem('google_cloud_api_key'); return apiKey; } // Function to set the API key in local storage document.addEventListener('input', (event) => { if (event.target.id === 'api-key-input') { const apiKey = event.target.value; if (apiKey) { localStorage.setItem('google_cloud_api_key', apiKey); alert('API key saved successfully.'); } } }); // Fetch the API key let API_KEY = getApiKey(); const API_URL = `https://speech.googleapis.com/v1/speech:recognize?key=${API_KEY}`; // Create a button to toggle the transcription panel window.mp3ToggleButton = document.createElement('div'); mp3ToggleButton.innerHTML = ` <button id="toggle-transcription-panel" style=" display: flex; align-items: center; gap: 8px; position: relative; top: 10px; right: 0px; left: 10px; padding: 7px 15px; background: transparent; color: #b0b0b0; border: none; border-radius: 8px; font-size: 16px; text-align: left; cursor: pointer; width: 90%; transition: background-color 0.1s, color 0.1s; z-index: 1001;"> <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" xml:space="preserve" width="20px" height="20px" style="margin-right: -2px;"> <path d="M6 11L6 13" stroke="#B0B0B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M9 9L9 15" stroke="#B0B0B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M15 9L15 15" stroke="#B0B0B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M18 11L18 13" stroke="#B0B0B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M12 11L12 13" stroke="#B0B0B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </svg> Show Transcript </button> `; const buttonElement = mp3ToggleButton.querySelector('button'); buttonElement.onmouseover = () => { buttonElement.style.backgroundColor = '#212121'; buttonElement.style.color = '#ffffff'; }; buttonElement.onmouseout = () => { buttonElement.style.backgroundColor = 'transparent'; buttonElement.style.color = '#b0b0b0'; }; // Append the button to the body //document.body.appendChild(mp3ToggleButton); // Add a simple panel to the webpage const panelHTML = ` <div id="transcription-panel" style="display: none;"> <h3>MP3 Transcription Tool</h3> <input type="text" id="api-key-input" placeholder="Enter Google Cloud API key here" /> <input type="text" id="mp3-url" placeholder="Enter MP3 URL here" /> <button id="transcribe-button">Transcribe</button> <textarea id="transcription-result" placeholder="Transcript will appear here..." readonly></textarea> </div> `; document.body.insertAdjacentHTML('beforeend', panelHTML); // Set the API key input value if it exists in local storage document.addEventListener('DOMContentLoaded', () => { const apiKeyInput = document.getElementById('api-key-input'); if (apiKeyInput) { const savedApiKey = getApiKey(); if (savedApiKey) { apiKeyInput.value = savedApiKey; API_KEY = savedApiKey; } } }); // Add styles for the panel GM_addStyle(` #transcription-panel { position: fixed; bottom: 50px; right: 10px; width: 300px; background: #f8f9fa; border: 1px solid #ccc; padding: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); z-index: 9999; font-family: Arial, sans-serif; } #transcription-panel h3 { margin: 0 0 10px; font-size: 16px; } #transcription-panel input, #transcription-panel textarea { width: 100%; margin-bottom: 10px; padding: 5px; box-sizing: border-box; } #transcription-panel button { width: 100%; padding: 5px; cursor: pointer; background: #007bff; color: white; border: none; border-radius: 3px; } `); // Add event listener to the toggle button document.addEventListener('click', (event) => { // Check if the clicked element is the button const button = event.target.closest('#toggle-transcription-panel'); if (button) { const panel = document.getElementById('transcription-panel'); if (panel) { if (panel.style.display === 'none') { panel.style.display = 'block'; button.innerText = 'Hide MP3 Transcription'; } else { panel.style.display = 'none'; button.innerText = 'Show MP3 Transcription'; } } else { console.error('Transcription panel not found!'); } } }); // Add event listener to the Transcribe button document.getElementById('transcribe-button').addEventListener('click', transcribe); // Function to transcribe the entered MP3 URL function transcribe() { const mp3Url = document.getElementById('mp3-url').value.trim(); if (!mp3Url) { alert('Please enter a valid MP3 URL.'); return; } // Check if the transcript already exists in local storage const storedTranscript = localStorage.getItem(mp3Url); if (storedTranscript) { document.getElementById('transcription-result').value = storedTranscript; return; } // Fetch MP3 file using GM_xmlhttpRequest and process it GM_xmlhttpRequest({ method: 'GET', url: mp3Url, responseType: 'arraybuffer', // Required for audio data onload: (response) => { // Convert the audio file to Base64 const audioBase64 = arrayBufferToBase64(response.response); // Send the Base64-encoded audio to Google Cloud Speech-to-Text API sendToGoogleCloud(audioBase64, mp3Url); }, onerror: (err) => { alert('Failed to fetch the MP3 file.'); console.error(err); }, }); } // Function to send the Base64 audio data to Google Cloud Speech-to-Text API function sendToGoogleCloud(audioBase64, mp3Url) { GM_xmlhttpRequest({ method: 'POST', url: API_URL, headers: { 'Content-Type': 'application/json' }, data: JSON.stringify({ config: { encoding: 'MP3', sampleRateHertz: 16000, languageCode: 'en-US', }, audio: { content: audioBase64, }, }), onload: (response) => { const result = JSON.parse(response.responseText); if (result.error) { alert(`Error: ${result.error.message}`); } else { const transcript = result.results ?.map((r) => r.alternatives[0].transcript) .join('\n'); document.getElementById('transcription-result').value = transcript || 'No transcript found.'; // Store the transcript in local storage localStorage.setItem(mp3Url, transcript || 'No transcript found.'); // Limit local storage to 3 entries manageLocalStorageLimit(); // Show "Transcript done!" message showTranscriptionDoneMessage(); } }, onerror: (err) => { alert('Failed to process the transcription.'); console.error(err); }, }); } // Function to convert ArrayBuffer to Base64 function arrayBufferToBase64(buffer) { const binary = []; const bytes = new Uint8Array(buffer); const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary.push(String.fromCharCode(bytes[i])); } return btoa(binary.join('')); } // Observe the conversation for changes and detect MP3 links const observer = new MutationObserver(() => { const messageItems = document.querySelectorAll('div[class*="messageContent_"]'); const lastMessage = messageItems[messageItems.length - 1]; if (lastMessage) { const mp3LinkMatch = lastMessage.innerText.match(/https:\/\/files\.shapes\.inc\/.*\.mp3/); if (mp3LinkMatch) { const mp3Link = mp3LinkMatch[0]; const storedLink = localStorage.getItem('lastMp3Link'); // Check if the link is new or different if (mp3Link !== storedLink) { localStorage.setItem('lastMp3Link', mp3Link); // Store the new link document.getElementById('mp3-url').value = mp3Link; // Populate the input transcribe(); // Automatically transcribe the new link } } } }); // Start observing the document body for new messages observer.observe(document.body, { childList: true, subtree: true }); // Function to manage local storage limit of 3 entries function manageLocalStorageLimit() { const keys = Object.keys(localStorage).filter((key) => key.startsWith('http')); if (keys.length > 10) { // Remove oldest entries until only 3 remain while (keys.length > 10) { localStorage.removeItem(keys.shift()); } } } // Function to show "Transcript done!" message function showTranscriptionDoneMessage() { let messageDiv = document.getElementById('transcription-done-message'); if (!messageDiv) { messageDiv = document.createElement('div'); messageDiv.id = 'transcription-done-message'; messageDiv.innerText = 'Transcript done!'; document.body.appendChild(messageDiv); } messageDiv.style.display = 'block'; setTimeout(() => { messageDiv.style.display = 'none'; }, 3000); } } // =================================================================== STYLES =================================================================== function StylesScript() { // Add custom CSS rule to modify the channel text area const style = document.createElement('style'); style.innerHTML = ` .channelTextArea_bdf0de { position: relative; width: 120%; text-indent: 0; border-radius: 8px; } .themedBackground_bdf0de { background: #2F2F2F; } .chatContent_a7d72e { position: relative; display: flex; flex-direction: column; min-width: 0; min-height: 0; flex: 1 1 auto; background: #212121 !important; } .theme-dark .container_ee69e0 { background: #191919; } .theme-dark .themed_fc4f04 { background: #212121; } .footer_f8ec41 { background: #131313; } .theme-dark .container_b2ca13 { background: #191919; } .wrapper_fea3ef { background-color: #131313; display: none !important; } `; document.head.appendChild(style); // Function to hide the targeted elements function hideElements() { // Select all elements that match the provided pattern const elements = document.querySelectorAll("[id^='message-accessories-'] > article > div > div > div.embedProvider_b0068a.embedMargin_b0068a, [id^='message-accessories-'] > article > div > div > div.embedTitle_b0068a.embedMargin_b0068a, .buttons_bdf0de .expression-picker-chat-input-button.buttonContainer_bdf0de, .channelAppLauncher_df39bd .buttonContainer_df39bd.app-launcher-entrypoint"); // Iterate over each element and hide it elements.forEach(element => { element.style.display = 'none'; }); } // Run the function initially to hide elements present at page load hideElements(); // Observe mutations to the DOM and hide elements when new ones are added const observer = new MutationObserver(hideElements); observer.observe(document.body, { childList: true, subtree: true }); // Create the toggleButton div // Create the toggleButton div window.serverbartoggleButton = document.createElement('div'); serverbartoggleButton.innerHTML = ` <button id="toggle-server-panel" style=" display: flex; align-items: center; gap: 8px; /* Add spacing between icon and text */ position: relative; top: 10px; right: 0px; left: 10px; padding: 7px 15px; background: transparent; color: #b0b0b0; border: none; border-radius: 8px; font-size: 16px; text-align: left; cursor: pointer; width: 90%; transition: background-color 0.1s, color 0.1s; z-index: 1001;"> <svg width="16px" height="16px" viewBox="0 0 16 16" style="padding-right: 0px; margin-left: 2px" xmlns="http://www.w3.org/2000/svg" fill="#000000"> <g fill="#B0B0B0"> <path d="m 6.5 14 v -12 h -5 v 12 z m 0 0" fill-opacity="0.34902"></path> <path d="m 3 1 c -1.644531 0 -3 1.355469 -3 3 v 8 c 0 1.644531 1.355469 3 3 3 h 10 c 1.644531 0 3 -1.355469 3 -3 v -8 c 0 -1.644531 -1.355469 -3 -3 -3 z m 0 2 h 10 c 0.570312 0 1 0.429688 1 1 v 8 c 0 0.570312 -0.429688 1 -1 1 h -10 c -0.570312 0 -1 -0.429688 -1 -1 v -8 c 0 -0.570312 0.429688 -1 1 -1 z m 0 0"></path> <path d="m 6 2 h 1 v 12 h -1 z m 0 0"></path> </g> </svg> Toggle Sidebar </button> `; // Append the toggleButton to the desired parent element (e.g., `DCstoragePanel`) //window.DCstoragePanel.appendChild(serverbartoggleButton); // Function to toggle .itemsContainer_fea3ef visibility function servertoggleItemsContainer() { const serveritemsContainer = document.querySelectorAll('.wrapper_fea3ef'); serveritemsContainer.forEach(container => { const currentDisplay = window.getComputedStyle(container).getPropertyValue('display'); if (currentDisplay === 'none') { container.setAttribute('style', 'display: flex !important;'); } else { container.setAttribute('style', 'display: none !important;'); } }); } // Add event listener to the toggle button serverbartoggleButton.addEventListener('click', servertoggleItemsContainer); const serverbuttonElement = serverbartoggleButton.querySelector('button'); serverbuttonElement.onmouseover = () => { serverbuttonElement.style.backgroundColor = '#212121'; serverbuttonElement.style.color = '#ffffff'; }; serverbuttonElement.onmouseout = () => { serverbuttonElement.style.backgroundColor = 'transparent'; serverbuttonElement.style.color = '#b0b0b0'; }; } // ================================================================== < > HIDER ================================================================= function HiderScript() { function hideEnclosedEntries() { const messageItems = document.querySelectorAll('li[class^="messageListItem_"]'); messageItems.forEach(messageItem => { const spans = messageItem.querySelectorAll('div[class*="messageContent_"] span'); let isHiding = false; spans.forEach(span => { const text = span.textContent.trim(); // Start hiding when encountering '<' if (text.startsWith('<') && !isHiding) { isHiding = true; } // Apply hiding style if within an enclosed entry if (isHiding) { span.style.opacity = '0'; // Make it invisible span.style.position = 'absolute'; // Remove it from the document flow } // Stop hiding when encountering '>' if (text.endsWith('>') && isHiding) { isHiding = false; } }); }); } // Observe for new messages being added to the DOM const observer = new MutationObserver(mutations => { mutations.forEach(() => { hideEnclosedEntries(); }); }); // Start observing the entire document body for changes observer.observe(document.body, { childList: true, subtree: true }); // Initial run hideEnclosedEntries(); } // ================================================================== NOTIFIER ================================================================= function NotifierScript() { // Create a button to toggle the transcription panel window.notifierToggleButton = document.createElement('div'); notifierToggleButton.innerHTML = ` <button id="toggle-notifier-panel" style=" display: flex; align-items: center; gap: 8px; position: relative; top: 10px; right: 0px; left: 10px; padding: 7px 15px; background: transparent; color: #b0b0b0; border: none; border-radius: 8px; font-size: 16px; text-align: left; cursor: pointer; width: 90%; transition: background-color 0.1s, color 0.1s; z-index: 1001;"> <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" xml:space="preserve" width="20px" height="20px" style="margin-right: -2px;"> <path d="M6 11L6 13" stroke="#B0B0B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M9 9L9 15" stroke="#B0B0B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M15 9L15 15" stroke="#B0B0B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M18 11L18 13" stroke="#B0B0B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> <path d="M12 11L12 13" stroke="#B0B0B0" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path> </svg> Show Notifiers </button> `; const notifierButtonElement = notifierToggleButton.querySelector('button'); notifierButtonElement.onmouseover = () => { notifierButtonElement.style.backgroundColor = '#212121'; notifierButtonElement.style.color = '#ffffff'; }; notifierButtonElement.onmouseout = () => { notifierButtonElement.style.backgroundColor = 'transparent'; notifierButtonElement.style.color = '#b0b0b0'; }; // Create the main panel container const notifierPanel = document.createElement('div'); notifierPanel.id = 'lorebookManagerPanel'; notifierPanel.style.position = 'fixed'; notifierPanel.style.top = '50%'; notifierPanel.style.left = '50%'; notifierPanel.style.transform = 'translate(-50%, -50%)'; // Size different for Mobile and Desktop if (window.innerWidth <= 768) { notifierPanel.style.width = '90%'; notifierPanel.style.height = '90%'; } else { notifierPanel.style.width = '800px'; notifierPanel.style.height = '700px'; } notifierPanel.style.backgroundColor = '#2F2F2F'; notifierPanel.style.borderRadius = '20px'; notifierPanel.style.padding = '10px'; notifierPanel.style.display = 'none'; notifierPanel.style.zIndex = '1000'; // Add close button for the panel const closeButton = document.createElement('button'); closeButton.innerText = '✕'; closeButton.style.position = 'absolute'; closeButton.style.borderRadius = '50%'; closeButton.style.color = '#ffffff'; closeButton.style.top = '20px'; closeButton.style.right = '20px'; closeButton.style.backgroundColor = 'transparent'; closeButton.style.cursor = 'pointer'; closeButton.addEventListener('mouseenter', () => { closeButton.style.backgroundColor = '#676767'; }); closeButton.addEventListener('mouseleave', () => { closeButton.style.backgroundColor = 'transparent'; }); // Hide the Lorebook Panel closeButton.addEventListener('click', () => { notifierPanel.style.display = 'none'; }); // Open the Lorebook Panel notifierToggleButton.addEventListener('click', () => { notifierPanel.style.display = notifierPanel.style.display === 'none' ? 'block' : 'none'; loadProfileEntries(); loadKeyValueEntries(); }); // Create the profiles container on the left const profilesContainer = document.createElement('div'); profilesContainer.style.width = '200px'; profilesContainer.style.height = '100%'; profilesContainer.style.float = 'left'; profilesContainer.style.borderRight = '1px solid #676767'; profilesContainer.style.padding = '10px'; profilesContainer.style.boxSizing = 'border-box'; profilesContainer.style.overflowY = 'auto'; // Create the profile entries container const profilesList = document.createElement('div'); profilesContainer.appendChild(profilesList); // Create input to add new profiles const newProfileInput = document.createElement('input'); newProfileInput.type = 'text'; newProfileInput.placeholder = 'New Profile Name'; newProfileInput.style.width = 'calc(100% - 20px)'; newProfileInput.style.marginTop = '10px'; newProfileInput.style.padding = '5px'; newProfileInput.style.borderRadius = '5px'; newProfileInput.style.border = '1px solid #676767'; // Create button to add new profiles const addProfileButton = document.createElement('button'); addProfileButton.innerText = 'Add Profile'; addProfileButton.style.marginTop = '10px'; addProfileButton.style.padding = '5px 10px'; addProfileButton.style.cursor = 'pointer'; addProfileButton.style.backgroundColor = '#4CAF50'; addProfileButton.style.color = '#ffffff'; addProfileButton.style.border = 'none'; addProfileButton.style.borderRadius = '5px'; addProfileButton.addEventListener('click', () => { const profileName = newProfileInput.value.trim(); if (profileName) { const profileKey = `notifier.profile:${profileName}`; localStorage.setItem(profileKey, '{}'); newProfileInput.value = ''; loadProfileEntries(); } }); profilesContainer.appendChild(newProfileInput); profilesContainer.appendChild(addProfileButton); notifierPanel.appendChild(profilesContainer); // Create the key-value input container on the right const keyValueContainer = document.createElement('div'); keyValueContainer.style.width = 'calc(100% - 220px)'; keyValueContainer.style.height = '100%'; keyValueContainer.style.float = 'right'; keyValueContainer.style.padding = '10px'; keyValueContainer.style.boxSizing = 'border-box'; keyValueContainer.style.overflowY = 'auto'; // Create the key-value entries container const keyValueList = document.createElement('div'); keyValueContainer.appendChild(keyValueList); // Create input to add new key-value pairs const newKeyInput = document.createElement('input'); newKeyInput.type = 'text'; newKeyInput.placeholder = 'New Key'; newKeyInput.style.width = 'calc(100% - 20px)'; newKeyInput.style.marginTop = '10px'; newKeyInput.style.padding = '5px'; newKeyInput.style.borderRadius = '5px'; newKeyInput.style.border = '1px solid #676767'; const newValueInput = document.createElement('input'); newValueInput.type = 'text'; newValueInput.placeholder = 'New Value'; newValueInput.style.width = 'calc(100% - 20px)'; newValueInput.style.marginTop = '10px'; newValueInput.style.padding = '5px'; newValueInput.style.borderRadius = '5px'; newValueInput.style.border = '1px solid #676767'; // Create a datetime picker input const newDateTimeInput = document.createElement('input'); newDateTimeInput.type = 'datetime-local'; newDateTimeInput.style.width = 'calc(100% - 20px)'; newDateTimeInput.style.marginTop = '10px'; newDateTimeInput.style.padding = '5px'; newDateTimeInput.style.borderRadius = '5px'; newDateTimeInput.style.border = '1px solid #676767'; // Create start time input with a time selector 'wheel' const startTimeInput = document.createElement('input'); startTimeInput.type = 'time'; startTimeInput.style.width = 'calc(50% - 15px)'; startTimeInput.style.marginTop = '10px'; startTimeInput.style.marginRight = '10px'; startTimeInput.style.padding = '5px'; startTimeInput.style.borderRadius = '5px'; startTimeInput.style.border = '1px solid #676767'; // Create end time input with a time selector 'wheel' const endTimeInput = document.createElement('input'); endTimeInput.type = 'time'; endTimeInput.style.width = 'calc(50% - 15px)'; endTimeInput.style.marginTop = '10px'; endTimeInput.style.padding = '5px'; endTimeInput.style.borderRadius = '5px'; endTimeInput.style.border = '1px solid #676767'; // Create checkbox for recurring daily events const recurringDailyInput = document.createElement('input'); recurringDailyInput.type = 'checkbox'; recurringDailyInput.style.marginTop = '10px'; recurringDailyInput.style.marginRight = '5px'; const recurringDailyLabel = document.createElement('label'); recurringDailyLabel.innerText = 'Recurring Daily'; recurringDailyLabel.style.marginTop = '10px'; recurringDailyLabel.style.display = 'block'; recurringDailyLabel.style.cursor = 'pointer'; recurringDailyLabel.appendChild(recurringDailyInput); // Create button to add new key-value pairs const addKeyValueButton = document.createElement('button'); addKeyValueButton.innerText = 'Add Key-Value'; addKeyValueButton.style.marginTop = '10px'; addKeyValueButton.style.padding = '5px 10px'; addKeyValueButton.style.cursor = 'pointer'; addKeyValueButton.style.backgroundColor = '#4CAF50'; addKeyValueButton.style.color = '#ffffff'; addKeyValueButton.style.border = 'none'; addKeyValueButton.style.borderRadius = '5px'; addKeyValueButton.addEventListener('click', () => { const selectedProfile = localStorage.getItem('selectedProfile.notifier'); const keyName = newKeyInput.value.trim(); const valueText = newValueInput.value.trim(); const dateTime = newDateTimeInput.value; const startTime = startTimeInput.value; const endTime = endTimeInput.value; const isRecurringDaily = recurringDailyInput.checked; if (selectedProfile && keyName && valueText && (dateTime || (startTime && endTime))) { let value = `<[Notifier] ${valueText}>`; if (dateTime) { value += ` ${dateTime}`; } else if (startTime && endTime) { value += ` ${startTime}-${endTime}`; } if (isRecurringDaily) { value += ' (Recurring Daily)'; } const key = `${selectedProfile}.notifier:${keyName}`; localStorage.setItem(key, value); newKeyInput.value = ''; newValueInput.value = ''; newDateTimeInput.value = ''; startTimeInput.value = ''; endTimeInput.value = ''; recurringDailyInput.checked = false; loadKeyValueEntries(); } }); keyValueContainer.appendChild(newKeyInput); keyValueContainer.appendChild(newValueInput); keyValueContainer.appendChild(newDateTimeInput); keyValueContainer.appendChild(startTimeInput); keyValueContainer.appendChild(endTimeInput); keyValueContainer.appendChild(recurringDailyLabel); keyValueContainer.appendChild(addKeyValueButton); notifierPanel.appendChild(keyValueContainer); // Load key-value pairs into the key-value list function loadKeyValueEntries() { keyValueList.innerHTML = ''; const selectedProfile = localStorage.getItem('selectedProfile.notifier'); if (!selectedProfile) return; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key.startsWith(`${selectedProfile}.notifier:`)) { const value = localStorage.getItem(key); const keyValueItem = document.createElement('div'); keyValueItem.style.display = 'flex'; keyValueItem.style.justifyContent = 'space-between'; keyValueItem.style.alignItems = 'center'; keyValueItem.style.padding = '10px'; keyValueItem.style.cursor = 'pointer'; keyValueItem.style.backgroundColor = '#3F3F3F'; keyValueItem.style.marginBottom = '5px'; keyValueItem.style.borderRadius = '5px'; const keySpan = document.createElement('span'); keySpan.innerText = `${key.split(':')[1]}: ${value}`; keySpan.style.flex = '1'; const deleteButton = document.createElement('button'); deleteButton.innerText = 'x'; deleteButton.style.marginLeft = '10px'; deleteButton.style.padding = '2px 5px'; deleteButton.style.backgroundColor = '#FF6347'; deleteButton.style.color = '#ffffff'; deleteButton.style.border = 'none'; deleteButton.style.borderRadius = '3px'; deleteButton.style.cursor = 'pointer'; deleteButton.addEventListener('click', () => { localStorage.removeItem(key); loadKeyValueEntries(); }); keyValueItem.appendChild(keySpan); keyValueItem.appendChild(deleteButton); keyValueList.appendChild(keyValueItem); } } } // Load profiles into the profiles list function loadProfileEntries() { profilesList.innerHTML = ''; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key.startsWith('notifier.profile:')) { const profileName = key.split(':')[1]; const profileItem = document.createElement('div'); profileItem.style.display = 'flex'; profileItem.style.justifyContent = 'space-between'; profileItem.style.alignItems = 'center'; profileItem.style.padding = '10px'; profileItem.style.cursor = 'pointer'; profileItem.style.backgroundColor = '#3F3F3F'; profileItem.style.marginBottom = '5px'; profileItem.style.borderRadius = '5px'; const profileNameSpan = document.createElement('span'); profileNameSpan.innerText = profileName; profileNameSpan.style.flex = '1'; profileNameSpan.addEventListener('click', () => { localStorage.setItem('selectedProfile.notifier', profileName); highlightSelectedProfile(profileItem); loadKeyValueEntries(); }); const deleteButton = document.createElement('button'); deleteButton.innerText = 'x'; deleteButton.style.marginLeft = '10px'; deleteButton.style.padding = '2px 5px'; deleteButton.style.backgroundColor = '#FF6347'; deleteButton.style.color = '#ffffff'; deleteButton.style.border = 'none'; deleteButton.style.borderRadius = '3px'; deleteButton.style.cursor = 'pointer'; deleteButton.addEventListener('click', () => { localStorage.removeItem(key); if (localStorage.getItem('selectedProfile.notifier') === profileName) { localStorage.removeItem('selectedProfile.notifier'); } loadProfileEntries(); loadKeyValueEntries(); }); profileItem.appendChild(profileNameSpan); profileItem.appendChild(deleteButton); profilesList.appendChild(profileItem); } } highlightSelectedProfile(); } // Highlight the selected profile function highlightSelectedProfile(selectedItem = null) { const selectedProfile = localStorage.getItem('selectedProfile.notifier'); Array.from(profilesList.children).forEach(profileItem => { if (selectedProfile && profileItem.children[0].innerText === selectedProfile) { profileItem.style.backgroundColor = '#4CAF50'; } else { profileItem.style.backgroundColor = '#3F3F3F'; } }); if (selectedItem) { selectedItem.style.backgroundColor = '#4CAF50'; } } // Append close button to the main panel document.body.appendChild(notifierPanel); notifierPanel.appendChild(closeButton); let messageSentTimestamps = {}; // Store timestamps of sent messages function setInputValue() { const inputElement = document.querySelector('[data-slate-editor="true"]') || document.querySelector('textarea[class*="textArea_"]'); if (inputElement) { notifierActive = true; // Set notifier active to true // Get the selected profile from localStorage const selectedProfile = localStorage.getItem('selectedProfile.notifier'); if (!selectedProfile) { console.error("Selected profile not found in localStorage"); return; } // Retrieve messageSentTimestamps from localStorage to keep track of sent messages messageSentTimestamps = JSON.parse(localStorage.getItem('messageSentTimestamps') || '{}'); // Find a notifier message key that starts with `${selectedProfile}.notifier:` let notifierMessage = null; let messageKey = null; let timeframe = null; let isTriggeredByTimeframe = false; for (let i = 0; i < localStorage.length; i++) { const key = localStorage.key(i); if (key.startsWith(`${selectedProfile}.notifier:`)) { const storedValue = localStorage.getItem(key); const dateTimeMatches = storedValue.match(/^<\[Notifier\]\s+(.+)>\s+(\d{4}-\d{2}-\d{2}T\d{2}:\d{2})(\s+\(Recurring Daily\))?$/); const timeframeMatches = storedValue.match(/^<\[Notifier\]\s+(.+)>\s+(\d{2}:\d{2}-\d{2}:\d{2})$/); const now = new Date(); if (dateTimeMatches) { // Handle exact date-time based messages const message = dateTimeMatches[1]; // Full message content inside <> const dateTimeInfo = dateTimeMatches[2]; const isRecurring = !!dateTimeMatches[3]; const dateTime = new Date(dateTimeInfo); // Create a key to track if this message has been sent today const sentTodayKey = `${key}-${now.toDateString()}`; if (isRecurring) { // Check only the time for recurring messages if (dateTime.getHours() === now.getHours() && dateTime.getMinutes() === now.getMinutes() && !messageSentTimestamps[sentTodayKey]) { notifierMessage = `<[Notifier] ${message}>`; // Ensure the entire message is used messageKey = sentTodayKey; break; // Exit loop after finding the first match } } else { // Check both date and time for one-time messages if (dateTime.toDateString() === now.toDateString() && dateTime.getHours() === now.getHours() && dateTime.getMinutes() === now.getMinutes() && !messageSentTimestamps[sentTodayKey]) { notifierMessage = `<[Notifier] ${message}>`; // Ensure the entire message is used messageKey = sentTodayKey; break; // Exit loop after finding the first match } } } else if (timeframeMatches) { // Handle timeframe-based messages const message = timeframeMatches[1]; // Full message content inside <> timeframe = timeframeMatches[2]; const [startTime, endTime] = timeframe.split('-').map(time => { const [hours, minutes] = time.split(':').map(Number); return { hours, minutes }; }); const currentHour = now.getHours(); const currentMinutes = now.getMinutes(); // Create a key to track if this message has been sent today const sentTodayKey = `${key}-${now.toDateString()}`; // Check if the current time falls within the specified timeframe const isWithinTimeframe = ( (currentHour > startTime.hours || (currentHour === startTime.hours && currentMinutes >= startTime.minutes)) && (currentHour < endTime.hours || (currentHour === endTime.hours && currentMinutes <= endTime.minutes)) ); if (isWithinTimeframe && !messageSentTimestamps[sentTodayKey]) { // Calculate the total number of minutes in the range const startMinutes = startTime.hours * 60 + startTime.minutes; const endMinutes = endTime.hours * 60 + endTime.minutes; const totalMinutesInRange = endMinutes - startMinutes; // Generate a random minute within the range let randomMinuteInRange = startMinutes + Math.floor(Math.random() * totalMinutesInRange); // Convert the random minute back to hours and minutes let randomHour = Math.floor(randomMinuteInRange / 60); let randomMinute = randomMinuteInRange % 60; // Check if the current time matches the randomly chosen minute if (currentHour === randomHour && currentMinutes === randomMinute) { notifierMessage = `<[Notifier] ${message}>`; // Ensure the entire message is used messageKey = sentTodayKey; isTriggeredByTimeframe = true; break; } } } } } if (!notifierMessage) { notifierActive = false; // Reset notifierActive to avoid interference return; } inputElement.focus(); // Prevent the same message from being sent multiple times in the same minute const lastSentTime = messageSentTimestamps[messageKey]; const now = new Date(); if (lastSentTime && (now - new Date(lastSentTime)) < 60000) { console.log("Message already sent recently, skipping"); notifierActive = false; // Reset notifierActive to avoid interference return; } if (inputElement.getAttribute('data-slate-editor') === 'true') { const inputEvent = new InputEvent('beforeinput', { bubbles: true, cancelable: true, inputType: 'insertText', data: `${notifierMessage}`, // Use the full notifier message from localStorage }); inputElement.dispatchEvent(inputEvent); } else { // For mobile version, we need to activate the textarea properly const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value').set; nativeInputValueSetter.call(inputElement, inputElement.value + notifierMessage); const inputEvent = new Event('input', { bubbles: true, cancelable: true, }); inputElement.dispatchEvent(inputEvent); // Manually set the caret position to make it appear as though the user is interacting inputElement.setSelectionRange(inputElement.value.length, inputElement.value.length); } // Automatically send the message sendNotifierMessage(inputElement); notifierActive = false; // Record the timestamp of the sent message in both memory and localStorage messageSentTimestamps[messageKey] = now.toISOString(); localStorage.setItem('messageSentTimestamps', JSON.stringify(messageSentTimestamps)); } } function sendNotifierMessage(inputElement) { notifierActive = true; let sendButton = document.querySelector('button[aria-label="Nachricht senden"]'); if (sendButton) { sendButton.click(); console.log('Send button clicked to send message'); notifierActive = false; // Reset notifierActive after sending } 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'); notifierActive = false; // Reset notifierActive after sending } } function checkForMessages() { setInterval(() => { setInputValue(); }, 20000); // Check every 20 seconds } const observer = new MutationObserver(() => { if (document.querySelector('[data-slate-editor="true"]') || document.querySelector('textarea[class*="textArea_"]')) { setInputValue(); } }); observer.observe(document.body, { childList: true, subtree: true }); checkForMessages(); } // ================================================================= MAIN LOGIC ================================================================= function MainLogicScript() { // 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 window.settingsWindow = document.createElement('div'); settingsWindow.style.bottom = '60px'; settingsWindow.style.right = '20px'; settingsWindow.style.width = '250px'; settingsWindow.style.padding = '15px'; settingsWindow.style.color = 'white'; settingsWindow.style.borderRadius = '5px'; settingsWindow.style.zIndex = '1001'; // 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'; enableCustomRuleLabel.style.color = '#b0b0b0'; // Wrap Custom Rule elements in a div const customRuleDiv = document.createElement('div'); customRuleDiv.style.marginBottom = '10px'; // Add spacing customRuleDiv.appendChild(enableCustomRuleCheckbox); customRuleDiv.appendChild(enableCustomRuleLabel); // 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'; enableScanForKeywordsLabel.style.color = '#b0b0b0'; // Wrap Scan for Keywords elements in a div const scanForKeywordsDiv = document.createElement('div'); scanForKeywordsDiv.style.marginBottom = '10px'; // Add spacing scanForKeywordsDiv.appendChild(enableScanForKeywordsCheckbox); scanForKeywordsDiv.appendChild(enableScanForKeywordsLabel); // Append elements to settings window settingsWindow.appendChild(customRuleDiv); settingsWindow.appendChild(scanForKeywordsDiv); // 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 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) { if (notifierActive) return; const inputElement = getInputElement(); if (event.key === 'Enter' && !event.shiftKey && !enterKeyDisabled) { if (inputElement && inputElement.nodeName === 'TEXTAREA') { return; } event.preventDefault(); event.stopPropagation(); event.stopImmediatePropagation(); console.log('Enter key disabled'); enterKeyDisabled = true; handleEnterKey(); enterKeyDisabled = false; } }, true); // Add event listener to the send button to execute handleEnterKey when clicked window.addEventListener('click', function(event) { if (notifierActive) { return; // Skip handling if the notifier is active } const sendButton = document.querySelector('button[aria-label="Nachricht senden"]'); const inputElement = getInputElement(); if (sendButton && sendButton.contains(event.target)) { if (inputElement && inputElement.nodeName === 'TEXTAREA') { // Skip handleEnterKey() if notifier is active if (!notifierActive) { handleEnterKey(); } } } }, true); // Main function that handles Enter key behavior function handleEnterKey() { if (notifierActive) { console.log('Notifier is active, skipping handleEnterKey'); return; // Prevent handleEnterKey from executing if notifier is active } 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 = window.customRuleLogic ? window.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 get the current profile from local storage function getCurrentProfile() { return localStorage.getItem('selectedProfile.lorebook'); } // 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, converting them to lowercase const bracketContent = hiddenSpans.map(span => span.textContent.toLowerCase()).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.toLowerCase(); 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 getRandomEntry(inputElement) { const selectedProfile = localStorage.getItem('selectedProfile.events'); if (selectedProfile) { let profileEntries = []; const currentHour = new Date().getHours(); for (let key in localStorage) { if (key.startsWith(`${selectedProfile}.events:`)) { 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 window.getRandomEntry = getRandomEntry; } } // =================================================================== BUTTONS ================================================================== function createDivider() { const divider = document.createElement('div'); divider.style.height = '1px'; divider.style.backgroundColor = '#212121'; divider.style.margin = '20px 20px 0px 20px'; return divider; } function createFooterText() { const footerText = document.createElement('div'); footerText.textContent = '© Vishanka 2024'; footerText.style.position = 'absolute'; footerText.style.bottom = '10px'; footerText.style.left = '50%'; footerText.style.transform = 'translateX(-50%)'; footerText.style.fontSize = '12px'; footerText.style.fontWeight = '550'; footerText.style.color = '#272727'; return footerText; } function initializeButton() { DCstoragePanel.appendChild(openLorebookButton); DCstoragePanel.appendChild(eventsButton); DCstoragePanel.appendChild(manageRulesButton); DCstoragePanel.appendChild(notifierToggleButton); DCstoragePanel.appendChild(createDivider()); // Add divider after manageRulesButton DCstoragePanel.appendChild(serverbartoggleButton); DCstoragePanel.appendChild(mp3ToggleButton); DCstoragePanel.appendChild(createDivider()); // Add divider after mp3ToggleButton DCstoragePanel.appendChild(exportButton); DCstoragePanel.appendChild(importButton); DCstoragePanel.appendChild(createDivider()); // Add divider after importButton DCstoragePanel.appendChild(settingsWindow); DCstoragePanel.appendChild(createFooterText()); } // ============================================================ SCRIPT LOADING ORDER ============================================================ LorebookScript(); ImportExportScript(); EventsScript(); RulesScript(); mp3Script(); StylesScript(); HiderScript(); MainLogicScript(); NotifierScript(); initializeButton(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址