Auto-fill JEE Form with Multiple Credentials

Auto-fills roll number and Base64-encoded password with an overlay for managing multiple credentials

// ==UserScript==
// @name         Auto-fill JEE Form with Multiple Credentials
// @namespace    http://tampermonkey.net/
// @version      5.1
// @description  Auto-fills roll number and Base64-encoded password with an overlay for managing multiple credentials
// @match        https://josaa.admissions.nic.in/Applicant/Root/CandidateLogin.aspx*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_listValues
// @grant        GM_deleteValue
// ==/UserScript==

(function() {
    'use strict';

    // Encode and decode Base64 string
    function encode(str) {
        return btoa(str);
    }
    
    function decode(str) {
        return atob(str);
    }

    // Get saved credentials
    let credentialsList = GM_getValue('jeeCredentialsList', []);
    if (credentialsList.length === 0) {
        // Add default credentials if none exist
        credentialsList.push({
            name: 'Default',
            rollNumber: '250310617863',
            encodedPassword: 'U2l0amVlQDg4',
            timestamp: new Date().toISOString().replace('T', ' ').substr(0, 19)
        });
        GM_setValue('jeeCredentialsList', credentialsList);
    }

    // Get last used profile index or default to 0
    let lastUsedProfileIndex = GM_getValue('jeeLastUsedProfileIndex', 0);
    
    // Make sure index is valid (in case profiles were deleted)
    if (lastUsedProfileIndex >= credentialsList.length) {
        lastUsedProfileIndex = 0;
        GM_setValue('jeeLastUsedProfileIndex', 0);
    }
    
    // Set active credentials to last used profile
    let activeCredentials = credentialsList[lastUsedProfileIndex];

    window.addEventListener('load', function() {
        applyCredentials(activeCredentials);
        createCredentialsOverlay();
    });

    function applyCredentials(creds) {
        const rollInput = document.getElementById('ctl00_ContentPlaceHolder1_rollno');
        const passInput = document.getElementById('ctl00_ContentPlaceHolder1_Password1');

        if (rollInput) {
            rollInput.value = creds.rollNumber;
        }

        if (passInput) {
            passInput.value = decode(creds.encodedPassword);
        }
    }

    function createCredentialsOverlay() {
        // Create overlay container
        const overlay = document.createElement('div');
        overlay.style.position = 'fixed';
        overlay.style.bottom = '0';
        overlay.style.left = '0';
        overlay.style.width = '100%';
        overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
        overlay.style.color = 'white';
        overlay.style.padding = '15px';
        overlay.style.zIndex = '9999';
        overlay.style.display = 'flex';
        overlay.style.flexDirection = 'column';
        overlay.style.alignItems = 'center';
        overlay.style.maxHeight = '70vh';
        overlay.style.overflowY = 'auto';

        // Create profile management section
        const profileSection = document.createElement('div');
        profileSection.style.width = '100%';
        profileSection.style.marginBottom = '10px';
        profileSection.style.display = 'flex';
        profileSection.style.flexWrap = 'wrap';
        profileSection.style.justifyContent = 'center';
        profileSection.style.alignItems = 'center';

        // Profile selector
        const profileSelect = document.createElement('select');
        profileSelect.style.padding = '5px';
        profileSelect.style.marginRight = '10px';

        // Populate profile selector
        credentialsList.forEach((cred, index) => {
            const option = document.createElement('option');
            option.value = index;
            option.textContent = cred.name + ' (' + cred.rollNumber + ')';
            if (index === lastUsedProfileIndex) {
                option.selected = true;
            }
            profileSelect.appendChild(option);
        });

        // Profile selector change handler
        profileSelect.addEventListener('change', function() {
            const selectedIndex = parseInt(this.value);
            activeCredentials = credentialsList[selectedIndex];
            
            // Save the last used profile index
            lastUsedProfileIndex = selectedIndex;
            GM_setValue('jeeLastUsedProfileIndex', lastUsedProfileIndex);
            
            // Update form fields
            nameInput.value = activeCredentials.name;
            rollInput.value = activeCredentials.rollNumber;
            passInput.value = decode(activeCredentials.encodedPassword);
            
            // Update status
            updateStatusMessage('Profile "' + activeCredentials.name + '" selected');
        });

        // Delete profile button
        const deleteBtn = document.createElement('button');
        deleteBtn.textContent = '🗑️ Delete';
        deleteBtn.style.padding = '5px 10px';
        deleteBtn.style.backgroundColor = '#f44336';
        deleteBtn.style.border = 'none';
        deleteBtn.style.color = 'white';
        deleteBtn.style.cursor = 'pointer';
        deleteBtn.style.marginRight = '10px';
        deleteBtn.addEventListener('click', function() {
            const selectedIndex = parseInt(profileSelect.value);
            
            // Can't delete if only one profile exists
            if (credentialsList.length <= 1) {
                updateStatusMessage('Cannot delete the only profile', 'error');
                return;
            }
            
            // Confirm deletion
            if (!confirm('Delete profile "' + credentialsList[selectedIndex].name + '"?')) {
                return;
            }
            
            // Delete profile
            credentialsList.splice(selectedIndex, 1);
            GM_setValue('jeeCredentialsList', credentialsList);
            
            // Adjust last used index if needed
            if (lastUsedProfileIndex >= credentialsList.length) {
                lastUsedProfileIndex = credentialsList.length - 1;
            }
            if (selectedIndex === lastUsedProfileIndex) {
                lastUsedProfileIndex = 0; // Reset to first profile if we're deleting the active one
            }
            GM_setValue('jeeLastUsedProfileIndex', lastUsedProfileIndex);
            
            // Set active credentials to the new last used profile
            activeCredentials = credentialsList[lastUsedProfileIndex];
            
            // Rebuild dropdown
            while (profileSelect.firstChild) {
                profileSelect.removeChild(profileSelect.firstChild);
            }
            
            // Repopulate dropdown
            credentialsList.forEach((cred, index) => {
                const option = document.createElement('option');
                option.value = index;
                option.textContent = cred.name + ' (' + cred.rollNumber + ')';
                if (index === lastUsedProfileIndex) {
                    option.selected = true;
                }
                profileSelect.appendChild(option);
            });
            
            // Update form fields
            nameInput.value = activeCredentials.name;
            rollInput.value = activeCredentials.rollNumber;
            passInput.value = decode(activeCredentials.encodedPassword);
            
            updateStatusMessage('Profile deleted successfully');
        });

        // Create form section
        const formSection = document.createElement('div');
        formSection.style.width = '100%';
        formSection.style.display = 'flex';
        formSection.style.flexWrap = 'wrap';
        formSection.style.justifyContent = 'center';
        formSection.style.alignItems = 'center';
        formSection.style.marginBottom = '10px';

        // Profile name input
        const nameLabel = document.createElement('label');
        nameLabel.textContent = 'Profile Name: ';
        nameLabel.style.marginRight = '5px';

        const nameInput = document.createElement('input');
        nameInput.type = 'text';
        nameInput.placeholder = 'Profile Name';
        nameInput.value = activeCredentials.name;
        nameInput.style.marginRight = '15px';
        nameInput.style.padding = '5px';

        // Create roll number input
        const rollLabel = document.createElement('label');
        rollLabel.textContent = 'Roll Number: ';
        rollLabel.style.marginRight = '5px';

        const rollInput = document.createElement('input');
        rollInput.type = 'text';
        rollInput.placeholder = 'Enter Roll Number';
        rollInput.value = activeCredentials.rollNumber;
        rollInput.style.marginRight = '15px';
        rollInput.style.padding = '5px';

        // Create password input
        const passLabel = document.createElement('label');
        passLabel.textContent = 'Password: ';
        passLabel.style.marginRight = '5px';

        const passInput = document.createElement('input');
        passInput.type = 'password';
        passInput.placeholder = 'Enter Password';
        passInput.value = decode(activeCredentials.encodedPassword);
        passInput.style.marginRight = '15px';
        passInput.style.padding = '5px';

        // Toggle password visibility button
        const togglePassBtn = document.createElement('button');
        togglePassBtn.textContent = '👁️';
        togglePassBtn.style.padding = '5px';
        togglePassBtn.style.marginRight = '15px';
        togglePassBtn.style.cursor = 'pointer';
        togglePassBtn.title = 'Toggle Password Visibility';
        togglePassBtn.addEventListener('click', function() {
            if (passInput.type === 'password') {
                passInput.type = 'text';
            } else {
                passInput.type = 'password';
            }
        });

        // Button section
        const buttonSection = document.createElement('div');
        buttonSection.style.width = '100%';
        buttonSection.style.display = 'flex';
        buttonSection.style.justifyContent = 'center';
        buttonSection.style.marginTop = '10px';

        // Create apply button
        const applyButton = document.createElement('button');
        applyButton.textContent = '✓ Apply';
        applyButton.style.padding = '5px 10px';
        applyButton.style.backgroundColor = '#4CAF50';
        applyButton.style.border = 'none';
        applyButton.style.color = 'white';
        applyButton.style.cursor = 'pointer';
        applyButton.style.marginRight = '10px';

        // Create save as new button
        const saveNewButton = document.createElement('button');
        saveNewButton.textContent = '💾 Save as New';
        saveNewButton.style.padding = '5px 10px';
        saveNewButton.style.backgroundColor = '#2196F3';
        saveNewButton.style.border = 'none';
        saveNewButton.style.color = 'white';
        saveNewButton.style.cursor = 'pointer';
        saveNewButton.style.marginRight = '10px';

        // Create update button
        const updateButton = document.createElement('button');
        updateButton.textContent = '🔄 Update Profile';
        updateButton.style.padding = '5px 10px';
        updateButton.style.backgroundColor = '#FF9800';
        updateButton.style.border = 'none';
        updateButton.style.color = 'white';
        updateButton.style.cursor = 'pointer';
        updateButton.style.marginRight = '10px';

        // Create close button
        const closeButton = document.createElement('button');
        closeButton.textContent = 'X';
        closeButton.style.padding = '5px 10px';
        closeButton.style.backgroundColor = '#f44336';
        closeButton.style.border = 'none';
        closeButton.style.color = 'white';
        closeButton.style.cursor = 'pointer';

        // Status message
        const statusMsg = document.createElement('div');
        statusMsg.style.width = '100%';
        statusMsg.style.textAlign = 'center';
        statusMsg.style.marginTop = '8px';
        statusMsg.style.fontSize = '0.9em';
        statusMsg.textContent = 'Select a profile or create a new one';

        // Update status message function
        function updateStatusMessage(message, type = 'info') {
            statusMsg.textContent = message;
            
            if (type === 'error') {
                statusMsg.style.color = '#f44336';
            } else if (type === 'success') {
                statusMsg.style.color = '#4CAF50';
            } else {
                statusMsg.style.color = 'white';
            }
            
            // Reset color after 3 seconds
            setTimeout(function() {
                statusMsg.style.color = 'white';
                statusMsg.textContent = 'Select a profile or create a new one';
            }, 3000);
        }

        // Add event listeners
        applyButton.addEventListener('click', function() {
            // Apply the current values and remember as last used
            const selectedIndex = parseInt(profileSelect.value);
            lastUsedProfileIndex = selectedIndex;
            GM_setValue('jeeLastUsedProfileIndex', lastUsedProfileIndex);
            
            const formRollInput = document.getElementById('ctl00_ContentPlaceHolder1_rollno');
            const formPassInput = document.getElementById('ctl00_ContentPlaceHolder1_Password1');
            
            if (formRollInput) {
                formRollInput.value = rollInput.value;
            }
            
            if (formPassInput) {
                formPassInput.value = passInput.value;
            }
            
            updateStatusMessage('Credentials applied successfully', 'success');
        });

        saveNewButton.addEventListener('click', function() {
            // Create a new profile
            const now = new Date();
            const timestamp = now.toISOString().replace('T', ' ').substr(0, 19);
            
            const newProfile = {
                name: nameInput.value || 'Profile ' + (credentialsList.length + 1),
                rollNumber: rollInput.value,
                encodedPassword: encode(passInput.value),
                timestamp: timestamp
            };
            
            // Add to list and save
            credentialsList.push(newProfile);
            GM_setValue('jeeCredentialsList', credentialsList);
            
            // Set as active and remember
            lastUsedProfileIndex = credentialsList.length - 1;
            GM_setValue('jeeLastUsedProfileIndex', lastUsedProfileIndex);
            activeCredentials = newProfile;
            
            // Add to dropdown
            const option = document.createElement('option');
            option.value = lastUsedProfileIndex;
            option.textContent = newProfile.name + ' (' + newProfile.rollNumber + ')';
            option.selected = true;
            profileSelect.appendChild(option);
            
            updateStatusMessage('New profile "' + newProfile.name + '" created', 'success');
        });

        updateButton.addEventListener('click', function() {
            const selectedIndex = parseInt(profileSelect.value);
            const now = new Date();
            const timestamp = now.toISOString().replace('T', ' ').substr(0, 19);
            
            // Update the profile
            credentialsList[selectedIndex] = {
                name: nameInput.value || credentialsList[selectedIndex].name,
                rollNumber: rollInput.value,
                encodedPassword: encode(passInput.value),
                timestamp: timestamp
            };
            
            // Update active credentials and remember
            activeCredentials = credentialsList[selectedIndex];
            lastUsedProfileIndex = selectedIndex;
            GM_setValue('jeeLastUsedProfileIndex', lastUsedProfileIndex);
            
            // Save changes
            GM_setValue('jeeCredentialsList', credentialsList);
            
            // Update dropdown
            profileSelect.options[selectedIndex].textContent = activeCredentials.name + ' (' + activeCredentials.rollNumber + ')';
            
            updateStatusMessage('Profile "' + activeCredentials.name + '" updated', 'success');
        });

        closeButton.addEventListener('click', function() {
            overlay.style.display = 'none';
            
            // Create a small button to show overlay again
            const showButton = document.createElement('button');
            showButton.textContent = 'Show Form';
            showButton.style.position = 'fixed';
            showButton.style.bottom = '10px';
            showButton.style.right = '10px';
            showButton.style.padding = '5px';
            showButton.style.backgroundColor = '#555';
            showButton.style.color = 'white';
            showButton.style.border = 'none';
            showButton.style.borderRadius = '5px';
            showButton.style.cursor = 'pointer';
            showButton.style.zIndex = '9998';
            
            showButton.addEventListener('click', function() {
                overlay.style.display = 'flex';
                showButton.remove();
            });
            
            document.body.appendChild(showButton);
        });

        // Append elements to profile section
        profileSection.appendChild(document.createTextNode('Select Profile: '));
        profileSection.appendChild(profileSelect);
        profileSection.appendChild(deleteBtn);

        // Append elements to form section
        formSection.appendChild(nameLabel);
        formSection.appendChild(nameInput);
        formSection.appendChild(document.createElement('br'));
        formSection.appendChild(document.createElement('br'));
        formSection.appendChild(rollLabel);
        formSection.appendChild(rollInput);
        formSection.appendChild(passLabel);
        formSection.appendChild(passInput);
        formSection.appendChild(togglePassBtn);

        // Append elements to button section
        buttonSection.appendChild(applyButton);
        buttonSection.appendChild(saveNewButton);
        buttonSection.appendChild(updateButton);
        buttonSection.appendChild(closeButton);

        // Append sections to overlay
        overlay.appendChild(profileSection);
        overlay.appendChild(document.createElement('hr'));
        overlay.appendChild(formSection);
        overlay.appendChild(buttonSection);
        overlay.appendChild(statusMsg);

        // Append overlay to body
        document.body.appendChild(overlay);
    }
})();