Skip to content

Commit

Permalink
Fix schedule dates (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
Twinki14 authored Nov 18, 2023
1 parent 5597876 commit 8fe82ae
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 90 deletions.
4 changes: 4 additions & 0 deletions src/Miha.Discord/Extensions/DiscordTimestampExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Discord;
using NodaTime;

namespace Miha.Discord.Extensions;

Expand All @@ -7,6 +8,9 @@ public static class DiscordTimestampExtensions
public static string ToDiscordTimestamp(this DateTimeOffset offset) => TimestampTag.FromDateTimeOffset(offset).ToString();
public static string ToDiscordTimestamp(this DateTimeOffset offset, TimestampTagStyles style) => TimestampTag.FromDateTimeOffset(offset, style).ToString();

public static string ToDiscordTimestamp(this Instant instant) => TimestampTag.FromDateTimeOffset(instant.ToDateTimeOffset()).ToString();
public static string ToDiscordTimestamp(this Instant instant, TimestampTagStyles style) => TimestampTag.FromDateTimeOffset(instant.ToDateTimeOffset(), style).ToString();

public static string? ToDiscordTimestamp(this DateTimeOffset? offset) => offset?.ToDiscordTimestamp();
public static string? ToDiscordTimestamp(this DateTimeOffset? offset, TimestampTagStyles style) => offset?.ToDiscordTimestamp(style);
}
10 changes: 6 additions & 4 deletions src/Miha.Discord/Services/GuildScheduledEventService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,25 @@
using FluentResults;
using Microsoft.Extensions.Logging;
using Miha.Discord.Services.Interfaces;
using Miha.Shared;
using Miha.Shared.ZonedClocks.Interfaces;
using NodaTime;
using NodaTime.Calendars;
using NodaTime.Extensions;

namespace Miha.Discord.Services;

