249 lines
8.3 KiB
C#
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;
|
|
}
|
|
}
|
|
} |