/// <summary> /// Get latest aircraftlist.json /// </summary> public static void GetAircraft() { JObject responseJson; try { //Generate aircraftlist.json url string url = ""; if (!Settings.acListUrl.Contains("?")) { url = Settings.acListUrl + "?trFmt=f&refreshTrails=1&lat=" + Settings.Lat + "&lng=" + Settings.Long; } else { url = Settings.acListUrl + "&trFmt=f&refreshTrails=1&lat=" + Settings.Lat + "&lng=" + Settings.Long; } //Create request request = (HttpWebRequest)WebRequest.Create(url); request.Method = "GET"; request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; request.Timeout = Settings.timeout * 1000; //Add credentials if they are provided if (Settings.VRSAuthenticate) { string encoded = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(Settings.VRSUsr + ":" + Settings.VRSPwd)); request.Headers.Add("Authorization", "Basic " + encoded); } //Send request and parse json response using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) using (Stream responsestream = response.GetResponseStream()) using (StreamReader reader = new StreamReader(responsestream)) using (JsonTextReader jsonreader = new JsonTextReader(reader)) responseJson = JsonSerializer.Create().Deserialize <JObject>(jsonreader); //Check if we actually got aircraft data if (responseJson["acList"] == null) { Core.UI.writeToConsole("ERROR: Invalid response recieved from server", Color.Red); Thread.Sleep(5000); return; } //Throw error if server time was not parsed if (responseJson["stm"] == null) { throw new JsonReaderException(); } //Parse aircraft data Core.aircraftlist.Clear(); foreach (JObject a in responseJson["acList"].ToList()) { //Ignore if no icao is provided if (a["Icao"] == null) { continue; } //Create new aircraft Core.Aircraft aircraft = new Core.Aircraft(a["Icao"].ToString()); //Parse aircraft trail if (a["Cot"] != null) { aircraft.Trail = new double[a["Cot"].Count()]; } for (int i = 0; i < aircraft.Trail.Length - 1; i++) { if (a["Cot"][i].Value <string>() != null) { aircraft.Trail[i] = Convert.ToDouble(a["Cot"][i].Value <string>()); } else { aircraft.Trail[i] = 0; } } //Parse aircraft properties List <JProperty> properties = a.Properties().ToList(); for (int i = 0; i < properties.Count(); i++) { aircraft.AddProperty(properties[i].Name, properties[i].Value.ToString()); } properties = null; //Add aircraft to list Core.aircraftlist.Add(aircraft); } //Get list of receivers Core.receivers.Clear(); foreach (JObject f in responseJson["feeds"]) { Core.receivers.Add(new Core.Reciever(f["id"].ToString(), f["name"].ToString())); } //Try to clean up json parsing responseJson.RemoveAll(); responseJson = null; GC.Collect(2, GCCollectionMode.Forced); } catch (UriFormatException) { Core.UI.writeToConsole("ERROR: AircraftList.json url invalid (" + Settings.acListUrl + ")", Color.Red); return; } catch (InvalidDataException) { Core.UI.writeToConsole("ERROR: Data returned from " + Settings.acListUrl + " was not gzip compressed", Color.Red); return; } catch (WebException e) { Core.UI.writeToConsole("ERROR: Error while connecting to AircraftList.json (" + e.Message + ")", Color.Red); return; } }
/// <summary> /// Constructor /// </summary> public static void Start() { //Name of receiver that provided the aircraft information string recieverName; //Number of triggers matching for condition int triggersMatching; //VRS name of property string propertyInternalName; //Index of match in waiting matches int wmIcaoIndex; //Alert message to send MailMessage message; //Text to display email property value string emailPropertyInfo; while (ThreadManager.threadStatus != ThreadManager.CheckerStatus.Stopping) { ThreadManager.threadStatus = ThreadManager.CheckerStatus.Running; //Set next check time nextCheck = DateTime.Now.AddSeconds(Settings.refreshRate); //Notify user that aircraft info is being downloaded Core.UI.updateStatusLabel("Downloading Aircraft Info..."); recieverName = ""; //Get latest aircraft information GetAircraft(); //Check if there are aircraft to check if (Core.aircraftlist.Count != 0) { //Aircraft number to be shown on UI int aircraftCount = 1; //Current condition being checked Core.Condition condition; foreach (Core.Aircraft aircraft in Core.aircraftlist.ToList()) { //Update UI with aircraft being checked Core.UI.updateStatusLabel("Checking conditions for aircraft " + aircraftCount + " of " + Core.aircraftlist.Count()); aircraftCount++; //Iterate conditions foreach (int conditionid in Core.conditions.Keys.ToList()) { condition = Core.conditions[conditionid]; //Skip if condition is disabled or condition is already matched if (condition.alertType == Core.AlertType.Disabled || (Core.activeMatches.ContainsKey(aircraft.ICAO) && Core.activeMatches[aircraft.ICAO].Conditions.Exists(x => x.ConditionID == conditionid))) { continue; } triggersMatching = 0; //Iterate triggers for condition foreach (Core.Trigger trigger in condition.triggers.Values) { //LEGEND //A = Equals/Not Equals //B = Higher Than + Lower Than //C = True/False Boolean //D = Starts With + Ends With //E = Contains //Get internal name for property to compare propertyInternalName = Core.vrsPropertyData[trigger.Property][2].ToString(); //If aircraft properties do not contain property, skip if (aircraft.GetProperty(propertyInternalName) == null) { continue; } //Check property against value if (trigger.ComparisonType == "Equals" && aircraft.GetProperty(propertyInternalName) == trigger.Value) { triggersMatching++; } else if (trigger.ComparisonType == "Not Equals" && aircraft.GetProperty(propertyInternalName) != trigger.Value) { triggersMatching++; } else if (trigger.ComparisonType == "Contains" && aircraft.GetProperty(propertyInternalName).Contains(trigger.Value)) { triggersMatching++; } else if (trigger.ComparisonType == "Higher Than" && Convert.ToDouble(aircraft.GetProperty(propertyInternalName)) > Convert.ToDouble(trigger.Value)) { triggersMatching++; } else if (trigger.ComparisonType == "Lower Than" && Convert.ToDouble(aircraft.GetProperty(propertyInternalName)) < Convert.ToDouble(trigger.Value)) { triggersMatching++; } else if (trigger.ComparisonType == "Starts With" && (aircraft.GetProperty(propertyInternalName).Length > trigger.Value.Length && aircraft.GetProperty(propertyInternalName).Substring(0, trigger.Value.Length) == trigger.Value)) { triggersMatching++; } else if (trigger.ComparisonType == "Ends With" && (aircraft.GetProperty(propertyInternalName).ToString().Length > trigger.Value.Length && aircraft.GetProperty(propertyInternalName).Substring(aircraft.GetProperty(propertyInternalName).Length - trigger.Value.Length) == trigger.Value)) { triggersMatching++; } } //Get position in waiting matches wmIcaoIndex = -1; for (int i = 0; i < Core.waitingMatches.Count; i++) { if (Core.waitingMatches[i][0] == aircraft.ICAO && Core.waitingMatches[i][1] == conditionid.ToString()) { wmIcaoIndex = i; break; } } //If condition doesn't exist in waiting matches and all triggers match, add to waiting matches if (wmIcaoIndex == -1 && triggersMatching == condition.triggers.Count) { Core.waitingMatches.Add(new string[] { aircraft.ICAO, conditionid.ToString() }); //If ignore following is true, skip the following conditions if (condition.ignoreFollowing) { break; } else { continue; } } //If condition exists in waiting matches with this condition id, remove from waiting matches and send alert if (wmIcaoIndex != -1 && Core.waitingMatches[wmIcaoIndex][1] == conditionid.ToString()) { Core.waitingMatches.RemoveAt(wmIcaoIndex); //Get receiver name foreach (Core.Reciever reciever in Core.receivers) { if (reciever.Id == aircraft.GetProperty("Rcvr")) { recieverName = reciever.Name; } } //Create email message message = new MailMessage(); //Set subject and email property info if (aircraft.GetProperty(Core.vrsPropertyData[(Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), condition.emailProperty.ToString())][2].ToString()) == null) { message.Subject = "First Contact Alert! " + condition.conditionName; emailPropertyInfo = condition.emailProperty.ToString() + ": No Value"; } else { message.Subject = "First Contact Alert! " + condition.conditionName + ": " + aircraft.GetProperty(Core.vrsPropertyData[(Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), condition.emailProperty.ToString())][2].ToString()); emailPropertyInfo = condition.emailProperty.ToString() + ": " + aircraft.GetProperty(Core.vrsPropertyData[(Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), condition.emailProperty.ToString())][2].ToString()); } //If active matches contains aircraft, add condition to the match if (Core.activeMatches.ContainsKey(aircraft.ICAO)) { Core.activeMatches[aircraft.ICAO].AddCondition(conditionid, condition, aircraft); } //Else add to active matches else { Core.Match m = new Core.Match(aircraft.ICAO); m.AddCondition(conditionid, condition, aircraft); Core.activeMatches.Add(aircraft.ICAO, m); } //Update stats and log to console Stats.updateStats(); Core.UI.writeToConsole(DateTime.Now.ToLongTimeString() + " | ADDED | " + aircraft.ICAO + " | Condition: " + condition.conditionName + " (" + emailPropertyInfo + ")", Color.LightGreen); //Send alert to emails in condition if (condition.alertType == Core.AlertType.First_and_Last_Contact || condition.alertType == Core.AlertType.First_Contact) { foreach (string email in condition.recieverEmails) { Email.SendEmail(email, message, condition, aircraft, recieverName, emailPropertyInfo, true); } } } } //If active matches contains this aircraft, update aircraft info if (Core.activeMatches.ContainsKey(aircraft.ICAO)) { foreach (Core.MatchedCondition c in Core.activeMatches[aircraft.ICAO].Conditions) { c.AircraftInfo = aircraft; } } } //Check if aircraft have lost signal and remove aircraft that have timed out Core.UI.updateStatusLabel("Checking aircraft are still on radar..."); //Iterate active matches foreach (Core.Match match in Core.activeMatches.Values.ToList()) { //Iterate match conditions foreach (Core.MatchedCondition c in match.Conditions) { //Check if signal has been lost for more than the removal timeout if (match.SignalLostTime != DateTime.MinValue && DateTime.Compare(match.SignalLostTime, DateTime.Now.AddSeconds((Settings.removalTimeout - (Settings.removalTimeout * 2)))) < 0) { //Log to UI Core.UI.writeToConsole(DateTime.Now.ToLongTimeString() + " | REMOVING | " + match.Icao + " | " + c.DisplayName, Color.Orange); //Remove from active matches Core.activeMatches.Remove(match.Icao); //Update aircraft info Core.Aircraft aircraft = c.AircraftInfo; //Alert if alert type is both or last if (c.Match.alertType == Core.AlertType.First_and_Last_Contact || c.Match.alertType == Core.AlertType.Last_Contact) { condition = c.Match; //Create new email message message = new MailMessage(); //Set email subject and email property info if (aircraft.GetProperty(Core.vrsPropertyData[(Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), condition.emailProperty.ToString())][2].ToString()) == null) { message.Subject = "Last Contact Alert! " + condition.conditionName; emailPropertyInfo = condition.emailProperty.ToString() + ": No Value"; } else { message.Subject = "Last Contact Alert! " + condition.conditionName + ": " + aircraft.GetProperty(Core.vrsPropertyData[(Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), condition.emailProperty.ToString())][2].ToString()); emailPropertyInfo = condition.emailProperty.ToString() + ": " + aircraft.GetProperty(Core.vrsPropertyData[(Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), condition.emailProperty.ToString())][2].ToString()); } //Make a ding noise or something SystemSounds.Exclamation.Play(); //Show notification Core.UI.notifyIcon.ShowBalloonTip(5000, "Plane Alert!", "Condition: " + condition.conditionName + " (" + emailPropertyInfo + ")\nRegistration: " + aircraft.GetProperty("Reg"), ToolTipIcon.Info); //Send alerts to all the emails foreach (string email in condition.recieverEmails) { Email.SendEmail(email, message, condition, aircraft, recieverName, emailPropertyInfo, false); } } break; } //Check if signal has been lost/returned bool stillActive = false; foreach (Core.Aircraft aircraft in Core.aircraftlist) { if (aircraft.ICAO == match.Icao) { stillActive = true; } } if (!stillActive && match.SignalLost == false) { Core.activeMatches[match.Icao].SignalLostTime = DateTime.Now; Core.activeMatches[match.Icao].SignalLost = true; Core.UI.writeToConsole(DateTime.Now.ToLongTimeString() + " | LOST SGNL | " + match.Icao + " | " + match.DisplayName, Color.LightGoldenrodYellow); } if (stillActive && match.SignalLost == true) { Core.activeMatches[match.Icao].SignalLost = false; Core.UI.writeToConsole(DateTime.Now.ToLongTimeString() + " | RETND SGNL | " + match.Icao + " | " + match.DisplayName, Color.LightGoldenrodYellow); } } } } //Cancel if thread is supposed to stop if (ThreadManager.threadStatus == ThreadManager.CheckerStatus.Stopping) { return; } //Set thread status to waiting Core.UI.updateStatusLabel("Waiting for next check..."); ThreadManager.threadStatus = ThreadManager.CheckerStatus.Waiting; //Wait until the next check time while (DateTime.Compare(DateTime.Now, nextCheck) < 0) { //Cancel if thread is supposed to stop if (ThreadManager.threadStatus == ThreadManager.CheckerStatus.Stopping) { return; } Thread.Sleep(1000); } } }
/// <summary> /// Send alert email /// </summary> /// <param name="emailaddress">Email address to send to</param> /// <param name="message">Message to send</param> /// <param name="condition">Condition that triggered alert</param> /// <param name="aircraft">Aircraft information for matched aircraft</param> /// <param name="recieverName">Name of receiver that got the last aircraft information</param> /// <param name="emailPropertyInfo">String for displaying email property value</param> /// <param name="isDetection">Is this a detection, not a removal?</param> public static void SendEmail(string emailaddress, MailMessage message, Core.Condition condition, Core.Aircraft aircraft, string recieverName, string emailPropertyInfo, bool isDetection) { //Array to store position trail Dictionary <int, string[]> pathArray = new Dictionary <int, string[]>(); //Google status map for position trail's url string staticMapUrl = ""; //Google maps url string googleMapsUrl = ""; //Transponder type from aircraft info string transponderName = ""; //Types of transponders string[] transponderTypes = new string[] { "Unknown", "Mode-S", "ADS-B", "ADS-Bv1", "ADS-Bv2" }; //Aircraft image urls string[] imageLinks = new string[2]; //Table for displaying aircraft property values string aircraftTable = "<table style='border: 2px solid;border-radius: 10px;border-spacing: 0px;' id='acTable'>"; //HTML for aircraft images string imageHTML = ""; //Airframes.org url string airframesUrl = ""; //Report url string reportUrl = ""; //Set message type to html message.IsBodyHtml = true; //Add email to message receiver list try { message.To.Clear(); message.To.Add(emailaddress); } catch { Core.UI.writeToConsole("ERROR: Email to send to is invalid (" + emailaddress + ")", Color.Red); return; } //Set sender email to the one set in settings try { message.From = new MailAddress(Settings.senderEmail, "PlaneAlerter Alerts"); } catch { Core.UI.writeToConsole("ERROR: Email to send from is invalid (" + Settings.senderEmail + ")", Color.Red); return; } if (Settings.EmailContentConfig.TwitterOptimised) { string typestring = "First Contact"; string regostring = "No rego, "; string callsignstring = "No callsign, "; string operatorstring = "No operator, "; string emailpropertystring = "No " + condition.emailProperty.ToString(); if (!isDetection) { typestring = "Last Contact"; } if (aircraft.GetProperty("Reg") != null) { regostring = aircraft.GetProperty("Reg") + ", "; } if (aircraft.GetProperty("Call") != null) { callsignstring = aircraft.GetProperty("Call") + ", "; } if (aircraft.GetProperty("OpIcao") != null) { operatorstring = aircraft.GetProperty("OpIcao") + ", "; } string emailpropertyvalue = aircraft.GetProperty(Core.vrsPropertyData[condition.emailProperty][2]); if (emailpropertyvalue != null) { emailpropertystring = emailpropertyvalue; } message.Body = "[" + typestring + "] " + condition.conditionName + " (" + emailpropertystring + "), " + regostring + operatorstring + aircraft.GetProperty("Type") + ", " + callsignstring + Settings.radarUrl; //[Last Contact] USAF (RCH9701), 79-1951, RCH, DC10, RCH9701 http://seqldradar.net } else { //Create request for aircraft image urls HttpWebRequest request = (HttpWebRequest)WebRequest.Create(Settings.acListUrl.Substring(0, Settings.acListUrl.LastIndexOf("/") + 1) + "AirportDataThumbnails.json?icao=" + aircraft.ICAO + "&numThumbs=" + imageLinks.Length); request.Method = "GET"; request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; request.Timeout = 5000; //If vrs authentication is used, add credentials to request if (Settings.VRSAuthenticate) { string encoded = Convert.ToBase64String(Encoding.GetEncoding("ISO-8859-1").GetBytes(Settings.VRSUsr + ":" + Settings.VRSPwd)); request.Headers.Add("Authorization", "Basic " + encoded); } //Send request and parse response try { //Get response HttpWebResponse imageResponse = (HttpWebResponse)request.GetResponse(); byte[] imageResponseBytes = new byte[32768]; imageResponse.GetResponseStream().Read(imageResponseBytes, 0, 32768); string imageResponseText = Encoding.ASCII.GetString(imageResponseBytes); //Parse json JObject imageResponseJson = (JObject)JsonConvert.DeserializeObject(imageResponseText); //If status is not 404, add images to image HTML if (imageResponseJson["status"].ToString() != "404") { foreach (JObject image in imageResponseJson["data"]) { imageHTML += "<img style='margin: 5px;border: 2px solid;border-radius: 10px;' src='" + image + "' />"; } } imageHTML += "<br>"; } catch (Exception) { } //If aircraft has a position, generate a google map url if (aircraft.GetProperty("Lat") != null) { if (aircraft.Trail.Count() != 3) { staticMapUrl = "http://maps.googleapis.com/maps/api/staticmap?center=" + aircraft.GetProperty("Lat") + "," + aircraft.GetProperty("Long") + "&size=800x800&markers=" + aircraft.GetProperty("Lat") + "," + aircraft.GetProperty("Long") + "&key=AIzaSyCJxiyiDWBHiYSMm7sjSTJkQubuo3XuR7s&path=color:0x000000|"; } else { staticMapUrl = "http://maps.googleapis.com/maps/api/staticmap?center=" + aircraft.GetProperty("Lat") + "," + aircraft.GetProperty("Long") + "&size=800x800&zoom=8&markers=" + aircraft.GetProperty("Lat") + "," + aircraft.GetProperty("Long") + "&key=AIzaSyCJxiyiDWBHiYSMm7sjSTJkQubuo3XuR7s&path=color:0x000000|"; } } //Process aircraft trail for (int i = 0; i < aircraft.Trail.Count() / 3; i++) { //Get coordinate string[] coord = new string[] { aircraft.Trail[i * 3].ToString(), aircraft.Trail[i * 3 + 1].ToString(), aircraft.Trail[i * 3 + 2].ToString() }; //Add coordinate to google map url staticMapUrl += coord[0] + "," + coord[1]; //If this is not the last coordinate, add a separator if (i != (aircraft.Trail.Count() / 3) - 1) { staticMapUrl += "|"; } } //Generate google maps url googleMapsUrl = "https://www.google.com.au/maps/search/" + aircraft.GetProperty("Lat") + "," + aircraft.GetProperty("Long"); //Generate airframes.org url if (aircraft.GetProperty("Reg") != null && aircraft.GetProperty("Reg") != "") { airframesUrl = "<h3><a style='text-decoration: none;' href='http://www.airframes.org/reg/" + aircraft.GetProperty("Reg").Replace("-", "").ToUpper() + "'>Airframes.org Lookup</a></h3>"; } //Generate radar url try { if (Settings.radarUrl[Settings.radarUrl.Length - 1] == '/') { reportUrl = Settings.radarUrl + "desktopReport.html?icao-Q=" + aircraft.ICAO; } else { reportUrl = Settings.radarUrl + "/desktopReport.html?icao-Q=" + aircraft.ICAO; } } catch { Core.UI.writeToConsole("ERROR: No radar url specified.", Color.Red); ThreadManager.StartOrRestart(); } //Get name of transponder type transponderName = transponderTypes[Convert.ToInt32(aircraft.GetProperty("Trt")) - 1]; //Write to log and UI Core.logFileSW.WriteLine(DateTime.Now.ToLongTimeString() + " | SENDING ALERT: " + Environment.NewLine + aircraft.ToString() + Environment.NewLine + Environment.NewLine); Core.UI.writeToConsole(DateTime.Now.ToLongTimeString() + " | SENDING | " + aircraft.ICAO + " | Condition: " + condition.conditionName + " (" + emailPropertyInfo + ")", Color.LightBlue); //Generate aircraft property value table bool isAlternateStyle = false; foreach (string child in aircraft.GetPropertyKeys()) { //TODO ADD TO VRSPROPERTIES string parameter = "UNKNOWN_PARAMETER"; //Set parameter to a readable name if it's not included in vrs property info switch (child.ToString()) { case "CNum": parameter = "Aircraft_Serial"; break; case "EngMount": parameter = "Engine_Mount"; break; case "FSeen": parameter = "First_Seen"; break; case "HasPic": parameter = "Has_Picture"; break; case "Id": parameter = "Id"; break; case "Lat": parameter = "Latitude"; break; case "Long": parameter = "Longitude"; break; case "PosTime": parameter = "Position_Time"; break; case "ResetTrail": parameter = "Reset_Trail"; break; case "Tisb": parameter = "TIS-B"; break; case "Trak": parameter = "Track"; break; case "TrkH": parameter = "Is_Track_Heading"; break; case "Year": parameter = "Year"; break; } //If parameter is set (not in vrs property info list) and property list type is set to essentials, skip this property if (parameter != "UNKNOWN_PARAMETER" && Settings.EmailContentConfig.PropertyList == Core.PropertyListType.Essentials) { continue; } //Get parameter information from vrs property info if (parameter == "UNKNOWN_PARAMETER") { foreach (Core.vrsProperty property in Core.vrsPropertyData.Keys) { if (Core.vrsPropertyData[property][2] == child.ToString()) { //If property list type is essentials and this property is not in the list of essentials, leave this property as unknown so it can be skipped if (Settings.EmailContentConfig.PropertyList == Core.PropertyListType.Essentials && !Core.essentialProperties.Contains(property)) { continue; } parameter = property.ToString(); } } if (parameter == "UNKNOWN_PARAMETER") { continue; } } //Add html for property if (isAlternateStyle) { aircraftTable += "<tr style='background-color:#CCC'>"; } else { aircraftTable += "<tr>"; } aircraftTable += "<td style='padding: 3px;font-weight:bold;'>" + parameter + "</td>"; aircraftTable += "<td style='padding: 3px'>" + aircraft.GetProperty(child) + "</td>"; aircraftTable += "</tr>"; isAlternateStyle = !isAlternateStyle; } aircraftTable += "</table>"; //Create the main html document message.Body = "<!DOCTYPE html PUBLIC ' -W3CDTD XHTML 1.0 TransitionalEN' 'http:www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'><html><body>"; if (isDetection) { message.Body += "<h1>Plane Alert - Last Contact</h1>"; } else { message.Body += "<h1>Plane Alert - First Contact</h1>"; } //Condition name + email property message.Body += "<h2 style='margin: 0px;margin-bottom: 2px;margin-left: 10px;'>Condition: " + condition.conditionName + " (" + emailPropertyInfo + ")</h2>"; //Receiver name if (Settings.EmailContentConfig.ReceiverName) { message.Body += "<h2 style='margin: 0px;margin-bottom: 2px;margin-left: 10px;'>Reciever: " + recieverName + "</h2>"; } //Transponder type if (Settings.EmailContentConfig.TransponderType) { message.Body += "<h2 style='margin: 0px;margin-bottom: 2px;margin-left: 10px;'>Transponder: " + transponderName + "</h2>"; } //Radar url if (Settings.EmailContentConfig.RadarLink) { message.Body += "<h3><a style='text-decoration: none;' href='" + Settings.radarUrl + "'>Goto Radar</a></h3>"; } //Report url if (Settings.EmailContentConfig.ReportLink) { message.Body += "<h3><a style='text-decoration: none;' href='" + reportUrl + "'>VRS Report Lookup</a></h3>"; } //Airframes.org url if (Settings.EmailContentConfig.AfLookup) { message.Body += airframesUrl; } message.Body += "<table><tr><td>"; //Property list if (Settings.EmailContentConfig.PropertyList != Core.PropertyListType.Hidden) { message.Body += aircraftTable; } message.Body += "</td><td style='padding: 10px;vertical-align: top;'>"; //Aircraft photos if (Settings.EmailContentConfig.AircraftPhotos) { message.Body += imageHTML; } //Map if (Settings.EmailContentConfig.Map && aircraft.GetProperty("Lat") != null) { message.Body += "<h3 style='margin: 0px'><a style='text-decoration: none' href=" + googleMapsUrl + ">Open in Google Maps</a></h3><br />" + "<img style='border: 2px solid;border-radius: 10px;' alt='Loading Map...' src='" + staticMapUrl + "' />"; } message.Body += "</td></tr></table></body></html>"; } //Send the alert try { mailClient.Send(message); } catch (SmtpException e) { if (e.InnerException != null) { Core.UI.writeToConsole("SMTP ERROR: " + e.Message + " (" + e.InnerException.Message + ")", Color.Red); return; } Core.UI.writeToConsole("SMTP ERROR: " + e.Message, Color.Red); return; } catch (InvalidOperationException e) { if (e.InnerException != null) { Core.UI.writeToConsole("SMTP ERROR: " + e.Message + " (" + e.InnerException.Message + ")", Color.Red); return; } Core.UI.writeToConsole("SMTP ERROR: " + e.Message, Color.Red); return; } //Increase sent emails for condition and update stats condition.increaseSentEmails(); Stats.updateStats(); //Log to UI Core.UI.writeToConsole(DateTime.Now.ToLongTimeString() + " | SENT | " + aircraft.ICAO + " | Condition: " + condition.conditionName + " (" + emailPropertyInfo + ")", Color.LightBlue); }