Dynamic Include Sites Script (Protocol-Independent)

Run on default & user-defined sites using wildcard patterns (ignores protocols), with full management features.

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greatest.deepsurf.us/scripts/526770/1596962/Dynamic%20Include%20Sites%20Script%20%28Protocol-Independent%29.js

  1. // ==UserScript==
  2. // @name Site Filter (Protocol-Independent)
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.7
  5. // @description Manage allowed sites dynamically and reference this in other scripts.
  6. // @author blvdmd
  7. // @match *://*/*
  8. // @noframes
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_download
  13. // @run-at document-start
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. 'use strict';
  18.  
  19. // Exit if the script is running in an iframe or embedded context
  20. if (window.self !== window.top) {
  21. return; // Stop execution for embedded pages
  22. }
  23.  
  24. const USE_EMOJI_FOR_STATUS = true; // Configurable flag to use emoji for true/false status
  25. const SHOW_STATUS_ONLY_IF_TRUE = true; // Configurable flag to show status only if any value is true
  26.  
  27. // ✅ Wait for `SCRIPT_STORAGE_KEY` to be set
  28. function waitForScriptStorageKey(maxWait = 1000) {
  29. return new Promise(resolve => {
  30. const startTime = Date.now();
  31. const interval = setInterval(() => {
  32. if (typeof window.SCRIPT_STORAGE_KEY !== 'undefined') {
  33. clearInterval(interval);
  34. resolve(window.SCRIPT_STORAGE_KEY);
  35. } else if (Date.now() - startTime > maxWait) {
  36. clearInterval(interval);
  37. console.error("🚨 SCRIPT_STORAGE_KEY is not set! Make sure your script sets it **before** @require.");
  38. resolve(null);
  39. }
  40. }, 50);
  41. });
  42. }
  43.  
  44. (async function initialize() {
  45. async function waitForDocumentReady() {
  46. if (document.readyState === "complete") return;
  47. return new Promise(resolve => {
  48. window.addEventListener("load", resolve, { once: true });
  49. });
  50. }
  51. // ✅ Wait for the script storage key
  52. const key = await waitForScriptStorageKey();
  53. if (!key) return;
  54.  
  55. // ✅ Ensure the document is fully loaded before setting `shouldRunOnThisSite`
  56. await waitForDocumentReady();
  57.  
  58. const STORAGE_KEY = `additionalSites_${key}`;
  59.  
  60. function getDefaultList() {
  61. return typeof window.GET_DEFAULT_LIST === "function" ? window.GET_DEFAULT_LIST() : [];
  62. }
  63.  
  64. function normalizeUrl(url) {
  65. if (typeof url !== 'string') {
  66. url = String(url);
  67. }
  68. return url.replace(/^https?:\/\//, '');
  69. }
  70.  
  71. let additionalSites = GM_getValue(STORAGE_KEY, []);
  72.  
  73. let mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(item => {
  74. if (typeof item === 'string') {
  75. return { pattern: normalizeUrl(item), preProcessingRequired: false, postProcessingRequired: false };
  76. }
  77. return { ...item, pattern: normalizeUrl(item.pattern) };
  78. });
  79.  
  80. GM_registerMenuCommand("➕ Add Current Site to Include List", addCurrentSiteMenu);
  81. GM_registerMenuCommand("📜 View Included Sites", viewIncludedSites);
  82. GM_registerMenuCommand("🗑️ Delete Specific Entries", deleteEntries);
  83. GM_registerMenuCommand("✏️ Edit an Entry", editEntry);
  84. GM_registerMenuCommand("🚨 Clear All Entries", clearAllEntries);
  85. GM_registerMenuCommand("📤 Export Site List as JSON", exportAdditionalSites);
  86. GM_registerMenuCommand("📥 Import Site List from JSON", importAdditionalSites);
  87.  
  88. async function shouldRunOnThisSite() {
  89. const currentFullPath = normalizeUrl(`${window.top.location.href}`);
  90. return mergedSites.some(item => wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath));
  91. }
  92.  
  93. function wildcardToRegex(pattern) {
  94. return new RegExp("^" + pattern
  95. .replace(/[-[\]{}()+^$|#\s]/g, '\\$&')
  96. .replace(/\./g, '\\.')
  97. .replace(/\?/g, '\\?')
  98. .replace(/\*/g, '.*')
  99. + "$");
  100. }
  101.  
  102. function addCurrentSiteMenu() {
  103. const currentHost = window.top.location.hostname; // Use window.top to get the top-level hostname
  104. const currentPath = window.top.location.pathname; // Use window.top to get the top-level pathname
  105. const domainParts = currentHost.split('.');
  106. const baseDomain = domainParts.length > 2 ? domainParts.slice(-2).join('.') : domainParts.join('.');
  107. const secondLevelDomain = domainParts.length > 2 ? domainParts.slice(-2, -1)[0] : domainParts[0];
  108. const options = [
  109. { name: `Preferred Domain Match (*${secondLevelDomain}.*)`, pattern: `*${secondLevelDomain}.*` },
  110. { name: `Base Hostname (*.${baseDomain}*)`, pattern: `*.${baseDomain}*` },
  111. { name: `Base Domain (*.${secondLevelDomain}.*)`, pattern: `*.${secondLevelDomain}.*` },
  112. { name: `Host Contains (*${secondLevelDomain}*)`, pattern: `*${secondLevelDomain}*` },
  113. { name: `Exact Path (${currentHost}${currentPath})`, pattern: normalizeUrl(`${window.top.location.href}`) },
  114. { name: "Custom Wildcard Pattern", pattern: normalizeUrl(`${window.top.location.href}`) }
  115. ];
  116.  
  117. const userChoice = prompt(
  118. "Select an option to add the site:\n" +
  119. options.map((opt, index) => `${index + 1}. ${opt.name}`).join("\n") +
  120. "\nEnter a number or cancel."
  121. );
  122.  
  123. if (!userChoice) return;
  124. const selectedIndex = parseInt(userChoice, 10) - 1;
  125. if (selectedIndex >= 0 && selectedIndex < options.length) {
  126. let pattern = normalizeUrl(options[selectedIndex].pattern);
  127. if (options[selectedIndex].name === "Custom Wildcard Pattern") {
  128. pattern = normalizeUrl(prompt("Edit custom wildcard pattern:", pattern));
  129. if (!pattern.trim()) return alert("Invalid pattern. Operation canceled.");
  130. }
  131.  
  132. const preProcessingRequired = prompt("Is pre-processing required? (y/n)", "n").toLowerCase() === 'y';
  133. const postProcessingRequired = prompt("Is post-processing required? (y/n)", "n").toLowerCase() === 'y';
  134. const onDemandFloatingButtonRequired = prompt("Is on-demand floating button required? (y/n)", "n").toLowerCase() === 'y';
  135. const backgroundChangeObserverRequired = prompt("Is background change observer required? (y/n)", "n").toLowerCase() === 'y';
  136.  
  137. const entry = {
  138. pattern,
  139. preProcessingRequired,
  140. postProcessingRequired,
  141. onDemandFloatingButtonRequired,
  142. backgroundChangeObserverRequired
  143. };
  144. if (!additionalSites.some(item => item.pattern === pattern)) {
  145. additionalSites.push(entry);
  146. GM_setValue(STORAGE_KEY, additionalSites);
  147. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(item => {
  148. if (typeof item === 'string') {
  149. return {
  150. pattern: normalizeUrl(item),
  151. preProcessingRequired: false,
  152. postProcessingRequired: false,
  153. onDemandFloatingButtonRequired: false,
  154. backgroundChangeObserverRequired: false
  155. };
  156. }
  157. return {
  158. ...item,
  159. pattern: normalizeUrl(item.pattern),
  160. onDemandFloatingButtonRequired: item.onDemandFloatingButtonRequired || false,
  161. backgroundChangeObserverRequired: item.backgroundChangeObserverRequired || false
  162. };
  163. });
  164. alert(`✅ Added site with pattern: ${pattern}`);
  165. } else {
  166. alert(`⚠️ Pattern "${pattern}" is already in the list.`);
  167. }
  168. }
  169. }
  170.  
  171. function viewIncludedSites() {
  172. const siteList = additionalSites.map(item => {
  173. const status = formatStatus(item.preProcessingRequired, item.postProcessingRequired, item.onDemandFloatingButtonRequired, item.backgroundChangeObserverRequired);
  174. return `${item.pattern}${status ? ` (${status})` : ''}`;
  175. }).join("\n");
  176. alert(`🔍 Included Sites:\n${siteList || "No sites added yet."}`);
  177. }
  178.  
  179. function deleteEntries() {
  180. if (additionalSites.length === 0) return alert("⚠️ No user-defined entries to delete.");
  181. const userChoice = prompt("Select entries to delete (comma-separated numbers):\n" +
  182. additionalSites.map((item, index) => `${index + 1}. ${item.pattern}`).join("\n"));
  183. if (!userChoice) return;
  184. const indicesToRemove = userChoice.split(',').map(num => parseInt(num.trim(), 10) - 1);
  185. additionalSites = additionalSites.filter((_, index) => !indicesToRemove.includes(index));
  186. GM_setValue(STORAGE_KEY, additionalSites);
  187. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(item => {
  188. if (typeof item === 'string') {
  189. return { pattern: normalizeUrl(item), preProcessingRequired: false, postProcessingRequired: false };
  190. }
  191. return { ...item, pattern: normalizeUrl(item.pattern) };
  192. });
  193. alert("✅ Selected entries have been deleted.");
  194. }
  195.  
  196. function editEntry() {
  197. if (additionalSites.length === 0) return alert("⚠️ No user-defined entries to edit.");
  198. const userChoice = prompt("Select an entry to edit:\n" +
  199. additionalSites.map((item, index) => {
  200. const status = formatStatus(item.preProcessingRequired, item.postProcessingRequired, item.onDemandFloatingButtonRequired, item.backgroundChangeObserverRequired);
  201. return `${index + 1}. ${item.pattern}${status ? ` (${status})` : ''}`;
  202. }).join("\n"));
  203. if (!userChoice) return;
  204. const selectedIndex = parseInt(userChoice, 10) - 1;
  205. if (selectedIndex < 0 || selectedIndex >= additionalSites.length) return alert("❌ Invalid selection.");
  206. const entry = additionalSites[selectedIndex];
  207. const newPattern = normalizeUrl(prompt("Edit the pattern:", entry.pattern));
  208. if (!newPattern || !newPattern.trim()) return;
  209. const preProcessingRequired = prompt("Is pre-processing required? (y/n)", entry.preProcessingRequired ? "y" : "n").toLowerCase() === 'y';
  210. const postProcessingRequired = prompt("Is post-processing required? (y/n)", entry.postProcessingRequired ? "y" : "n").toLowerCase() === 'y';
  211. const onDemandFloatingButtonRequired = prompt("Is on-demand floating button required? (y/n)", entry.onDemandFloatingButtonRequired ? "y" : "n").toLowerCase() === 'y';
  212. const backgroundChangeObserverRequired = prompt("Is background change observer required? (y/n)", entry.backgroundChangeObserverRequired ? "y" : "n").toLowerCase() === 'y';
  213. entry.pattern = newPattern.trim();
  214. entry.preProcessingRequired = preProcessingRequired;
  215. entry.postProcessingRequired = postProcessingRequired;
  216. entry.onDemandFloatingButtonRequired = onDemandFloatingButtonRequired;
  217. entry.backgroundChangeObserverRequired = backgroundChangeObserverRequired;
  218. GM_setValue(STORAGE_KEY, additionalSites);
  219. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(item => {
  220. if (typeof item === 'string') {
  221. return {
  222. pattern: normalizeUrl(item),
  223. preProcessingRequired: false,
  224. postProcessingRequired: false,
  225. onDemandFloatingButtonRequired: false,
  226. backgroundChangeObserverRequired: false
  227. };
  228. }
  229. return {
  230. ...item,
  231. pattern: normalizeUrl(item.pattern),
  232. onDemandFloatingButtonRequired: item.onDemandFloatingButtonRequired || false,
  233. backgroundChangeObserverRequired: item.backgroundChangeObserverRequired || false
  234. };
  235. });
  236. alert("✅ Entry updated.");
  237. }
  238.  
  239. function clearAllEntries() {
  240. if (additionalSites.length === 0) return alert("⚠️ No user-defined entries to clear.");
  241. if (confirm(`🚨 You have ${additionalSites.length} entries. Clear all?`)) {
  242. additionalSites = [];
  243. GM_setValue(STORAGE_KEY, additionalSites);
  244. mergedSites = [...getDefaultList()].map(item => {
  245. if (typeof item === 'string') {
  246. return { pattern: normalizeUrl(item), preProcessingRequired: false, postProcessingRequired: false };
  247. }
  248. return { ...item, pattern: normalizeUrl(item.pattern) };
  249. });
  250. alert("✅ All user-defined entries cleared.");
  251. }
  252. }
  253.  
  254. // function exportAdditionalSites() {
  255. // GM_download("data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(additionalSites, null, 2)), "additionalSites_backup.json");
  256. // alert("📤 Additional sites exported as JSON.");
  257. // }
  258.  
  259. function exportAdditionalSites() {
  260. const data = JSON.stringify(additionalSites, null, 2);
  261. const blob = new Blob([data], { type: 'application/json' });
  262. const url = URL.createObjectURL(blob);
  263. const a = document.createElement('a');
  264. a.href = url;
  265. a.download = 'additionalSites_backup.json';
  266. document.body.appendChild(a);
  267. a.click();
  268. document.body.removeChild(a);
  269. URL.revokeObjectURL(url);
  270. alert("📤 Additional sites exported as JSON.");
  271. }
  272.  
  273. // function importAdditionalSites() {
  274. // const input = document.createElement("input");
  275. // input.type = "file";
  276. // input.accept = ".json";
  277. // input.onchange = event => {
  278. // const reader = new FileReader();
  279. // reader.onload = e => {
  280. // additionalSites = JSON.parse(e.target.result);
  281. // GM_setValue(STORAGE_KEY, additionalSites);
  282. // mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(item => {
  283. // if (typeof item === 'string') {
  284. // return { pattern: normalizeUrl(item), preProcessingRequired: false, postProcessingRequired: false };
  285. // }
  286. // return { ...item, pattern: normalizeUrl(item.pattern) };
  287. // });
  288. // alert("📥 Sites imported successfully.");
  289. // };
  290. // reader.readAsText(event.target.files[0]);
  291. // };
  292. // input.click();
  293. // }
  294.  
  295. function importAdditionalSites() {
  296. const input = document.createElement('input');
  297. input.type = 'file';
  298. input.accept = '.json';
  299. input.style.display = 'none';
  300. input.onchange = event => {
  301. const reader = new FileReader();
  302. reader.onload = e => {
  303. try {
  304. const importedData = JSON.parse(e.target.result);
  305. if (Array.isArray(importedData)) {
  306. additionalSites = importedData.map(item => {
  307. if (typeof item === 'string') {
  308. return normalizeUrl(item);
  309. } else if (typeof item === 'object' && item.pattern) {
  310. return { ...item, pattern: normalizeUrl(item.pattern) };
  311. }
  312. throw new Error('Invalid data format');
  313. });
  314. GM_setValue(STORAGE_KEY, additionalSites);
  315. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(item => {
  316. if (typeof item === 'string') {
  317. return normalizeUrl(item);
  318. }
  319. return { ...item, pattern: normalizeUrl(item.pattern) };
  320. });
  321. alert('📥 Sites imported successfully.');
  322. } else {
  323. throw new Error('Invalid data format');
  324. }
  325. } catch (error) {
  326. alert('❌ Failed to import sites: ' + error.message);
  327. }
  328. };
  329. reader.readAsText(event.target.files[0]);
  330. };
  331. document.body.appendChild(input);
  332. input.click();
  333. document.body.removeChild(input);
  334. }
  335.  
  336. function formatStatus(preProcessingRequired, postProcessingRequired, onDemandFloatingButtonRequired, backgroundChangeObserverRequired) {
  337. if (SHOW_STATUS_ONLY_IF_TRUE && !preProcessingRequired && !postProcessingRequired && !onDemandFloatingButtonRequired && !backgroundChangeObserverRequired) {
  338. return '';
  339. }
  340. const preStatus = USE_EMOJI_FOR_STATUS ? (preProcessingRequired ? '✅' : '✖️') : (preProcessingRequired ? 'true' : 'false');
  341. const postStatus = USE_EMOJI_FOR_STATUS ? (postProcessingRequired ? '✅' : '✖️') : (postProcessingRequired ? 'true' : 'false');
  342. const floatingButtonStatus = USE_EMOJI_FOR_STATUS ? (onDemandFloatingButtonRequired ? '✅' : '✖️') : (onDemandFloatingButtonRequired ? 'true' : 'false');
  343. const backgroundObserverStatus = USE_EMOJI_FOR_STATUS ? (backgroundChangeObserverRequired ? '✅' : '✖️') : (backgroundChangeObserverRequired ? 'true' : 'false');
  344. return `Pre: ${preStatus}, Post: ${postStatus}, Floating Button: ${floatingButtonStatus}, Background Observer: ${backgroundObserverStatus}`;
  345. }
  346.  
  347. window.shouldRunOnThisSite = shouldRunOnThisSite;
  348. window.isPreProcessingRequired = function() {
  349. const currentFullPath = normalizeUrl(`${window.top.location.href}`);
  350. const entry = mergedSites.find(item => wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath));
  351. return entry ? entry.preProcessingRequired : false;
  352. };
  353. window.isPostProcessingRequired = function() {
  354. const currentFullPath = normalizeUrl(`${window.top.location.href}`);
  355. const entry = mergedSites.find(item => wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath));
  356. return entry ? entry.postProcessingRequired : false;
  357. };
  358. // Expose isOnDemandFloatingButtonRequired
  359. window.isOnDemandFloatingButtonRequired = function() {
  360. const currentFullPath = normalizeUrl(`${window.top.location.href}`);
  361. const entry = mergedSites.find(item => wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath));
  362. return entry ? entry.onDemandFloatingButtonRequired : false;
  363. };
  364.  
  365. // Expose isBackgroundChangeObserverRequired
  366. window.isBackgroundChangeObserverRequired = function() {
  367. const currentFullPath = normalizeUrl(`${window.top.location.href}`);
  368. const entry = mergedSites.find(item => wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath));
  369. return entry ? entry.backgroundChangeObserverRequired : false;
  370. };
  371. })();
  372. })();
  373.  
  374. //To use this in another script use @require
  375.  
  376. // // @run-at document-end
  377. // // ==/UserScript==
  378.  
  379. // window.SCRIPT_STORAGE_KEY = "magnetLinkHashChecker"; // UNIQUE STORAGE KEY
  380.  
  381.  
  382. // window.GET_DEFAULT_LIST = function() {
  383. // return [
  384. // { pattern: "*1337x.*", preProcessingRequired: false, postProcessingRequired: false, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
  385. // { pattern: "*yts.*", preProcessingRequired: true, postProcessingRequired: true, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
  386. // { pattern: "*torrentgalaxy.*", preProcessingRequired: false, postProcessingRequired: true, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
  387. // { pattern: "*bitsearch.*", preProcessingRequired: false, postProcessingRequired: false, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
  388. // { pattern: "*thepiratebay.*", preProcessingRequired: false, postProcessingRequired: false, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
  389. // { pattern: "*ext.*", preProcessingRequired: false, postProcessingRequired: false, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false }
  390. // ];
  391. // };
  392.  
  393.  
  394. // (async function () {
  395. // 'use strict';
  396.  
  397. // // ✅ Wait until `shouldRunOnThisSite` is available
  398. // while (typeof shouldRunOnThisSite === 'undefined') {
  399. // await new Promise(resolve => setTimeout(resolve, 50));
  400. // }
  401.  
  402. // if (!(await shouldRunOnThisSite())) return;
  403. // //alert("running");
  404.  
  405. // console.log("Pre-Customization enabled for this site: " + isPreProcessingRequired() );
  406. // console.log("Post-Customization enabled for this site: " + isPostProcessingRequired() );
  407.  
  408. // const OFFCLOUD_CACHE_API_URL = 'https://offcloud.com/api/cache';