// See https://aka.ms/new-console-template for more information using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Tesses.WebServer; using LiteDB; using System.Security.Cryptography; using System.Text; using System.Web; using MimeKit; using MailKit.Net.Smtp; bool serve =true; string path = "data"; string GetIP(ServerContext ctx) { if(ctx.RequestHeaders.TryGetFirst("X-Real-IP",out var ip)) { return ip; } return ctx.Client.Address.ToString(); } long HashIP(ServerContext ctx) { return BitConverter.ToInt64(SHA512.HashData(Encoding.UTF8.GetBytes(GetIP(ctx))),0); } if(args.Length > 0) { if(args[0] == "serve") { serve = true; } else if(args[0] == "send") { serve = false; } else { Console.WriteLine("Command help"); Console.WriteLine("serve: serve this app using data folder in working directory"); Console.WriteLine("send: send emails using data folder in working directory"); Console.WriteLine("serve : serve this app using path to data folder"); Console.WriteLine("send : send emails using path to data folder"); return; } if(args.Length>1) { path=args[1]; } } else { Console.WriteLine("Command help"); Console.WriteLine("serve: serve this app using data folder in working directory"); Console.WriteLine("send: send emails using data folder in working directory"); Console.WriteLine("serve : serve this app using path to data folder"); Console.WriteLine("send : send emails using path to data folder"); return; } Directory.CreateDirectory(path); EmailBoxConfiguration configuration = EmailBoxConfiguration.OpenConfiguration(Path.Combine(path,"config.json")); LiteDatabase db = new LiteDatabase(Path.Combine(path,"emailbox.db")); if(serve) { RouteServer routeServer=new RouteServer(); routeServer.CorsHeader=false; routeServer.Add("/",async(ctx)=>{ var ips = db.GetCollection("ips"); long id=HashIP(ctx); if(ips.FindById(id) ==null) { ips.Insert(id,new IPS()); } long count=ips.LongCount(); var emails = db.GetCollection("emails"); await ctx.SendTextAsync(File.ReadAllText(Path.Combine(path,"index.html")).Replace("%Name%",configuration.Name).Replace("%Viewers%",count.ToString()).Replace("%Mailing%",emails.LongCount().ToString()).Replace("%Loads%",Views.Increment(db).ToString())); }); routeServer.Add("/bootstrap.min.js",async(ctx)=>{ await ctx.SendFileAsync(Path.Combine(path,"bootstrap.min.js")); }); routeServer.Add("/bootstrap.min.css",async(ctx)=>{ await ctx.SendFileAsync(Path.Combine(path,"bootstrap.min.css")); }); routeServer.Add("/addtomailinglist",async(ctx)=>{ ctx.ParseBody(); if(ctx.QueryParams.TryGetFirst("email",out var email)) { var emails = db.GetCollection("emails"); var emailAcnt= emails.FindOne(e=>e.Email == email); if(emailAcnt == null) { emailAcnt=new Emails(); emailAcnt.Email = email; var rnd=Convert.ToBase64String(RandomNumberGenerator.GetBytes(32)); emailAcnt.UnsubscribeCode = rnd; emails.Insert(emailAcnt); } await SendUnsubscribeEmailAsync(emailAcnt); } await ctx.SendRedirectAsync($"{configuration.Website.TrimEnd('/')}/"); },"POST"); routeServer.Add("/unsubscribe",async(ctx)=>{ Emails? emailAcnt=null; var emails = db.GetCollection("emails"); if(ctx.QueryParams.TryGetFirst("email",out var email) && ctx.QueryParams.TryGetFirst("token",out var token)) { emailAcnt = emails.FindOne(e=>e.Email == email); if(emailAcnt != null && emailAcnt.UnsubscribeCode != token) { emailAcnt = null; } } if(emailAcnt != null) { emails.Delete(emailAcnt.Id); await ctx.SendFileAsync(Path.Combine(path,"unsubscribe_success.html")); } else await ctx.SendFileAsync(Path.Combine(path,"unsubscribe_failed.html")); }); routeServer.StartServer(configuration.Port); } else { var emails = db.GetCollection("emails"); foreach(var item in emails.FindAll()) { await SendActualEmailAsync(item); } } async Task SendEmailAsync(Emails emailAcnt,string subject,string body) { if(InternetAddress.TryParse(emailAcnt.Email,out var to) && InternetAddress.TryParse(configuration.Email.Email,out var from)) using(var smtp=new SmtpClient()) { await smtp.ConnectAsync(configuration.Email.Host,configuration.Email.Port,configuration.Email.Encryption); await smtp.AuthenticateAsync(configuration.Email.User,configuration.Email.Pass); MimeMessage message=new MimeMessage(); message.From.Add(from); message.To.Add(to); message.Body = new TextPart(MimeKit.Text.TextFormat.Html){Text=body}; message.Subject = subject; await smtp.SendAsync(message); } } async Task SendActualEmailAsync(Emails emailAcnt) { await SendEmailAsync(emailAcnt,configuration.ActualEmailSubject,File.ReadAllText(Path.Combine(path,"email.html")).Replace("%Website%",HttpUtility.HtmlAttributeEncode($"{configuration.Website.TrimEnd('/')}/")).Replace("%Name%",HttpUtility.HtmlEncode(configuration.Name).Replace("%Email%",HttpUtility.HtmlEncode(configuration.Email)))); } async Task SendUnsubscribeEmailAsync(Emails emailAcnt) { string unsubscribeUrl = $"{configuration.Website.TrimEnd('/')}/unsubscribe?email={HttpUtility.UrlEncode(emailAcnt.Email)}&token={HttpUtility.UrlEncode(emailAcnt.UnsubscribeCode)}"; await SendEmailAsync(emailAcnt,$"Heres your unsubscribe email for {configuration.Name}",File.ReadAllText(Path.Combine(path,"unsubscribe_email.html")).Replace("%Website%",HttpUtility.HtmlAttributeEncode($"{configuration.Website.TrimEnd('/')}/")).Replace("%Name%",HttpUtility.HtmlEncode(configuration.Name)).Replace("%Unsubscribe%",HttpUtility.HtmlAttributeEncode(unsubscribeUrl))); } public class EmailBoxConfiguration { string _aeS=""; public string ActualEmailSubject { get{ if(string.IsNullOrWhiteSpace(_aeS)) { return $"Email from {Name}"; } return _aeS; } set{ _aeS = value; } } public string Website {get;set;}="/"; public int Port {get;set;}=51777; public string Name {get;set;}=""; public EmailBoxEmailConfiguration Email {get;set;}=new EmailBoxEmailConfiguration(); internal static EmailBoxConfiguration OpenConfiguration(string v) { if(File.Exists(v)) { var p = JsonConvert.DeserializeObject(File.ReadAllText(v)); if(p!=null) return p; } return new EmailBoxConfiguration(); } } public class EmailBoxEmailConfiguration { public string Host {get;set;}=""; public string Email {get;set;}=""; public string User {get;set;}=""; public string Pass {get;set;}=""; public int Port {get;set;}=587; [JsonConverter(typeof(StringEnumConverter))] public MailKit.Security.SecureSocketOptions Encryption {get;set;}= MailKit.Security.SecureSocketOptions.StartTls; } public class IPS { public long Id {get;set;} } public class Emails { public long Id {get;set;} public string Email {get;set;}=""; public string UnsubscribeCode {get;set;}=""; } public class Views { public long Id {get;set;}=1; public long PageViews {get;set;}=1; public static long Increment(ILiteDatabase db) { lock(db){ var views=db.GetCollection("views"); var item = views.FindById(1); if(item==null) { item=new Views(); views.Insert(item); } else { item.PageViews++; views.Update(item); } return item.PageViews; } } }