diff --git a/src/Miha.Discord/Services/BirthdayAnnouncementService.cs b/src/Miha.Discord/Services/BirthdayAnnouncementService.cs
index 3bab6ca..2204ca4 100644
--- a/src/Miha.Discord/Services/BirthdayAnnouncementService.cs
+++ b/src/Miha.Discord/Services/BirthdayAnnouncementService.cs
@@ -3,15 +3,24 @@
using Discord.Addons.Hosting.Util;
using Discord.WebSocket;
using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
using Miha.Logic.Services.Interfaces;
+using Miha.Redis.Documents;
using Miha.Shared.ZonedClocks.Interfaces;
namespace Miha.Discord.Services;
-public class BirthdayAnnouncementService : DiscordClientService
+///
+/// Announces birthdays to a channel by checking and using s in the database
+///
+public partial class BirthdayAnnouncementService : DiscordClientService
{
private readonly DiscordSocketClient _client;
+ private readonly IGuildService _guildService;
+ private readonly IUserService _userService;
+ private readonly IBirthdayJobService _birthdayJobService;
private readonly IEasternStandardZonedClock _easternStandardZonedClock;
+ private readonly DiscordOptions _discordOptions;
private readonly ILogger _logger;
private const string Schedule = "0,5,10,15,20,25,30,35,40,45,50,55 8-19 * * *"; // https://crontab.cronhub.io/
@@ -19,12 +28,19 @@ public class BirthdayAnnouncementService : DiscordClientService
public BirthdayAnnouncementService(
DiscordSocketClient client,
- IEasternStandardZonedClock easternStandardZonedClock,
+ IGuildService guildService,
+ IUserService userService,
IBirthdayJobService birthdayJobService,
+ IEasternStandardZonedClock easternStandardZonedClock,
+ IOptions discordOptions,
ILogger logger) : base(client, logger)
{
_client = client;
+ _guildService = guildService;
+ _userService = userService;
+ _birthdayJobService = birthdayJobService;
_easternStandardZonedClock = easternStandardZonedClock;
+ _discordOptions = discordOptions.Value;
_logger = logger;
_cron = CronExpression.Parse(Schedule, CronFormat.Standard);
@@ -47,6 +63,82 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)
}
await Task.Delay(nextUtc.Value - utcNow, stoppingToken);
+
+ await AnnounceBirthdaysAsync();
}
}
+
+ private async Task AnnounceBirthdaysAsync()
+ {
+ SocketGuild guild;
+
+ try
+ {
+ guild = Client.GetGuild(_discordOptions.Guild!.Value);
+ if (guild is null)
+ {
+ _logger.LogCritical("Guild is null {GuildId}", _discordOptions.Guild.Value);
+ return;
+ }
+ }
+ catch (Exception e)
+ {
+ LogError(e);
+ return;
+ }
+
+ var birthdayAnnouncementChannel = await _guildService.GetBirthdayAnnouncementChannelAsync(guild.Id);
+ if (birthdayAnnouncementChannel.IsFailed)
+ {
+ // TODO
+ return;
+ }
+
+ var jobDocuments = await _birthdayJobService.GetAllAsync();
+ if (jobDocuments.IsFailed)
+ {
+ // TODO
+ return;
+ }
+
+ var today = _easternStandardZonedClock.GetCurrentDate();
+ foreach (var birthday in jobDocuments.Value.Where(s => s.BirthdayDate == today))
+ {
+ var user = await _userService.GetAsync(birthday.UserId);
+ var userDoc = await _userService.GetAsync(birthday.UserId);
+
+ if (user.IsFailed || user.Value is null)
+ {
+ continue;
+ }
+
+ if (userDoc.IsFailed || userDoc.Value is null)
+ {
+ continue;
+ }
+
+ if (userDoc.Value.EnableBirthday is false)
+ {
+ await _userService.UpsertAsync(userDoc.Value.Id, doc => doc.LastBirthdateAnnouncement = today);
+ }
+
+ // do announcement
+
+ var result = await _userService.UpsertAsync(userDoc.Value.Id, doc => doc.LastBirthdateAnnouncement = today);
+ var delete = await _birthdayJobService.DeleteAsync(birthday.Id);
+ }
+
+
+ // pull all birthday job documents, remove any that don't have a birth date of today (in est, should be already converted)
+ // for each birthday job document, we need to get the userDoc and user for it
+ // if the userDoc birthday is disabled, remove the birthdayjobdocument and set it as announced
+ // if the user has a role that isn't whitelisted, remove the job doc and set it as announced
+ // announce the birthday finally and set it as announced
+ }
+
+ [LoggerMessage(EventId = 1, Level = LogLevel.Error, Message = "Exception occurred in BirthdayAnnouncementService")]
+ public partial void LogError(Exception e);
+
+ [LoggerMessage(EventId = 2, Level = LogLevel.Error, Message = "Failed to get the configured announcement channel")]
+ public partial void LogBirthdayAnnouncementChannelFailure();
}
diff --git a/src/Miha.Logic/Services/GuildService.cs b/src/Miha.Logic/Services/GuildService.cs
index 407e505..dc652ca 100644
--- a/src/Miha.Logic/Services/GuildService.cs
+++ b/src/Miha.Logic/Services/GuildService.cs
@@ -98,6 +98,44 @@ public async Task> GetAnnouncementChannelAsync(ulong? guild
}
}
+ public async Task> GetBirthdayAnnouncementChannelAsync(ulong? guildId)
+ {
+ try
+ {
+ if (guildId is null)
+ {
+ throw new ArgumentNullException(nameof(guildId));
+ }
+
+ var optionsResult = await GetAsync(guildId);
+ if (optionsResult.IsFailed)
+ {
+ _logger.LogWarning("Guild doesn't have any document when trying to get birthday announcement channel {GuildId}", guildId);
+ return optionsResult.ToResult();
+ }
+
+ var birthdayAnnouncementChannel = optionsResult.Value?.BirthdayAnnouncementChannel;
+ if (!birthdayAnnouncementChannel.HasValue)
+ {
+ _logger.LogDebug("Guild doesn't have a birthday announcement channel set {GuildId}", guildId);
+ return Result.Fail("Announcement channel not set");
+ }
+
+ if (await _client.GetChannelAsync(birthdayAnnouncementChannel.Value) is ITextChannel loggingChannel)
+ {
+ return Result.Ok(loggingChannel);
+ }
+
+ _logger.LogWarning("Guild's birthday announcement channel wasn't found, or might not be a Text Channel {GuildId} {BirthdayAnnouncementChannelAnnouncementChannelId}", guildId, birthdayAnnouncementChannel.Value);
+ return Result.Fail("Announcement channel not found");
+ }
+ catch (Exception e)
+ {
+ LogErrorException(e);
+ return Result.Fail(e.Message);
+ }
+ }
+
public async Task> GetAnnouncementRoleAsync(ulong? guildId)
{
try
diff --git a/src/Miha.Logic/Services/Interfaces/IGuildService.cs b/src/Miha.Logic/Services/Interfaces/IGuildService.cs
index 716aa94..87e6d7e 100644
--- a/src/Miha.Logic/Services/Interfaces/IGuildService.cs
+++ b/src/Miha.Logic/Services/Interfaces/IGuildService.cs
@@ -13,5 +13,6 @@ public interface IGuildService
Task> GetLoggingChannelAsync(ulong? guildId);
Task> GetAnnouncementChannelAsync(ulong? guildId);
+ Task> GetBirthdayAnnouncementChannelAsync(ulong? guildId);
Task> GetAnnouncementRoleAsync(ulong? guildId);
}
diff --git a/src/Miha.Logic/Services/Interfaces/IUserService.cs b/src/Miha.Logic/Services/Interfaces/IUserService.cs
index 12967b0..4faa4ee 100644
--- a/src/Miha.Logic/Services/Interfaces/IUserService.cs
+++ b/src/Miha.Logic/Services/Interfaces/IUserService.cs
@@ -1,4 +1,5 @@
-using FluentResults;
+using Discord;
+using FluentResults;
using Miha.Redis.Documents;
using NodaTime;
@@ -11,6 +12,7 @@ public interface IUserService
Task> UpsertAsync(ulong? userId, Action userFunc);
Task DeleteAsync(ulong? userId, bool successIfNotFound = false);
+ Task> GetUserAsync(ulong? userId);
Task>> GetAllUsersWithBirthdayForWeekAsync(LocalDate weekDate, bool includeAlreadyAnnounced);
Task> UpsertVrchatUserIdAsync(ulong? userId, string vrcProfileUrl);
}
diff --git a/src/Miha.Logic/Services/UserService.cs b/src/Miha.Logic/Services/UserService.cs
index 764ead2..cb69473 100644
--- a/src/Miha.Logic/Services/UserService.cs
+++ b/src/Miha.Logic/Services/UserService.cs
@@ -1,4 +1,6 @@
using System.Text.RegularExpressions;
+using Discord;
+using Discord.WebSocket;
using FluentResults;
using Microsoft.Extensions.Logging;
using Miha.Logic.Services.Interfaces;
@@ -11,17 +13,44 @@ namespace Miha.Logic.Services;
public partial class UserService : DocumentService, IUserService
{
+ private readonly DiscordSocketClient _client;
private readonly IUserRepository _repository;
private readonly ILogger _logger;
public UserService(
+ DiscordSocketClient client,
IUserRepository repository,
ILogger logger) : base(repository, logger)
{
+ _client = client;
_repository = repository;
_logger = logger;
}
+ public async Task> GetUserAsync(ulong? userId)
+ {
+ try
+ {
+ if (userId is null)
+ {
+ throw new ArgumentNullException(nameof(userId));
+ }
+
+ if (await _client.GetUserAsync(userId.Value) is { } user)
+ {
+ return Result.Ok(user);
+ }
+
+ _logger.LogWarning("User Id did not correspond to a known Discord user {UserId}", userId.Value);
+ return Result.Fail("User not found");
+ }
+ catch (Exception e)
+ {
+ LogErrorException(e);
+ return Result.Fail(e.Message);
+ }
+ }
+
public async Task>> GetAllUsersWithBirthdayForWeekAsync(LocalDate weekDate, bool includeAlreadyAnnounced)
{
var weekNumberInYear = WeekYearRules.Iso.GetWeekOfWeekYear(weekDate);
diff --git a/src/Miha.Redis/Documents/BirthdayJobDocument.cs b/src/Miha.Redis/Documents/BirthdayJobDocument.cs
index 5217c59..8a48005 100644
--- a/src/Miha.Redis/Documents/BirthdayJobDocument.cs
+++ b/src/Miha.Redis/Documents/BirthdayJobDocument.cs
@@ -7,8 +7,11 @@ namespace Miha.Redis.Documents;
public class BirthdayJobDocument : Document
{
[Indexed]
- public ulong UserDocumentId { get; set; }
+ public ulong UserId { get; set; }
+ ///
+ /// Users birthdate converted to EST
+ ///
[Indexed]
public LocalDate BirthdayDate { get; set; }
}
diff --git a/src/Miha/Services/BirthdayScannerService.cs b/src/Miha/Services/BirthdayScannerService.cs
index c7f4520..f3c1d96 100644
--- a/src/Miha/Services/BirthdayScannerService.cs
+++ b/src/Miha/Services/BirthdayScannerService.cs
@@ -75,19 +75,19 @@ private async Task ScanBirthdaysAsync()
return;
}
- var birthdayJobs = await _birthdayJobService.GetAllAsync();
+ // TODO This could be changed to query birthday job documents whose UserId matches any of the users who have birthdays this week
+ var birthdayJobs = await _birthdayJobService.GetAllAsync();
if (birthdayJobs.IsFailed)
{
_logger.LogError("Failed getting birthday jobs");
return;
}
- var unscheduledBirthdays = unannouncedBirthdaysThisWeek.Value.Where(user => !birthdayJobs.Value.Contains(new BirthdayJobDocument { UserDocumentId = user.Id })).ToList();
-
+ var unscheduledBirthdays = unannouncedBirthdaysThisWeek.Value.Where(user => !birthdayJobs.Value.Contains(new BirthdayJobDocument { UserId = user.Id })).ToList();
if (!unscheduledBirthdays.Any())
{
- _logger.LogInformation("All birthdays for this week are already scheduled");
+ _logger.LogDebug("All birthdays for this week are already scheduled");
}
foreach (var unscheduledBirthday in unscheduledBirthdays)
@@ -95,7 +95,7 @@ private async Task ScanBirthdaysAsync()
var result = await _birthdayJobService.UpsertAsync(new BirthdayJobDocument
{
Id = unscheduledBirthday.Id,
- UserDocumentId = unscheduledBirthday.Id,
+ UserId = unscheduledBirthday.Id,
BirthdayDate = unscheduledBirthday.GetBirthdateInEst(today.Year)!.Value
});