// ==UserScript== // @name RO Remote Client Override // @namespace romobile-map-vps // @version 2.0.0 // @description ชี้ remoteClient ของ RoPlay ไปที่ asset server ของเราเอง — ไม่ต้องมี VPN หรือใบรับรอง // @author romobile-map-vps // @match https://m.furr-ro.com/* // @run-at document-start // @grant none // @noframes // ==/UserScript== (function () { 'use strict'; var TAG = '[ro-remote]'; var target = "https://map.ro2pc.com/client/"; console.log(TAG, 'จะชี้ remoteClient ไปที่', target); try { var s = document.createElement('script'); s.textContent = "(function () {\n\t'use strict';\n\tvar remote = \"https://map.ro2pc.com/client/\";\n\tvar extra = {};\n\tvar T = '[ro-remote]';\n\tvar state = { hook: false, patched: false, via: null, sw: null, error: null };\n\twindow.__roRemote = state;\n\n\tfunction mark(via) {\n\t\tstate.patched = true;\n\t\tstate.via = via;\n\t\ttry { document.documentElement.setAttribute('data-ro-remote', remote); } catch (e) {}\n\t}\n\n\tfunction apply(cfg, via) {\n\t\tif (!cfg || typeof cfg !== 'object') return false;\n\t\tif (cfg.remoteClient === remote) return true;\n\t\tvar before = cfg.remoteClient;\n\t\tcfg.remoteClient = remote;\n\t\tconsole.log(T, 'เปลี่ยน remoteClient (' + via + ')', before, '->', remote);\n\t\tfor (var k in extra) {\n\t\t\tconsole.log(T, 'ตั้ง ' + k, cfg[k], '->', extra[k]);\n\t\t\tcfg[k] = extra[k];\n\t\t}\n\t\tmark(via);\n\t\treturn true;\n\t}\n\n\t/* บล็อก service worker */\n\ttry {\n\t\tvar swc = navigator.serviceWorker;\n\t\tif (swc) {\n\t\t\tvar fake = {\n\t\t\t\tscope: location.origin + '/', installing: null, waiting: null, active: null,\n\t\t\t\tupdateViaCache: 'none',\n\t\t\t\tupdate: function () { return Promise.resolve(); },\n\t\t\t\tunregister: function () { return Promise.resolve(true); },\n\t\t\t\taddEventListener: function () {}, removeEventListener: function () {},\n\t\t\t\tdispatchEvent: function () { return false; }\n\t\t\t};\n\t\t\tObject.defineProperty(swc, 'register', {\n\t\t\t\tconfigurable: true, writable: true,\n\t\t\t\tvalue: function () { console.log(T, 'บล็อกการลงทะเบียน service worker'); return Promise.resolve(fake); }\n\t\t\t});\n\t\t\tswc.getRegistrations().then(function (regs) {\n\t\t\t\tstate.sw = regs.length;\n\t\t\t\tif (!regs.length) return;\n\t\t\t\treturn Promise.all(regs.map(function (r) { return r.unregister(); })).then(function () {\n\t\t\t\t\tconsole.log(T, 'ถอนทะเบียน service worker เดิม', regs.length, 'ตัว');\n\t\t\t\t\tvar key = 'roRemoteReloaded';\n\t\t\t\t\tif (swc.controller && !sessionStorage.getItem(key)) {\n\t\t\t\t\t\tsessionStorage.setItem(key, '1');\n\t\t\t\t\t\tconsole.log(T, 'โหลดหน้าใหม่ครั้งเดียวเพื่อหลุดจาก service worker');\n\t\t\t\t\t\tlocation.reload();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}).catch(function (e) { console.error(T, 'ถอน service worker ไม่ได้:', e && e.message); });\n\t\t}\n\t} catch (e) {\n\t\tstate.error = String(e && e.message);\n\t\tconsole.error(T, 'จัดการ service worker ไม่ได้:', e && e.message);\n\t}\n\n\t/* ชั้น 1: ดักตอนหน้าเว็บ assign — อ่านค่าเดิมก่อนวาง accessor ไม่งั้นลบ config หน้าเว็บทิ้ง */\n\tvar stored = window.ROConfig;\n\tif (stored) {\n\t\ttry { apply(stored, 'existing'); } catch (e) { console.error(T, 'แก้ ROConfig ที่มีอยู่ไม่ได้:', e && e.message); }\n\t}\n\ttry {\n\t\tObject.defineProperty(window, 'ROConfig', {\n\t\t\tconfigurable: true, enumerable: true,\n\t\t\tget: function () { return stored; },\n\t\t\tset: function (v) {\n\t\t\t\ttry { apply(v, 'setter'); }\n\t\t\t\tcatch (e) { console.error(T, 'setter พลาด:', e && e.message); state.error = String(e && e.message); }\n\t\t\t\tstored = v;\n\t\t\t}\n\t\t});\n\t\tstate.hook = true;\n\t\tdocument.documentElement.setAttribute('data-ro-remote-hook', '1');\n\t} catch (e) {\n\t\tstate.error = String(e && e.message);\n\t\tconsole.error(T, 'ดัก window.ROConfig ไม่ได้:', e && e.message);\n\t}\n\n\t/* ชั้น 2: ดักตอน RoPlay.js ถูกใส่เข้า DOM */\n\ttry {\n\t\tvar mo = new MutationObserver(function (records) {\n\t\t\tfor (var i = 0; i < records.length; i++) {\n\t\t\t\tvar nodes = records[i].addedNodes;\n\t\t\t\tfor (var j = 0; j < nodes.length; j++) {\n\t\t\t\t\tvar node = nodes[j];\n\t\t\t\t\tif (node.tagName !== 'SCRIPT') continue;\n\t\t\t\t\tvar src = node.getAttribute && node.getAttribute('src');\n\t\t\t\t\tif (!src || src.indexOf('RoPlay') === -1) continue;\n\t\t\t\t\tif (!state.patched) apply(window.ROConfig, 'script-observer');\n\t\t\t\t\tmo.disconnect();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tmo.observe(document.documentElement, { childList: true, subtree: true });\n\t\twindow.addEventListener('load', function () { mo.disconnect(); }, { once: true });\n\t} catch (e) { console.error(T, 'MutationObserver ล้ม:', e && e.message); }\n\n\t/* ชั้น 3: กันเหนียวตอน DOM พร้อม */\n\tdocument.addEventListener('DOMContentLoaded', function () {\n\t\tif (!state.patched && !apply(window.ROConfig, 'domcontentloaded')) {\n\t\t\tconsole.error(T, 'ไม่เจอ window.ROConfig เลย — หน้าเว็บอาจเปลี่ยนวิธีตั้งค่า');\n\t\t}\n\t}, { once: true });\n})();"; (document.head || document.documentElement).appendChild(s); s.remove(); } catch (e) { console.error(TAG, 'ฉีดโค้ดเข้าหน้าเว็บไม่สำเร็จ:', e && e.message); } if (document.documentElement.getAttribute('data-ro-remote-hook') !== '1') { console.error(TAG, 'hook ไม่ติด — หน้านี้อาจมี CSP บล็อก inline script'); } function badge(kind, msg) { var id = 'ro-remote-badge'; var el = document.getElementById(id); if (!el) { if (!document.body) { document.addEventListener('DOMContentLoaded', function () { badge(kind, msg); }, { once: true }); return; } el = document.createElement('div'); el.id = id; el.style.cssText = 'position:fixed;z-index:2147483647;left:8px;bottom:8px;padding:5px 9px;border-radius:8px;font:11px/1.3 ui-monospace,Menlo,Consolas,monospace;color:#fff;background:rgba(20,22,28,.86);border:1px solid rgba(255,255,255,.14);cursor:pointer;max-width:70vw;word-break:break-all'; el.title = 'แตะเพื่อซ่อน'; el.onclick = function () { el.remove(); }; document.body.appendChild(el); } var color = kind === 'ok' ? '#4ade80' : kind === 'bad' ? '#f87171' : '#fbbf24'; el.innerHTML = '\u25CF ro-remote · ' + msg; } function report() { var root = document.documentElement; var installed = root.getAttribute('data-ro-remote-hook') === '1'; var patched = root.getAttribute('data-ro-remote') === target; if (patched) return badge('ok', 'พร้อม · ' + target); if (installed) return badge('wait', 'hook ติดตั้งแล้ว รอหน้าเว็บตั้ง ROConfig'); badge('bad', 'ฉีด hook เข้าหน้าเว็บไม่สำเร็จ'); } function ping() { fetch(target + '__ping', { cache: 'no-store' }) .then(function (r) { return r.ok ? r.text() : Promise.reject(new Error('HTTP ' + r.status)); }) .then(report) .catch(function (err) { console.error(TAG, 'ต่อ asset server ไม่ได้:', err && err.message); badge('bad', 'ต่อเซิร์ฟเวอร์ไม่ได้ (' + (err && err.message) + ')'); }); } setTimeout(ping, 600); window.addEventListener('load', function () { setTimeout(report, 300); }, { once: true }); })();