Rabobank Internetbankieren auto-input

Quickly select from multiple bank acccounts in the Rabobank login and payment pages

当前为 2014-09-24 提交的版本,查看 最新版本

// ==UserScript==
// @name                Rabobank Internetbankieren auto-input
// @namespace           FelixAkk
// @description         Quickly select from multiple bank acccounts in the Rabobank login and payment pages
// @include             https://bankieren.rabobank.nl/klanten*
// @include             https://bankieren.rabobank.nl/rib/*
// @include             https://betalen.rabobank.nl/ide/qslo*
// @include             https://betalen.rabobank.nl/ideal-betaling/*
// @grant               GM_setValue
// @grant               GM_getValue
// @grant               GM_listValues
// @grant               GM_deleteValue
// @grant               GM_addStyle
// @grant               GM_registerMenuCommand
// @require             http://ajax.googleapis.com/ajax/libs/jquery/2.0.2/jquery.min.js
// @copyright           2014, Felix Akkermans
// @copyright           2014, Matthijs Kooijman ([email protected])
// @license             The MIT license; http://opensource.org/licenses/MIT
// @homepageURL         https://github.com/matthijskooijman/greasemonkey-rabobank-autoinput
// @version             2.1.0
// ==/UserScript==

/*jslint undef: true, vars: true, newcap: true, maxerr: 50, maxlen: 200, indent: 4 */

/**
 * The `jslint` comment is an annotation with flags for JSLint.com and alike tools. Some explanation on the options:
 *
 * browser: true        Will make it accept use of document, window, JSON and such without declaration
 * vars: true           Tollerate many seperate variable declarations
 * maxlen: 200          Set the maximum line length to 200 characters
 * undef: true          Tollerate usage of undefined functions. To accept GM_* functions.
 * plusplus: true       Tollerate ++ and -- operators. Just shush already, I just them responsibly.
 */

/**
 * The global annotation is also for JSLint, which will tell it not to whine about the declaration
 */
/*global GM_setValue, GM_getValue, GM_listValues, GM_deleteValue, window, document */

