예제 #1
0
 /// <summary>
 /// Initialise form with a condition to update
 /// </summary>
 /// <param name="_conditionToUpdate"></param>
 public Condition_Editor(int _conditionToUpdate)
 {
     isUpdating        = true;
     conditionToUpdate = _conditionToUpdate;
     //Initialise form options
     Initialise();
     //Set form element values from condition info
     Core.Condition c = EditorConditionsList.conditions[conditionToUpdate];
     conditionNameTextBox.Text  = c.conditionName;
     emailPropertyComboBox.Text = c.emailProperty.ToString();
     recieverEmailTextBox.Text  = string.Join(Environment.NewLine, c.recieverEmails.ToArray());
     alertTypeComboBox.Text     = c.alertType.ToString().Replace('_', ' ');
     foreach (Core.Trigger trigger in c.triggers.Values)
     {
         triggerDataGridView.Rows.Add();
         DataGridViewRow          newRow       = triggerDataGridView.Rows[triggerDataGridView.Rows.Count - 2];
         DataGridViewComboBoxCell comboBoxCell = (DataGridViewComboBoxCell)(newRow.Cells[0]);
         foreach (Core.vrsProperty property in Enum.GetValues(typeof(Core.vrsProperty)))
         {
             comboBoxCell.Items.Add(property.ToString());
         }
         newRow.Cells[0].Value = trigger.Property.ToString();
         newRow.Cells[1].Value = trigger.ComparisonType;
         newRow.Cells[2].Value = trigger.Value;
     }
 }
예제 #2
0
 /// <summary>
 /// Load conditions
 /// </summary>
 public static void LoadConditions()
 {
     try {
         //Clear conditions and active matches
         Core.conditions.Clear();
         Core.activeMatches.Clear();
         //Parse conditions file
         JObject conditionJson;
         using (FileStream filestream = new FileStream("conditions.json", FileMode.Open))
             using (StreamReader reader = new StreamReader(filestream))
                 using (JsonTextReader jsonreader = new JsonTextReader(reader))
                     conditionJson = JsonSerializer.Create().Deserialize <JObject>(jsonreader);
         if (conditionJson == null)
         {
             return;
         }
         //Iterate parsed conditions
         for (int conditionid = 0; conditionid < conditionJson.Count; conditionid++)
         {
             JToken condition = conditionJson[conditionid.ToString()];
             //Create condition and copy values
             Core.Condition newCondition = new Core.Condition {
                 conditionName   = condition["conditionName"].ToString(),
                 emailProperty   = (Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), condition["emailProperty"].ToString()),
                 alertType       = (Core.AlertType)Enum.Parse(typeof(Core.AlertType), condition["alertType"].ToString()),
                 ignoreFollowing = (bool)condition["ignoreFollowing"]
             };
             List <string> emailsArray = new List <string>();
             foreach (JToken email in condition["recieverEmails"])
             {
                 emailsArray.Add(email.ToString());
             }
             newCondition.recieverEmails = emailsArray;
             foreach (JToken trigger in condition["triggers"].Values())
             {
                 newCondition.triggers.Add(newCondition.triggers.Count, new Core.Trigger((Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), trigger["Property"].ToString()), trigger["Value"].ToString(), trigger["ComparisonType"].ToString()));
             }
             //Add condition to list
             Core.conditions.Add(conditionid, newCondition);
         }
         //Try to clean up json parsing
         conditionJson.RemoveAll();
         conditionJson = null;
         //Update condition list
         Core.UI.Invoke((MethodInvoker)(() => {
             Core.UI.updateConditionList();
         }));
         Core.UI.conditionTreeView.Nodes[0].Expand();
         //Log to UI
         Core.UI.writeToConsole("Conditions Loaded", Color.White);
         //Restart threads
         if (ThreadManager.threadStatus != ThreadManager.CheckerStatus.WaitingForLoad)
         {
             ThreadManager.StartOrRestart();
         }
     }
     catch (Exception e) {
         MessageBox.Show(e.Message + "\n\n" + e.StackTrace);
     }
 }
예제 #3
0
 /// <summary>
 /// Update condition list
 /// </summary>
 public void updateConditionList()
 {
     conditionEditorTreeView.Nodes.Clear();
     foreach (int conditionid in EditorConditionsList.conditions.Keys)
     {
         Core.Condition condition     = EditorConditionsList.conditions[conditionid];
         TreeNode       conditionNode = conditionEditorTreeView.Nodes.Add(conditionid + ": " + condition.conditionName);
         conditionNode.Tag = conditionid;
         conditionNode.Nodes.Add("Id: " + conditionid);
         conditionNode.Nodes.Add("Email Parameter: " + condition.emailProperty.ToString());
         conditionNode.Nodes.Add("Reciever Emails: " + string.Join(", ", condition.recieverEmails.ToArray()));
         conditionNode.Nodes.Add("Alert Type: " + condition.alertType);
         TreeNode triggersNode = conditionNode.Nodes.Add("Condition Triggers");
         foreach (Core.Trigger trigger in condition.triggers.Values)
         {
             triggersNode.Nodes.Add(trigger.Property.ToString() + " " + trigger.ComparisonType + " " + trigger.Value);
         }
     }
 }
