Site Filter (Protocol-Independent)

Manage allowed sites dynamically and reference this in other scripts.

נכון ליום 07-05-2025. ראה הגרסה האחרונה.

אין להתקין סקריפט זה ישירות. זוהי ספריה עבור סקריפטים אחרים // @require https://update.greatest.deepsurf.us/scripts/526770/1584894/Site%20Filter%20%28Protocol-Independent%29.js

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