feat: upgrade to .NET 6, refactor everything

This commit is contained in:
spiral 2021-11-26 21:10:56 -05:00
parent d28e99ba43
commit 1918c56937
No known key found for this signature in database
GPG Key ID: A6059F0CA0E1BD31
314 changed files with 27954 additions and 27966 deletions

View File

@ -1,4 +1,3 @@
[*] [*]
charset = utf-8 charset = utf-8
end_of_line = lf end_of_line = lf

View File

@ -1,13 +1,11 @@
using System.Collections.Generic;
using Myriad.Types; using Myriad.Types;
namespace Myriad.Builders namespace Myriad.Builders;
{
public class EmbedBuilder public class EmbedBuilder
{ {
private Embed _embed = new();
private readonly List<Embed.Field> _fields = new(); private readonly List<Embed.Field> _fields = new();
private Embed _embed = new();
public EmbedBuilder Title(string? title) public EmbedBuilder Title(string? title)
{ {
@ -35,47 +33,32 @@ namespace Myriad.Builders
public EmbedBuilder Footer(Embed.EmbedFooter? footer) public EmbedBuilder Footer(Embed.EmbedFooter? footer)
{ {
_embed = _embed with _embed = _embed with { Footer = footer };
{
Footer = footer
};
return this; return this;
} }
public EmbedBuilder Image(Embed.EmbedImage? image) public EmbedBuilder Image(Embed.EmbedImage? image)
{ {
_embed = _embed with _embed = _embed with { Image = image };
{
Image = image
};
return this; return this;
} }
public EmbedBuilder Thumbnail(Embed.EmbedThumbnail? thumbnail) public EmbedBuilder Thumbnail(Embed.EmbedThumbnail? thumbnail)
{ {
_embed = _embed with _embed = _embed with { Thumbnail = thumbnail };
{
Thumbnail = thumbnail
};
return this; return this;
} }
public EmbedBuilder Author(Embed.EmbedAuthor? author) public EmbedBuilder Author(Embed.EmbedAuthor? author)
{ {
_embed = _embed with _embed = _embed with { Author = author };
{
Author = author
};
return this; return this;
} }
public EmbedBuilder Timestamp(string? timestamp) public EmbedBuilder Timestamp(string? timestamp)
{ {
_embed = _embed with _embed = _embed with { Timestamp = timestamp };
{
Timestamp = timestamp
};
return this; return this;
} }
@ -88,4 +71,3 @@ namespace Myriad.Builders
public Embed Build() => public Embed Build() =>
_embed with { Fields = _fields.ToArray() }; _embed with { Fields = _fields.ToArray() };
} }
}

View File

@ -1,12 +1,9 @@
using System.Linq;
using System.Threading.Tasks;
using Myriad.Extensions; using Myriad.Extensions;
using Myriad.Gateway; using Myriad.Gateway;
using Myriad.Types; using Myriad.Types;
namespace Myriad.Cache namespace Myriad.Cache;
{
public static class DiscordCacheExtensions public static class DiscordCacheExtensions
{ {
public static ValueTask HandleGatewayEvent(this IDiscordCache cache, IGatewayEvent evt) public static ValueTask HandleGatewayEvent(this IDiscordCache cache, IGatewayEvent evt)
@ -94,12 +91,10 @@ namespace Myriad.Cache
await cache.SaveUser(mention); await cache.SaveUser(mention);
} }
private static ValueTask TrySaveDmChannelStub(this IDiscordCache cache, ulong? guildId, ulong channelId) private static ValueTask TrySaveDmChannelStub(this IDiscordCache cache, ulong? guildId, ulong channelId) =>
{
// DM messages don't get Channel Create events first, so we need to save // DM messages don't get Channel Create events first, so we need to save
// some kind of stub channel object until we get the real one // some kind of stub channel object until we get the real one
return guildId != null ? default : cache.SaveDmChannelStub(channelId); guildId != null ? default : cache.SaveDmChannelStub(channelId);
}
private static async ValueTask SaveThreadListSync(this IDiscordCache cache, ThreadListSyncEvent evt) private static async ValueTask SaveThreadListSync(this IDiscordCache cache, ThreadListSyncEvent evt)
{ {
@ -120,6 +115,4 @@ namespace Myriad.Cache
return PermissionSet.Dm; return PermissionSet.Dm;
} }
}
} }

View File

@ -1,10 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Myriad.Types; using Myriad.Types;
namespace Myriad.Cache namespace Myriad.Cache;
{
public interface IDiscordCache public interface IDiscordCache
{ {
public ValueTask SaveOwnUser(ulong userId); public ValueTask SaveOwnUser(ulong userId);
@ -31,4 +28,3 @@ namespace Myriad.Cache
public IAsyncEnumerable<Guild> GetAllGuilds(); public IAsyncEnumerable<Guild> GetAllGuilds();
public Task<IEnumerable<Channel>> GetGuildChannels(ulong guildId); public Task<IEnumerable<Channel>> GetGuildChannels(ulong guildId);
} }
}

View File

@ -1,21 +1,17 @@
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Myriad.Types; using Myriad.Types;
namespace Myriad.Cache namespace Myriad.Cache;
{
public class MemoryDiscordCache: IDiscordCache public class MemoryDiscordCache: IDiscordCache
{ {
private readonly ConcurrentDictionary<ulong, Channel> _channels = new(); private readonly ConcurrentDictionary<ulong, Channel> _channels = new();
private readonly ConcurrentDictionary<ulong, ulong> _dmChannels = new(); private readonly ConcurrentDictionary<ulong, ulong> _dmChannels = new();
private readonly ConcurrentDictionary<ulong, GuildMemberPartial> _guildMembers = new();
private readonly ConcurrentDictionary<ulong, CachedGuild> _guilds = new(); private readonly ConcurrentDictionary<ulong, CachedGuild> _guilds = new();
private readonly ConcurrentDictionary<ulong, Role> _roles = new(); private readonly ConcurrentDictionary<ulong, Role> _roles = new();
private readonly ConcurrentDictionary<ulong, User> _users = new(); private readonly ConcurrentDictionary<ulong, User> _users = new();
private readonly ConcurrentDictionary<ulong, GuildMemberPartial> _guildMembers = new();
private ulong? _ownUserId { get; set; } private ulong? _ownUserId { get; set; }
public ValueTask SaveGuild(Guild guild) public ValueTask SaveGuild(Guild guild)
@ -38,14 +34,12 @@ namespace Myriad.Cache
guild.Channels.TryAdd(channel.Id, true); guild.Channels.TryAdd(channel.Id, true);
if (channel.Recipients != null) if (channel.Recipients != null)
{
foreach (var recipient in channel.Recipients) foreach (var recipient in channel.Recipients)
{ {
_dmChannels[recipient.Id] = channel.Id; _dmChannels[recipient.Id] = channel.Id;
await SaveUser(recipient); await SaveUser(recipient);
} }
} }
}
public ValueTask SaveOwnUser(ulong userId) public ValueTask SaveOwnUser(ulong userId)
{ {
@ -86,16 +80,11 @@ namespace Myriad.Cache
} }
if (!found) if (!found)
{
_guilds[guildId] = guild with _guilds[guildId] = guild with
{ {
Guild = guild.Guild with Guild = guild.Guild with { Roles = guild.Guild.Roles.Concat(new[] { role }).ToArray() }
{
Roles = guild.Guild.Roles.Concat(new[] { role }).ToArray()
}
}; };
} }
}
return default; return default;
} }
@ -104,11 +93,7 @@ namespace Myriad.Cache
{ {
// Use existing channel object if present, otherwise add a stub // Use existing channel object if present, otherwise add a stub
// We may get a message create before channel create and we want to have it saved // We may get a message create before channel create and we want to have it saved
_channels.GetOrAdd(channelId, id => new Channel _channels.GetOrAdd(channelId, id => new Channel { Id = id, Type = Channel.ChannelType.Dm });
{
Id = id,
Type = Channel.ChannelType.Dm
});
return default; return default;
} }
@ -203,4 +188,3 @@ namespace Myriad.Cache
public readonly ConcurrentDictionary<ulong, bool> Channels = new(); public readonly ConcurrentDictionary<ulong, bool> Channels = new();
} }
} }
}

View File

@ -1,12 +1,9 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Myriad.Cache; using Myriad.Cache;
using Myriad.Rest; using Myriad.Rest;
using Myriad.Types; using Myriad.Types;
namespace Myriad.Extensions namespace Myriad.Extensions;
{
public static class CacheExtensions public static class CacheExtensions
{ {
public static async Task<Guild> GetGuild(this IDiscordCache cache, ulong guildId) public static async Task<Guild> GetGuild(this IDiscordCache cache, ulong guildId)
@ -37,7 +34,8 @@ namespace Myriad.Extensions
return role; return role;
} }
public static async ValueTask<User?> GetOrFetchUser(this IDiscordCache cache, DiscordApiClient rest, ulong userId) public static async ValueTask<User?> GetOrFetchUser(this IDiscordCache cache, DiscordApiClient rest,
ulong userId)
{ {
if (await cache.TryGetUser(userId) is User cacheUser) if (await cache.TryGetUser(userId) is User cacheUser)
return cacheUser; return cacheUser;
@ -48,7 +46,8 @@ namespace Myriad.Extensions
return restUser; return restUser;
} }
public static async ValueTask<Channel?> GetOrFetchChannel(this IDiscordCache cache, DiscordApiClient rest, ulong channelId) public static async ValueTask<Channel?> GetOrFetchChannel(this IDiscordCache cache, DiscordApiClient rest,
ulong channelId)
{ {
if (await cache.TryGetChannel(channelId) is { } cacheChannel) if (await cache.TryGetChannel(channelId) is { } cacheChannel)
return cacheChannel; return cacheChannel;
@ -59,7 +58,8 @@ namespace Myriad.Extensions
return restChannel; return restChannel;
} }
public static async Task<Channel> GetOrCreateDmChannel(this IDiscordCache cache, DiscordApiClient rest, ulong recipientId) public static async Task<Channel> GetOrCreateDmChannel(this IDiscordCache cache, DiscordApiClient rest,
ulong recipientId)
{ {
if (await cache.TryGetDmChannel(recipientId) is { } cacheChannel) if (await cache.TryGetDmChannel(recipientId) is { } cacheChannel)
return cacheChannel; return cacheChannel;
@ -79,4 +79,3 @@ namespace Myriad.Extensions
return parent; return parent;
} }
} }
}

View File

