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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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.

ستحتاج إلى تثبيت إضافة مثل Stylus لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتتمكن من تثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

ستحتاج إلى تثبيت إضافة لإدارة أنماط المستخدم لتثبيت هذا النمط.

(لدي بالفعل مثبت أنماط للمستخدم، دعني أقم بتثبيته!)

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

})();