using Newsbot.Collector.Database; using Newsbot.Collector.Database.Repositories; using Newsbot.Collector.Domain.Entities; using Newsbot.Collector.Domain.Interfaces; using Newsbot.Collector.Domain.Models; using Newsbot.Collector.Services.Notifications.Discord; using Serilog; namespace Newsbot.Collector.Services.Jobs; public class MessageTypeNotRequestedException : Exception { public MessageTypeNotRequestedException() { } public MessageTypeNotRequestedException(string message) : base(message) { } public MessageTypeNotRequestedException(string message, Exception inner) : base(message, inner) { } } public class DiscordNotificationJobOptions { public string? ConnectionString { get; init; } public string? OpenTelemetry { get; init; } public bool IsEnabled { get; init; } } public class DiscordNotificationJob { private const string JobName = "DiscordNotifications"; private ILogger _logger; //private DatabaseContext _databaseContext; private IArticlesRepository _article; private IAuthorTable _author; private IIconsRepository _icons; private IDiscordQueueRepository _queue; private ISourcesRepository _sources; private IDiscordNotificationRepository _subs; private IDiscordWebHooksRepository _webhook; public DiscordNotificationJob() { _queue = new DiscordQueueTable(""); _article = new ArticlesTable(""); _author = new AuthorsTable(""); _webhook = new DiscordWebhooksTable(""); _sources = new SourcesTable(""); _subs = new DiscordNotificationTable(""); _icons = new IconsTable(""); _logger = JobLogger.GetLogger("", JobName); } public void InitAndExecute(DiscordNotificationJobOptions options) { //_databaseContext = new DatabaseContext(options.ConnectionString ?? ""); _queue = new DiscordQueueTable(options.ConnectionString ?? ""); _article = new ArticlesTable(options.ConnectionString ?? ""); _webhook = new DiscordWebhooksTable(options.ConnectionString ?? ""); _sources = new SourcesTable(options.ConnectionString ?? ""); _subs = new DiscordNotificationTable(options.ConnectionString ?? ""); _icons = new IconsTable(options.ConnectionString ?? ""); _logger = JobLogger.GetLogger(options.OpenTelemetry ?? "", JobName); if (!options.IsEnabled) { _logger.Warning($"{JobName} - Going to exit because feature flag is off."); return; } _logger.Information($"{JobName} - Starting up the job."); Execute(); } private void Execute() { //collect all the new requests var requests = _queue.List(100); _logger.Debug($"{JobName} - Collected {requests.Count} items to send"); foreach (var request in requests) ProcessQueueItem(request); _logger.Information($"{JobName} - Loop has been completed."); } public void ProcessQueueItem(DiscordQueueEntity request) { _logger.Debug($"{JobName} - Processing {request.Id}"); // Get all details on the article in the queue var articleDetails = _article.GetById(request.ArticleId); // Get the details of the source var sourceDetails = _sources.GetById(articleDetails.SourceId); if (sourceDetails.Id == Guid.Empty) { _logger.Error( $"{JobName} - Article ({articleDetails.Id}) was linked to a empty Source ID. Removing from the queue."); _queue.Delete(request.Id); return; } var author = _author.GetBySourceIdAndNameAsync(sourceDetails.Id, sourceDetails.Name); author.Wait(); var sourceIcon = new IconEntity(); try { sourceIcon = _icons.GetBySourceId(sourceDetails.Id); } catch { _logger.Warning("{JobName} - Source ID \'{SourceDetailsId}\' is missing an icon", JobName, sourceDetails.Id); } // Find all the subscriptions for that source var allSubscriptions = _subs.ListBySourceId(sourceDetails.Id); foreach (var sub in allSubscriptions) SendSubscriptionNotification(request.Id, articleDetails, sourceDetails, sourceIcon, sub, author.Result ?? new AuthorEntity()); _logger.Debug("{JobName} - Removing {RequestId} from the queue", JobName, request.Id); _queue.Delete(request.Id); } public void SendSubscriptionNotification(Guid requestId, ArticlesEntity articleDetails, SourceEntity sourceDetails, IconEntity sourceIcon, DiscordNotificationEntity sub, AuthorEntity authorEntity) { // Check if the subscription code flags // If the article is a code commit and the subscription does not want them, skip. if (articleDetails.CodeIsCommit && !sub.CodeAllowCommits) throw new MessageTypeNotRequestedException("Message was a code commit and was not requested by the subscription."); // same for releases if (articleDetails.CodeIsRelease && !sub.CodeAllowReleases) throw new MessageTypeNotRequestedException("Message was a code release and was not requested by the subscription"); // find the discord webhooks we need to post to var discordDetails = _webhook.GetById(sub.DiscordWebHookId); if (discordDetails.Enabled == false) return; var client = new DiscordClient(discordDetails.Url); try { client.SendMessage(GenerateDiscordMessage(sourceDetails, articleDetails, sourceIcon, authorEntity)); } catch (Exception e) { _logger.Error("Failed to post message to Discord. {ErrorMessage}", e.Message); _logger.Debug("Queue Record: {RequestId}", requestId); _logger.Debug("Article: {ArticleDetailsId}", articleDetails.Id); _logger.Debug("Source: {SourceDetailsId}", sourceDetails.Id); _logger.Debug("Subscription: {SubId}", sub.Id); } Thread.Sleep(3000); } public DiscordMessage GenerateDiscordMessage(SourceEntity source, ArticlesEntity article, IconEntity icon, AuthorEntity author) { var embed = new DiscordMessageEmbed { Title = article.Title, Color = DiscordMessageEmbedColors.Red, Description = MessageValidation.ConvertHtmlCodes(article.Description), Author = new DiscordMessageEmbedAuthor { Name = author.Name, IconUrl = icon.FileName }, Footer = new DiscordMessageEmbedFooter { Text = "Brought to you by Newsbot" }, Fields = new DiscordMessageEmbedField[] { new() { Name = "Link", Value = article.Url, Inline = false } } }; if (article.Url is not null && article.Url != "") embed.Url = article.Url; if (article.Thumbnail != "") embed.Image = new DiscordMessageEmbedImage { Url = article.Thumbnail }; embed.Author.IconUrl = author.Image ?? ""; return new DiscordMessage { Embeds = new[] { embed } }; } }