您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
Enhanced Security Configuration Dialog with improved performance, validation, and security features
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/549278/1659574/ModernMonkeyConfig%20Enhanced%20Security%20Edition%20v040.js
// ==UserScript== // @name ModernMonkeyConfig Enhanced Security Edition v0.4.0 // @noframes // @version 0.4.0 // @namespace http://odyniec.net/ // @include * // @description Enhanced Security Configuration Dialog with improved performance, validation, and security features // @require https://cdnjs.cloudflare.com/ajax/libs/dompurify/3.2.5/purify.min.js#sha384-qSFej5dZNviyoPgYJ5+Xk4bEbX8AYddxAHPuzs1aSgRiXxJ3qmyWNaPsRkpv/+x5 // ==/UserScript== /** * ModernMonkeyConfig Enhanced Security Edition * نسخة محسنة أمنياً مع تحسينات شاملة للأداء والحماية */ class ModernMonkeyConfig { constructor(data) { this.version = '0.4.0'; this.data = this.validateAndSanitizeConfig(data); this.params = this.data.parameters || this.data.params || {}; this.values = {}; this.storageKey = ''; this.displayed = false; this.openLayer = null; this.shadowRoot = null; this.container = null; this.iframeFallback = null; this.elementCache = new Map(); this.eventListeners = new Map(); this.trustedPolicy = null; this.validationRules = new Map(); this.init(); } /** * تسجيل الأخطاء والرسائل */ log(message, level = 'info') { try { const timestamp = new Date().toISOString(); const formattedMessage = `[ModernMonkeyConfig v${this.version}] ${timestamp}: ${message}`; if (console[level]) { console[level](formattedMessage); } else { console.log(`[${level.toUpperCase()}] ${formattedMessage}`); } } catch (e) { console.error(`[ModernMonkeyConfig v${this.version}] Logging failed: ${e.message}`); } } /** * التحقق من صحة وتنظيف إعدادات التكوين */ validateAndSanitizeConfig(data) { if (!data || typeof data !== 'object') { throw new Error('Configuration data must be an object'); } const sanitized = { title: this.sanitizeString(data.title) || 'Configuration', buttons: Array.isArray(data.buttons) ? data.buttons.filter(btn => ['save', 'reset', 'close', 'reload', 'homepage'].includes(btn) ) : ['save', 'reset', 'close', 'reload', 'homepage'], menuCommand: Boolean(data.menuCommand), parameters: {}, shadowWidth: this.validateDimension(data.shadowWidth) || '600px', shadowHeight: this.validateDimension(data.shadowHeight) || '400px', iframeWidth: this.validateDimension(data.iframeWidth) || '600px', iframeHeight: this.validateDimension(data.iframeHeight) || '400px', shadowFontSize: this.validateFontSize(data.shadowFontSize) || '14px', shadowFontColor: this.validateColor(data.shadowFontColor) || '#000000', iframeFontSize: this.validateFontSize(data.iframeFontSize) || '14px', iframeFontColor: this.validateColor(data.iframeFontColor) || '#000000', onSave: typeof data.onSave === 'function' ? data.onSave : null }; if (data.parameters && typeof data.parameters === 'object') { for (const [key, param] of Object.entries(data.parameters)) { if (this.isValidParameterKey(key) && this.isValidParameter(param)) { sanitized.parameters[key] = this.sanitizeParameter(param); } else { this.log(`Invalid parameter skipped: ${key}`, 'warn'); } } } return sanitized; } /** * التحقق من صحة مفتاح المعامل */ isValidParameterKey(key) { return typeof key === 'string' && key.length > 0 && key.length <= 50 && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key); } /** * التحقق من صحة المعامل */ isValidParameter(param) { if (!param || typeof param !== 'object') return false; const validTypes = ['checkbox', 'number', 'text', 'color', 'textarea', 'range', 'radio', 'file', 'button', 'select', 'group', 'custom']; return validTypes.includes(param.type); } /** * تنظيف المعامل */ sanitizeParameter(param) { const sanitized = { type: param.type, label: this.sanitizeString(param.label), default: this.sanitizeValue(param.default, param.type), column: this.validateColumn(param.column) }; switch (param.type) { case 'number': case 'range': sanitized.min = this.sanitizeNumber(param.min); sanitized.max = this.sanitizeNumber(param.max); sanitized.step = this.sanitizeNumber(param.step) || 1; break; case 'textarea': sanitized.rows = Math.max(1, Math.min(20, parseInt(param.rows) || 4)); sanitized.cols = Math.max(10, Math.min(100, parseInt(param.cols) || 20)); break; case 'radio': case 'select': if (param.choices && typeof param.choices === 'object') { sanitized.choices = this.sanitizeChoices(param.choices); } sanitized.multiple = Boolean(param.multiple); break; case 'file': sanitized.accept = this.sanitizeString(param.accept) || '*/*'; break; case 'custom': sanitized.html = this.sanitizeString(param.html); if (typeof param.get === 'function') sanitized.get = param.get; if (typeof param.set === 'function') sanitized.set = param.set; break; } ['fontSize', 'fontColor', 'inputWidth', 'inputHeight', 'checkboxWidth', 'checkboxHeight'].forEach(prop => { if (param[prop]) { sanitized[prop] = this.sanitizeString(param[prop]); } }); return sanitized; } /** * تنظيف النص */ sanitizeString(str) { if (typeof str !== 'string') return ''; return str.trim().substring(0, 1000); } /** * تنظيف الرقم */ sanitizeNumber(num) { const parsed = parseFloat(num); return isNaN(parsed) ? undefined : parsed; } /** * تنظيف القيمة حسب النوع */ sanitizeValue(value, type) { switch (type) { case 'number': case 'range': return this.sanitizeNumber(value); case 'checkbox': return Boolean(value); case 'text': case 'color': case 'textarea': return this.sanitizeString(value); case 'select': return Array.isArray(value) ? value.map(v => this.sanitizeString(v)) : this.sanitizeString(value); default: return value; } } /** * تنظيف الخيارات */ sanitizeChoices(choices) { const sanitized = {}; for (const [key, value] of Object.entries(choices)) { const cleanKey = this.sanitizeString(key); const cleanValue = this.sanitizeString(value); if (cleanKey && cleanValue) { sanitized[cleanKey] = cleanValue; } } return sanitized; } /** * التحقق من صحة العمود */ validateColumn(column) { const validColumns = ['left', 'right', 'top', 'bottom', 'left&top', 'right&top', 'left&bottom', 'right&bottom']; return validColumns.includes(column) ? column : null; } /** * التحقق من صحة الأبعاد */ validateDimension(dimension) { if (typeof dimension !== 'string') return null; return /^\d+(px|em|rem|%|vh|vw)$/.test(dimension) ? dimension : null; } /** * التحقق من صحة حجم الخط */ validateFontSize(fontSize) { if (typeof fontSize !== 'string') return null; return /^\d+(px|em|rem)$/.test(fontSize) ? fontSize : null; } /** * التحقق من صحة اللون */ validateColor(color) { if (typeof color !== 'string') return null; return /^#([0-9A-Fa-f]{3}){1,2}$/.test(color) ? color : null; } /** * إنشاء Trusted Types Policy */ createTrustedPolicy() { try { if (window.trustedTypes && window.trustedTypes.createPolicy) { this.trustedPolicy = window.trustedTypes.createPolicy(`monkeyConfig-${Date.now()}`, { createHTML: (input) => { if (typeof DOMPurify === 'undefined') { this.log('DOMPurify not available, using fallback sanitization', 'warn'); return this.fallbackSanitize(input); } return DOMPurify.sanitize(input, { ALLOWED_TAGS: ['div', 'span', 'table', 'tr', 'td', 'input', 'textarea', 'button', 'label', 'select', 'option', 'fieldset', 'legend', 'h1', 'br', 'svg', 'path', 'style'], ALLOWED_ATTR: ['type', 'name', 'id', 'class', 'style', 'for', 'value', 'min', 'max', 'step', 'rows', 'cols', 'multiple', 'accept', 'width', 'height', 'viewBox', 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'd', 'colspan', 'checked'], ALLOW_DATA_ATTR: false, RETURN_TRUSTED_TYPE: true }); } }); } else { this.trustedPolicy = { createHTML: (input) => this.fallbackSanitize(input) }; } } catch (e) { this.log(`Failed to create Trusted Types policy: ${e.message}`, 'error'); this.trustedPolicy = { createHTML: (input) => this.fallbackSanitize(input) }; } } /** * تنظيف احتياطي للHTML */ fallbackSanitize(input) { return String(input) .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') .replace(/on\w+\s*=\s*"[^"]*"/gi, '') .replace(/on\w+\s*=\s*'[^']*'/gi, '') .replace(/javascript:/gi, '') .replace(/vbscript:/gi, '') .replace(/data:/gi, ''); } /** * إنشاء HTML موثوق */ createTrustedHTML(htmlString) { try { return this.trustedPolicy.createHTML(htmlString); } catch (e) { this.log(`Failed to create TrustedHTML: ${e.message}`, 'error'); return ''; } } /** * تهيئة الكلاس */ init() { try { this.createTrustedPolicy(); this.setupValidationRules(); this.storageKey = `_ModernMonkeyConfig_${this.data.title.replace(/[^a-zA-Z0-9]/g, '_')}_cfg`; this.loadStoredValues(); if (this.data.menuCommand) { this.registerMenuCommand(); } this.setupPublicMethods(); this.log('ModernMonkeyConfig initialized successfully'); } catch (e) { this.log(`Initialization failed: ${e.message}`, 'error'); throw e; } } /** * إعداد قواعد التحقق */ setupValidationRules() { this.validationRules.set('email', /^[^\s@]+@[^\s@]+\.[^\s@]+$/); this.validationRules.set('url', /^https?:\/\/.+/); this.validationRules.set('number', /^\d+$/); this.validationRules.set('float', /^\d*\.?\d+$/); } /** * تحميل القيم المحفوظة */ loadStoredValues() { try { let storedValues = {}; if (typeof GM_getValue !== 'undefined') { const stored = GM_getValue(this.storageKey); if (stored) { storedValues = JSON.parse(stored); } } this.shadowWidth = this.validateDimension(storedValues.shadowWidth) || this.data.shadowWidth; this.shadowHeight = this.validateDimension(storedValues.shadowHeight) || this.data.shadowHeight; this.iframeWidth = this.validateDimension(storedValues.iframeWidth) || this.data.iframeWidth; this.iframeHeight = this.validateDimension(storedValues.iframeHeight) || this.data.iframeHeight; this.shadowFontSize = this.validateFontSize(storedValues.shadowFontSize) || this.data.shadowFontSize; this.shadowFontColor = this.validateColor(storedValues.shadowFontColor) || this.data.shadowFontColor; this.iframeFontSize = this.validateFontSize(storedValues.iframeFontSize) || this.data.iframeFontSize; this.iframeFontColor = this.validateColor(storedValues.iframeFontColor) || this.data.iframeFontColor; for (const [key, param] of Object.entries(this.params)) { this.values[key] = storedValues[key] !== undefined ? this.sanitizeValue(storedValues[key], param.type) : param.default; } } catch (e) { this.log(`Failed to load stored values: ${e.message}`, 'error'); for (const [key, param] of Object.entries(this.params)) { this.values[key] = param.default; } } } /** * تسجيل أمر القائمة */ registerMenuCommand() { try { if (typeof GM_registerMenuCommand !== 'undefined') { const commandText = this.data.menuCommand === true ? this.data.title : String(this.data.menuCommand); GM_registerMenuCommand(commandText, () => this.open()); } } catch (e) { this.log(`Failed to register menu command: ${e.message}`, 'error'); } } /** * إعداد الطرق العامة */ setupPublicMethods() { this.open = this.open.bind(this); this.close = this.close.bind(this); this.get = this.get.bind(this); this.set = this.set.bind(this); this.validate = this.validate.bind(this); this.reset = this.reset.bind(this); } /** * الحصول على قيمة */ get(name) { if (!this.params[name]) { this.log(`Parameter '${name}' does not exist`, 'warn'); return undefined; } return this.values[name]; } /** * تعيين قيمة */ set(name, value) { try { if (!this.params[name]) { this.log(`Parameter '${name}' does not exist`, 'warn'); return false; } const sanitizedValue = this.sanitizeValue(value, this.params[name].type); if (this.validateValue(name, sanitizedValue)) { this.values[name] = sanitizedValue; this.updateUI(); return true; } return false; } catch (e) { this.log(`Failed to set value for ${name}: ${e.message}`, 'error'); return false; } } /** * التحقق من صحة القيمة */ validateValue(name, value) { const param = this.params[name]; if (!param) return false; switch (param.type) { case 'number': case 'range': const num = parseFloat(value); if (isNaN(num)) return false; if (param.min !== undefined && num < param.min) return false; if (param.max !== undefined && num > param.max) return false; return true; case 'text': if (param.validation && this.validationRules.has(param.validation)) { return this.validationRules.get(param.validation).test(value); } return typeof value === 'string' && value.length <= 1000; case 'checkbox': return typeof value === 'boolean'; default: return true; } } /** * التحقق من صحة جميع القيم */ validate() { const errors = []; for (const [name, value] of Object.entries(this.values)) { if (!this.validateValue(name, value)) { errors.push(`Invalid value for parameter '${name}': ${value}`); } } return errors; } /** * إعادة تعيين القيم الافتراضية */ reset() { try { for (const [key, param] of Object.entries(this.params)) { this.values[key] = param.default; } this.updateUI(); this.log('Configuration reset to defaults'); } catch (e) { this.log(`Failed to reset configuration: ${e.message}`, 'error'); } } /** * هروب HTML */ escapeHtml(string) { if (string == null) return ''; return String(string) .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } /** * الحصول على أيقونة */ getIcon(type) { const icons = { save: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 6 9 17l-5-5"/></svg>', reset: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>', close: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>', reload: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/><path d="M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16"/><path d="M16 16h5v5"/></svg>', homepage: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/><path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>' }; return icons[type] || ''; } /** * الحصول على نص الزر */ getButtonText(buttonType) { const texts = { save: 'Save Without Reload', reset: 'Reset', reload: 'Save With Reload', homepage: 'Homepage' }; return texts[buttonType] || buttonType; } /** * إنشاء HTML للواجهة */ render() { try { const title = this.escapeHtml(this.data.title); let html = ` <div class="__MonkeyConfig_container"> <div class="__MonkeyConfig_header"> <h1>${title}</h1> <button type="button" id="__MonkeyConfig_button_close" class="__MonkeyConfig_close_btn" aria-label="Close"> ${this.getIcon('close')} </button> </div> <div class="__MonkeyConfig_content"> <div class="__MonkeyConfig_sections">`; html += this.renderSection('top'); html += this.renderColumns('top'); html += this.renderColumns('middle'); html += this.renderDefaultSection(); html += this.renderColumns('bottom'); html += this.renderSection('bottom'); html += ` </div> </div> <div class="__MonkeyConfig_footer"> ${this.renderButtons()} </div> </div>`; return this.createTrustedHTML(html); } catch (e) { this.log(`Failed to render HTML: ${e.message}`, 'error'); return this.createTrustedHTML('<div class="__MonkeyConfig_error">Error rendering configuration dialog</div>'); } } /** * إنشاء قسم */ renderSection(position) { const items = Object.entries(this.params) .filter(([, param]) => param.column === position) .map(([key, param]) => this.renderField(key, param)) .join(''); return items ? `<div class="__MonkeyConfig_section_${position}">${items}</div>` : ''; } /** * إنشاء الأعمدة */ renderColumns(position) { const leftColumn = position !== 'middle' ? `left&${position}` : 'left'; const rightColumn = position !== 'middle' ? `right&${position}` : 'right'; const leftItems = Object.entries(this.params) .filter(([, param]) => param.column === leftColumn) .map(([key, param]) => this.renderField(key, param)) .join(''); const rightItems = Object.entries(this.params) .filter(([, param]) => param.column === rightColumn) .map(([key, param]) => this.renderField(key, param)) .join(''); if (!leftItems && !rightItems) return ''; return ` <div class="__MonkeyConfig_columns"> <div class="__MonkeyConfig_left_column">${leftItems}</div> <div class="__MonkeyConfig_right_column">${rightItems}</div> </div>`; } /** * إنشاء القسم الافتراضي */ renderDefaultSection() { const items = Object.entries(this.params) .filter(([, param]) => !param.column) .map(([key, param]) => this.renderField(key, param)) .join(''); return items ? `<table class="__MonkeyConfig_default_table">${items}</table>` : ''; } /** * إنشاء حقل */ renderField(name, param) { const fieldId = `__MonkeyConfig_field_${this.escapeHtml(name)}`; const parentId = `__MonkeyConfig_parent_${this.escapeHtml(name)}`; const label = this.renderLabel(name, param, fieldId); const field = this.renderInput(name, param, fieldId); const isInline = ['checkbox', 'number', 'text'].includes(param.type); if (param.type === 'group') { return `<tr><td colspan="2">${field}</td></tr>`; } if (isInline) { return ` <tr> <td id="${parentId}" colspan="2" class="__MonkeyConfig_inline"> ${label}${field} </td> </tr>`; } return ` <tr> <td class="__MonkeyConfig_label_cell">${label}</td> <td id="${parentId}" class="__MonkeyConfig_field_cell">${field}</td> </tr>`; } /** * إنشاء تسمية */ renderLabel(name, param, fieldId) { const labelText = param.label || name.charAt(0).toUpperCase() + name.slice(1).replace(/_/g, ' '); const styles = []; if (param.fontSize) styles.push(`font-size:${this.escapeHtml(param.fontSize)}`); if (param.fontColor) styles.push(`color:${this.escapeHtml(param.fontColor)}`); if (param.labelAlign) styles.push(`text-align:${this.escapeHtml(param.labelAlign)}`); const styleAttr = styles.length ? ` style="${styles.join(';')}"` : ''; return `<label for="${fieldId}"${styleAttr}>${this.escapeHtml(labelText)}</label>`; } /** * إنشاء حقل إدخال */ renderInput(name, param, fieldId) { const inputName = this.escapeHtml(name); const value = this.values[name]; switch (param.type) { case 'checkbox': return `<input type="checkbox" id="${fieldId}" name="${inputName}" ${value ? 'checked' : ''}>`; case 'number': return `<input type="number" id="${fieldId}" name="${inputName}" value="${this.escapeHtml(value || '')}" ${param.min !== undefined ? `min="${param.min}"` : ''} ${param.max !== undefined ? `max="${param.max}"` : ''} ${param.step !== undefined ? `step="${param.step}"` : ''}>`; case 'text': return `<input type="text" id="${fieldId}" name="${inputName}" value="${this.escapeHtml(value || '')}">`; case 'color': return `<input type="color" id="${fieldId}" name="${inputName}" value="${this.escapeHtml(value || '#000000')}">`; case 'textarea': return `<textarea id="${fieldId}" name="${inputName}" rows="${param.rows || 4}" cols="${param.cols || 20}">${this.escapeHtml(value || '')}</textarea>`; case 'range': return `<input type="range" id="${fieldId}" name="${inputName}" value="${this.escapeHtml(value || param.min || 0)}" min="${param.min || 0}" max="${param.max || 100}" step="${param.step || 1}"> <span class="__MonkeyConfig_range_value">${value || param.min || 0}</span>`; case 'select': return this.renderSelect(fieldId, inputName, param, value); case 'radio': return this.renderRadio(fieldId, inputName, param, value); case 'file': return `<input type="file" id="${fieldId}" name="${inputName}" accept="${this.escapeHtml(param.accept || '*/*')}">`; case 'button': return `<button type="button" id="${fieldId}" name="${inputName}" class="__MonkeyConfig_custom_button">${this.escapeHtml(param.label || 'Button')}</button>`; case 'group': return this.renderGroup(fieldId, inputName, param); case 'custom': return this.renderCustom(fieldId, inputName, param); default: return `<span class="__MonkeyConfig_error">Unknown field type: ${this.escapeHtml(param.type)}</span>`; } } /** * إنشاء قائمة منسدلة */ renderSelect(fieldId, inputName, param, value) { const multipleAttr = param.multiple ? ' multiple' : ''; const selectedValues = Array.isArray(value) ? value : [value]; let options = ''; if (param.choices) { for (const [key, label] of Object.entries(param.choices)) { const selected = selectedValues.includes(key) ? ' selected' : ''; options += `<option value="${this.escapeHtml(key)}"${selected}>${this.escapeHtml(label)}</option>`; } } return `<select id="${fieldId}" name="${inputName}"${multipleAttr}>${options}</select>`; } /** * إنشاء أزرار الراديو */ renderRadio(fieldId, inputName, param, value) { let radioHtml = '<div class="__MonkeyConfig_radio_group">'; if (param.choices) { for (const [key, label] of Object.entries(param.choices)) { const checked = value === key ? ' checked' : ''; const radioId = `${fieldId}_${key}`; radioHtml += ` <label class="__MonkeyConfig_radio_label"> <input type="radio" id="${radioId}" name="${inputName}" value="${this.escapeHtml(key)}"${checked}> ${this.escapeHtml(label)} </label>`; } } radioHtml += '</div>'; return radioHtml; } /** * إنشاء مجموعة */ renderGroup(fieldId, inputName, param) { const legend = param.label ? `<legend>${this.escapeHtml(param.label)}</legend>` : ''; return `<fieldset id="${fieldId}" class="__MonkeyConfig_group">${legend}</fieldset>`; } /** * إنشاء حقل مخصص */ renderCustom(fieldId, inputName, param) { const customHtml = param.html || ''; return `<div id="${fieldId}" class="__MonkeyConfig_custom" data-name="${inputName}">${customHtml}</div>`; } /** * إنشاء الأزرار */ renderButtons() { let buttonsHtml = ''; for (const buttonType of this.data.buttons) { if (buttonType === 'close') continue; // Close button is in header const buttonId = `__MonkeyConfig_button_${buttonType}`; const buttonText = this.getButtonText(buttonType); const icon = this.getIcon(buttonType); buttonsHtml += ` <button type="button" id="${buttonId}" class="__MonkeyConfig_btn __MonkeyConfig_btn_${buttonType}"> ${icon} <span>${buttonText}</span> </button>`; } return buttonsHtml; } /** * الحصول على CSS */ getCSS() { return ` <style> .__MonkeyConfig_container { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: ${this.shadowWidth}; max-width: 90vw; height: ${this.shadowHeight}; max-height: 90vh; background: #ffffff; border: 1px solid #ddd; border-radius: 8px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3); font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-size: ${this.shadowFontSize}; color: ${this.shadowFontColor}; z-index: 2147483647; display: flex; flex-direction: column; overflow: hidden; } .__MonkeyConfig_header { display: flex; justify-content: space-between; align-items: center; padding: 16px 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 8px 8px 0 0; } .__MonkeyConfig_header h1 { margin: 0; font-size: 18px; font-weight: 600; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); } .__MonkeyConfig_close_btn { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: white; width: 32px; height: 32px; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; transition: all 0.2s ease; } .__MonkeyConfig_close_btn:hover { background: rgba(255, 255, 255, 0.2); transform: scale(1.1); } .__MonkeyConfig_content { flex: 1; padding: 20px; overflow-y: auto; overflow-x: hidden; } .__MonkeyConfig_sections { display: flex; flex-direction: column; gap: 20px; } .__MonkeyConfig_columns { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } .__MonkeyConfig_default_table { width: 100%; border-collapse: collapse; } .__MonkeyConfig_default_table tr { border-bottom: 1px solid #f0f0f0; } .__MonkeyConfig_default_table td { padding: 12px 8px; vertical-align: top; } .__MonkeyConfig_label_cell { width: 30%; font-weight: 500; color: #333; } .__MonkeyConfig_field_cell { width: 70%; } .__MonkeyConfig_inline { display: flex; align-items: center; gap: 12px; } .__MonkeyConfig_inline label { margin: 0; font-weight: 500; } /* Form Controls */ .__MonkeyConfig_container input, .__MonkeyConfig_container textarea, .__MonkeyConfig_container select { border: 2px solid #e1e5e9; border-radius: 6px; padding: 8px 12px; font-size: 14px; transition: all 0.2s ease; background: white; } .__MonkeyConfig_container input:focus, .__MonkeyConfig_container textarea:focus, .__MonkeyConfig_container select:focus { outline: none; border-color: #667eea; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); } .__MonkeyConfig_container input[type="checkbox"] { width: 18px; height: 18px; margin: 0; cursor: pointer; } .__MonkeyConfig_container input[type="color"] { width: 50px; height: 40px; padding: 2px; cursor: pointer; } .__MonkeyConfig_container input[type="range"] { width: 150px; margin-right: 10px; } .__MonkeyConfig_range_value { display: inline-block; min-width: 40px; text-align: center; font-weight: 500; color: #667eea; } .__MonkeyConfig_radio_group { display: flex; flex-direction: column; gap: 8px; } .__MonkeyConfig_radio_label { display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 4px 0; } .__MonkeyConfig_radio_label input[type="radio"] { margin: 0; } .__MonkeyConfig_group { border: 2px solid #e1e5e9; border-radius: 6px; padding: 16px; margin: 8px 0; } .__MonkeyConfig_group legend { font-weight: 600; color: #333; padding: 0 8px; } .__MonkeyConfig_custom { padding: 8px 0; } .__MonkeyConfig_custom_button { background: #f8f9fa; border: 2px solid #e1e5e9; color: #333; padding: 8px 16px; border-radius: 6px; cursor: pointer; transition: all 0.2s ease; } .__MonkeyConfig_custom_button:hover { background: #e9ecef; border-color: #667eea; } /* Footer */ .__MonkeyConfig_footer { padding: 16px 20px; background: #f8f9fa; border-top: 1px solid #e1e5e9; display: flex; gap: 12px; justify-content: flex-end; flex-wrap: wrap; } .__MonkeyConfig_btn { display: flex; align-items: center; gap: 8px; padding: 10px 16px; border: 2px solid transparent; border-radius: 6px; font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; text-decoration: none; } .__MonkeyConfig_btn_save { background: #28a745; color: white; border-color: #28a745; } .__MonkeyConfig_btn_save:hover { background: #218838; transform: translateY(-1px); } .__MonkeyConfig_btn_reset { background: #dc3545; color: white; border-color: #dc3545; } .__MonkeyConfig_btn_reset:hover { background: #c82333; transform: translateY(-1px); } .__MonkeyConfig_btn_reload { background: #007bff; color: white; border-color: #007bff; } .__MonkeyConfig_btn_reload:hover { background: #0056b3; transform: translateY(-1px); } .__MonkeyConfig_btn_homepage { background: #6c757d; color: white; border-color: #6c757d; } .__MonkeyConfig_btn_homepage:hover { background: #545b62; transform: translateY(-1px); } .__MonkeyConfig_error { color: #dc3545; font-weight: 500; padding: 8px; background: #f8d7da; border: 1px solid #f5c6cb; border-radius: 4px; } /* Responsive Design */ @media (max-width: 768px) { .__MonkeyConfig_container { width: 95vw; height: 95vh; margin: 0; transform: translate(-50%, -50%); } .__MonkeyConfig_columns { grid-template-columns: 1fr; } .__MonkeyConfig_footer { flex-direction: column; } .__MonkeyConfig_btn { justify-content: center; } } /* Dark mode support */ @media (prefers-color-scheme: dark) { .__MonkeyConfig_container { background: #1a1a1a; border-color: #444; color: #e0e0e0; } .__MonkeyConfig_default_table tr { border-color: #333; } .__MonkeyConfig_container input, .__MonkeyConfig_container textarea, .__MonkeyConfig_container select { background: #2a2a2a; border-color: #444; color: #e0e0e0; } .__MonkeyConfig_footer { background: #2a2a2a; border-color: #444; } .__MonkeyConfig_group { border-color: #444; } .__MonkeyConfig_custom_button { background: #2a2a2a; border-color: #444; color: #e0e0e0; } } /* Animation */ @keyframes __MonkeyConfig_fadeIn { from { opacity: 0; transform: translate(-50%, -50%) scale(0.9); } to { opacity: 1; transform: translate(-50%, -50%) scale(1); } } .__MonkeyConfig_container { animation: __MonkeyConfig_fadeIn 0.3s ease-out; } </style>`; } /** * فتح مربع الحوار */ open() { if (this.displayed) { this.log('Configuration dialog is already open', 'warn'); return; } try { this.createShadowDOM(); this.displayed = true; this.log('Configuration dialog opened'); } catch (e) { this.log(`Failed to open configuration dialog: ${e.message}`, 'error'); this.createIframeFallback(); } } /** * إنشاء Shadow DOM */ createShadowDOM() { try { this.openLayer = document.createElement('div'); this.openLayer.style.cssText = ` position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; background: rgba(0, 0, 0, 0.5) !important; z-index: 2147483646 !important; backdrop-filter: blur(2px) !important; `; if (this.openLayer.attachShadow) { this.shadowRoot = this.openLayer.attachShadow({ mode: 'closed' }); const container = document.createElement('div'); container.innerHTML = this.getCSS() + this.render(); this.shadowRoot.appendChild(container); this.container = this.shadowRoot.querySelector('.__MonkeyConfig_container'); } else { throw new Error('Shadow DOM not supported'); } document.body.appendChild(this.openLayer); this.attachEventListeners(); // Focus management setTimeout(() => { const firstInput = this.shadowRoot.querySelector('input, textarea, select, button'); if (firstInput) firstInput.focus(); }, 100); } catch (e) { this.log(`Failed to create Shadow DOM: ${e.message}`, 'error'); throw e; } } /** * إنشاء Iframe كبديل */ createIframeFallback() { try { this.log('Using iframe fallback', 'info'); this.openLayer = document.createElement('div'); this.openLayer.style.cssText = ` position: fixed !important; top: 0 !important; left: 0 !important; width: 100% !important; height: 100% !important; background: rgba(0, 0, 0, 0.5) !important; z-index: 2147483646 !important; `; this.iframeFallback = document.createElement('iframe'); this.iframeFallback.style.cssText = ` position: absolute !important; top: 50% !important; left: 50% !important; transform: translate(-50%, -50%) !important; width: ${this.iframeWidth} !important; height: ${this.iframeHeight} !important; border: none !important; border-radius: 8px !important; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3) !important; `; this.openLayer.appendChild(this.iframeFallback); document.body.appendChild(this.openLayer); const iframeDoc = this.iframeFallback.contentDocument || this.iframeFallback.contentWindow.document; const html = ` <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>${this.escapeHtml(this.data.title)}</title> ${this.getCSS().replace(this.shadowFontSize, this.iframeFontSize).replace(this.shadowFontColor, this.iframeFontColor)} </head> <body style="margin:0;padding:0;overflow:hidden;"> ${this.render()} </body> </html> `; iframeDoc.open(); iframeDoc.write(html); iframeDoc.close(); this.container = iframeDoc.querySelector('.__MonkeyConfig_container'); this.attachEventListeners(iframeDoc); this.displayed = true; } catch (e) { this.log(`Failed to create iframe fallback: ${e.message}`, 'error'); alert('Failed to open configuration dialog. Please check console for details.'); } } /** * ربط مستمعي الأحداث */ attachEventListeners(doc = null) { try { const context = doc || this.shadowRoot; if (!context) return; // Event delegation for better performance const container = context.querySelector('.__MonkeyConfig_container'); if (!container) return; const handleEvent = (e) => { const target = e.target; const targetId = target.id; const targetName = target.name; // Button clicks if (targetId && targetId.startsWith('__MonkeyConfig_button_')) { e.preventDefault(); const buttonType = targetId.replace('__MonkeyConfig_button_', ''); this.handleButtonClick(buttonType); return; } // Form field changes if (targetName && this.params[targetName]) { this.handleFieldChange(targetName, target); return; } }; // Add single event listener with delegation container.addEventListener('click', handleEvent); container.addEventListener('change', handleEvent); container.addEventListener('input', handleEvent); // Close on backdrop click if (this.openLayer) { this.openLayer.addEventListener('click', (e) => { if (e.target === this.openLayer) { this.close(); } }); } // Keyboard shortcuts const handleKeyDown = (e) => { if (e.key === 'Escape') { this.close(); } else if (e.key === 's' && (e.ctrlKey || e.metaKey)) { e.preventDefault(); this.handleButtonClick('save'); } }; (doc || document).addEventListener('keydown', handleKeyDown); this.eventListeners.set('keydown', handleKeyDown); } catch (e) { this.log(`Failed to attach event listeners: ${e.message}`, 'error'); } } /** * معالجة النقر على الأزرار */ handleButtonClick(buttonType) { try { switch (buttonType) { case 'save': this.save(); break; case 'reset': if (confirm('Are you sure you want to reset all settings to default values?')) { this.reset(); } break; case 'close': this.close(); break; case 'reload': this.save(); if (typeof GM_getValue !== 'undefined') { location.reload(); } else { alert('Settings saved. Please refresh the page manually.'); } break; case 'homepage': if (typeof GM_info !== 'undefined' && GM_info.script && GM_info.script.homepage) { window.open(GM_info.script.homepage, '_blank'); } else { alert('Homepage not available'); } break; default: this.log(`Unknown button type: ${buttonType}`, 'warn'); } } catch (e) { this.log(`Error handling button click (${buttonType}): ${e.message}`, 'error'); } } /** * معالجة تغيير الحقول */ handleFieldChange(name, element) { try { const param = this.params[name]; if (!param) return; let value; switch (param.type) { case 'checkbox': value = element.checked; break; case 'number': case 'range': value = parseFloat(element.value); if (isNaN(value)) value = param.default; break; case 'select': if (param.multiple) { value = Array.from(element.selectedOptions).map(option => option.value); } else { value = element.value; } break; case 'custom': if (param.get && typeof param.get === 'function') { value = param.get(element); } else { value = element.value; } break; default: value = element.value; } if (this.validateValue(name, value)) { this.values[name] = value; // Update range display if (param.type === 'range') { const rangeValue = element.parentNode.querySelector('.__MonkeyConfig_range_value'); if (rangeValue) { rangeValue.textContent = value; } } this.log(`Field '${name}' updated to: ${value}`); } else { this.log(`Invalid value for field '${name}': ${value}`, 'warn'); element.value = this.values[name]; // Revert to previous value } } catch (e) { this.log(`Error handling field change (${name}): ${e.message}`, 'error'); } } /** * تحديث واجهة المستخدم */ updateUI() { try { const context = this.shadowRoot || (this.iframeFallback && this.iframeFallback.contentDocument); if (!context) return; for (const [name, value] of Object.entries(this.values)) { const param = this.params[name]; if (!param) continue; const element = context.querySelector(`[name="${name}"]`); if (!element) continue; switch (param.type) { case 'checkbox': element.checked = Boolean(value); break; case 'select': if (param.multiple && Array.isArray(value)) { Array.from(element.options).forEach(option => { option.selected = value.includes(option.value); }); } else { element.value = value; } break; case 'radio': const radioButton = context.querySelector(`[name="${name}"][value="${value}"]`); if (radioButton) radioButton.checked = true; break; case 'range': element.value = value; const rangeValue = element.parentNode.querySelector('.__MonkeyConfig_range_value'); if (rangeValue) rangeValue.textContent = value; break; case 'custom': if (param.set && typeof param.set === 'function') { param.set(element, value); } else { element.value = value; } break; default: element.value = value; } } } catch (e) { this.log(`Error updating UI: ${e.message}`, 'error'); } } /** * حفظ الإعدادات */ save() { try { const errors = this.validate(); if (errors.length > 0) { alert('Validation errors:\n' + errors.join('\n')); return false; } const dataToSave = { ...this.values, shadowWidth: this.shadowWidth, shadowHeight: this.shadowHeight, iframeWidth: this.iframeWidth, iframeHeight: this.iframeHeight, shadowFontSize: this.shadowFontSize, shadowFontColor: this.shadowFontColor, iframeFontSize: this.iframeFontSize, iframeFontColor: this.iframeFontColor }; if (typeof GM_setValue !== 'undefined') { GM_setValue(this.storageKey, JSON.stringify(dataToSave)); this.log('Settings saved successfully'); } else { localStorage.setItem(this.storageKey, JSON.stringify(dataToSave)); this.log('Settings saved to localStorage'); } if (this.data.onSave && typeof this.data.onSave === 'function') { try { this.data.onSave(this.values); } catch (e) { this.log(`Error in onSave callback: ${e.message}`, 'error'); } } return true; } catch (e) { this.log(`Failed to save settings: ${e.message}`, 'error'); alert('Failed to save settings. Please check console for details.'); return false; } } /** * إغلاق مربع الحوار */ close() { try { if (!this.displayed) return; // Remove event listeners for (const [event, handler] of this.eventListeners) { document.removeEventListener(event, handler); } this.eventListeners.clear(); // Remove DOM elements if (this.openLayer && this.openLayer.parentNode) { this.openLayer.parentNode.removeChild(this.openLayer); } // Clear references this.openLayer = null; this.shadowRoot = null; this.container = null; this.iframeFallback = null; this.elementCache.clear(); this.displayed = false; this.log('Configuration dialog closed'); } catch (e) { this.log(`Error closing dialog: ${e.message}`, 'error'); } } /** * تنظيف الموارد */ destroy() { try { this.close(); // Clear all references this.data = null; this.params = null; this.values = null; this.validationRules.clear(); this.trustedPolicy = null; this.log('ModernMonkeyConfig destroyed'); } catch (e) { this.log(`Error during cleanup: ${e.message}`, 'error'); } } } // Export for different environments if (typeof module !== 'undefined' && module.exports) { module.exports = ModernMonkeyConfig; } else if (typeof window !== 'undefined') { window.ModernMonkeyConfig = ModernMonkeyConfig; } /** * Factory function for creating configuration dialogs */ function createMonkeyConfig(configData) { try { return new ModernMonkeyConfig(configData); } catch (e) { console.error(`[ModernMonkeyConfig] Failed to create configuration: ${e.message}`); return null; } } // Export factory function if (typeof window !== 'undefined') { window.createMonkeyConfig = createMonkeyConfig; } /** * Example usage: const config = createMonkeyConfig({ title: 'My Script Configuration', menuCommand: true, buttons: ['save', 'reset', 'close', 'reload'], parameters: { enabled: { type: 'checkbox', label: 'Enable Script', default: true }, maxItems: { type: 'number', label: 'Maximum Items', default: 10, min: 1, max: 100 }, theme: { type: 'select', label: 'Theme', choices: { 'light': 'Light Theme', 'dark': 'Dark Theme', 'auto': 'Auto Detect' }, default: 'auto' } }, onSave: (values) => { console.log('Settings saved:', values); } }); // Access values const isEnabled = config.get('enabled'); const maxItems = config.get('maxItems'); // Set values config.set('enabled', false); // Open dialog config.open(); */