Site Filter (Protocol-Independent)

Manage allowed sites dynamically and reference this in other scripts.

目前為 2025-02-13 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/526770/1536593/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. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. // Check for a script-specific storage key
  18. if (typeof window.SCRIPT_STORAGE_KEY === 'undefined') {
  19. console.error("🚨 SCRIPT_STORAGE_KEY is not set! Each script must define its own storage key before @require.");
  20. return;
  21. }
  22.  
  23. const STORAGE_KEY = `additionalSites_${window.SCRIPT_STORAGE_KEY}`; // Unique per script
  24. //const STORAGE_KEY = "additionalSites";
  25.  
  26. function getDefaultList() {
  27. return [
  28. "*.example.*",
  29. "*example2*"
  30. ];
  31. }
  32.  
  33. function normalizeUrl(url) {
  34. return url.replace(/^https?:\/\//, ''); // Remove "http://" or "https://"
  35. }
  36.  
  37. // Load stored additional sites (default is an empty array)
  38. let additionalSites = GM_getValue(STORAGE_KEY, []);
  39.  
  40. // Merge user-defined sites with default sites (protocols ignored)
  41. let mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(normalizeUrl);
  42.  
  43. GM_registerMenuCommand("➕ Add Current Site to Include List", addCurrentSiteMenu);
  44. GM_registerMenuCommand("📜 View Included Sites", viewIncludedSites);
  45. GM_registerMenuCommand("🗑️ Delete Specific Entries", deleteEntries);
  46. GM_registerMenuCommand("✏️ Edit an Entry", editEntry);
  47. GM_registerMenuCommand("🚨 Clear All Entries", clearAllEntries);
  48. GM_registerMenuCommand("📤 Export Site List as JSON", exportAdditionalSites);
  49. GM_registerMenuCommand("📥 Import Site List from JSON", importAdditionalSites);
  50.  
  51. async function shouldRunOnThisSite() {
  52. const currentFullPath = normalizeUrl(`${window.location.href}`);
  53. return mergedSites.some(pattern => wildcardToRegex(normalizeUrl(pattern)).test(currentFullPath));
  54. }
  55.  
  56. // function wildcardToRegex(pattern) {
  57. // return new RegExp("^" + pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '.*').replace(/\?/g, '.') + "$");
  58. // }
  59.  
  60. /**
  61. * Convert a wildcard pattern (e.g., "*.example.com/index.php?/forums/*") into a valid regex.
  62. * - `*` → Matches any characters (`.*`)
  63. * - `?` → Treated as a **literal question mark** (`\?`)
  64. * - `.` → Treated as a **literal dot** (`\.`)
  65. */
  66. function wildcardToRegex(pattern) {
  67. return new RegExp("^" + pattern
  68. .replace(/[-[\]{}()+^$|#\s]/g, '\\$&') // Escape regex special characters (EXCEPT `.` and `?`)
  69. .replace(/\./g, '\\.') // Ensure `.` is treated as a literal dot
  70. .replace(/\?/g, '\\?') // Ensure `?` is treated as a literal question mark
  71. .replace(/\*/g, '.*') // Convert `*` to `.*` (match any sequence)
  72. + "$");
  73. }
  74.  
  75.  
  76. function addCurrentSiteMenu() {
  77. const currentHost = window.location.hostname;
  78. const currentPath = window.location.pathname;
  79. const domainParts = currentHost.split('.');
  80. const baseDomain = domainParts.length > 2 ? domainParts.slice(-2).join('.') : domainParts.join('.');
  81. const secondLevelDomain = domainParts.length > 2 ? domainParts.slice(-2, -1)[0] : domainParts[0];
  82.  
  83. const options = [
  84. { name: `Preferred Domain Match (*${secondLevelDomain}.*)`, pattern: `*${secondLevelDomain}.*` },
  85. { name: `Base Hostname (*.${baseDomain}*)`, pattern: `*.${baseDomain}*` },
  86. { name: `Base Domain (*.${secondLevelDomain}.*)`, pattern: `*.${secondLevelDomain}.*` },
  87. { name: `Host Contains (*${secondLevelDomain}*)`, pattern: `*${secondLevelDomain}*` },
  88. { name: `Exact Path (${currentHost}${currentPath})`, pattern: normalizeUrl(`${window.location.href}`) },
  89. { name: "Custom Wildcard Pattern", pattern: normalizeUrl(`${window.location.href}`) }
  90. ];
  91.  
  92. const userChoice = prompt(
  93. "Select an option to add the site:\n" +
  94. options.map((opt, index) => `${index + 1}. ${opt.name}`).join("\n") +
  95. "\nEnter a number or cancel."
  96. );
  97.  
  98. if (!userChoice) return;
  99. const selectedIndex = parseInt(userChoice, 10) - 1;
  100. if (selectedIndex >= 0 && selectedIndex < options.length) {
  101. let pattern = normalizeUrl(options[selectedIndex].pattern);
  102. if (options[selectedIndex].name === "Custom Wildcard Pattern") {
  103. pattern = normalizeUrl(prompt("Edit custom wildcard pattern:", pattern));
  104. if (!pattern.trim()) return alert("Invalid pattern. Operation canceled.");
  105. }
  106. if (!additionalSites.includes(pattern)) {
  107. additionalSites.push(pattern);
  108. GM_setValue(STORAGE_KEY, additionalSites);
  109. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(normalizeUrl);
  110. alert(`✅ Added site with pattern: ${pattern}`);
  111. } else {
  112. alert(`⚠️ Pattern "${pattern}" is already in the list.`);
  113. }
  114. }
  115. }
  116.  
  117. function viewIncludedSites() {
  118. //alert(`🔍 Included Sites:\n${mergedSites.join("\n") || "No sites added yet."}`);
  119. alert(`🔍 Included Sites:\n${additionalSites.join("\n") || "No sites added yet."}`);
  120. }
  121.  
  122. function deleteEntries() {
  123. if (additionalSites.length === 0) return alert("⚠️ No user-defined entries to delete.");
  124. const userChoice = prompt("Select entries to delete (comma-separated numbers):\n" +
  125. additionalSites.map((item, index) => `${index + 1}. ${item}`).join("\n"));
  126. if (!userChoice) return;
  127. const indicesToRemove = userChoice.split(',').map(num => parseInt(num.trim(), 10) - 1);
  128. additionalSites = additionalSites.filter((_, index) => !indicesToRemove.includes(index));
  129. GM_setValue(STORAGE_KEY, additionalSites);
  130. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(normalizeUrl);
  131. alert("✅ Selected entries have been deleted.");
  132. }
  133.  
  134. function editEntry() {
  135. if (additionalSites.length === 0) return alert("⚠️ No user-defined entries to edit.");
  136. const userChoice = prompt("Select an entry to edit:\n" +
  137. additionalSites.map((item, index) => `${index + 1}. ${item}`).join("\n"));
  138. if (!userChoice) return;
  139. const selectedIndex = parseInt(userChoice, 10) - 1;
  140. if (selectedIndex < 0 || selectedIndex >= additionalSites.length) return alert("❌ Invalid selection.");
  141. const newPattern = normalizeUrl(prompt("Edit the pattern:", additionalSites[selectedIndex]));
  142. if (newPattern && newPattern.trim() && newPattern !== additionalSites[selectedIndex]) {
  143. additionalSites[selectedIndex] = newPattern.trim();
  144. GM_setValue(STORAGE_KEY, additionalSites);
  145. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(normalizeUrl);
  146. alert("✅ Entry updated.");
  147. }
  148. }
  149.  
  150. function clearAllEntries() {
  151. if (additionalSites.length === 0) return alert("⚠️ No user-defined entries to clear.");
  152. if (confirm(`🚨 You have ${additionalSites.length} entries. Clear all?`)) {
  153. additionalSites = [];
  154. GM_setValue(STORAGE_KEY, additionalSites);
  155. mergedSites = [...getDefaultList()].map(normalizeUrl);
  156. alert("✅ All user-defined entries cleared.");
  157. }
  158. }
  159.  
  160. function exportAdditionalSites() {
  161. GM_download("data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(additionalSites, null, 2)), "additionalSites_backup.json");
  162. alert("📤 Additional sites exported as JSON.");
  163. }
  164.  
  165. function importAdditionalSites() {
  166. const input = document.createElement("input");
  167. input.type = "file";
  168. input.accept = ".json";
  169. input.onchange = event => {
  170. const reader = new FileReader();
  171. reader.onload = e => {
  172. additionalSites = JSON.parse(e.target.result);
  173. GM_setValue(STORAGE_KEY, additionalSites);
  174. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(normalizeUrl);
  175. alert("📥 Sites imported successfully.");
  176. };
  177. reader.readAsText(event.target.files[0]);
  178. };
  179. input.click();
  180. }
  181.  
  182. window.shouldRunOnThisSite = shouldRunOnThisSite;
  183. })();