Website Analyser

Outputs capturable elements to console.log.

// ==UserScript==
// @name         Website Analyser
// @name:en      Website Analyser
// @name:ja      サイト分析
// @namespace    http://tampermonkey.net/
// @version      2025-05-09
// @description   Outputs capturable elements to console.log.
// @description:en Outputs capturable elements to console.log
// @description:ja キャプチャ可能な要素をconsole.logに出力する
// @author       ぐらんぴ
// @match        http*://*/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-start
// @license      MIT
// ==/UserScript==

//const log = console.log;
let settings = {
    querySelector: GM_getValue('querySelector', false),
    querySelectorAll: GM_getValue('querySelectorAll', false),
    append: GM_getValue('append', false),
    appendChild: GM_getValue('appendChild', false),
    defineProperty: GM_getValue('defineProperty', false),
    event: GM_getValue('event', false),
    fetch: GM_getValue('fetch', false),
    webSocket: GM_getValue('webSocket', false),
    XMLHttpRequest: GM_getValue('XMLHttpRequest', false),
    ShadowDOM: GM_getValue('ShadowDOM', false),
    before: GM_getValue('before', false),
    after: GM_getValue('after', false),
    insertBefore: GM_getValue('insertBefore', false),
    dispatchEvent: GM_getValue('dispatchEvent', false),
    setInterval: GM_getValue('setInterval', false),
    setTimeout: GM_getValue('setTimeout', false),
    requestIdleCallback: GM_getValue('requestIdleCallback', false),
};

const toggleSetting = (key) => {
    settings[key] = !settings[key];
    GM_setValue(key, settings[key]);
    document.getElementById(key).checked = settings[key]; // チェックボックスの状態を同期
    console.log(`${key} is now ${settings[key]}`);
};

