Discord/Shapes - Lorebook

Storing and loading Lorebook Entries for Discord/Shapes

  1. // ==UserScript==
  2. // @name Discord/Shapes - Lorebook
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.8
  5. // @description Storing and loading Lorebook Entries for Discord/Shapes
  6. // @author Vishanka
  7. // @match https://discord.com/channels/*
  8. // @grant unsafeWindow
  9. // @run-at document-idle
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // Create and add the arrow button to open the storage panel
  16. const arrowButton = document.createElement('div');
  17. arrowButton.innerHTML = '〈'; // Unicode for a more spread left-pointing angle bracket
  18. arrowButton.style.position = 'fixed';
  19. arrowButton.style.bottom = '50%';
  20. arrowButton.style.right = '0'; // Start visible at the screen edge
  21. arrowButton.style.padding = '10px';
  22. arrowButton.style.fontSize = '24px';
  23. arrowButton.style.zIndex = '1000';
  24. arrowButton.style.cursor = 'pointer';
  25. //arrowButton.style.backgroundColor = '#212121';
  26. arrowButton.style.color = '#B4B4B4'; // Light grey color
  27. arrowButton.style.borderRadius = '5px 0 0 5px';
  28. arrowButton.style.transition = 'transform 0.3s ease, right 0.3s ease, background-color 0.1s';
  29.  
  30. arrowButton.onmouseover = () => {
  31. arrowButton.style.backgroundColor = 'transparent';
  32. };
  33. arrowButton.onmouseout = () => {
  34. arrowButton.style.backgroundColor = 'transparent';
  35. };
  36.  
  37. document.body.appendChild(arrowButton);
  38. // Create the fancy sliding panel
  39. unsafeWindow.DCstoragePanel = document.createElement('div');
  40. DCstoragePanel.style.position = 'fixed';
  41. DCstoragePanel.style.top = '0';
  42. DCstoragePanel.style.right = '-250px'; // Initially hidden
  43. DCstoragePanel.style.height = '100%';
  44. DCstoragePanel.style.width = '250px';
  45. DCstoragePanel.style.backgroundColor = '#171717';
  46. //storagePanel.style.boxShadow = '-4px 0px 8px rgba(0, 0, 0, 0.5)';
  47. DCstoragePanel.style.transition = 'right 0.3s ease';
  48. DCstoragePanel.style.zIndex = '999';
  49.  
  50. document.body.appendChild(DCstoragePanel);
  51.  
  52. // Create the header above the button
  53. const storagePanelHeader = document.createElement('div');
  54. storagePanelHeader.innerText = 'Shapes Tools';
  55. storagePanelHeader.style.margin = '20px';
  56. storagePanelHeader.style.padding = '10px';
  57. storagePanelHeader.style.fontSize = '19px';
  58. storagePanelHeader.style.fontWeight = '550';
  59. storagePanelHeader.style.color = '#ECECEC';
  60. storagePanelHeader.style.textAlign = 'center';
  61.  
  62. DCstoragePanel.appendChild(storagePanelHeader);
  63.  
  64. // Create a divider line
  65. const dividerLine = document.createElement('div');
  66. dividerLine.style.height = '1px';
  67. dividerLine.style.backgroundColor = '#212121'; // Dark grey color for subtle separation
  68. dividerLine.style.margin = '10px 20px'; // Space around the line (top/bottom, left/right)
  69. DCstoragePanel.appendChild(dividerLine);
  70. // Create the button inside the panel
  71.  
  72. // Create the button to open the panel
  73. const button = document.createElement('button');
  74. button.id = 'profileManagerButton';
  75. button.innerText = 'Manage Lorebook';
  76. button.style.marginTop = '20px';
  77. button.style.marginLeft = '13px';
  78. button.style.marginRight = '5px';
  79. button.style.padding = '7px 15px';
  80. button.style.fontSize = '16px';
  81. button.style.cursor = 'pointer';
  82. button.style.backgroundColor = 'transparent';
  83. button.style.color = '#b0b0b0'; // Light grey text color
  84. button.style.borderRadius = '8px';
  85. button.style.textAlign = 'center';
  86. button.style.width = '90%';
  87. button.style.transition = 'background-color 0.1s, color 0.1s';
  88.  
  89. button.onmouseover = () => {
  90. button.style.backgroundColor = '#212121';
  91. button.style.color = '#ffffff';
  92. };
  93. button.onmouseout = () => {
  94. button.style.backgroundColor = 'transparent';
  95. button.style.color = '#b0b0b0';
  96. };
  97.  
  98. DCstoragePanel.appendChild(button);
  99. //DCstoragePanel.appendChild(uiButton);
  100. //DCstoragePanel.appendChild(settingsWindow);
  101. //DCstoragePanel.appendChild(importButton);
  102. //DCstoragePanel.appendChild(exportButton);
  103. // Toggle panel visibility
  104. arrowButton.addEventListener('click', () => {
  105. if (DCstoragePanel.style.right === '-250px') {
  106. DCstoragePanel.style.right = '0';
  107. arrowButton.style.right = '250px'; // Clamp arrow button to the panel border
  108. arrowButton.style.transform = 'rotate(180deg)'; // Rotate the arrow
  109. } else {
  110. DCstoragePanel.style.right = '-250px';
  111. arrowButton.style.right = '0'; // Keep arrow button visible at the screen edge
  112. arrowButton.style.transform = 'rotate(0deg)';
  113. }
  114. });
  115.  
  116.  
  117.  
  118.  
  119.  
  120.  
  121. // Create the main panel container
  122. const panel = document.createElement('div');
  123. panel.id = 'profileManagerPanel';
  124. panel.style.position = 'fixed';
  125. panel.style.top = '50%';
  126. panel.style.left = '50%';
  127. panel.style.transform = 'translate(-50%, -50%)';
  128.  
  129. // Check if the device is mobile
  130. if (window.innerWidth <= 768) { // Example threshold for mobile
  131. panel.style.width = '90%'; // Wider for mobile
  132. panel.style.height = '90%'; // Taller for mobile
  133. } else {
  134. panel.style.width = '800px'; // Default for non-mobile
  135. panel.style.height = '700px'; // Default for non-mobile
  136. }
  137.  
  138. panel.style.backgroundColor = '#2F2F2F';
  139. panel.style.borderRadius = '20px';
  140. panel.style.padding = '10px';
  141. panel.style.display = 'none';
  142. panel.style.zIndex = '1000';
  143. document.body.appendChild(panel);
  144.  
  145. // Add close button for the panel
  146. const closeButton = document.createElement('button');
  147. closeButton.innerText = '✕';
  148. closeButton.style.position = 'absolute';
  149. closeButton.style.borderRadius = '50%';
  150. closeButton.style.color = '#ffffff';
  151. closeButton.style.top = '20px';
  152. closeButton.style.right = '20px';
  153. closeButton.style.backgroundColor = 'transparent';
  154. closeButton.style.cursor = 'pointer';
  155.  
  156. // Hover effect
  157. closeButton.addEventListener('mouseenter', () => {
  158. // closeButton.style.transform = 'scale(1.1)';
  159. closeButton.style.backgroundColor = '#676767';
  160. });
  161.  
  162. closeButton.addEventListener('mouseleave', () => {
  163. // closeButton.style.transform = 'scale(1)';
  164. closeButton.style.backgroundColor = 'transparent';
  165. });
  166.  
  167.  
  168. panel.appendChild(closeButton);
  169.  
  170.  
  171.  
  172. closeButton.addEventListener('click', () => {
  173. panel.style.display = 'none';
  174. });
  175.  
  176. // Toggle panel visibility when button is clicked
  177. button.addEventListener('click', () => {
  178. panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
  179. loadProfileEntries();
  180. });
  181.  
  182. const profileslabel = document.createElement('div');
  183. profileslabel.textContent = 'Profiles';
  184. profileslabel.style.color = '#dddddd';
  185. profileslabel.style.fontSize = '14px';
  186. profileslabel.style.marginBottom = '5px';
  187. profileslabel.style.marginLeft = '3px';
  188. profileslabel.style.marginTop = '5px';
  189. profileslabel.style.fontSize = '20px';
  190. profileslabel.style.fontWeight = '550';
  191. panel.appendChild(profileslabel);
  192. // Create the profile management panel
  193. const profilePanel = document.createElement('div');
  194. profilePanel.id = 'profilePanel';
  195. profilePanel.style.float = 'left';
  196. profilePanel.style.width = '20%';
  197. profilePanel.style.borderRight = '0.5px solid #444444';
  198. profilePanel.style.height = '93%';
  199.  
  200. panel.appendChild(profilePanel);
  201.  
  202. // Create the profile list container
  203. const profileList = document.createElement('div');
  204. profileList.id = 'profileList';
  205. profileList.style.height = '95%';
  206. profileList.style.color = 'white';
  207. profileList.style.overflowY = 'auto';
  208.  
  209. profilePanel.appendChild(profileList);
  210.  
  211. // Create the add profile button
  212. const addProfileButton = document.createElement('button');
  213. addProfileButton.innerText = 'Add Profile';
  214. addProfileButton.style.padding = '8px';
  215. addProfileButton.style.border = '0.2px solid #4E4E4E';
  216. addProfileButton.style.backgroundColor = 'transparent';
  217. addProfileButton.style.color = '#fff';
  218. addProfileButton.style.borderRadius = '20px';
  219. addProfileButton.style.width = '90%';
  220. addProfileButton.style.cursor = 'pointer';
  221.  
  222. addProfileButton.onmouseover = () => {
  223. addProfileButton.style.backgroundColor = '#424242';
  224. };
  225. addProfileButton.onmouseout = () => {
  226. addProfileButton.style.backgroundColor = 'transparent';
  227. };
  228. profilePanel.appendChild(addProfileButton);
  229.  
  230.  
  231. // Add functionality to add profile button
  232. addProfileButton.addEventListener('click', () => {
  233. const profileName = prompt('Enter profile name:');
  234. if (profileName) {
  235. const profileKey = `profile-${profileName}`;
  236. if (!localStorage.getItem(profileKey)) {
  237. localStorage.setItem(profileKey, JSON.stringify({})); // Save profile under key "profile-[profilename]" with empty value
  238. loadProfiles();
  239. } else {
  240. alert('Profile already exists.');
  241. }
  242. }
  243. });
  244.  
  245. // Create the profile selection functionality
  246. function loadProfiles() {
  247. profileList.innerHTML = '';
  248. Object.keys(localStorage).forEach(profileKey => {
  249. if (profileKey.startsWith('profile-')) {
  250. const profileName = profileKey.replace('profile-', '');
  251. const profileItem = document.createElement('div');
  252. profileItem.innerText = profileName;
  253. profileItem.style.padding = '5px';
  254. profileItem.style.marginBottom = '5px';
  255. profileItem.style.cursor = 'pointer';
  256. profileItem.style.backgroundColor = profileName === getCurrentProfile() ? '#424242' : '#2F2F2F';
  257. profileItem.style.borderRadius = '5px';
  258. profileItem.style.width = '90%';
  259. profileItem.style.position = 'relative';
  260.  
  261. profileItem.addEventListener('click', () => {
  262. setCurrentProfile(profileName);
  263. loadProfiles();
  264. loadProfileEntries();
  265. });
  266.  
  267. const removeButton = document.createElement('button');
  268. removeButton.innerText = '✕';
  269. removeButton.style.position = 'absolute';
  270. removeButton.style.top = '3px';
  271. removeButton.style.right = '10px';
  272. removeButton.style.cursor = 'pointer';
  273. removeButton.style.backgroundColor = 'transparent';
  274. removeButton.style.color = 'white';
  275. removeButton.addEventListener('click', (e) => {
  276. e.stopPropagation();
  277. localStorage.removeItem(profileKey);
  278. if (profileName === getCurrentProfile()) {
  279. setCurrentProfile(null);
  280. }
  281. loadProfiles();
  282. loadProfileEntries();
  283. });
  284.  
  285. profileItem.appendChild(removeButton);
  286. profileList.appendChild(profileItem);
  287. }
  288. });
  289. }
  290.  
  291.  
  292.  
  293. const title = document.createElement('h3');
  294. title.innerText = 'Manage Lorebook Entries';
  295. title.style.fontWeight = 'normal';
  296. title.style.color = '#ffffff';
  297. title.style.textAlign = 'left';
  298. title.style.fontSize = '24px';
  299. title.style.marginTop = '20px';
  300. title.style.position = 'relative';
  301. title.style.marginLeft = '23%';
  302. title.style.marginBottom = '15px';
  303. title.style.fontWeight = '550';
  304. panel.appendChild(title);
  305. // Create profile entries list
  306. const profileEntriesList = document.createElement('div');
  307. profileEntriesList.id = 'profileEntriesList';
  308. profileEntriesList.style.marginTop = '20px';
  309. profileEntriesList.style.height = '48%';
  310. // Check if the device is mobile
  311. if (window.innerWidth <= 768) { // Example threshold for mobile
  312. profileEntriesList.style.height = '30%';
  313. } else {
  314. profileEntriesList.style.height = '48%';
  315. }
  316.  
  317.  
  318. profileEntriesList.style.overflowY = 'auto';
  319. panel.appendChild(profileEntriesList);
  320.  
  321.  
  322. const entrieslabel = document.createElement('div');
  323. entrieslabel.textContent = 'Enter keys and description:';
  324. entrieslabel.style.color = '#dddddd';
  325. entrieslabel.style.fontSize = '14px';
  326. entrieslabel.style.marginBottom = '5px';
  327. entrieslabel.style.marginTop = '5px';
  328. entrieslabel.style.marginLeft = '23%';
  329. panel.appendChild(entrieslabel);
  330. // Create key-value input fields
  331. const inputContainer = document.createElement('div');
  332. inputContainer.id = 'inputContainer';
  333. inputContainer.style.marginTop = '10px';
  334. inputContainer.style.display = 'flex';
  335. inputContainer.style.flexDirection = 'column';
  336. inputContainer.style.alignItems = 'center'; // Center children horizontally
  337. inputContainer.style.margin = '0 auto';
  338. panel.appendChild(inputContainer);
  339.  
  340. const keyInput = document.createElement('input');
  341. // keyInput.id = 'keyInput';
  342. keyInput.type = 'text';
  343. keyInput.placeholder = 'Entry Keywords (comma-separated)';
  344. keyInput.style.width = '90%';
  345. keyInput.style.marginBottom = '5px';
  346. keyInput.style.padding = '10px';
  347. keyInput.style.border = '1px solid #444444';
  348. keyInput.style.borderRadius = '8px';
  349. keyInput.style.backgroundColor = '#1e1e1e';
  350. keyInput.style.color = '#dddddd';
  351. inputContainer.appendChild(keyInput);
  352.  
  353. const valueInput = document.createElement('textarea');
  354. // valueInput.id = 'valueInput';
  355. valueInput.placeholder = ' ';
  356. valueInput.style.width = '90%';
  357. valueInput.style.marginBottom = '5px';
  358. valueInput.style.padding = '10px';
  359. valueInput.style.border = '1px solid #444444';
  360. valueInput.style.borderRadius = '8px';
  361. valueInput.style.backgroundColor = '#1e1e1e';
  362. valueInput.style.color = '#dddddd';
  363. valueInput.style.height = '100px';
  364. valueInput.style.resize = 'vertical';
  365. valueInput.maxLength = 1000;
  366. valueInput.style.overflow = 'auto';
  367.  
  368. const charCounter = document.createElement('div');
  369. charCounter.style.color = '#dddddd';
  370. charCounter.style.fontSize = '12px';
  371. charCounter.style.marginTop = '0px';
  372. charCounter.style.marginBottom = '15px';
  373. charCounter.style.textAlign = 'right';
  374. charCounter.style.marginRight = '-87%';
  375. charCounter.style.color = 'grey';
  376. charCounter.textContent = `0/${valueInput.maxLength}`;
  377.  
  378. // Update the counter as the user types
  379. valueInput.addEventListener('input', () => {
  380. charCounter.textContent = `${valueInput.value.length}/${valueInput.maxLength}`;
  381. });
  382.  
  383.  
  384.  
  385. inputContainer.appendChild(valueInput);
  386. inputContainer.appendChild(charCounter);
  387. const saveButton = document.createElement('button');
  388. saveButton.innerText = 'Add Entry';
  389. saveButton.style.padding = '10px 20px';
  390. saveButton.style.border = '0.2px solid #4E4E4E';
  391. saveButton.style.backgroundColor = '#2F2F2F';
  392. saveButton.style.color = '#fff';
  393. saveButton.style.borderRadius = '50px';
  394. saveButton.style.cursor = 'pointer';
  395. saveButton.style.width = '95%';
  396.  
  397. inputContainer.appendChild(saveButton);
  398.  
  399. let isEditing = false;
  400. let editingKey = '';
  401.  
  402. saveButton.addEventListener('click', () => {
  403. const key = keyInput.value.trim(); // Trim any extra spaces
  404. const value = valueInput.value;
  405. const currentProfile = getCurrentProfile();
  406.  
  407. if (key && currentProfile) {
  408. const profileKey = `${currentProfile}-lorebook:${key.toLowerCase()}`; // Normalize to lowercase
  409. const formattedValue = `<[Lorebook: ${key}] ${value}>`;
  410.  
  411. // Check for duplicate keys (case-insensitive)
  412. const isDuplicateKey = Object.keys(localStorage).some(storageKey => {
  413. return storageKey.toLowerCase() === profileKey.toLowerCase();
  414. });
  415.  
  416. // Allow overwrite if editing, otherwise enforce uniqueness
  417. if (!isEditing && isDuplicateKey) {
  418. alert('The key is already used in an existing entry (case-insensitive). Please use a different key.');
  419. return;
  420. }
  421.  
  422. localStorage.setItem(profileKey, formattedValue);
  423. // alert('Saved!');
  424. keyInput.value = ''; // Clear the key input
  425. valueInput.value = ''; // Clear the value input
  426. isEditing = false;
  427. editingKey = '';
  428. loadProfileEntries();
  429. } else {
  430. alert('Please select a profile and enter a key.');
  431. }
  432. });
  433.  
  434.  
  435. function loadProfileEntries() {
  436. profileEntriesList.innerHTML = '';
  437. profileEntriesList.style.display = 'flex';
  438. profileEntriesList.style.flexDirection = 'column';
  439. profileEntriesList.style.alignItems = 'center'; // Center children horizontally
  440. profileEntriesList.style.margin = '0 auto'; // Center the entire container
  441.  
  442. const currentProfile = getCurrentProfile();
  443. if (currentProfile) {
  444. Object.keys(localStorage).forEach(storageKey => {
  445. if (storageKey.startsWith(`${currentProfile}-lorebook:`)) {
  446. const entryKey = storageKey.replace(`${currentProfile}-lorebook:`, '');
  447. const entryValue = localStorage.getItem(storageKey);
  448. const displayedValue = entryValue.replace(/^<\[Lorebook:.*?\]\s*/, '').replace(/>$/, '');
  449.  
  450. const entryItem = document.createElement('div');
  451. entryItem.style.padding = '10px';
  452. entryItem.style.marginBottom = '12px';
  453. entryItem.style.borderRadius = '8px';
  454. // entryItem.style.borderBottom = '0.5px solid #424242';
  455. entryItem.style.backgroundColor = '#424242';
  456. entryItem.style.position = 'relative';
  457. entryItem.style.color = 'white';
  458. entryItem.style.flexDirection = 'column';
  459. entryItem.style.width = '90%';
  460.  
  461.  
  462. const keyElement = document.createElement('div');
  463. keyElement.innerText = entryKey;
  464. keyElement.style.fontWeight = 'bold';
  465. keyElement.style.marginBottom = '10px';
  466. entryItem.appendChild(keyElement);
  467.  
  468. const valueElement = document.createElement('div');
  469. valueElement.innerText = displayedValue;
  470. entryItem.appendChild(valueElement);
  471.  
  472. entryItem.addEventListener('click', () => {
  473. keyInput.value = entryKey;
  474. valueInput.value = entryValue.replace(`<[Lorebook: ${entryKey}] `, '').replace('>', '');
  475. isEditing = true;
  476. editingKey = entryKey;
  477. });
  478.  
  479. const removeButton = document.createElement('button');
  480. removeButton.innerText = '✕';
  481. removeButton.style.position = 'absolute';
  482. removeButton.style.top = '10px';
  483. removeButton.style.right = '10px';
  484. removeButton.style.cursor = 'pointer';
  485. removeButton.style.backgroundColor = 'transparent';
  486. removeButton.style.color = 'white';
  487. removeButton.addEventListener('click', () => {
  488. localStorage.removeItem(storageKey);
  489. loadProfileEntries();
  490. });
  491.  
  492. entryItem.appendChild(removeButton);
  493. profileEntriesList.appendChild(entryItem);
  494. }
  495. });
  496. }
  497. }
  498.  
  499.  
  500. // Utility functions to manage profiles and local storage
  501. function getCurrentProfile() {
  502. return localStorage.getItem('currentProfile');
  503. }
  504.  
  505. function setCurrentProfile(profileName) {
  506. localStorage.setItem('currentProfile', profileName);
  507. }
  508.  
  509. // Initial load
  510. loadProfiles();
  511. })();