Manage allowed sites dynamically and reference this in other scripts.
Version vom
Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyfork.org/scripts/526770/1670391/Site%20Filter%20%28Protocol-Independent%29.js
// ==UserScript==
// @name Site Filter (Protocol-Independent)
// @namespace http://tampermonkey.net/
// @version 3.2
// @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];
// Reordered: Custom Wildcard Pattern first, then others
const options = [
{ name: "Custom Wildcard Pattern", pattern: normalizeUrl(`${window.top.location.href}`) },
{ 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}`) }
];
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 = 'Pattern to add:';
sectionTitle.style.cssText = 'margin: 20px 0 10px 0; font-size: 16px; color: #333;';
content.appendChild(sectionTitle);
let selectedOption = 0; // Default to first option (Custom Wildcard Pattern)
const optionButtons = [];
// Custom Wildcard Pattern input (always visible, selected by default)
const patternInput = createInput('text', normalizeUrl(`${window.top.location.href}`), 'Enter custom wildcard pattern');
patternInput.style.marginBottom = '12px';
content.appendChild(patternInput);
// Container for other pattern options (initially hidden)
const otherPatternsContainer = document.createElement('div');
otherPatternsContainer.style.cssText = 'display: none; margin-top: 10px;';
// Create buttons for other patterns (indices 1-5)
for (let index = 1; index < options.length; index++) {
const opt = options[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 = () => {
selectedOption = index;
patternInput.value = normalizeUrl(opt.pattern);
// Hide other patterns after selection
otherPatternsContainer.style.display = 'none';
toggleBtn.textContent = '▼ Show Other Patterns';
};
optionButtons.push(optBtn);
otherPatternsContainer.appendChild(optBtn);
}
// Toggle button for other patterns
const toggleBtn = document.createElement('button');
toggleBtn.textContent = '▼ Show Other Patterns';
toggleBtn.style.cssText = `
width: 100%;
padding: 10px;
margin: 8px 0 12px 0;
border: 2px solid #667eea;
border-radius: 6px;
background: white;
color: #667eea;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s;
`;
toggleBtn.onmouseover = () => toggleBtn.style.background = '#f5f3ff';
toggleBtn.onmouseout = () => toggleBtn.style.background = 'white';
toggleBtn.onclick = () => {
if (otherPatternsContainer.style.display === 'none') {
otherPatternsContainer.style.display = 'block';
toggleBtn.textContent = '▲ Hide Other Patterns';
} else {
otherPatternsContainer.style.display = 'none';
toggleBtn.textContent = '▼ Show Other Patterns';
}
};
content.appendChild(toggleBtn);
content.appendChild(otherPatternsContainer);
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', () => {
// Get pattern from input (now always from patternInput since it's the primary control)
const 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');
// Get current site for matching
const currentFullPath = normalizeUrl(`${window.top.location.href}`);
// Compact stats header
const stats = document.createElement('div');
stats.style.cssText = `
background: #667eea;
color: white;
padding: 10px 15px;
border-radius: 6px;
margin-bottom: 12px;
display: flex;
justify-content: space-between;
align-items: center;
`;
stats.innerHTML = `
<div><span style="font-size: 20px; font-weight: 700;">${additionalSites.length}</span> <span style="font-size: 13px;">Sites</span></div>
<div style="font-size: 12px; opacity: 0.9;">${countWildcardMatches()} matching current page</div>
`;
content.appendChild(stats);
// Compact button bar
const buttonBar = document.createElement('div');
buttonBar.style.cssText = `
display: flex;
gap: 8px;
margin-bottom: 12px;
flex-wrap: wrap;
`;
const refreshList = () => {
document.body.querySelectorAll('[data-modal-advanced]').forEach(m => document.body.removeChild(m));
showAdvancedManagement();
};
const compactBtn = (text, onClick, style) => {
const btn = createButton(text, onClick, style);
btn.style.padding = '6px 12px';
btn.style.fontSize = '13px';
btn.style.margin = '0';
return btn;
};
buttonBar.appendChild(compactBtn('📤 Export', exportAdditionalSites, 'primary'));
buttonBar.appendChild(compactBtn('📥 Import', () => {
importAdditionalSites(refreshList);
}, 'primary'));
buttonBar.appendChild(compactBtn('🗑️ Clear All', () => {
clearAllEntriesConfirm(refreshList);
}, 'danger'));
content.appendChild(buttonBar);
if (additionalSites.length === 0) {
const emptyMsg = document.createElement('div');
emptyMsg.style.cssText = `
text-align: center;
padding: 30px;
color: #9ca3af;
font-size: 14px;
`;
emptyMsg.textContent = 'No user-defined sites added yet.';
content.appendChild(emptyMsg);
} else {
// Search/Filter input
const searchContainer = document.createElement('div');
searchContainer.style.cssText = 'margin-bottom: 12px;';
const searchInput = createInput('text', '', '🔍 Search patterns...');
searchInput.style.margin = '0';
searchInput.style.padding = '8px 12px';
searchInput.style.fontSize = '13px';
// Explicitly ensure input is editable
searchInput.readOnly = false;
searchInput.disabled = false;
searchInput.autocomplete = 'off';
searchContainer.appendChild(searchInput);
content.appendChild(searchContainer);
// Sort sites: matching patterns first, then alphabetically
const sortedSites = additionalSites.map((item, index) => ({
...item,
originalIndex: index,
isMatch: wildcardToRegex(normalizeUrl(item.pattern)).test(currentFullPath)
})).sort((a, b) => {
// First by match status (matching first)
if (a.isMatch !== b.isMatch) return b.isMatch ? 1 : -1;
// Then alphabetically by pattern
return a.pattern.localeCompare(b.pattern);
});
const listContainer = document.createElement('div');
listContainer.style.cssText = `
max-height: 450px;
overflow-y: auto;
border: 1px solid #e0e0e0;
border-radius: 6px;
`;
// Function to render a site entry
function renderSiteEntry(item) {
const siteCard = document.createElement('div');
siteCard.style.cssText = `
padding: 8px 12px;
border-bottom: 1px solid #f0f0f0;
background: ${item.isMatch ? '#f0f9ff' : 'white'};
`;
siteCard.setAttribute('data-pattern', item.pattern.toLowerCase());
const topRow = document.createElement('div');
topRow.style.cssText = 'display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;';
const patternDiv = document.createElement('div');
patternDiv.style.cssText = `
font-weight: 600;
color: #1f2937;
word-break: break-all;
font-size: 13px;
flex: 1;
`;
patternDiv.textContent = (item.isMatch ? '✓ ' : '') + item.pattern;
const actionBar = document.createElement('div');
actionBar.style.cssText = 'display: flex; gap: 6px; flex-shrink: 0; margin-left: 10px;';
const editBtn = document.createElement('button');
editBtn.textContent = '✏️';
editBtn.title = 'Edit';
editBtn.style.cssText = `
padding: 4px 8px;
border: 1px solid #d1d5db;
background: white;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
`;
editBtn.onclick = () => editEntryDialog(item.originalIndex, refreshList);
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '🗑️';
deleteBtn.title = 'Delete';
deleteBtn.style.cssText = `
padding: 4px 8px;
border: 1px solid #fecaca;
background: #fee2e2;
color: #991b1b;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
`;
deleteBtn.onclick = () => deleteEntryConfirm(item.originalIndex, refreshList);
actionBar.appendChild(editBtn);
actionBar.appendChild(deleteBtn);
topRow.appendChild(patternDiv);
topRow.appendChild(actionBar);
const statusDiv = document.createElement('div');
statusDiv.style.cssText = 'font-size: 11px; color: #6b7280;';
const status = formatStatus(item.preProcessingRequired, item.postProcessingRequired, item.onDemandFloatingButtonRequired, item.backgroundChangeObserverRequired);
statusDiv.textContent = status || 'Default settings';
siteCard.appendChild(topRow);
if (status) siteCard.appendChild(statusDiv);
return siteCard;
}
// Render all sorted sites
sortedSites.forEach(item => {
listContainer.appendChild(renderSiteEntry(item));
});
content.appendChild(listContainer);
// Filter functionality
searchInput.oninput = () => {
const searchTerm = searchInput.value.toLowerCase();
const cards = listContainer.querySelectorAll('[data-pattern]');
let visibleCount = 0;
cards.forEach(card => {
const pattern = card.getAttribute('data-pattern');
if (pattern.includes(searchTerm)) {
card.style.display = '';
visibleCount++;
} else {
card.style.display = 'none';
}
});
// Show "no results" message if needed
let noResultsMsg = listContainer.querySelector('[data-no-results]');
if (visibleCount === 0 && searchTerm) {
if (!noResultsMsg) {
noResultsMsg = document.createElement('div');
noResultsMsg.setAttribute('data-no-results', 'true');
noResultsMsg.style.cssText = 'padding: 20px; text-align: center; color: #9ca3af; font-size: 13px;';
noResultsMsg.textContent = 'No patterns match your search';
listContainer.appendChild(noResultsMsg);
}
} else if (noResultsMsg) {
noResultsMsg.remove();
}
};
}
const { modal } = createModal('⚙️ IncludeSites-Advanced', content, { maxWidth: '700px' });
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';