convenience field focus, entering address data, a bit of jsdoc

This commit is contained in:
iw0 2024-02-15 20:29:38 +01:00
parent ffa5c60d1a
commit 46d4bd3597

View File

@ -1,15 +1,44 @@
"use strict"; "use strict";
// Put all the javascript code here, that you want to execute after page load. // Put all the javascript code here, that you want to execute after page load.
/**
*
* @typedef {Object} Stage
* @property {string} name
* @property {function.<Node|MutationRecord>} match
* @property {function.<Node|MutationRecord>} execute
* @property {('mutation'|'node'|undefined)} expects
*/
/**
* @type Stage
*/
let currentStage; let currentStage;
const settings = browser.storage.sync;
const _clickEv = () => { return new Event('click', { bubbles: true }) };
/** @param {string} s */
const $ = s => document.querySelector(s);
/**
* @param {Element} n
* @param {string} s
*/
const $$ = (n, s) => n.querySelector(s);
const pressKey = (...keys) => {
keys.forEach(k => {
let d = document.dispatchEvent;
d(new KeyboardEvent('keydown', { bubbles: true, key: k }));
d(new KeyboardEvent('keyup', { bubbles: true, key: k }));
})
}
/** @param {Node} node */
function executeStage(node) { function executeStage(node) {
if (node.nodeType === 1) { if (node.nodeType === 1) {
if (currentStage === undefined) { if (currentStage === undefined) {
currentStage = stages.shift(); currentStage = stages.shift();
} }
if (currentStage.match(node)) { if (currentStage.match(node)) {
console.log(currentStage.name, "matched"); console.log(currentStage.name, "matched: ", node);
console.log(currentStage.name, currentStage.execute(node) ? "executed" : "execution failed"); console.log(currentStage.name, currentStage.execute(node) ? "executed" : "execution failed");
if (stages.length > 0) { if (stages.length > 0) {
currentStage = stages.shift(); currentStage = stages.shift();
@ -17,32 +46,85 @@ function executeStage(node) {
observer.disconnect(); observer.disconnect();
} }
} else { } else {
console.log(currentStage.name, "did not match: ", node); console.log(currentStage.name, "did not match: ", mutation);
} }
} }
} }
let stages; let stages;
const personalDataConfigKeys = ["addr__appellation", "addr__firstName", "addr__surName", "addr__email", "addr__street", "addr__postcode", "addr__placename"]; const personalDataConfigKeys = [
"addr__appellation", "addr__title", "addr__firstName", "addr__surName",
"addr__email", "addr__street", "addr__postcode", "addr__placename"];
const bankDetailConfigKeys = ["pymt__iban", "pymt__bic"]; const bankDetailConfigKeys = ["pymt__iban", "pymt__bic"];
/**
* @param {Node} n
*/
function processSingleAddedNode(n) {
if (currentStage.match(n)) {
console.log(currentStage.name, "matched: ", n);
console.log(currentStage.name, currentStage.execute(n) ? "executed" : "execution failed");
nextStage();
} else {
console.log(currentStage.name, "did not match: ", n);
}
}
/**
*
* @param {MutationRecord[]} mutationList
* @param {MutationObserver} observer
*/
function processMutations(mutationList, observer) { function processMutations(mutationList, observer) {
if (currentStage === undefined) {
currentStage = stages.shift();
}
for (const mutation of mutationList) { for (const mutation of mutationList) {
if (mutation.type === "childList") { if (Object.keys(currentStage).includes('expects')) {
mutation.addedNodes.forEach(executeStage); if (currentStage.expects == 'mutation') {
if (currentStage.match(mutation)) {
console.log(currentStage.name, "matched: ", mutation);
console.log(currentStage.name, currentStage.execute(mutation) ? "executed" : "execution failed");
nextStage();
} else {
console.log(currentStage.name, "did not match: ", mutation);
}
} else if (currentStage.expects == 'node' && mutation.type === "childList") {
mutation.addedNodes.forEach(processSingleAddedNode);
}
} else if (mutation.type === "childList") {
//last resort
mutation.addedNodes.forEach(n => {
if (n.nodeType === Node.ELEMENT_NODE) {
processSingleAddedNode(n);
} else if (!([Node.COMMENT_NODE, Node.TEXT_NODE].includes(n.nodeType))) {
console.log("skipping node", n);
}
});
} else {
console.log("err: could not dispatch mutation", mutation);
} }
} }
} }
let clickThroughForms; let clickThroughForms;
let hasConfiguredBankDetails, hasConfiguredPersonalData; let hasConfiguredBankDetails, hasConfiguredPersonalData;
let observer = new MutationObserver(processMutations); let observer = new MutationObserver(processMutations);
function nextStage() {
if (stages.length > 0) {
currentStage = stages.shift();
} else {
observer.disconnect();
}
}
const addObserver = () => { const addObserver = () => {
browser.storage.sync.get(['autocontinue', 'enable'].concat(personalDataConfigKeys, bankDetailConfigKeys)).then(v => { settings.get(
clickThroughForms = !!v.autocontinue; ['autocontinue', 'enable'].concat(personalDataConfigKeys, bankDetailConfigKeys)
hasConfiguredPersonalData = Object.keys(v).filter(k => personalDataConfigKeys.includes(k)).length > 0; ).then(userSettings => {
hasConfiguredBankDetails = Object.keys(v).filter(k => bankDetailConfigKeys.includes(k)).length > 0; clickThroughForms = !!userSettings.autocontinue;
if (!!v.enable) { hasConfiguredPersonalData = Object.keys(userSettings).filter(k => personalDataConfigKeys.includes(k)).length > 0;
hasConfiguredBankDetails = Object.keys(userSettings).filter(k => bankDetailConfigKeys.includes(k)).length > 0;
if (!!userSettings.enable) {
observer.observe(document.body, { observer.observe(document.body, {
childList: true, subtree: true childList: true, subtree: true
}) })
@ -53,72 +135,54 @@ const addObserver = () => {
addObserver(); addObserver();
function fillTextInput(parentNode, selector, value) { function fillTextInput(parentNode, selector, value) {
const node = parentNode.querySelector(selector); const node = $$(parentNode, selector);
node.value = value; node.value = value;
node.dispatchEvent(new Event("input", { bubbles: true })); // node.focus();
// node.dispatchEvent(new FocusEvent("focus"));
// node.dispatchEvent(new FocusEvent("focusin", { bubbles: true }));
node.dispatchEvent(new Event("input", { bubbles: true, inputType: "insertFromPaste", data: value }));
// node.dispatchEvent(new FocusEvent("focusout", { bubbles: true }));
} }
const startClaim = { const startClaim = {
name: "startClaim", name: "startClaim",
match: node => node.classList.contains("antrag-starten"), match: node => node.classList.contains("main-layout"),
execute: node => { execute: node => {
const startenButton = node.querySelector('button.test-antrag-starten-button'); const startenButton = node.querySelector('button.test-antrag-starten-button');
if (startenButton instanceof HTMLButtonElement) { if (startenButton instanceof HTMLButtonElement) {
startenButton.dispatchEvent(new Event('click', { bubbles: true })); startenButton.dispatchEvent(_clickEv());
return true; return true;
} }
return false; return false;
} }
} }
function fillBcnum(bcNumberInput) {
browser.storage.sync.get('bcnum').then(v => {
let bcNum = v.bcnum || null;
if (bcNum !== null && bcNum !== "") {
bcNumberInput.value = bcNum;
bcNumberInput.dispatchEvent(new Event('input', { bubbles: true }));
return true;
}
})
return false;
}
function fillBday(birthdayInput) {
browser.storage.sync.get('bday').then(v => {
const bDay = v.bday || null;
if (bDay !== null && bDay !== "") {
birthdayInput.value = bDay;
birthdayInput.dispatchEvent(new Event('input', { bubbles: true }));
return true;
}
})
return false;
}
const fillData = { const fillData = {
name: "fillData", name: "fillData",
match: node => node.classList.contains("fahrgastrechte-bahn-card-auswahl"), match: node => node.classList.contains("fahrgastrechte-bahn-card-auswahl"),
execute: node => { execute: node => {
let bcNumField, bdayField; settings.get(["bcnum", "bday"]).then(cfg => {
node.querySelectorAll('input').forEach(e => { for (const [k, v] of Object.entries(cfg)) {
if (e.name === "fahrgastrechte-bahn-card-nummer") { if (k == "bcnum" && !!v) {
bcNumField = e; fillTextInput(node, '#fahrgastrechte-bahn-card-auswahl-nummer--db-web-text-input', v);
} else if (e.name === "fahrgastrechte-bahn-card-auswahl-geburts-datum") { }
bdayField = e; if (k == "bday" && !!v) {
fillTextInput(node, "#fahrgastrechte-bahn-card-auswahl-geburts-datum--db-web-text-input", v);
}
} }
}) });
fillBcnum(bcNumField);
fillBday(bdayField);
return true; return true;
} }
} }
const clickContinue = { const clickContinue = {
name: "clickContinue", name: "clickContinue",
match: () => true, match: () => $('#fahrgastrechte-bahn-card-auswahl-geburts-datum--db-web-text-input').value !== "",
execute: e => { execute: e => {
const continueButton = document.querySelector('.fahrgastrechte-bahn-card-auswahl button.fahrgastrechte-continue-button'); const continueButton = $('.fahrgastrechte-bahn-card-auswahl button.fahrgastrechte-continue-button');
if (continueButton instanceof Element) { if (continueButton instanceof Element) {
continueButton.dispatchEvent(new Event('click')); continueButton.dispatchEvent(_clickEv());
return true; return true;
} }
return false; return false;
@ -130,7 +194,7 @@ const iWasDelayed = {
name: "iWasDelayed", name: "iWasDelayed",
match: node => node.classList.contains("antrags-typ-auswahl") && clickThroughForms, match: node => node.classList.contains("antrags-typ-auswahl") && clickThroughForms,
execute: node => { execute: node => {
const delay = node.querySelector('input#antragstyp-verspaetung'); const delay = $$(node, 'input#antragstyp-verspaetung');
if (delay instanceof HTMLInputElement) { if (delay instanceof HTMLInputElement) {
delay.dispatchEvent(new Event('change')); delay.dispatchEvent(new Event('change'));
return true; return true;
@ -148,20 +212,118 @@ const moreThan60Minutes = {
const continueToForm = { const continueToForm = {
name: "continueToForm", name: "continueToForm",
match: node => node.classList.contains("verspaetung-bestaetigung") && clickThroughForms, match: node => node.classList.contains("verspaetung-bestaetigung") && clickThroughForms,
execute: node => node.querySelector('button.fahrgastrechte-continue-button').dispatchEvent(new Event('click', { bubbles: true })) execute: node => $$(node, 'button.fahrgastrechte-continue-button').dispatchEvent(_clickEv())
} }
const enterPersonalData = { const focusDepartureInput = {
name: "enterPersonalData", name: "focusDepartureInput",
match: node => node.classList.contains("fahrplan") && clickThroughForms,
execute: node => {
const depInput = $$(node, '.fahrplan__start .fahrplan__haltestelle input');
const obs = new IntersectionObserver((entries, intObserver) => {
if (!(entries.some(e => e.isIntersecting))) return false;
console.log("observer fired:", entries);
depInput.focus();
intObserver.disconnect();
}, { threshold: 1 });
obs.observe(depInput);
return true;
}
}
const jumpToTimeInput = {
name: "jumpToTimeInput",
match: node => node.classList.contains("ankunft-zeit") && clickThroughForms,
execute: node => {
$$(node, '#fahrgastrechte-ankunft-uhrzeit--db-web-text-input').focus();
return true;
}
}
const activateAppellationDropdown = {
name: "activateAppellationDropdown",
match: node => node.classList.contains("persoenlicheangaben") && hasConfiguredPersonalData, match: node => node.classList.contains("persoenlicheangaben") && hasConfiguredPersonalData,
execute: node => { execute: node => {
browser.storage.sync.get(personalDataConfigKeys).then(foundKeys => { settings.get("addr__appellation").then(foundKeys => {
console.log("storage returned", foundKeys); console.log("storage returned", foundKeys);
//TODO the dropdowns are crazy if (Object.keys(foundKeys).includes("addr__appellation")) {
// if (foundKeys.keys().contains("addr__appellation")){ const selectList = node.querySelector('.test-name-anrede.db-web-select');
// let dropDownSelectList = node.querySelector('.test-name-anrede ul.db-web-select-list'); selectList.querySelector('button').dispatchEvent(_clickEv());
}
});
return true;
}
}
/**@type Stage */
const enterAppellationAndActivateTitleDropdown = {
name: "enterAppellationAndActivateTitleDropdown",
match: node => node.classList.contains("db-web-dropdown-outer-container") && node.querySelector(".db-web-select-list") !== null,
execute: node => {
settings.get("addr__appellation").then(foundKeys => {
console.log("storage returned", foundKeys);
if (Object.keys(foundKeys).includes("addr__appellation")) {
const selectList = $$(node, "ul");
selectList.querySelector(`[data-value=${foundKeys.addr__appellation}]`).dispatchEvent(_clickEv());
} else {
node.parentElement.parentElement.parentElement.querySelector("button").dispatchEvent(_clickEv());
}
$('.test-name-titel.db-web-select button').dispatchEvent(_clickEv());
});
return true;
}
}
// } const enterTitle = {
name: "enterTitle",
/**@param {Element} node */
match: node => node.classList.contains("db-web-dropdown-outer-container") && node.querySelector(".db-web-select-list") !== null,
/**@param {Element} node */
execute: node => {
settings.get("addr__title").then(foundKeys => {
console.log("storage returned", foundKeys);
if (Object.keys(foundKeys).includes("addr__title")) {
const selectList = $$(node, "ul");
selectList.querySelector(`[data-value=${foundKeys.addr__title}]`).dispatchEvent(_clickEv());
} else {
node.parentElement.parentElement.parentElement.querySelector("button").dispatchEvent(_clickEv());
}
});
return true;
}
}
const enterFirstName = {
name: "enterFirstName",
expects: "mutation",
match: mutation => {
return mutation.target.parentNode.parentNode.classList.contains("test-name-titel") &&
Array.from(mutation.removedNodes).some(
n => n.nodeType === Node.ELEMENT_NODE &&
n.classList.contains("db-web-dropdown-outer-container"))
},
execute: () => {
let node = document;
settings.get()
}
}
/**
* @type Stage
*/
const enterTextPersonalData = {
name: "enterTextPersonalData",
expects: 'mutation',
/** @param {MutationRecord} mutation */
match: mutation => {
return (mutation.target.parentNode.parentNode.classList.contains("test-name-titel") &&
Array.from(mutation.removedNodes).some(
n => n.nodeType === Node.ELEMENT_NODE &&
n.classList.contains("db-web-dropdown-outer-container")))
},
execute: () => {
let node = document;
let delay = 100;
settings.get(personalDataConfigKeys).then(foundKeys => {
console.log("storage returned", foundKeys);
const configKey_Selector = { const configKey_Selector = {
"addr__firstName": ".test-name-vorname input", "addr__firstName": ".test-name-vorname input",
"addr__surName": ".test-name-nachname input", "addr__surName": ".test-name-nachname input",
@ -170,32 +332,46 @@ const enterPersonalData = {
"addr__postcode": ".test-adresse-plz input", "addr__postcode": ".test-adresse-plz input",
"addr__placename": ".test-adresse-ort input" "addr__placename": ".test-adresse-ort input"
} }
for (const [k, v] of Object.entries(foundKeys)) { for (const [k, v] of Object.entries(foundKeys)) {
if (Object.keys(configKey_Selector).includes(k)) { if (Object.keys(configKey_Selector).includes(k)) {
//TODO WIP this only works on some fields //TODO WIP this only works on some fields
console.log("filling", configKey_Selector, "with", v); console.log("filling", configKey_Selector[k], "with", v);
fillTextInput(node, configKey_Selector[k], v); setTimeout(() => {
fillTextInput(node, configKey_Selector[k], v)
}, delay);
delay += 100;
} else { } else {
console.log("no selector found for config key", k); console.log("no selector found for config key", k);
} }
} }
const continueBtn = document.querySelector(".fahrgastrechte-editable__buttons button.fahrgastrechte-continue-button"); setTimeout(() => {
if (continueBtn.querySelector("span span.db-web-button__label").textContent == "OK, weiter" && clickThroughForms) { const continueBtn = $(".fahrgastrechte-editable__buttons button.fahrgastrechte-continue-button");
continueBtn.dispatchEvent(new Event("click", { bubbles: true })); continueBtn.focus();
} continueBtn.dispatchEvent(_clickEv());
}, delay);
return true; return true;
}) })
}, },
} }
/** @type Stage */
const continueToPayout = {
name: "continueToPayout",
expects: "mutation",
match: () => Object.is(document.activeElement, $(".fahrgastrechte-editable__buttons button.fahrgastrechte-continue-button"))
,
execute: () => document.activeElement.dispatchEvent(_clickEv()),
}
const enterPaymentDetails = { const enterPaymentDetails = {
name: "enterPaymentDetails", name: "enterPaymentDetails",
match: node => node.classList.contains("entschaedigung") && hasConfiguredBankDetails, match: node => node.querySelector(".entschaedigung") &&
hasConfiguredBankDetails,
execute: node => { execute: node => {
node.querySelector('#ueberweisung').dispatchEvent(new Event('change')); const xfrRadio = node.querySelector('#ueberweisung');
browser.storage.sync.get(bankDetailConfigKeys).then(results => { xfrRadio.dispatchEvent(new Event('change'));
settings.get(bankDetailConfigKeys).then(results => {
console.log(results); console.log(results);
for (const [k, v] of Object.entries(results)) { for (const [k, v] of Object.entries(results)) {
switch (k) { switch (k) {
@ -211,8 +387,11 @@ const enterPaymentDetails = {
true true
}, },
} }
const defaultStages = [ const defaultStages = [
startClaim, fillData, clickContinue, iWasDelayed, moreThan60Minutes, continueToForm, enterPersonalData, enterPaymentDetails startClaim, fillData, clickContinue,
iWasDelayed, moreThan60Minutes, continueToForm, focusDepartureInput, jumpToTimeInput,
activateAppellationDropdown, enterAppellationAndActivateTitleDropdown, enterTitle,
enterTextPersonalData, /* continueToPayout */, enterPaymentDetails
]; ];
stages = defaultStages; /** @type Stage[] */
stages = defaultStages;