diff --git a/Projects/Scripts/Engines/Help/PageQueue.cs b/Projects/Scripts/Engines/Help/PageQueue.cs index f719ddea8..fa934094a 100644 --- a/Projects/Scripts/Engines/Help/PageQueue.cs +++ b/Projects/Scripts/Engines/Help/PageQueue.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Net.Mail; -using Server.Accounting; using Server.Misc; using Server.Mobiles; using Server.Network; @@ -49,7 +46,7 @@ namespace Server.Engines.Help m_Timer.Start(); } - public Mobile Sender{ get; } + public Mobile Sender { get; } public Mobile Handler { @@ -61,17 +58,17 @@ namespace Server.Engines.Help } } - public DateTime Sent{ get; } + public DateTime Sent { get; } - public string Message{ get; } + public string Message { get; } - public PageType Type{ get; } + public PageType Type { get; } - public Point3D PageLocation{ get; } + public Point3D PageLocation { get; } - public Map PageMap{ get; } + public Map PageMap { get; } - public List SpeechLog{ get; } + public List SpeechLog { get; } public void Stop() { @@ -118,7 +115,7 @@ namespace Server.Engines.Help private static Dictionary m_KeyedByHandler = new Dictionary(); private static Dictionary m_KeyedBySender = new Dictionary(); - public static List List{ get; } = new List(); + public static List List { get; } = new List(); public static void Initialize() { @@ -207,6 +204,7 @@ namespace Server.Engines.Help Remove(GetEntry(sender)); } + public static void Enqueue(PageEntry entry) { List.Add(entry); @@ -230,55 +228,9 @@ namespace Server.Engines.Help entry.Sender.SendMessage( "We are sorry, but no staff members are currently available to assist you. Your page will remain in the queue until one becomes available, or until you cancel it manually."); - if (Email.FromAddress != null && Email.SpeechLogPageAddresses != null && entry.SpeechLog != null) - SendEmail(entry); + if (Email.FROM_ADDRESS != null && Email.SPEECH_LOG_PAGE_ADDRESS != null && entry.SpeechLog != null) + Email.SendQueueEmail(entry, GetPageTypeName(entry.Type)); } - private static void SendEmail(PageEntry entry) - { - Mobile sender = entry.Sender; - DateTime time = DateTime.UtcNow; - - MailMessage mail = new MailMessage(Email.FromAddress, Email.SpeechLogPageAddresses) - { - Subject = "RunUO Speech Log Page Forwarding" - }; - - using (StringWriter writer = new StringWriter()) - { - writer.WriteLine("RunUO Speech Log Page - {0}", GetPageTypeName(entry.Type)); - writer.WriteLine(); - - writer.WriteLine("From: '{0}', Account: '{1}'", sender.RawName, - sender.Account is Account ? sender.Account.Username : "???"); - writer.WriteLine("Location: {0} [{1}]", sender.Location, sender.Map); - writer.WriteLine("Sent on: {0}/{1:00}/{2:00} {3}:{4:00}:{5:00}", time.Year, time.Month, time.Day, time.Hour, - time.Minute, time.Second); - writer.WriteLine(); - - writer.WriteLine("Message:"); - writer.WriteLine("'{0}'", entry.Message); - writer.WriteLine(); - - writer.WriteLine("Speech Log"); - writer.WriteLine("=========="); - - foreach (SpeechLogEntry logEntry in entry.SpeechLog) - { - Mobile from = logEntry.From; - string fromName = from.RawName; - string fromAccount = from.Account is Account ? from.Account.Username : "???"; - DateTime created = logEntry.Created; - string speech = logEntry.Speech; - - writer.WriteLine("{0}:{1:00}:{2:00} - {3} ({4}): '{5}'", created.Hour, created.Minute, created.Second, - fromName, fromAccount, speech); - } - - mail.Body = writer.ToString(); - } - - Email.AsyncSend(mail); - } } } diff --git a/Projects/Scripts/Misc/CrashGuard.cs b/Projects/Scripts/Misc/CrashGuard.cs index ce1e60e16..092e9ca42 100644 --- a/Projects/Scripts/Misc/CrashGuard.cs +++ b/Projects/Scripts/Misc/CrashGuard.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Net.Mail; using Server.Accounting; using Server.Network; @@ -43,18 +42,9 @@ namespace Server.Misc { Console.Write("Crash: Sending email..."); - MailMessage message = new MailMessage(Email.FromAddress, Email.CrashAddresses); + if (Email.FROM_ADDRESS != null && Email.CRASH_ADDRESS != null) + Email.SendCrashEmail(filePath); - message.Subject = "Automated RunUO Crash Report"; - - message.Body = "Automated RunUO Crash Report. See attachment for details."; - - message.Attachments.Add(new Attachment(filePath)); - - if (Email.Send(message)) - Console.WriteLine("done"); - else - Console.WriteLine("failed"); } private static string GetRoot() @@ -69,13 +59,7 @@ namespace Server.Misc } } - private static string Combine(string path1, string path2) - { - if (path1.Length == 0) - return path2; - - return Path.Combine(path1, path2); - } + private static string Combine(string path1, string path2) => path1.Length == 0 ? path2 : Path.Combine(path1, path2); private static void Restart(CrashedEventArgs e) { @@ -247,7 +231,7 @@ namespace Server.Misc Console.WriteLine("done"); - if (Email.FromAddress != null && Email.CrashAddresses != null) + if (Email.FROM_ADDRESS != null && Email.CRASH_ADDRESS != null) SendEmail(filePath); } catch @@ -263,4 +247,4 @@ namespace Server.Misc return $"{now.Day}-{now.Month}-{now.Year}-{now.Hour}-{now.Minute}-{now.Second}"; } } -} \ No newline at end of file +} diff --git a/Projects/Scripts/Misc/Email.cs b/Projects/Scripts/Misc/Email.cs index 43d1d99c9..7481f3a0f 100644 --- a/Projects/Scripts/Misc/Email.cs +++ b/Projects/Scripts/Misc/Email.cs @@ -1,98 +1,131 @@ +using MailKit.Net.Smtp; +using MimeKit; +using Server.Accounting; +using Server.Engines.Help; using System; -using System.Net; -using System.Net.Mail; -using System.Text.RegularExpressions; -using System.Threading; +using System.IO; +using System.Threading.Tasks; namespace Server.Misc { - public class Email + public static class Email { - /* In order to support emailing, fill in EmailServer and FromAddress: - * Example: - * public static readonly string EmailServer = "mail.domain.com"; - * public static readonly string FromAddress = "runuo@domain.com"; - * - * If you want to add crash reporting emailing, fill in CrashAddresses: - * Example: - * public static readonly string CrashAddresses = "first@email.here,second@email.here,third@email.here"; - * - * If you want to add speech log page emailing, fill in SpeechLogPageAddresses: - * Example: - * public static readonly string SpeechLogPageAddresses = "first@email.here,second@email.here,third@email.here"; - */ + public static readonly MailboxAddress FROM_ADDRESS = new MailboxAddress(Configuration.Instance.emailSettings.FromName, Configuration.Instance.emailSettings.FromAddress); + public static readonly MailboxAddress CRASH_ADDRESS = new MailboxAddress(Configuration.Instance.emailSettings.crashName, Configuration.Instance.emailSettings.crashAddress); + public static readonly MailboxAddress SPEECH_LOG_PAGE_ADDRESS = new MailboxAddress(Configuration.Instance.emailSettings.speechLogPageName, Configuration.Instance.emailSettings.speechLogPageAddress); + public static readonly string EMAIL_SERVER = Configuration.Instance.emailSettings.emailServer; + public static readonly int EMAIL_PORT = Configuration.Instance.emailSettings.emailPort; + public static readonly string EMAIL_SERVER_USERNAME = Configuration.Instance.emailSettings.emailUsername; + public static readonly string EMAIL_SERVER_PASSWORD = Configuration.Instance.emailSettings.emailPassword; + public static readonly int RETRY_SEND_EMAIL_COUNT = 5; + public static readonly int SEND_DELAY_SECONDS = 2; - public static readonly string EmailServer = null; - public static readonly int EmailPort = 25; - - public static readonly string FromAddress = null; - public static readonly string EmailUsername = null; - public static readonly string EmailPassword = null; - - - public static readonly string CrashAddresses = null; - public static readonly string SpeechLogPageAddresses = null; - - private static Regex _pattern = new Regex(@"^[a-z0-9.+_-]+@([a-z0-9-]+\.)+[a-z]+$", - RegexOptions.Compiled | RegexOptions.IgnoreCase); - - private static SmtpClient _Client; - - public static bool IsValid(string address) + /// + /// Sends Queue-Page request using Email + /// + /// + /// + public static void SendQueueEmail(PageEntry entry, string pageType) { - if (address == null || address.Length > 320) - return false; + Mobile sender = entry.Sender; + DateTime time = DateTime.UtcNow; - return _pattern.IsMatch(address); - } + var message = new MimeMessage(); + message.From.Add(FROM_ADDRESS); + message.To.Add(SPEECH_LOG_PAGE_ADDRESS); + message.Subject = "ModernUO Speech Log Page Forwarding"; - public static void Configure() - { - if (EmailServer != null) + using (StringWriter writer = new StringWriter()) { - _Client = new SmtpClient(EmailServer, EmailPort); - if (EmailUsername != null) _Client.Credentials = new NetworkCredential(EmailUsername, EmailPassword); - } - } + writer.WriteLine(@$" + ModernUO Speech Log Page - {pageType} - public static bool Send(MailMessage message) - { - try - { - // .NET relies on the MTA to generate Message-ID header. Not all MTAs will add this header. + From: '{sender.RawName}', Account: '{((sender.Account is Account accSend) ? accSend.Username : " ??? ")}' - DateTime now = DateTime.UtcNow; - string messageID = $"<{now.ToString("yyyyMMdd")}.{now.ToString("HHmmssff")}@{EmailServer}>"; - message.Headers.Add("Message-ID", messageID); + Location: {sender.Location} [{sender.Map}] + Sent on: {time.Year}/{time.Month:00}/{time.Day:00} {time.Hour}:{time.Minute:00}:{time.Second:00} - message.Headers.Add("X-Mailer", "RunUO"); + Message: + '{entry.Message}' - lock (_Client) + Speech Log + ========== + "); + + foreach (SpeechLogEntry logEntry in entry.SpeechLog) { - _Client.Send(message); + Mobile from = logEntry.From; + string fromName = from.RawName; + string fromAccount = from.Account is Account accFrom ? accFrom.Username : "???"; + DateTime created = logEntry.Created; + string speech = logEntry.Speech; + writer.WriteLine(@$"{created.Hour}:{created.Minute:00}:{created.Second:00} - {fromName} ({fromAccount}): '{speech}'"); } + + message.Body = new BodyBuilder + { + TextBody = writer.ToString(), + HtmlBody = null + }.ToMessageBody(); + } - catch + SendAsync(message); + } + + /// + /// Sends crash email + /// + /// + public static void SendCrashEmail(string filePath) + { + var message = new MimeMessage(); + message.From.Add(FROM_ADDRESS); + message.To.Add(CRASH_ADDRESS); + message.Subject = "Automated ModernUO Crash Report"; + var builder = new BodyBuilder { - return false; - } - - return true; + TextBody = "Automated ModernUO Crash Report. See attachment for details.", + HtmlBody = null + }; + builder.Attachments.Add(filePath); + message.Body = builder.ToMessageBody(); } - public static void AsyncSend(MailMessage message) + /// + /// Sends emails async + /// + /// + private static async void SendAsync(MimeMessage message) { - ThreadPool.QueueUserWorkItem(SendCallback, message); - } + DateTime now = DateTime.UtcNow; + string messageID = $"<{now:yyyyMMdd}.{now:HHmmssff}@{EMAIL_SERVER}>"; + message.Headers.Add("Message-ID", messageID); + message.From.Add(FROM_ADDRESS); - private static void SendCallback(object state) - { - MailMessage message = (MailMessage)state; + int delay = SEND_DELAY_SECONDS; - if (Send(message)) - Console.WriteLine("Sent e-mail '{0}' to '{1}'.", message.Subject, message.To); - else - Console.WriteLine("Failure sending e-mail '{0}' to '{1}'.", message.Subject, message.To); + for (int i = 0; i < RETRY_SEND_EMAIL_COUNT; i++) + try + { + using SmtpClient client = new SmtpClient(); + await client.ConnectAsync(EMAIL_SERVER, EMAIL_PORT, true); + await client.AuthenticateAsync(EMAIL_SERVER_USERNAME, EMAIL_SERVER_PASSWORD); + await client.SendAsync(message); + await client.DisconnectAsync(true); + return; + } + catch (Exception ex) + { + if (i == 0) + { + Console.WriteLine(ex.Message); + Console.WriteLine(ex.StackTrace); + } + + delay *= delay; + + await Task.Delay(delay * 1000); + } } } -} \ No newline at end of file +} diff --git a/Projects/Scripts/Scripts.csproj b/Projects/Scripts/Scripts.csproj index 0c40038f0..336e20fab 100644 --- a/Projects/Scripts/Scripts.csproj +++ b/Projects/Scripts/Scripts.csproj @@ -36,6 +36,7 @@ + diff --git a/Projects/Server/Configuration.cs b/Projects/Server/Configuration.cs index e8ed729d7..6f0d39927 100644 --- a/Projects/Server/Configuration.cs +++ b/Projects/Server/Configuration.cs @@ -15,6 +15,9 @@ namespace Server [JsonPropertyName("dataDirectories")] public List DataDirectories { get; set; } = new List(); + [JsonPropertyName("emailSettings")] + public EmailSettings emailSettings { get; set; } = new EmailSettings(); + private static string FilePath => Path.Join(Core.BaseDirectory, "modernuo.json"); private static void PromptDataDirectories(Configuration config) @@ -73,4 +76,29 @@ namespace Server Console.ResetColor(); } } + + // TODO: Make configuration pluggable. Move this to scripts + public class EmailSettings + { + [JsonPropertyName("fromAddress")] + public string FromAddress { get; set; } + [JsonPropertyName("fromName")] + public string FromName { get; set; } + [JsonPropertyName("crashAddress")] + public string crashAddress { get; set; } + [JsonPropertyName("crashName")] + public string crashName { get; set; } + [JsonPropertyName("speechLogPageAddress")] + public string speechLogPageAddress { get; set; } + [JsonPropertyName("speechLogPageName")] + public string speechLogPageName { get; set; } + [JsonPropertyName("emailServer")] + public string emailServer { get; set; } + [JsonPropertyName("emailPort")] + public int emailPort { get; set; } + [JsonPropertyName("emailUsername")] + public string emailUsername { get; set; } + [JsonPropertyName("emailPassword")] + public string emailPassword { get; set; } + } }