// ==UserScript== // @name Auto Login & OTP // @namespace https://github.com/pcider // @version 2026-05-22 // @description try to take over the world! :3 // @author cider // @match https://ease.sutd.edu.sg/* // @icon https://www.google.com/s2/favicons?sz=64&domain=edu.sg // @grant none // ==/UserScript== // leave empty if you dont want the script to autofill for you const USERNAME = ''; const PASSWORD = ''; // in your authenticator app, you should get a URL like // "otpauth://totp/ease.sutd.edu.sg:?secret=&issuer=ease.sutd.edu.sg" // input the part of the URL here. // leave empty if you dont want the script to autofill for you const TOTP_SECRET = ''; // set to true if you want the script to click the button 4 u const AUTO_SUBMIT = true; const MAX_TRIES = 21; // try for 21 times with 500ms intervals before giving up. const TIME_INTERVAL = 500; // 500ms const USERNAME_FIELD_SELECTOR = "input[autocomplete='username']"; const PASSWORD_FIELD_SELECTOR = "input[type='password']"; const SUBMIT_BUTTON_SELECTOR = "input[type='submit']"; const TOTP_FIELD_SELECTOR = "input[name='credentials.passcode']"; const ERROR_ICON_SELECTOR = ".error-16"; window.jsOTP = null; const updateInput = input => { input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); }; const main = (() => { let tries = 0; const interval = setInterval(() => { 'use strict'; if (tries >= MAX_TRIES) { console.log(`maxTries ${tries} reached. giving up.`) clearInterval(interval); return; } console.log('meow', tries); const sel = document.querySelector.bind(document); const loginInput = sel(USERNAME_FIELD_SELECTOR); const pwInput = sel(PASSWORD_FIELD_SELECTOR); const submitBtn = sel(SUBMIT_BUTTON_SELECTOR); const totpInput = sel(TOTP_FIELD_SELECTOR); const errorIcon = sel(ERROR_ICON_SELECTOR); const errorTextBox = errorIcon && errorIcon.parentNode.children[1]; const isLoginPage = loginInput && pwInput && submitBtn; const isOTPPage = totpInput && !pwInput && submitBtn; const isPwOnlyPage = pwInput && submitBtn; const isError = !!errorIcon; if (isError) { errorTextBox.innerText += '\n.\n[Auto Login & OTP] error detected... bailing out' clearInterval(interval); } else if (isLoginPage) { if (USERNAME) loginInput.value = USERNAME; if (PASSWORD) pwInput.value = PASSWORD; if (AUTO_SUBMIT && loginInput.textLength > 0 && pwInput.textLength > 0) { updateInput(loginInput); updateInput(pwInput); submitBtn.click(); } else { console.log("waiting for username & password..."); } } else if (isPwOnlyPage) { if (PASSWORD) pwInput.value = PASSWORD; if (AUTO_SUBMIT && pwInput.textLength > 0) { updateInput(pwInput); submitBtn.click(); } } else if (isOTPPage) { if (TOTP_SECRET) totpInput.value = new window.jsOTP.totp().getOtp(TOTP_SECRET); if (AUTO_SUBMIT && totpInput.textLength > 0) { updateInput(totpInput); submitBtn.click(); } else { console.log("waiting for TOTP..."); } } else { console.log("waiting for inputs... ") console.log('loginInput', loginInput); console.log('pwInput', pwInput); console.log('submitBtn', submitBtn); tries++; } }, TIME_INTERVAL); }); // https://github.com/jiangts/JS-OTP/blob/master/dist/jsOTP-es5.min.js /* * File combining * (1) sha.js by Brian Turek 2008-2013 under BSD license * (2) and a modified js OTP implementation found on JSFiddle */ "use strict";var _createClass=function(){function r(r,e){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:30,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:6;if(_classCallCheck(this,r),this.expiry=e,this.length=n,this.length>8||this.length<6)throw"Error: invalid code length"}return _createClass(r,[{key:"dec2hex",value:function(r){return(r<15.5?"0":"")+Math.round(r).toString(16)}},{key:"hex2dec",value:function(r){return parseInt(r,16)}},{key:"base32tohex",value:function(r){var e,n,t,o,i;for("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567",e="",t="",o=0;o=r.length&&(r=Array(e+1-r.length).join(n)+r),r}},{key:"getOtp",value:function(r){var e,n,t,o,i,h,w,u=arguments.length>1&&void 0!==arguments[1]?arguments[1]:(new Date).getTime();if(t=this.base32tohex(r),e=Math.round(u/1e3),w=this.leftpad(this.dec2hex(Math.floor(e/this.expiry)),16,"0"),(h=new jsSHA("SHA-1","HEX")).setHMACKey(t,"HEX"),h.update(w),"KEY MUST BE IN BYTE INCREMENTS"===(n=h.getHMAC("HEX")))throw"Error: hex key must be in byte increments";return o=this.hex2dec(n.substring(n.length-1)),i=(i=(this.hex2dec(n.substr(2*o,8))&this.hex2dec("7fffffff"))+"").length>this.length?i.substr(i.length-this.length,this.length):this.leftpad(i,this.length,"0")}}]),r}(),r=function(){function r(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:6;if(_classCallCheck(this,r),this.length=e,this.length>8||this.length<6)throw"Error: invalid code length"}return _createClass(r,[{key:"uintToString",value:function(r){var e;return e=String.fromCharCode.apply(null,r),decodeURIComponent(escape(e))}},{key:"getOtp",value:function(r,e){var n,t,o,i,h;return(i=new jsSHA("SHA-1","TEXT")).setHMACKey(r,"TEXT"),i.update(this.uintToString(new Uint8Array(this.intToBytes(e)))),n=i.getHMAC("HEX"),h=(127&(t=this.hexToBytes(n))[o=15&t[19]])<<24|(255&t[o+1])<<16|(255&t[o+2])<<8|255&t[o+3],(h+="").substr(h.length-this.length,this.length)}},{key:"intToBytes",value:function(r){var e,n;for(e=[],n=7;n>=0;)e[n]=255&r,r>>=8,--n;return e}},{key:"hexToBytes",value:function(r){var e,n,t;for(n=[],t=0,e=r.length;t>>3,0!=a%2)throw new Error("String of HEX type must be in byte increments");for(o=0;o>>1)+u)>>>2;t.length<=h;)t.push(0);t[h]|=i<<8*(3-w%4)}return{value:t,binLen:4*a+n}}function t(r,e,n){var t,o,i,h,w,u=[];for(u=e||[0],i=(n=n||0)>>>3,o=0;o>>2,u.length<=h&&u.push(0),u[h]|=t<<8*(3-w%4);return{value:u,binLen:8*r.length+n}}function o(r,e,n){var t,o,i,h,w,u,a,d,l=[],f=0;if(l=e||[0],u=(n=n||0)>>>3,-1===r.search(/^[a-zA-Z0-9=+\/]+$/))throw new Error("Invalid character in base-64 string");if(w=r.indexOf("="),r=r.replace(/\=/g,""),-1!==w&&w