const analyzer = () => {
    if(settings.defineProperty){
        const origObjDefineProperty = Object.defineProperty;
        Object.defineProperty = (...args)=>{
            console.log("defineProperty:", args);
            return origObjDefineProperty(...args);
        };
    }
    if(settings.fetch){
        const origFetch = unsafeWindow.fetch;
        unsafeWindow.fetch = async (...args) => {
            console.log("fetch:", args);
            const res = await origFetch(...args);
            return res;
        };

    }
    if(settings.webSocket){
        const origWb = unsafeWindow.WebSocket;
        unsafeWindow.WebSocket = function (...args) {
            const ws = new origWb(...args);
            ws.addEventListener("message", e => {
                const data = JSON.parse(e.data);
                console.log("webSocket:", data);
            });
            return ws;
        };
    }
    if(settings.event){
        const origAddEventListener = EventTarget.prototype.addEventListener;
        EventTarget.prototype.addEventListener = function(type, listener, options) {
            console.log("event:", type);
            return origAddEventListener.call(this, type, listener, options);
        };
    }
    if(settings.appendChild){
        const origAppendChild = Element.prototype.appendChild;
        Element.prototype.appendChild = function(...args) {
            console.log("appendChild:", args);
            return origAppendChild.apply(this, args);
        };
    }
    if(settings.querySelector){
        const originalQuerySelector = Document.prototype.querySelector;
        Object.defineProperty(Document.prototype, 'querySelector', {
            value: function(...args) {
                console.log("selector:", ...args);
                return originalQuerySelector.apply(this, args);
            },
            writable: true,
            configurable: true
        });
    }
    if(settings.querySelectorAll){
        const originalQuerySelectorAll = Document.prototype.querySelectorAll;
        Object.defineProperty(Document.prototype, 'querySelectorAll', {
            value: function(...args) {
                console.log("selectorAll:", ...args);
                return originalQuerySelectorAll.apply(this, args);
            },
            writable: true,
            configurable: true
        });
    }
    if(settings.XMLHttpRequest){
        const originalOpen = XMLHttpRequest.prototype.open;
        const originalSend = XMLHttpRequest.prototype.send;

        XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
            this._requestInfo = { method, url, async, user, password };
            console.log('XHR Request:', { method, url, async, user });
            return originalOpen.apply(this, arguments);
        };

        XMLHttpRequest.prototype.send = function(body) {
            this.addEventListener('load', function() {
                console.log('XHR Response:', {
                    url: this._requestInfo.url,
                    status: this.status,
                    statusText: this.statusText,
                    response: this.responseText.substring(0, 200) // untill 200
                });
            });
            console.log('XHR Send:', {
                url: this._requestInfo.url,
                body: body ? body.substring(0, 200) : null
            });
            return originalSend.apply(this, arguments);
        };
    }
    if(settings.ShadowDOM){
        const originalAttachShadow = Element.prototype.attachShadow;
        Element.prototype.attachShadow = function(...args) {
            console.log('Shadow DOM:', this,/* 'Args:', args*/);

            const shadowRoot = originalAttachShadow.apply(this, args);

            console.log('ShadowRoot:', shadowRoot);

            return shadowRoot;
        };
    }
    if(settings.before){
        const origBefore = Element.prototype.before;
        Element.prototype.before = function(...args) {
            setTimeout(() => {
                console.log('Before:', args)
            },0)
            return origBefore.apply(this, args);
        };
    }
    if(settings.after){
        const origAfter = Element.prototype.after;
        Element.prototype.after = function(...args) {
            setTimeout(() => {
                console.log('After:', args)
            },0)
            return origAfter.apply(this, args);
        };
    }
    if(settings.insertBefore){
        const origInsertBefore = Node.prototype.insertBefore;
        Node.prototype.insertBefore = function(newNode, referenceNode) {
            console.log('newNode:', newNode);
            console.log('referenceNode:', referenceNode);
            return origInsertBefore.apply(this, [newNode, referenceNode]);
        };
    }
    if(settings.dispatchEvent){
        const originalDispatchEvent = EventTarget.prototype.dispatchEvent;
        EventTarget.prototype.dispatchEvent = function(event) {
            console.log('DispatchEvent:', {
                target: this,
                event: {
                    type: event.type,
                    eventObject: event,
                }
            });
            return originalDispatchEvent.apply(this, arguments);
        };
    }
    if(settings.remove){
        const originalRemove = Element.prototype.remove;
        Element.prototype.remove = function() {
            console.log('Removed:', this);
            return originalRemove.apply(this);
        };
    }
    if(settings.setInterval){
        const originalSetInterval = unsafeWindow.setInterval;
        unsafeWindow.setInterval = function(callback, delay, ...args) {
            if (callback.toString() !== "function () { [native code] }") {
                console.log('setInterval:', {
                    function: callback.toString(),
                    callback: callback,
                    delay: delay,
                    args: args
                });
            }
            return originalSetInterval(callback, delay, ...args);
        };
    }
    if(settings.setTimeout){
        const originalSetTimeout = unsafeWindow.setTimeout;
        unsafeWindow.setTimeout = function(callback, delay, ...args) {
            if (callback.toString() !== "function () { [native code] }") {
                console.log('setTimeout:', {
                    function: callback.toString(),
                    callback: callback,
                    delay: delay,
                    args: args
                });
            }
            return originalSetTimeout(callback, delay, ...args);
        }
    }
    if(settings.requestIdleCallback){
        const originalRequestIdleCallback = unsafeWindow.requestIdleCallback;
        unsafeWindow.requestIdleCallback = function(callback, options) {
            if (callback.toString() !== "function () { [native code] }") {
                console.log('requestIdleCallback', {
                    function: callback.toString(),
                    callback: callback,
                    options,
                });
            }
            return originalRequestIdleCallback(callback, options);
        };
    }
};
analyzer()

