-
-
Save shivangp/00e521f6ea4458c32560e462c33e3a15 to your computer and use it in GitHub Desktop.
| // ==UserScript== | |
| // @name Auto Check-In to Southwest Flights | |
| // @namespace https://gist.github.com/ryanizzo/058829a5fafe733bd876410db7a1e699 | |
| // @version 1.9 | |
| // @author Nicholas Buroojy (http://userscripts.org/users/83813) | |
| // @contributor Ryan Izzo (http://www.ryanizzo.com) | |
| // @contributor JR Hehnly (http://www.okstorms.com @stormchasing) | |
| // @contributor Trevor McClellan (github.com/trevormcclellan) | |
| // @description Automatically check in to Southwest Airline flights at the appropriate time. | |
| // @include https://www.southwest.com/air/check-in/index.html* | |
| // @include https://www.southwest.com/flight/selectCheckinDocDelivery.html* | |
| // @include https://www.southwest.com/air/check-in/review.html* | |
| // @include https://www.southwest.com/air/check-in/confirmation.html* | |
| // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html | |
| // @grant GM_getValue | |
| // @grant GM_setValue | |
| // ==/UserScript== | |
| /* This program is free software: you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation, either version 3 of the License, or | |
| (at your option) any later version. | |
| This program is distributed in the hope that it will be useful, | |
| but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| GNU General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with this program. If not, see <http://www.gnu.org/licenses/>. | |
| History | |
| 10/2012 v1.2 Ryan Izzo (ryanizzo.com) | |
| Updated to use new Southwest Check In page, added validation | |
| 7/2014 v1.3 Ryan Izzo (ryanizzo.com) | |
| Moved script to GitHub since UserScripts appears dead | |
| 10/2014 v1.4 JR Hehnly (@stormchasing) | |
| Added phone number entry to auto-text boarding pass docs to mobile device | |
| 3/28/2016 v1.5.1 Trevor McClellan (github.com/trevormcclellan) | |
| Fixed phone number entry system | |
| 9/2017 v1.6 JR Hehnly (@stormchasing) | |
| Initial changes to handle new Southwest confirmation lookup page | |
| 10/03/2017 v1.6.1 JR Hehnly (@stormchasing) | |
| Got the script to a state where check-in works, but no auto-text boarding pass yet. | |
| Have not determined what event listeners need to be triggered in the phone number form field. | |
| Submits sucessfully if last phone number character is manually typed, but fails validation if it's only filled by the script. | |
| 12/2017 v1.7 Ryan Izzo (ryanizzo.com) | |
| - Matching visual styles of page. | |
| - Making countdown timer larger. | |
| - Using constants for page values (hopefully this makes it easier to udpdate later when the pages change). | |
| - Aggregating validation errors to avoid cases where you may have additional, unreported errors preventing submission. | |
| - Commenting out confirmation page for texting boarding pass as this doesnt appear to be fully working. | |
| - Using single input for date and time | |
| 2/2/2018 v1.8 Shivang Patel (@shivangp) | |
| - Fixed autoPassengerPage function - now does not have check box and only checkin button | |
| - changed default auto set time to :05 seconds - :02 seems to early | |
| - clear displayCountdown interval so does not cause error on other pages | |
| - removed timeout setTime for displayCountdownWrapper and just called directly | |
| 2/19/2019 v1.9 Shivang Patel (@shivangp) | |
| - Added interval to continously click submit button after timer is done, in case time is not perfect | |
| TODO: Use Southwest's server's time instead of the client's clock. | |
| TODO: Test select passenger page. | |
| */ | |
| ///////////// CONSTANTS //////////////// | |
| // Check In page | |
| const CHECKIN_FORM_DIV_CLASS = "air-reservation-confirmation-number-search-form"; | |
| const CHECKIN_CONF_INPUT_ID = "confirmationNumber"; | |
| const CHECKIN_FIRSTNAME_INPUT_ID = "passengerFirstName"; | |
| const CHECKIN_LASTNAME_INPUT_ID = "passengerLastName"; | |
| const SUBMIT_BUTTON_ID = "form-mixin--submit-button"; | |
| const FORM_HEADING_CLASS = 'heading heading_medium retrieve-reservation-form-container--form-title'; | |
| const FORM_LABEL_CLASS = 'form-control--label'; | |
| const FORM_TEXT_CLASS = 'form-control--label-text'; | |
| const REQUIRED_CLASS = 'form--required-indicator'; | |
| const BUTTON_DIV_CLASS = 'form-container--search-block'; | |
| const BUTTON_CLASS = 'actionable actionable_button actionable_large-button actionable_no-outline actionable_primary button submit-button'; | |
| // Review page | |
| const REVIEW_SUBMIT_BUTTON_CLASS = "actionable actionable_button actionable_large-button actionable_no-outline actionable_primary button submit-button air-check-in-review-results--check-in-button"; | |
| // Confirmation page | |
| //const CONF_TEXT_ID = "textBoardingPass"; | |
| ///////////// CHECK IN PAGE //////////////// | |
| var globalDisplayCountdownInterval; | |
| var globalSubmitDate; | |
| //var allDone = false; | |
| var submitNowInterval; | |
| /** | |
| * @brief Submit the check in form on the Southwest Check In Online page. | |
| */ | |
| function submitNow() | |
| { | |
| try{ | |
| var submitButton = document.getElementById(SUBMIT_BUTTON_ID); | |
| submitButton.click(); | |
| } | |
| catch(e){ | |
| alert('submitNow: An error has occurred: '+ e.message); | |
| } | |
| } | |
| /** | |
| * @brief Display the countdown. | |
| * | |
| * TODO: Some formatting is wrong eg ("1:0:12" means 1 hour and 12 seconds remain). Make sure everything is represented with 2 digits. | |
| */ | |
| function displayCountdown() | |
| { | |
| try{ | |
| var countdown = document.getElementById("countdown"); | |
| var timeRemain = globalSubmitDate - new Date(); | |
| var days = Math.floor(timeRemain / (1000 * 60 * 60 * 24)); | |
| var hours = Math.floor(timeRemain / (1000 * 60 * 60)) % 24; | |
| var minutes = Math.floor(timeRemain / (1000 * 60)) % 60; | |
| //round to the nearest second | |
| var seconds = Math.round(timeRemain / 1000) % 60; | |
| //Don't print negative time. | |
| if (hours < 0 || minutes < 0 || seconds < 0) | |
| { | |
| countdown.nodeValue = "Checking In..."; | |
| return; | |
| } | |
| var time = ''; | |
| //If 0 days remain, omit them. | |
| if (days !== 0) | |
| time += days + "d "; | |
| //If 0 hours remain, omit them. | |
| if (hours !== 0) | |
| time += hours + "h "; | |
| //Add padding to minute | |
| if (minutes !==0 ) | |
| //area.innerHTML += "0"; | |
| time += minutes + "m "; | |
| //Add padding to second | |
| //if (seconds < 10) | |
| //area.innerHTML += "0"; | |
| time += seconds; | |
| time += "s"; | |
| countdown.textContent = time; | |
| } | |
| catch(e){ | |
| // has the page changed? | |
| if(/review/.test(document.location.href)) | |
| { | |
| /* clear the display countdown interval - otherwise will get error when trying to update */ | |
| clearInterval(globalDisplayCountdownInterval); | |
| autoPassengerPage(); | |
| return; | |
| } | |
| /*else if(/confirmation/.test(document.location.href)) | |
| { | |
| if (allDone === false) | |
| { | |
| autoTextBoardingDocs(); | |
| } | |
| return; | |
| }*/ | |
| alert('displayCountdown: An error has occurred: ' +e.message); | |
| } | |
| } | |
| /** | |
| * @brief Updates the countdown every second. | |
| */ | |
| function displayCountdownWrapper() | |
| { | |
| try{ | |
| globalDisplayCountdownInterval = window.setInterval(displayCountdown, 1000); | |
| } | |
| catch(e){ | |
| alert('displayCountdownWrapper:" An error has occurred: ' +e.message); | |
| } | |
| } | |
| /** | |
| * @brief Begins the delay at the next even second. | |
| */ | |
| function beginDelay() | |
| { | |
| try{ | |
| var confNumber = document.getElementById(CHECKIN_CONF_INPUT_ID).value; | |
| var firstName = document.getElementById(CHECKIN_FIRSTNAME_INPUT_ID).value; | |
| var lastName = document.getElementById(CHECKIN_LASTNAME_INPUT_ID).value; | |
| var date = document.getElementById("date-input").value; | |
| var time = document.getElementById("time-input").value; | |
| // var phoneArea = document.getElementById("phoneArea").value; | |
| // var phonePrefix = document.getElementById("phonePrefix").value; | |
| // var phoneNumber = document.getElementById("phoneNumber").value; | |
| var errors = ""; | |
| if(confNumber === "" || firstName === "" || lastName === "" ) | |
| errors += "- Must fill out Confirmation Number, First Name and Last Name.\r"; | |
| if(confNumber.length != 6 ) | |
| errors += "- Confirmation Number must be 6 characters.\r"; | |
| if(!isValidDate(date)) | |
| errors += "- Date is not valid.\r"; | |
| if(!isValidTime(time)) | |
| errors += "- Time is not valid.\r"; | |
| if(isValidDate(date) && isValidTime(time)){ | |
| //Build the date | |
| var submitDate = new Date(date); | |
| // Parse the date parts to integers | |
| var parts = time.split(":"); | |
| var hour = parseInt(parts[0], 10); | |
| var min = parseInt(parts[1], 10); | |
| var sec = parseInt(parts[2], 10); | |
| submitDate.setHours(hour, min, sec, 0); | |
| var now = new Date(); | |
| var msRemaining = submitDate - now; | |
| var maxDays = 14; | |
| if(msRemaining < 0) | |
| errors += "- Date/Time must be in the future." + submitDate; | |
| else if(msRemaining > maxDays * 1000 * 60 * 60 * 24) | |
| errors += "- Date/Time cannot be more than " + maxDays + " days in the future." + msRemaining; | |
| } | |
| /*if (phoneArea.search(/\d\d\d/g) == -1 || phonePrefix.search(/\d\d\d/g) == -1 || phoneNumber.search(/\d\d\d\d/g) == -1) { | |
| errors += "- Invalid phone number.\r"; | |
| } | |
| else{ | |
| //save the text number for later | |
| GM_setValue("phoneArea", phoneArea); | |
| GM_setValue("phonePrefix", phonePrefix); | |
| GM_setValue("phoneNumber", phoneNumber); | |
| }*/ | |
| if(errors.length == 0){ | |
| //Install the timeout to submit the form. | |
| window.setTimeout(function() { | |
| submitNow(); | |
| submitNowInterval = window.setInterval(submitNow, 500); | |
| }, msRemaining); | |
| globalSubmitDate = submitDate; | |
| //Install a short term timeout to call the countdown wrapper at the beginning of the next second. | |
| //window.setTimeout(displayCountdownWrapper, msRemaining % 1000); | |
| displayCountdownWrapper(); | |
| } | |
| else{ | |
| alert(errors); | |
| } | |
| } | |
| catch(e){ | |
| alert('beginDelay: An error has occurred: '+ e.message); | |
| } | |
| } | |
| //Validates that the input string is a valid date formatted as "mm/dd/yyyy" | |
| function isValidDate(dateString) | |
| { | |
| // First check for the pattern | |
| if(!/^\d{1,2}\/\d{1,2}\/\d{4}$/.test(dateString)) | |
| return false; | |
| // Parse the date parts to integers | |
| var parts = dateString.split("/"); | |
| var day = parseInt(parts[1], 10); | |
| var month = parseInt(parts[0], 10); | |
| var year = parseInt(parts[2], 10); | |
| // Check the ranges of month and year | |
| if(year < 2017 || year > 2100 || month == 0 || month > 12) | |
| return false; | |
| var monthLength = [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ]; | |
| // Adjust for leap years | |
| if(year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)) | |
| monthLength[1] = 29; | |
| // Check the range of the day | |
| return day > 0 && day <= monthLength[month - 1]; | |
| } | |
| //Validates that the input string is a valid date formatted as "mm/dd/yyyy" | |
| function isValidTime(timeString) | |
| { | |
| // First check for the pattern | |
| if(!/^\d{1,2}:\d{1,2}:\d{1,2}$/.test(timeString)) | |
| return false; | |
| // Parse the date parts to integers | |
| var parts = timeString.split(":"); | |
| var hour = parseInt(parts[0], 10); | |
| var min = parseInt(parts[1], 10); | |
| var sec = parseInt(parts[2], 10); | |
| // Check the ranges of month and year | |
| if(hour < 0 || hour > 23 || min < 0 || min > 59 || sec < 0 || sec > 59) | |
| return false; | |
| return true; | |
| } | |
| /** | |
| * @brief Edits the check in page; Adds Date, time, and Auto Check In button | |
| * | |
| * TODO Dater and Time picker | |
| */ | |
| function checkInPageFormEdit() | |
| { | |
| try{ | |
| var delayDiv = document.createElement("div"); | |
| delayDiv.setAttribute('id','checkInDelay'); | |
| var dateSelect = document.createElement("span"); | |
| dateSelect.setAttribute('id','date-select'); | |
| //The big label at the top of the menu | |
| var mainLabel = document.createElement("h2"); | |
| mainLabel.setAttribute('class',FORM_HEADING_CLASS); | |
| var mainLabelDiv = document.createElement("div"); | |
| mainLabelDiv.textContent = 'Set Check In Date and Time'; | |
| mainLabel.appendChild(mainLabelDiv); | |
| dateSelect.innerHTML += "<br/>"; | |
| dateSelect.appendChild(mainLabel); | |
| //The date portion. | |
| var today = new Date(); | |
| var dateLabel = document.createElement("label"); | |
| dateLabel.innerHTML = "<span class=\""+FORM_LABEL_CLASS+"\"><span class=\""+FORM_TEXT_CLASS+"\">Date: (mm/dd/yyyy)</span><span class=\"" +REQUIRED_CLASS+"\">*</span></span>"; | |
| var dateInput = document.createElement("input"); | |
| dateInput.setAttribute('id','date-input'); | |
| dateInput.setAttribute('class','input--text'); | |
| dateInput.setAttribute('type','text'); | |
| dateInput.setAttribute('maxlength','10'); | |
| dateInput.setAttribute('value',(today.getMonth()+1)+'/'+today.getDate()+'/'+today.getFullYear()); | |
| // dateInput.setAttribute('onfocus','if(this.value==\'mm\') this.value=\'\';'); | |
| dateInput.setAttribute('tabindex','7'); | |
| dateSelect.appendChild(dateLabel); | |
| dateSelect.appendChild(dateInput); | |
| // The time portion. | |
| var timeLabel = document.createElement("label"); | |
| timeLabel.innerHTML = "<span class=\""+FORM_LABEL_CLASS+"\"><span class=\""+FORM_TEXT_CLASS+"\">Time: (24-hour format hh:mm:ss)</span><span class=\"" +REQUIRED_CLASS+"\">*</span></span>"; | |
| var timeInput = document.createElement("input"); | |
| timeInput.setAttribute('id','time-input'); | |
| timeInput.setAttribute('class','input--text'); | |
| timeInput.setAttribute('type','text'); | |
| timeInput.setAttribute('maxlength','8'); | |
| timeInput.setAttribute('value',(today.getHours()+1)+':'+today.getMinutes()+':05'); | |
| // timeInput.setAttribute('onfocus','if(this.value==\'mm\') this.value=\'\';'); | |
| timeInput.setAttribute('tabindex','8'); | |
| dateSelect.appendChild(timeLabel); | |
| dateSelect.appendChild(timeInput); | |
| delayDiv.appendChild(dateSelect); | |
| //auto-text boarding pass section | |
| /*var autoTextArea = document.createElement("div"); | |
| var textLabel = document.createElement("label"); | |
| textLabel.innerHTML = " Boarding pass text number:"; | |
| var phoneArea = document.createElement("input"); | |
| phoneArea.setAttribute('id','phoneArea'); | |
| phoneArea.setAttribute('type','text'); | |
| phoneArea.setAttribute('maxlength','3'); | |
| phoneArea.setAttribute('size','3'); | |
| phoneArea.setAttribute('value', GM_getValue("phoneArea") !== undefined ? GM_getValue("phoneArea") : ''); | |
| phoneArea.setAttribute('tabindex','11'); | |
| var phonePrefix = document.createElement("input"); | |
| phonePrefix.setAttribute('id','phonePrefix'); | |
| phonePrefix.setAttribute('type','text'); | |
| phonePrefix.setAttribute('maxlength','3'); | |
| phonePrefix.setAttribute('size','3'); | |
| phonePrefix.setAttribute('value', GM_getValue("phonePrefix") !== undefined ? GM_getValue("phonePrefix") : ''); | |
| phonePrefix.setAttribute('tabindex','12'); | |
| var phoneNumber = document.createElement("input"); | |
| phoneNumber.setAttribute('id','phoneNumber'); | |
| phoneNumber.setAttribute('type','text'); | |
| phoneNumber.setAttribute('maxlength','4'); | |
| phoneNumber.setAttribute('size','4'); | |
| phoneNumber.setAttribute('value', GM_getValue("phoneNumber") !== undefined ? GM_getValue("phoneNumber") : ''); | |
| phoneNumber.setAttribute('tabindex','13'); | |
| autoTextArea.innerHTML += "<br/>"; | |
| autoTextArea.appendChild(textLabel); | |
| autoTextArea.innerHTML += "<br/>"; | |
| autoTextArea.innerHTML += "("; | |
| autoTextArea.appendChild(phoneArea); | |
| autoTextArea.innerHTML += ") "; | |
| autoTextArea.appendChild(phonePrefix); | |
| autoTextArea.innerHTML += " - "; | |
| autoTextArea.appendChild(phoneNumber); | |
| delayDiv.appendChild(autoTextArea); | |
| */ | |
| // The area that displays how much time remains before the form is submitted. | |
| delayDiv.innerHTML += "<br/><br />"; | |
| var countdownH2 = document.createElement("h2"); | |
| countdownH2.setAttribute('class',FORM_HEADING_CLASS); | |
| var countdownDiv = document.createElement("div"); | |
| countdownDiv.setAttribute('id','countdown'); | |
| countdownDiv.textContent = 'Click to Start ->'; | |
| countdownH2.appendChild(countdownDiv); | |
| delayDiv.appendChild(countdownH2); | |
| // Auto Check In button | |
| var delayButtonDiv = document.createElement("div"); | |
| delayButtonDiv.setAttribute('class',BUTTON_DIV_CLASS); | |
| var delayButton = document.createElement("button"); | |
| delayButton.setAttribute('class',BUTTON_CLASS); | |
| delayButton.setAttribute('type','button'); | |
| delayButton.addEventListener("click", beginDelay, true); | |
| delayButton.setAttribute('tabindex','14'); | |
| var delayButtonSpan = document.createElement("span"); | |
| delayButtonSpan.textContent = 'Auto Check in'; | |
| delayButton.appendChild(delayButtonSpan); | |
| delayButtonDiv.appendChild(delayButton); | |
| delayDiv.appendChild(delayButtonDiv); | |
| document.getElementsByClassName(CHECKIN_FORM_DIV_CLASS)[0].appendChild(delayDiv); | |
| } | |
| catch(e){ | |
| alert('checkInPageFormEdit: An error has occurred: ' +e.message); | |
| } | |
| } | |
| ///////////// SELECT PASSENGER PAGE //////////////// | |
| //automatically select all passengers and submit the form | |
| function autoPassengerPage() | |
| { | |
| try{ | |
| //find error notification | |
| if(document.title == "Error") | |
| return; | |
| // Check all the check boxes. | |
| //var node_list = document.getElementsByTagName('input'); | |
| //for (var i = 0; i < node_list.length; i++) { | |
| // var node = node_list[i]; | |
| // if (node.getAttribute('type') == 'checkbox') { | |
| // node.checked = true; | |
| // } | |
| //} | |
| //Click the print button | |
| var button = document.getElementsByClassName('air-check-in-review-results--check-in-button')[0]; | |
| button.click(); | |
| } | |
| catch(e){ | |
| alert('autoPassengerPage: An error has occurred: '+ e.message); | |
| } | |
| } | |
| ///////////// BOARDING DOC DELIVERY PAGE //////////////// | |
| /*function autoTextBoardingDocs() | |
| { | |
| try{ | |
| //find error notification | |
| if (document.title == "Error") | |
| return; | |
| //click the Text button | |
| var button = document.getElementsByClassName(REVIEW_SUBMIT_BUTTON_CLASS)[0]; | |
| button.click(); | |
| window.setTimeout(waitForSendButton, 500); | |
| } | |
| catch(e){ | |
| alert('autoTextBoardingDocs: An error has occurred: '+ e.message); | |
| } | |
| } | |
| function waitForTextBoardingPass() { | |
| if(document.getElementById("textBoardingPass") === null) { | |
| window.setTimeout(waitForTextBoardingPass, 100); | |
| } else { | |
| document.getElementById("textBoardingPass").focus(); | |
| document.getElementById("textBoardingPass").value = parseInt(GM_getValue("phoneArea") + GM_getValue("phonePrefix") + GM_getValue("phoneNumber")); | |
| waitForSendButton(); | |
| } | |
| } | |
| function waitForSendButton() { | |
| if(document.getElementById(SUBMIT_BUTTON_ID) === null || document.getElementById(CONF_TEXT_ID).value != GM_getValue("phoneArea") + GM_getValue("phonePrefix") + GM_getValue("phoneNumber")) { | |
| document.getElementById(CONF_TEXT_ID).focus(); | |
| document.getElementById(CONF_TEXT_ID).value = parseInt(GM_getValue("phoneArea") + GM_getValue("phonePrefix") + GM_getValue("phoneNumber")); | |
| window.setTimeout(waitForSendButton, 100); | |
| } else { | |
| //document.getElementById(SUBMIT_BUTTON_ID).focus(); | |
| //document.getElementById(SUBMIT_BUTTON_ID).click(); | |
| allDone = true; | |
| } | |
| }*/ | |
| //case of the select boarding pass page (regex match the url) | |
| if(/check-in/.test(document.location.href)) | |
| { | |
| checkInPageFormEdit(); | |
| } else { | |
| window.clearInterval(submitNowInterval); | |
| alert("interval cleared"); | |
| } | |
| /*else if(/confirmation/.test(document.location.href)) | |
| { | |
| autoTextBoardingDocs(); | |
| }*/ |
@danimal1986 Sorry but I am no longer maintaining this code
Dang, something must have been updated and nuked it....ohh well, thanks for doing the work on this. RIP
Edit: Somehow it started working again! Not sure what happened.
Edit2: i think it may have been the site i was using. i didn't have the..../index.html at the end. May have been it.
The code still works as of today got mid B's, the only thing is error "submitNow: An error has occurred: Cannot read properties of null (reading 'click')"
The code still works as of today got mid B's, the only thing is error "submitNow: An error has occurred: Cannot read properties of null (reading 'click')"
I think the issue i was having was that i was using "southwest.com/air/check-in" and not "southwest.com/air/check-in/index.html"
Previously i remember getting the same error as you, i would just check back in and it would take me to the page to text/email my boarding pass.
@danimal1986 Sorry but I am no longer maintaining this code