Mini DevTools Mobile

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

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu komut dosyasını yüklemek için bir kullanıcı komut dosyası yöneticisi uzantısı yüklemeniz gerekecek.

(Zaten bir kullanıcı komut dosyası yöneticim var, kurmama izin verin!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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

    });

})();