--- recipe-496790-1.py	2008-09-23 18:35:58.000000000 -0700
+++ southwest-checkin.py	2008-09-24 20:54:01.000000000 -0700
@@ -6,6 +6,7 @@
 
 # imports
 import re
+import os
 import sys
 import time
 import sched
@@ -28,18 +29,20 @@
 # 4) Southwest Airlines - Print Boarding Pass.htm
 # 5) Southwest Airlines - Retrieve-Print Boarding Pass.htm
 # ========================================================================
-confirmation = 'ABCDEF'
-firstname = 'Firstname'
-lastname = 'Lastname'
-DEBUG_SCH = 0
+confirmation = 'ABCDEF'
+firstname = 'Firstname'
+lastname = 'Lastname'
+DEBUG_SCH = 0
 # ========================================================================
 # fill in these parameters if you want the script to email you
 # to disable make the emailaddr = None
-emailaddr = None
-# emailaddr = "myemail@myemail.com"
-smtpserver = "smtpserver.com"
+#emailaddr = "one@example.com, two@example.com"
+#emailaddr = "one@example.com"
+emailaddr = None
+emailfrom = "from@example.com"
+smtpserver = "localhost"
 smtpauth = False
-smtpport = 80
+smtpport = 25
 smtpuser = "username"
 smtppass = "password"
 
@@ -47,7 +50,7 @@
 # fixed page locations and parameters
 # DO NOT change these parameters
 main_url = 'www.southwest.com'
-checkin_url = '/travel_center/retrieveCheckinDoc.html'
+checkin_url = '/content/travel_center/retrieveCheckinDoc.html'
 retrieve_url = '/travel_center/retrieveItinerary.html'
 defaultboxes = ["recordLocator", "firstName", "lastName"]
 # ========================================================================
@@ -69,7 +72,7 @@
 
     def _reset(self):
         self.searchtags = {}
-        self.hiddentags = {}
+        self.hiddentags = []
         self.searchaction = ""
         self.formaction = ""
         self.is_search = False
@@ -113,7 +116,7 @@
                 if self.is_search:
                     self.searchtags[thename] = thevalue
                 else:
-                    self.hiddentags[thename] = thevalue
+                    self.hiddentags.append((thename, thevalue))
 
             # otherwise, append the name of the text fields
             elif istext and self.is_search==False and issubmit==False:
@@ -132,26 +135,116 @@
                         self.formaction = theaction
                         self.is_search = False
 
