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