Orion iOS Eruda Console Toggle

Persistent, stronger Eruda launcher for Orion iOS with always-on mode and blocker-resilient loading fallbacks.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

Advertisement:

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

Advertisement:

// ==UserScript==
// @name         Orion iOS Eruda Console Toggle
// @namespace    https://littlelooney.example/orion-eruda
// @version      2026-05-24.2
// @description  Persistent, stronger Eruda launcher for Orion iOS with always-on mode and blocker-resilient loading fallbacks.
// @author       LittleLooney + OpenAI
// @match        *://*/*
// @require      https://cdn.jsdelivr.net/npm/[email protected]/eruda.min.js
// @grant        none
// @run-at       document-start
// ==/UserScript==

(() => {
  'use strict';

  const STORAGE_KEY = 'orion-eruda-toggle-position';
  const AUTO_KEY = 'orion-eruda-auto-open';
  const BTN_ID = 'orion-eruda-toggle-btn';
  const CDN_FALLBACKS = [
    'https://cdn.jsdelivr.net/npm/[email protected]/eruda.min.js',
    'https://unpkg.com/[email protected]/eruda.min.js'
  ];

  let erudaReady = false;
  let erudaVisible = false;

  function loadPosition() {
    try {
      const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
      if (typeof saved.left === 'number' && typeof saved.top === 'number') return saved;
    } catch {}
    return { left: 14, top: 86 };
  }

  function savePosition(left, top) {
    localStorage.setItem(STORAGE_KEY, JSON.stringify({ left, top }));
  }

  function isAutoOpenEnabled() {
    return localStorage.getItem(AUTO_KEY) !== '0';
  }

  function setAutoOpenEnabled(enabled) {
    localStorage.setItem(AUTO_KEY, enabled ? '1' : '0');
  }

  function clamp(value, min, max) {
    return Math.min(Math.max(value, min), max);
  }

  function updateButtonStyle(btn, pos) {
    const maxLeft = Math.max(8, window.innerWidth - btn.offsetWidth - 8);
    const maxTop = Math.max(8, window.innerHeight - btn.offsetHeight - 8);
    const left = clamp(pos.left, 8, maxLeft);
    const top = clamp(pos.top, 8, maxTop);
    btn.style.left = `${left}px`;
    btn.style.top = `${top}px`;
    savePosition(left, top);
  }

  function loadScript(src) {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = src;
      script.async = true;
      script.onload = resolve;
      script.onerror = () => reject(new Error(`Failed to load ${src}`));
      (document.head || document.documentElement).appendChild(script);
    });
  }

  async function ensureEruda() {
    if (!window.eruda) {
      for (const src of CDN_FALLBACKS) {
        try {
          await loadScript(src);
          if (window.eruda) break;
        } catch {}
      }
    }
    if (!window.eruda) throw new Error('Eruda unavailable (possibly blocked by network/content blocker).');

    if (!erudaReady) {
      window.eruda.init({ useShadowDom: false, autoScale: true });
      erudaReady = true;
    }
  }

  function setButtonState(btn, text) {
    btn.textContent = text;
    btn.setAttribute('aria-label', text);
  }

  async function openEruda(btn) {
    await ensureEruda();
    window.eruda.show();
    erudaVisible = true;
    setButtonState(btn, isAutoOpenEnabled() ? 'Console: ON • AUTO' : 'Console: ON');
  }

  function closeEruda(btn) {
    if (window.eruda) window.eruda.hide();
    erudaVisible = false;
    setButtonState(btn, isAutoOpenEnabled() ? 'Console: OFF • AUTO' : 'Console: OFF');
  }

  async function toggleEruda(btn) {
    btn.disabled = true;
    try {
      if (erudaVisible) closeEruda(btn); else await openEruda(btn);
    } catch (err) {
      console.error('[Orion Eruda]', err);
      setButtonState(btn, 'Console: ERR');
      setTimeout(() => setButtonState(btn, erudaVisible ? 'Console: ON' : 'Console: OFF'), 1800);
    } finally {
      btn.disabled = false;
    }
  }

  function addAutoToggle(btn) {
    btn.addEventListener('contextmenu', (e) => {
      e.preventDefault();
      const next = !isAutoOpenEnabled();
      setAutoOpenEnabled(next);
      if (erudaVisible) setButtonState(btn, next ? 'Console: ON • AUTO' : 'Console: ON');
      else setButtonState(btn, next ? 'Console: OFF • AUTO' : 'Console: OFF');
    });
  }

  function makeDraggable(btn) {
    let dragging = false, moved = false, startX = 0, startY = 0, originLeft = 0, originTop = 0;
    const onMove = (x, y) => {
      if (!dragging) return;
      const dx = x - startX, dy = y - startY;
      moved = moved || Math.abs(dx) > 4 || Math.abs(dy) > 4;
      updateButtonStyle(btn, { left: originLeft + dx, top: originTop + dy });
    };

    btn.addEventListener('touchstart', (e) => {
      const t = e.touches[0]; if (!t) return;
      dragging = true; moved = false; startX = t.clientX; startY = t.clientY;
      originLeft = parseFloat(btn.style.left) || 0; originTop = parseFloat(btn.style.top) || 0;
    }, { passive: true });
    btn.addEventListener('touchmove', (e) => { const t = e.touches[0]; if (t) onMove(t.clientX, t.clientY); }, { passive: true });
    btn.addEventListener('touchend', () => { dragging = false; }, { passive: true });
    btn.addEventListener('mousedown', (e) => {
      dragging = true; moved = false; startX = e.clientX; startY = e.clientY;
      originLeft = parseFloat(btn.style.left) || 0; originTop = parseFloat(btn.style.top) || 0; e.preventDefault();
    });
    window.addEventListener('mousemove', (e) => onMove(e.clientX, e.clientY));
    window.addEventListener('mouseup', () => { dragging = false; });

    btn.addEventListener('click', (e) => {
      if (moved) { e.preventDefault(); e.stopPropagation(); moved = false; return; }
      toggleEruda(btn);
    });
  }

  function attachKeyboardShortcut(btn) {
    window.addEventListener('keydown', (e) => {
      if (e.altKey && e.key.toLowerCase() === 'd') {
        e.preventDefault();
        toggleEruda(btn);
      }
    });
  }

  function injectUi() {
    if (document.getElementById(BTN_ID)) return document.getElementById(BTN_ID);
    const btn = document.createElement('button');
    btn.id = BTN_ID;
    btn.type = 'button';
    setButtonState(btn, isAutoOpenEnabled() ? 'Console: OFF • AUTO' : 'Console: OFF');
    btn.title = 'Tap: toggle console. Drag: move. Long-press/right-click: toggle AUTO mode.';
    Object.assign(btn.style, { position:'fixed', zIndex:'2147483647', padding:'10px 12px', borderRadius:'12px', border:'1px solid rgba(255,255,255,0.25)', background:'rgba(16,16,20,0.85)', color:'#f5f5f5', fontFamily:'-apple-system, BlinkMacSystemFont, Segoe UI, sans-serif', fontSize:'13px', fontWeight:'600', boxShadow:'0 6px 24px rgba(0,0,0,0.25)', cursor:'pointer', userSelect:'none', WebkitUserSelect:'none', opacity:'0.95' });
    (document.documentElement || document.body).appendChild(btn);
    updateButtonStyle(btn, loadPosition());
    makeDraggable(btn);
    attachKeyboardShortcut(btn);
    addAutoToggle(btn);
    window.addEventListener('resize', () => updateButtonStyle(btn, { left: parseFloat(btn.style.left) || 8, top: parseFloat(btn.style.top) || 8 }));
    return btn;
  }

  async function boot() {
    const btn = injectUi();
    if (isAutoOpenEnabled()) {
      try { await openEruda(btn); } catch (e) { console.warn('[Orion Eruda] Auto-open failed', e); }
    }
  }

  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, { once: true });
  else boot();
})();