diff --git a/Discord.API/DataTypes/ChannelData.cs b/Discord.API/DataTypes/ChannelData.cs index 0805a2d..56f6906 100644 --- a/Discord.API/DataTypes/ChannelData.cs +++ b/Discord.API/DataTypes/ChannelData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; namespace Discord.API; -public class ChannelData +public class ChannelData : IId { public required ulong Id { get; init; } public required int Type { get; init; } diff --git a/Discord.API/DataTypes/IId.cs b/Discord.API/DataTypes/IId.cs new file mode 100644 index 0000000..14fce09 --- /dev/null +++ b/Discord.API/DataTypes/IId.cs @@ -0,0 +1,6 @@ +namespace Discord.API; + +public interface IId +{ + public ulong Id {get;} +} diff --git a/Discord.API/DataTypes/RoleData.cs b/Discord.API/DataTypes/RoleData.cs index 0231339..ce8a758 100644 --- a/Discord.API/DataTypes/RoleData.cs +++ b/Discord.API/DataTypes/RoleData.cs @@ -2,7 +2,7 @@ using System.Text.Json.Serialization; namespace Discord.API; -public class RoleData : IJsonOnDeserialized +public class RoleData : IJsonOnDeserialized, IId { public required ulong Id { get; init; } public required string Name { get; init; } diff --git a/Discord.Model/Types/BaseType.cs b/Discord.Model/Types/BaseType.cs new file mode 100644 index 0000000..abacef4 --- /dev/null +++ b/Discord.Model/Types/BaseType.cs @@ -0,0 +1,20 @@ +namespace Discord.Model; + +public abstract class BaseType +{ + public virtual Snowflake Id {get; } + public event EventHandler? Deleted; + public event EventHandler? Updated; + + protected BaseType(Snowflake id){ + this.Id=id; + } + protected BaseType(){ + } + + internal virtual void Delete(){ + Deleted?.Invoke(this, EventArgs.Empty); + } + + internal abstract void Update(T data); +} diff --git a/Discord.Model/Types/Channel.cs b/Discord.Model/Types/Channel.cs deleted file mode 100644 index 73beeb7..0000000 --- a/Discord.Model/Types/Channel.cs +++ /dev/null @@ -1,38 +0,0 @@ -using Discord.API; - -namespace Discord.Model; - -public class Channel -{ - public enum ChannelType { - GuildText = 0, - DM = 1, - GuildVoice = 2, - GroupDM = 3, - GuildCategory = 4, - GuildAnnouncement = 5, - AnnouncementThread = 10, - PublicThread = 11, - PrivateThread = 12, - GuildStageVoice = 13, - GuildDirectory = 14, - GuildForum = 15, - GuildMedia = 16 - } - - public Snowflake Id { get; } - public ChannelType Type { get; protected set; } - - public Channel(ChannelData data){ - Id = data.Id; - Update(data, false); - } - - protected void Update(ChannelData data, bool call_base_update){ - Type = (ChannelType)data.Type; - } - - public virtual void Update(ChannelData data){ - Update(data, true); - } -} diff --git a/Discord.Model/Types/ChannelType.cs b/Discord.Model/Types/ChannelType.cs new file mode 100644 index 0000000..59b8307 --- /dev/null +++ b/Discord.Model/Types/ChannelType.cs @@ -0,0 +1,18 @@ +namespace Discord.Model; + +public enum ChannelType +{ + GuildText = 0, + DM = 1, + GuildVoice = 2, + GroupDM = 3, + GuildCategory = 4, + GuildAnnouncement = 5, + AnnouncementThread = 10, + PublicThread = 11, + PrivateThread = 12, + GuildStageVoice = 13, + GuildDirectory = 14, + GuildForum = 15, + GuildMedia = 16 +} diff --git a/Discord.Model/Types/DiscordCollection.cs b/Discord.Model/Types/DiscordCollection.cs new file mode 100644 index 0000000..f4fe8ea --- /dev/null +++ b/Discord.Model/Types/DiscordCollection.cs @@ -0,0 +1,71 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using Discord.API; +using Serilog; + +namespace Discord.Model; + +public abstract class DiscordCollection : IReadOnlyDictionary where T : BaseType where D: IId +{ + internal DiscordCollection(int capacity){ + _dict = new(capacity); + } + + internal void Update(D[] data){ + // Update existing, add new items + foreach(var item in data){ + if(_dict.TryGetValue(item.Id, out T? obj)){ + obj.Update(item); + }else{ + _dict.Add(item.Id, CreateInstance(item)); + } + } + + // Delete items no longer present + foreach(Snowflake id in _dict.Select(item => item.Value.Id)){ + if(!data.Any(data => data.Id == id)){ + T obj = _dict[id]; + _dict.Remove(id); + obj.Delete(); + } + } + } + + internal void Remove(Snowflake id){ + if(_dict.TryGetValue(id, out T? item)){ + _dict.Remove(id); + item.Delete(); + }else{ + Log.Warning("Trying to delete item not in the dictionary. Id: {id}, Type: {type}", id, typeof(T)); + } + } + + internal void UpdateSingle(D data){ + if(_dict.TryGetValue(data.Id, out T? obj)){ + obj.Update(data); + }else{ + _dict.Add(data.Id, CreateInstance(data)); + } + } + + protected abstract T CreateInstance(D data); + + private Dictionary _dict; + + public T this[Snowflake key] => _dict[key]; + + public IEnumerable Keys => _dict.Keys; + + public IEnumerable Values => _dict.Values; + + public int Count => _dict.Count; + + public bool ContainsKey(Snowflake key) => _dict.ContainsKey(key); + + public IEnumerator> GetEnumerator() => _dict.GetEnumerator(); + + public bool TryGetValue(Snowflake key, [MaybeNullWhen(false)] out T value) + => _dict.TryGetValue(key, out value); + + IEnumerator IEnumerable.GetEnumerator() => _dict.GetEnumerator(); +} diff --git a/Discord.Model/Types/Guild.cs b/Discord.Model/Types/Guild.cs new file mode 100644 index 0000000..68b6aa0 --- /dev/null +++ b/Discord.Model/Types/Guild.cs @@ -0,0 +1,34 @@ +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using Discord.API; + +namespace Discord.Model; + +public class Guild : BaseType +{ + public string Name { get; private set; } + public ulong? AfkChannelId { get; private set; } + public int AfkTimeout { get; private set; } + public RoleCollection Roles {get; } + public ulong? SystemChannelId { get; private set; } + public uint SystemChannelFlags { get; private set; } + public string? Description { get; private set; } + public int NsfwLevel { get; private set; } + + internal Guild(GuildData data) : base(data.Id){ + Roles = new(data.Roles.Length); + Update(data); + } + + [MemberNotNull(nameof(Name))] + internal override void Update(GuildData data){ + Name = data.Name; + AfkChannelId = data.AfkChannelId; + AfkTimeout = data.AfkTimeout; + Roles.Update(data.Roles); + SystemChannelId = data.SystemChannelId; + SystemChannelFlags = data.SystemChannelFlags; + Description = data.Description; + NsfwLevel = data.NsfwLevel; + } +} diff --git a/Discord.Model/Types/GuildChannel.cs b/Discord.Model/Types/GuildChannel.cs index 7072f3a..5b0e742 100644 --- a/Discord.Model/Types/GuildChannel.cs +++ b/Discord.Model/Types/GuildChannel.cs @@ -4,44 +4,37 @@ using Serilog; namespace Discord.Model; -public class GuildChannel : Channel +public class GuildChannel : BaseType { - public string Name {get; protected set;} - public Snowflake? ParentId {get; protected set;} + public string Name {get; private set;} + public Snowflake? ParentId {get; private set;} + public ChannelType Type {get; private set;} - internal GuildChannel(GuildChannelData data) : base(data){ - Update(data, false); + //TODO: Optimise this shit + protected virtual ChannelType[] ValidTypes { get; } = [ + ChannelType.GuildText, + ChannelType.GuildVoice, + ChannelType.GuildCategory, + ChannelType.GuildAnnouncement, + ChannelType.GuildStageVoice, + ChannelType.GuildForum, + ChannelType.GuildMedia + ]; + + internal GuildChannel(GuildChannelData data) : base(data.Id) { + Update(data); } [MemberNotNull(nameof(Name))] - protected void Update(GuildChannelData data, bool call_base_update) + internal override void Update(GuildChannelData data) { - if((ChannelType)data.Type is not ChannelType.GuildText or - ChannelType.GuildVoice or - ChannelType.GuildCategory or - ChannelType.GuildAnnouncement or - ChannelType.AnnouncementThread or - ChannelType.PublicThread or - ChannelType.PrivateThread or - ChannelType.GuildStageVoice or - ChannelType.GuildForum or - ChannelType.GuildMedia) + if(!ValidTypes.Contains((ChannelType)data.Type)) { Log.Warning("GuildChannel has type {type} that is not compatible", (ChannelType)data.Type); } Name = data.Name; ParentId = data.ParentId; - - if(call_base_update) base.Update(data); - } - - public override void Update(ChannelData data) - { - if(data is GuildChannelData guild_data){ - Update(guild_data, true); - }else{ - throw new ArgumentException("data must be GuildChannelData", nameof(data)); - } + Type = (ChannelType) data.Type; } } diff --git a/Discord.Model/Types/GuildTextChannel.cs b/Discord.Model/Types/GuildTextChannel.cs index bf0ca8a..949e828 100644 --- a/Discord.Model/Types/GuildTextChannel.cs +++ b/Discord.Model/Types/GuildTextChannel.cs @@ -5,25 +5,12 @@ namespace Discord.Model; public class GuildTextChannel : GuildChannel { + protected override ChannelType[] ValidTypes { get; } = [ + ChannelType.GuildText, + ChannelType.GuildAnnouncement, + ChannelType.GuildForum + ]; internal GuildTextChannel(GuildChannelData data) : base(data){ - Update(data, false); } - - protected new void Update(GuildChannelData data, bool call_base_update){ - if((ChannelType)data.Type is not ChannelType.GuildText or ChannelType.GuildAnnouncement){ - Log.Warning("Channel type is not compatible with GuildTextChannel: {type}", (ChannelType)data.Type); - } - if(call_base_update) base.Update(data, call_base_update); - } - - public override void Update(ChannelData data) - { - if(data is GuildChannelData guild_data){ - Update(guild_data, true); - }else{ - throw new ArgumentException("data must be GuildChannelData", nameof(data)); - } - } - } diff --git a/Discord.Model/Types/GuildVoiceChannel.cs b/Discord.Model/Types/GuildVoiceChannel.cs index 35fbd6d..a4bc64b 100644 --- a/Discord.Model/Types/GuildVoiceChannel.cs +++ b/Discord.Model/Types/GuildVoiceChannel.cs @@ -1,28 +1,29 @@ -using System.Data; -using Discord.API; +using Discord.API; +using Serilog; namespace Discord.Model; public class GuildVoiceChannel : GuildChannel { - public int Bitrate {get; protected set;} + public int Bitrate {get; protected set;} + + protected override ChannelType[] ValidTypes {get; } = [ + ChannelType.GuildVoice, + ChannelType.GuildStageVoice, + ChannelType.GuildMedia + ]; internal GuildVoiceChannel(GuildChannelData data) : base(data) { - Update(data, false); } - protected new void Update(GuildChannelData data, bool call_base_update){ - Bitrate = data.Bitrate!.Value; - if(call_base_update) base.Update(data, call_base_update); - } - - public override void Update(ChannelData data) + internal override void Update(GuildChannelData data) { - if(data is GuildChannelData guild_data){ - Update(guild_data, true); + if(data.Bitrate != null){ + Bitrate = data.Bitrate.Value; }else{ - throw new ArgumentException("data must be GuildChannelData", nameof(data)); + Log.Warning("VoiceChannel data did not contain Bitrate"); } + base.Update(data); } } diff --git a/Discord.Model/Types/Role.cs b/Discord.Model/Types/Role.cs index 38a50e2..0be8769 100644 --- a/Discord.Model/Types/Role.cs +++ b/Discord.Model/Types/Role.cs @@ -3,9 +3,8 @@ using Discord.API; namespace Discord.Model; -public sealed class Role +public sealed class Role : BaseType { - public Snowflake Id { get; private set; } public string Name { get; private set; } public uint Color { get; private set; } public bool Hoist { get; private set; } @@ -13,13 +12,12 @@ public sealed class Role public bool Managed { get; private set; } public bool Mentionable { get; private set; } - public Role(RoleData data){ - this.Id = data.Id; + public Role(RoleData data) : base(data.Id){ Update(data); } [MemberNotNull(nameof(Name))] - private void Update(RoleData data){ + internal override void Update(RoleData data){ this.Name = data.Name; this.Color = data.Color; this.Hoist = data.Hoist; diff --git a/Discord.Model/Types/RoleCollection.cs b/Discord.Model/Types/RoleCollection.cs new file mode 100644 index 0000000..029d1b2 --- /dev/null +++ b/Discord.Model/Types/RoleCollection.cs @@ -0,0 +1,13 @@ +using Discord.API; + +namespace Discord.Model; + +public class RoleCollection : DiscordCollection +{ + public RoleCollection(int capacity) : base(capacity) + { + } + + protected override Role CreateInstance(RoleData data) + => new Role(data); +}