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