@ -1,7 +1,7 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Extensions namespace Myriad.Extensions;
{
public static class ChannelExtensions public static class ChannelExtensions
{ {
public static string Mention(this Channel channel) => $"<#{channel.Id}>"; public static string Mention(this Channel channel) => $"<#{channel.Id}>";
@ -13,4 +13,3 @@ namespace Myriad.Extensions
or Channel.ChannelType.GuildPrivateThread or Channel.ChannelType.GuildPrivateThread
or Channel.ChannelType.GuildNewsThread; or Channel.ChannelType.GuildNewsThread;
} }
}

View File

@ -1,7 +1,7 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Extensions namespace Myriad.Extensions;
{
public static class GuildExtensions public static class GuildExtensions
{ {
public static int FileSizeLimit(this Guild guild) public static int FileSizeLimit(this Guild guild)
@ -19,4 +19,3 @@ namespace Myriad.Extensions
} }
} }
} }
}

View File

@ -1,8 +1,8 @@
using Myriad.Gateway; using Myriad.Gateway;
using Myriad.Types; using Myriad.Types;
namespace Myriad.Extensions namespace Myriad.Extensions;
{
public static class MessageExtensions public static class MessageExtensions
{ {
public static string JumpLink(this Message msg) => public static string JumpLink(this Message msg) =>
@ -11,4 +11,3 @@ namespace Myriad.Extensions
public static string JumpLink(this MessageReactionAddEvent msg) => public static string JumpLink(this MessageReactionAddEvent msg) =>
$"https://discord.com/channels/{msg.GuildId}/{msg.ChannelId}/{msg.MessageId}"; $"https://discord.com/channels/{msg.GuildId}/{msg.ChannelId}/{msg.MessageId}";
} }
}

View File

@ -1,23 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Myriad.Cache; using Myriad.Cache;
using Myriad.Gateway; using Myriad.Gateway;
using Myriad.Types; using Myriad.Types;
namespace Myriad.Extensions namespace Myriad.Extensions;
{
public static class PermissionExtensions public static class PermissionExtensions
{ {
public static Task<PermissionSet> PermissionsFor(this IDiscordCache cache, MessageCreateEvent message) => private const PermissionSet NeedsViewChannel =
PermissionsFor(cache, message.ChannelId, message.Author.Id, message.Member, isWebhook: message.WebhookId != null); PermissionSet.SendMessages |
PermissionSet.SendTtsMessages |
PermissionSet.ManageMessages |
PermissionSet.EmbedLinks |
PermissionSet.AttachFiles |
PermissionSet.ReadMessageHistory |
PermissionSet.MentionEveryone |
PermissionSet.UseExternalEmojis |
PermissionSet.AddReactions |
PermissionSet.Connect |
PermissionSet.Speak |
PermissionSet.MuteMembers |
PermissionSet.DeafenMembers |
PermissionSet.MoveMembers |
PermissionSet.UseVad |
PermissionSet.Stream |
PermissionSet.PrioritySpeaker;
public static Task<PermissionSet> PermissionsFor(this IDiscordCache cache, ulong channelId, GuildMember member) => private const PermissionSet NeedsSendMessages =
PermissionSet.MentionEveryone |
PermissionSet.SendTtsMessages |
PermissionSet.AttachFiles |
PermissionSet.EmbedLinks;
public static Task<PermissionSet> PermissionsFor(this IDiscordCache cache, MessageCreateEvent message) =>
PermissionsFor(cache, message.ChannelId, message.Author.Id, message.Member, message.WebhookId != null);
public static Task<PermissionSet>
PermissionsFor(this IDiscordCache cache, ulong channelId, GuildMember member) =>
PermissionsFor(cache, channelId, member.User.Id, member); PermissionsFor(cache, channelId, member.User.Id, member);
public static async Task<PermissionSet> PermissionsFor(this IDiscordCache cache, ulong channelId, ulong userId, GuildMemberPartial? member, bool isWebhook = false) public static async Task<PermissionSet> PermissionsFor(this IDiscordCache cache, ulong channelId, ulong userId,
GuildMemberPartial? member, bool isWebhook = false)
{ {
if (!(await cache.TryGetChannel(channelId) is Channel channel)) if (!(await cache.TryGetChannel(channelId) is Channel channel))
// todo: handle channel not found better // todo: handle channel not found better
@ -59,7 +81,8 @@ namespace Myriad.Extensions
public static PermissionSet PermissionsFor(Guild guild, Channel channel, MessageCreateEvent msg) => public static PermissionSet PermissionsFor(Guild guild, Channel channel, MessageCreateEvent msg) =>
PermissionsFor(guild, channel, msg.Author.Id, msg.Member); PermissionsFor(guild, channel, msg.Author.Id, msg.Member);
public static PermissionSet PermissionsFor(Guild guild, Channel channel, ulong userId, GuildMemberPartial? member) public static PermissionSet PermissionsFor(Guild guild, Channel channel, ulong userId,
GuildMemberPartial? member)
{ {
if (channel.Type == Channel.ChannelType.Dm) if (channel.Type == Channel.ChannelType.Dm)
return PermissionSet.Dm; return PermissionSet.Dm;
@ -90,10 +113,8 @@ namespace Myriad.Extensions
var perms = PermissionSet.None; var perms = PermissionSet.None;
foreach (var role in guild.Roles) foreach (var role in guild.Roles)
{
if (role.Id == guild.Id || roleIds.Contains(role.Id)) if (role.Id == guild.Id || roleIds.Contains(role.Id))
perms |= role.Permissions; perms |= role.Permissions;
}
if (perms.HasFlag(PermissionSet.Administrator)) if (perms.HasFlag(PermissionSet.Administrator))
return PermissionSet.All; return PermissionSet.All;
@ -115,7 +136,6 @@ namespace Myriad.Extensions
var userAllow = PermissionSet.None; var userAllow = PermissionSet.None;
foreach (var overwrite in channel.PermissionOverwrites) foreach (var overwrite in channel.PermissionOverwrites)
{
switch (overwrite.Type) switch (overwrite.Type)
{ {
case Channel.OverwriteType.Role when overwrite.Id == channel.GuildId: case Channel.OverwriteType.Role when overwrite.Id == channel.GuildId:
@ -131,7 +151,6 @@ namespace Myriad.Extensions
userAllow |= overwrite.Allow; userAllow |= overwrite.Allow;
break; break;
} }
}
perms &= ~everyoneDeny; perms &= ~everyoneDeny;
perms |= everyoneAllow; perms |= everyoneAllow;
@ -142,35 +161,7 @@ namespace Myriad.Extensions
return perms; return perms;
} }
private const PermissionSet NeedsViewChannel = public static string ToPermissionString(this PermissionSet perms) =>
PermissionSet.SendMessages |
PermissionSet.SendTtsMessages |
PermissionSet.ManageMessages |
PermissionSet.EmbedLinks |
PermissionSet.AttachFiles |
PermissionSet.ReadMessageHistory |
PermissionSet.MentionEveryone |
PermissionSet.UseExternalEmojis |
PermissionSet.AddReactions |
PermissionSet.Connect |
PermissionSet.Speak |
PermissionSet.MuteMembers |
PermissionSet.DeafenMembers |
PermissionSet.MoveMembers |
PermissionSet.UseVad |
PermissionSet.Stream |
PermissionSet.PrioritySpeaker;
private const PermissionSet NeedsSendMessages =
PermissionSet.MentionEveryone |
PermissionSet.SendTtsMessages |
PermissionSet.AttachFiles |
PermissionSet.EmbedLinks;
public static string ToPermissionString(this PermissionSet perms)
{
// TODO: clean string // TODO: clean string
return perms.ToString(); perms.ToString();
}
}
} }

View File

@ -1,9 +1,7 @@
using System;
using Myriad.Types; using Myriad.Types;
namespace Myriad.Extensions namespace Myriad.Extensions;
{
public static class SnowflakeExtensions public static class SnowflakeExtensions
{ {
public static readonly DateTimeOffset DiscordEpoch = new(2015, 1, 1, 0, 0, 0, TimeSpan.Zero); public static readonly DateTimeOffset DiscordEpoch = new(2015, 1, 1, 0, 0, 0, TimeSpan.Zero);
@ -17,4 +15,3 @@ namespace Myriad.Extensions
public static DateTimeOffset Timestamp(this Webhook webhook) => SnowflakeToTimestamp(webhook.Id); public static DateTimeOffset Timestamp(this Webhook webhook) => SnowflakeToTimestamp(webhook.Id);
public static DateTimeOffset Timestamp(this User user) => SnowflakeToTimestamp(user.Id); public static DateTimeOffset Timestamp(this User user) => SnowflakeToTimestamp(user.Id);
} }
}

View File

@ -1,7 +1,7 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Extensions namespace Myriad.Extensions;
{
public static class UserExtensions public static class UserExtensions
{ {
public static string Mention(this User user) => $"<@{user.Id}>"; public static string Mention(this User user) => $"<@{user.Id}>";
@ -9,4 +9,3 @@ namespace Myriad.Extensions
public static string AvatarUrl(this User user, string? format = "png", int? size = 128) => public static string AvatarUrl(this User user, string? format = "png", int? size = 128) =>
$"https://cdn.discordapp.com/avatars/{user.Id}/{user.Avatar}.{format}?size={size}"; $"https://cdn.discordapp.com/avatars/{user.Id}/{user.Avatar}.{format}?size={size}";
} }
}

View File

@ -1,16 +1,12 @@
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Myriad.Gateway.Limit; using Myriad.Gateway.Limit;
using Myriad.Types; using Myriad.Types;
using Serilog; using Serilog;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public class Cluster public class Cluster
{ {
private readonly GatewaySettings _gatewaySettings; private readonly GatewaySettings _gatewaySettings;
@ -25,9 +21,9 @@ namespace Myriad.Gateway
} }
public Func<Shard, IGatewayEvent, Task>? EventReceived { get; set; } public Func<Shard, IGatewayEvent, Task>? EventReceived { get; set; }
public event Action<Shard>? ShardCreated;
public IReadOnlyDictionary<int, Shard> Shards => _shards; public IReadOnlyDictionary<int, Shard> Shards => _shards;
public event Action<Shard>? ShardCreated;
public async Task Start(GatewayInfo.Bot info) public async Task Start(GatewayInfo.Bot info)
{ {
@ -46,6 +42,7 @@ namespace Myriad.Gateway
await StartShards(); await StartShards();
} }
private async Task StartShards() private async Task StartShards()
{ {
_logger.Information("Connecting shards..."); _logger.Information("Connecting shards...");
@ -79,12 +76,9 @@ namespace Myriad.Gateway
private IGatewayRatelimiter GetRateLimiter(int recommendedConcurrency) private IGatewayRatelimiter GetRateLimiter(int recommendedConcurrency)
{ {
if (_gatewaySettings.GatewayQueueUrl != null) if (_gatewaySettings.GatewayQueueUrl != null)
{
return new TwilightGatewayRatelimiter(_logger, _gatewaySettings.GatewayQueueUrl); return new TwilightGatewayRatelimiter(_logger, _gatewaySettings.GatewayQueueUrl);
}
var concurrency = GetActualShardConcurrency(recommendedConcurrency); var concurrency = GetActualShardConcurrency(recommendedConcurrency);
return new LocalGatewayRatelimiter(_logger, concurrency); return new LocalGatewayRatelimiter(_logger, concurrency);
} }
} }
}

View File

@ -1,6 +1,5 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record ChannelCreateEvent: Channel, IGatewayEvent; public record ChannelCreateEvent: Channel, IGatewayEvent;
}

View File

@ -1,6 +1,5 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record ChannelDeleteEvent: Channel, IGatewayEvent; public record ChannelDeleteEvent: Channel, IGatewayEvent;
}

View File

@ -1,6 +1,5 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record ChannelUpdateEvent: Channel, IGatewayEvent; public record ChannelUpdateEvent: Channel, IGatewayEvent;
}

View File

@ -1,11 +1,10 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record GuildCreateEvent: Guild, IGatewayEvent public record GuildCreateEvent: Guild, IGatewayEvent
{ {
public Channel[] Channels { get; init; } public Channel[] Channels { get; init; }
public GuildMember[] Members { get; init; } public GuildMember[] Members { get; init; }
public Channel[] Threads { get; init; } public Channel[] Threads { get; init; }
} }
}

View File

@ -1,4 +1,3 @@
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record GuildDeleteEvent(ulong Id, bool Unavailable): IGatewayEvent; public record GuildDeleteEvent(ulong Id, bool Unavailable): IGatewayEvent;
}

View File

@ -1,9 +1,8 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record GuildMemberAddEvent: GuildMember, IGatewayEvent public record GuildMemberAddEvent: GuildMember, IGatewayEvent
{ {
public ulong GuildId { get; init; } public ulong GuildId { get; init; }
} }
}

View File

@ -1,10 +1,9 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public class GuildMemberRemoveEvent: IGatewayEvent public class GuildMemberRemoveEvent: IGatewayEvent
{ {
public ulong GuildId { get; init; } public ulong GuildId { get; init; }
public User User { get; init; } public User User { get; init; }
} }
}

View File

@ -1,9 +1,8 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record GuildMemberUpdateEvent: GuildMember, IGatewayEvent public record GuildMemberUpdateEvent: GuildMember, IGatewayEvent
{ {
public ulong GuildId { get; init; } public ulong GuildId { get; init; }
} }
}

View File

@ -1,6 +1,5 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record GuildRoleCreateEvent(ulong GuildId, Role Role): IGatewayEvent; public record GuildRoleCreateEvent(ulong GuildId, Role Role): IGatewayEvent;
}

View File

@ -1,4 +1,3 @@
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record GuildRoleDeleteEvent(ulong GuildId, ulong RoleId): IGatewayEvent; public record GuildRoleDeleteEvent(ulong GuildId, ulong RoleId): IGatewayEvent;
}

View File

@ -1,6 +1,5 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record GuildRoleUpdateEvent(ulong GuildId, Role Role): IGatewayEvent; public record GuildRoleUpdateEvent(ulong GuildId, Role Role): IGatewayEvent;
}

View File

@ -1,6 +1,5 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record GuildUpdateEvent: Guild, IGatewayEvent; public record GuildUpdateEvent: Guild, IGatewayEvent;
}

View File

@ -1,8 +1,5 @@
using System; namespace Myriad.Gateway;
using System.Collections.Generic;
namespace Myriad.Gateway
{
public interface IGatewayEvent public interface IGatewayEvent
{ {
public static readonly Dictionary<string, Type> EventTypes = new() public static readonly Dictionary<string, Type> EventTypes = new()
@ -36,4 +33,3 @@ namespace Myriad.Gateway
{ "INTERACTION_CREATE", typeof(InteractionCreateEvent) } { "INTERACTION_CREATE", typeof(InteractionCreateEvent) }
}; };
} }
}

View File

@ -1,6 +1,5 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record InteractionCreateEvent: Interaction, IGatewayEvent; public record InteractionCreateEvent: Interaction, IGatewayEvent;
}

View File

@ -1,9 +1,8 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record MessageCreateEvent: Message, IGatewayEvent public record MessageCreateEvent: Message, IGatewayEvent
{ {
public GuildMemberPartial? Member { get; init; } public GuildMemberPartial? Member { get; init; }
} }
}

View File

@ -1,4 +1,3 @@
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record MessageDeleteBulkEvent(ulong[] Ids, ulong ChannelId, ulong? GuildId): IGatewayEvent; public record MessageDeleteBulkEvent(ulong[] Ids, ulong ChannelId, ulong? GuildId): IGatewayEvent;
}

View File

@ -1,4 +1,3 @@
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record MessageDeleteEvent(ulong Id, ulong ChannelId, ulong? GuildId): IGatewayEvent; public record MessageDeleteEvent(ulong Id, ulong ChannelId, ulong? GuildId): IGatewayEvent;
}

View File

@ -1,8 +1,7 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record MessageReactionAddEvent(ulong UserId, ulong ChannelId, ulong MessageId, ulong? GuildId, public record MessageReactionAddEvent(ulong UserId, ulong ChannelId, ulong MessageId, ulong? GuildId,
GuildMember? Member, GuildMember? Member,
Emoji Emoji): IGatewayEvent; Emoji Emoji): IGatewayEvent;
}

View File

@ -1,4 +1,3 @@
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record MessageReactionRemoveAllEvent(ulong ChannelId, ulong MessageId, ulong? GuildId): IGatewayEvent; public record MessageReactionRemoveAllEvent(ulong ChannelId, ulong MessageId, ulong? GuildId): IGatewayEvent;
}

View File

@ -1,7 +1,6 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record MessageReactionRemoveEmojiEvent public record MessageReactionRemoveEmojiEvent
(ulong ChannelId, ulong MessageId, ulong? GuildId, Emoji Emoji): IGatewayEvent; (ulong ChannelId, ulong MessageId, ulong? GuildId, Emoji Emoji): IGatewayEvent;
}

View File

@ -1,7 +1,6 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record MessageReactionRemoveEvent public record MessageReactionRemoveEvent
(ulong UserId, ulong ChannelId, ulong MessageId, ulong? GuildId, Emoji Emoji): IGatewayEvent; (ulong UserId, ulong ChannelId, ulong MessageId, ulong? GuildId, Emoji Emoji): IGatewayEvent;
}

View File

@ -1,15 +1,15 @@
using Myriad.Types; using Myriad.Types;
using Myriad.Utils; using Myriad.Utils;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record MessageUpdateEvent(ulong Id, ulong ChannelId): IGatewayEvent public record MessageUpdateEvent(ulong Id, ulong ChannelId): IGatewayEvent
{ {
public Optional<string?> Content { get; init; } public Optional<string?> Content { get; init; }
public Optional<User> Author { get; init; } public Optional<User> Author { get; init; }
public Optional<GuildMemberPartial> Member { get; init; } public Optional<GuildMemberPartial> Member { get; init; }
public Optional<Message.Attachment[]> Attachments { get; init; } public Optional<Message.Attachment[]> Attachments { get; init; }
public Optional<ulong?> GuildId { get; init; } public Optional<ulong?> GuildId { get; init; }
// TODO: lots of partials // TODO: lots of partials
} }
}

View File

@ -2,8 +2,8 @@ using System.Text.Json.Serialization;
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record ReadyEvent: IGatewayEvent public record ReadyEvent: IGatewayEvent
{ {
[JsonPropertyName("v")] public int Version { get; init; } [JsonPropertyName("v")] public int Version { get; init; }
@ -12,4 +12,3 @@ namespace Myriad.Gateway
public ShardInfo? Shard { get; init; } public ShardInfo? Shard { get; init; }
public ApplicationPartial Application { get; init; } public ApplicationPartial Application { get; init; }
} }
}

View File

@ -1,4 +1,3 @@
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record ResumedEvent: IGatewayEvent; public record ResumedEvent: IGatewayEvent;
}

View File

@ -1,6 +1,5 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record ThreadCreateEvent: Channel, IGatewayEvent; public record ThreadCreateEvent: Channel, IGatewayEvent;
}

View File

@ -1,7 +1,7 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record ThreadDeleteEvent: IGatewayEvent public record ThreadDeleteEvent: IGatewayEvent
{ {
public ulong Id { get; init; } public ulong Id { get; init; }
@ -9,4 +9,3 @@ namespace Myriad.Gateway
public ulong? ParentId { get; init; } public ulong? ParentId { get; init; }
public Channel.ChannelType Type { get; init; } public Channel.ChannelType Type { get; init; }
} }
}

View File

@ -1,11 +1,10 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record ThreadListSyncEvent: IGatewayEvent public record ThreadListSyncEvent: IGatewayEvent
{ {
public ulong GuildId { get; init; } public ulong GuildId { get; init; }
public ulong[]? ChannelIds { get; init; } public ulong[]? ChannelIds { get; init; }
public Channel[] Threads { get; init; } public Channel[] Threads { get; init; }
} }
}

View File

@ -1,6 +1,5 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record ThreadUpdateEvent: Channel, IGatewayEvent; public record ThreadUpdateEvent: Channel, IGatewayEvent;
}

View File

@ -1,7 +1,5 @@
using System; namespace Myriad.Gateway;
namespace Myriad.Gateway
{
// TODO: unused? // TODO: unused?
public class GatewayCloseException: Exception public class GatewayCloseException: Exception
{ {
@ -32,4 +30,3 @@ namespace Myriad.Gateway
public const int InvalidIntent = 4013; public const int InvalidIntent = 4013;
public const int DisallowedIntent = 4014; public const int DisallowedIntent = 4014;
} }
}

View File

@ -1,7 +1,5 @@
using System; namespace Myriad.Gateway;
namespace Myriad.Gateway
{
[Flags] [Flags]
public enum GatewayIntent public enum GatewayIntent
{ {
@ -21,4 +19,3 @@ namespace Myriad.Gateway
DirectMessageReactions = 1 << 13, DirectMessageReactions = 1 << 13,
DirectMessageTyping = 1 << 14 DirectMessageTyping = 1 << 14
} }
}

View File

@ -1,7 +1,7 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record GatewayPacket public record GatewayPacket
{ {
[JsonPropertyName("op")] public GatewayOpcode Opcode { get; init; } [JsonPropertyName("op")] public GatewayOpcode Opcode { get; init; }
@ -30,4 +30,3 @@ namespace Myriad.Gateway
Hello = 10, Hello = 10,
HeartbeatAck = 11 HeartbeatAck = 11
} }
}

View File

@ -1,5 +1,5 @@
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record GatewaySettings public record GatewaySettings
{ {
public string Token { get; init; } public string Token { get; init; }
@ -7,4 +7,3 @@ namespace Myriad.Gateway
public int? MaxShardConcurrency { get; init; } public int? MaxShardConcurrency { get; init; }
public string? GatewayQueueUrl { get; init; } public string? GatewayQueueUrl { get; init; }
} }
}

View File

@ -1,9 +1,6 @@
using System.Threading.Tasks; namespace Myriad.Gateway.Limit;
namespace Myriad.Gateway.Limit
{
public interface IGatewayRatelimiter public interface IGatewayRatelimiter
{ {
public Task Identify(int shard); public Task Identify(int shard);
} }
}

View File

@ -1,21 +1,19 @@
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading.Tasks;
using Serilog; using Serilog;
namespace Myriad.Gateway.Limit namespace Myriad.Gateway.Limit;
{
public class LocalGatewayRatelimiter: IGatewayRatelimiter public class LocalGatewayRatelimiter: IGatewayRatelimiter
{ {
// docs specify 5 seconds, but we're actually throttling connections, not identify, so we need a bit of leeway // docs specify 5 seconds, but we're actually throttling connections, not identify, so we need a bit of leeway
private static readonly TimeSpan BucketLength = TimeSpan.FromSeconds(6); private static readonly TimeSpan BucketLength = TimeSpan.FromSeconds(6);
private readonly ConcurrentDictionary<int, ConcurrentQueue<TaskCompletionSource>> _buckets = new(); private readonly ConcurrentDictionary<int, ConcurrentQueue<TaskCompletionSource>> _buckets = new();
private readonly ILogger _logger;
private readonly int _maxConcurrency; private readonly int _maxConcurrency;
private Task? _refillTask; private Task? _refillTask;
private readonly ILogger _logger;
public LocalGatewayRatelimiter(ILogger logger, int maxConcurrency) public LocalGatewayRatelimiter(ILogger logger, int maxConcurrency)
{ {
@ -70,4 +68,3 @@ namespace Myriad.Gateway.Limit
} }
} }
} }
}

View File

@ -1,19 +1,13 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Serilog; using Serilog;
namespace Myriad.Gateway.Limit namespace Myriad.Gateway.Limit;
{
public class TwilightGatewayRatelimiter: IGatewayRatelimiter public class TwilightGatewayRatelimiter: IGatewayRatelimiter
{ {
private readonly string _url; private readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(60) };
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly HttpClient _httpClient = new() private readonly string _url;
{
Timeout = TimeSpan.FromSeconds(60)
};
public TwilightGatewayRatelimiter(ILogger logger, string url) public TwilightGatewayRatelimiter(ILogger logger, string url)
{ {
@ -24,7 +18,6 @@ namespace Myriad.Gateway.Limit
public async Task Identify(int shard) public async Task Identify(int shard)
{ {
while (true) while (true)
{
try try
{ {
_logger.Information("Shard {ShardId}: Requesting identify at gateway queue {GatewayQueueUrl}", _logger.Information("Shard {ShardId}: Requesting identify at gateway queue {GatewayQueueUrl}",
@ -32,10 +25,6 @@ namespace Myriad.Gateway.Limit
await _httpClient.GetAsync(_url); await _httpClient.GetAsync(_url);
return; return;
} }
catch (TimeoutException) catch (TimeoutException) { }
{
}
}
}
} }
} }

View File

@ -1,4 +1,3 @@
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record GatewayHello(int HeartbeatInterval); public record GatewayHello(int HeartbeatInterval);
}

View File

@ -1,7 +1,7 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record GatewayIdentify public record GatewayIdentify
{ {
public string Token { get; init; } public string Token { get; init; }
@ -25,4 +25,3 @@ namespace Myriad.Gateway
[JsonPropertyName("$device")] public string Device { get; init; } [JsonPropertyName("$device")] public string Device { get; init; }
} }
} }
}

View File

@ -1,4 +1,3 @@
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record GatewayResume(string Token, string SessionId, int Seq); public record GatewayResume(string Token, string SessionId, int Seq);
}

View File

@ -3,8 +3,8 @@ using System.Text.Json.Serialization;
using Myriad.Serialization; using Myriad.Serialization;
using Myriad.Types; using Myriad.Types;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record GatewayStatusUpdate public record GatewayStatusUpdate
{ {
[JsonConverter(typeof(JsonSnakeCaseStringEnumConverter))] [JsonConverter(typeof(JsonSnakeCaseStringEnumConverter))]
@ -22,4 +22,3 @@ namespace Myriad.Gateway
public UserStatus Status { get; init; } public UserStatus Status { get; init; }
public bool Afk { get; init; } public bool Afk { get; init; }
} }
}

View File

@ -1,7 +1,5 @@
using System;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks;
using Myriad.Gateway.Limit; using Myriad.Gateway.Limit;
using Myriad.Gateway.State; using Myriad.Gateway.State;
@ -11,8 +9,8 @@ using Myriad.Types;
using Serilog; using Serilog;
using Serilog.Context; using Serilog.Context;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public class Shard public class Shard
{ {
private const string LibraryName = "Myriad (for PluralKit)"; private const string LibraryName = "Myriad (for PluralKit)";
@ -121,13 +119,11 @@ namespace Myriad.Gateway
} }
public async Task UpdateStatus(GatewayStatusUpdate payload) public async Task UpdateStatus(GatewayStatusUpdate payload)
{ => await _conn.Send(new GatewayPacket
await _conn.Send(new GatewayPacket
{ {
Opcode = GatewayOpcode.PresenceUpdate, Opcode = GatewayOpcode.PresenceUpdate,
Payload = payload Payload = payload
}); });
}
private async Task ConnectInner() private async Task ConnectInner()
{ {
@ -149,14 +145,11 @@ namespace Myriad.Gateway
} }
} }
private async Task DisconnectInner(WebSocketCloseStatus closeStatus) private Task DisconnectInner(WebSocketCloseStatus closeStatus)
{ => _conn.Disconnect(closeStatus, null);
await _conn.Disconnect(closeStatus, null);
}
private async Task SendIdentify() private async Task SendIdentify()
{ => await _conn.Send(new GatewayPacket
await _conn.Send(new GatewayPacket
{ {
Opcode = GatewayOpcode.Identify, Opcode = GatewayOpcode.Identify,
Payload = new GatewayIdentify Payload = new GatewayIdentify
@ -174,21 +167,16 @@ namespace Myriad.Gateway
LargeThreshold = 50 LargeThreshold = 50
} }
}); });
}
private async Task SendResume((string SessionId, int? LastSeq) arg) private async Task SendResume((string SessionId, int? LastSeq) arg)
{ => await _conn.Send(new GatewayPacket
await _conn.Send(new GatewayPacket
{ {
Opcode = GatewayOpcode.Resume, Opcode = GatewayOpcode.Resume,
Payload = new GatewayResume(_settings.Token, arg.SessionId, arg.LastSeq ?? 0) Payload = new GatewayResume(_settings.Token, arg.SessionId, arg.LastSeq ?? 0)
}); });
}
private async Task SendHeartbeat(int? lastSeq) private async Task SendHeartbeat(int? lastSeq)
{ => await _conn.Send(new GatewayPacket { Opcode = GatewayOpcode.Heartbeat, Payload = lastSeq });
await _conn.Send(new GatewayPacket { Opcode = GatewayOpcode.Heartbeat, Payload = lastSeq });
}
private async Task Reconnect(WebSocketCloseStatus closeStatus, TimeSpan delay) private async Task Reconnect(WebSocketCloseStatus closeStatus, TimeSpan delay)
{ {
@ -221,4 +209,3 @@ namespace Myriad.Gateway
SocketClosed?.Invoke(closeStatus, description); SocketClosed?.Invoke(closeStatus, description);
} }
} }
}

View File

@ -1,27 +1,30 @@
using System;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Text.Json; using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Serilog; using Serilog;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public class ShardConnection: IAsyncDisposable public class ShardConnection: IAsyncDisposable
{ {
private ClientWebSocket? _client;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly ShardPacketSerializer _serializer; private readonly ShardPacketSerializer _serializer;
private ClientWebSocket? _client;
public ShardConnection(JsonSerializerOptions jsonSerializerOptions, ILogger logger)
{
_logger = logger.ForContext<ShardConnection>();
_serializer = new ShardPacketSerializer(jsonSerializerOptions);
}
public WebSocketState State => _client?.State ?? WebSocketState.Closed; public WebSocketState State => _client?.State ?? WebSocketState.Closed;
public WebSocketCloseStatus? CloseStatus => _client?.CloseStatus; public WebSocketCloseStatus? CloseStatus => _client?.CloseStatus;
public string? CloseStatusDescription => _client?.CloseStatusDescription; public string? CloseStatusDescription => _client?.CloseStatusDescription;
public ShardConnection(JsonSerializerOptions jsonSerializerOptions, ILogger logger) public async ValueTask DisposeAsync()
{ {
_logger = logger.ForContext<ShardConnection>(); await CloseInner(WebSocketCloseStatus.NormalClosure, null);
_serializer = new(jsonSerializerOptions); _client?.Dispose();
} }
public async Task Connect(string url, CancellationToken ct) public async Task Connect(string url, CancellationToken ct)
@ -53,12 +56,6 @@ namespace Myriad.Gateway
} }
} }
public async ValueTask DisposeAsync()
{
await CloseInner(WebSocketCloseStatus.NormalClosure, null);
_client?.Dispose();
}
public async Task<GatewayPacket?> Read() public async Task<GatewayPacket?> Read()
{ {
// from `ManagedWebSocket.s_validReceiveStates` // from `ManagedWebSocket.s_validReceiveStates`
@ -80,10 +77,7 @@ namespace Myriad.Gateway
return null; return null;
} }
private Uri GetConnectionUri(string baseUri) => new UriBuilder(baseUri) private Uri GetConnectionUri(string baseUri) => new UriBuilder(baseUri) { Query = "v=9&encoding=json" }.Uri;
{
Query = "v=9&encoding=json"
}.Uri;
private async Task CloseInner(WebSocketCloseStatus closeStatus, string? description) private async Task CloseInner(WebSocketCloseStatus closeStatus, string? description)
{ {
@ -119,4 +113,3 @@ namespace Myriad.Gateway
} }
} }
} }
}

View File

@ -1,4 +1,3 @@
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public record ShardInfo(int ShardId, int NumShards); public record ShardInfo(int ShardId, int NumShards);
}

View File

@ -1,12 +1,9 @@
using System;
using System.Buffers; using System.Buffers;
using System.IO;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public class ShardPacketSerializer public class ShardPacketSerializer
{ {
private const int BufferSize = 64 * 1024; private const int BufferSize = 64 * 1024;
@ -40,7 +37,8 @@ namespace Myriad.Gateway
await socket.SendAsync(bytes.AsMemory(), WebSocketMessageType.Text, true, default); await socket.SendAsync(bytes.AsMemory(), WebSocketMessageType.Text, true, default);
} }
private async Task<(WebSocketMessageType type, GatewayPacket packet)> DeserializeMultipleBuffer(ClientWebSocket socket, IMemoryOwner<byte> buf, ValueWebSocketReceiveResult res) private async Task<(WebSocketMessageType type, GatewayPacket packet)> DeserializeMultipleBuffer(
ClientWebSocket socket, IMemoryOwner<byte> buf, ValueWebSocketReceiveResult res)
{ {
await using var stream = new MemoryStream(BufferSize * 4); await using var stream = new MemoryStream(BufferSize * 4);
stream.Write(buf.Memory.Span.Slice(0, res.Count)); stream.Write(buf.Memory.Span.Slice(0, res.Count));
@ -61,10 +59,10 @@ namespace Myriad.Gateway
return DeserializeObject(res, span); return DeserializeObject(res, span);
} }
private (WebSocketMessageType type, GatewayPacket packet) DeserializeObject(ValueWebSocketReceiveResult res, Span<byte> span) private (WebSocketMessageType type, GatewayPacket packet) DeserializeObject(
ValueWebSocketReceiveResult res, Span<byte> span)
{ {
var packet = JsonSerializer.Deserialize<GatewayPacket>(span, _jsonSerializerOptions)!; var packet = JsonSerializer.Deserialize<GatewayPacket>(span, _jsonSerializerOptions)!;
return (res.MessageType, packet); return (res.MessageType, packet);
} }
} }
}

View File

@ -1,9 +1,5 @@
using System; namespace Myriad.Gateway.State;
using System.Threading;
using System.Threading.Tasks;
namespace Myriad.Gateway.State
{
public class HeartbeatWorker: IAsyncDisposable public class HeartbeatWorker: IAsyncDisposable
{ {
private Task? _worker; private Task? _worker;
@ -11,6 +7,11 @@ namespace Myriad.Gateway.State
public TimeSpan? CurrentHeartbeatInterval { get; private set; } public TimeSpan? CurrentHeartbeatInterval { get; private set; }
public async ValueTask DisposeAsync()
{
await Stop();
}
public async ValueTask Start(TimeSpan heartbeatInterval, Func<Task> callback) public async ValueTask Start(TimeSpan heartbeatInterval, Func<Task> callback)
{ {
if (_worker != null) if (_worker != null)
@ -54,10 +55,4 @@ namespace Myriad.Gateway.State
private static TimeSpan GetInitialHeartbeatDelay(TimeSpan heartbeatInterval) => private static TimeSpan GetInitialHeartbeatDelay(TimeSpan heartbeatInterval) =>
// Docs specify `heartbeat_interval * random.random()` but we'll add a lil buffer :) // Docs specify `heartbeat_interval * random.random()` but we'll add a lil buffer :)
heartbeatInterval * (new Random().NextDouble() * 0.9 + 0.05); heartbeatInterval * (new Random().NextDouble() * 0.9 + 0.05);
public async ValueTask DisposeAsync()
{
await Stop();
}
}
} }

View File

@ -1,5 +1,5 @@
namespace Myriad.Gateway.State namespace Myriad.Gateway.State;
{
public enum ShardState public enum ShardState
{ {
Disconnected, Disconnected,
@ -8,4 +8,3 @@ namespace Myriad.Gateway.State
Connected, Connected,
Reconnecting Reconnecting
} }
}

View File

@ -1,32 +1,37 @@
using System;
using System.Net.WebSockets; using System.Net.WebSockets;
using System.Text.Json; using System.Text.Json;
using System.Threading.Tasks;
using Myriad.Gateway.State; using Myriad.Gateway.State;
using Myriad.Types; using Myriad.Types;
using Serilog; using Serilog;
namespace Myriad.Gateway namespace Myriad.Gateway;
{
public class ShardStateManager public class ShardStateManager
{ {
private readonly HeartbeatWorker _heartbeatWorker = new(); private readonly HeartbeatWorker _heartbeatWorker = new();
private readonly ILogger _logger;
private readonly ShardInfo _info; private readonly ShardInfo _info;
private readonly JsonSerializerOptions _jsonSerializerOptions; private readonly JsonSerializerOptions _jsonSerializerOptions;
private ShardState _state = ShardState.Disconnected; private readonly ILogger _logger;
private DateTimeOffset? _lastHeartbeatSent;
private TimeSpan? _latency;
private bool _hasReceivedHeartbeatAck; private bool _hasReceivedHeartbeatAck;
private string? _sessionId; private DateTimeOffset? _lastHeartbeatSent;
private int? _lastSeq; private int? _lastSeq;
private TimeSpan? _latency;
private string? _sessionId;
public ShardStateManager(ShardInfo info, JsonSerializerOptions jsonSerializerOptions, ILogger logger)
{
_info = info;
_jsonSerializerOptions = jsonSerializerOptions;
_logger = logger.ForContext<ShardStateManager>();
}
public ShardState State { get; private set; } = ShardState.Disconnected;
public ShardState State => _state;
public TimeSpan? Latency => _latency; public TimeSpan? Latency => _latency;
public User? User { get; private set; } public User? User { get; private set; }
public ApplicationPartial? Application { get; private set; } public ApplicationPartial? Application { get; private set; }
@ -40,16 +45,9 @@ namespace Myriad.Gateway
public event Action<TimeSpan> OnHeartbeatReceived; public event Action<TimeSpan> OnHeartbeatReceived;
public ShardStateManager(ShardInfo info, JsonSerializerOptions jsonSerializerOptions, ILogger logger)
{
_info = info;
_jsonSerializerOptions = jsonSerializerOptions;
_logger = logger.ForContext<ShardStateManager>();
}
public Task HandleConnectionOpened() public Task HandleConnectionOpened()
{ {
_state = ShardState.Handshaking; State = ShardState.Handshaking;
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -103,6 +101,7 @@ namespace Myriad.Gateway
await HandleEvent(evt); await HandleEvent(evt);
} }
break; break;
} }
} }
@ -118,7 +117,7 @@ namespace Myriad.Gateway
private async Task IdentifyOrResume() private async Task IdentifyOrResume()
{ {
_state = ShardState.Identifying; State = ShardState.Identifying;
if (_sessionId != null) if (_sessionId != null)
{ {
@ -173,7 +172,7 @@ namespace Myriad.Gateway
_logger.Information("Shard {ShardId}: Received Ready", _info.ShardId); _logger.Information("Shard {ShardId}: Received Ready", _info.ShardId);
_sessionId = ready.SessionId; _sessionId = ready.SessionId;
_state = ShardState.Connected; State = ShardState.Connected;
User = ready.User; User = ready.User;
Application = ready.Application; Application = ready.Application;
return Task.CompletedTask; return Task.CompletedTask;
@ -183,7 +182,7 @@ namespace Myriad.Gateway
{ {
_logger.Information("Shard {ShardId}: Received Resume", _info.ShardId); _logger.Information("Shard {ShardId}: Received Resume", _info.ShardId);
_state = ShardState.Connected; State = ShardState.Connected;
return Task.CompletedTask; return Task.CompletedTask;
} }
@ -212,7 +211,7 @@ namespace Myriad.Gateway
private async Task DoReconnect(WebSocketCloseStatus closeStatus, TimeSpan delay) private async Task DoReconnect(WebSocketCloseStatus closeStatus, TimeSpan delay)
{ {
_state = ShardState.Reconnecting; State = ShardState.Reconnecting;
await Reconnect(closeStatus, delay); await Reconnect(closeStatus, delay);
} }
@ -232,15 +231,16 @@ namespace Myriad.Gateway
try try
{ {
_logger.Verbose("Shard {ShardId}: Deserializing {EventType} to {ClrType}", _info.ShardId, eventType, clrType); _logger.Verbose("Shard {ShardId}: Deserializing {EventType} to {ClrType}", _info.ShardId, eventType,
clrType);
return JsonSerializer.Deserialize(payload.GetRawText(), clrType, _jsonSerializerOptions) return JsonSerializer.Deserialize(payload.GetRawText(), clrType, _jsonSerializerOptions)
as IGatewayEvent; as IGatewayEvent;
} }
catch (JsonException e) catch (JsonException e)
{ {
_logger.Error(e, "Shard {ShardId}: Error deserializing event {EventType} to {ClrType}", _info.ShardId, eventType, clrType); _logger.Error(e, "Shard {ShardId}: Error deserializing event {EventType} to {ClrType}", _info.ShardId,
eventType, clrType);
return null; return null;
} }
} }
} }
}

View File

@ -1,8 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net5.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>

View File

@ -1,13 +1,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Net; using System.Net;
using System.Net.Http;
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Net.Http.Json; using System.Net.Http.Json;
using System.Text.Json; using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Myriad.Rest.Exceptions; using Myriad.Rest.Exceptions;
using Myriad.Rest.Ratelimit; using Myriad.Rest.Ratelimit;
@ -19,16 +15,17 @@ using Polly;
using Serilog; using Serilog;
using Serilog.Context; using Serilog.Context;
namespace Myriad.Rest namespace Myriad.Rest;
{
public class BaseRestClient: IAsyncDisposable public class BaseRestClient: IAsyncDisposable
{ {
private readonly string _baseUrl;
private readonly Version _httpVersion = new(2, 0); private readonly Version _httpVersion = new(2, 0);
private readonly JsonSerializerOptions _jsonSerializerOptions; private readonly JsonSerializerOptions _jsonSerializerOptions;
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly Ratelimiter _ratelimiter; private readonly Ratelimiter _ratelimiter;
private readonly AsyncPolicy<HttpResponseMessage> _retryPolicy; private readonly AsyncPolicy<HttpResponseMessage> _retryPolicy;
private readonly string _baseUrl; public EventHandler<(string, int, long)> OnResponseEvent;
public BaseRestClient(string userAgent, string token, ILogger logger, string baseUrl) public BaseRestClient(string userAgent, string token, ILogger logger, string baseUrl)
{ {
@ -61,7 +58,6 @@ namespace Myriad.Rest
} }
public HttpClient Client { get; } public HttpClient Client { get; }
public EventHandler<(string, int, long)> OnResponseEvent;
public ValueTask DisposeAsync() public ValueTask DisposeAsync()
{ {
@ -94,7 +90,8 @@ namespace Myriad.Rest
return await ReadResponse<T>(response); return await ReadResponse<T>(response);
} }
public async Task<T?> PostMultipart<T>(string path, (string endpointName, ulong major) ratelimitParams, object? payload, MultipartFile[]? files) public async Task<T?> PostMultipart<T>(string path, (string endpointName, ulong major) ratelimitParams,
object? payload, MultipartFile[]? files)
where T : class where T : class
{ {
using var response = await Send(() => using var response = await Send(() =>
@ -151,13 +148,11 @@ namespace Myriad.Rest
mfd.Add(new ByteArrayContent(bodyJson), "payload_json"); mfd.Add(new ByteArrayContent(bodyJson), "payload_json");
if (files != null) if (files != null)
{
for (var i = 0; i < files.Length; i++) for (var i = 0; i < files.Length; i++)
{ {
var (filename, stream, _) = files[i]; var (filename, stream, _) = files[i];
mfd.Add(new StreamContent(stream), $"files[{i}]", filename); mfd.Add(new StreamContent(stream), $"files[{i}]", filename);
} }
}
request.Content = mfd; request.Content = mfd;
} }
@ -195,7 +190,8 @@ namespace Myriad.Rest
} }
catch (Exception exc) catch (Exception exc)
{ {
_logger.Error(exc, "HTTP error: {RequestMethod} {RequestUrl}", request.Method, request.RequestUri); _logger.Error(exc, "HTTP error: {RequestMethod} {RequestUrl}", request.Method,
request.RequestUri);
// kill the running thread // kill the running thread
// in PluralKit.Bot, this error is ignored in "IsOurProblem" (PluralKit.Bot/Utils/MiscUtils.cs) // in PluralKit.Bot, this error is ignored in "IsOurProblem" (PluralKit.Bot/Utils/MiscUtils.cs)
@ -235,12 +231,14 @@ namespace Myriad.Rest
var body = await response.Content.ReadAsStringAsync(); var body = await response.Content.ReadAsStringAsync();
var apiError = TryParseApiError(body); var apiError = TryParseApiError(body);
if (apiError != null) if (apiError != null)
_logger.Warning("Discord API error: {DiscordErrorCode} {DiscordErrorMessage}", apiError.Code, apiError.Message); _logger.Warning("Discord API error: {DiscordErrorCode} {DiscordErrorMessage}", apiError.Code,
apiError.Message);
throw CreateDiscordException(response, body, apiError); throw CreateDiscordException(response, body, apiError);
} }
private DiscordRequestException CreateDiscordException(HttpResponseMessage response, string body, DiscordApiError? apiError) private DiscordRequestException CreateDiscordException(HttpResponseMessage response, string body,
DiscordApiError? apiError)
{ {
return response.StatusCode switch return response.StatusCode switch
{ {
@ -320,4 +318,3 @@ namespace Myriad.Rest
return path; return path;
} }
} }
}

View File

@ -1,6 +1,4 @@
using System;
using System.Net; using System.Net;
using System.Threading.Tasks;
using Myriad.Rest.Types; using Myriad.Rest.Types;
using Myriad.Rest.Types.Requests; using Myriad.Rest.Types.Requests;
@ -8,22 +6,22 @@ using Myriad.Types;
using Serilog; using Serilog;
namespace Myriad.Rest namespace Myriad.Rest;
{
public class DiscordApiClient public class DiscordApiClient
{ {
public const string UserAgent = "DiscordBot (https://github.com/xSke/PluralKit/tree/main/Myriad/, v1)"; public const string UserAgent = "DiscordBot (https://github.com/xSke/PluralKit/tree/main/Myriad/, v1)";
private const string DefaultApiBaseUrl = "https://discord.com/api/v9"; private const string DefaultApiBaseUrl = "https://discord.com/api/v9";
private readonly BaseRestClient _client; private readonly BaseRestClient _client;
public EventHandler<(string, int, long)> OnResponseEvent;
public DiscordApiClient(string token, ILogger logger, string? baseUrl = null) public DiscordApiClient(string token, ILogger logger, string? baseUrl = null)
{ {
_client = new BaseRestClient(UserAgent, token, logger, baseUrl ?? DefaultApiBaseUrl); _client = new BaseRestClient(UserAgent, token, logger, baseUrl ?? DefaultApiBaseUrl);
_client.OnResponseEvent += (_, ev) => OnResponseEvent?.Invoke(null, ev); _client.OnResponseEvent += (_, ev) => OnResponseEvent?.Invoke(null, ev);
} }
public EventHandler<(string, int, long)> OnResponseEvent;
public Task<GatewayInfo> GetGateway() => public Task<GatewayInfo> GetGateway() =>
_client.Get<GatewayInfo>("/gateway", ("GetGateway", default))!; _client.Get<GatewayInfo>("/gateway", ("GetGateway", default))!;
@ -50,15 +48,19 @@ namespace Myriad.Rest
("GetGuildMember", guildId)); ("GetGuildMember", guildId));
public Task<Message> CreateMessage(ulong channelId, MessageRequest request, MultipartFile[]? files = null) => public Task<Message> CreateMessage(ulong channelId, MessageRequest request, MultipartFile[]? files = null) =>
_client.PostMultipart<Message>($"/channels/{channelId}/messages", ("CreateMessage", channelId), request, files)!; _client.PostMultipart<Message>($"/channels/{channelId}/messages", ("CreateMessage", channelId), request,
files)!;
public Task<Message> EditMessage(ulong channelId, ulong messageId, MessageEditRequest request) => public Task<Message> EditMessage(ulong channelId, ulong messageId, MessageEditRequest request) =>
_client.Patch<Message>($"/channels/{channelId}/messages/{messageId}", ("EditMessage", channelId), request)!; _client.Patch<Message>($"/channels/{channelId}/messages/{messageId}", ("EditMessage", channelId), request)!;
public Task DeleteMessage(ulong channelId, ulong messageId) => public Task DeleteMessage(ulong channelId, ulong messageId) =>
_client.Delete($"/channels/{channelId}/messages/{messageId}", ("DeleteMessage", channelId)); _client.Delete($"/channels/{channelId}/messages/{messageId}", ("DeleteMessage", channelId));
public Task DeleteMessage(Message message) => public Task DeleteMessage(Message message) =>
_client.Delete($"/channels/{message.ChannelId}/messages/{message.Id}", ("DeleteMessage", message.ChannelId)); _client.Delete($"/channels/{message.ChannelId}/messages/{message.Id}",
("DeleteMessage", message.ChannelId));
public Task CreateReaction(ulong channelId, ulong messageId, Emoji emoji) => public Task CreateReaction(ulong channelId, ulong messageId, Emoji emoji) =>
_client.Put<object>($"/channels/{channelId}/messages/{messageId}/reactions/{EncodeEmoji(emoji)}/@me", _client.Put<object>($"/channels/{channelId}/messages/{messageId}/reactions/{EncodeEmoji(emoji)}/@me",
("CreateReaction", channelId), null); ("CreateReaction", channelId), null);
@ -141,10 +143,9 @@ namespace Myriad.Rest
} }
public Task<Channel> CreateDm(ulong recipientId) => public Task<Channel> CreateDm(ulong recipientId) =>
_client.Post<Channel>($"/users/@me/channels", ("CreateDM", default), new CreateDmRequest(recipientId))!; _client.Post<Channel>("/users/@me/channels", ("CreateDM", default), new CreateDmRequest(recipientId))!;
private static string EncodeEmoji(Emoji emoji) => private static string EncodeEmoji(Emoji emoji) =>
WebUtility.UrlEncode(emoji.Id != null ? $"{emoji.Name}:{emoji.Id}" : emoji.Name) ?? WebUtility.UrlEncode(emoji.Id != null ? $"{emoji.Name}:{emoji.Id}" : emoji.Name) ??
throw new ArgumentException("Could not encode emoji"); throw new ArgumentException("Could not encode emoji");
} }
}

View File

@ -1,9 +1,8 @@
using System.Text.Json; using System.Text.Json;
namespace Myriad.Rest namespace Myriad.Rest;
{
public record DiscordApiError(string Message, int Code) public record DiscordApiError(string Message, int Code)
{ {
public JsonElement? Errors { get; init; } public JsonElement? Errors { get; init; }
} }
}

View File

@ -1,9 +1,7 @@
using System;
using System.Net; using System.Net;
using System.Net.Http;
namespace Myriad.Rest.Exceptions namespace Myriad.Rest.Exceptions;
{
public class DiscordRequestException: Exception public class DiscordRequestException: Exception
{ {
public DiscordRequestException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError) public DiscordRequestException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError)
@ -36,7 +34,8 @@ namespace Myriad.Rest.Exceptions
public class UnauthorizedException: DiscordRequestException public class UnauthorizedException: DiscordRequestException
{ {
public UnauthorizedException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError) : base( public UnauthorizedException(HttpResponseMessage response, string responseBody, DiscordApiError? apiError) :
base(
response, responseBody, apiError) response, responseBody, apiError)
{ } { }
} }
@ -74,4 +73,3 @@ namespace Myriad.Rest.Exceptions
public UnknownDiscordRequestException(HttpResponseMessage response, string responseBody, public UnknownDiscordRequestException(HttpResponseMessage response, string responseBody,
DiscordApiError? apiError) : base(response, responseBody, apiError) { } DiscordApiError? apiError) : base(response, responseBody, apiError) { }
} }
}

View File

@ -1,9 +1,7 @@
using System;
using Myriad.Rest.Ratelimit; using Myriad.Rest.Ratelimit;
namespace Myriad.Rest.Exceptions namespace Myriad.Rest.Exceptions;
{
public class RatelimitException: Exception public class RatelimitException: Exception
{ {
public RatelimitException(string? message) : base(message) { } public RatelimitException(string? message) : base(message) { }
@ -26,4 +24,3 @@ namespace Myriad.Rest.Exceptions
{ {
public GloballyRatelimitedException() : base("Global rate limit hit") { } public GloballyRatelimitedException() : base("Global rate limit hit") { }
} }
}

View File

@ -1,10 +1,7 @@
using System;
using System.Threading;
using Serilog; using Serilog;
namespace Myriad.Rest.Ratelimit namespace Myriad.Rest.Ratelimit;
{
public class Bucket public class Bucket
{ {
private static readonly TimeSpan Epsilon = TimeSpan.FromMilliseconds(10); private static readonly TimeSpan Epsilon = TimeSpan.FromMilliseconds(10);
@ -14,10 +11,10 @@ namespace Myriad.Rest.Ratelimit
private readonly ILogger _logger; private readonly ILogger _logger;
private readonly SemaphoreSlim _semaphore = new(1, 1); private readonly SemaphoreSlim _semaphore = new(1, 1);
private bool _hasReceivedHeaders;
private DateTimeOffset? _nextReset; private DateTimeOffset? _nextReset;
private bool _resetTimeValid; private bool _resetTimeValid;
private bool _hasReceivedHeaders;
public Bucket(ILogger logger, string key, ulong major, int limit) public Bucket(ILogger logger, string key, ulong major, int limit)
{ {
@ -82,8 +79,10 @@ namespace Myriad.Rest.Ratelimit
var headerNextReset = DateTimeOffset.UtcNow + headers.ResetAfter.Value; // todo: server time var headerNextReset = DateTimeOffset.UtcNow + headers.ResetAfter.Value; // todo: server time
if (_nextReset == null || headerNextReset > _nextReset) if (_nextReset == null || headerNextReset > _nextReset)
{ {
_logger.Verbose("{BucketKey}/{BucketMajor}: Received reset time {NextReset} from server (after: {NextResetAfter}, remaining: {Remaining}, local remaining: {LocalRemaining})", _logger.Verbose(
Key, Major, headerNextReset, headers.ResetAfter.Value, headers.Remaining, Remaining); "{BucketKey}/{BucketMajor}: Received reset time {NextReset} from server (after: {NextResetAfter}, remaining: {Remaining}, local remaining: {LocalRemaining})",
Key, Major, headerNextReset, headers.ResetAfter.Value, headers.Remaining, Remaining
);
_nextReset = headerNextReset; _nextReset = headerNextReset;
_resetTimeValid = true; _resetTimeValid = true;
@ -98,7 +97,8 @@ namespace Myriad.Rest.Ratelimit
var oldRemaining = Remaining; var oldRemaining = Remaining;
Remaining = Math.Min(headers.Remaining.Value, Remaining); Remaining = Math.Min(headers.Remaining.Value, Remaining);
_logger.Debug("{BucketKey}/{BucketMajor}: Received first remaining of {HeaderRemaining}, previous local remaining is {LocalRemaining}, new local remaining is {Remaining}", _logger.Debug(
"{BucketKey}/{BucketMajor}: Received first remaining of {HeaderRemaining}, previous local remaining is {LocalRemaining}, new local remaining is {Remaining}",
Key, Major, headers.Remaining.Value, oldRemaining, Remaining); Key, Major, headers.Remaining.Value, oldRemaining, Remaining);
_hasReceivedHeaders = true; _hasReceivedHeaders = true;
} }
@ -170,4 +170,3 @@ namespace Myriad.Rest.Ratelimit
return delay; return delay;
} }
} }
}

View File

@ -1,12 +1,9 @@
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using Serilog; using Serilog;
namespace Myriad.Rest.Ratelimit namespace Myriad.Rest.Ratelimit;
{
public class BucketManager: IDisposable public class BucketManager: IDisposable
{ {
private static readonly TimeSpan StaleBucketTimeout = TimeSpan.FromMinutes(5); private static readonly TimeSpan StaleBucketTimeout = TimeSpan.FromMinutes(5);
@ -44,7 +41,8 @@ namespace Myriad.Rest.Ratelimit
if (!_knownKeyLimits.TryGetValue(key, out var knownLimit)) if (!_knownKeyLimits.TryGetValue(key, out var knownLimit))
return null; return null;
_logger.Debug("Creating new bucket {BucketKey}/{BucketMajor} with limit {KnownLimit}", key, major, knownLimit); _logger.Debug("Creating new bucket {BucketKey}/{BucketMajor} with limit {KnownLimit}", key, major,
knownLimit);
return _buckets.GetOrAdd((key, major), return _buckets.GetOrAdd((key, major),
k => new Bucket(_logger, k.Item1, k.Item2, knownLimit)); k => new Bucket(_logger, k.Item1, k.Item2, knownLimit));
} }
@ -79,4 +77,3 @@ namespace Myriad.Rest.Ratelimit
} }
} }
} }
}

View File

@ -1,12 +1,7 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Polly; using Polly;
namespace Myriad.Rest.Ratelimit namespace Myriad.Rest.Ratelimit;
{
public class DiscordRateLimitPolicy: AsyncPolicy<HttpResponseMessage> public class DiscordRateLimitPolicy: AsyncPolicy<HttpResponseMessage>
{ {
public const string EndpointContextKey = "Endpoint"; public const string EndpointContextKey = "Endpoint";
@ -43,4 +38,3 @@ namespace Myriad.Rest.Ratelimit
return response; return response;
} }
} }
}

View File

@ -1,10 +1,7 @@
using System;
using System.Globalization; using System.Globalization;
using System.Linq;
using System.Net.Http;
namespace Myriad.Rest.Ratelimit namespace Myriad.Rest.Ratelimit;
{
public record RatelimitHeaders public record RatelimitHeaders
{ {
private const string LimitHeader = "X-RateLimit-Limit"; private const string LimitHeader = "X-RateLimit-Limit";
@ -26,8 +23,6 @@ namespace Myriad.Rest.Ratelimit
public bool HasRatelimitInfo => public bool HasRatelimitInfo =>
Limit != null && Remaining != null && Reset != null && ResetAfter != null && Bucket != null; Limit != null && Remaining != null && Reset != null && ResetAfter != null && Bucket != null;
public RatelimitHeaders() { }
public static RatelimitHeaders Parse(HttpResponseMessage response) public static RatelimitHeaders Parse(HttpResponseMessage response)
{ {
var headers = new RatelimitHeaders var headers = new RatelimitHeaders
@ -82,4 +77,3 @@ namespace Myriad.Rest.Ratelimit
return value; return value;
} }
} }
}

View File

@ -1,11 +1,9 @@
using System;
using Myriad.Rest.Exceptions; using Myriad.Rest.Exceptions;
using Serilog; using Serilog;
namespace Myriad.Rest.Ratelimit namespace Myriad.Rest.Ratelimit;
{
public class Ratelimiter: IDisposable public class Ratelimiter: IDisposable
{ {
private readonly BucketManager _buckets; private readonly BucketManager _buckets;
@ -83,4 +81,3 @@ namespace Myriad.Rest.Ratelimit
private bool IsGloballyRateLimited(DateTimeOffset now) => private bool IsGloballyRateLimited(DateTimeOffset now) =>
_globalRateLimitExpiry > now; _globalRateLimitExpiry > now;
} }
}

View File

@ -2,8 +2,8 @@ using System.Text.Json.Serialization;
using Myriad.Serialization; using Myriad.Serialization;
namespace Myriad.Rest.Types namespace Myriad.Rest.Types;
{
public record AllowedMentions public record AllowedMentions
{ {
[JsonConverter(typeof(JsonSnakeCaseStringEnumConverter))] [JsonConverter(typeof(JsonSnakeCaseStringEnumConverter))]
@ -19,4 +19,3 @@ namespace Myriad.Rest.Types
public ulong[]? Roles { get; set; } public ulong[]? Roles { get; set; }
public bool RepliedUser { get; set; } public bool RepliedUser { get; set; }
} }
}

View File

@ -1,6 +1,3 @@
using System.IO; namespace Myriad.Rest.Types;
namespace Myriad.Rest.Types
{
public record MultipartFile(string Filename, Stream Data, string? Description); public record MultipartFile(string Filename, Stream Data, string? Description);
}

View File

@ -1,13 +1,10 @@
using System.Collections.Generic;
using Myriad.Types; using Myriad.Types;
namespace Myriad.Rest.Types namespace Myriad.Rest.Types;
{
public record ApplicationCommandRequest public record ApplicationCommandRequest
{ {
public string Name { get; init; } public string Name { get; init; }
public string Description { get; init; } public string Description { get; init; }
public List<ApplicationCommandOption>? Options { get; init; } public List<ApplicationCommandOption>? Options { get; init; }
} }
}

View File

@ -1,4 +1,3 @@
namespace Myriad.Rest.Types.Requests namespace Myriad.Rest.Types.Requests;
{
public record CreateDmRequest(ulong RecipientId); public record CreateDmRequest(ulong RecipientId);
}

View File

@ -1,4 +1,3 @@
namespace Myriad.Rest.Types.Requests namespace Myriad.Rest.Types.Requests;
{
public record CreateWebhookRequest(string Name); public record CreateWebhookRequest(string Name);
}

View File

@ -1,7 +1,7 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Rest.Types.Requests namespace Myriad.Rest.Types.Requests;
{
public record ExecuteWebhookRequest public record ExecuteWebhookRequest
{ {
public string? Content { get; init; } public string? Content { get; init; }
@ -11,4 +11,3 @@ namespace Myriad.Rest.Types.Requests
public Message.Attachment[] Attachments { get; set; } public Message.Attachment[] Attachments { get; set; }
public AllowedMentions? AllowedMentions { get; init; } public AllowedMentions? AllowedMentions { get; init; }
} }
}

View File

@ -3,8 +3,8 @@ using System.Text.Json.Serialization;
using Myriad.Types; using Myriad.Types;
using Myriad.Utils; using Myriad.Utils;
namespace Myriad.Rest.Types.Requests namespace Myriad.Rest.Types.Requests;
{
public record MessageEditRequest public record MessageEditRequest
{ {
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
@ -22,4 +22,3 @@ namespace Myriad.Rest.Types.Requests
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public Optional<MessageComponent[]?> Components { get; init; } public Optional<MessageComponent[]?> Components { get; init; }
} }
}

View File

@ -1,7 +1,7 @@
using Myriad.Types; using Myriad.Types;
namespace Myriad.Rest.Types.Requests namespace Myriad.Rest.Types.Requests;
{
public record MessageRequest public record MessageRequest
{ {
public string? Content { get; set; } public string? Content { get; set; }
@ -11,4 +11,3 @@ namespace Myriad.Rest.Types.Requests
public Embed? Embed { get; set; } public Embed? Embed { get; set; }
public MessageComponent[]? Components { get; set; } public MessageComponent[]? Components { get; set; }
} }
}

View File

@ -1,7 +1,6 @@
namespace Myriad.Rest.Types namespace Myriad.Rest.Types;
{
public record ModifyGuildMemberRequest public record ModifyGuildMemberRequest
{ {
public string? Nick { get; init; } public string? Nick { get; init; }
} }
}

View File

@ -2,8 +2,8 @@ using System.Text.Json.Serialization;
using Myriad.Utils; using Myriad.Utils;
namespace Myriad.Rest.Types.Requests namespace Myriad.Rest.Types.Requests;
{
public record WebhookMessageEditRequest public record WebhookMessageEditRequest
{ {
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
@ -12,4 +12,3 @@ namespace Myriad.Rest.Types.Requests
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public Optional<AllowedMentions> AllowedMentions { get; init; } public Optional<AllowedMentions> AllowedMentions { get; init; }
} }
}

View File

@ -1,8 +1,8 @@
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace Myriad.Serialization namespace Myriad.Serialization;
{
public static class JsonSerializerOptionsExtensions public static class JsonSerializerOptionsExtensions
{ {
public static JsonSerializerOptions ConfigureForMyriad(this JsonSerializerOptions opts) public static JsonSerializerOptions ConfigureForMyriad(this JsonSerializerOptions opts)
@ -18,4 +18,3 @@ namespace Myriad.Serialization
return opts; return opts;
} }
} }
}

View File

@ -1,9 +1,8 @@
using System;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
namespace Myriad.Serialization namespace Myriad.Serialization;
{
// From https://github.com/J0rgeSerran0/JsonNamingPolicy/blob/master/JsonSnakeCaseNamingPolicy.cs, no NuGet :/ // From https://github.com/J0rgeSerran0/JsonNamingPolicy/blob/master/JsonSnakeCaseNamingPolicy.cs, no NuGet :/
public class JsonSnakeCaseNamingPolicy: JsonNamingPolicy public class JsonSnakeCaseNamingPolicy: JsonNamingPolicy
{ {
@ -85,4 +84,3 @@ namespace Myriad.Serialization
return stringBuilder.ToString().ToLower(); return stringBuilder.ToString().ToLower();
} }
} }
}

View File

@ -1,9 +1,8 @@
using System;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace Myriad.Serialization namespace Myriad.Serialization;
{
public class JsonSnakeCaseStringEnumConverter: JsonConverterFactory public class JsonSnakeCaseStringEnumConverter: JsonConverterFactory
{ {
private readonly JsonStringEnumConverter _inner = new(new JsonSnakeCaseNamingPolicy()); private readonly JsonStringEnumConverter _inner = new(new JsonSnakeCaseNamingPolicy());
@ -14,4 +13,3 @@ namespace Myriad.Serialization
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) => public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) =>
_inner.CreateConverter(typeToConvert, options); _inner.CreateConverter(typeToConvert, options);
} }
}

View File

@ -1,9 +1,8 @@
using System;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
namespace Myriad.Serialization namespace Myriad.Serialization;
{
public class JsonStringConverter: JsonConverter<object> public class JsonStringConverter: JsonConverter<object>
{ {
public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
@ -19,4 +18,3 @@ namespace Myriad.Serialization
writer.WriteStringValue(inner); writer.WriteStringValue(inner);
} }
} }
}

View File

@ -1,28 +1,13 @@
using System;
using System.Reflection; using System.Reflection;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Myriad.Utils; using Myriad.Utils;
namespace Myriad.Serialization namespace Myriad.Serialization;
{
public class OptionalConverterFactory: JsonConverterFactory public class OptionalConverterFactory: JsonConverterFactory
{ {
public class Inner<T>: JsonConverter<Optional<T>>
{
public override Optional<T> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var inner = JsonSerializer.Deserialize<T>(ref reader, options);
return new(inner!);
}
public override void Write(Utf8JsonWriter writer, Optional<T> value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value.HasValue ? value.GetValue() : default, typeof(T), options);
}
}
public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options) public override JsonConverter? CreateConverter(Type typeToConvert, JsonSerializerOptions options)
{ {
var innerType = typeToConvert.GetGenericArguments()[0]; var innerType = typeToConvert.GetGenericArguments()[0];
@ -44,5 +29,19 @@ namespace Myriad.Serialization
return true; return true;
} }
public class Inner<T>: JsonConverter<Optional<T>>
{
public override Optional<T> Read(ref Utf8JsonReader reader, Type typeToConvert,
JsonSerializerOptions options)
{
var inner = JsonSerializer.Deserialize<T>(ref reader, options);
return new Optional<T>(inner!);
}
public override void Write(Utf8JsonWriter writer, Optional<T> value, JsonSerializerOptions options)
{
JsonSerializer.Serialize(writer, value.HasValue ? value.GetValue() : default, typeof(T), options);
}
} }
} }

View File

@ -1,11 +1,10 @@
using System;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Myriad.Types; using Myriad.Types;
namespace Myriad.Serialization namespace Myriad.Serialization;
{
public class PermissionSetJsonConverter: JsonConverter<PermissionSet> public class PermissionSetJsonConverter: JsonConverter<PermissionSet>
{ {
public override PermissionSet Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override PermissionSet Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
@ -21,4 +20,3 @@ namespace Myriad.Serialization
writer.WriteStringValue(((ulong)value).ToString()); writer.WriteStringValue(((ulong)value).ToString());
} }
} }
}

View File

@ -1,11 +1,10 @@
using System;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Myriad.Gateway; using Myriad.Gateway;
namespace Myriad.Serialization namespace Myriad.Serialization;
{
public class ShardInfoJsonConverter: JsonConverter<ShardInfo> public class ShardInfoJsonConverter: JsonConverter<ShardInfo>
{ {
public override ShardInfo? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override ShardInfo? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
@ -25,4 +24,3 @@ namespace Myriad.Serialization
writer.WriteEndArray(); writer.WriteEndArray();
} }
} }
}

View File

@ -1,5 +1,5 @@
namespace Myriad.Types namespace Myriad.Types;
{
public record Application: ApplicationPartial public record Application: ApplicationPartial
{ {
public string Name { get; init; } public string Name { get; init; }
@ -22,4 +22,3 @@ namespace Myriad.Types
public ulong Id { get; init; } public ulong Id { get; init; }
public int Flags { get; init; } public int Flags { get; init; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Myriad.Types namespace Myriad.Types;
{
public record ApplicationCommand public record ApplicationCommand
{ {
public ulong Id { get; init; } public ulong Id { get; init; }
@ -8,4 +8,3 @@ namespace Myriad.Types
public string Description { get; init; } public string Description { get; init; }
public ApplicationCommandOption[]? Options { get; init; } public ApplicationCommandOption[]? Options { get; init; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Myriad.Types namespace Myriad.Types;
{
public record ApplicationCommandInteractionData public record ApplicationCommandInteractionData
{ {
public ulong? Id { get; init; } public ulong? Id { get; init; }
@ -8,4 +8,3 @@ namespace Myriad.Types
public string? CustomId { get; init; } public string? CustomId { get; init; }
public ComponentType? ComponentType { get; init; } public ComponentType? ComponentType { get; init; }
} }
}

View File

@ -1,9 +1,8 @@
namespace Myriad.Types namespace Myriad.Types;
{
public record ApplicationCommandInteractionDataOption public record ApplicationCommandInteractionDataOption
{ {
public string Name { get; init; } public string Name { get; init; }
public object? Value { get; init; } public object? Value { get; init; }
public ApplicationCommandInteractionDataOption[]? Options { get; init; } public ApplicationCommandInteractionDataOption[]? Options { get; init; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Myriad.Types namespace Myriad.Types;
{
public record ApplicationCommandOption(ApplicationCommandOption.OptionType Type, string Name, string Description) public record ApplicationCommandOption(ApplicationCommandOption.OptionType Type, string Name, string Description)
{ {
public enum OptionType public enum OptionType
@ -21,4 +21,3 @@ namespace Myriad.Types
public record Choice(string Name, object Value); public record Choice(string Name, object Value);
} }
}

View File

@ -1,5 +1,5 @@
namespace Myriad.Types namespace Myriad.Types;
{
public record Interaction public record Interaction
{ {
public enum InteractionType public enum InteractionType
@ -19,4 +19,3 @@ namespace Myriad.Types
public string Token { get; init; } public string Token { get; init; }
public Message? Message { get; init; } public Message? Message { get; init; }
} }
}

View File

@ -3,8 +3,8 @@ using System.Text.Json.Serialization;
using Myriad.Rest.Types; using Myriad.Rest.Types;
using Myriad.Utils; using Myriad.Utils;
namespace Myriad.Types namespace Myriad.Types;
{
public record InteractionApplicationCommandCallbackData public record InteractionApplicationCommandCallbackData
{ {
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
@ -25,4 +25,3 @@ namespace Myriad.Types
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public Optional<MessageComponent[]?> Components { get; init; } public Optional<MessageComponent[]?> Components { get; init; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Myriad.Types namespace Myriad.Types;
{
public record InteractionResponse public record InteractionResponse
{ {
public enum ResponseType public enum ResponseType
@ -14,4 +14,3 @@ namespace Myriad.Types
public ResponseType Type { get; init; } public ResponseType Type { get; init; }
public InteractionApplicationCommandCallbackData? Data { get; init; } public InteractionApplicationCommandCallbackData? Data { get; init; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Myriad.Types namespace Myriad.Types;
{
public record Channel public record Channel
{ {
public enum ChannelType public enum ChannelType
@ -41,5 +41,5 @@ namespace Myriad.Types
Role = 0, Role = 0,
Member = 1 Member = 1
} }
}
} }

View File

@ -1,5 +1,5 @@
namespace Myriad.Types namespace Myriad.Types;
{
public enum ButtonStyle public enum ButtonStyle
{ {
Primary = 1, Primary = 1,
@ -8,4 +8,3 @@ namespace Myriad.Types
Danger = 4, Danger = 4,
Link = 5 Link = 5
} }
}

View File

@ -1,8 +1,7 @@
namespace Myriad.Types namespace Myriad.Types;
{
public enum ComponentType public enum ComponentType
{ {
ActionRow = 1, ActionRow = 1,
Button = 2 Button = 2
} }
}

View File

@ -1,5 +1,5 @@
namespace Myriad.Types namespace Myriad.Types;
{
public record MessageComponent public record MessageComponent
{ {
public ComponentType Type { get; init; } public ComponentType Type { get; init; }
@ -11,4 +11,3 @@ namespace Myriad.Types
public bool? Disabled { get; init; } public bool? Disabled { get; init; }
public MessageComponent[]? Components { get; init; } public MessageComponent[]? Components { get; init; }
} }
}

View File

@ -1,5 +1,5 @@
namespace Myriad.Types namespace Myriad.Types;
{
public record Embed public record Embed
{ {
public string? Title { get; init; } public string? Title { get; init; }
@ -59,4 +59,3 @@ namespace Myriad.Types
bool Inline = false bool Inline = false
); );
} }
}

Some files were not shown because too many files have changed in this diff Show More