+# This returns the timezone for a given airport code
+def airportCodeTZ(code):
+    airportzone = {}
+    airportzone["ALB"] = "US/Eastern"
+    airportzone["ABQ"] = "US/Mountain"
+    airportzone["AMA"] = "US/Central"
+    airportzone["AUS"] = "US/Central"
+    airportzone["BWI"] = "US/Eastern"
+    airportzone["BHM"] = "US/Central"
+    airportzone["BOI"] = "US/Mountain"
+    airportzone["BUF"] = "US/Eastern"
+    airportzone["BUR"] = "US/Pacific"
+    airportzone["MDW"] = "US/Central"
+    airportzone["CLE"] = "US/Eastern"
+    airportzone["CMH"] = "US/Eastern"
+    airportzone["CRP"] = "US/Central"
+    airportzone["DAL"] = "US/Central"
+    airportzone["DEN"] = "US/Mountain"
+    airportzone["DTW"] = "US/Eastern"
+    airportzone["ELP"] = "US/Mountain"
+    airportzone["FLL"] = "US/Eastern"
+    airportzone["RSW"] = "US/Eastern"
+    airportzone["HRL"] = "US/Central"
+    airportzone["BDL"] = "US/Eastern"
+    airportzone["HOU"] = "US/Central"
+    airportzone["IND"] = "US/Eastern"
+    airportzone["JAN"] = "US/Central"
+    airportzone["JAX"] = "US/Eastern"
+    airportzone["MCI"] = "US/Central"
+    airportzone["LAS"] = "US/Pacific"
+    airportzone["LIT"] = "US/Central"
+    airportzone["ISP"] = "US/Eastern"
+    airportzone["LAX"] = "US/Pacific"
+    airportzone["SDF"] = "US/Eastern"
+    airportzone["LBB"] = "US/Central"
+    airportzone["MHT"] = "US/Eastern"
+    airportzone["MAF"] = "US/Central"
+    airportzone["BNA"] = "US/Central"
+    airportzone["MSY"] = "US/Central"
+    airportzone["ORF"] = "US/Eastern"
+    airportzone["OAK"] = "US/Pacific"
+    airportzone["OKC"] = "US/Central"
+    airportzone["OMA"] = "US/Central"
+    airportzone["ONT"] = "US/Pacific"
+    airportzone["SNA"] = "US/Pacific"
+    airportzone["MCO"] = "US/Eastern"
+    airportzone["PHL"] = "US/Eastern"
+    airportzone["PHX"] = "US/Arizona"
+    airportzone["PIT"] = "US/Eastern"
+    airportzone["PDX"] = "US/Pacific"
+    airportzone["PVD"] = "US/Eastern"
+    airportzone["RDU"] = "US/Eastern"
+    airportzone["RNO"] = "US/Pacific"
+    airportzone["SMF"] = "US/Pacific"
+    airportzone["SLC"] = "US/Mountain"
+    airportzone["SAT"] = "US/Central"
+    airportzone["SAN"] = "US/Pacific"
+    airportzone["SJC"] = "US/Pacific"
+    airportzone["SFO"] = "US/Pacific"
+    airportzone["SEA"] = "US/Pacific"
+    airportzone["GEG"] = "US/Pacific"
+    airportzone["STL"] = "US/Central"
+    airportzone["TPA"] = "US/Eastern"
+    airportzone["TUS"] = "US/Arizona"
+    airportzone["TUL"] = "US/Central"
+    airportzone["IAD"] = "US/Eastern"
+    airportzone["PBI"] = "US/Eastern"
+
+    return airportzone[code]
 
 # function to send an simple text email message
 def sendEmail(wdata, emailaddr, smtpserver, smtpport= 25, \
               smtpauth=False, smtpuser="", smtppass=""):
-    import smtplib
+    import smtplib, sys, MimeWriter, base64, StringIO
     from email.MIMEText import MIMEText
 
-    # Create a text/plain message
-    msg = MIMEText(wdata)
+    fd = open("boardingpass.htm", "r")
+    boardingpass = fd.read(-1)
+    fd.close()
 
-    # me == the sender's email address
-    # you == the recipient's email address
-    msg['Subject'] = 'Results of Southwest checkin script'
-    msg['From'] = emailaddr
-    msg['To'] = emailaddr
-    s = smtplib.SMTP()
+    # Change a few things on boardingpass so that images resolve correctly via email:
+    bpmod = re.sub('/styles/', 'http://www.southwest.com/styles/', boardingpass)
+    boardingpass = re.sub('img src="/', 'img src="http://www.southwest.com/', bpmod)
+
+    # Create a MIME Multipart message
+    message = StringIO.StringIO()
+    writer = MimeWriter.MimeWriter(message)
+    writer.addheader("Subject", "Results of Southwest Check-In Script")
+    writer.addheader('MIME-Version', '1.0')
+    writer.addheader('To', emailaddr)
+    writer.startmultipartbody('mixed')
+
+    # Text Segment
+    part = writer.nextpart()
+    body = part.startbody('text/plain')
+    body.write(wdata)
+
+    # HTML Segment (Boarding Pass)
+    part = writer.nextpart()
+    body = part.startbody('text/html')
+    body.write(boardingpass)
 
+    # finish email
+    writer.lastpart()
+
+    # Send the email
     # connect on the designated port - will be 25 or 80
+    s = smtplib.SMTP()
     try:
-        s.connect(smtpserver, 80)
+        s.connect(smtpserver, smtpport)
     except Exception, connerror:
         print connerror.__class__, connerror
         return False
@@ -163,7 +256,7 @@
             print connerror.__class__, connerror
             return False
     try:
-        s.sendmail(emailaddr, emailaddr, msg.as_string())
+        s.sendmail(emailfrom, emailaddr, message.getvalue())
     except Exception, connerror:
         print connerror.__class__, connerror
         return False
@@ -288,6 +381,19 @@
     to_depart_time = ts[0]
     to_arrive_time = ts[1]
 
