Detta skript bör inte installeras direkt. Det är ett bibliotek för andra skript att inkludera med meta-direktivet // @require https://update.greasyfork.org/scripts/526770/1670381/Site%20Filter%20%28Protocol-Independent%29.js
// ==UserScript==
// @name Site Filter (Protocol-Independent)
// @namespace http://tampermonkey.net/
// @version 3.0
// @description Manage allowed sites dynamically and reference this in other scripts.
// @author blvdmd
// @match *://*/*
// @noframes
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @grant GM_download
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
// Exit if the script is running in an iframe or embedded context
if (window.self !== window.top) {
return; // Stop execution for embedded pages
}
const USE_EMOJI_FOR_STATUS = true; // Configurable flag to use emoji for true/false status
const SHOW_STATUS_ONLY_IF_TRUE = true; // Configurable flag to show status only if any value is true
// ✅ Wait for `SCRIPT_STORAGE_KEY` to be set
function waitForScriptStorageKey(maxWait = 1000) {
return new Promise(resolve => {
const startTime = Date.now();
const interval = setInterval(() => {
if (typeof window.SCRIPT_STORAGE_KEY !== 'undefined') {
clearInterval(interval);
resolve(window.SCRIPT_STORAGE_KEY);
} else if (Date.now() - startTime > maxWait) {
clearInterval(interval);
console.error("🚨 SCRIPT_STORAGE_KEY is not set! Make sure your script sets it **before** @require.");
resolve(null);
}
}, 50);
});
}
(async function initialize() {
async function waitForDocumentReady() {
if (document.readyState === "complete") return;
return new Promise(resolve => {
window.addEventListener("load", resolve, { once: true });
});
}
// ✅ Wait for the script storage key
const key = await waitForScriptStorageKey();
if (!key) return;
// ✅ Ensure the document is fully loaded before setting `shouldRunOnThisSite`
await waitForDocumentReady();
const STORAGE_KEY = `additionalSites_${key}`;
function getDefaultList() {
return typeof window.GET_DEFAULT_LIST === "function" ? window.GET_DEFAULT_LIST() : [];
}
function normalizeUrl(url) {
if (typeof url !== 'string') {
url = String(url);
}
return url.replace(/^https?:\/\//, '');
}
let additionalSites = GM_getValue(STORAGE_KEY, []);
let mergedSites = buildMergedSites();
// ====== REFACTORED: Deduplicated mergedSites building logic ======
function buildMergedSites() {
return [...new Set([...getDefaultList(), ...additionalSites])].map(item => {
if (typeof item === 'string') {
return {
pattern: normalizeUrl(item),
preProcessingRequired: false,
postProcessingRequired: false,
onDemandFloatingButtonRequired: false,
backgroundChangeObserverRequired: false
};
}
return {
...item,
pattern: normalizeUrl(item.pattern),
preProcessingRequired: item.preProcessingRequired || false,
postProcessingRequired: item.postProcessingRequired || false,
onDemandFloatingButtonRequired: item.onDemandFloatingButtonRequired || false,
backgroundChangeObserverRequired: item.backgroundChangeObserverRequired || false
};
});
}
function refreshMergedSites() {
mergedSites = buildMergedSites();
}
function wildcardToRegex(pattern) {
return new RegExp("^" + pattern
.replace(/[-[\]{}()+^$|#\s]/g, '\\$&')
.replace(/\./g, '\\.')
.replace(/\?/g, '\\?')
.replace(/\*/g, '.*')
+ "$");
}
async function shouldRunOnThisSite() {
const currentFullPath = normalizeUrl(`${window.top.location.href}`);
return mergedSites.some(item => wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath));
}
// ====== UTILITY: Count wildcard matches for current site ======
function countWildcardMatches() {
const currentFullPath = normalizeUrl(`${window.top.location.href}`);
return mergedSites.filter(item => wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath)).length;
}
// ====== HTML UI HELPER FUNCTIONS ======
function createModal(title, content, options = {}) {
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 999999;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
`;
const dialog = document.createElement('div');
dialog.style.cssText = `
background: white;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
max-width: ${options.maxWidth || '600px'};
width: 90%;
max-height: 85vh;
display: flex;
flex-direction: column;
overflow: hidden;
`;
const header = document.createElement('div');
header.style.cssText = `
padding: 20px;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
`;
const titleEl = document.createElement('h2');
titleEl.textContent = title;
titleEl.style.cssText = 'margin: 0; font-size: 20px; font-weight: 600;';
const closeBtn = document.createElement('button');
closeBtn.innerHTML = '✕';
closeBtn.style.cssText = `
background: transparent;
border: none;
color: white;
font-size: 24px;
cursor: pointer;
padding: 0;
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background 0.2s;
`;
closeBtn.onmouseover = () => closeBtn.style.background = 'rgba(255, 255, 255, 0.2)';
closeBtn.onmouseout = () => closeBtn.style.background = 'transparent';
closeBtn.onclick = () => {
document.body.removeChild(modal);
if (options.onClose) options.onClose();
};
header.appendChild(titleEl);
header.appendChild(closeBtn);
const body = document.createElement('div');
body.style.cssText = `
padding: 20px;
overflow-y: auto;
flex: 1;
`;
if (typeof content === 'string') {
body.innerHTML = content;
} else {
body.appendChild(content);
}
dialog.appendChild(header);
dialog.appendChild(body);
modal.appendChild(dialog);
// Close on backdrop click
modal.onclick = (e) => {
if (e.target === modal) {
document.body.removeChild(modal);
if (options.onClose) options.onClose();
}
};
document.body.appendChild(modal);
return { modal, body, closeBtn };
}
function createButton(text, onClick, style = 'primary') {
const btn = document.createElement('button');
btn.textContent = text;
const baseStyle = `
padding: 10px 20px;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
margin: 5px;
`;
const styles = {
primary: `${baseStyle} background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;`,
success: `${baseStyle} background: #10b981; color: white;`,
danger: `${baseStyle} background: #ef4444; color: white;`,
secondary: `${baseStyle} background: #6b7280; color: white;`,
outline: `${baseStyle} background: transparent; color: #667eea; border: 2px solid #667eea;`
};
btn.style.cssText = styles[style] || styles.primary;
btn.onmouseover = () => btn.style.transform = 'scale(1.05)';
btn.onmouseout = () => btn.style.transform = 'scale(1)';
btn.onclick = onClick;
return btn;
}
function createInput(type, value, placeholder = '') {
const input = document.createElement(type === 'textarea' ? 'textarea' : 'input');
if (type !== 'textarea') input.type = type;
input.value = value || '';
input.placeholder = placeholder;
input.style.cssText = `
width: 100%;
padding: 10px;
border: 2px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
font-family: inherit;
margin: 5px 0;
box-sizing: border-box;
transition: border-color 0.2s;
`;
input.onfocus = () => input.style.borderColor = '#667eea';
input.onblur = () => input.style.borderColor = '#e0e0e0';
return input;
}
function createCheckbox(label, checked = false) {
const container = document.createElement('label');
container.style.cssText = `
display: flex;
align-items: center;
margin: 10px 0;
cursor: pointer;
user-select: none;
`;
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.checked = checked;
checkbox.style.cssText = `
width: 20px;
height: 20px;
margin-right: 10px;
cursor: pointer;
`;
const labelText = document.createElement('span');
labelText.textContent = label;
labelText.style.cssText = 'font-size: 14px;';
container.appendChild(checkbox);
container.appendChild(labelText);
return { container, checkbox };
}
function formatStatus(preProcessingRequired, postProcessingRequired, onDemandFloatingButtonRequired, backgroundChangeObserverRequired) {
if (SHOW_STATUS_ONLY_IF_TRUE && !preProcessingRequired && !postProcessingRequired && !onDemandFloatingButtonRequired && !backgroundChangeObserverRequired) {
return '';
}
const preStatus = USE_EMOJI_FOR_STATUS ? (preProcessingRequired ? '✅' : '✖️') : (preProcessingRequired ? 'true' : 'false');
const postStatus = USE_EMOJI_FOR_STATUS ? (postProcessingRequired ? '✅' : '✖️') : (postProcessingRequired ? 'true' : 'false');
const floatingButtonStatus = USE_EMOJI_FOR_STATUS ? (onDemandFloatingButtonRequired ? '✅' : '✖️') : (onDemandFloatingButtonRequired ? 'true' : 'false');
const backgroundObserverStatus = USE_EMOJI_FOR_STATUS ? (backgroundChangeObserverRequired ? '✅' : '✖️') : (backgroundChangeObserverRequired ? 'true' : 'false');
return `Pre: ${preStatus}, Post: ${postStatus}, FB: ${floatingButtonStatus}, BO: ${backgroundObserverStatus}`;
}
// ====== MENU COMMAND 1: Add Current Site ======
function addCurrentSiteMenu() {
const matchCount = countWildcardMatches();
const currentHost = window.top.location.hostname;
const currentPath = window.top.location.pathname;
const domainParts = currentHost.split('.');
const baseDomain = domainParts.length > 2 ? domainParts.slice(-2).join('.') : domainParts.join('.');
const secondLevelDomain = domainParts.length > 2 ? domainParts.slice(-2, -1)[0] : domainParts[0];
const options = [
{ name: `Preferred Domain Match (*${secondLevelDomain}.*)`, pattern: `*${secondLevelDomain}.*` },
{ name: `Base Hostname (*.${baseDomain}*)`, pattern: `*.${baseDomain}*` },
{ name: `Base Domain (*.${secondLevelDomain}.*)`, pattern: `*.${secondLevelDomain}.*` },
{ name: `Host Contains (*${secondLevelDomain}*)`, pattern: `*${secondLevelDomain}*` },
{ name: `Exact Path (${currentHost}${currentPath})`, pattern: normalizeUrl(`${window.top.location.href}`) },
{ name: "Custom Wildcard Pattern", pattern: normalizeUrl(`${window.top.location.href}`) }
];
const content = document.createElement('div');
const infoBox = document.createElement('div');
infoBox.style.cssText = `
background: #f0f9ff;
border: 2px solid #0ea5e9;
border-radius: 8px;
padding: 15px;
margin-bottom: 20px;
`;
infoBox.innerHTML = `
<div style="font-weight: 600; color: #0c4a6e; margin-bottom: 5px;">
📊 Current Page Wildcard Matches: ${matchCount}
</div>
<div style="font-size: 13px; color: #075985;">
This page matches ${matchCount} existing rule${matchCount !== 1 ? 's' : ''} in your include list.
</div>
`;
content.appendChild(infoBox);
const sectionTitle = document.createElement('h3');
sectionTitle.textContent = 'Select a pattern to add:';
sectionTitle.style.cssText = 'margin: 20px 0 15px 0; font-size: 16px; color: #333;';
content.appendChild(sectionTitle);
let selectedOption = null;
const optionButtons = [];
options.forEach((opt, index) => {
const optBtn = document.createElement('button');
optBtn.textContent = opt.name;
optBtn.style.cssText = `
display: block;
width: 100%;
padding: 12px;
margin: 8px 0;
border: 2px solid #e0e0e0;
border-radius: 8px;
background: white;
cursor: pointer;
text-align: left;
font-size: 14px;
transition: all 0.2s;
`;
optBtn.onclick = () => {
optionButtons.forEach(btn => {
btn.style.borderColor = '#e0e0e0';
btn.style.background = 'white';
});
optBtn.style.borderColor = '#667eea';
optBtn.style.background = '#f5f3ff';
selectedOption = index;
};
optionButtons.push(optBtn);
content.appendChild(optBtn);
});
const patternInput = createInput('text', '', 'Custom pattern (only for Custom Wildcard Pattern)');
patternInput.style.display = 'none';
content.appendChild(patternInput);
optionButtons[5].onclick = () => {
optionButtons.forEach(btn => {
btn.style.borderColor = '#e0e0e0';
btn.style.background = 'white';
});
optionButtons[5].style.borderColor = '#667eea';
optionButtons[5].style.background = '#f5f3ff';
selectedOption = 5;
patternInput.style.display = 'block';
patternInput.value = normalizeUrl(`${window.top.location.href}`);
};
const configTitle = document.createElement('h3');
configTitle.textContent = 'Configuration Options:';
configTitle.style.cssText = 'margin: 25px 0 15px 0; font-size: 16px; color: #333;';
content.appendChild(configTitle);
const preCheck = createCheckbox('Pre-processing Required', false);
const postCheck = createCheckbox('Post-processing Required', false);
const floatingBtnCheck = createCheckbox('On-demand Floating Button Required', false);
const backgroundObsCheck = createCheckbox('Background Change Observer Required', false);
content.appendChild(preCheck.container);
content.appendChild(postCheck.container);
content.appendChild(floatingBtnCheck.container);
content.appendChild(backgroundObsCheck.container);
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = 'margin-top: 25px; display: flex; justify-content: flex-end; gap: 10px;';
const { modal } = createModal('➕ Add Current Site to Include List', content, { maxWidth: '650px' });
const cancelBtn = createButton('Cancel', () => {
document.body.removeChild(modal);
}, 'secondary');
const addBtn = createButton('Add Site', () => {
if (selectedOption === null) {
alert('⚠️ Please select a pattern option.');
return;
}
let pattern = normalizeUrl(options[selectedOption].pattern);
if (selectedOption === 5) {
pattern = normalizeUrl(patternInput.value.trim());
if (!pattern) {
alert('⚠️ Invalid pattern. Operation canceled.');
return;
}
}
const entry = {
pattern,
preProcessingRequired: preCheck.checkbox.checked,
postProcessingRequired: postCheck.checkbox.checked,
onDemandFloatingButtonRequired: floatingBtnCheck.checkbox.checked,
backgroundChangeObserverRequired: backgroundObsCheck.checkbox.checked
};
if (!additionalSites.some(item => item.pattern === pattern)) {
additionalSites.push(entry);
GM_setValue(STORAGE_KEY, additionalSites);
refreshMergedSites();
document.body.removeChild(modal);
alert(`✅ Added site with pattern: ${pattern}`);
} else {
alert(`⚠️ Pattern "${pattern}" is already in the list.`);
}
}, 'success');
buttonContainer.appendChild(cancelBtn);
buttonContainer.appendChild(addBtn);
content.appendChild(buttonContainer);
}
// ====== MENU COMMAND 2: Advanced Management ======
function showAdvancedManagement() {
const content = document.createElement('div');
const stats = document.createElement('div');
stats.style.cssText = `
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 15px;
border-radius: 8px;
margin-bottom: 20px;
text-align: center;
`;
stats.innerHTML = `
<div style="font-size: 32px; font-weight: 700;">${additionalSites.length}</div>
<div style="font-size: 14px; opacity: 0.9;">User-Defined Sites</div>
`;
content.appendChild(stats);
const buttonBar = document.createElement('div');
buttonBar.style.cssText = `
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-bottom: 20px;
justify-content: center;
`;
const refreshList = () => {
document.body.querySelectorAll('[data-modal-advanced]').forEach(m => document.body.removeChild(m));
showAdvancedManagement();
};
buttonBar.appendChild(createButton('📤 Export', exportAdditionalSites, 'primary'));
buttonBar.appendChild(createButton('📥 Import', () => {
importAdditionalSites(refreshList);
}, 'primary'));
buttonBar.appendChild(createButton('🗑️ Clear All', () => {
clearAllEntriesConfirm(refreshList);
}, 'danger'));
content.appendChild(buttonBar);
const listTitle = document.createElement('h3');
listTitle.textContent = 'Included Sites:';
listTitle.style.cssText = 'margin: 20px 0 15px 0; font-size: 16px; color: #333;';
content.appendChild(listTitle);
if (additionalSites.length === 0) {
const emptyMsg = document.createElement('div');
emptyMsg.style.cssText = `
text-align: center;
padding: 40px;
color: #9ca3af;
font-size: 16px;
`;
emptyMsg.textContent = 'No user-defined sites added yet.';
content.appendChild(emptyMsg);
} else {
const listContainer = document.createElement('div');
listContainer.style.cssText = `
max-height: 400px;
overflow-y: auto;
border: 1px solid #e0e0e0;
border-radius: 8px;
`;
additionalSites.forEach((item, index) => {
const siteCard = document.createElement('div');
siteCard.style.cssText = `
padding: 15px;
border-bottom: 1px solid #e0e0e0;
transition: background 0.2s;
`;
siteCard.onmouseover = () => siteCard.style.background = '#f9fafb';
siteCard.onmouseout = () => siteCard.style.background = 'white';
const patternDiv = document.createElement('div');
patternDiv.style.cssText = `
font-weight: 600;
color: #1f2937;
margin-bottom: 8px;
word-break: break-all;
`;
patternDiv.textContent = item.pattern;
const statusDiv = document.createElement('div');
statusDiv.style.cssText = 'font-size: 12px; color: #6b7280; margin-bottom: 10px;';
const status = formatStatus(item.preProcessingRequired, item.postProcessingRequired, item.onDemandFloatingButtonRequired, item.backgroundChangeObserverRequired);
statusDiv.textContent = status || 'No special processing';
const actionBar = document.createElement('div');
actionBar.style.cssText = 'display: flex; gap: 8px;';
const editBtn = createButton('✏️ Edit', () => {
editEntryDialog(index, refreshList);
}, 'outline');
editBtn.style.padding = '6px 12px';
editBtn.style.fontSize = '12px';
const deleteBtn = createButton('🗑️ Delete', () => {
deleteEntryConfirm(index, refreshList);
}, 'danger');
deleteBtn.style.padding = '6px 12px';
deleteBtn.style.fontSize = '12px';
actionBar.appendChild(editBtn);
actionBar.appendChild(deleteBtn);
siteCard.appendChild(patternDiv);
siteCard.appendChild(statusDiv);
siteCard.appendChild(actionBar);
listContainer.appendChild(siteCard);
});
content.appendChild(listContainer);
}
const { modal } = createModal('⚙️ IncludeSites-Advanced (View/Edit/Delete/Import/Export)', content, { maxWidth: '750px' });
modal.setAttribute('data-modal-advanced', 'true');
}
function editEntryDialog(index, onComplete) {
const entry = additionalSites[index];
const content = document.createElement('div');
const label1 = document.createElement('label');
label1.textContent = 'Pattern:';
label1.style.cssText = 'display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 600; color: #374151;';
content.appendChild(label1);
const patternInput = createInput('text', entry.pattern, 'Enter pattern');
content.appendChild(patternInput);
const configTitle = document.createElement('h3');
configTitle.textContent = 'Configuration Options:';
configTitle.style.cssText = 'margin: 25px 0 15px 0; font-size: 16px; color: #333;';
content.appendChild(configTitle);
const preCheck = createCheckbox('Pre-processing Required', entry.preProcessingRequired);
const postCheck = createCheckbox('Post-processing Required', entry.postProcessingRequired);
const floatingBtnCheck = createCheckbox('On-demand Floating Button Required', entry.onDemandFloatingButtonRequired);
const backgroundObsCheck = createCheckbox('Background Change Observer Required', entry.backgroundChangeObserverRequired);
content.appendChild(preCheck.container);
content.appendChild(postCheck.container);
content.appendChild(floatingBtnCheck.container);
content.appendChild(backgroundObsCheck.container);
const buttonContainer = document.createElement('div');
buttonContainer.style.cssText = 'margin-top: 25px; display: flex; justify-content: flex-end; gap: 10px;';
const { modal } = createModal('✏️ Edit Entry', content, { maxWidth: '600px' });
const cancelBtn = createButton('Cancel', () => {
document.body.removeChild(modal);
}, 'secondary');
const saveBtn = createButton('Save Changes', () => {
const newPattern = normalizeUrl(patternInput.value.trim());
if (!newPattern) {
alert('⚠️ Invalid pattern. Operation canceled.');
return;
}
entry.pattern = newPattern;
entry.preProcessingRequired = preCheck.checkbox.checked;
entry.postProcessingRequired = postCheck.checkbox.checked;
entry.onDemandFloatingButtonRequired = floatingBtnCheck.checkbox.checked;
entry.backgroundChangeObserverRequired = backgroundObsCheck.checkbox.checked;
GM_setValue(STORAGE_KEY, additionalSites);
refreshMergedSites();
document.body.removeChild(modal);
alert('✅ Entry updated successfully.');
if (onComplete) onComplete();
}, 'success');
buttonContainer.appendChild(cancelBtn);
buttonContainer.appendChild(saveBtn);
content.appendChild(buttonContainer);
}
function deleteEntryConfirm(index, onComplete) {
const entry = additionalSites[index];
if (confirm(`🗑️ Are you sure you want to delete this entry?\n\nPattern: ${entry.pattern}`)) {
additionalSites.splice(index, 1);
GM_setValue(STORAGE_KEY, additionalSites);
refreshMergedSites();
alert('✅ Entry deleted successfully.');
if (onComplete) onComplete();
}
}
function clearAllEntriesConfirm(onComplete) {
if (additionalSites.length === 0) {
alert('⚠️ No user-defined entries to clear.');
return;
}
if (confirm(`🚨 You have ${additionalSites.length} entries. Clear all?`)) {
additionalSites = [];
GM_setValue(STORAGE_KEY, additionalSites);
refreshMergedSites();
alert('✅ All user-defined entries cleared.');
if (onComplete) onComplete();
}
}
function exportAdditionalSites() {
const data = JSON.stringify(additionalSites, null, 2);
const blob = new Blob([data], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'additionalSites_backup.json';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
alert('📤 Additional sites exported as JSON.');
}
function importAdditionalSites(onComplete) {
const input = document.createElement('input');
input.type = 'file';
input.accept = '.json';
input.style.display = 'none';
input.onchange = event => {
const reader = new FileReader();
reader.onload = e => {
try {
const importedData = JSON.parse(e.target.result);
if (Array.isArray(importedData)) {
additionalSites = importedData.map(item => {
if (typeof item === 'string') {
return {
pattern: normalizeUrl(item),
preProcessingRequired: false,
postProcessingRequired: false,
onDemandFloatingButtonRequired: false,
backgroundChangeObserverRequired: false
};
} else if (typeof item === 'object' && item.pattern) {
return {
pattern: normalizeUrl(item.pattern),
preProcessingRequired: item.preProcessingRequired || false,
postProcessingRequired: item.postProcessingRequired || false,
onDemandFloatingButtonRequired: item.onDemandFloatingButtonRequired || false,
backgroundChangeObserverRequired: item.backgroundChangeObserverRequired || false
};
}
throw new Error('Invalid data format');
});
GM_setValue(STORAGE_KEY, additionalSites);
refreshMergedSites();
alert('📥 Sites imported successfully.');
if (onComplete) onComplete();
} else {
throw new Error('Invalid data format');
}
} catch (error) {
alert('❌ Failed to import sites: ' + error.message);
}
};
reader.readAsText(event.target.files[0]);
};
document.body.appendChild(input);
input.click();
document.body.removeChild(input);
}
// ====== REGISTER MENU COMMANDS (Simplified to 2) ======
GM_registerMenuCommand(`➕ Add Current Site to Include List (Included via ${countWildcardMatches()} wildcard matches)`, addCurrentSiteMenu);
GM_registerMenuCommand("⚙️ IncludeSites-Advanced (View/Edit/Delete/Import/Export)", showAdvancedManagement);
// ====== EXPOSE PUBLIC API ======
window.shouldRunOnThisSite = shouldRunOnThisSite;
window.isPreProcessingRequired = function() {
const currentFullPath = normalizeUrl(`${window.top.location.href}`);
const entry = mergedSites.find(item => wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath));
return entry ? entry.preProcessingRequired : false;
};
window.isPostProcessingRequired = function() {
const currentFullPath = normalizeUrl(`${window.top.location.href}`);
const entry = mergedSites.find(item => wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath));
return entry ? entry.postProcessingRequired : false;
};
window.isOnDemandFloatingButtonRequired = function() {
const currentFullPath = normalizeUrl(`${window.top.location.href}`);
const entry = mergedSites.find(item => wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath));
return entry ? entry.onDemandFloatingButtonRequired : false;
};
window.isBackgroundChangeObserverRequired = function() {
const currentFullPath = normalizeUrl(`${window.top.location.href}`);
const entry = mergedSites.find(item => wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath));
return entry ? entry.backgroundChangeObserverRequired : false;
};
})();
})();
//To use this in another script use @require
// // @run-at document-end
// // ==/UserScript==
// window.SCRIPT_STORAGE_KEY = "magnetLinkHashChecker"; // UNIQUE STORAGE KEY
// window.GET_DEFAULT_LIST = function() {
// return [
// { pattern: "*1337x.*", preProcessingRequired: false, postProcessingRequired: false, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
// { pattern: "*yts.*", preProcessingRequired: true, postProcessingRequired: true, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
// { pattern: "*torrentgalaxy.*", preProcessingRequired: false, postProcessingRequired: true, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
// { pattern: "*bitsearch.*", preProcessingRequired: false, postProcessingRequired: false, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
// { pattern: "*thepiratebay.*", preProcessingRequired: false, postProcessingRequired: false, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false },
// { pattern: "*ext.*", preProcessingRequired: false, postProcessingRequired: false, onDemandFloatingButtonRequired: false, backgroundChangeObserverRequired: false }
// ];
// };
// (async function () {
// 'use strict';
// // ✅ Wait until `shouldRunOnThisSite` is available
// while (typeof shouldRunOnThisSite === 'undefined') {
// await new Promise(resolve => setTimeout(resolve, 50));
// }
// if (!(await shouldRunOnThisSite())) return;
// //alert("running");
// console.log("Pre-Customization enabled for this site: " + isPreProcessingRequired() );
// console.log("Post-Customization enabled for this site: " + isPostProcessingRequired() );
// const OFFCLOUD_CACHE_API_URL = 'https://offcloud.com/api/cache';