// ==UserScript==
// @name Discord/Shapes Tools
// @namespace https://vishanka.com
// @version 1.1
// @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';
// ================================================================== 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="
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: center;
cursor: pointer;
width: 90%;
transition: background-color 0.1s, color 0.1s;
z-index: 1001;">
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 = `
<div style="display: flex; align-items: center; justify-content: center;">
<button style="margin-top: 20px; margin-left: 4px; margin-right: 5px; padding: 7px 51px; font-size: 16px; cursor: pointer; background-color: transparent; color: #b0b0b0; border-radius: 8px; text-align: center; transition: background-color 0.1s, color 0.1s;">Export localStorage</button>
</div>
`;
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 = `
<div style="display: flex; align-items: center; justify-content: center;">
<button style="margin-top: 20px; margin-left: 4px; margin-right: 5px; padding: 7px 50px; font-size: 16px; cursor: pointer; background-color: transparent; color: #b0b0b0; border-radius: 8px; text-align: center; transition: background-color 0.1s, color 0.1s;">Import localStorage</button>
</div>
`;
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')) {
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;
background: transparent;
color: #b0b0b0;
border: none;
border-radius: 8px;
font-size: 16px;
text-align: center;
cursor: pointer;
width: 90%;
transition: background-color 0.1s, color 0.1s;
z-index: 1001;">
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: center;
cursor: pointer;
width: 90%;
transition: background-color 0.1s, color 0.1s;
z-index: 1001;">
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.overflowY = 'auto';
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);
manageRulesButton.addEventListener('click', () => {
rulesPanel.style.display = rulesPanel.style.display === 'none' ? 'block' : 'none';
renderPanel();
});
function renderPanel() {
rulesPanel.innerHTML = '';
rulesPanel.appendChild(title);
rulesPanel.appendChild(closeButton);
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);
const ruleInput = document.createElement('input');
ruleInput.type = 'text';
ruleInput.placeholder = rule.replace(/<Rule\d+: (.*?)>/, '$1'); // Only display the inner text as a placeholder
ruleInput.style.flex = '2';
ruleInput.style.padding = '5px';
ruleInput.style.borderRadius = '5px';
ruleInput.style.border = '0.5px solid #ccc';
ruleInput.style.backgroundColor = '#2F2F2F';
ruleInput.style.color = 'gray'; // Default color to indicate unsaved state
ruleContainer.appendChild(ruleInput);
// Change color to indicate unsaved changes
ruleInput.addEventListener('input', () => {
ruleInput.style.color = '#D16262';
});
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]];
customRules = customRules.map((rule, i) => `<Rule${i + 1}: ${rule.match(/<Rule\d+: (.*?)>/)[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]];
customRules = customRules.map((rule, i) => `<Rule${i + 1}: ${rule.match(/<Rule\d+: (.*?)>/)[1]}>`);
updateLocalStorageKeys();
renderPanel();
});
moveContainer.appendChild(downButton);
}
ruleContainer.appendChild(moveContainer);
rulesPanel.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);
}
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="
position: relative;
top: 10px;
right: 0px;
left: 10px;
padding: 7px 15px;
margin-top: 20px;
background: transparent;
color: #b0b0b0;
border: none;
border-radius: 8px;
font-size: 16px;
text-align: center;
cursor: pointer;
width: 90%;
transition: background-color 0.1s, color 0.1s;
z-index: 1001;">
Show MP3 Transcription
</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="
position: relative;
top: 10px;
right: 0px;
left: 10px;
padding: 7px 15px;
margin-top: 10px;
background: transparent;
color: #b0b0b0;
border: none;
border-radius: 8px;
font-size: 16px;
text-align: center;
cursor: pointer;
width: 90%;
transition: background-color 0.1s, color 0.1s;
z-index: 1001;">
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();
}
// ================================================================= 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.position = 'fixed';
settingsWindow.style.bottom = '60px';
settingsWindow.style.right = '20px';
settingsWindow.style.width = '250px';
settingsWindow.style.padding = '15px';
// settingsWindow.style.backgroundColor = '#2f3136';
settingsWindow.style.color = 'white';
// settingsWindow.style.border = '1px solid #5865F2';
settingsWindow.style.borderRadius = '5px';
// settingsWindow.style.display = 'none';
settingsWindow.style.zIndex = '1001';
// DCstoragePanel.appendChild(settingsWindow);
// Custom Rule Checkbox
const enableCustomRuleCheckbox = document.createElement('input');
enableCustomRuleCheckbox.type = 'checkbox';
enableCustomRuleCheckbox.checked = customRuleEnabled;
enableCustomRuleCheckbox.id = 'enableCustomRuleCheckbox';
const enableCustomRuleLabel = document.createElement('label');
enableCustomRuleLabel.htmlFor = 'enableCustomRuleCheckbox';
enableCustomRuleLabel.innerText = ' Enable Custom Rules';
// Scan for Keywords Checkbox
const enableScanForKeywordsCheckbox = document.createElement('input');
enableScanForKeywordsCheckbox.type = 'checkbox';
enableScanForKeywordsCheckbox.checked = scanForKeywordsEnabled;
enableScanForKeywordsCheckbox.id = 'enableScanForKeywordsCheckbox';
const enableScanForKeywordsLabel = document.createElement('label');
enableScanForKeywordsLabel.htmlFor = 'enableScanForKeywordsCheckbox';
enableScanForKeywordsLabel.innerText = ' Enable Lorebook';
// Append elements to settings window
settingsWindow.appendChild(enableCustomRuleCheckbox);
settingsWindow.appendChild(enableCustomRuleLabel);
settingsWindow.appendChild(document.createElement('br'));
settingsWindow.appendChild(enableScanForKeywordsCheckbox);
settingsWindow.appendChild(enableScanForKeywordsLabel);
// document.body.appendChild(settingsWindow);
// Update customRuleEnabled when checkbox is toggled, and save it in localStorage
enableCustomRuleCheckbox.addEventListener('change', function() {
customRuleEnabled = enableCustomRuleCheckbox.checked;
localStorage.setItem('customRuleEnabled', JSON.stringify(customRuleEnabled));
});
// Update scanForKeywordsEnabled when checkbox is toggled, and save it in localStorage
enableScanForKeywordsCheckbox.addEventListener('change', function() {
scanForKeywordsEnabled = enableScanForKeywordsCheckbox.checked;
localStorage.setItem('scanForKeywordsEnabled', JSON.stringify(scanForKeywordsEnabled));
});
// Function to get the correct input element based on the mode
// Function to get the correct input element based on the mode
// Function to get the correct input element based on the mode
function getInputElement() {
return document.querySelector('[data-slate-editor="true"]') || document.querySelector('textarea[class*="textArea_"]');
}
// Add event listener to handle Enter key behavior
window.addEventListener('keydown', function(event) {
const inputElement = getInputElement();
if (event.key === 'Enter' && !event.shiftKey && !enterKeyDisabled) {
if (inputElement && inputElement.nodeName === 'TEXTAREA') {
// Mobile version: Only allow line break
return;
}
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
console.log('Enter key disabled');
enterKeyDisabled = true;
// Execute main handler for Enter key
handleEnterKey();
enterKeyDisabled = false;
}
}, true); // Use capture mode to intercept the event before Discord's handlers
// Add event listener to the send button to execute handleEnterKey when clicked
window.addEventListener('click', function(event) {
const sendButton = document.querySelector('button[aria-label="Nachricht senden"]');
if (sendButton && sendButton.contains(event.target)) {
// Execute main handler for Enter key
handleEnterKey();
}
}, true);
// Main function that handles Enter key behavior
function handleEnterKey() {
let inputElement = getInputElement();
if (inputElement) {
inputElement.focus();
setCursorToEnd(inputElement);
if (customRuleEnabled) {
applyCustomRule(inputElement);
}
setCursorToEnd(inputElement);
if (scanForKeywordsEnabled) {
scanForKeywords(inputElement);
}
getRandomEntry(inputElement);
sendMessage(inputElement);
anotherCustomFunction();
}
}
// Function to apply custom rules for the input field
function applyCustomRule(inputElement) {
const customRule = 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 initializeButton() {
DCstoragePanel.appendChild(openLorebookButton);
DCstoragePanel.appendChild(eventsButton);
DCstoragePanel.appendChild(manageRulesButton);
DCstoragePanel.appendChild(serverbartoggleButton);
DCstoragePanel.appendChild(mp3ToggleButton);
DCstoragePanel.appendChild(exportButton);
DCstoragePanel.appendChild(importButton);
DCstoragePanel.appendChild(settingsWindow);
}
// ============================================================ SCRIPT LOADING ORDER ============================================================
LorebookScript();
ImportExportScript();
EventsScript();
RulesScript();
mp3Script();
StylesScript();
HiderScript();
MainLogicScript();
initializeButton();
})();