+    # Use a regular expression to find the departing Airport
+    reap = re.compile("\((...)\)")
+    ts = reap.findall(timeline)
+    to_tz = airportCodeTZ(ts[0])
+    to_dest_tz = airportCodeTZ(ts[1])
+
+    if(len(to_tz) < 1):
+        print "Error, could not find timezone for airport code ", ts[0], ". Using PST"
+        to_tz = "US/Pacific"
+    if(len(to_dest_tz) < 1):
+        print "Error, could not find timezone for airport code ", ts[1], ". Using PST"
+        to_dest_tz = "US/Pacific"
+
     # now find the return flight date and time information
     dateloc_2 = reservations.find("Details", timeloc)
     dateloc_3 = reservations.find("bookingFormText", dateloc_2)
@@ -301,9 +407,24 @@
     ts = tm.findall(timeline)
     fr_depart_time = ts[0]
     fr_arrive_time = ts[1]
+
+    # Use a regular expression to find the returning Airport
+    reap = re.compile("\((...)\)")
+    ts = reap.findall(timeline)
+    fr_tz = airportCodeTZ(ts[0])
+    fr_dest_tz = airportCodeTZ(ts[1])
+
+    if(len(fr_tz) < 1):
+        print "Error, could not find timezone for airport code ", ts[0], ". Using PST"
+        fr_tz = "US/Pacific"
+    if(len(fr_dest_tz) < 1):
+        print "Error, could not find timezone for airport code ", ts[1], ". Using PST"
+        fr_dest_tz = "US/Pacific"
     
-    departure = (to_date, to_depart_time, to_arrive_time, \
-                 fr_date, fr_depart_time, fr_arrive_time)
+    departure = (to_date, to_depart_time, to_arrive_time, to_tz, to_dest_tz, \
+                 fr_date, fr_depart_time, fr_arrive_time, fr_tz, fr_dest_tz)
+
+#    print "Departure: ", departure
     
     return departure
 
@@ -334,34 +455,49 @@
     # where the names are obtained from the parser
     params = setInputBoxes(gh.textnames, conf_number, first_name, last_name)
 
-    # submit the request to pull up the reservations
-    if DEBUG_SCH > 1:
-        fd = open("Southwest Airlines - Print Boarding Pass.htm","r")
-        reservations = fd.read(-1)
-        fd.close()
-    else:
-        reservations = PostUrl(main_url, post_url, params)
-
-    if reservations==None or len(reservations)==0:
-        print "Error: no data returned from ", main_url+post_url
-        print "Params = ", params
-        sys.exit(1)
-
-    # parse the returned reservations page
-    rh = HTMLSouthwestParser(reservations)
+    # Keep trying until we successfully get our boarding pass. This can happen if we're a bit early
+    count = 0
+    while True:
+        count = count + 1
+        if count > 180:
+            print "Been trying for two hours to check in - giving up!"
+            sys.exit(1)
+        #print "Params = ", params
+        # submit the request to pull up the reservations
+        if DEBUG_SCH > 1:
+            fd = open("Southwest Airlines - Print Boarding Pass.htm","r")
+            reservations = fd.read(-1)
+            fd.close()
+        else:
+            reservations = PostUrl(main_url, post_url, params)
 
-    # Extract the name of the post function to check into the flight
-    final_url = rh.formaction
+        if reservations==None or len(reservations)==0:
+            print "Error: no data returned from ", main_url+post_url
+            print "Params = ", params
+            #sys.exit(1)
+            print "Trying again in 60 seconds."
+            time.sleep(60)
+            continue
 
