Construct 3 Auto Login

Automates the Construct 3 login process: First time requires one click on username or password field so it autofills, after that it's zero click as it saves on storage. I am not responsible for what this does to you

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

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

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Construct 3 Auto Login
// @namespace    http://tampermonkey.net/
// @version      18
// @description  Automates the Construct 3 login process: First time requires one click on username or password field so it autofills, after that it's zero click as it saves on storage. I am not responsible for what this does to you
// @author       Clovelt (feat. Gemini)
// @match        https://editor.construct.net/*
// @match        https://account.construct.net/login*
// @allFrames    true
// @grant        GM_setValue
// @grant        GM_getValue
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // --- Clean, standard clicker for Construct 3's UI ---
    function simulateRealClick(element) {
        if (!element) return;
        const rect = element.getBoundingClientRect();
        const x = Math.round(rect.left + (rect.width / 2));
        const y = Math.round(rect.top + (rect.height / 2));

        const config = {
            bubbles: true, cancelable: true, composed: true,
            view: document.defaultView, detail: 1,
            clientX: x, clientY: y, screenX: x, screenY: y,
            button: 0, buttons: 1, pointerId: 1, pointerType: "mouse", isPrimary: true
        };
        const upConfig = { ...config, buttons: 0 };

        element.dispatchEvent(new PointerEvent('pointerover', config));
        element.dispatchEvent(new PointerEvent('pointerenter', config));
        element.dispatchEvent(new PointerEvent('pointerdown', config));
        element.dispatchEvent(new MouseEvent('mousedown', config));
        element.dispatchEvent(new PointerEvent('pointerup', upConfig));
        element.dispatchEvent(new MouseEvent('mouseup', upConfig));
        element.dispatchEvent(new MouseEvent('click', config));

        if (typeof element.click === 'function') {
            element.click();
        }
    }

    // --- Search for the dropdown "Log in" button ---
    function findAndClickLoginMenu() {
        const elements = document.querySelectorAll('span, div, button, ui-menuitem, ui-menu-item');
        for (let i = elements.length - 1; i >= 0; i--) {
            const el = elements[i];
            const txt = (el.innerText || el.textContent || "").trim();
            if (txt === "Log in" || txt === "Login") {
                const rect = el.getBoundingClientRect();
                if (rect.width > 0 && rect.height > 0 && rect.top >= 0) {
                    const target = el.closest('ui-menuitem, ui-menu-item, button, [role="button"]') || el;
                    simulateRealClick(target);
                    return true;
                }
            }
        }
        return false;
    }

    // --- Inject Text into React/Vue fields ---
    function fillField(field, val) {
        field.focus();

        // React 15/16+ value setter bypass
        const nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
        if (nativeInputValueSetter) {
            nativeInputValueSetter.call(field, val);
        } else {
            field.value = val;
        }

        field.dispatchEvent(new Event('input', { bubbles: true }));
        field.dispatchEvent(new Event('change', { bubbles: true }));
        field.blur();
    }

    // =========================================================================
    // SCENARIO 1: We are inside the Login Iframe (account.construct.net)
    // =========================================================================
    if (window.location.hostname === "account.construct.net") {
        console.log("[C3 AutoLogin] Running inside login iframe...");

        let checkboxTicked = false;
        let loginTriggered = false;

        // Retrieve saved credentials from Tampermonkey Vault
        const savedUser = GM_getValue("c3_username_v2", "");
        const savedPass = GM_getValue("c3_password_v2", "");

        const autoSubmitInterval = setInterval(() => {
            const passwordField = document.querySelector('input[type="password"]');
            const userField = document.querySelector('input[type="text"], input[type="email"], input[name="username"]');

            // 1. Tick the Checkbox (Using forceful DOM manipulation)
            if (!checkboxTicked) {
                const checkBoxes = document.querySelectorAll('input[type="checkbox"]');
                for (let cb of checkBoxes) {
                    if (!cb.checked) {
                        console.log("[C3 AutoLogin] Forcing 'Keep me logged in' checkbox to true...");
                        cb.click(); // Native browser click
                        cb.checked = true; // Force backend value
                        cb.dispatchEvent(new Event('change', { bubbles: true })); // Tell framework it changed
                    }
                }
                checkboxTicked = true;
            }

            // 2. Find submit button
            let submitButton = document.querySelector('button[type="submit"], input[type="submit"]');
            if (!submitButton) {
                const allButtons = document.querySelectorAll('button, .button');
                for (let b of allButtons) {
                    const txt = (b.innerText || "").toLowerCase().trim();
                    if (txt === "log in" || txt === "login") { submitButton = b; break; }
                }
            }

            if (userField && passwordField && submitButton && !loginTriggered) {

                // === PATH A: WE HAVE SAVED CREDS === (Zero-Click Auto Login)
                if (savedUser && savedPass) {
                    console.log("[C3 AutoLogin] Found saved credentials! Injecting instantly...");
                    loginTriggered = true;
                    clearInterval(autoSubmitInterval);

                    // Inject values
                    fillField(userField, savedUser);
                    fillField(passwordField, savedPass);

                    // Submit after 300ms to let the framework register the text and checkbox
                    setTimeout(() => {
                        simulateRealClick(submitButton);
                    }, 300);
                }

                // === PATH B: FIRST TIME SETUP === (Wait for Chrome to drop the text in)
                else {
                    if (passwordField.value.length > 0 && userField.value.length > 0) {
                        console.log("[C3 AutoLogin] Chrome filled the creds! Saving to TM Vault for next time...");
                        loginTriggered = true;
                        clearInterval(autoSubmitInterval);

                        // Save them to Tampermonkey for next time
                        GM_setValue("c3_username_v2", userField.value);
                        GM_setValue("c3_password_v2", passwordField.value);

                        // Fire submit
                        passwordField.dispatchEvent(new Event('input', { bubbles: true }));
                        setTimeout(() => simulateRealClick(submitButton), 300);
                    } else {
                        // Nudge Chrome UI to show the dropdown
                        if (document.activeElement !== userField && document.activeElement !== passwordField) {
                            userField.focus();
                            userField.click();
                        }
                    }
                }
            }
        }, 300);
        return;
    }

    // =========================================================================
    // SCENARIO 2: We are on the Main Editor Page (editor.construct.net)
    // =========================================================================
    console.log("[C3 AutoLogin] Monitoring main editor page...");
    let lastAttemptTime = 0;

    setInterval(() => {
        if (Date.now() - lastAttemptTime < 5000) return;

        // Check 1: Session Expired
        const confirmDialog = document.querySelector('#confirmDialog');
        if (confirmDialog && confirmDialog.hasAttribute('open')) {
            const message = confirmDialog.querySelector('.confirmMessage');
            if (message && message.innerText.includes('logged out')) {
                const confirmBtn = confirmDialog.querySelector('.confirmButton');
                if (confirmBtn) {
                    console.log("[C3 AutoLogin] Session expired dialog found. Clicking 'Log in'...");
                    lastAttemptTime = Date.now();
                    simulateRealClick(confirmBtn);
                    return;
                }
            }
        }

        // Check 2: Guest State
        const accountName = document.querySelector('#userAccountName');
        const loginDialog = document.querySelector('#loginDialog');

        if (accountName && accountName.innerText.trim() === "Guest") {
            if (!loginDialog || !loginDialog.hasAttribute('open')) {
                const accountWrap = document.querySelector('#userAccountWrap');
                if (accountWrap) {
                    console.log("[C3 AutoLogin] Detected 'Guest' state. Opening user menu...");
                    lastAttemptTime = Date.now();

                    simulateRealClick(accountWrap);

                    let scans = 0;
                    const scanInterval = setInterval(() => {
                        scans++;
                        if (findAndClickLoginMenu()) {
                            clearInterval(scanInterval);
                            lastAttemptTime = Date.now();
                        } else if (scans > 15) {
                            clearInterval(scanInterval);
                        }
                    }, 200);
                }
            }
        }
    }, 2000);

})();