您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Стабильный виджет: сбор прокси из нескольких источников, кэш между сессиями, автообновление, TXT-экспорт, SwitchyOmega backup (PAC-профили Random/US/Mixed), тема (светлая/тёмная), таймер. Сделано в Украине 💙💛
// ==UserScript== // @name Proxy Dashboard (Safe) — Multi-Source + Cache + Auto-Refresh + SwitchyOmega Export (UA) // @namespace proxy-helper // @version 3.1 // @description Стабильный виджет: сбор прокси из нескольких источников, кэш между сессиями, автообновление, TXT-экспорт, SwitchyOmega backup (PAC-профили Random/US/Mixed), тема (светлая/тёмная), таймер. Сделано в Украине 💙💛 // @author Gemini // @match *://*/* // @exclude *://chrome.google.com/* // @run-at document-idle // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @connect hidxxy.name // @connect free-proxy.cz // @connect free.geonix.com // @connect smallseotools.com // @connect geonode.com // @connect fineproxy.org // @connect iproyal.com // @connect proxyscrape.com // @connect free-proxy-list.net // @connect proxy-list.download // @connect api.ipify.org // @connect * // @noframes // @license MIT // ==/UserScript== (function () { 'use strict'; if (window.__PX_DASH_RUNNING__) return; // защита от двойного запуска window.__PX_DASH_RUNNING__ = true; // -------- Settings / keys -------- var STORE_KEYS = { proxies: 'px_all_proxies_v31', ts: 'px_updated_ts_v31', theme: 'px_theme_v31', collapsed: 'px_collapsed_v31', refreshMin: 'px_refresh_min_v31' }; var DEFAULT_REFRESH_MIN = 10; var EXPERIMENTAL_TEST = false; // в обычном TM параметр proxy для GM_xmlhttpRequest не работает // -------- State -------- var ALL_PROXIES = []; var TIMER = null; var nextRefreshAt = 0; var widget = null; // -------- Utils -------- function get(k, d){ try{ var v=GM_getValue(k); return (v===undefined)?d:v; }catch(e){ return d; } } function set(k, v){ try{ GM_setValue(k, v); }catch(e){} } function now(){ return Date.now(); } function minutes(m){ return m*60*1000; } function uniqueBy(arr, keyFn){ var map = Object.create(null), out=[], i, k; for(i=0;i<arr.length;i++){ k = keyFn(arr[i]); if(!map[k]){ map[k]=1; out.push(arr[i]); } } return out; } function shuffle(a){ var arr=a.slice(), i, j, t; for (i=arr.length-1;i>0;i--){ j=(Math.random()*(i+1))|0; t=arr[i]; arr[i]=arr[j]; arr[j]=t; } return arr; } // -------- Sources -------- var sources = [ { url: 'https://hidxxy.name/proxy-list/', parser: parseHidemyName }, { url: 'http://free-proxy.cz/ru/', parser: parseFreeProxyCz }, { url: 'https://free.geonix.com/ru/', parser: parseGeonix }, { url: 'https://smallseotools.com/ru/free-proxy-list/', parser: parseSmallSeoTools }, { url: 'https://geonode.com/free-proxy-list', parser: parseGeonode }, { url: 'https://fineproxy.org/free-proxies/north-america/united-states/', parser: parseFineProxy }, { url: 'https://iproyal.com/free-proxy-list/', parser: parseIproyal }, { url: 'https://proxyscrape.com/free-proxy-list', parser: parseProxyScrape }, { url: 'https://free-proxy-list.net/', parser: parseFreeProxyListNet }, { url: 'https://www.proxy-list.download/api/v1/get?type=http&anon=elite&country=US', parser: parseProxyListDownloadUS } ]; var IPIFY = 'https://api.ipify.org?format=json'; // -------- Theme -------- function currentTheme(){ return get(STORE_KEYS.theme, 'dark'); } function setTheme(t){ try{ document.documentElement.setAttribute('data-px-theme', t); set(STORE_KEYS.theme, t); }catch(e){} } // -------- Styles -------- var css = ` :root { --px-bg:#0f1220; --px-fg:#e9eef3; --px-muted:#9aa4b2; --px-accent:#2dd4bf; --px-border:#263042; --px-btn:#243147; --px-red:#ef4444; --px-orange:#f59e0b; --px-green:#22c55e; --px-blue:#3b82f6; } html[data-px-theme="light"] { --px-bg:#ffffff; --px-fg:#111827; --px-muted:#4b5563; --px-accent:#0891b2; --px-border:#e5e7eb; --px-btn:#f3f4f6; --px-red:#dc2626; --px-orange:#d97706; --px-green:#16a34a; --px-blue:#2563eb; } #px-root{position:fixed;top:84px;right:24px;width:360px;background:var(--px-bg);color:var(--px-fg);border:1px solid var(--px-border);border-radius:14px;box-shadow:0 12px 32px rgba(0,0,0,.35);font:14px/1.45 system-ui,-apple-system,Segoe UI,Roboto,Ubuntu,'Helvetica Neue',Arial,sans-serif;z-index:2147483647;overflow:hidden} #px-header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:linear-gradient(135deg,#0ea5e9,#22c55e)} #px-title{font-weight:800;font-size:15px;display:flex;align-items:center;gap:8px} #px-ua{display:flex;align-items:center;gap:6px;font-weight:800;background:#ffe15a;color:#0057b8;padding:3px 8px;border-radius:999px;border:1px solid #ffd24a;box-shadow:0 0 0 2px rgba(0,0,0,.05)} #px-body{padding:10px;display:block;max-height:480px;overflow:auto;background:var(--px-bg)} .px-row{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:8px} .px-input, .px-select{width:100%;background:transparent;border:1px solid var(--px-border);color:var(--px-fg);border-radius:10px;padding:8px 10px;outline:none} .px-btn{background:var(--px-btn);color:var(--px-fg);border:1px solid var(--px-border);border-radius:10px;padding:9px 10px;font-weight:700;cursor:pointer} .px-btn.primary{background:var(--px-blue);border-color:transparent;color:#fff} .px-btn.good{background:var(--px-green);border-color:transparent;color:#fff} .px-btn.warn{background:var(--px-orange);border-color:transparent;color:#111} .px-note{font-size:12px;color:var(--px-muted)} .px-badge{display:inline-flex;align-items:center;gap:6px;background:transparent;border:1px solid var(--px-border);padding:4px 8px;border-radius:8px;font-size:12px} .px-proxy-list{font:12px/1.5 ui-monospace,Menlo,Consolas,monospace;background:rgba(0,0,0,.2);border:1px solid var(--px-border);border-radius:10px;padding:8px;color:var(--px-fg);white-space:pre-wrap;word-break:break-all;max-height:180px;overflow:auto} .px-right{display:flex;gap:6px;align-items:center} .px-toggle{appearance:none;width:42px;height:24px;background:var(--px-border);border-radius:999px;position:relative;outline:none;cursor:pointer;border:none} .px-toggle:after{content:"";position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:#fff;transition:all .2s;box-shadow:0 1px 3px rgba(0,0,0,.3)} .px-toggle:checked{background:var(--px-blue)} .px-toggle:checked:after{left:21px} #px-min{background:rgba(0,0,0,.15);border:1px solid var(--px-border);color:#fff} .px-progress{height:6px;background:rgba(0,0,0,.15);border-radius:999px;overflow:hidden;margin:8px 0} .px-progress > div{height:100%;background:var(--px-blue);width:0%} `; try { GM_addStyle(css); } catch(e){ var st = document.createElement('style'); st.textContent = css; (document.head||document.documentElement).appendChild(st); } setTheme(currentTheme()); // -------- UI -------- function buildUI(){ widget = document.createElement('div'); widget.id = 'px-root'; widget.innerHTML = '<div id="px-header">' + '<div id="px-title">🧭 Proxy Dashboard <span class="px-badge"><span id="px-count">0</span> proxies</span></div>' + '<div class="px-right">' + '<label class="px-note">Light</label><input id="px-theme" type="checkbox" class="px-toggle">' + '<button id="px-min" class="px-btn">—</button>' + '</div>' + '</div>' + '<div id="px-body">' + '<div class="px-note" style="display:flex;align-items:center;gap:8px;justify-content:space-between">' + '<span id="px-madeinua"><span id="px-ua" title="Сделано в Украине">🇺🇦 <span>Made in Ukraine</span></span></span>' + '<span class="px-badge">Кэш: <span id="px-updated">—</span></span>' + '</div>' + '<div class="px-row" style="margin-top:8px">' + '<div><label class="px-note">Интервал автообновления (мин)</label><input id="px-refresh" type="number" min="1" step="1" class="px-input" value="' + esc(get(STORE_KEYS.refreshMin, DEFAULT_REFRESH_MIN)) + '"></div>' + '<div><label class="px-note">Профиль/фильтр</label><select id="px-filter" class="px-select">' + '<option value="random">Random</option>' + '<option value="us">US only</option>' + '<option value="mixed">Mixed</option>' + '</select></div>' + '</div>' + '<div class="px-row">' + '<button id="px-refresh-now" class="px-btn primary">🔄 Обновить сейчас</button>' + '<button id="px-copy" class="px-btn">📋 Скопировать всё</button>' + '</div>' + '<div class="px-row">' + '<button id="px-export-txt" class="px-btn">⬇️ Экспорт TXT</button>' + '<button id="px-export-omega" class="px-btn good">🧩 Export SwitchyOmega</button>' + '</div>' + '<div class="px-row">' + '<button id="px-test-one" class="px-btn warn">🧪 Тест (эксп.)</button>' + '<span class="px-badge">До обновления: <span id="px-eta">—</span></span>' + '</div>' + '<div class="px-progress"><div id="px-progress-bar"></div></div>' + '<div id="px-list" class="px-proxy-list">—</div>' + '<div class="px-note">Источники: ' + sources.length + ' (парсеры защищены — если разметка изменилась, источник пропускается).</div>' + '</div>' + '<div id="px-footer"><span class="px-note">IP-проверка: ' + (EXPERIMENTAL_TEST?'экспериментальная':'отключена') + '</span></div>'; (document.body||document.documentElement).appendChild(widget); // Theme init & toggle var themeToggle = document.getElementById('px-theme'); themeToggle.checked = (currentTheme()==='light'); themeToggle.addEventListener('change', function(){ setTheme(this.checked ? 'light' : 'dark'); }); // Collapse var body = document.getElementById('px-body'); var collapsed = !!get(STORE_KEYS.collapsed, false); if (collapsed) body.style.display = 'none'; var minBtn = document.getElementById('px-min'); minBtn.textContent = collapsed ? '+' : '—'; minBtn.addEventListener('click', function(){ var hidden = body.style.display === 'none'; body.style.display = hidden ? 'block' : 'none'; minBtn.textContent = hidden ? '—' : '+'; set(STORE_KEYS.collapsed, !hidden); }); // Drag header var header = document.getElementById('px-header'); var dragging=false,sx=0,sy=0,ox=0,oy=0; header.addEventListener('mousedown', function(e){ if (e.target && e.target.id === 'px-theme') return; dragging = true; sx=e.clientX; sy=e.clientY; var r = widget.getBoundingClientRect(); ox=r.left; oy=r.top; document.body.style.userSelect='none'; }); document.addEventListener('mousemove', function(e){ if(!dragging) return; widget.style.left = (ox + (e.clientX - sx)) + 'px'; widget.style.top = (oy + (e.clientY - sy)) + 'px'; widget.style.right = 'auto'; }); document.addEventListener('mouseup', function(){ if(!dragging) return; dragging=false; document.body.style.userSelect=''; }); // Buttons id('px-refresh-now').addEventListener('click', function(){ refreshAll(true); }); id('px-copy').addEventListener('click', copyAll); id('px-export-txt').addEventListener('click', exportTXT); id('px-export-omega').addEventListener('click', exportSwitchyOmega); id('px-test-one').addEventListener('click', testOneProxy); // Refresh input id('px-refresh').addEventListener('change', function(){ var v = parseInt(this.value,10); if (!v || v<1) v = DEFAULT_REFRESH_MIN; set(STORE_KEYS.refreshMin, v); scheduleNext(); }); // Load from cache var cached = get(STORE_KEYS.proxies, []); var ts = get(STORE_KEYS.ts, 0); if (cached && cached.length){ ALL_PROXIES = cached; renderList(); updateUpdated(ts); } scheduleNext(); var refreshMin = get(STORE_KEYS.refreshMin, DEFAULT_REFRESH_MIN); var stale = !ts || (now()-ts > minutes(refreshMin)); if (stale) refreshAll(true); } // -------- DOM helpers -------- function id(s){ return document.getElementById(s); } function esc(s){ return String(s===undefined||s===null?'':s); } function updateProgress(pct){ var bar = id('px-progress-bar'); if (bar) bar.style.width = (Math.max(0, Math.min(100, pct))+'%'); } function updateUpdated(ts){ var el = id('px-updated'); if (!el) return; el.textContent = ts ? new Date(ts).toLocaleString() : '—'; } function renderList(){ var list = id('px-list'), count = id('px-count'); if (count) count.textContent = String(ALL_PROXIES.length||0); var lines = []; for (var i=0;i<ALL_PROXIES.length;i++){ var p = ALL_PROXIES[i]; lines.push(p.ip + ':' + p.port + (p.country ? (' # ' + p.country) : '')); } list.textContent = lines.length ? lines.join('\n') : '—'; } function scheduleNext(){ var mins = get(STORE_KEYS.refreshMin, DEFAULT_REFRESH_MIN); nextRefreshAt = now() + minutes(mins); if (TIMER) clearInterval(TIMER); TIMER = setInterval(function(){ var etaEl = id('px-eta'); if (!etaEl) return; var d = nextRefreshAt - now(); if (d<=0){ etaEl.textContent='0s'; clearInterval(TIMER); refreshAll(true); return; } var s = Math.ceil(d/1000), mm = Math.floor(s/60), ss=s%60; etaEl.textContent = mm + ':' + (ss<10?'0':'') + ss; }, 1000); } // -------- Refresh / parsers -------- function refreshAll(withProgress){ var total = sources.length, done = 0; updateProgress(0); var collected = []; var i; function onDone(){ done++; if (withProgress) updateProgress(100*done/total); if (done>=total) finalize(); } for (i=0;i<sources.length;i++){ (function(src){ GM_xmlhttpRequest({ method: 'GET', url: src.url, timeout: 20000, onload: function(res){ try{ var list = src.parser(res.responseText || ''); for (var j=0;j<list.length;j++){ var raw = list[j]; if (typeof raw === 'string'){ var parts = raw.split(':'); if (parts.length>=2) collected.push({ip:parts[0].trim(), port:parts[1].trim(), source:src.url}); } else if (raw && raw.ip && raw.port){ collected.push({ip:raw.ip, port:raw.port, country:raw.country, source:src.url}); } } }catch(e){ /* ignore */ } onDone(); }, onerror: onDone, ontimeout: onDone }); })(sources[i]); } function finalize(){ ALL_PROXIES = uniqueBy(collected, function(o){ return o.ip+':'+o.port; }); set(STORE_KEYS.proxies, ALL_PROXIES); var ts = now(); set(STORE_KEYS.ts, ts); updateUpdated(ts); renderList(); scheduleNext(); } } // ---- Parsers (максимально совместимые) ---- function parseHidemyName(html){ var d = new DOMParser().parseFromString(html,'text/html'), out=[], rows=d.querySelectorAll('tbody tr'), i; for (i=0;i<rows.length;i++){ var tds = rows[i].querySelectorAll('td'); if (tds.length>=2){ var ip = (tds[0].textContent||'').trim(); var port = (tds[1].textContent||'').trim(); if (ip && port) out.push({ip:ip,port:port}); } } return out; } function parseFreeProxyCz(html){ var d = new DOMParser().parseFromString(html,'text/html'), out=[], rows=d.querySelectorAll('.fwp-list > table tr'), i; for (i=0;i<rows.length;i++){ var ipCell = rows[i].querySelector('td:nth-of-type(1)'); var portCell = rows[i].querySelector('td:nth-of-type(2)'); if (ipCell && portCell){ try{ var sc = ipCell.querySelector('script'); var enc = sc ? ((sc.textContent||'').match(/"(.*?)"/)||[])[1] : null; var ip = enc ? atob(enc) : (ipCell.textContent||'').trim(); var port = (portCell.textContent||'').trim(); if (ip && port) out.push({ip:ip,port:port}); }catch(e){} } } return out; } function parseGeonix(html){ var d = new DOMParser().parseFromString(html,'text/html'), out=[], rows=d.querySelectorAll('.proxy-list tbody tr'), i; for (i=0;i<rows.length;i++){ var ip = (rows[i].querySelector('td:nth-of-type(1)')||{}).textContent; var port = (rows[i].querySelector('td:nth-of-type(2)')||{}).textContent; if (ip && port){ ip=ip.trim(); port=port.trim(); if(ip&&port) out.push({ip:ip,port:port}); } } return out; } function parseSmallSeoTools(html){ var d = new DOMParser().parseFromString(html,'text/html'), out=[], rows=d.querySelectorAll('#content-section table tbody tr'), i; for (i=0;i<rows.length;i++){ var ip = (rows[i].querySelector('td:nth-of-type(1)')||{}).textContent; var port = (rows[i].querySelector('td:nth-of-type(2)')||{}).textContent; if (ip && port){ ip=ip.trim(); port=port.trim(); if(ip&&port) out.push({ip:ip,port:port}); } } return out; } function parseGeonode(html){ var d = new DOMParser().parseFromString(html,'text/html'), out=[], rows=d.querySelectorAll('.list-container .list-row'), i; for (i=0;i<rows.length;i++){ var ip = (rows[i].querySelector('p:nth-of-type(1)')||{}).textContent; var port = (rows[i].querySelector('p:nth-of-type(2)')||{}).textContent; if (ip && port){ ip=ip.trim(); port=port.trim(); if(ip&&port) out.push({ip:ip,port:port}); } } return out; } function parseFineProxy(html){ var d = new DOMParser().parseFromString(html,'text/html'), out=[], rows=d.querySelectorAll('#proxytable tbody tr'), i; for (i=0;i<rows.length;i++){ var tds = rows[i].querySelectorAll('td'); if (tds.length>=2){ var ip = (tds[0].textContent||'').trim(); var port = (tds[1].textContent||'').trim(); var country = (tds[3] && tds[3].textContent) ? tds[3].textContent.trim() : ''; if (ip && port) out.push({ip:ip,port:port,country:country}); } } return out; } function parseIproyal(html){ var d = new DOMParser().parseFromString(html,'text/html'), out=[], ips=d.querySelectorAll('td.ip'), ports=d.querySelectorAll('td.port'); var n = Math.min(ips.length, ports.length), i; for (i=0;i<n;i++){ var ip=(ips[i].textContent||'').trim(); var port=(ports[i].textContent||'').trim(); if (ip && port) out.push({ip:ip,port:port}); } return out; } function parseProxyScrape(html){ var d = new DOMParser().parseFromString(html,'text/html'), out=[], rows=d.querySelectorAll('table.w-full.text-left.text-sm tbody tr'), i; for (i=0;i<rows.length;i++){ var ip = (rows[i].querySelector('td:nth-of-type(1)')||{}).textContent; var port = (rows[i].querySelector('td:nth-of-type(2)')||{}).textContent; if (ip && port){ ip=ip.trim(); port=port.trim(); if(ip&&port) out.push({ip:ip,port:port}); } } return out; } function parseFreeProxyListNet(html){ var d = new DOMParser().parseFromString(html,'text/html'), out=[], rows=d.querySelectorAll('#proxylisttable tbody tr'), i; for (i=0;i<rows.length;i++){ var ip = (rows[i].querySelector('td:nth-of-type(1)')||{}).textContent; var port = (rows[i].querySelector('td:nth-of-type(2)')||{}).textContent; var country = (rows[i].querySelector('td:nth-of-type(4)')||{}).textContent; if (ip && port){ ip=ip.trim(); port=port.trim(); if(ip&&port) out.push({ip:ip,port:port,country:country?country.trim():''}); } } return out; } function parseProxyListDownloadUS(text){ var lines = String(text||'').split('\n'), out=[], i; for (i=0;i<lines.length;i++){ var s = lines[i].trim(); if (!s) continue; var parts = s.split(':'); if (parts.length>=2){ out.push({ip:parts[0].trim(), port:parts[1].trim(), country:'US'}); } } return out; } // -------- Filters -------- function filteredList(mode){ var list = ALL_PROXIES.slice(); if (mode==='us') { var us = [], i, p, c; for (i=0;i<list.length;i++){ p = list[i]; c = (p.country||'') + ' ' + (p.source||''); if (/(^|\s)(US|United\s+States)(\s|$)/i.test(c) || /country=US/i.test(c)) us.push(p); } return us; } if (mode==='mixed'){ var allUS = filteredList('us'); var non = []; for (var j=0;j<list.length;j++){ if (allUS.indexOf(list[j])===-1) non.push(list[j]); } var size = Math.min(list.length, 50), half = Math.floor(size/2); return shuffle(allUS).slice(0,half).concat(shuffle(non).slice(0,size-half)); } return shuffle(list); } // -------- Export / copy -------- function copyAll(){ var mode = id('px-filter').value; var arr = filteredList(mode), out=[], i; for (i=0;i<arr.length;i++){ out.push(arr[i].ip+':'+arr[i].port); } navigator.clipboard.writeText(out.join('\n')).catch(function(){ alert('Не удалось скопировать в буфер.'); }); } function exportTXT(){ var mode = id('px-filter').value; var arr = filteredList(mode), out=[], i; for (i=0;i<arr.length;i++){ out.push(arr[i].ip+':'+arr[i].port); } blobDownload(out.join('\n'), 'proxies_'+mode+'_'+isoStamp()+'.txt', 'text/plain'); } function exportSwitchyOmega(){ function makePAC(arr){ var list=[], i, lim=Math.min(arr.length, 200); for (i=0;i<lim;i++){ list.push(arr[i].ip+':'+arr[i].port); } var body = 'function FindProxyForURL(url, host){\n' + ' var L = ' + JSON.stringify(list) + ';\n' + ' if (!L || L.length===0) return "DIRECT";\n' + ' var i = Math.floor(Math.random()*L.length);\n' + ' return "PROXY " + L[i] + "; DIRECT";\n' + '}'; return body; } var backup = { version: '3.0.0', time: Date.now(), schema: 'omega-profiles', profiles: [ { name:'Random', type:'pac', profilePicture:'pac', pacScript: makePAC(filteredList('random')) }, { name:'US', type:'pac', profilePicture:'pac', pacScript: makePAC(filteredList('us')) }, { name:'Mixed', type:'pac', profilePicture:'pac', pacScript: makePAC(filteredList('mixed')) } ] }; blobDownload(JSON.stringify(backup,null,2), 'SwitchyOmega_Backup_'+isoStamp()+'.json', 'application/json'); } function blobDownload(content, filename, type){ var blob = new Blob([content], {type:type}); var url = URL.createObjectURL(blob); var a = document.createElement('a'); a.href=url; a.download=filename; (document.body||document.documentElement).appendChild(a); a.click(); a.remove(); setTimeout(function(){ URL.revokeObjectURL(url); }, 2000); } function isoStamp(){ return new Date().toISOString().slice(0,19).replace(/[:T]/g,'-'); } // -------- Test (informative only) -------- function testOneProxy(){ if (!EXPERIMENTAL_TEST){ alert('В стандартном Tampermonkey тест через сам прокси недоступен. Экспорт/кэш/обновления работают.'); return; } var mode = id('px-filter').value; var list = filteredList(mode); if (!list.length) return alert('Список пуст.'); var pick = list[(Math.random()*list.length)|0]; var proxyURL = 'http://' + pick.ip + ':' + pick.port; GM_xmlhttpRequest({ method: 'GET', url: IPIFY, timeout: 8000, proxy: proxyURL, // скорее всего будет проигнорировано onload: function(res){ try{ var data = JSON.parse(res.responseText||'{}'); alert('Ответ: ' + (data.ip||'—')); }catch(e){ alert('Ответ получен, но JSON не разобран.'); } }, onerror: function(){ alert('Ошибка запроса (вероятно, TM проигнорировал proxy-поле).'); }, ontimeout: function(){ alert('Таймаут/прокси медленный.'); } }); } // -------- Boot -------- try { buildUI(); } catch(e){ console.error('[PX] UI build error', e); } })();