355 lines
No EOL
14 KiB
JavaScript
355 lines
No EOL
14 KiB
JavaScript
// ==================== ЯДРО РАСШИРЕНИЯ ====================
|
||
|
||
// ============ ГЛАВНАЯ КОНФИГУРАЦИЯ РАСШИРЕНИЯ ============
|
||
// Значения по умолчанию (будут перезаписаны из chrome.storage)
|
||
let CONFIG = {
|
||
// Настройки отладки
|
||
debug: {
|
||
enabled: true, // Главный переключатель (если false, все логи отключены)
|
||
modules: {
|
||
core: true, // Логи ядра
|
||
video: true, // Логи модуля видео
|
||
mnemo: true, // Логи модуля мнемосхемы
|
||
close: true, // Логи модуля закрытия
|
||
content: true, // Логи главного файла
|
||
novnc: true, // Логи модуля noVNC
|
||
autologin: true // Логи модуля автологина
|
||
}
|
||
},
|
||
|
||
// Настройки портов
|
||
ports: {
|
||
video: '5000', // Порт для видео/настроек
|
||
scheme: '8080' // Порт для мнемосхемы
|
||
},
|
||
|
||
// Настройки модуля noVNC
|
||
novnc: {
|
||
mode: 'button', // 'redirect' - автоматическая переадресация, 'button' - добавить кнопку
|
||
redirectDelay: 500 // Задержка перед переадресацией (мс), только для mode: 'redirect'
|
||
},
|
||
|
||
// Настройки кнопки "Мнемосхема"
|
||
mnemo: {
|
||
mode: 'host-port', // 'host-port' — host:порт мнемосхемы, 'host-scheme' — http://хост/scheme
|
||
skipOnDomain: false, // не добавлять кнопку при заходе по домену (не по IP)
|
||
addVideoButton: true, // добавлять кнопку "Лещ" (переход на host:порт видео)
|
||
addSchemeButton: true // добавлять кнопку "Мнемосхема"
|
||
},
|
||
|
||
// Настройки автологина
|
||
autologin: {
|
||
enabled: false, // Включить/выключить автологин
|
||
username: '', // Логин для автозаполнения
|
||
password: '', // Пароль для автозаполнения
|
||
autoSubmit: true, // Автоматически нажимать кнопку "Вход"
|
||
delay: 500 // Задержка перед автовходом (мс)
|
||
},
|
||
|
||
// Сайты, на которых расширение отключено (массив hostname, например ['example.com'])
|
||
disabledSites: [],
|
||
|
||
// Замена логотипа CleverPark на иконку CAPS (icons/caps.svg)
|
||
replaceLogo: true
|
||
};
|
||
|
||
// Обновление конфигурации (изменяет существующий объект, а не создает новый)
|
||
function updateConfig(newConfig) {
|
||
// Обновляем debug
|
||
if (newConfig.debug) {
|
||
CONFIG.debug.enabled = newConfig.debug.enabled;
|
||
if (newConfig.debug.modules) {
|
||
Object.assign(CONFIG.debug.modules, newConfig.debug.modules);
|
||
}
|
||
}
|
||
|
||
// Обновляем ports
|
||
if (newConfig.ports) {
|
||
Object.assign(CONFIG.ports, newConfig.ports);
|
||
}
|
||
|
||
// Обновляем novnc
|
||
if (newConfig.novnc) {
|
||
Object.assign(CONFIG.novnc, newConfig.novnc);
|
||
}
|
||
|
||
// Обновляем mnemo (совместимость со старыми значениями mode)
|
||
if (newConfig.mnemo) {
|
||
const m = newConfig.mnemo;
|
||
const mode = m.mode === 'ip-scheme' ? 'host-scheme' : (m.mode === 'skip-on-domain' || m.mode === 'default' ? 'host-port' : (m.mode || 'host-port'));
|
||
CONFIG.mnemo.mode = mode;
|
||
CONFIG.mnemo.skipOnDomain = m.skipOnDomain === true || m.mode === 'skip-on-domain';
|
||
if (typeof m.addVideoButton === 'boolean') CONFIG.mnemo.addVideoButton = m.addVideoButton;
|
||
else if (CONFIG.mnemo.addVideoButton === undefined) CONFIG.mnemo.addVideoButton = true;
|
||
if (typeof m.addSchemeButton === 'boolean') CONFIG.mnemo.addSchemeButton = m.addSchemeButton;
|
||
else if (CONFIG.mnemo.addSchemeButton === undefined) CONFIG.mnemo.addSchemeButton = true;
|
||
}
|
||
|
||
// Обновляем autologin
|
||
if (newConfig.autologin) {
|
||
Object.assign(CONFIG.autologin, newConfig.autologin);
|
||
}
|
||
|
||
// Обновляем disabledSites
|
||
if (Array.isArray(newConfig.disabledSites)) {
|
||
CONFIG.disabledSites = newConfig.disabledSites;
|
||
}
|
||
|
||
// Обновляем replaceLogo
|
||
if (typeof newConfig.replaceLogo === 'boolean') {
|
||
CONFIG.replaceLogo = newConfig.replaceLogo;
|
||
}
|
||
|
||
// Обновляем константы портов
|
||
window.VideoIPRedirect.TARGET_PORT_VIDEO = CONFIG.ports.video;
|
||
window.VideoIPRedirect.TARGET_PORT_SCHEME = CONFIG.ports.scheme;
|
||
}
|
||
|
||
// Загрузка конфигурации из chrome.storage (возвращает Promise)
|
||
function loadConfig() {
|
||
return new Promise((resolve) => {
|
||
if (typeof chrome !== 'undefined' && chrome.storage) {
|
||
chrome.storage.sync.get(['config'], (result) => {
|
||
if (result.config) {
|
||
// Обновляем CONFIG из сохранённых настроек
|
||
updateConfig(result.config);
|
||
debugLog('core', '⚙️ Конфигурация загружена из настроек:', CONFIG);
|
||
} else {
|
||
debugLog('core', 'ℹ️ Используется конфигурация по умолчанию');
|
||
}
|
||
resolve();
|
||
});
|
||
} else {
|
||
debugLog('core', 'ℹ️ chrome.storage недоступен, используется конфигурация по умолчанию');
|
||
resolve();
|
||
}
|
||
});
|
||
}
|
||
|
||
// Слушаем обновления конфигурации
|
||
if (typeof chrome !== 'undefined' && chrome.runtime) {
|
||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||
if (message.type === 'CONFIG_UPDATED') {
|
||
updateConfig(message.config);
|
||
debugLog('core', '🔄 Конфигурация обновлена:', CONFIG);
|
||
|
||
// Отправляем событие об обновлении конфигурации
|
||
document.dispatchEvent(new CustomEvent('VideoIPRedirect:ConfigUpdated', {
|
||
detail: { config: CONFIG }
|
||
}));
|
||
|
||
debugLog('core', '💡 Для применения всех изменений рекомендуется перезагрузить страницу');
|
||
}
|
||
});
|
||
}
|
||
|
||
// Константы портов (для обратной совместимости)
|
||
const TARGET_PORT_VIDEO = CONFIG.ports.video;
|
||
const TARGET_PORT_SCHEME = CONFIG.ports.scheme;
|
||
|
||
// Улучшенная функция отладочного логирования
|
||
function debugLog(module, ...args) {
|
||
// Проверяем глобальный флаг и флаг конкретного модуля
|
||
if (!CONFIG.debug.enabled || !CONFIG.debug.modules[module]) {
|
||
return;
|
||
}
|
||
|
||
// Цвета для разных модулей
|
||
const moduleColors = {
|
||
core: '#4CAF50', // Зелёный
|
||
video: '#2196F3', // Синий
|
||
mnemo: '#FF9800', // Оранжевый
|
||
close: '#F44336', // Красный
|
||
content: '#9C27B0', // Фиолетовый
|
||
novnc: '#00BCD4', // Голубой
|
||
autologin: '#8BC34A' // Светло-зелёный
|
||
};
|
||
|
||
const color = moduleColors[module] || '#607D8B';
|
||
const prefix = `[CAPS_Enhancer::${module.toUpperCase()}]`;
|
||
|
||
console.log(
|
||
`%c${prefix}`,
|
||
`background: ${color}; color: white; padding: 2px 5px; border-radius: 3px; font-weight: bold;`,
|
||
...args
|
||
);
|
||
}
|
||
|
||
// ПРОСТАЯ проверка типа страницы
|
||
function checkPageType() {
|
||
debugLog('core', '=== ПРОВЕРКА ТИПА СТРАНИЦЫ ===');
|
||
|
||
// 1. Проверяем маркер приложения
|
||
const hasAppMarker = checkAppMarker();
|
||
debugLog('core', 'Маркер приложения:', hasAppMarker ? '✅' : '❌');
|
||
|
||
// 2. Проверяем логотип (/images/cleverpark.svg или alt="CLEVER PARK")
|
||
const logoSelector = 'img[src*="cleverpark.svg"], img[src*="cleverpark"], img[alt*="CLEVER PARK"], img[alt*="CLEVER"]';
|
||
const logo = document.querySelector(logoSelector);
|
||
const hasLogo = !!logo;
|
||
debugLog('core', 'Логотип CLEVER PARK:', hasLogo ? '✅' : '❌');
|
||
if (logo) debugLog('core', ' Найден лого:', logo.src);
|
||
|
||
// 3. Проверяем признаки страницы noVNC (экран на 80 порту)
|
||
const isNoVNC = checkNoVNCPage();
|
||
debugLog('core', 'Страница noVNC:', isNoVNC ? '✅' : '❌');
|
||
|
||
const pageType = {
|
||
isMainPage: hasLogo && hasAppMarker && !isNoVNC,
|
||
isVideoPage: !hasLogo && hasAppMarker && !isNoVNC,
|
||
isNoVNCPage: isNoVNC,
|
||
hasAppMarker: hasAppMarker,
|
||
hasLogo: hasLogo
|
||
};
|
||
|
||
debugLog('core', 'ИТОГОВЫЙ ТИП СТРАНИЦЫ:',
|
||
pageType.isNoVNCPage ? 'NOVNC (экран)' :
|
||
pageType.isMainPage ? 'ГЛАВНАЯ' :
|
||
pageType.isVideoPage ? 'ВИДЕО' :
|
||
'НЕ ЦЕЛЕВАЯ');
|
||
|
||
return pageType;
|
||
}
|
||
|
||
// Проверка страницы noVNC по характерным признакам
|
||
function checkNoVNCPage() {
|
||
// Признак 1: noscript с текстом "You need to enable JavaScript to run this app."
|
||
const noscripts = document.querySelectorAll('noscript');
|
||
let hasNoVNCNoscript = false;
|
||
for (let noscript of noscripts) {
|
||
if (noscript.textContent.includes('You need to enable JavaScript to run this app')) {
|
||
hasNoVNCNoscript = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
// Признак 2: div с классом начинающимся на "app_AppWindow__"
|
||
const appWindow = document.querySelector('div[class*="app_AppWindow__"]');
|
||
const hasAppWindow = !!appWindow;
|
||
|
||
// Страница noVNC определяется по наличию обоих признаков
|
||
return hasNoVNCNoscript && hasAppWindow;
|
||
}
|
||
|
||
// Проверка маркера приложения
|
||
function checkAppMarker() {
|
||
const noscripts = document.querySelectorAll('noscript');
|
||
for (let noscript of noscripts) {
|
||
if (noscript.textContent.includes('You need to enable JavaScript')) {
|
||
return true;
|
||
}
|
||
}
|
||
return document.body?.textContent?.includes('You need to enable JavaScript') || false;
|
||
}
|
||
|
||
// ПРОСТОЕ извлечение IP
|
||
function extractIpAndPort(videoUrl) {
|
||
if (!videoUrl) return null;
|
||
const match = videoUrl.match(/\/getVideo\/http:\/\/([^\/:?#]+)(?::(\d+))?/i);
|
||
return match ? { ip: match[1], originalPort: match[2] || '80' } : null;
|
||
}
|
||
|
||
// Проверка видимости элемента (общая функция для всех модулей)
|
||
function isElementVisible(element) {
|
||
if (!element) return false;
|
||
|
||
try {
|
||
const style = window.getComputedStyle(element);
|
||
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
|
||
return false;
|
||
}
|
||
|
||
const rect = element.getBoundingClientRect();
|
||
if (rect.width === 0 || rect.height === 0) {
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
} catch (e) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Создание стилизованной кнопки (общая функция)
|
||
function createStyledButton(text, onClick, className = 'redirect-btn') {
|
||
const button = document.createElement('button');
|
||
button.className = className;
|
||
button.textContent = text;
|
||
button.style.cssText = `
|
||
margin-left: 12px;
|
||
padding: 6px 16px;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
color: white;
|
||
border: none;
|
||
border-radius: 18px;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
font-weight: bold;
|
||
display: inline-block;
|
||
vertical-align: middle;
|
||
transition: all 0.2s ease;
|
||
border: 1px solid rgba(255,255,255,0.5);
|
||
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
||
font-family: Arial, sans-serif;
|
||
`;
|
||
|
||
button.onmouseover = () => {
|
||
button.style.transform = 'scale(1.05)';
|
||
button.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)';
|
||
};
|
||
|
||
button.onmouseout = () => {
|
||
button.style.transform = 'scale(1)';
|
||
button.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
|
||
};
|
||
|
||
button.onclick = onClick;
|
||
|
||
return button;
|
||
}
|
||
|
||
// Утилита для повторных попыток
|
||
function retryWithBackoff(fn, maxAttempts = 3, delays = [500, 1500, 3000]) {
|
||
delays.forEach((delay, index) => {
|
||
setTimeout(() => {
|
||
if (index < maxAttempts) {
|
||
fn();
|
||
}
|
||
}, delay);
|
||
});
|
||
}
|
||
|
||
// Глобальный объект
|
||
window.VideoIPRedirect = {
|
||
CONFIG: CONFIG,
|
||
debugLog: debugLog,
|
||
checkPageType: checkPageType,
|
||
extractIpAndPort: extractIpAndPort,
|
||
isElementVisible: isElementVisible,
|
||
createStyledButton: createStyledButton,
|
||
retryWithBackoff: retryWithBackoff,
|
||
TARGET_PORT_VIDEO: TARGET_PORT_VIDEO,
|
||
TARGET_PORT_SCHEME: TARGET_PORT_SCHEME,
|
||
activeSchemeButton: false,
|
||
|
||
// Для обратной совместимости
|
||
DEBUG_CONFIG: CONFIG.debug
|
||
};
|
||
|
||
debugLog('core', '✅ CORE: Модуль инициализирован');
|
||
debugLog('core', '📌 СТРАНИЦА:', window.location.href);
|
||
debugLog('core', '📌 USER AGENT:', navigator.userAgent);
|
||
|
||
// Флаг готовности конфигурации
|
||
window.VideoIPRedirect.configReady = false;
|
||
|
||
// Загружаем конфигурацию из настроек
|
||
loadConfig().then(() => {
|
||
window.VideoIPRedirect.configReady = true;
|
||
debugLog('core', '🎯 Конфигурация готова к использованию');
|
||
|
||
// Отправляем событие о готовности конфигурации
|
||
document.dispatchEvent(new CustomEvent('VideoIPRedirect:ConfigReady', {
|
||
detail: { config: CONFIG }
|
||
}));
|
||
}); |