using AngleSharp;
using AngleSharp.Css.Dom;
using AngleSharp.Dom;
using Dpz.Core.Infrastructure;
using Dpz.Core.Public.ViewModel;
using Dpz.Core.Service.RepositoryService;
using HandlebarsDotNet;
using Hangfire;
using MailKit;
using MailKit.Net.Smtp;
using MailKit.Security;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using MimeKit;
using IConfiguration = Microsoft.Extensions.Configuration.IConfiguration;
namespace Dpz.Core.Hangfire;
public class EmailSenderActivator(
IConfiguration configuration,
ICommentService commentService,
IProtocolLogger protocolLogger,
ILogger<EmailSenderActivator> logger)
: JobActivator
{
private readonly EmailConfiguration _smtpConfiguration =
configuration.GetSection("EmailService").Get<EmailConfiguration>() ??
throw new Exception("EmailService configuration is null");
private readonly bool _openService = configuration.GetSection("MailServiceOpen").Get<bool?>() ?? false;
private const string MasterEmail = "pengqian089@hotmail.com";
[ProlongExpirationTime]
public async Task SendMasterAsync(VmPublishComment comment)
{
if (!_openService)
{
logger.LogInformation("invoke {Method}", nameof(SendMasterAsync));
}
var message = new MimeMessage();
message.To.Add(new MailboxAddress("", MasterEmail));
message.From.Add(new MailboxAddress("叫我阿胖-邮件服务", _smtpConfiguration.Email));
message.Subject = $"收到了来自 {comment.NickName} 新评论";
var bodyBuilder = new BodyBuilder
{
HtmlBody = await MessageTemplate(comment)
};
message.Body = bodyBuilder.ToMessageBody();
using var client = new SmtpClient(protocolLogger);
client.ServerCertificateValidationCallback = (_, _, _, _) => true;
try
{
await client.ConnectAsync(_smtpConfiguration.Smtp, _smtpConfiguration.Port, SecureSocketOptions.StartTls);
await client.AuthenticateAsync(_smtpConfiguration.Email, _smtpConfiguration.Password);
await client.SendAsync(message);
}
catch (Exception e)
{
logger.LogError(e, "邮件发送失败");
}
finally
{
await client.DisconnectAsync(true);
}
}
[ProlongExpirationTime]
public async Task SendCommenterAsync(VmPublishComment? reply)
{
if (!_openService)
{
logger.LogInformation("invoke {Method}", nameof(SendCommenterAsync));
}
if (!string.IsNullOrEmpty(reply?.ReplyId))
{
var comment = await commentService.GetComment(reply.ReplyId);
if (comment is { Commenter: VmGuestCommenter guestCommenter } && guestCommenter.Email != reply.Email)
{
await SendEmailAsync(guestCommenter, comment, reply.NickName, reply.CommentText);
}
}
}
private async Task SendEmailAsync(
VmGuestCommenter guestCommenter,
CommentViewModel comment,
string nickName,
string commentText)
{
var message = new MimeMessage();
message.To.Add(new MailboxAddress("", guestCommenter.Email));
message.From.Add(new MailboxAddress("叫我阿胖-邮件服务", _smtpConfiguration.Email));
message.Subject = "您的评论有新的回复了。";
var bodyBuilder = new BodyBuilder
{
HtmlBody = await ReplyTemplate(comment, nickName, commentText)
};
message.Body = bodyBuilder.ToMessageBody();
using var client = new SmtpClient(protocolLogger);
client.ServerCertificateValidationCallback = (_, _, _, _) => true;
try
{
await client.ConnectAsync(_smtpConfiguration.Smtp, _smtpConfiguration.Port, SecureSocketOptions.StartTls);
await client.AuthenticateAsync(_smtpConfiguration.Email, _smtpConfiguration.Password);
await client.SendAsync(message);
}
catch (Exception e)
{
logger.LogError(e, "邮件发送失败");
}
finally
{
await client.DisconnectAsync(true);
}
}
[ProlongExpirationTime]
public async Task SendCommenterAsync(MembleComment? reply)
{
if (!_openService)
{
logger.LogInformation("invoke {Method}", nameof(SendCommenterAsync));
}
if (!string.IsNullOrEmpty(reply?.ReplyId))
{
var comment = await commentService.GetComment(reply.ReplyId);
if (comment is { Commenter: VmGuestCommenter guestCommenter })
{
await SendEmailAsync(guestCommenter, comment, reply.User.Name, reply.CommentText);
}
}
}
/// <summary>
/// 评论回复通知模板
/// </summary>
private static string _replyTemplate = "";
private async Task<string> ReplyTemplate(CommentViewModel comment, string nickName, string commentText)
{
if (string.IsNullOrEmpty(_replyTemplate))
{
var templatePath = Path.Combine(AppContext.BaseDirectory, "Template", "ReplyTemplate.html");
if (!File.Exists(templatePath)) return "";
using var reader = new StreamReader(templatePath);
_replyTemplate = await reader.ReadToEndAsync();
}
if (comment.Commenter is VmGuestCommenter guestCommenter)
{
var handlebars = Handlebars.Create();
handlebars.Configuration.TextEncoder = new IgnoreTextEncoder();
var template = handlebars.Compile(_replyTemplate);
return template(new Dictionary<string, string>
{
{ nameof(VmGuestCommenter.NickName), guestCommenter.NickName },
{ nameof(nickName), nickName },
{ nameof(CommentViewModel.PublishTime), comment.PublishTime.ToString("yyyy-MM-dd HH:mm:ss") },
{ nameof(CommentViewModel.CommentText), await HandleContent(comment.CommentText) },
{ "replyTime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") },
{ "replyText", await HandleContent(commentText) }
});
var context = BrowsingContext.New(Configuration.Default);
var document = await context.OpenAsync(y => y.Content(_replyTemplate));
var now = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
document.QuerySelector("span.comment-nickname")?.SetInnerText(guestCommenter.NickName);
document.QuerySelector("span.reply-nickname")?.SetInnerText(nickName);
document.QuerySelector("p.send-time")?.SetInnerText(now);
document.QuerySelector("span.head-nickname")?.SetInnerText(guestCommenter.NickName);
document.QuerySelector("time.head-timeago")
?.SetInnerText(comment.PublishTime.ToString("yyyy-MM-dd HH:mm:ss"));
var divCommentText = document.QuerySelector("div.comment-text");
if (divCommentText != null)
{
divCommentText.InnerHtml = await HandleContent(comment.CommentText);
}
document.QuerySelector("span.head-reply-nickname")?.SetInnerText(nickName);
document.QuerySelector("time.head-reply-timeago")?.SetInnerText(now);
var divReplyText = document.QuerySelector("div.reply-text");
if (divReplyText != null)
{
divReplyText.InnerHtml = await HandleContent(commentText);
}
return document.Body?.InnerHtml ?? "";
}
return "";
}
/// <summary>
/// 原始模板内容
/// </summary>
private static string _templateContent = "";
private async Task<string> MessageTemplate(VmPublishComment comment)
{
if (string.IsNullOrEmpty(_templateContent))
{
var templatePath = Path.Combine(AppContext.BaseDirectory, "Template", "MessageTemplate.html");
if (!File.Exists(templatePath)) return "";
using var reader = new StreamReader(templatePath);
_templateContent = await reader.ReadToEndAsync();
}
var handlebars = Handlebars.Create();
handlebars.Configuration.TextEncoder = new IgnoreTextEncoder();
var template = handlebars.Compile(_templateContent);
return template(new Dictionary<string, string>
{
{ nameof(VmPublishComment.NickName), comment.NickName },
{ nameof(VmPublishComment.Email), comment.Email },
{ nameof(VmPublishComment.SendTime), comment.SendTime.ToString("yyyy-MM-dd HH:mm:ss") },
{ nameof(VmPublishComment.CommentText), await HandleContent(comment.CommentText) },
});
}
private async Task<string> HandleContent(string? content)
{
if (string.IsNullOrEmpty(content))
return "";
var html = content.MarkdownToHtml();
var context = BrowsingContext.New(Configuration.Default);
var document = await context.OpenAsync(y => y.Content(html));
var images = document.GetElementsByTagName("img");
foreach (var image in images)
{
image.SetStyle("max-width:100%");
}
return document.Body?.InnerHtml ?? "";
}
internal class EmailConfiguration
{
public required string Email { get; set; }
public required string Password { get; set; }
public required string Smtp { get; set; }
public required int Port { get; set; }
}
}