-    # the returned web page contains three unique security-related hidden fields
-    # plus a dynamically generated value for the checkbox or radio button
-    # these must be sent to the next submit post to work properly
-    # they are obtained from the parser object
-    params = rh.hiddentags
-    if len(params) < 4:
-        print "Error: Fewer than the expect 4 special fields returned from ", main_url+post_url
-        print "Params = ", params
-        sys.exit(1)
+        # parse the returned reservations page
+        rh = HTMLSouthwestParser(reservations)
+    
+        # Extract the name of the post function to check into the flight
+        final_url = rh.formaction
+    
+        # the returned web page contains three unique security-related hidden fields
+        # plus a dynamically generated value for the checkbox or radio button
+        # these must be sent to the next submit post to work properly
+        # they are obtained from the parser object
+        hiddenparams = rh.hiddentags
+        if len(hiddenparams) < 4:
+            print "Error: Fewer than the expect 4 special fields returned from ", main_url+post_url
+            print "Params = ", params
+            #sys.exit(1)
+            print "Trying again in 60 seconds."
+            time.sleep(60)
+        else:
+            break
 
     # finally, lets check in the flight and make our success file
     if DEBUG_SCH > 1:
@@ -369,12 +505,12 @@
         checkinresult = fd.read(-1)
         fd.close()
     else:
-        checkinresult = PostUrl(main_url, final_url, params)
+        checkinresult = PostUrl(main_url, final_url, hiddenparams)
 
     # write the returned page to a file for later inspection
     if checkinresult==None or len(checkinresult)==0:
         print "Error: no data returned from ", main_url+final_url
-        print "Params = ", params
+        print "Params = ", hiddenparams
         sys.exit(1)
 
     # always save the returned file for later viewing
@@ -385,12 +521,13 @@
     # look for what boarding letter and number we got in the file
     myre = re.compile("boarding[ABC]\.gif")
     bgroup = myre.findall(checkinresult)
-    i0 = checkinresult.find("passNum\">")
+    myre2 = re.compile("boarding(\d).gif")
+    bnumber = myre2.findall(checkinresult)
+    if(len(bnumber) > 0):
+        bnumber = bnumber[0] + bnumber[1] # String concatenation
 
-    if len(bgroup)>0 and i0>0:
+    if len(bgroup)>0 and len(bnumber)>0:
         bletter = bgroup[0][8]
-        i1 = checkinresult.find("</span>", i0)
-        bnumber = str(checkinresult[i0+9:i1])
         msg = "Boarding group = " + bletter + "   Boarding number = " + bnumber
     else:
         msg = "Boarding group and number are unknown"
@@ -408,10 +545,10 @@
 def getDelay(leave_time):
 
     # start with 24 hours before departure and pad it by 3 minutes just to be sure
-    sched_time = time.mktime(leave_time) - 24.0*3600.0
-    sched_time = sched_time + 180.0
+    sched_time = leave_time - 24.0*3600.0
+#    sched_time = sched_time + 180.0
 
-    now = time.time()
+    now = time.time() # UTC
     min_left = int(sched_time - now) / 60
     absmin = abs(min_left)
     days = absmin / 60 / 24
@@ -437,6 +574,22 @@
     print "Departure date/time: ", to_time," Arriving: ", to_arrive
     print "Return date/time:    ", fr_time," Arriving: ", fr_arrive
 
+def makedate(monthday, daytime, TZ):
+    now = time.time()
+    current_time = time.gmtime(now)
+
+    os.environ['TZ'] = TZ
+    time.tzset()
+    to_time = "%s %s %s" % (monthday, daytime, str(current_time[0]))
+    time_struct = time.strptime(to_time, "%b %d %I:%M%p %Y")
+    if time.mktime(time_struct) < (now - (6 * 30 * 24 * 60 * 60)):
+        to_time = "%s %s %s" % (monthday, daytime, str(current_time[0]+1))
+        time_struct = time.strptime(to_time, "%b %d %I:%M%p %Y")
+
+#    print time_struct, time.gmtime(time.mktime(time_struct)) , time.mktime(time_struct)
+
+    return (time_struct, time.gmtime(time.mktime(time_struct)), time.mktime(time_struct))
+    
 # main program
 def main():
 
@@ -445,23 +598,26 @@
 
     # get the current time
     now = time.time()
-    current_time = time.localtime(now)
+    current_time = time.gmtime(now)
+
+    # Convert times to UTC times
+    times = makedate(departure[0], departure[1], departure[3])
+    to_time = time.strftime("%b-%d-%Y %I:%M%p ", times[0]) + departure[3]
+    leave_time_1 = times[2]
+
+    times = makedate(departure[0], departure[2], departure[4])
+    to_arrive = time.strftime("%I:%M%p ", times[0]) + departure[4]
+
+    times = makedate(departure[5], departure[6], departure[8])
+    fr_time = time.strftime("%b-%d-%Y %I:%M%p ", times[0]) + departure[8]
+    leave_time_2 = times[2]
 
