emailbox/Program.cs

249 lines
8.3 KiB
C#

// 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 <path>: serve this app using path to data folder");
Console.WriteLine("send <path>: 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 <path>: serve this app using path to data folder");
Console.WriteLine("send <path>: 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>("ips");
long id=HashIP(ctx);
if(ips.FindById(id) ==null)
{
ips.Insert(id,new IPS());
}
long count=ips.LongCount();
var emails = db.GetCollection<Emails>("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>("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>("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>("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<EmailBoxConfiguration>(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>("views");
var item = views.FindById(1);
if(item==null)
{
item=new Views();
views.Insert(item);
}
else
{
item.PageViews++;
views.Update(item);
}
return item.PageViews;
}
}
}