// http://stackoverflow.com/questions/1335851/what-does-use-strict-do-in-javascript-and-what-is-the-reasoning-behind-it
(function () {
	"use strict";
	// --------------- General settings -------------------
	// tweak it to your own likings (script may malfunction on bad values)

	// Want me to fill in your default/primary account automatically as a page loads?
	var autoFillDefault = true,
		// Change to the account you want to select from the accountsArray (start counting from zero).
		defaultAccount = 0,
		// Time in miliseconds to wait for Rabobank's native JavaScript that's ran on DOM-ready to finish.
		// Increase this if you see your account selected but not filled in.
		autoFillDelay = 1000,
		// Optionally you can also choose your account through the GreaseMonkey command menu (off by default)
		GMMenu = false,
		// Want me to remove that puny original Rabobank 'remember my card' checkbox?
		// It's bugged, and I just made it obsolete... Yeah, that's what I though :)
		removeOriginalRemember = true;

	// ---------------- CSS/Stylesheet --------------------

	var stylesheet =
	'div#account_selection {'+
	'    margin-bottom: 20px;'+
	'    margin-left: 196px;'+
	'    margin-right: 196px;'+
	'    width: 528px;'+
	'}'+
	'div#account_selection table#accounts {'+
	'    background: none repeat scroll 0 0 White;'+
	'    border: 1px solid #CCCCCC;'+
	'    margin: 10px;'+
	'    padding: 5px;'+
	'    width: 96%;'+
	'}'+
	'table#accounts th {'+
	'    text-align: left;'+
	'}'+
	'table#accounts td.account_name label {'+
	'    display: inline-block;'+
	'    max-width: 200px;'+
	'    overflow: hidden;'+
	'    text-overflow: ellipsis;'+
	'    vertical-align: middle;'+
	'    white-space: nowrap;'+
	'    word-wrap: normal;'+
	'}'+
	'table#accounts td.account_name label {'+
	'}'+
	'a.edit {'+
	'    background: no-repeat scroll 0 0 transparent;'+
	'    display: block;'+
	'    margin: 10px;'+
	'    padding-left: 25px;'+
	'}'+
	'a.edit.enter {'+
	'    background-image: url("http://www.famfamfam.com/lab/icons/silk/icons/page_white_edit.png");'+
	'}'+
	'a.edit.exit {'+
	'    background-image: url("http://www.famfamfam.com/lab/icons/silk/icons/page_white_put.png");'+
	'}'+
	'a.account.add, a.account.delete {'+
	'    background: no-repeat scroll 0 0 transparent;'+
	'    display: inline-block;'+
	'    height: 16px;'+
	'    opacity: 0.3;'+
	'    vertical-align: text-bottom;'+
	'    width: 16px;'+
	'}'+
	'a.account.add:hover, a.account.delete:hover {'+
	'    opacity: 1;'+
	'}'+
	'a.account.add {'+
	'    background-image: url("http://www.famfamfam.com/lab/icons/silk/icons/add.png");'+
	'}'+
	'a.account.delete {'+
	'    background-image: url("http://www.famfamfam.com/lab/icons/silk/icons/delete.png");'+
	'}';

	// Construct selection panel DOM
	var selectionPanel =
	// I'll employ the styles of the Rabobank CSS classes .imgstripe, .newpanel to make it look identical.
	'<div id="account_selection" class="imgstripe newpanel">'+
	'  <form>'+
	'    <table id="accounts">'+
	// Static table header
	'      <thead>'+
	'        <tr>'+
	'          <th>Selecteer uw Rabobank rekening</th>'+
	'          <th>Rekening nummer</th>'+
	'          <th>Kaart nummer</th>'+
	'        </tr>'+
	'      </thead>'+
	'      <tbody></tbody>'+
	'    </table>'+
	'  <form>'+
	// The edit button
	'  <a href="#" class="edit enter">Rekeningen instellen...</a>'+
	'  <a href="#" class="edit exit">Veranderingen opslaan</a>'+
	'</div>'
	selectionPanel = $(selectionPanel);

	// Account object
	function Account(descr, acc, card) {
		this.description = descr;
		this.number = acc;
		this.cardNumber = card;
	}

	// Define whether the page is in the editing stage
	var editing = false;
	// Example accounts
	var examples = [
		new Account("Bijv. Privé coulante rekening", 123456789, 1234),
		new Account("Bijv. Spaar rekening", 123456789, 1234),
		new Account("Bijv. Zakelijke rekening", 123456789, 1234)
	];

	// Prefetch storage
	var accountsJSON = GM_getValue('accountsJSON');
	// Actual accounts
	var accountsArray;
	// If we have no values stored yet (first run), initialize the storage with example values
	if (accountsJSON === undefined) {
		GM_setValue('accountsJSON', JSON.stringify(examples));
		accountsArray = examples;
	} else {
		// Else load the bank accounts
		accountsArray = JSON.parse(accountsJSON);
	}

	// Utility function that takes a bank account number and returns it seperated by points like on the Rabobank cards
	function numberFormat(account) {
		var nr = String(account);
		return nr.substring(0, 4) + '.' + nr.substring(4, 6) + '.' + nr.substring(6);
	}

	// Build accounts table contents (the rows), depending on whether we're editing (true) or selecting (false)
	function constructTable() {
		var t = $('#accounts tbody');
		t.empty();
		for (var idx = 0; idx < accountsArray.length; idx++) {
			if (editing)
				t.append(constructEditableRow(accountsArray[idx]));
			else
				t.append(constructSelectableRow(idx, accountsArray[idx]));
		}
		if (editing)
			$('<tr><td colspan="4"><a href="#" class="account add"/></td></tr>').on('click', addAccount).appendTo(t);
	}

	function constructSelectableRow(idx, acc) {
		return $('<tr class="selectable-account"/>').append(
			$('<td/>').append(
				$('<input type="radio" name="account" />'),
				$('<label/>').text(acc.description)
			),
			$('<td/>').append($('<label/>').text(acc.number)),
			$('<td/>').append($('<label/>').text(acc.cardNumber))
		).on('click', function() { selectAccount(idx); });
	}

	function constructEditableRow(acc) {
		return $('<tr class="editable-account"/>').append(
			$('<td/>').append($('<input type="text" />').attr('value', acc.description)),
			$('<td/>').append($('<input type="text" size="9" maxlength="9"/>').attr('value', acc.number)),
			$('<td/>').append($('<input type="text" size="4" maxlength="4"/>').attr('value', acc.cardNumber)),
			$('<td/>').append($('<a href="#" class="account delete"/>').on('click', deleteAccount))
		);
	}

	function accountFromRow(row) {
		var inputs = $('input', row);
		return new Account(inputs[0].value, inputs[1].value, inputs[2].value);
	}

	// Define event handling function for selecting an account; fills in the account details
	function selectAccount(idx) {
		$('#AuthIdv4').prop('value', accountsArray[idx].number);
		$('#AuthBpasNrv4').prop('value', accountsArray[idx].cardNumber);
		// Login pages have AuthCdv4, sign pages have SignCdv4.
		// Focuse whatever one is available.
		$('#AuthCdv4, #SignCdv4').focus();
		$('#accounts input[type="radio"]')[idx].checked = true;
	}

	// Define event handling function for deleting an account/row in the accounts table when editing accounts
	function deleteAccount() {
		$(this).closest('tr').remove();
	}

	// Define event handling function for inserting an account/row in the accounts table after a given index (when editing accounts)
	function addAccount(accountIdx) {
		var row = constructEditableRow(new Account());
		row.insertBefore($('#accounts tbody tr').last());
	}

	// Event handler for the save changes button
	function saveChanges() {
		accountsArray = []; // new array, clean slate
		// Fill array
		$('#accounts tbody tr').slice(0, -1).each(function(idx, row) {
			accountsArray.push(accountFromRow(row));
		});
		// Save it all
		GM_setValue('accountsJSON', JSON.stringify(accountsArray));
		setEditMode(false);
	}

	// Function to add an menu item to the GreaseMonkey command item to select your account.
	// This function is necessary to get 'idx' to be a seperate variable at runtime instead of the end value of the 'idx' variable of the insertPanel function.
	function addGMMenuItem(idx) {
		// Format caption like '1. Account description (12345689 : 1234)'
		var caption = (idx + 1) + '. ' + accountsArray[idx].description + '  (' + numberFormat(accountsArray[idx].number) + ' : ' + accountsArray[idx].cardNumber + ')';
		// Have to wrap the select account function in an anonymous function to create a closure with the index parameter bound
		var handler = function () { selectAccount(idx); };
		GM_registerMenuCommand(caption, handler);
	}

	function setEditMode(value) {
		// Update edit/save links
		$("a.edit.enter", selectionPanel).toggle(!value);
		$("a.edit.exit", selectionPanel).toggle(value);
		// Remember new edit mode
		editing = value;
		// Reconstruct table
		constructTable();
	}

	function initAccountsPanel() {
		$("a.edit.enter", selectionPanel).on("click", function() { setEditMode(true)});
		$("a.edit.exit", selectionPanel).on("click", function() { saveChanges()});
		setEditMode(false);
	}

	function initialize() {
		// Load the defined custom styles
		GM_addStyle(stylesheet);

		// Insert and fill the custom account selection panel
		var container = $('#brt_content-section'); // Random reader
		if (container.length == 0)
			container = $('#loginform').parent().parent(); // Rabo Scanner
		container.prepend(selectionPanel);
		initAccountsPanel(editing);

		if (GMMenu) {
			for (var idx = 0; idx < accountsArray.length; idx++) {
				addGMMenuItem(idx);
			}
		}

		// Want me to remove that puny original Rabobank 'remember my card' checkbox? It's bugged, and I just made it obsolete... Yeah, that's what I though :)
		if (removeOriginalRemember) {
			$('input[type="checkbox"]#brtcheck01').parent().hide();
		}

		// And for good measure, we may select the default/primary straight away.
		// This basically just fires the event listener function without setting the it's this.id attribute.
		if (autoFillDefault) {
			// We have to wait a little while to avoid conflict with Rabobank's native JavaScript ran on DOM-ready
			window.setTimeout(function() { selectAccount(defaultAccount); }, autoFillDelay);
		}
	}
	/**
	/* Run only when script is of use, i.e.;
	/* if we're on a Rabobank page where we have the kind of form we can input something usefull into.
	 */
	if(
		$('#brt_form, #loginform').length != 0 &&
		$('#AuthIdv4').length != 0 &&
		$('#AuthBpasNrv4').length != 0
	) {
		initialize();
	}

}());