Mini DevTools Mobile

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

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==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');

    });

})();