|
// ==UserScript== |
|
// @name mBank Saldo |
|
// @namespace https://gist.github.com/darkowic/f4454f06658ec2ef1e2dbe0d6e8648d2 |
|
// @version 0.1 |
|
// @description Wpływy - Wydatki = Saldo - skrypt dodaje pole "Saldo" w widoku histori transakcji dla serwisu transakcyjnego mbank.pl. |
|
// @author https://github.com/darkowic |
|
// @match https://online.mbank.pl/* |
|
// @icon https://www.google.com/s2/favicons?sz=64&domain=mbank.pl |
|
// @grant none |
|
// @downloadURL https://gist.github.com/darkowic/f4454f06658ec2ef1e2dbe0d6e8648d2/raw/mbank-saldo.user.js |
|
// ==/UserScript== |
|
|
|
(async function () { |
|
("use strict"); |
|
|
|
function getAmountNode(node) { |
|
return node.querySelector('[data-component="Amount"]'); |
|
} |
|
|
|
function getSummaryNodes() { |
|
const operationsCountNode = document.querySelector( |
|
'[testid="OperationsSummary:operationsCount"]' |
|
); |
|
return operationsCountNode.previousSibling; |
|
} |
|
|
|
function getAmount(node) { |
|
return parseFloat( |
|
getAmountNode(node).textContent.replaceAll(/\s/g, "").replaceAll(",", ".") |
|
); |
|
} |
|
|
|
function setAmount(node, amount) { |
|
const intPart = Math.trunc(amount); |
|
const decimalPart = amount - intPart; |
|
node.removeChild(node.firstChild); |
|
node.prepend( |
|
(intPart < 0 ? "-" : "") + |
|
Math.abs(intPart) |
|
// add non-breaking space between every 3 digits |
|
.toString() |
|
.split("") |
|
.reduce((acc, next, index, string) => { |
|
const indexFromEnd = string.length - index; |
|
if ( |
|
indexFromEnd !== string.length && |
|
indexFromEnd && |
|
indexFromEnd % 3 === 0 |
|
) { |
|
acc += "\xa0"; |
|
} |
|
return (acc += next); |
|
}, "") |
|
); |
|
const decimalPartNode = node.children[0]; |
|
decimalPartNode.removeChild(decimalPartNode.lastChild); |
|
decimalPartNode.append( |
|
Math.abs(decimalPart).toFixed(2).toString().slice(2) |
|
); |
|
} |
|
|
|
const TOTAL_NODE_ID = "script-total-node"; |
|
|
|
function createTotalNode(node) { |
|
const totalNode = node.cloneNode(true); |
|
totalNode.setAttribute("id", TOTAL_NODE_ID); |
|
totalNode.firstChild.textContent = "Saldo"; |
|
const amountNode = getAmountNode(totalNode); |
|
setAmount(amountNode, 0); |
|
return totalNode; |
|
} |
|
|
|
function getOrCreateTotalNode(parentNode) { |
|
const totalNode = document.getElementById(TOTAL_NODE_ID); |
|
|
|
if (totalNode) { |
|
return totalNode; |
|
} |
|
|
|
const newTotalNode = createTotalNode(parentNode.children[0]); |
|
parentNode.appendChild(newTotalNode); |
|
|
|
return newTotalNode; |
|
} |
|
|
|
function updateTotal() { |
|
const parent = getSummaryNodes(); |
|
const income = getAmount(parent.children[0]); |
|
const spending = getAmount(parent.children[1]); |
|
const totalNode = getOrCreateTotalNode(parent); |
|
|
|
const total = parseFloat(income - Math.abs(spending)).toFixed(2); |
|
|
|
setAmount(getAmountNode(totalNode), total); |
|
} |
|
|
|
async function waitForSummaryNode() { |
|
return new Promise((resolve) => { |
|
const observer = new MutationObserver((_, obs) => { |
|
try { |
|
const summaryNode = getSummaryNodes(); |
|
obs.disconnect(); |
|
resolve(summaryNode); |
|
} catch (e) { |
|
// waiting |
|
} |
|
}); |
|
|
|
observer.observe(document.getElementById("root"), { |
|
childList: true, |
|
subtree: true, |
|
}); |
|
}); |
|
} |
|
|
|
function watchChange(node, callback) { |
|
const observer = new MutationObserver(() => { |
|
callback(); |
|
}); |
|
|
|
observer.observe(node, { |
|
characterData: true, |
|
subtree: true, |
|
}); |
|
|
|
return observer; |
|
} |
|
|
|
function doesPathMatch(paths) { |
|
return paths.some((path) => window.location.pathname.startsWith(path)); |
|
} |
|
|
|
function runUnderPaths(paths, callback) { |
|
let isUnderCorrectPath = false; |
|
let cleanupSave = null; |
|
|
|
const observer = new MutationObserver(() => { |
|
if (doesPathMatch(paths)) { |
|
if (!isUnderCorrectPath) { |
|
callback().then((cleanup) => { |
|
if (isUnderCorrectPath) { |
|
cleanupSave = cleanup; |
|
} else { |
|
cleanup(); |
|
} |
|
}); |
|
} |
|
isUnderCorrectPath = true; |
|
} else { |
|
isUnderCorrectPath = false; |
|
if (cleanupSave) { |
|
cleanupSave(); |
|
cleanupSave = null; |
|
} |
|
} |
|
}); |
|
|
|
observer.observe(document.body, { |
|
childList: true, |
|
subtree: true, |
|
}); |
|
|
|
return observer; |
|
} |
|
|
|
runUnderPaths(["/history"], async () => { |
|
const summaryNode = await waitForSummaryNode(); |
|
|
|
updateTotal(); |
|
|
|
const observer1 = watchChange(summaryNode.children[0], updateTotal); |
|
const observer2 = watchChange(summaryNode.children[1], updateTotal); |
|
|
|
return () => { |
|
observer1.disconnect(); |
|
observer2.disconnect(); |
|
}; |
|
}); |
|
})(); |