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 }; }