From 3792241774d9f1a042327b661bd1962435b08280 Mon Sep 17 00:00:00 2001 From: Madpeter <57852827+Madpeterz@users.noreply.github.com> Date: Thu, 6 Feb 2025 03:48:55 +0000 Subject: [PATCH] smtp support --- Dockerfile | 18 +- SecondBotEvents/Commands/Self.cs | 28 +++ SecondBotEvents/Config/SmtpConfig.cs | 97 +++++++++ SecondBotEvents/EventsSecondBot.cs | 5 +- SecondBotEvents/SecondBotEvents.csproj | 1 + SecondBotEvents/Services/CommandsService.cs | 13 ++ SecondBotEvents/Services/SmtpService.cs | 228 ++++++++++++++++++++ 7 files changed, 387 insertions(+), 3 deletions(-) create mode 100644 SecondBotEvents/Config/SmtpConfig.cs create mode 100644 SecondBotEvents/Services/SmtpService.cs diff --git a/Dockerfile b/Dockerfile index dc90205..17250f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -135,8 +135,22 @@ ENV basic_Username='' \ chatgpt_RedisCountIm='60' \ onevent_Enabled='false' \ onevent_Count=0 \ - onevent_HideStatusOutput='false' - + onevent_HideStatusOutput='false' \ + smtp_Enabled='false' \ + smtp_AllowEmailAsReplyTarget='false' \ + smtp_AllowCommandSendMail='false' \ + smtp_UseAllowedRecivers='false' \ + smtp_AllowedReciversCSV='none' \ + smtp_AllowSendAlertStatus='false' \ + smtp_AllowSendLoginNotice='false' \ + smtp_MailReplyAddress='me@myemail.tld' \ + smtp_Port='587' \ + smtp_Host='smtp.mail.example' \ + smtp_useSSL='false' \ + smtp_User='me@email.addr.tld' \ + smtp_Token='none' \ + smtp_SendAlertsAndLoginsTo='me@somewhere.tld' \ + smtp_HideStatusOutput='false' EXPOSE 80 ENV ASPNETCORE_URLS http://+:80 diff --git a/SecondBotEvents/Commands/Self.cs b/SecondBotEvents/Commands/Self.cs index 36bfa4b..dc4df33 100644 --- a/SecondBotEvents/Commands/Self.cs +++ b/SecondBotEvents/Commands/Self.cs @@ -13,6 +13,34 @@ namespace SecondBotEvents.Commands { } + [About("use the SMTP service to send a email (requires AllowCommandSendMail to be true)")] + [ReturnHints("sending")] + [ReturnHintsFailure("Send was rejected because: X")] + [ReturnHintsFailure("ARG is empty")] + [ArgHints("to", "the email addres to send to")] + [ArgHints("subject", "the subject of the email")] + [ArgHints("body", "the contents of the email")] + public object SendEmail(string to, string subject, string body) + { + if (SecondbotHelpers.isempty(to) == true) { + return Failure("to is empty"); + } + else if (SecondbotHelpers.isempty(subject) == true) + { + return Failure("subject is empty", [ to ]); + } + else if (SecondbotHelpers.isempty(body) == true) + { + return Failure("body is empty", [to, subject]); + } + KeyValuePair reply = master.SmtpService.commandEmail(to, subject, body); + if(reply.Key == false) + { + return Failure("Send was rejected because: "+reply.Value, [ to, subject, body ]); + } + return BasicReply("sending",[to,subject,body]); + } + [About("Makes the bot teleport to its home region")] [ReturnHints("ok")] public object GoHome() diff --git a/SecondBotEvents/Config/SmtpConfig.cs b/SecondBotEvents/Config/SmtpConfig.cs new file mode 100644 index 0000000..c7e8907 --- /dev/null +++ b/SecondBotEvents/Config/SmtpConfig.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace SecondBotEvents.Config +{ + public class SmtpConfig : Config + { + public SmtpConfig(bool fromENV, string fromFolder = "") : base(fromENV, fromFolder) { } + protected override void MakeSettings() + { + filename = "smtp"; + settings.Add("Enabled"); + settings.Add("AllowEmailAsReplyTarget"); + settings.Add("AllowCommandSendMail"); + settings.Add("UseAllowedRecivers"); + settings.Add("AllowedReciversCSV"); + settings.Add("AllowSendAlertStatus"); + settings.Add("AllowSendLoginNotice"); + settings.Add("MailReplyAddress"); + settings.Add("Port"); + settings.Add("Host"); + settings.Add("useSSL"); + settings.Add("User"); + settings.Add("Token"); + settings.Add("SendAlertsAndLoginsTo"); + settings.Add("HideStatusOutput"); + + } + public bool GetUseAllowedRecivers() + { + return ReadSettingAsBool("UseAllowedRecivers", false); + } + public string[] GetAllowedRecivers() + { + return ReadSettingAsString("AllowedReciversCSV", "none").Split(','); + } + public bool GetUseSSL() + { + return ReadSettingAsBool("useSSL", false); + } + + public string GetMailReplyAddress() + { + return ReadSettingAsString("MailReplyAddress", "me@myemail.tld"); + } + + public string GetSendAlertsAndLoginsTo() + { + return ReadSettingAsString("SendAlertsAndLoginsTo", "me@somewhere.tld"); + } + public int GetPort() + { + return ReadSettingAsInt("Port", 587); + } + + public string GetHost() + { + return ReadSettingAsString("Host", "smtp.mail.example"); + } + + public string GetUser() + { + return ReadSettingAsString("User", "me@email.addr.tld"); + } + + public string GetToken() + { + return ReadSettingAsString("Token", "none"); + } + + public bool GetEnabled() + { + return ReadSettingAsBool("Enabled", false); + } + + public bool GetAllowEmailAsReplyTarget() + { + return ReadSettingAsBool("AllowEmailAsReplyTarget", false); + } + + public bool GetAllowCommandSendMail() + { + return ReadSettingAsBool("AllowCommandSendMail", false); + } + + public bool GetAllowSendAlertStatus() + { + return ReadSettingAsBool("AllowSendAlertStatus", false); + } + + public bool GetAllowSendLoginNotice() + { + return ReadSettingAsBool("AllowSendLoginNotice", false); + } + } +} \ No newline at end of file diff --git a/SecondBotEvents/EventsSecondBot.cs b/SecondBotEvents/EventsSecondBot.cs index 0d4c63b..79060e3 100644 --- a/SecondBotEvents/EventsSecondBot.cs +++ b/SecondBotEvents/EventsSecondBot.cs @@ -41,7 +41,9 @@ namespace SecondBotEvents public HomeboundService HomeboundService { get { return (HomeboundService)GetService("HomeboundService"); } } public EventsService EventsService { get { return (EventsService)GetService("EventsService"); } } public DialogService DialogService { get { return (DialogService)GetService("DialogService"); } } - + + public SmtpService SmtpService { get { return (SmtpService)GetService("SmtpService"); } } + public ChatGptService ChatGptService { get { return (ChatGptService)GetService("ChatGptService"); } } public TriggerOnEventService TriggerOnEventService { get { return (TriggerOnEventService)GetService("TriggerOnEventService"); } } @@ -174,6 +176,7 @@ namespace SecondBotEvents RegisterService("RelayService"); RegisterService("ChatGptService"); RegisterService("RecoveryService"); + RegisterService("SmtpService"); if (BotClient.IsLoaded() == false) { LogFormater.Info("Config is not loaded :("); diff --git a/SecondBotEvents/SecondBotEvents.csproj b/SecondBotEvents/SecondBotEvents.csproj index 363b5ff..5268337 100644 --- a/SecondBotEvents/SecondBotEvents.csproj +++ b/SecondBotEvents/SecondBotEvents.csproj @@ -17,6 +17,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/SecondBotEvents/Services/CommandsService.cs b/SecondBotEvents/Services/CommandsService.cs index 4e97ec4..d46c158 100644 --- a/SecondBotEvents/Services/CommandsService.cs +++ b/SecondBotEvents/Services/CommandsService.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Reflection; using System.Net.Http; using OpenMetaverse.ImportExport.Collada14; +using System.ComponentModel.DataAnnotations; namespace SecondBotEvents.Services { @@ -252,6 +253,10 @@ namespace SecondBotEvents.Services mode = "IM"; UUID.TryParse(UUIDfetch, out target_avatar); } + else if (new EmailAddressAttribute().IsValid(target) == true) + { + mode = "EMAIL"; + } else if (int.TryParse(target, out target_channel) == false) { return; @@ -287,6 +292,14 @@ namespace SecondBotEvents.Services LogFormater.Crit("[SmartReply] HTTP failed: " + e.Message + ""); } } + else if(mode == "EMAIL") + { + KeyValuePair reply = master.SmtpService.sendReplyTarget(target,command,output); + if(reply.Key == false) + { + LogFormater.Crit("[SmartReply] EMAIL failed: " + reply.Value); + } + } } protected HttpClient HTTPclient = new HttpClient(); diff --git a/SecondBotEvents/Services/SmtpService.cs b/SecondBotEvents/Services/SmtpService.cs new file mode 100644 index 0000000..0a8cbd3 --- /dev/null +++ b/SecondBotEvents/Services/SmtpService.cs @@ -0,0 +1,228 @@ +using MailKit.Net.Imap; +using MailKit.Net.Smtp; +using MimeKit; +using OpenMetaverse; +using SecondBotEvents.Config; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace SecondBotEvents.Services +{ + public class SmtpService : BotServices + { + protected new SmtpConfig myConfig; + protected int sent = 0; + protected int failed = 0; + protected bool botConnected = false; + public SmtpService(EventsSecondBot setMaster) : base(setMaster) + { + myConfig = new SmtpConfig(master.fromEnv, master.fromFolder); + if (myConfig.GetEnabled() == false) + { + return; + } + } + public override string Status() + { + if (myConfig == null) + { + return "No Config"; + } + else if(myConfig.GetEnabled() == false) + { + return "Disabled"; + } + else if (myConfig.GetHideStatusOutput() == true) + { + return "hidden"; + } + else if(botConnected == false) + { + return "Standby"; + } + return "send: "+sent.ToString()+" ~ Failed: "+failed.ToString(); + } + + protected void BotAlertMessage(object o, AlertMessageEventArgs e) + { + if (myConfig == null) + { + return; + } + else if (myConfig.GetEnabled() == false) + { + return; + } + if (myConfig.GetAllowSendAlertStatus() == false) + { + return; + } + if(myConfig.GetSendAlertsAndLoginsTo() == "me@somewhere.tld") + { + return; + } + SendMail("Bot - Alert message",e.Message, myConfig.GetSendAlertsAndLoginsTo()); + } + + public KeyValuePair commandEmail(string to,string subject, string message) + { + if(myConfig == null) + { + return new KeyValuePair(false,"No SMTP config"); + } + else if(myConfig.GetEnabled() == false) + { + return new KeyValuePair(false, "SMTP service not enabled"); + } + else if(myConfig.GetAllowCommandSendMail() == false) + { + return new KeyValuePair(false, "Send email via commands not enabled"); + } + return SendMail(subject, message, to); + } + + public KeyValuePair sendReplyTarget(string to,string command,string output) + { + if (myConfig == null) + { + return new KeyValuePair(false, "No SMTP config"); + } + else if (myConfig.GetEnabled() == false) + { + return new KeyValuePair(false, "SMTP service not enabled"); + } + else if (myConfig.GetAllowEmailAsReplyTarget() == false) + { + return new KeyValuePair(false, "Send email via command reply is not enabled"); + } + return SendMail(command+"##commandreply", output, to); + } + + protected KeyValuePair SendMail(string subject, string body, string to) + { + if(myConfig.GetMailReplyAddress() == "me@myemail.tld") + { + LogFormater.Crit("you need to set the reply address"); + failed++; + return new KeyValuePair(false, "you need to set the reply address"); + } + if(myConfig.GetUseAllowedRecivers() == true) + { + if(myConfig.GetAllowedRecivers().Contains(to) == false) + { + return new KeyValuePair(false, to + " is not in allowed recivers"); + } + } + try + { + MimeMessage message = new MimeMessage(); + message.From.Add(new MailboxAddress(myConfig.GetMailReplyAddress() + " via secondbot", myConfig.GetMailReplyAddress())); + message.To.Add(new MailboxAddress(to, to)); + message.Subject = subject; + message.Body = new TextPart("plain") { Text = body }; + using (var client = new SmtpClient()) + { + client.Connect(myConfig.GetHost(), myConfig.GetPort(), myConfig.GetUseSSL()); + + // Note: only needed if the SMTP server requires authentication + if (myConfig.GetToken() != "none") + { + client.Authenticate(myConfig.GetUser(), myConfig.GetToken()); + } + + client.Send(message); + client.Disconnect(true); + } + sent++; + return new KeyValuePair(true, "Sending"); + } + catch (Exception ex) + { + LogFormater.Warn("Failed to send email: "+ex.Message); + failed++; + return new KeyValuePair(false, "Failed to send email: " + ex.Message); + } + } + + protected void BotClientRestart(object o, BotClientNotice e) + { + if (e.isStart == false) + { + return; + } + botConnected = false; + LogFormater.Info("SMTP Service [Attached to new client]"); + GetClient().Network.LoggedOut += BotLoggedOut; + GetClient().Network.SimConnected += BotLoggedIn; + GetClient().Self.AlertMessage += BotAlertMessage; + } + + protected void BotLoggedOut(object o, LoggedOutEventArgs e) + { + botConnected = false; + LogFormater.Info("SMTP Service [Standby]"); + } + protected void BotLoggedIn(object o, SimConnectedEventArgs e) + { + bool loginEvent = false; + if(botConnected == false) + { + loginEvent = true; + } + LogFormater.Info("SMTP Service [Active]"); + botConnected = true; + if (loginEvent == true) + { + if (myConfig.GetAllowSendAlertStatus() == false) + { + return; + } + if (myConfig.GetSendAlertsAndLoginsTo() == "me@somewhere.tld") + { + return; + } + SendMail("Bot - Login message", GetClient().Self.Name + " has signed in", myConfig.GetSendAlertsAndLoginsTo()); + } + + } + + public override void Start(bool updateEnabled = false, bool setEnabledTo = false) + { + if (updateEnabled) + { + myConfig.setEnabled(setEnabledTo); + } + if (myConfig.GetEnabled() == false) + { + return; + } + Stop(); + running = true; + master.BotClientNoticeEvent += BotClientRestart; + LogFormater.Info("SMTP Service [Starting]"); + } + + public override void Stop() + { + if(running == true) + { + LogFormater.Info("SMTP Service [Stopping]"); + } + running = false; + master.BotClientNoticeEvent -= BotClientRestart; + if (master.BotClient != null) + { + if (GetClient() != null) + { + GetClient().Network.SimConnected -= BotLoggedIn; + GetClient().Self.AlertMessage -= BotAlertMessage; + } + } + + } + } +} + + +