Site Filter (Protocol-Independent)

Manage allowed sites dynamically and reference this in other scripts.

Ajankohdalta 23.2.2025. Katso uusin versio.

Tätä skriptiä ei tulisi asentaa suoraan. Se on kirjasto muita skriptejä varten sisällytettäväksi metadirektiivillä // @require https://update.greatest.deepsurf.us/scripts/526770/1541937/Site%20Filter%20%28Protocol-Independent%29.js.

  1. // ==UserScript==
  2. // @name Site Filter (Protocol-Independent)
  3. // @namespace http://tampermonkey.net/
  4. // @version 2.1
  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. const USE_EMOJI_FOR_STATUS = true; // Configurable flag to use emoji for true/false status
  19. const SHOW_STATUS_ONLY_IF_TRUE = true; // Configurable flag to show status only if any value is true
  20.  
  21. function waitForScriptStorageKey(maxWait = 1000) {
  22. return new Promise(resolve => {
  23. const startTime = Date.now();
  24. const interval = setInterval(() => {
  25. if (typeof window.SCRIPT_STORAGE_KEY !== 'undefined') {
  26. clearInterval(interval);
  27. resolve(window.SCRIPT_STORAGE_KEY);
  28. } else if (Date.now() - startTime > maxWait) {
  29. clearInterval(interval);
  30. console.error("🚨 SCRIPT_STORAGE_KEY is not set! Make sure your script sets it **before** @require.");
  31. resolve(null);
  32. }
  33. }, 50);
  34. });
  35. }
  36.  
  37. (async function initialize() {
  38. async function waitForDocumentReady() {
  39. if (document.readyState === "complete") return;
  40. return new Promise(resolve => {
  41. window.addEventListener("load", resolve, { once: true });
  42. });
  43. }
  44.  
  45. const key = await waitForScriptStorageKey();
  46. if (!key) return;
  47.  
  48. await waitForDocumentReady();
  49.  
  50. const STORAGE_KEY = `additionalSites_${key}`;
  51.  
  52. function getDefaultList() {
  53. return typeof window.GET_DEFAULT_LIST === "function" ? window.GET_DEFAULT_LIST() : [];
  54. }
  55.  
  56. function normalizeUrl(url) {
  57. if (typeof url !== 'string') {
  58. url = String(url);
  59. }
  60. return url.replace(/^https?:\/\//, '');
  61. }
  62.  
  63. let additionalSites = GM_getValue(STORAGE_KEY, []);
  64.  
  65. let mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(item => {
  66. if (typeof item === 'string') {
  67. return { pattern: normalizeUrl(item), preProcessingRequired: false, postProcessingRequired: false };
  68. }
  69. return { ...item, pattern: normalizeUrl(item.pattern) };
  70. });
  71.  
  72. GM_registerMenuCommand("➕ Add Current Site to Include List", addCurrentSiteMenu);
  73. GM_registerMenuCommand("📜 View Included Sites", viewIncludedSites);
  74. GM_registerMenuCommand("🗑️ Delete Specific Entries", deleteEntries);
  75. GM_registerMenuCommand("✏️ Edit an Entry", editEntry);
  76. GM_registerMenuCommand("🚨 Clear All Entries", clearAllEntries);
  77. GM_registerMenuCommand("📤 Export Site List as JSON", exportAdditionalSites);
  78. GM_registerMenuCommand("📥 Import Site List from JSON", importAdditionalSites);
  79.  
  80. async function shouldRunOnThisSite() {
  81. const currentFullPath = normalizeUrl(`${window.location.href}`);
  82. return mergedSites.some(item => wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath));
  83. }
  84.  
  85. function wildcardToRegex(pattern) {
  86. return new RegExp("^" + pattern
  87. .replace(/[-[\]{}()+^$|#\s]/g, '\\$&')
  88. .replace(/\./g, '\\.')
  89. .replace(/\?/g, '\\?')
  90. .replace(/\*/g, '.*')
  91. + "$");
  92. }
  93.  
  94. function addCurrentSiteMenu() {
  95. const currentHost = window.location.hostname;
  96. const currentPath = window.location.pathname;
  97. const domainParts = currentHost.split('.');
  98. const baseDomain = domainParts.length > 2 ? domainParts.slice(-2).join('.') : domainParts.join('.');
  99. const secondLevelDomain = domainParts.length > 2 ? domainParts.slice(-2, -1)[0] : domainParts[0];
  100.  
  101. const options = [
  102. { name: `Preferred Domain Match (*${secondLevelDomain}.*)`, pattern: `*${secondLevelDomain}.*` },
  103. { name: `Base Hostname (*.${baseDomain}*)`, pattern: `*.${baseDomain}*` },
  104. { name: `Base Domain (*.${secondLevelDomain}.*)`, pattern: `*.${secondLevelDomain}.*` },
  105. { name: `Host Contains (*${secondLevelDomain}*)`, pattern: `*${secondLevelDomain}*` },
  106. { name: `Exact Path (${currentHost}${currentPath})`, pattern: normalizeUrl(`${window.location.href}`) },
  107. { name: "Custom Wildcard Pattern", pattern: normalizeUrl(`${window.location.href}`) }
  108. ];
  109.  
  110. const userChoice = prompt(
  111. "Select an option to add the site:\n" +
  112. options.map((opt, index) => `${index + 1}. ${opt.name}`).join("\n") +
  113. "\nEnter a number or cancel."
  114. );
  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.  
  125. const preProcessingRequired = prompt("Is pre-processing required? (y/n)", "n").toLowerCase() === 'y';
  126. const postProcessingRequired = prompt("Is post-processing required? (y/n)", "n").toLowerCase() === 'y';
  127.  
  128. const entry = { pattern, preProcessingRequired, postProcessingRequired };
  129.  
  130. if (!additionalSites.some(item => item.pattern === pattern)) {
  131. additionalSites.push(entry);
  132. GM_setValue(STORAGE_KEY, additionalSites);
  133. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(item => {
  134. if (typeof item === 'string') {
  135. return { pattern: normalizeUrl(item), preProcessingRequired: false, postProcessingRequired: false };
  136. }
  137. return { ...item, pattern: normalizeUrl(item.pattern) };
  138. });
  139. alert(`✅ Added site with pattern: ${pattern}`);
  140. } else {
  141. alert(`⚠️ Pattern "${pattern}" is already in the list.`);
  142. }
  143. }
  144. }
  145.  
  146. function viewIncludedSites() {
  147. const siteList = additionalSites.map(item => {
  148. const status = formatStatus(item.preProcessingRequired, item.postProcessingRequired);
  149. return `${item.pattern}${status ? ` (${status})` : ''}`;
  150. }).join("\n");
  151. alert(`🔍 Included Sites:\n${siteList || "No sites added yet."}`);
  152. }
  153.  
  154. function deleteEntries() {
  155. if (additionalSites.length === 0) return alert("⚠️ No user-defined entries to delete.");
  156. const userChoice = prompt("Select entries to delete (comma-separated numbers):\n" +
  157. additionalSites.map((item, index) => `${index + 1}. ${item.pattern}`).join("\n"));
  158. if (!userChoice) return;
  159. const indicesToRemove = userChoice.split(',').map(num => parseInt(num.trim(), 10) - 1);
  160. additionalSites = additionalSites.filter((_, index) => !indicesToRemove.includes(index));
  161. GM_setValue(STORAGE_KEY, additionalSites);
  162. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(item => {
  163. if (typeof item === 'string') {
  164. return { pattern: normalizeUrl(item), preProcessingRequired: false, postProcessingRequired: false };
  165. }
  166. return { ...item, pattern: normalizeUrl(item.pattern) };
  167. });
  168. alert("✅ Selected entries have been deleted.");
  169. }
  170.  
  171. function editEntry() {
  172. if (additionalSites.length === 0) return alert("⚠️ No user-defined entries to edit.");
  173. const userChoice = prompt("Select an entry to edit:\n" +
  174. additionalSites.map((item, index) => {
  175. const status = formatStatus(item.preProcessingRequired, item.postProcessingRequired);
  176. return `${index + 1}. ${item.pattern}${status ? ` (${status})` : ''}`;
  177. }).join("\n"));
  178. if (!userChoice) return;
  179. const selectedIndex = parseInt(userChoice, 10) - 1;
  180. if (selectedIndex < 0 || selectedIndex >= additionalSites.length) return alert("❌ Invalid selection.");
  181. const entry = additionalSites[selectedIndex];
  182. const newPattern = normalizeUrl(prompt("Edit the pattern:", entry.pattern));
  183. if (!newPattern || !newPattern.trim()) return;
  184.  
  185. const preProcessingRequired = prompt("Is pre-processing required? (y/n)", entry.preProcessingRequired ? "y" : "n").toLowerCase() === 'y';
  186. const postProcessingRequired = prompt("Is post-processing required? (y/n)", entry.postProcessingRequired ? "y" : "n").toLowerCase() === 'y';
  187.  
  188. entry.pattern = newPattern.trim();
  189. entry.preProcessingRequired = preProcessingRequired;
  190. entry.postProcessingRequired = postProcessingRequired;
  191. GM_setValue(STORAGE_KEY, additionalSites);
  192. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(item => {
  193. if (typeof item === 'string') {
  194. return { pattern: normalizeUrl(item), preProcessingRequired: false, postProcessingRequired: false };
  195. }
  196. return { ...item, pattern: normalizeUrl(item.pattern) };
  197. });
  198. alert("✅ Entry updated.");
  199. }
  200.  
  201. function clearAllEntries() {
  202. if (additionalSites.length === 0) return alert("⚠️ No user-defined entries to clear.");
  203. if (confirm(`🚨 You have ${additionalSites.length} entries. Clear all?`)) {
  204. additionalSites = [];
  205. GM_setValue(STORAGE_KEY, additionalSites);
  206. mergedSites = [...getDefaultList()].map(item => {
  207. if (typeof item === 'string') {
  208. return { pattern: normalizeUrl(item), preProcessingRequired: false, postProcessingRequired: false };
  209. }
  210. return { ...item, pattern: normalizeUrl(item.pattern) };
  211. });
  212. alert("✅ All user-defined entries cleared.");
  213. }
  214. }
  215.  
  216. function exportAdditionalSites() {
  217. GM_download("data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(additionalSites, null, 2)), "additionalSites_backup.json");
  218. alert("📤 Additional sites exported as JSON.");
  219. }
  220.  
  221. function importAdditionalSites() {
  222. const input = document.createElement("input");
  223. input.type = "file";
  224. input.accept = ".json";
  225. input.onchange = event => {
  226. const reader = new FileReader();
  227. reader.onload = e => {
  228. additionalSites = JSON.parse(e.target.result);
  229. GM_setValue(STORAGE_KEY, additionalSites);
  230. mergedSites = [...new Set([...getDefaultList(), ...additionalSites])].map(item => {
  231. if (typeof item === 'string') {
  232. return { pattern: normalizeUrl(item), preProcessingRequired: false, postProcessingRequired: false };
  233. }
  234. return { ...item, pattern: normalizeUrl(item.pattern) };
  235. });
  236. alert("📥 Sites imported successfully.");
  237. };
  238. reader.readAsText(event.target.files[0]);
  239. };
  240. input.click();
  241. }
  242.  
  243. function formatStatus(preProcessingRequired, postProcessingRequired) {
  244. if (SHOW_STATUS_ONLY_IF_TRUE && !preProcessingRequired && !postProcessingRequired) {
  245. return '';
  246. }
  247. const preStatus = USE_EMOJI_FOR_STATUS ? (preProcessingRequired ? '✅' : '✖️') : (preProcessingRequired ? 'true' : 'false');
  248. const postStatus = USE_EMOJI_FOR_STATUS ? (postProcessingRequired ? '✅' : '✖️') : (postProcessingRequired ? 'true' : 'false');
  249. return `Pre: ${preStatus}, Post: ${postStatus}`;
  250. }
  251.  
  252. window.shouldRunOnThisSite = shouldRunOnThisSite;
  253. window.isPreProcessingRequired = function() {
  254. const currentFullPath = normalizeUrl(`${window.location.href}`);
  255. const entry = mergedSites.find(item => wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath));
  256. return entry ? entry.preProcessingRequired : false;
  257. };
  258. window.isPostProcessingRequired = function() {
  259. const currentFullPath = normalizeUrl(`${window.location.href}`);
  260. const entry = mergedSites.find(item => wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath));
  261. return entry ? entry.postProcessingRequired : false;
  262. };
  263. })();
  264. })();