/* TODO: Refactor to smaller functions * Checks if the email needs to be parsed for Door Dash or GrubHub, * and saves the order to file for reference */ private static Order HandleMessage(string messageId) { Debug.WriteLine("Handling message: " + messageId); bool isGrubHubOrder = false; string base64Input = null; //the input to be converted to base64url encoding format string fileName = null; //the file name without the complete path string storageDir = null; //the file saving directory string filePath = null; //the full path to the file var emailResponse = GetMessage(service, userId, messageId); if (emailResponse == null) { Debug.WriteLine("Message deleted, returning"); return(null); } var headers = emailResponse.Payload.Headers; MessagePartHeader dateHeader = headers.FirstOrDefault(item => item.Name == "Received"); MessagePartHeader fromHeader = headers.FirstOrDefault(item => item.Name == "From"); DateTime dateTime = ParseDateTime(dateHeader.Value); string senderAddress = fromHeader.Value; //Check if the email is from GrubHub if (fromHeader.Value == "*****@*****.**") { Debug.WriteLine("Email Type: GrubHub"); isGrubHubOrder = true; try { var body = emailResponse.Payload.Body.Data; base64Input = body; fileName = messageId + ".html"; storageDir = GrubHubDir; } catch (Exception e) { Debug.WriteLine(e.Message); } //Otherwise check if the email is from DoorDash } else if (fromHeader.Value == @"DoorDash <*****@*****.**>") { Debug.WriteLine("Email Type: DoorDash"); try { var attachId = emailResponse.Payload.Parts[1].Body.AttachmentId; //Need to do another API call to get the actual attachment from the attachment id MessagePartBody attachPart = GetAttachment(service, userId, messageId, attachId); base64Input = attachPart.Data; fileName = messageId + ".pdf"; storageDir = DoorDashDir; }catch (Exception e) { Debug.WriteLine(e.Message); } //The email is refers to a cancelled GrubHub order, so set the associated OrderCons' status to cancelled } else if (fromHeader.Value == "*****@*****.**") { Debug.WriteLine("From [email protected]"); MessagePartHeader subjectHeader = headers.FirstOrDefault(item => item.Name == "Subject"); string orderNum = subjectHeader.Value.Split(' ')[1]; //gets orderNum from ("Order" {orderNum} "Cancelled") Debug.WriteLine("OrderNum: " + orderNum); if (form1.InvokeRequired) { form1.Invoke((MethodInvoker) delegate { form1.SetOrderToCancelled(orderNum); }); } else { form1.SetOrderToCancelled(orderNum); } return(null); //The email is irrelevant } else { Debug.WriteLine("Not an order, returning"); return(null); } byte[] data = FromBase64ForUrlString(base64Input); filePath = Path.Combine(storageDir, fileName); //Saves the order to file if it doesn't exist if (!File.Exists(filePath)) { Debug.WriteLine("Writing new file: " + fileName); File.WriteAllBytes(filePath, data); } else { Debug.WriteLine("File already exists: " + fileName); } Debug.WriteLine("----------------------"); if (isGrubHubOrder) { GrubHubParser grubHubParser = new GrubHubParser(); string decodedBody = Encoding.UTF8.GetString(data); Order order = grubHubParser.ParseOrder(decodedBody, dateTime, messageId); if (DebugPrint) { order.PrintOrder(); } return(order); } else { DoorDashParser doorDashParser = new DoorDashParser(); List <string> lines = doorDashParser.ExtractTextFromPDF(filePath, messageId); Order order = doorDashParser.ParseOrder(lines, dateTime, messageId); string s = emailResponse.Payload.Parts[0].Parts[1].Body.Data; byte[] data2 = FromBase64ForUrlString(emailResponse.Payload.Parts[0].Parts[1].Body.Data); string decodedBody = Encoding.UTF8.GetString(data2); doorDashParser.ParseConfirmURL(order, decodedBody); if (DebugPrint) { order.PrintOrder(); } return(order); } }
/* Extracts the order from an grubhub html file and returns an Order object */ public Order ParseOrder(string html, DateTime dateTime, string messageId) { Order order = new Order(); order.Service = "GrubHub"; order.TimeReceived = dateTime; order.MessageId = messageId; var htmlDoc = new HtmlDocument(); htmlDoc.LoadHtml(html); //We begin setting the possible locations of the relevant information, and then choose which one depending on the format on the html doc string metaInfoLoc1 = "//body/table/tbody/tr/td/table/tbody/tr/td/table[3]/tbody/tr/th[2]/table/tbody/tr/th/div/div[2]/div/div"; string metaInfoLoc2 = "//body/table/tbody/tr/td/table/tbody/tr/td/table[4]/tbody/tr/th[2]/table/tbody/tr/th/div/div/div/div"; string metaInfoLoc3 = "//body/table/tbody/tr/td/table/tbody/tr/td/table[5]/tbody/tr/th[2]/table/tbody/tr/th/div/div/div/div"; string metaInfoBaseLoc1 = "//body/table/tbody/tr/td/table/tbody/tr/td/table[3]"; string metaInfoBaseLoc2 = "//body/table/tbody/tr/td/table/tbody/tr/td/table[4]"; string metaInfoBaseLoc3 = "//body/table/tbody/tr/td/table/tbody/tr/td/table[5]"; string delivMethodLoc1 = @"/tbody/tr/th/table/tbody/tr/th/div/div[2]/div/span/span"; string delivMethodLoc2 = @"/tbody/tr/th/table/tbody/tr/th/div/div/div/span/span"; string delivMethodLoc3 = @"/tbody/tr/th/table/tbody/tr/th/div/div/div/span/span"; string pickUpTimeLoc1 = @"/tbody/tr/th/table/tbody/tr/th/div/div[2]/div[2]/span"; string pickUpTimeLoc2 = @"/tbody/tr/th/table/tbody/tr/th/div/div/div[2]/span"; string pickUpTimeLoc3 = @"/tbody/tr/th/table/tbody/tr/th/div/div/div[2]/span"; HtmlNodeCollection metaInfoNodes = null; HtmlNode deliveryMethodNode = null; HtmlNode pickupTimeNode = null; //If this is not null, the order is in standard format if (htmlDoc.DocumentNode.SelectNodes(metaInfoLoc1) != null) { //Debug.WriteLine("Using Standard format"); metaInfoNodes = htmlDoc.DocumentNode.SelectNodes(metaInfoLoc1); deliveryMethodNode = htmlDoc.DocumentNode.SelectSingleNode(metaInfoBaseLoc1 + delivMethodLoc1); pickupTimeNode = htmlDoc.DocumentNode.SelectSingleNode(metaInfoBaseLoc1 + pickUpTimeLoc1); } //Otherwise the previous loc is null and there's an extra <table> "SCHEDULED ORDER" before the pickup/delivery <table> //so we use location 2 else if (htmlDoc.DocumentNode.SelectNodes(metaInfoLoc2) != null) { //Debug.WriteLine("Using Scheduled Order format"); metaInfoNodes = htmlDoc.DocumentNode.SelectNodes(metaInfoLoc2); deliveryMethodNode = htmlDoc.DocumentNode.SelectSingleNode(metaInfoBaseLoc2 + delivMethodLoc2); pickupTimeNode = htmlDoc.DocumentNode.SelectSingleNode(metaInfoBaseLoc2 + pickUpTimeLoc2); } //Otherwise the previous loc is null and there's also an extra <table> "ORDER ADJUSTMENT" before the relevant <table> //so we use location 3 else if (htmlDoc.DocumentNode.SelectNodes(metaInfoLoc3) != null) { //Debug.WriteLine("Using Adjusted Order format"); metaInfoNodes = htmlDoc.DocumentNode.SelectNodes(metaInfoLoc3); deliveryMethodNode = htmlDoc.DocumentNode.SelectSingleNode(metaInfoBaseLoc3 + delivMethodLoc3); pickupTimeNode = htmlDoc.DocumentNode.SelectSingleNode(metaInfoBaseLoc3 + pickUpTimeLoc3); } //If location 3 is still null, then the format is not recognized else { //Debug.WriteLine("Cannot parse order - format not recognized"); return(null); } var orderNumberNode = htmlDoc.DocumentNode.SelectSingleNode("//body/table/tbody/tr/td/table/tbody/tr/td/table[2]/tbody/tr/th/table/tbody/tr/th/div/div[4]/span[2]"); ParseOrderNumber(orderNumberNode, order); ParsePickUpTime(pickupTimeNode, order); int metaDivCount = metaInfoNodes.Count; //the # of <div> elems int nonItemCount = 5; //the last 5 <tr>'s of the <tbody> is meta information int lastAddressIndex = metaDivCount - 3; //We need to know whether it's DELIVERY or PICKUP because there's a difference in the html structure if (deliveryMethodNode.InnerHtml.Trim() == "DELIVERY") { nonItemCount = 6; } else { order.DeliveryMethod = "Pickup"; } var pickupByNameNode = metaInfoNodes.ElementAt(1); ParsePickupName(pickupByNameNode, order); ParseContactNumber(metaInfoNodes.ElementAt(metaDivCount - 1), order); var orderContentNodes = htmlDoc.DocumentNode.SelectNodes("//td[@class='orderSummary__data']"); //order.UniqueItemCount = orderContentNodes.Count - nonItemCount; //Loops through all the td nodes with class = "orderSummary__data" //Parses itemName from td's with inner structure <div> //Parses addOns from td's with inner structure <div> <ul> //Does nothing for td's with no css child nodes //Stops parsing when hitting the td with inner node <span>, or when there are no more tdNodes to scan bool parsedItemName = false; Item item = null; for (int i = 0; i < orderContentNodes.Count; i++) { var tdNode = orderContentNodes[i]; string spanNodePath = tdNode.XPath + "/span"; var spanNode = tdNode.SelectSingleNode(spanNodePath); if (spanNode != null) { //Debug.WriteLine("Hit td with <span> - breaking"); break; } var divNode = tdNode.Element("div"); if (divNode == null) { //Debug.WriteLine("No childs nodes - no addons"); continue; } if (divNode.Element("ul") != null) { //Debug.WriteLine("contains addOns"); ParseAddOns(divNode.Element("ul"), item); } else { //Since Special Instructions and ItemNames have the same html strucuture, //we need manually check the innerHTML to decide which it is if (divNode.InnerHtml.Trim().StartsWith("Instructions")) { ParseSpecialInstruction(divNode, item); } else { //Debug.WriteLine("is an itemName"); if (item != null) { order.ItemList.Add(item); //add the previous item if it's not null, then handle the new item } item = new Item(); ParseItemName(divNode, item); ParseQuantity(tdNode, item); ParseItemType(divNode, item); } } //SetItemCount(i + 1, order.UniqueItemCount, item); } order.ItemList.Add(item); //add the last item since it won't be added without a next item DoorDashParser.SetDrinkAndSnackCount(order); DoorDashParser.SetOrderSize(order); //Need to move function to be general ParseConfirmURL(order, htmlDoc); return(order); }