public class GuildScheduledEventService : IGuildScheduledEventService
{
private readonly DiscordSocketClient _discordClient;
private readonly IEasternStandardZonedClock _easternStandardZonedClock;
private readonly ILogger<GuildScheduledEventService> _logger;

public GuildScheduledEventService(
DiscordSocketClient discordClient,
IEasternStandardZonedClock easternStandardZonedClock,
ILogger<GuildScheduledEventService> logger)
{
_discordClient = discordClient;
_easternStandardZonedClock = easternStandardZonedClock;
_logger = logger;
}

Expand All @@ -38,8 +40,8 @@ public async Task<Result<IEnumerable<IGuildScheduledEvent>>> GetScheduledWeeklyE

var eventsThisWeek = events.Where(guildEvent =>
{
var estDate = guildEvent.StartTime.ToZonedDateTime()
.WithZone(DateTimeZoneProviders.Tzdb[Timezones.IanaEasternTime]).Date;
var estDate = _easternStandardZonedClock.ToZonedDateTime(guildEvent.StartTime).Date;

var weekOfDate = WeekYearRules.Iso.GetWeekOfWeekYear(estDate);

return weekOfDate == weekNumberInYear;
Expand Down
159 changes: 116 additions & 43 deletions src/Miha.Discord/Services/Hosted/GuildEventScheduleService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,43 +116,78 @@ private async Task PostWeeklyScheduleAsync()
return;
}

var eventsByDay = new Dictionary<string, IList<IGuildScheduledEvent>>();
foreach (var guildScheduledEvent in eventsThisWeek.OrderBy(e => e.StartTime.Date))
var daysThisWeek = _easternStandardZonedClock.GetCurrentWeekAsDates();

var eventsByDay = new Dictionary<DateOnly, IList<IGuildScheduledEvent>>();
var eventsThisWeekList = eventsThisWeek.ToList();
foreach (var dayOfWeek in daysThisWeek.OrderBy(d => d))
{
var day = guildScheduledEvent
.StartTime
.ToZonedDateTime()
.WithZone(_easternStandardZonedClock.GetTzdbTimeZone())
.Date.AtMidnight().ToDateTimeUnspecified().ToString("dddd");
eventsByDay.Add(dayOfWeek, new List<IGuildScheduledEvent>());

if (!eventsByDay.ContainsKey(day))
foreach (var guildScheduledEvent in eventsThisWeekList.Where(e => _easternStandardZonedClock.ToZonedDateTime(e.StartTime).Date.ToDateOnly() == dayOfWeek))
{
eventsByDay.Add(day, new List<IGuildScheduledEvent>());
eventsByDay[dayOfWeek].Add(guildScheduledEvent);
}

eventsByDay[day].Add(guildScheduledEvent);
}

_logger.LogInformation("Wiping weekly schedule messages");

var deletedMessages = 0;
var messages = await weeklyScheduleChannel

_logger.LogInformation("Updating weekly schedule");

var postedHeader = false;
var postedFooter = false;

var messages = (await weeklyScheduleChannel
.GetMessagesAsync(50)
.FlattenAsync();
.FlattenAsync())
.ToList();

foreach (var message in messages.Where(m => m.Author.Id == _client.CurrentUser.Id))
// Wipe any existing messages if a message by day doesn't already exist
foreach (var (day, _) in eventsByDay)
{
await message.DeleteAsync();
deletedMessages++;
}
var lastPostedMessage = messages
.FirstOrDefault(m =>
m.Author.Id == _client.CurrentUser.Id &&
m.Embeds.Any(e => e.Description.Contains(day.ToString("dddd"))));

_logger.LogInformation("Wiped {DeletedMessages} messages", deletedMessages);

_logger.LogInformation("Posting weekly schedule");
if (lastPostedMessage is not null)
{
continue;
}

var messagesToDelete = messages
.Where(m => m.Author.Id == _client.CurrentUser.Id)
.ToList();

if (messagesToDelete.Any())
{
var deletedMessages = 0;

_logger.LogInformation("Wiping posted messages");

foreach (var message in messagesToDelete)
{
await message.DeleteAsync();
deletedMessages++;
}

_logger.LogInformation("Deleted {DeletedMessages} messages", deletedMessages);

// Update the messages list
messages = (await weeklyScheduleChannel
.GetMessagesAsync(50)
.FlattenAsync())
.ToList();
}

break;
}

var postedHeader = false;
var postedFooter = false;
// TODO - Future me
// If the ordering becomes a problem, a potential solution could be to use an index
// to update the message at [1] (Tuesday), [6] (Sunday), [0] Monday for example
// this would ensure the order of messages align with the days of the week
// and to delete all messages from the bot if there's any more than 7 messages total

// Update (or post) a message with an embed of events for that day, for each day of the week
foreach (var (day, events) in eventsByDay)
{
var embed = new EmbedBuilder();
Expand All @@ -165,28 +200,50 @@ private async Task PostWeeklyScheduleAsync()
.WithAuthor("Weekly event schedule", _client.CurrentUser.GetAvatarUrl());
postedHeader = true;
}

var timeStamp = day
.ToLocalDate()
.AtStartOfDayInZone(_easternStandardZonedClock.GetTzdbTimeZone())
.ToInstant()
.ToDiscordTimestamp(TimestampTagStyles.ShortDate);

description.AppendLine("### " + day + " - " + DiscordTimestampExtensions.ToDiscordTimestamp(events.First().StartTime.Date, TimestampTagStyles.ShortDate));

foreach (var guildEvent in events.OrderBy(e => e.StartTime))
{
var location = guildEvent.Location ?? "Unknown";
var url = $"https://discord.com/events/{guildEvent.Guild.Id}/{guildEvent.Id}";
description.AppendLine($"### {day.ToString("dddd")} - {timeStamp}");

if (location is "Unknown" && guildEvent.ChannelId is not null)
if (!events.Any())
{
description.AppendLine("*No events scheduled*");
}
else
{
foreach (var guildEvent in events.OrderBy(e => e.StartTime))
{
location = "Discord";
}
var location = guildEvent.Location ?? "Unknown";
var url = $"https://discord.com/events/{guildEvent.Guild.Id}/{guildEvent.Id}";

description.AppendLine($"- [{location} - {guildEvent.Name}]({url})");
description.AppendLine($" - {guildEvent.StartTime.ToDiscordTimestamp(TimestampTagStyles.ShortTime)} - {guildEvent.StartTime.ToDiscordTimestamp(TimestampTagStyles.Relative)}");
if (location is "Unknown" && guildEvent.ChannelId is not null)
{
location = "Discord";
}

if (guildEvent.Creator is not null)
{
description.AppendLine($" - Hosted by {guildEvent.Creator.Mention}");
description.AppendLine($"- [{location} - {guildEvent.Name}]({url})");

description.Append($" - {guildEvent.StartTime.ToDiscordTimestamp(TimestampTagStyles.ShortTime)}");
if (guildEvent.Status is GuildScheduledEventStatus.Active)
{
description.AppendLine(" - Happening now!");
}
else
{
description.AppendLine($" - {guildEvent.StartTime.ToDiscordTimestamp(TimestampTagStyles.Relative)}");
}

if (guildEvent.Creator is not null)
{
description.AppendLine($" - Hosted by {guildEvent.Creator.Mention}");
}
}
}

if (!postedFooter && day == eventsByDay.Last().Key)
{
embed
Expand All @@ -199,10 +256,26 @@ private async Task PostWeeklyScheduleAsync()
embed
.WithColor(new Color(255, 43, 241))
.WithDescription(description.ToString());

var lastPostedMessage = messages
.FirstOrDefault(m =>
m.Author.Id == _client.CurrentUser.Id &&
m.Embeds.Any(e => e.Description.Contains(day.ToString("dddd"))));

await weeklyScheduleChannel.SendMessageAsync(embed: embed.Build());
if (lastPostedMessage is null)
{
_logger.LogInformation("Posting new message");
await weeklyScheduleChannel.SendMessageAsync(embed: embed.Build());
}
else
{
await weeklyScheduleChannel.ModifyMessageAsync(lastPostedMessage.Id, props =>
{
props.Embed = embed.Build();
});
}

_logger.LogInformation("Finished posting weekly schedule");
_logger.LogInformation("Finished updating weekly schedule");
}
}

Expand Down
39 changes: 30 additions & 9 deletions src/Miha.Shared/ZonedClocks/Interfaces/IZonedClock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,38 +9,59 @@ public interface IZonedClock : IClock
/// to the time zone of this object.
/// </summary>
/// <returns>The current instant provided by the underlying clock, adjusted to the
/// time zone of this object.</returns>
/// time zone of this <see cref="IZonedClock"/>.</returns>
public ZonedDateTime GetCurrentZonedDateTime();

/// <summary>
/// Returns the local date/time of the current instant provided by the underlying clock, adjusted
/// to the time zone of this object.
/// to the time zone of this <see cref="IZonedClock"/>.
/// </summary>
/// <returns>The local date/time of the current instant provided by the underlying clock, adjusted to the
/// time zone of this object.</returns>
/// time zone of this <see cref="IZonedClock"/>.</returns>
public LocalDateTime GetCurrentLocalDateTime();

/// <summary>
/// Returns the offset date/time of the current instant provided by the underlying clock, adjusted
/// to the time zone of this object.
/// to the time zone of this <see cref="IZonedClock"/>.
/// </summary>
/// <returns>The offset date/time of the current instant provided by the underlying clock, adjusted to the
/// time zone of this object.</returns>
/// time zone of this <see cref="IZonedClock"/>.</returns>
public OffsetDateTime GetCurrentOffsetDateTime();

/// <summary>
/// Returns the local date of the current instant provided by the underlying clock, adjusted
/// to the time zone of this object.
/// to the time zone of this <see cref="IZonedClock"/>.
/// </summary>
/// <returns>The local date of the current instant provided by the underlying clock, adjusted to the
/// time zone of this object.</returns>
/// time zone of this <see cref="IZonedClock"/>.</returns>
public LocalDate GetCurrentDate();

/// <summary>
/// Returns the local time of the current instant provided by the underlying clock, adjusted
/// to the time zone of this object.
/// to the time zone of this <see cref="IZonedClock"/>.
/// </summary>
/// <returns>The local time of the current instant provided by the underlying clock, adjusted to the
/// time zone of this object.</returns>
/// time zone of this <see cref="IZonedClock"/>.</returns>
public LocalTime GetCurrentTimeOfDay();

/// <summary>
/// Returns the current week of the year, adjusted to the time zone of this <see cref="IZonedClock"/>, and abides
/// by the ISO-8601 standard.
/// </summary>
/// <returns>The current week of the year, adjusted to the time zone of this <see cref="IZonedClock"/>.</returns>
public int GetCurrentWeek();

/// <summary>
/// Returns an enumerable containing all the days of the current week, adjusted
/// to the time zone of this <see cref="IZonedClock"/> and abides by the ISO-8601 standard.
/// </summary>
/// <returns>A list of dates of the current week, adjusted to the time zone of this <see cref="IZonedClock"/>.</returns>
public IEnumerable<DateOnly> GetCurrentWeekAsDates(IsoDayOfWeek isoDayOfWeek = IsoDayOfWeek.Monday);

/// <summary>
/// Converts an offset to a <see cref="ZonedDateTime"/> using the time zone of this <see cref="IZonedClock"/>.
/// </summary>
/// <param name="offset"></param>
/// <returns></returns>
public ZonedDateTime ToZonedDateTime(DateTimeOffset offset);
}
Loading

0 comments on commit 8fe82ae

Please sign in to comment.