Mini DevTools Mobile

DevTools cho điện thoại, nút nổi mở panel

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey to install this script.

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Mini DevTools Mobile
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  DevTools cho điện thoại, nút nổi mở panel
// @author       You
// @match        *://*/*
// @include      *
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // Đợi DOM sẵn sàng
    function waitForBody(callback) {
        if (document.body) {
            callback();
        } else {
            setTimeout(function() { waitForBody(callback); }, 100);
        }
    }

    waitForBody(function() {

        // ========== NÚT NỔI ==========
        var floatBtn = document.createElement('div');
        floatBtn.id = 'dtm-float-btn';
        floatBtn.textContent = '🔧';
        floatBtn.style.cssText = 
            'position:fixed;top:15px;right:15px;z-index:9999999;' +
            'width:45px;height:45px;background:#007acc;color:white;' +
            'border-radius:50%;text-align:center;line-height:45px;' +
            'font-size:22px;cursor:pointer;box-shadow:0 4px 12px rgba(0,0,0,0.5);' +
            'user-select:none;-webkit-user-select:none;' +
            '-webkit-tap-highlight-color:transparent;';

        // Kéo thả nút nổi
        var isDragging = false;
        var startX, startY, startLeft, startTop;

        floatBtn.addEventListener('touchstart', function(e) {
            var touch = e.touches[0];
            startX = touch.clientX;
            startY = touch.clientY;
            startLeft = floatBtn.offsetLeft;
            startTop = floatBtn.offsetTop;
            isDragging = false;
        });

        floatBtn.addEventListener('touchmove', function(e) {
            var touch = e.touches[0];
            var dx = touch.clientX - startX;
            var dy = touch.clientY - startY;
            if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
                isDragging = true;
                floatBtn.style.left = Math.max(0, Math.min(window.innerWidth - 45, startLeft + dx)) + 'px';
                floatBtn.style.top = Math.max(0, Math.min(window.innerHeight - 45, startTop + dy)) + 'px';
                floatBtn.style.right = 'auto';
            }
        });

        floatBtn.addEventListener('touchend', function(e) {
            if (!isDragging) {
                togglePanel();
            }
        });

        document.body.appendChild(floatBtn);

        // ========== PANEL ==========
        var panel = document.createElement('div');
        panel.id = 'dtm-panel';
        panel.innerHTML = 
            '<div id="dtm-header">' +
                '<span>🔧 Mini DevTools</span>' +
                '<div style="display:flex;gap:3px;flex-wrap:wrap;">' +
                    '<button id="dtm-tab-network" class="dtm-tab-btn">🌐 Net</button>' +
                    '<button id="dtm-tab-console" class="dtm-tab-btn active">📜 JS</button>' +
                    '<button id="dtm-tab-sources" class="dtm-tab-btn">📁 Src</button>' +
                    '<button id="dtm-tab-storage" class="dtm-tab-btn">💾 Stg</button>' +
                    '<button id="dtm-tab-iframe" class="dtm-tab-btn">🖼️ Iframe</button>' +
                    '<button id="dtm-btn-pause" style="background:#8b0000;color:#fff;">⏯️</button>' +
                    '<button id="dtm-btn-close">❌</button>' +
                '</div>' +
            '</div>' +
            '<div id="dtm-log"></div>' +
            '<textarea id="dtm-input" placeholder="Gõ lệnh JS..."></textarea>';
        panel.style.cssText = 
            'position:fixed;bottom:0;left:0;right:0;height:45vh;background:#1e1e1e;' +
            'color:#d4d4d4;font-family:monospace;font-size:11px;z-index:9999998;' +
            'display:none;flex-direction:column;border-top:3px solid #007acc;' +
            'border-radius:12px 12px 0 0;overflow:hidden;';
        document.body.appendChild(panel);

        // Style bổ sung
        var style = document.createElement('style');
        style.textContent = 
            '#dtm-header{display:flex;justify-content:space-between;align-items:center;' +
            'padding:8px;background:#252526;border-bottom:1px solid #3e3e3e;flex-shrink:0;flex-wrap:wrap;gap:4px;}' +
            '#dtm-header span{font-weight:bold;color:#007acc;font-size:13px;}' +
            '.dtm-tab-btn{background:#3e3e3e;color:#ccc;border:1px solid #555;' +
            'padding:5px 8px;border-radius:3px;font-size:10px;white-space:nowrap;}' +
            '.dtm-tab-btn.active{background:#007acc;color:white;}' +
            '#dtm-log{flex:1;overflow-y:auto;padding:8px;font-size:10px;' +
            'line-height:1.4;white-space:pre-wrap;word-break:break-all;}' +
            '#dtm-input{width:100%;background:#2d2d2d;color:#4fc1ff;border:none;' +
            'border-top:1px solid #3e3e3e;padding:10px;font-family:monospace;' +
            'font-size:13px;outline:none;box-sizing:border-box;flex-shrink:0;}' +
            '.dtm-log-info{color:#4fc1ff;}' +
            '.dtm-log-warn{color:#ffcc00;}' +
            '.dtm-log-error{color:#ff6b6b;}' +
            '.dtm-log-success{color:#4caf50;}' +
            '.dtm-log-network{color:#ce9178;}';
        document.head.appendChild(style);

        // ========== REFS ==========
        function $(id) { return document.getElementById(id); }

        function getLog() { return $('dtm-log'); }
        function getInput() { return $('dtm-input'); }

        // ========== TOGGLE ==========
        function togglePanel() {
            var p = $('dtm-panel');
            if (p.style.display === 'none' || p.style.display === '') {
                p.style.display = 'flex';
                floatBtn.textContent = '▼';
                floatBtn.style.background = '#e74c3c';
            } else {
                p.style.display = 'none';
                floatBtn.textContent = '🔧';
                floatBtn.style.background = '#007acc';
                if (isPaused) resumeExecution();
            }
        }

        // ========== LOG ==========
        function dtLog(msg, type) {
            type = type || 'info';
            var log = getLog();
            if (!log) return;
            var line = document.createElement('div');
            line.className = 'dtm-log-' + type;
            line.textContent = '[' + new Date().toLocaleTimeString() + '] ' + msg;
            log.appendChild(line);
            log.scrollTop = log.scrollHeight;
        }

        // ========== OVERRIDE CONSOLE ==========
        var _log = console.log.bind(console);
        var _warn = console.warn.bind(console);
        var _error = console.error.bind(console);
        console.log = function() {
            _log.apply(console, arguments);
            dtLog([].slice.call(arguments).map(function(a) {
                return typeof a === 'object' ? JSON.stringify(a) : String(a);
            }).join(' '), 'info');
        };
        console.warn = function() { _warn.apply(console, arguments); dtLog([].slice.call(arguments).join(' '), 'warn'); };
        console.error = function() { _error.apply(console, arguments); dtLog([].slice.call(arguments).join(' '), 'error'); };

        // ========== NETWORK ==========
        var oldFetch = window.fetch;
        window.fetch = function() {
            var url = typeof arguments[0] === 'string' ? arguments[0] : arguments[0].url;
            dtLog('📡 FETCH → ' + url, 'network');
            return oldFetch.apply(this, arguments).then(function(r) {
                dtLog('📡 FETCH ← ' + r.status + ' ' + url, 'network');
                return r;
            });
        };
        var oldXHRO = XMLHttpRequest.prototype.open;
        var oldXHRS = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.open = function(m, url) {
            this._u = url; this._m = m;
            return oldXHRO.apply(this, arguments);
        };
        XMLHttpRequest.prototype.send = function() {
            var s = this;
            dtLog('📡 XHR ' + s._m + ' → ' + s._u, 'network');
            s.addEventListener('load', function() { dtLog('📡 XHR ← ' + s.status, 'network'); });
            return oldXHRS.apply(s, arguments);
        };

        // ========== IFRAME ==========
        function getGameWindow() {
            var iframes = document.querySelectorAll('iframe');
            for (var i = 0; i < iframes.length; i++) {
                try { if (iframes[i].contentWindow) return iframes[i].contentWindow; } catch(e) {}
            }
            return window;
        }

        // ========== EXEC ==========
        function exec(cmd) {
            var ctx = getGameWindow();
            try {
                var r = (function() { return eval(cmd); }).call(ctx);
                if (r !== undefined) dtLog(String(r), 'success');
            } catch(e) {
                dtLog('❌ ' + e.message, 'error');
            }
        }

        // ========== PAUSE ==========
        var isPaused = false;
        var pauseTimer = null;
        function pauseExecution() {
            if (isPaused) return;
            isPaused = true;
            $('dtm-btn-pause').style.background = '#006400';
            $('dtm-btn-pause').textContent = '▶️';
            dtLog('⏸️ PAUSE', 'warn');
            pauseTimer = setInterval(function() {
                var s = Date.now(); while (Date.now() - s < 50) {}
            }, 0);
        }
        function resumeExecution() {
            if (!isPaused) return;
            isPaused = false;
            $('dtm-btn-pause').style.background = '#8b0000';
            $('dtm-btn-pause').textContent = '⏯️';
            dtLog('▶️ RESUME', 'success');
            if (pauseTimer) { clearInterval(pauseTimer); pauseTimer = null; }
        }

        // ========== SOURCES ==========
        function showSources() {
            var log = getLog();
            log.innerHTML = '';
            var doc = document;
            var gameWin = getGameWindow();
            try { doc = gameWin.document; } catch(e) {}

            dtLog('📁 SCRIPTS:', 'success');
            var scripts = doc.querySelectorAll('script[src]');
            scripts.forEach(function(s, i) {
                dtLog('  📄 ' + (s.src.split('/').pop() || ('script_' + i)), 'info');
            });
            dtLog('📁 INLINE SCRIPTS: ' + doc.querySelectorAll('script:not([src])').length, 'info');
            dtLog('📁 CANVAS: ' + doc.querySelectorAll('canvas').length, 'info');
        }

        // ========== STORAGE ==========
        function showStorage() {
            var log = getLog();
            log.innerHTML = '';
            dtLog('💾 LOCAL STORAGE:', 'success');
            try {
                var ls = getGameWindow().localStorage || localStorage;
                for (var i = 0; i < ls.length; i++) {
                    var k = ls.key(i);
                    dtLog(k + ': ' + ls.getItem(k).substring(0, 150), 'info');
                }
            } catch(e) { dtLog('Lỗi: ' + e.message, 'error'); }
        }

        // ========== IFRAME INFO ==========
        function showIframe() {
            var log = getLog();
            log.innerHTML = '';
            var iframes = document.querySelectorAll('iframe');
            dtLog('🖼️ Tổng iframe: ' + iframes.length, 'info');
            iframes.forEach(function(f, i) {
                dtLog('  #' + i + ': ' + (f.src || '(no src)'), 'info');
                try {
                    dtLog('    Canvas: ' + f.contentDocument.querySelectorAll('canvas').length, 'success');
                } catch(e) {
                    dtLog('    ⚠️ Cross-origin', 'warn');
                }
            });
        }

        // ========== TAB SWITCH ==========
        function switchTab(tab) {
            ['network','console','sources','storage','iframe'].forEach(function(t) {
                var btn = $('dtm-tab-' + t);
                if (btn) btn.classList.remove('active');
            });
            var active = $('dtm-tab-' + tab);
            if (active) active.classList.add('active');

            var log = getLog();
            if (log) log.innerHTML = '';

            switch(tab) {
                case 'network': dtLog('🌐 Đang theo dõi...', 'info'); break;
                case 'console': dtLog('📜 Sẵn sàng', 'info'); break;
                case 'sources': showSources(); break;
                case 'storage': showStorage(); break;
                case 'iframe': showIframe(); break;
            }
        }

        // ========== EVENTS ==========
        $('dtm-btn-close').addEventListener('click', togglePanel);
        $('dtm-btn-pause').addEventListener('click', function() {
            isPaused ? resumeExecution() : pauseExecution();
        });

        $('dtm-tab-network').addEventListener('click', function() { switchTab('network'); });
        $('dtm-tab-console').addEventListener('click', function() { switchTab('console'); });
        $('dtm-tab-sources').addEventListener('click', function() { switchTab('sources'); });
        $('dtm-tab-storage').addEventListener('click', function() { switchTab('storage'); });
        $('dtm-tab-iframe').addEventListener('click', function() { switchTab('iframe'); });

        $('dtm-input').addEventListener('keydown', function(e) {
            if (e.key === 'Enter') {
                e.preventDefault();
                var cmd = this.value.trim();
                if (cmd) {
                    dtLog('> ' + cmd, 'info');
                    exec(cmd);
                    this.value = '';
                }
            }
        });

        // ========== START ==========
        dtLog('✅ DevTools Mobile sẵn sàng', 'success');
        dtLog('👆 Bấm nút 🔧 góc phải để mở', 'info');
        dtLog('📜 Tab JS để chạy lệnh', 'info');

    });

})();