Skip to content

Instantly share code, notes, and snippets.

@pasrom
Last active February 7, 2025 08:16
Show Gist options
  • Save pasrom/46ee95dd801a431de76b9f67480fb4e5 to your computer and use it in GitHub Desktop.
Save pasrom/46ee95dd801a431de76b9f67480fb4e5 to your computer and use it in GitHub Desktop.
Automates fields for account information, leave request, and PZE correction requests
// ==UserScript==
// @name Account Information and Leave Request Automation
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Automates fields for account information, leave request, and PZE correction requests
// @author Roman Passler
// @match https://*/APplusProd7/time/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @require https://cdnjs.cloudflare.com/ajax/libs/sjcl/1.0.8/sjcl.min.js
// ==/UserScript==
(function () {
'use strict';
// Store timestamps for Kommen and Gehen
let kommenTime = GM_getValue("kommenTime", "");
let gehenTime = GM_getValue("gehenTime", "");
let encryptionKey = getEncryptionKey();
let username, password;
try {
username = loadOrPrompt("user", "Enter your username:");
password = loadOrPrompt("password", "Enter your password:");
} catch (error) {
console.error("Failed to load stored user and credentials, prompting for new user and password.");
alert("Decryption error! Please enter a new user and password.");
updateCredential("user", "Enter a new username:", true);
updateCredential("password", "Enter a new password:", true);
password = loadOrPrompt("password", "Enter your password:");
}
// Register menu commands
GM_registerMenuCommand("Reset Encryption Key", resetEncryptionKey);
GM_registerMenuCommand("Change Username", () => updateCredential("user", "Enter a new username:", true));
GM_registerMenuCommand("Change Password", () => updateCredential("password", "Enter a new password:", true));
setupStatusField();
updateStatus();
// Event listener for button clicks
document.addEventListener('click', async (event) => {
const checkInButton = event.target.closest('button[accesskey="K"]');
const checkOutButton = event.target.closest('button[accesskey="G"]');
const accountInfoButton = event.target.closest('button[accesskey="I"]');
const leaveRequestButton = event.target.closest('button[accesskey="Q"]');
const pzeCorrectionButton = event.target.closest('button');
if (checkInButton) {
console.log("Check-In button clicked!");
handleFormAutomation(username, password, false);
if (!kommenTime) {
kommenTime = new Date().toLocaleString();
GM_setValue("kommenTime", kommenTime);
console.log(`Kommen clicked. Time recorded: ${kommenTime}`);
} else {
await handleKommenClick();
GM_setValue("kommenTime", kommenTime);
console.log(`Kommen was already clicked at: ${kommenTime}`);
}
gehenTime = ""; // Reset Gehen time when Kommen is clicked
GM_setValue("gehenTime", gehenTime);
updateStatus();
}
if (checkOutButton) {
console.log("Check-Out button clicked!");
handleFormAutomation(username, password, false);
if (!gehenTime) {
gehenTime = new Date().toLocaleString();
GM_setValue("gehenTime", gehenTime);
console.log(`Gehen clicked. Time recorded: ${gehenTime}`);
}
kommenTime = ""
GM_setValue("kommenTime", kommenTime);
updateStatus();
}
if (accountInfoButton) {
console.log("Account Information button clicked!");
handleFormAutomation(username, password, false);
}
if (leaveRequestButton) {
console.log("Leave Request button clicked!");
handleFormAutomation(username, password, true);
}
if (pzeCorrectionButton && pzeCorrectionButton.textContent.trim() === "PZE-Korrekturantrag") {
console.log("PZE Correction Request button clicked!");
handleFormAutomation(username, password, true);
}
});
function setupStatusField() {
const headerCell = document.getElementById('HEADER');
if (!headerCell) {
console.error('Header cell not found.');
return;
}
if (!document.getElementById('statusField')) {
const statusField = document.createElement('span');
statusField.id = 'statusField';
statusField.textContent = "Status Field"; // Set initial text
Object.assign(statusField.style, {
marginLeft: '10px', padding: '5px 10px',
fontSize: '14px', fontFamily: 'Arial, sans-serif', display: 'inline-block'
});
headerCell.appendChild(statusField);
}
}
// Observe changes in the document to re-add the field if removed
const observer = new MutationObserver(() => {
setupStatusField();
updateStatus(); // Ensure status is updated whenever the page changes
});
// Start observing the entire document for changes
observer.observe(document.body, { childList: true, subtree: true });
function extractTimeFromTextarea() {
const textarea = document.querySelector('#DISPLAY');
return textarea?.value.match(/seit (\d{2}\.\d{2}\.\d{4} \d{2}:\d{2}:\d{2})/)?.[1] || null;
}
// Function to wait for the time extraction
function waitForExtractTime(timeout = 200) {
return new Promise((resolve) => {
setTimeout(() => {
const extractedTime = extractTimeFromTextarea();
resolve(extractedTime); // Resolve the promise with the extracted time
}, timeout);
});
}
// Async handler for "Kommen" button clicks
async function handleKommenClick() {
console.log("Waiting for time extraction...");
const extractedTime = await waitForExtractTime(50); // Wait for 50 ms
if (extractedTime) {
kommenTime = extractedTime;
GM_setValue("kommenTime", kommenTime);
console.log(`Extracted time: ${kommenTime}`);
} else {
kommenTime = new Date().toLocaleString();
GM_setValue("kommenTime", kommenTime);
console.log(`No time found. Using current time: ${kommenTime}`);
}
}
// Function to update the status field
function updateStatus() {
const statusField = document.getElementById('statusField');
if (!statusField) return;
if (kommenTime && !gehenTime) {
statusField.textContent = `Kommen clicked at: ${kommenTime}`;
} else if (gehenTime) {
statusField.textContent = `Gehen clicked at: ${gehenTime}`;
} else {
statusField.textContent = "No actions recorded yet.";
}
}
// Automation function for the form
function handleFormAutomation(accountValue, passwordValue, isPasswordRequired) {
setTimeout(() => {
const accountField = document.querySelector('input#AUSWEIS');
if (accountField) {
accountField.value = accountValue;
console.log(`Account field value set to ${accountValue}`);
} else {
console.error("Account field not found!");
}
const passwordField = document.querySelector('input#PASSWORT');
if (passwordField) {
if (isPasswordRequired && passwordValue !== "") {
passwordField.value = passwordValue;
console.log("Password field value set.");
} else if (isPasswordRequired) {
console.warn("Password required but no value provided.");
} else {
console.log("Password field left empty.");
}
} else {
console.error("Password field not found!");
}
const submitButton = document.querySelector('button#melden');
if (submitButton) {
console.log("Submit button found, clicking...");
submitButton.click();
} else {
console.error("Submit button not found!");
}
}, 5);
}
// Helper functions
function loadOrPrompt(key, message) {
let encryptedData = GM_getValue(key, "");
if (!encryptedData) {
let value = prompt(message, "");
if (value) {
GM_setValue(key, encrypt(value));
}
return value;
}
return decrypt(encryptedData);
}
function resetEncryptionKey() {
encryptionKey = generateEncryptionKey();
GM_setValue("encryptionKey", encryptionKey);
console.log("Encryption key reset. Re-enter username and password.");
updateCredential("user", "Enter a new username:");
updateCredential("password", "Enter a new password:");
location.reload();
}
function updateCredential(key, message, reload = false) {
let value = prompt(message, "");
if (value) {
GM_setValue(key, encrypt(value));
console.log(`${key} successfully updated.`);
if (reload) location.reload();
}
}
function encrypt(data) {
return sjcl.encrypt(encryptionKey, data);
}
function decrypt(data) {
return sjcl.decrypt(encryptionKey, data);
}
function generateEncryptionKey() {
const array = new Uint8Array(16);
window.crypto.getRandomValues(array);
return Array.from(array, byte => byte.toString(16).padStart(2, "0")).join("");
}
function getEncryptionKey() {
let key = GM_getValue("encryptionKey", "");
if (!key) {
key = generateEncryptionKey();
GM_setValue("encryptionKey", key);
}
return key;
}
})();
@pasrom
Copy link
Author

pasrom commented Feb 6, 2025

Account Automation Script for APplus Terminal

This Tampermonkey script automates account login, leave requests, and PZE correction requests in the APplus terminal, making check-ins and check-outs faster and more efficient.

🔧 Installation & Setup

Step 1: Install Tampermonkey

To run this script, install Tampermonkey, a browser extension for managing custom scripts:

Step 2: Install the Script

  1. Open the script in GitHub.
  2. Click the “Raw” button to view the plain script.
  3. Tampermonkey will prompt you to install it. Click “Install”.

Step 3: Initial Setup

  1. Visit the APplus terminal in your browser
  2. A prompt will ask for your username – enter it and click OK.
  3. A second prompt will ask for your password – enter it and click OK.
  4. The script is now configured and ready to use.

🚀 How to Use

  • The script automates login fields and records check-in/check-out timestamps.
  • Additional options are available in the Tampermonkey menu:
    ✅ Reset Encryption Key
    ✅ Change Username
    ✅ Change Password

⚠️ Troubleshooting

  • If the script does not work correctly, reset the encryption key via Tampermonkey and re-enter credentials.
  • Ensure Tampermonkey is enabled and the script is active.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment