diff --git a/src/StevesBot.Worker.Tests/Unit/LockReleaserTests.cs b/src/StevesBot.Worker.Tests/Unit/LockReleaserTests.cs new file mode 100644 index 0000000..5b534e8 --- /dev/null +++ b/src/StevesBot.Worker.Tests/Unit/LockReleaserTests.cs @@ -0,0 +1,45 @@ +namespace StevesBot.Worker.Tests.Unit; + +public class LockReleaserTests +{ + [Fact] + public void Constructor_WhenCalled_ItShouldCreateInstance() + { + using var semaphore = new SemaphoreSlim(1); + using var lockReleaser = new LockReleaser(semaphore); + + lockReleaser.Should().NotBeNull(); + lockReleaser.Should().BeOfType(); + } + + [Fact] + public void Constructor_WhenCalledAndSemaphoreIsNull_ItShouldThrowArgumentNullException() + { + var act = static () => new LockReleaser(null!); + + act.Should().Throw(); + } + + [Fact] + public void Dispose_WhenCalled_ItShouldReleaseSemaphore() + { + using var semaphore = new SemaphoreSlim(0, 1); + using var lockReleaser = new LockReleaser(semaphore); + + lockReleaser.Dispose(); + + semaphore.CurrentCount.Should().Be(1); + } + + [Fact] + public void Dispose_WhenCalledMultipleTimes_ItShouldReleaseSemaphoreOnlyOnce() + { + using var semaphore = new SemaphoreSlim(0, 1); + using var lockReleaser = new LockReleaser(semaphore); + + lockReleaser.Dispose(); + lockReleaser.Dispose(); + + semaphore.CurrentCount.Should().Be(1); + } +} \ No newline at end of file diff --git a/src/StevesBot.Worker/Discord/DiscordIntents.cs b/src/StevesBot.Worker/Discord/DiscordIntents.cs new file mode 100644 index 0000000..5d9e28c --- /dev/null +++ b/src/StevesBot.Worker/Discord/DiscordIntents.cs @@ -0,0 +1,47 @@ +namespace StevesBot.Worker.Discord; + +internal static class DiscordIntents +{ + public const int Guilds = 1 << 0; + public const int GuildMembers = 1 << 1; + public const int GuildModeration = 1 << 2; + public const int GuildExpressions = 1 << 3; + public const int GuildIntegrations = 1 << 4; + public const int GuildWebhooks = 1 << 5; + public const int GuildInvites = 1 << 6; + public const int GuildVoiceStates = 1 << 7; + public const int GuildPresences = 1 << 8; + public const int GuildMessages = 1 << 9; + public const int GuildMessageReactions = 1 << 10; + public const int GuildMessageTyping = 1 << 11; + public const int DirectMessages = 1 << 12; + public const int DirectMessageReactions = 1 << 13; + public const int DirectMessageTyping = 1 << 14; + public const int MessageContent = 1 << 15; + public const int GuildScheduledEvents = 1 << 16; + public const int AutoModerationConfiguration = 1 << 20; + public const int AutoModerationExecution = 1 << 21; + public const int GuildMessagePolls = 1 << 24; + public const int DirectMessagePolls = 1 << 25; + public const int All = Guilds + | GuildMembers + | GuildModeration + | GuildExpressions + | GuildIntegrations + | GuildWebhooks + | GuildInvites + | GuildVoiceStates + | GuildPresences + | GuildMessages + | GuildMessageReactions + | GuildMessageTyping + | DirectMessages + | DirectMessageReactions + | DirectMessageTyping + | MessageContent + | GuildScheduledEvents + | AutoModerationConfiguration + | AutoModerationExecution + | GuildMessagePolls + | DirectMessagePolls; +} diff --git a/src/StevesBot.Worker/Discord/Events/DiscordEventConverter.cs b/src/StevesBot.Worker/Discord/Events/DiscordEventConverter.cs index da3f641..cb611af 100644 --- a/src/StevesBot.Worker/Discord/Events/DiscordEventConverter.cs +++ b/src/StevesBot.Worker/Discord/Events/DiscordEventConverter.cs @@ -12,13 +12,26 @@ internal sealed class DiscordEventConverter : JsonConverter return op switch { + DiscordOpCodes.Dispatch => DeserializeDispatchEvent(root, options), DiscordOpCodes.Hello => JsonSerializer.Deserialize(root.GetRawText(), options), + DiscordOpCodes.HeartbeatAck => JsonSerializer.Deserialize(root.GetRawText(), options), _ => JsonSerializer.Deserialize(root.GetRawText(), options) }; } public override void Write(Utf8JsonWriter writer, DiscordEvent value, JsonSerializerOptions options) { - throw new NotImplementedException(); + JsonSerializer.Serialize(writer, value, value.GetType(), options); + } + + private static DiscordEvent? DeserializeDispatchEvent(JsonElement root, JsonSerializerOptions options) + { + var type = root.GetProperty("t").GetString(); + + return type switch + { + DiscordEventTypes.Ready => JsonSerializer.Deserialize(root.GetRawText(), options), + _ => JsonSerializer.Deserialize(root.GetRawText(), options), + }; } } \ No newline at end of file diff --git a/src/StevesBot.Worker/Discord/Events/DiscordEventTypes.cs b/src/StevesBot.Worker/Discord/Events/DiscordEventTypes.cs new file mode 100644 index 0000000..0c64469 --- /dev/null +++ b/src/StevesBot.Worker/Discord/Events/DiscordEventTypes.cs @@ -0,0 +1,6 @@ +namespace StevesBot.Worker.Discord.Events; + +internal static class DiscordEventTypes +{ + public const string Ready = "READY"; +} \ No newline at end of file diff --git a/src/StevesBot.Worker/Discord/Events/DiscordOpCodes.cs b/src/StevesBot.Worker/Discord/Events/DiscordOpCodes.cs index 7eefb1d..1ddcaa5 100644 --- a/src/StevesBot.Worker/Discord/Events/DiscordOpCodes.cs +++ b/src/StevesBot.Worker/Discord/Events/DiscordOpCodes.cs @@ -2,5 +2,8 @@ namespace StevesBot.Worker.Discord.Events; internal static class DiscordOpCodes { + public const int Dispatch = 0; + public const int Heartbeat = 1; + public const int HeartbeatAck = 11; public const int Hello = 10; } \ No newline at end of file diff --git a/src/StevesBot.Worker/Discord/Events/HeartbeatAckDiscordEvent.cs b/src/StevesBot.Worker/Discord/Events/HeartbeatAckDiscordEvent.cs new file mode 100644 index 0000000..e67fde8 --- /dev/null +++ b/src/StevesBot.Worker/Discord/Events/HeartbeatAckDiscordEvent.cs @@ -0,0 +1,5 @@ +namespace StevesBot.Worker.Discord.Events; + +internal record HeartbeatAckDiscordEvent : DiscordEvent +{ +} \ No newline at end of file diff --git a/src/StevesBot.Worker/Discord/Events/HeartbeatDiscordEvent.cs b/src/StevesBot.Worker/Discord/Events/HeartbeatDiscordEvent.cs new file mode 100644 index 0000000..a0b6aaa --- /dev/null +++ b/src/StevesBot.Worker/Discord/Events/HeartbeatDiscordEvent.cs @@ -0,0 +1,10 @@ +namespace StevesBot.Worker.Discord.Events; + +internal record HeartbeatDiscordEvent : DiscordEvent +{ + public HeartbeatDiscordEvent(int? sequence) + { + OpCode = DiscordOpCodes.Heartbeat; + Sequence = sequence; + } +} \ No newline at end of file diff --git a/src/StevesBot.Worker/Discord/Events/ReadyDiscordEvent.cs b/src/StevesBot.Worker/Discord/Events/ReadyDiscordEvent.cs new file mode 100644 index 0000000..ce16476 --- /dev/null +++ b/src/StevesBot.Worker/Discord/Events/ReadyDiscordEvent.cs @@ -0,0 +1,19 @@ +namespace StevesBot.Worker.Discord.Events; + +internal record ReadyDiscordEvent : DiscordEvent +{ + [JsonPropertyName("d")] + public new ReadyData Data { get; init; } = new ReadyData(); +} + +internal record ReadyData +{ + [JsonPropertyName("v")] + public int Version { get; init; } + + [JsonPropertyName("session_id")] + public string SessionId { get; init; } = string.Empty; + + [JsonPropertyName("resume_gateway_url")] + public string ResumeGatewayUrl { get; init; } = string.Empty; +} diff --git a/src/StevesBot.Worker/Threading/LockReleaser.cs b/src/StevesBot.Worker/Threading/LockReleaser.cs index 60a530b..2d094e7 100644 --- a/src/StevesBot.Worker/Threading/LockReleaser.cs +++ b/src/StevesBot.Worker/Threading/LockReleaser.cs @@ -7,6 +7,8 @@ internal sealed class LockReleaser : IDisposable public LockReleaser(SemaphoreSlim semaphore) { + ArgumentNullException.ThrowIfNull(semaphore); + _semaphore = semaphore; }