-    # this has a limitation that it only works when the flight is in the same year as now
-    # this limitation is because Southwest doesn't print the year on their web page
-    # we could later add some logic to check for a low number months when now is high
-    to_time = "%s %s %s" % (departure[0],departure[1],str(current_time[0]))
-    fr_time = "%s %s %s" % (departure[3],departure[4],str(current_time[0]))
-    to_arrive = "%s %s" % (departure[2],str(current_time[0]))
-    fr_arrive = "%s %s" % (departure[5],str(current_time[0]))
-
-    # parse the two departure times into the same format
-    leave_time_1 = time.strptime(to_time,"%b %d %I:%M%p %Y")
-    leave_time_2 = time.strptime(fr_time,"%b %d %I:%M%p %Y")
+    times = makedate(departure[5], departure[7], departure[9])
+    fr_arrive = time.strftime("%I:%M%p ", times[0]) + departure[9]
 
     # print some information to the terminal for confirmation purposes
     displayFlightInfo(to_time, to_arrive, fr_time, fr_arrive)
-    print "Current time: ", time.strftime("%b %d %I:%M%p %Y",current_time)
+    print "Current time: ", time.strftime("%b %d %I:%M%p %Y UTC",current_time)
 
     # find the time when we can check in within the 24 hour time window
     sched_1, msg_depart = getDelay(leave_time_1)
@@ -473,28 +629,37 @@
     # also we check to see if we have at least an hour before departure
     # if not then we skip to the return flight - if that fails we do nothing
     msg1 = "Please wait for the scheduler to check in the departing flight..."
-    msg2 = "Departing flight has been checked in - see sw-success.htm for details"
+    msg2 = "Departing flight has been checked in - see boardingpass.htm for details"
     msg3 = ""
     if sched_1 > now:
         print msg_depart
         ev1 = sch.enterabs(sched_1, 1, getBoardingPass, (checkin_url, confirmation, firstname, lastname))
-    elif (time.mktime(leave_time_1)-3660.0) > now:
+    elif (leave_time_1-3660.0) > now:
         print msg_depart
         print "Obtaining departing flight boarding pass right now..."
         msg3 = getBoardingPass(checkin_url, confirmation, firstname, lastname)
+
+    # run the scheduled events
+    # nothing will be printed during this long pause, so run in the background
+    if not sch.empty():
+        print msg1
+        if DEBUG_SCH==0: sch.run()
+
+    print msg2
+    print msg3
+
+    # schedule the second pass or call it now if we are within an hour
+    msg1 = "Please wait for the scheduler to check in the returning flight..."
+    msg2 = "Returning flight has been checked in - see boardingpass.htm for details"
+    sched_2, msg_depart = getDelay(leave_time_2)
+    print msg_depart
+    if sched_2 > now:
+        ev2 = sch.enterabs(sched_2, 1, getBoardingPass, (checkin_url, confirmation, firstname, lastname))
+    elif (leave_time_2-3660.0) > now:
+        print "Obtaining returning flight boarding pass right now..."
+        msg3 = getBoardingPass(checkin_url, confirmation, firstname, lastname)
     else:
-        # schedule the second pass or call it now if we are within an hour
-        msg1 = "Please wait for the scheduler to check in the returning flight..."
-        msg2 = "Returning flight has been checked in - see sw-success.htm for details"
-        sched_2, msg_depart = getDelay(leave_time_2)
-        print msg_depart
-        if sched_2 > now:
-            ev2 = sch.enterabs(sched_2, 1, getBoardingPass, (checkin_url, confirmation, firstname, lastname))
-        elif (time.mktime(leave_time_2)-3660.0) > now:
-            print "Obtaining returning flight boarding pass right now..."
-            msg3 = getBoardingPass(checkin_url, confirmation, firstname, lastname)
-        else:
-            msg2 = "It is too late to check in the returning flight"
+        msg2 = "It is too late to check in the returning flight"
 
     # run the scheduled events
     # nothing will be printed during this long pause, so run in the background
