NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript==
// @name TFL Delay Checker
// @namespace http://tampermonkey.net/
// @version 0.91
// @description Check each journey in TFL Journey Planner to look for delays of 15 minutes or over.
// @author You
// @match https://contactless.tfl.gov.uk/NewStatements*
// @grant GM_setValue
// @grant GM_getValue
// @updateURL https://openuserjs.org/meta/nascent/TFL_Delay_Checker.meta.js
// @downloadURL https://openuserjs.org/install/nascent/TFL_Delay_Checker.user.js
// @license GPL-3.0-or-later
// ==/UserScript==
'use strict';
// version 0.9 - Automatically collapse or expand all journey headers depending on setting.
// version 0.8 - Removed modal alert if expected refunds requested, now highlights rows red and changed the statement header message.
// version 0.7 - Stations and expected times are now queried on first run and stored in local settings.
// version 0.6 - Journey times and delay lengths are now displayed on web page.
// version 0.5 - Updated default times for new journey, added filter for specified journey stations.
// version 0.4 - Nested evaluate recode to correct get date from parent node.
// version 0.3 - Recode for TFL redesign
// version 0.2 - Fix for time occuring across midnight
// version 0.1 - initial release
//Todo: ui to clear saved stations
//Todo: support for multiple start and end stations
//Manually override stored settings (Station names need to read exactly as show in https://tfl.gov.uk/plan-a-journey)
//GM_setValue("tflStation1","Stratford International"); // eg: "Oxford Circus"
//GM_setValue("tflStation2","London St Pancras International"); // eg: "Mansion House"
//GM_setValue("expectedJourneyTime","23"); //in minutes
//GM_setValue("expandAllJourneys","true"); // automatically expand all journeys
//User settings varaiables (use the above GM_setValue block to make changes rather than here)
var expectedJourneyTime = 0;
var maxDelayTime = 15; //constant. tfl allow for 15 minutes worth of delays before allowing delay refunds
var journeyStation1 = "";
var journeyStation2 = "";
var autoExpandAllJourneys = false;
autoExpandAllJourneys = GM_getValue("expandAllJourneys",false);
//Automatically Collapse/Expand journeys
var journeys = document.getElementsByClassName("list-group collapse");
//alert ("collapsed " + journeys.length);
// Loop through the journeys array
Array.prototype.forEach.call(journeys, function(el) {
if (autoExpandAllJourneys) {
//Expand all
if (el.className == "list-group collapse"){
el.className = "list-group collapse in";
}
}
else {
//Collpase all
if (el.className == "list-group collapse in"){
el.className = "list-group collapse";
}
}
});
//Calculate journey times and alert on delays
expectedJourneyTime = GM_getValue("expectedJourneyTime");
if (expectedJourneyTime === undefined) {
var input = prompt("What is the expected journey time of your commute (minutes)?", 20);
if (input === null) {
alert("Input must be valid");
return;
}
else {
expectedJourneyTime = input;
GM_setValue("expectedJourneyTime",expectedJourneyTime);
}
}
journeyStation1 = GM_getValue("tflStation1");
if (journeyStation1 === undefined) {
input = prompt("What is station 1 of your commute?", "Oxford Circus");
if (input === null) {
alert("Input must be valid");
return;
}
else {
journeyStation1 = input;
GM_setValue("tflStation1",journeyStation1);
}
}
journeyStation2 = GM_getValue("tflStation2");
if (journeyStation2 === undefined) {
input = prompt("What is station 2 of your commute?", "Mansion House");
if (input === null) {
alert("Input must be valid");
return;
}
else {
journeyStation2 = input;
GM_setValue("tflStation2",journeyStation2);
}
}
//Display settings
//alert("expectedJourneyTime: " + expectedJourneyTime);
//alert("journeyStation1: " + journeyStation1);
//alert("journeyStation2: " + journeyStation2);
var allDivs, thisDiv;
allDivs = document.evaluate(
"//div[@class='statements-list clearfix']",
document,
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
var delay = 0;
var anyRefundsNeeded = false;
if (allDivs) {
console.debug("Total Divs: " +allDivs.snapshotLength);
for (var i = 0; i < allDivs.snapshotLength; i++) {
thisDiv = allDivs.snapshotItem(i).innerHTML;
//window.prompt(i + ", thisDiv", thisDiv);
var divArray = thisDiv.match(/class="date-link">(.+?)</); //Journey Divs
var allRows, thisRow;
allRows = document.evaluate(
".//div[@class='col-xs-9']",
allDivs.snapshotItem(i),
null,
XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
null);
if (allRows) {
for (var j = 0; j < allRows.snapshotLength; j++) {
thisRow = allRows.snapshotItem(j).innerHTML;
var rowArray = thisRow.match(/journey-from">(.+?)<[\s\S]+?journey-to">(.+?)<[\s\S]+?journey-time">(.+?)</); //Journey Divs
if (rowArray) {
var res = rowArray[3].split(" - ");
var journeyTime;
journeyTime = CompareTimes(res[0], res[1]); // / 1000 / 60
allRows.snapshotItem(j).innerHTML += "<p style='color:black;font-size:10px'>Journey Length: " + journeyTime + "mins</p>";
//Is the journey between desired 2 stations
if ((journeyStation1 == rowArray[1] && journeyStation2 == rowArray[2]) || (journeyStation1 == rowArray[2] && journeyStation2 == rowArray[1])) {
allRows.snapshotItem(j).innerHTML += "<p style='color:black;font-size:10px'>Expected Time: " + expectedJourneyTime + "mins</p>";
delay = journeyTime - expectedJourneyTime;
if (delay > 0) {
allRows.snapshotItem(j).innerHTML += "<p style='color:black;font-size:10px'>Delay: " + delay + "mins</p>";
}
else {
allRows.snapshotItem(j).innerHTML += "<p style='color:black;font-size:10px'>No delay!</p>";
}
if (delay > maxDelayTime) { //Delay more than journeyTime (" + expectedJourneyTime + ") + " + maxDelayTime + " minutes delayTime
allRows.snapshotItem(j).innerHTML += "<p style='font-size:11px'><t style='background-color:red;;color:white;'>Refund needed!</t></p>";
var rootDiv = allRows.snapshotItem(j).parentNode.parentNode.parentNode.parentNode;
rootDiv.childNodes[1].setAttribute("style", "color: red !important");
rootDiv.childNodes[1].setAttribute("style", "background-color:red !important");
anyRefundsNeeded = true;
}
else {
allRows.snapshotItem(j).innerHTML += "<p style='color:black;font-size:11px'>No refund expected. "+journeyTime+"-"+expectedJourneyTime+"</p>";
}
}
}
else
{
console.error("TFL Delay Checker: No rows found " + divArray.length + "\n " + thisRow);
}
}
}
else {
console.error("Unable to evaluate rows");
}
}
if (anyRefundsNeeded == true) {
var headerNodes = document.getElementsByTagName("h2");
for (var h = 0; h < headerNodes.length; h++) {
if (headerNodes[h].innerText == "Statement"){
headerNodes[h].innerText = "Some journeys require a refund";
headerNodes[h].setAttribute("style", "color: red !important");
}
}
}
}
else {
console.error("Unable to evaluate divs");
}
function CompareTimes(startTime, endTime) { //07:56 - 08:39
var startDate = new Date("January 1, 1970 " + startTime +":00");
var endDate = new Date("January 1, 1970 " + endTime +":00");
if (endDate < startDate) {
endDate.setDate(endDate.getDate() + 1);
}
var timeDiff = Math.abs(startDate - endDate);
var hh = Math.floor(timeDiff / 1000 / 60 / 60);
if(hh < 10) {
hh = '0' + hh;
}
timeDiff -= hh * 1000 * 60 * 60;
var mm = Math.floor(timeDiff / 1000 / 60);
if(mm < 10) {
mm = '0' + mm;
}
timeDiff -= mm * 1000 * 60;
var ss = Math.floor(timeDiff / 1000);
if(ss < 10) {
ss = '0' + ss;
}
return Math.floor(Math.abs(startDate - endDate) / 1000 / 60);
}
function parseTime(s) {
var part = s.match(/(\d+):(\d+)(?: )?(am|pm)?/i);
var hh = parseInt(part[1], 10);
var mm = parseInt(part[2], 10);
var ap = part[3] ? part[3].toUpperCase() : null;
if (ap === "AM") {
if (hh == 12) {
hh = 0;
}
}
if (ap === "PM") {
if (hh != 12) {
hh += 12;
}
}
//alert(hh + " " + mm);
return { hh: hh, mm: mm };
}