Site Filter (Protocol-Independent)

Manage allowed sites dynamically and reference this in other scripts.

Tính đến 13-02-2025. Xem phiên bản mới nhất.

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greatest.deepsurf.us/scripts/526770/1536621/Site%20Filter%20%28Protocol-Independent%29.js

  1. // ==UserScript==
  2. // @name Site Filter (Protocol-Independent)
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.0
  5. // @description Manage allowed sites dynamically and reference this in other scripts.
  6. // @author blvdmd
  7. // @match *://*/*
  8. // @grant GM_getValue
  9. // @grant GM_setValue
  10. // @grant GM_registerMenuCommand
  11. // @grant GM_download
  12. // @run-at document-start
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17.  
  18. // ✅ Wait for `SCRIPT_STORAGE_KEY` to be set
  19. function waitForScriptStorageKey(maxWait = 1000) {
  20. return new Promise(resolve => {
  21. const startTime = Date.now();
  22. const interval = setInterval(() => {
  23. if (typeof window.SCRIPT_STORAGE_KEY !== 'undefined') {
  24. clearInterval(interval);
  25. resolve(window.SCRIPT_STORAGE_KEY);
  26. } else if (Date.now() - startTime > maxWait) {
  27. clearInterval(interval);
  28. console.error("🚨 SCRIPT_STORAGE_KEY is not set! Make sure your script sets it **before** @require.");
  29. resolve(null);
  30. }
  31. }, 50);
  32. });
  33. }
  34.  
  35. (async function initialize() {
  36.  
  37. async function waitForDocumentReady() {
  38. if (document.readyState === "complete") return;
  39. return new Promise(resolve => {
  40. window.addEventListener("load", resolve, { once: true });
  41. });
  42. }
  43. // ✅ Wait for the script storage key
  44. const key = await waitForScriptStorageKey();
  45. if (!key) return;
  46.  
  47. // ✅ Ensure the document is fully loaded before setting `shouldRunOnThisSite`
  48. await waitForDocumentReady();
  49.  
  50. const STORAGE_KEY = `additionalSites_${key}`; // Unique per script
  51.  
  52. // function getDefaultList() {
  53. // return [
  54. // "*.example.*",
  55. // "*example2*"
  56. // ];
  57. // }
  58.  
  59. function getDefaultList() {
  60. return typeof window.GET_DEFAULT_LIST === "function" ? window.GET_DEFAULT_LIST() : [];
  61. }
  62. function normalizeUrl(url) {
  63. return url.replace(/^https?:\/\//, ''); // Remove "http://" or "https://"
  64. }
  65. // Load stored additional sites (default is an empty array)
  66. let additionalSites = GM_getValue(STORAGE_KEY, []);
  67. // Merge user-defined sites with default sites (protocols ignored)
  68. let mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(normalizeUrl);
  69. GM_registerMenuCommand("➕ Add Current Site to Include List", addCurrentSiteMenu);
  70. GM_registerMenuCommand("📜 View Included Sites", viewIncludedSites);
  71. GM_registerMenuCommand("🗑️ Delete Specific Entries", deleteEntries);
  72. GM_registerMenuCommand("✏️ Edit an Entry", editEntry);
  73. GM_registerMenuCommand("🚨 Clear All Entries", clearAllEntries);
  74. GM_registerMenuCommand("📤 Export Site List as JSON", exportAdditionalSites);
  75. GM_registerMenuCommand("📥 Import Site List from JSON", importAdditionalSites);
  76. async function shouldRunOnThisSite() {
  77. const currentFullPath = normalizeUrl(`${window.location.href}`);
  78. return mergedSites.some(pattern => wildcardToRegex(normalizeUrl(pattern)).test(currentFullPath));
  79. }
  80. // function wildcardToRegex(pattern) {
  81. // return new RegExp("^" + pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.') + "$");
  82. // }
  83. /**
  84. * Convert a wildcard pattern (e.g., "*.example.com/index.php?/forums/*") into a valid regex.
  85. * - `*` → Matches any characters (`.*`)
  86. * - `?` → Treated as a **literal question mark** (`\?`)
  87. * - `.` → Treated as a **literal dot** (`\.`)
  88. */
  89. function wildcardToRegex(pattern) {
  90. return new RegExp("^" + pattern
  91. .replace(/[-[\]{}()+^$|#\s]/g, '\\$&') // Escape regex special characters (EXCEPT `.` and `?`)
  92. .replace(/\./g, '\\.') // Ensure `.` is treated as a literal dot
  93. .replace(/\?/g, '\\?') // Ensure `?` is treated as a literal question mark
  94. .replace(/\*/g, '.*') // Convert `*` to `.*` (match any sequence)
  95. + "$");
  96. }
  97. function addCurrentSiteMenu() {
  98. const currentHost = window.location.hostname;
  99. const currentPath = window.location.pathname;
  100. const domainParts = currentHost.split('.');
  101. const baseDomain = domainParts.length > 2 ? domainParts.slice(-2).join('.') : domainParts.join('.');
  102. const secondLevelDomain = domainParts.length > 2 ? domainParts.slice(-2, -1)[0] : domainParts[0];
  103. const options = [
  104. { name: `Preferred Domain Match (*${secondLevelDomain}.*)`, pattern: `*${secondLevelDomain}.*` },
  105. { name: `Base Hostname (*.${baseDomain}*)`, pattern: `*.${baseDomain}*` },
  106. { name: `Base Domain (*.${secondLevelDomain}.*)`, pattern: `*.${secondLevelDomain}.*` },
  107. { name: `Host Contains (*${secondLevelDomain}*)`, pattern: `*${secondLevelDomain}*` },
  108. { name: `Exact Path (${currentHost}${currentPath})`, pattern: normalizeUrl(`${window.location.href}`) },
  109. { name: "Custom Wildcard Pattern", pattern: normalizeUrl(`${window.location.href}`) }
  110. ];
  111. const userChoice = prompt(
  112. "Select an option to add the site:\n" +
  113. options.map((opt, index) => `${index + 1}. ${opt.name}`).join("\n") +
  114. "\nEnter a number or cancel."
  115. );
  116. if (!userChoice) return;
  117. const selectedIndex = parseInt(userChoice, 10) - 1;
  118. if (selectedIndex >= 0 && selectedIndex < options.length) {
  119. let pattern = normalizeUrl(options[selectedIndex].pattern);
  120. if (options[selectedIndex].name === "Custom Wildcard Pattern") {
  121. pattern = normalizeUrl(prompt("Edit custom wildcard pattern:", pattern));
  122. if (!pattern.trim()) return alert("Invalid pattern. Operation canceled.");
  123. }
  124. if (!additionalSites.includes(pattern)) {
  125. additionalSites.push(pattern);
  126. GM_setValue(STORAGE_KEY, additionalSites);
  127. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(normalizeUrl);
  128. alert(`✅ Added site with pattern: ${pattern}`);
  129. } else {
  130. alert(`⚠️ Pattern "${pattern}" is already in the list.`);
  131. }
  132. }
  133. }
  134. function viewIncludedSites() {
  135. //alert(`🔍 Included Sites:\n${mergedSites.join("\n") || "No sites added yet."}`);
  136. alert(`🔍 Included Sites:\n${additionalSites.join("\n") || "No sites added yet."}`);
  137. }
  138. function deleteEntries() {
  139. if (additionalSites.length === 0) return alert("⚠️ No user-defined entries to delete.");
  140. const userChoice = prompt("Select entries to delete (comma-separated numbers):\n" +
  141. additionalSites.map((item, index) => `${index + 1}. ${item}`).join("\n"));
  142. if (!userChoice) return;
  143. const indicesToRemove = userChoice.split(',').map(num => parseInt(num.trim(), 10) - 1);
  144. additionalSites = additionalSites.filter((_, index) => !indicesToRemove.includes(index));
  145. GM_setValue(STORAGE_KEY, additionalSites);
  146. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(normalizeUrl);
  147. alert("✅ Selected entries have been deleted.");
  148. }
  149. function editEntry() {
  150. if (additionalSites.length === 0) return alert("⚠️ No user-defined entries to edit.");
  151. const userChoice = prompt("Select an entry to edit:\n" +
  152. additionalSites.map((item, index) => `${index + 1}. ${item}`).join("\n"));
  153. if (!userChoice) return;
  154. const selectedIndex = parseInt(userChoice, 10) - 1;
  155. if (selectedIndex < 0 || selectedIndex >= additionalSites.length) return alert("❌ Invalid selection.");
  156. const newPattern = normalizeUrl(prompt("Edit the pattern:", additionalSites[selectedIndex]));
  157. if (newPattern && newPattern.trim() && newPattern !== additionalSites[selectedIndex]) {
  158. additionalSites[selectedIndex] = newPattern.trim();
  159. GM_setValue(STORAGE_KEY, additionalSites);
  160. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(normalizeUrl);
  161. alert("✅ Entry updated.");
  162. }
  163. }
  164. function clearAllEntries() {
  165. if (additionalSites.length === 0) return alert("⚠️ No user-defined entries to clear.");
  166. if (confirm(`🚨 You have ${additionalSites.length} entries. Clear all?`)) {
  167. additionalSites = [];
  168. GM_setValue(STORAGE_KEY, additionalSites);
  169. mergedSites = [...getDefaultList()].map(normalizeUrl);
  170. alert("✅ All user-defined entries cleared.");
  171. }
  172. }
  173. function exportAdditionalSites() {
  174. GM_download("data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(additionalSites, null, 2)), "additionalSites_backup.json");
  175. alert("📤 Additional sites exported as JSON.");
  176. }
  177. function importAdditionalSites() {
  178. const input = document.createElement("input");
  179. input.type = "file";
  180. input.accept = ".json";
  181. input.onchange = event => {
  182. const reader = new FileReader();
  183. reader.onload = e => {
  184. additionalSites = JSON.parse(e.target.result);
  185. GM_setValue(STORAGE_KEY, additionalSites);
  186. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(normalizeUrl);
  187. alert("📥 Sites imported successfully.");
  188. };
  189. reader.readAsText(event.target.files[0]);
  190. };
  191. input.click();
  192. }
  193. window.shouldRunOnThisSite = shouldRunOnThisSite;
  194. })();
  195. })();
  196.  
  197. //To use this in another script use @require
  198. // @run-at document-end
  199. // ==/UserScript==
  200.  
  201. // window.SCRIPT_STORAGE_KEY = "magnetLinkHashChecker"; // UNIQUE STORAGE KEY
  202.  
  203.  
  204. // (async function () {
  205. // 'use strict';
  206.  
  207. // // ✅ Wait until `shouldRunOnThisSite` is available
  208. // while (typeof shouldRunOnThisSite === 'undefined') {
  209. // await new Promise(resolve => setTimeout(resolve, 50));
  210. // }
  211.  
  212. // if (!(await shouldRunOnThisSite())) return;
  213. // alert("running");
  214.  
  215.  
  216. // const OFFCLOUD_CACHE_API_URL = 'https://offcloud.com/api/cache';