예제 #4
0
 /// <summary>
 /// Updates the condition list
 /// </summary>
 public void updateConditionList()
 {
     conditionTreeView.Nodes[0].Nodes.Clear();
     foreach (int conditionid in Core.conditions.Keys)
     {
         TreeNode       conditionNode;
         Core.Condition c = Core.conditions[conditionid];
         conditionNode = conditionTreeView.Nodes[0].Nodes.Add("Name: " + c.conditionName);
         conditionNode.Nodes.Add("Id: " + conditionid);
         conditionNode.Nodes.Add("Email Parameter: " + c.emailProperty.ToString());
         conditionNode.Nodes.Add("Reciever Email: " + string.Join(", ", c.recieverEmails.ToArray()));
         conditionNode.Nodes.Add("Alert Type: " + c.alertType);
         conditionNode.Nodes.Add("Emails Sent: " + c.emailsThisSession.ToString());
         TreeNode triggersNode = conditionNode.Nodes.Add("Condition Triggers");
         foreach (Core.Trigger trigger in c.triggers.Values)
         {
             triggersNode.Nodes.Add(trigger.Property.ToString() + " " + trigger.ComparisonType + " " + trigger.Value);
         }
     }
 }
예제 #5
0
        public ConditionEditor()
        {
            //Initialise form elements
            InitializeComponent();

            //Load conditions
            if (File.Exists("conditions.json"))
            {
                EditorConditionsList.conditions.Clear();
                //Parse json
                string  conditionsJsonText = File.ReadAllText("conditions.json");
                JObject conditionJson      = (JObject)JsonConvert.DeserializeObject(conditionsJsonText);
                if (conditionJson != null)
                {
                    //Iterate parsed conditions
                    for (int conditionid = 0; conditionid < conditionJson.Count; conditionid++)
                    {
                        JToken condition = conditionJson[conditionid.ToString()];
                        //Create condition from parsed json
                        Core.Condition newCondition = new Core.Condition();
                        newCondition.conditionName   = condition["conditionName"].ToString();
                        newCondition.emailProperty   = (Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), condition["emailProperty"].ToString());
                        newCondition.alertType       = (Core.AlertType)Enum.Parse(typeof(Core.AlertType), condition["alertType"].ToString());
                        newCondition.ignoreFollowing = (bool)condition["ignoreFollowing"];
                        List <string> emailsArray = new List <string>();
                        foreach (JToken email in condition["recieverEmails"])
                        {
                            emailsArray.Add(email.ToString());
                        }
                        newCondition.recieverEmails = emailsArray;
                        foreach (JToken trigger in condition["triggers"].Values())
                        {
                            newCondition.triggers.Add(newCondition.triggers.Count, new Core.Trigger((Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), trigger["Property"].ToString()), trigger["Value"].ToString(), trigger["ComparisonType"].ToString()));
                        }
                        //Add condition to list
                        EditorConditionsList.conditions.Add(conditionid, newCondition);
                    }
                }
            }
            updateConditionList();
        }
예제 #6
0
        /// <summary>
        /// Move down button
        /// </summary>
        /// <param name="sender">Sender</param>
        /// <param name="e">Event Args</param>
        private void moveDownButton_Click(object sender, EventArgs e)
        {
            //Cancel if selected node is invalid
            if (conditionEditorTreeView.SelectedNode == null || conditionEditorTreeView.SelectedNode.Tag == null || conditionEditorTreeView.SelectedNode.Tag.ToString() == "")
            {
                return;
            }
            //Swap conditions then update condition list
            int conditionid = Convert.ToInt32(conditionEditorTreeView.SelectedNode.Tag);

            if (conditionid == EditorConditionsList.conditions.Count - 1)
            {
                return;
            }
            Core.Condition c1 = EditorConditionsList.conditions[conditionid];
            Core.Condition c2 = EditorConditionsList.conditions[conditionid + 1];
            EditorConditionsList.conditions.Remove(conditionid + 1);
            EditorConditionsList.conditions.Remove(conditionid);
            EditorConditionsList.conditions.Add(conditionid + 1, c1);
            EditorConditionsList.conditions.Add(conditionid, c2);
            updateConditionList();
        }
예제 #7
0
        /// <summary>
        /// Save button click
        /// </summary>
        /// <param name="sender">Sender</param>
        /// <param name="e">Event Args</param>
        void SaveButtonClick(object sender, EventArgs e)
        {
            //Check if values are empty/invalid
            bool cancelSave = false;

            if (conditionNameTextBox.Text == "")
            {
                conditionNameLabel.ForeColor = Color.Red;
                cancelSave = true;
            }
            else
            {
                conditionNameLabel.ForeColor = SystemColors.ControlText;
            }
            if (emailPropertyComboBox.Text == "")
            {
                emailPropertyLabel.ForeColor = Color.Red;
                cancelSave = true;
            }
            else
            {
                emailPropertyLabel.ForeColor = SystemColors.ControlText;
            }
            if (recieverEmailTextBox.Text == "")
            {
                emailToSendToLabel.ForeColor = Color.Red;
                cancelSave = true;
            }
            else
            {
                emailToSendToLabel.ForeColor = SystemColors.ControlText;
            }
            if (alertTypeComboBox.Text == "")
            {
                alertTypeLabel.ForeColor = Color.Red;
                cancelSave = true;
            }
            else
            {
                alertTypeLabel.ForeColor = SystemColors.ControlText;
            }
            if (triggerDataGridView.Rows.Count == 1)
            {
                triggerDataGridView.BackgroundColor = Color.Red;
                cancelSave = true;
            }
            else
            {
                triggerDataGridView.BackgroundColor = SystemColors.AppWorkspace;
            }
            //Check if emails are valid
            foreach (string line in recieverEmailTextBox.Lines)
            {
                try {
                    new System.Net.Mail.MailAddress(line);
                }
                catch (FormatException) {
                    emailToSendToLabel.ForeColor = Color.Red;
                    cancelSave = true;
                    break;
                }
            }
            //Cancel if values are invalid
            if (cancelSave)
            {
                return;
            }

            //If condition is being updated, remove the old one
            if (isUpdating)
            {
                EditorConditionsList.conditions.Remove(conditionToUpdate);
            }

            //Sort conditions
            List <int> list = EditorConditionsList.conditions.Keys.ToList();
            SortedDictionary <int, Core.Condition> sortedConditions = new SortedDictionary <int, Core.Condition>();

            list.Sort();
            foreach (var key in list)
            {
                sortedConditions.Add(key, EditorConditionsList.conditions[key]);
            }
            EditorConditionsList.conditions = sortedConditions;

            //Create new condition
            Core.Condition newCondition = new Core.Condition {
                conditionName   = conditionNameTextBox.Text,
                emailProperty   = (Core.vrsProperty)Enum.Parse(typeof(Core.vrsProperty), emailPropertyComboBox.Text),
                recieverEmails  = recieverEmailTextBox.Text.Split(new string[] { Environment.NewLine }, StringSplitOptions.None).ToList(),
                alertType       = (Core.AlertType)Enum.Parse(typeof(Core.AlertType), alertTypeComboBox.Text.Replace(' ', '_')),
                ignoreFollowing = ignoreFollowingCheckbox.Checked
            };
            if (triggerDataGridView.Rows.Count != 0)
            {
                foreach (DataGridViewRow row in triggerDataGridView.Rows)
                {
                    if (row.Index != triggerDataGridView.Rows.Count - 1)
                    {
                        foreach (Core.vrsProperty property in Enum.GetValues(typeof(Core.vrsProperty)))
                        {
                            if (property.ToString() == row.Cells[0].Value.ToString())
                            {
                                newCondition.triggers.Add(newCondition.triggers.Count, new Core.Trigger(property, row.Cells[2].Value.ToString(), row.Cells[1].Value.ToString()));
                                break;
                            }
                        }
                    }
                }
            }
            //Add condition to condition list
            if (isUpdating)
            {
                EditorConditionsList.conditions.Add(conditionToUpdate, newCondition);
            }
            else
            {
                EditorConditionsList.conditions.Add(EditorConditionsList.conditions.Count, newCondition);
            }
            //Close form
            Close();
        }
예제 #8
0
        /// <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") + "&amp;size=800x800&amp;markers=" + aircraft.GetProperty("Lat") + "," + aircraft.GetProperty("Long") + "&amp;key=AIzaSyCJxiyiDWBHiYSMm7sjSTJkQubuo3XuR7s&amp;path=color:0x000000|";
                    }
                    else
                    {
                        staticMapUrl = "http://maps.googleapis.com/maps/api/staticmap?center=" + aircraft.GetProperty("Lat") + "," + aircraft.GetProperty("Long") + "&amp;size=800x800&amp;zoom=8&amp;markers=" + aircraft.GetProperty("Lat") + "," + aircraft.GetProperty("Long") + "&amp;key=AIzaSyCJxiyiDWBHiYSMm7sjSTJkQubuo3XuR7s&amp;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);
        }