Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
Twinki14 committed Aug 15, 2023
1 parent e71c919 commit 138092a
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 9 deletions.
90 changes: 88 additions & 2 deletions src/Miha.Discord/Services/BirthdayAnnouncementService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,44 @@
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
/// <summary>
/// Announces birthdays to a channel by checking and using <see cref="BirthdayJobDocument"/>s in the database
/// </summary>
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<BirthdayAnnouncementService> _logger;
private const string Schedule = "0,5,10,15,20,25,30,35,40,45,50,55 8-19 * * *"; // https://crontab.cronhub.io/

private readonly CronExpression _cron;

public BirthdayAnnouncementService(
DiscordSocketClient client,
IEasternStandardZonedClock easternStandardZonedClock,
IGuildService guildService,
IUserService userService,
IBirthdayJobService birthdayJobService,
IEasternStandardZonedClock easternStandardZonedClock,
IOptions<DiscordOptions> discordOptions,
ILogger<BirthdayAnnouncementService> 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);
Expand All @@ -47,6 +63,76 @@ 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)
{
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);
}

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);
}
38 changes: 38 additions & 0 deletions src/Miha.Logic/Services/GuildService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,44 @@ public async Task<Result<ITextChannel>> GetAnnouncementChannelAsync(ulong? guild
}
}

public async Task<Result<ITextChannel>> 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<ITextChannel>();
}

var birthdayAnnouncementChannel = optionsResult.Value?.BirthdayAnnouncementChannel;
if (!birthdayAnnouncementChannel.HasValue)
{
_logger.LogDebug("Guild doesn't have a birthday announcement channel set {GuildId}", guildId);
return Result.Fail<ITextChannel>("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<ITextChannel>("Announcement channel not found");
}
catch (Exception e)
{
LogErrorException(e);
return Result.Fail<ITextChannel>(e.Message);
}
}

public async Task<Result<IRole>> GetAnnouncementRoleAsync(ulong? guildId)
{
try
Expand Down
1 change: 1 addition & 0 deletions src/Miha.Logic/Services/Interfaces/IGuildService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ public interface IGuildService

Task<Result<ITextChannel>> GetLoggingChannelAsync(ulong? guildId);
Task<Result<ITextChannel>> GetAnnouncementChannelAsync(ulong? guildId);
Task<Result<ITextChannel>> GetBirthdayAnnouncementChannelAsync(ulong? guildId);
Task<Result<IRole>> GetAnnouncementRoleAsync(ulong? guildId);
}
4 changes: 3 additions & 1 deletion src/Miha.Logic/Services/Interfaces/IUserService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FluentResults;
using Discord;
using FluentResults;
using Miha.Redis.Documents;
using NodaTime;

Expand All @@ -11,6 +12,7 @@ public interface IUserService
Task<Result<UserDocument?>> UpsertAsync(ulong? userId, Action<UserDocument> userFunc);
Task<Result> DeleteAsync(ulong? userId, bool successIfNotFound = false);

Task<Result<IUser>> GetUserAsync(ulong? userId);
Task<Result<IEnumerable<UserDocument>>> GetAllUsersWithBirthdayForWeekAsync(LocalDate weekDate, bool includeAlreadyAnnounced);
Task<Result<UserDocument?>> UpsertVrchatUserIdAsync(ulong? userId, string vrcProfileUrl);
}
29 changes: 29 additions & 0 deletions src/Miha.Logic/Services/UserService.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Text.RegularExpressions;
using Discord;
using Discord.WebSocket;
using FluentResults;
using Microsoft.Extensions.Logging;
using Miha.Logic.Services.Interfaces;
Expand All @@ -11,17 +13,44 @@ namespace Miha.Logic.Services;

public partial class UserService : DocumentService<UserDocument>, IUserService
{
private readonly DiscordSocketClient _client;
private readonly IUserRepository _repository;
private readonly ILogger<UserService> _logger;

public UserService(
DiscordSocketClient client,
IUserRepository repository,
ILogger<UserService> logger) : base(repository, logger)
{
_client = client;
_repository = repository;
_logger = logger;
}

public async Task<Result<IUser>> 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<IUser>("User not found");
}
catch (Exception e)
{
LogErrorException(e);
return Result.Fail<IUser>(e.Message);
}
}

public async Task<Result<IEnumerable<UserDocument>>> GetAllUsersWithBirthdayForWeekAsync(LocalDate weekDate, bool includeAlreadyAnnounced)
{
var weekNumberInYear = WeekYearRules.Iso.GetWeekOfWeekYear(weekDate);
Expand Down
5 changes: 4 additions & 1 deletion src/Miha.Redis/Documents/BirthdayJobDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ namespace Miha.Redis.Documents;
public class BirthdayJobDocument : Document
{
[Indexed]
public ulong UserDocumentId { get; set; }
public ulong UserId { get; set; }

/// <summary>
/// Users birthdate converted to EST
/// </summary>
[Indexed]
public LocalDate BirthdayDate { get; set; }
}
10 changes: 5 additions & 5 deletions src/Miha/Services/BirthdayScannerService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,27 +75,27 @@ 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)
{
var result = await _birthdayJobService.UpsertAsync(new BirthdayJobDocument
{
Id = unscheduledBirthday.Id,
UserDocumentId = unscheduledBirthday.Id,
UserId = unscheduledBirthday.Id,
BirthdayDate = unscheduledBirthday.GetBirthdateInEst(today.Year)!.Value
});

Expand Down

0 comments on commit 138092a

Please sign in to comment.