const createSettingsUI = () => {
    // Remove existing settings menu if it exists
    const existingMenu = document.getElementById("settingsMenu");
    if (existingMenu) {
        document.body.removeChild(existingMenu);
    }

    // 画面サイズを取得し、メニューの大きさを調整
    const screenWidth = window.innerWidth * 0.8;
    const screenHeight = window.innerHeight * 0.8;

    const div = document.createElement('div');
    div.style.position = 'fixed';
    div.style.top = '50%';
    div.style.left = '50%';
    div.style.transform = 'translate(-50%, -50%)';
    div.style.background = 'white';
    div.style.padding = '20px';
    div.style.zIndex = '10000';
    div.style.width = `${screenWidth}px`;
    div.style.height = `${screenHeight}px`;
    div.style.display = 'flex';
    div.style.flexDirection = 'column';
    div.style.boxShadow = '0px 0px 10px rgba(0,0,0,0.2)';
    div.style.overflowY = "scroll";
    div.style.overflow = 'auto'; // はみ出し防止
    div.style.flexDirection = 'row';
    div.style.flexWrap = 'wrap';
    div.id = "settingsMenuGRMP";

    // 閉じるボタン(右上)
    const closeBtn = document.createElement('button');
    closeBtn.innerText = '✖';
    closeBtn.style.position = 'absolute';
    closeBtn.style.top = '10px';
    closeBtn.style.right = '10px';
    closeBtn.style.border = 'none';
    closeBtn.style.background = 'none';
    closeBtn.style.fontSize = '18px';
    closeBtn.style.cursor = 'pointer';
    closeBtn.onclick = () => document.body.removeChild(div);
    div.appendChild(closeBtn);

    // 設定項目を追加
    const categories = {
        "DOM": ["append", "appendChild", "before", "after", "insertBefore", "ShadowDOM", "remove", "querySelector", "querySelectorAll"],
        "Object": ["defineProperty"],
        "Event": ["event", "dispatchEvent"],
        "Network": ["fetch", "XMLHttpRequest", "webSocket"],
        "Timer": ["setInterval", "setTimeout", "requestIdleCallback"],
    };

    for (let category in categories) {
        const categoryDiv = document.createElement('div');
        categoryDiv.style.padding = '10px';
        categoryDiv.style.overflow = 'hidden';

        // 親カテゴリーのラベル
        const categoryLabel = document.createElement('label');
        categoryLabel.style.display = 'flex';
        categoryLabel.style.alignItems = 'center';
        categoryLabel.style.fontSize = '18px';
        categoryLabel.style.marginBottom = '10px';
        categoryLabel.style.fontWeight = 'bold';

        const categoryCheckbox = document.createElement('input');
        categoryCheckbox.type = 'checkbox';
        categoryCheckbox.id = category
        categoryCheckbox.checked = settings[category] || false;
        categoryCheckbox.style.marginRight = '10px';
        categoryCheckbox.onclick = e => {
            let targetCategory = e.target.id;
            let checks;

            if (targetCategory == "DOM") checks = document.querySelectorAll(".DOM");
            if (targetCategory == "Object") checks = document.querySelectorAll(".Object");
            if (targetCategory == "Event") checks = document.querySelectorAll(".Event");
            if (targetCategory == "Network") checks = document.querySelectorAll(".Network");
            if (targetCategory == "Timer") checks = document.querySelectorAll(".Timer");

            let isChecked = categoryCheckbox.checked;

            checks.forEach(val => {
                val.checked = isChecked;
                GM_setValue(val.id, isChecked);
                console.log(`${val.id} is now ${isChecked}`);
            });
        };

        categoryLabel.appendChild(categoryCheckbox);
        categoryLabel.appendChild(document.createTextNode(category));
        categoryDiv.appendChild(categoryLabel);

        // 子要素のチェックボックス
        categories[category].forEach(key => {
            const label = document.createElement('label');
            label.style.display = 'flex';
            label.style.alignItems = 'center';
            label.style.fontSize = '16px';
            label.style.marginBottom = '5px';

            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.id = key;
            checkbox.className = [category];
            checkbox.checked = settings[key] || false;
            checkbox.onclick = e => {
                let targetCategory = e.target.className;
                let checks;

                if(targetCategory == "DOM") checks = document.querySelectorAll(".DOM");
                if(targetCategory == "Object") checks = document.querySelectorAll(".Object");
                if(targetCategory == "Event") checks = document.querySelectorAll(".Event");
                if(targetCategory == "Network") checks = document.querySelectorAll(".Network");
                if(targetCategory == "Timer") checks = document.querySelectorAll(".Timer");

                checks.forEach(elm => {
                    if (elm.checked == false) {
                        document.querySelector("#" + elm.className).checked = false;
                    }

                    if (document.querySelectorAll('.' + elm.className + ':checked').length == checks.length){
                        document.querySelector("#" + elm.className).checked = true;
                    }
                });
                toggleSetting(key);
            }
            checkbox.style.marginRight = '10px';

            label.appendChild(checkbox);
            label.appendChild(document.createTextNode(key));
            categoryDiv.appendChild(label);
        });

        div.appendChild(categoryDiv);
    }

    document.body.appendChild(div);

    // 設定メニュー以外の要素をクリックすると閉じる
    document.addEventListener('click', e => {
        if (!div.contains(e.target)) {
            document.body.removeChild(div);
        }
    }, { once: true });
};

// メニューコマンドを登録
GM_registerMenuCommand("Open Settings", createSettingsUI);