BHD Saved Searches

Adds combobox and related buttons to save searches

  1. // ==UserScript==
  2. // @name BHD Saved Searches
  3. // @namespace Violentmonkey Scripts
  4. // @version 1.1
  5. // @description Adds combobox and related buttons to save searches
  6. // @author CodeX0
  7. // @match *://beyond-hd.me/library*
  8. // @exclude *://beyond-hd.me/library/title*
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_deleteValue
  12. // @grant GM_addStyle
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18.  
  19. if (document.getElementById('bhd-search-manager')) {
  20. return;
  21. }
  22.  
  23. GM_addStyle(`
  24. #bhd-search-manager {
  25. display: flex;
  26. align-items: center;
  27. gap: 8px;
  28. margin-left: 2%;
  29. margin-top: 10px;
  30. }
  31. #bhd-search-select {
  32. width: 200px !important;
  33. padding: 6px 10px !important;
  34. border-radius: 4px !important;
  35. border: 1px solid #766d61 !important;
  36. }
  37. .bhd-search-btn {
  38. background: #555c63 !important;
  39. color: white !important;
  40. border: none !important;
  41. border-radius: 4px !important;
  42. padding: 6px 12px !important;
  43. cursor: pointer !important;
  44. font-size: 13px !important;
  45. white-space: nowrap;
  46. }
  47. .bhd-search-btn.delete {
  48. background: #800101 !important;
  49. }
  50. .bhd-search-btn:hover {
  51. opacity: 0.9;
  52. }
  53. `);
  54.  
  55. class SearchManager {
  56. constructor() {
  57. this.searches = GM_getValue('librarySearches', []);
  58. }
  59.  
  60. addSearch(name, url) {
  61. const cleanUrl = url.split('#')[0];
  62.  
  63. if (this.searches.some(search => search.name === name || search.url === cleanUrl)) {
  64. return false;
  65. }
  66.  
  67. this.searches.push({ name, url: cleanUrl });
  68. GM_setValue('librarySearches', this.searches);
  69. return true;
  70. }
  71.  
  72. deleteSearch(name) {
  73. const initialLength = this.searches.length;
  74. this.searches = this.searches.filter(search => search.name !== name);
  75.  
  76. if (this.searches.length !== initialLength) {
  77. GM_setValue('librarySearches', this.searches);
  78. return true;
  79. }
  80. return false;
  81. }
  82.  
  83. getSearches() {
  84. return this.searches;
  85. }
  86. }
  87.  
  88. class UI {
  89. constructor(searchManager) {
  90. this.searchManager = searchManager;
  91. this.createUI();
  92. }
  93.  
  94. createUI() {
  95. const container = document.createElement('div');
  96. container.id = 'bhd-search-manager';
  97.  
  98. this.selectBox = document.createElement('select');
  99. this.selectBox.id = 'bhd-search-select';
  100.  
  101. const searchBtn = this.createButton('Search', () => this.loadSearch());
  102. const saveBtn = this.createButton('Save', () => this.saveSearch());
  103. const deleteBtn = this.createButton('Delete', () => this.deleteSearch(), 'delete');
  104.  
  105. const placeholderOption = document.createElement('option');
  106. placeholderOption.value = '';
  107. placeholderOption.textContent = 'Saved Searches';
  108. placeholderOption.disabled = true;
  109. placeholderOption.selected = true;
  110. this.selectBox.appendChild(placeholderOption);
  111.  
  112. this.updateSelectBox();
  113.  
  114. container.appendChild(this.selectBox);
  115. container.appendChild(searchBtn);
  116. container.appendChild(saveBtn);
  117. container.appendChild(deleteBtn);
  118.  
  119. this.addToPage(container);
  120. }
  121.  
  122. createButton(text, onClick, className = '') {
  123. const btn = document.createElement('button');
  124. btn.className = `bhd-search-btn ${className}`;
  125. btn.textContent = text;
  126. btn.addEventListener('click', onClick);
  127. return btn;
  128. }
  129.  
  130. addToPage(container) {
  131. const exactContainer = document.querySelector('.bhd-outer > #stickyBar > .text-center');
  132.  
  133. const targetContainer = exactContainer ||
  134. document.querySelector('#stickyBar .text-center') ||
  135. document.querySelector('.text-center') ||
  136. document.querySelector('.navbar-header');
  137.  
  138. if (targetContainer) {
  139. if (!targetContainer.querySelector('#bhd-search-manager')) {
  140. targetContainer.appendChild(container);
  141. }
  142. } else {
  143. console.warn('Could not find suitable container for search manager');
  144. }
  145. }
  146.  
  147. updateSelectBox() {
  148. while (this.selectBox.options.length > 1) {
  149. this.selectBox.remove(1);
  150. }
  151.  
  152. this.searchManager.getSearches().forEach(search => {
  153. const option = document.createElement('option');
  154. option.value = search.url;
  155. option.textContent = search.name;
  156. this.selectBox.appendChild(option);
  157. });
  158.  
  159. if (this.selectBox.options.length > 0) {
  160. this.selectBox.selectedIndex = 0;
  161. }
  162. }
  163.  
  164. saveSearch() {
  165. const currentUrl = window.location.href;
  166. const searchName = prompt('Enter a name for this search:',
  167. `Search ${this.searchManager.getSearches().length + 1}`);
  168.  
  169. if (searchName && searchName.trim()) {
  170. if (this.searchManager.addSearch(searchName.trim(), currentUrl)) {
  171. this.updateSelectBox();
  172. this.showMessage('Search saved successfully!');
  173. } else {
  174. this.showMessage('This search already exists!', true);
  175. }
  176. }
  177. }
  178.  
  179. loadSearch() {
  180. const selectedIndex = this.selectBox.selectedIndex;
  181. if (selectedIndex > 0) {
  182. const selectedUrl = this.selectBox.options[selectedIndex].value;
  183. window.location.href = selectedUrl;
  184. } else {
  185. this.showMessage('Please select a search first!', true);
  186. }
  187. }
  188.  
  189. deleteSearch() {
  190. const selectedIndex = this.selectBox.selectedIndex;
  191. if (selectedIndex > 0) {
  192. const searchName = this.selectBox.options[selectedIndex].text;
  193. if (confirm(`Are you sure you want to delete "${searchName}"?`)) {
  194. if (this.searchManager.deleteSearch(searchName)) {
  195. this.updateSelectBox();
  196. this.showMessage('Search deleted successfully!');
  197. }
  198. }
  199. } else {
  200. this.showMessage('Please select a search to delete!', true);
  201. }
  202. }
  203.  
  204. showMessage(text, isError = false) {
  205. const msg = document.createElement('div');
  206. msg.textContent = text;
  207. msg.style.position = 'fixed';
  208. msg.style.bottom = '20px';
  209. msg.style.right = '20px';
  210. msg.style.padding = '10px 15px';
  211. msg.style.backgroundColor = isError ? '#e74c3c' : '#2ecc71';
  212. msg.style.color = 'white';
  213. msg.style.borderRadius = '4px';
  214. msg.style.zIndex = '9999';
  215. document.body.appendChild(msg);
  216.  
  217. setTimeout(() => {
  218. msg.style.transition = 'opacity 0.5s';
  219. msg.style.opacity = '0';
  220. setTimeout(() => msg.remove(), 500);
  221. }, 3000);
  222. }
  223. }
  224.  
  225. function initializeSearchManager() {
  226. if (!window.location.pathname.includes('/library')) return;
  227.  
  228. if (document.getElementById('bhd-search-manager')) return;
  229.  
  230. const searchManager = new SearchManager();
  231. new UI(searchManager);
  232. }
  233.  
  234. function setupObserver() {
  235. initializeSearchManager();
  236.  
  237. const observer = new MutationObserver((mutations) => {
  238. let shouldInitialize = false;
  239.  
  240. if (!document.getElementById('bhd-search-manager')) {
  241. const targetAreas = [
  242. '.bhd-outer > #stickyBar > .text-center',
  243. '#stickyBar .text-center',
  244. '.text-center',
  245. '.navbar-header'
  246. ];
  247.  
  248. shouldInitialize = targetAreas.some(selector => {
  249. const container = document.querySelector(selector);
  250. return container && !container.querySelector('#bhd-search-manager');
  251. });
  252. }
  253.  
  254. if (shouldInitialize) {
  255. initializeSearchManager();
  256. }
  257. });
  258.  
  259. observer.observe(document.body, {
  260. childList: true,
  261. subtree: true
  262. });
  263. }
  264.  
  265. if (document.readyState === 'loading') {
  266. document.addEventListener('DOMContentLoaded', setupObserver);
  267. } else {
  268. setupObserver();
  269. }
  270. })();