diff --git a/Fluxer.Net/Data/Apps/Application.cs b/Fluxer.Net/Data/Apps/Application.cs index 3864a50..3f6dc9d 100644 --- a/Fluxer.Net/Data/Apps/Application.cs +++ b/Fluxer.Net/Data/Apps/Application.cs @@ -23,7 +23,7 @@ public static Application Create(FluxerBaseClient client, ApplicationJson json) return data; } - internal void Update(FluxerBaseClient client, ApplicationJson json) + internal virtual void Update(FluxerBaseClient client, ApplicationJson json) { base.Update(client, json); RedirectUrls = json.RedirectUrls; diff --git a/Fluxer.Net/Data/Channels/Channel.cs b/Fluxer.Net/Data/Channels/Channel.cs index c387079..f74e6c7 100644 --- a/Fluxer.Net/Data/Channels/Channel.cs +++ b/Fluxer.Net/Data/Channels/Channel.cs @@ -106,7 +106,7 @@ public static Channel Create(FluxerBaseClient client, ChannelJson json) break; case ChannelType.DmPersonalNotes: { - data = new SavedMessagesChannel(client); + data = new SavedNotesChannel(client); data.IsTextable = true; } break; @@ -128,7 +128,10 @@ public static Channel Create(FluxerBaseClient client, ChannelJson json) break; default: { - data = new Channel(client); + if (data.GuildId.HasValue) + data = new GuildChannel(client); + else + data = new Channel(client); data.IsTextable = true; } break; @@ -137,7 +140,7 @@ public static Channel Create(FluxerBaseClient client, ChannelJson json) return data; } - internal void Update(FluxerBaseClient client, ChannelJson json) + internal virtual void Update(FluxerBaseClient client, ChannelJson json) { Id = json.Id; GuildId = json.GuildId; diff --git a/Fluxer.Net/Data/Channels/GuildChannel.cs b/Fluxer.Net/Data/Channels/GuildChannel.cs index baac528..16d3a8d 100644 --- a/Fluxer.Net/Data/Channels/GuildChannel.cs +++ b/Fluxer.Net/Data/Channels/GuildChannel.cs @@ -6,16 +6,4 @@ internal GuildChannel(FluxerBaseClient client) : base(client) { } - - public static GuildChannel Create(FluxerBaseClient client, ChannelJson json) - { - var data = new GuildChannel(client); - data.Update(client, json); - return data; - } - - internal void Update(FluxerBaseClient client, ChannelJson json) - { - base.Update(client, json); - } } diff --git a/Fluxer.Net/Data/Channels/IPermissionOverwrite.cs b/Fluxer.Net/Data/Channels/IPermissionOverwrite.cs index a74274c..a5c0459 100644 --- a/Fluxer.Net/Data/Channels/IPermissionOverwrite.cs +++ b/Fluxer.Net/Data/Channels/IPermissionOverwrite.cs @@ -15,10 +15,10 @@ public interface IPermissionOverwrite /// /// The bitwise value of allowed permissions. /// - ulong Allow { get; } + ChannelPermissions Allow { get; } /// /// The bitwise value of denied permissions. /// - ulong Deny { get; } + ChannelPermissions Deny { get; } } diff --git a/Fluxer.Net/Data/Channels/NotesChannel.cs b/Fluxer.Net/Data/Channels/NotesChannel.cs deleted file mode 100644 index 9d3c14c..0000000 --- a/Fluxer.Net/Data/Channels/NotesChannel.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Fluxer.Net; - -public class SavedMessagesChannel : Channel, ITextable -{ - internal SavedMessagesChannel(FluxerBaseClient client) : base(client) - { - - } -} diff --git a/Fluxer.Net/Data/Channels/PermissionOverwrite.cs b/Fluxer.Net/Data/Channels/PermissionOverwrite.cs index b2f6660..13ece8b 100644 --- a/Fluxer.Net/Data/Channels/PermissionOverwrite.cs +++ b/Fluxer.Net/Data/Channels/PermissionOverwrite.cs @@ -10,10 +10,10 @@ public class PermissionOverwrite : Entity, IPermissionOverwrite public int Type { get; internal set; } /// - public ulong Allow { get; internal set; } + public ChannelPermissions Allow { get; internal set; } /// - public ulong Deny { get; internal set; } + public ChannelPermissions Deny { get; internal set; } internal PermissionOverwrite(FluxerBaseClient client) : base(client) { diff --git a/Fluxer.Net/Data/Channels/PermissionOverwriteJson.cs b/Fluxer.Net/Data/Channels/PermissionOverwriteJson.cs index 0e14b79..648907f 100644 --- a/Fluxer.Net/Data/Channels/PermissionOverwriteJson.cs +++ b/Fluxer.Net/Data/Channels/PermissionOverwriteJson.cs @@ -18,9 +18,9 @@ public class PermissionOverwriteJson : IPermissionOverwrite /// [JsonProperty("allow")] - public ulong Allow { get; set; } + public ChannelPermissions Allow { get; set; } /// [JsonProperty("deny")] - public ulong Deny { get; set; } + public ChannelPermissions Deny { get; set; } } diff --git a/Fluxer.Net/Data/Channels/SavedNotesChannel.cs b/Fluxer.Net/Data/Channels/SavedNotesChannel.cs new file mode 100644 index 0000000..1833ccd --- /dev/null +++ b/Fluxer.Net/Data/Channels/SavedNotesChannel.cs @@ -0,0 +1,9 @@ +namespace Fluxer.Net; + +public class SavedNotesChannel : Channel, ITextable +{ + internal SavedNotesChannel(FluxerBaseClient client) : base(client) + { + + } +} diff --git a/Fluxer.Net/Data/Channels/SocketCategoryChannel.cs b/Fluxer.Net/Data/Channels/SocketCategoryChannel.cs new file mode 100644 index 0000000..8fa63fe --- /dev/null +++ b/Fluxer.Net/Data/Channels/SocketCategoryChannel.cs @@ -0,0 +1,16 @@ +namespace Fluxer.Net; + +public class SocketCategoryChannel : CategoryChannel +{ + internal SocketCategoryChannel(FluxerBaseClient client) : base(client) + { + + } + + public static SocketCategoryChannel Create(FluxerBaseClient client, ChannelJson json) + { + var data = new SocketCategoryChannel(client); + data.Update(client, json); + return data; + } +} diff --git a/Fluxer.Net/Data/Channels/SocketChannel.cs b/Fluxer.Net/Data/Channels/SocketChannel.cs deleted file mode 100644 index 5fa6384..0000000 --- a/Fluxer.Net/Data/Channels/SocketChannel.cs +++ /dev/null @@ -1,32 +0,0 @@ -namespace Fluxer.Net; - -public class SocketChannel : Channel -{ - /// - /// Permissions for the channel. - /// - public ChannelPermissions Permissions { get; internal set; } - - internal SocketChannel(FluxerBaseClient client) : base(client) - { - - } - - public static SocketChannel Create(FluxerBaseClient client, ChannelJson json, ulong guildId) - { - SocketChannel data = new SocketChannel(client); - data.GuildId = guildId; - data.Update(client, json); - return data; - } - - internal void Update(FluxerBaseClient client, ChannelJson json) - { - base.Update(client, json); - PermissionOverwriteJson? overwrite = json.PermissionOverwrites.FirstOrDefault(x => x.Id == Id); - if (overwrite != null) - Permissions = new ChannelPermissions((Permissions)overwrite.Allow); - else - Permissions = new ChannelPermissions(0); - } -} \ No newline at end of file diff --git a/Fluxer.Net/Data/Channels/SocketDMChannel.cs b/Fluxer.Net/Data/Channels/SocketDMChannel.cs new file mode 100644 index 0000000..c0c12c5 --- /dev/null +++ b/Fluxer.Net/Data/Channels/SocketDMChannel.cs @@ -0,0 +1,9 @@ +namespace Fluxer.Net; + +public class SocketDMChannel : DMChannel +{ + internal SocketDMChannel(FluxerBaseClient client) : base(client) + { + + } +} diff --git a/Fluxer.Net/Data/Channels/SocketGroupChannel.cs b/Fluxer.Net/Data/Channels/SocketGroupChannel.cs new file mode 100644 index 0000000..807b453 --- /dev/null +++ b/Fluxer.Net/Data/Channels/SocketGroupChannel.cs @@ -0,0 +1,16 @@ +namespace Fluxer.Net; + +public class SocketGroupChannel : GroupChannel +{ + internal SocketGroupChannel(FluxerBaseClient client) : base(client) + { + + } + + public static SocketGroupChannel Create(FluxerBaseClient client, ChannelJson json) + { + var data = new SocketGroupChannel(client); + data.Update(client, json); + return data; + } +} diff --git a/Fluxer.Net/Data/Channels/SocketLinkChannel.cs b/Fluxer.Net/Data/Channels/SocketLinkChannel.cs new file mode 100644 index 0000000..6d59fef --- /dev/null +++ b/Fluxer.Net/Data/Channels/SocketLinkChannel.cs @@ -0,0 +1,16 @@ +namespace Fluxer.Net; + +public class SocketLinkChannel : LinkChannel +{ + internal SocketLinkChannel(FluxerBaseClient client) : base(client) + { + + } + + public static SocketLinkChannel Create(FluxerBaseClient client, ChannelJson json) + { + var data = new SocketLinkChannel(client); + data.Update(client, json); + return data; + } +} diff --git a/Fluxer.Net/Data/Channels/SocketSavedNotesChannel.cs b/Fluxer.Net/Data/Channels/SocketSavedNotesChannel.cs new file mode 100644 index 0000000..98d5442 --- /dev/null +++ b/Fluxer.Net/Data/Channels/SocketSavedNotesChannel.cs @@ -0,0 +1,16 @@ +namespace Fluxer.Net; + +public class SocketSavedNotesChannel : SavedNotesChannel +{ + internal SocketSavedNotesChannel(FluxerBaseClient client) : base(client) + { + + } + + public static SocketSavedNotesChannel Create(FluxerBaseClient client, ChannelJson json) + { + var data = new SocketSavedNotesChannel(client); + data.Update(client, json); + return data; + } +} diff --git a/Fluxer.Net/Data/Channels/SocketTextChannel.cs b/Fluxer.Net/Data/Channels/SocketTextChannel.cs new file mode 100644 index 0000000..2c44f76 --- /dev/null +++ b/Fluxer.Net/Data/Channels/SocketTextChannel.cs @@ -0,0 +1,9 @@ +namespace Fluxer.Net; + +public class SocketTextChannel : TextChannel +{ + internal SocketTextChannel(FluxerBaseClient client) : base(client) + { + + } +} diff --git a/Fluxer.Net/Data/Channels/SocketUnknownChannel.cs b/Fluxer.Net/Data/Channels/SocketUnknownChannel.cs new file mode 100644 index 0000000..01dd8ca --- /dev/null +++ b/Fluxer.Net/Data/Channels/SocketUnknownChannel.cs @@ -0,0 +1,84 @@ +namespace Fluxer.Net; + +public class SocketUnknownChannel : Channel +{ + /// + /// Permissions for the channel. + /// + public ChannelPermissions Permissions { get; internal set; } + + internal SocketUnknownChannel(FluxerBaseClient client) : base(client) + { + + } + + public static Channel Create(FluxerBaseClient client, ChannelJson json, ulong guildId) + { + Channel data = null; + + switch (json.Type) + { + case ChannelType.GuildText: + { + data = new SocketTextChannel(client); + data.IsTextable = true; + } + break; + case ChannelType.GuildVoice: + { + data = new SocketVoiceChannel(client); + } + break; + case ChannelType.Dm: + { + data = new SocketDMChannel(client); + data.IsTextable = true; + } + break; + case ChannelType.DmPersonalNotes: + { + data = new SocketSavedNotesChannel(client); + data.IsTextable = true; + } + break; + case ChannelType.GroupDm: + { + data = new SocketGroupChannel(client); + data.IsTextable = true; + } + break; + case ChannelType.GuildCategory: + { + data = new SocketCategoryChannel(client); + } + break; + case ChannelType.GuildLink: + { + data = new SocketLinkChannel(client); + } + break; + default: + { + if (data.GuildId.HasValue) + data = new SocketUnknownGuildChannel(client); + else + data = new SocketUnknownChannel(client); + data.IsTextable = true; + } + break; + } + data.GuildId = guildId; + data.Update(client, json); + return data; + } + + internal override void Update(FluxerBaseClient client, ChannelJson json) + { + base.Update(client, json); + PermissionOverwriteJson? overwrite = json.PermissionOverwrites.FirstOrDefault(x => x.Id == Id); + if (overwrite != null) + Permissions = overwrite.Allow; + else + Permissions = new ChannelPermissions(0); + } +} \ No newline at end of file diff --git a/Fluxer.Net/Data/Channels/SocketUnknownGuildChannel.cs b/Fluxer.Net/Data/Channels/SocketUnknownGuildChannel.cs new file mode 100644 index 0000000..5627bf7 --- /dev/null +++ b/Fluxer.Net/Data/Channels/SocketUnknownGuildChannel.cs @@ -0,0 +1,9 @@ +namespace Fluxer.Net; + +public class SocketUnknownGuildChannel : GuildChannel +{ + internal SocketUnknownGuildChannel(FluxerBaseClient client) : base(client) + { + + } +} diff --git a/Fluxer.Net/Data/Channels/SocketVoiceChannel.cs b/Fluxer.Net/Data/Channels/SocketVoiceChannel.cs new file mode 100644 index 0000000..aa14312 --- /dev/null +++ b/Fluxer.Net/Data/Channels/SocketVoiceChannel.cs @@ -0,0 +1,9 @@ +namespace Fluxer.Net; + +public class SocketVoiceChannel : VoiceChannel +{ + internal SocketVoiceChannel(FluxerBaseClient client) : base(client) + { + + } +} diff --git a/Fluxer.Net/Data/Channels/VoiceChannel.cs b/Fluxer.Net/Data/Channels/VoiceChannel.cs index 8bde753..8f318ab 100644 --- a/Fluxer.Net/Data/Channels/VoiceChannel.cs +++ b/Fluxer.Net/Data/Channels/VoiceChannel.cs @@ -1,6 +1,6 @@ namespace Fluxer.Net; -internal class VoiceChannel : GuildChannel +public class VoiceChannel : GuildChannel { internal VoiceChannel(FluxerBaseClient client) : base(client) { diff --git a/Fluxer.Net/Data/Guilds/Guild.cs b/Fluxer.Net/Data/Guilds/Guild.cs index 03735c5..cd15907 100644 --- a/Fluxer.Net/Data/Guilds/Guild.cs +++ b/Fluxer.Net/Data/Guilds/Guild.cs @@ -57,7 +57,7 @@ public static Guild Create(FluxerBaseClient client, GuildJson json) return data; } - internal void Update(FluxerBaseClient client, GuildJson json) + internal virtual void Update(FluxerBaseClient client, GuildJson json) { base.Update(client, json); OwnerId = json.OwnerId; diff --git a/Fluxer.Net/Data/Guilds/Members/GuildMember.cs b/Fluxer.Net/Data/Guilds/Members/GuildMember.cs index ed6aecf..52e4f62 100644 --- a/Fluxer.Net/Data/Guilds/Members/GuildMember.cs +++ b/Fluxer.Net/Data/Guilds/Members/GuildMember.cs @@ -86,7 +86,7 @@ public static GuildMember Create(FluxerBaseClient client, GuildMemberJson json) return data; } - internal void Update(FluxerBaseClient client, GuildMemberJson json) + internal virtual void Update(FluxerBaseClient client, GuildMemberJson json) { GuildId = json.GuildId; User = User.Create(client, json.User); diff --git a/Fluxer.Net/Data/Guilds/Members/IRole.cs b/Fluxer.Net/Data/Guilds/Members/IRole.cs index 5a41c17..4b0664e 100644 --- a/Fluxer.Net/Data/Guilds/Members/IRole.cs +++ b/Fluxer.Net/Data/Guilds/Members/IRole.cs @@ -20,7 +20,7 @@ public interface IRole /// /// The role's permission bitfield. Sent as a quoted string by the gateway (e.g. "8933636165184"). /// - Permissions Permissions { get; } + GuildPermissions Permissions { get; } /// /// The position of the role in the role hierarchy. diff --git a/Fluxer.Net/Data/Guilds/Members/Role.cs b/Fluxer.Net/Data/Guilds/Members/Role.cs index e501626..7b8c3a9 100644 --- a/Fluxer.Net/Data/Guilds/Members/Role.cs +++ b/Fluxer.Net/Data/Guilds/Members/Role.cs @@ -16,7 +16,7 @@ public class Role : Entity, IRole public string Name { get; internal set; } /// - public Permissions Permissions { get; internal set; } + public GuildPermissions Permissions { get; internal set; } /// public int Position { get; internal set; } @@ -46,7 +46,7 @@ public static Role Create(FluxerBaseClient client, RoleJson json, ulong guildId) return data; } - internal void Update(FluxerBaseClient client, RoleJson json) + internal virtual void Update(FluxerBaseClient client, RoleJson json) { Id = json.Id; Name = json.Name; diff --git a/Fluxer.Net/Data/Guilds/Members/RoleJson.cs b/Fluxer.Net/Data/Guilds/Members/RoleJson.cs index df65c6f..a2ea468 100644 --- a/Fluxer.Net/Data/Guilds/Members/RoleJson.cs +++ b/Fluxer.Net/Data/Guilds/Members/RoleJson.cs @@ -19,7 +19,7 @@ public class RoleJson : IRole /// [JsonProperty("permissions")] - public Permissions Permissions { get; set; } + public GuildPermissions Permissions { get; set; } /// [JsonProperty("position")] diff --git a/Fluxer.Net/Data/Guilds/Members/SocketGuildMember.cs b/Fluxer.Net/Data/Guilds/Members/SocketGuildMember.cs index 5934942..9291664 100644 --- a/Fluxer.Net/Data/Guilds/Members/SocketGuildMember.cs +++ b/Fluxer.Net/Data/Guilds/Members/SocketGuildMember.cs @@ -4,6 +4,20 @@ public class SocketGuildMember : GuildMember { public SocketGuild Guild { get; internal set; } + public IEnumerable Roles + => RoleIds.Select(id => Guild.Roles[id]).Where(x => x != null); + + public bool HasPermission(Permissions permission) + { + foreach (var r in Roles) + { + if (r.Permissions.RawValue.HasFlag(permission)) + return true; + } + + return false; + } + internal SocketGuildMember(FluxerBaseClient client) : base(client) { @@ -16,7 +30,7 @@ public static SocketGuildMember Create(FluxerBaseClient client, GuildMemberJson return data; } - internal void Update(FluxerBaseClient client, GuildMemberJson json) + internal override void Update(FluxerBaseClient client, GuildMemberJson json) { base.Update(client, json); } diff --git a/Fluxer.Net/Data/Guilds/Members/SocketRole.cs b/Fluxer.Net/Data/Guilds/Members/SocketRole.cs index 9ace1c0..6b974f9 100644 --- a/Fluxer.Net/Data/Guilds/Members/SocketRole.cs +++ b/Fluxer.Net/Data/Guilds/Members/SocketRole.cs @@ -2,20 +2,34 @@ public class SocketRole : Role { + public SocketGuild Guild { get; internal set; } + + public bool HasPermission(Permissions permission) + { + if (Permissions.RawValue.HasFlag(permission)) + return true; + + if (Guild.Permissions.RawValue.HasFlag(permission)) + return true; + + return false; + } + internal SocketRole(FluxerBaseClient client) : base(client) { } - public static SocketRole Create(FluxerBaseClient client, RoleJson json, ulong guildId) + public static SocketRole Create(FluxerBaseClient client, RoleJson json, SocketGuild guild) { SocketRole data = new SocketRole(client); - data.GuildId = guildId; + data.GuildId = guild.Id; + data.Guild = guild; data.Update(client, json); return data; } - internal void Update(FluxerBaseClient client, RoleJson json) + internal override void Update(FluxerBaseClient client, RoleJson json) { base.Update(client, json); } diff --git a/Fluxer.Net/Data/Guilds/SocketGuild.cs b/Fluxer.Net/Data/Guilds/SocketGuild.cs index 9ff63d2..6af7ffe 100644 --- a/Fluxer.Net/Data/Guilds/SocketGuild.cs +++ b/Fluxer.Net/Data/Guilds/SocketGuild.cs @@ -1,15 +1,42 @@ -namespace Fluxer.Net; +using System.Collections.Concurrent; + +namespace Fluxer.Net; /// /// Cached guild from the gateway. /// public class SocketGuild : Guild { + internal TaskCompletionSource? _downloaderPromise; + /// /// Cached current logged in member for the guild. /// public SocketGuildMember CurrentMember { get; internal set; } + public SocketRole EveryoneRole => Roles.GetValueOrDefault(Id); + + public bool HasAllMembers { get; internal set; } + + public ConcurrentDictionary Members { get; internal set; } = new ConcurrentDictionary(); + public ConcurrentDictionary Channels { get; internal set; } = new ConcurrentDictionary(); + public ConcurrentDictionary Roles { get; internal set; } = new ConcurrentDictionary(); + + public SocketGuildMember? GetMember(ulong userId) + { + return Members.GetValueOrDefault(userId); + } + + public SocketRole? GetRole(ulong roleId) + { + return Roles.GetValueOrDefault(roleId); + } + + public Channel? GetChannel(ulong channelId) + { + return Channels.GetValueOrDefault(channelId); + } + /// /// Permissions for the guild. /// @@ -24,18 +51,27 @@ public static SocketGuild Create(FluxerBaseClient client, GuildJson json, Socket { SocketGuild data = new SocketGuild(client); data.CurrentMember = member; + data.Members.TryAdd(member.UserId, member); data.CurrentMember.Guild = data; data.Update(client, json); return data; } - internal void Update(FluxerBaseClient client, GuildJson json) + internal override void Update(FluxerBaseClient client, GuildJson json) { base.Update(client, json); } internal void UpdatePermissions(SocketRole role) { - Permissions = new GuildPermissions(role.Permissions); + Permissions = role.Permissions; + } + + internal void AddOrUpdate(FluxerClient client, GuildMemberJson member) + { + var mem = SocketGuildMember.Create(client, member); + mem.Guild = this; + if (!Members.TryAdd(member.UserId, mem)) + Members[member.UserId].Update(client, member); } } \ No newline at end of file diff --git a/Fluxer.Net/Extensions/ChannelPermissionsConverter.cs b/Fluxer.Net/Extensions/ChannelPermissionsConverter.cs new file mode 100644 index 0000000..dfb0431 --- /dev/null +++ b/Fluxer.Net/Extensions/ChannelPermissionsConverter.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace Fluxer.Net.Extensions; + +public class ChannelPermissionsConverter : JsonConverter +{ + public override ChannelPermissions ReadJson(JsonReader reader, Type objectType, ChannelPermissions GuildPermissions, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.Value == null) + return new ChannelPermissions(0); + + if (reader.TokenType == JsonToken.String) + return new ChannelPermissions((Permissions)ulong.Parse((string)reader.Value)); + + if (reader.TokenType == JsonToken.Integer) + return new ChannelPermissions((Permissions)Convert.ToUInt64(reader.Value)); + + throw new JsonSerializationException( + $"Unexpected token type '{reader.TokenType}' when deserializing GuildPermissions at path '{reader.Path}'."); + } + + public override void WriteJson(JsonWriter writer, ChannelPermissions value, JsonSerializer serializer) + => writer.WriteValue((ulong)value.RawValue); +} \ No newline at end of file diff --git a/Fluxer.Net/Extensions/GuildPermissionsConverter.cs b/Fluxer.Net/Extensions/GuildPermissionsConverter.cs new file mode 100644 index 0000000..c8f4d76 --- /dev/null +++ b/Fluxer.Net/Extensions/GuildPermissionsConverter.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace Fluxer.Net.Extensions; + +public class GuildPermissionsConverter : JsonConverter +{ + public override GuildPermissions ReadJson(JsonReader reader, Type objectType, GuildPermissions GuildPermissions, bool hasExistingValue, JsonSerializer serializer) + { + if (reader.Value == null) + return new GuildPermissions(0); + + if (reader.TokenType == JsonToken.String) + return new GuildPermissions((Permissions)ulong.Parse((string)reader.Value)); + + if (reader.TokenType == JsonToken.Integer) + return new GuildPermissions((Permissions)Convert.ToUInt64(reader.Value)); + + throw new JsonSerializationException( + $"Unexpected token type '{reader.TokenType}' when deserializing GuildPermissions at path '{reader.Path}'."); + } + + public override void WriteJson(JsonWriter writer, GuildPermissions value, JsonSerializer serializer) + => writer.WriteValue((ulong)value.RawValue); +} diff --git a/Fluxer.Net/FluxerClient.cs b/Fluxer.Net/FluxerClient.cs index 68d340f..044c90e 100644 --- a/Fluxer.Net/FluxerClient.cs +++ b/Fluxer.Net/FluxerClient.cs @@ -66,12 +66,6 @@ public FluxerClient(string token, FluxerConfig? config = null) internal static JsonSerializer _serializer { get; set; } = CreateGatewaySerializer(); - // Used by both api and gateway - internal static JsonSerializerSettings _serializerSettings { get; set; } = new JsonSerializerSettings() - { - NullValueHandling = NullValueHandling.Ignore - }; - internal static JsonSerializer CreateGatewaySerializer() { var serializer = new JsonSerializer @@ -79,9 +73,26 @@ internal static JsonSerializer CreateGatewaySerializer() NullValueHandling = NullValueHandling.Ignore }; serializer.Converters.Add(new StringUInt64Converter()); + serializer.Converters.Add(new GuildPermissionsConverter()); + serializer.Converters.Add(new ChannelPermissionsConverter()); return serializer; } + // Used by both api and gateway + internal static JsonSerializerSettings _serializerSettings { get; set; } = CreateRestSerializer(); + + internal static JsonSerializerSettings CreateRestSerializer() + { + var serializer = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore + }; + serializer.Converters.Add(new GuildPermissionsConverter()); + serializer.Converters.Add(new ChannelPermissionsConverter()); + return serializer; + } + + /// /// Validates that the token has a recognized prefix for the Fluxer API. /// diff --git a/Fluxer.Net/FluxerNet.xml b/Fluxer.Net/FluxerNet.xml index 10bfafd..c6aedfe 100644 --- a/Fluxer.Net/FluxerNet.xml +++ b/Fluxer.Net/FluxerNet.xml @@ -1314,7 +1314,7 @@ - + Permissions for the channel. @@ -7255,6 +7255,11 @@ Useful for managing event load in bots that are in many guilds. + + + The guild ID. + + WebSocket gateway client for real-time events from the Fluxer platform. diff --git a/Fluxer.Net/Gateway/Data/Guilds/GuildMembersChunkGatewayData.cs b/Fluxer.Net/Gateway/Data/Guilds/GuildMembersChunkGatewayData.cs new file mode 100644 index 0000000..3831adc --- /dev/null +++ b/Fluxer.Net/Gateway/Data/Guilds/GuildMembersChunkGatewayData.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace Fluxer.Net.Gateway.Data.Guilds; + +public class GuildMembersChunkGatewayData +{ + [JsonProperty("chunk_count")] + public int ChunkCount { get; set; } + + [JsonProperty("chunk_index")] + public int ChunkIndex { get; set; } + + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + + [JsonProperty("members")] + public GuildMemberJson[] Members { get; set; } +} diff --git a/Fluxer.Net/Gateway/GatewayClient.cs b/Fluxer.Net/Gateway/GatewayClient.cs index 072bdad..fb150df 100644 --- a/Fluxer.Net/Gateway/GatewayClient.cs +++ b/Fluxer.Net/Gateway/GatewayClient.cs @@ -13,6 +13,7 @@ using Newtonsoft.Json.Linq; using Serilog.Core; using System.Collections.Concurrent; +using System.Data; using System.Diagnostics; using System.Reflection; using System.Text.RegularExpressions; @@ -102,10 +103,32 @@ internal GatewayClient(FluxerClient client) #region Cache public CurrentUser? CurrentUser { get; internal set; } - public ConcurrentDictionary Guilds = new ConcurrentDictionary(); - public ConcurrentDictionary Channels = new ConcurrentDictionary(); - public ConcurrentDictionary Roles = new ConcurrentDictionary(); - public ConcurrentDictionary CurrentMembers = new ConcurrentDictionary(); + public ConcurrentDictionary Guilds { get; internal set; } = new ConcurrentDictionary(); + public ConcurrentDictionary Channels { get; internal set; } = new ConcurrentDictionary(); + public ConcurrentDictionary Roles { get; internal set; } = new ConcurrentDictionary(); + public ConcurrentDictionary CurrentMembers { get; internal set; } = new ConcurrentDictionary(); + public RtcRegion[] RtcRegions; + + public SocketGuildMember? GetCurrentMember(ulong userId) + { + return CurrentMembers.GetValueOrDefault(userId); + } + + public SocketGuild? GetGuild(ulong guildId) + { + return Guilds.GetValueOrDefault(guildId); + } + + public SocketRole? GetRole(ulong roleId) + { + return Roles.GetValueOrDefault(roleId); + } + + public Channel? GetChannel(ulong channelId) + { + return Channels.GetValueOrDefault(channelId); + } + #endregion #region Gateway @@ -228,6 +251,29 @@ public async Task ConnectAsync() } } + public async Task DownloadMembersAsync(SocketGuild guild) + { + if (guild._downloaderPromise != null) + return await guild._downloaderPromise.Task; + + guild._downloaderPromise = new TaskCompletionSource(); + + CancellationToken token = CancellationToken.None; + GatewayPacket login = new GatewayPacket + { + OpCode = FluxerOpCode.RequestGuildMembers, + Data = JToken.FromObject(new RequestMembersPacket + { + GuildId = guild.Id.ToString() + }) + }; + SendGatewayPacket(login); + + await Task.WhenAny(guild._downloaderPromise.Task, Task.Delay(new TimeSpan(0, 0, 30))); + + return guild._downloaderPromise.Task.Result; + } + /// /// Sends a gateway packet to the Fluxer WebSocket server. /// @@ -462,23 +508,36 @@ private void HandleDispatch(GatewayPacket p) if (data != null) { CurrentUser = CurrentUser.Create(_client, data.User); + + _sessionId = data.SessionId; + _isConnecting = false; // Connection successfully established + _reconnectAttemptCount = 0; // Reset backoff counter + GuildIds = data.Guilds.Select(x => x.Id).ToHashSet(); Channels.Clear(); Roles.Clear(); CurrentMembers.Clear(); Guilds.Clear(); + RtcRegions = data.RtcRegions.Select(x => RtcRegion.Create(_client, x)).ToArray(); if (data.Guilds != null) { - foreach (var g in data.Guilds) + foreach (GuildGatewayData g in data.Guilds) { CurrentMembers.TryAdd(g.Id, SocketGuildMember.Create(_client, g.Members.First(x => x.UserId == CurrentUser.Id))); - Guilds.TryAdd(g.Id, SocketGuild.Create(_client, g.Properties, CurrentMembers[g.Id])); + SocketGuild guild = SocketGuild.Create(_client, g.Properties, CurrentMembers[g.Id]); + foreach (Gateway.Data.Guilds.GuildMemberGatewayData m in g.Members) + { + if (m.UserId != CurrentUser.Id) + { + SocketGuildMember member = SocketGuildMember.Create(_client, m); + member.Guild = guild; + guild.Members.TryAdd(m.UserId, member); + } + } + Guilds.TryAdd(g.Id, guild); } } - _sessionId = data.SessionId; - _isConnecting = false; // Connection successfully established - _reconnectAttemptCount = 0; // Reset backoff counter _logger.Information("Connection established successfully with session ID: {SessionId}", _sessionId); Ready?.Invoke(data); } @@ -592,7 +651,15 @@ private void HandleDispatch(GatewayPacket p) ChannelGatewayData? data = p.Data.ToObject(FluxerClient._serializer); if (data != null) { - Channels.TryAdd(data.Id, SocketChannel.Create(_client, data, data.GuildId.Value)); + Channel channel = SocketUnknownChannel.Create(_client, data, data.GuildId.Value); + if (!Channels.TryAdd(channel.Id, channel)) + { + channel = Channels[channel.Id]; + channel.Update(_client, data); + } + if (channel.GuildId.HasValue && Guilds.TryGetValue(channel.GuildId.Value, out var guild)) + guild.Channels.TryAdd(channel.Id, channel); + ChannelCreate?.Invoke(data); } else @@ -604,8 +671,9 @@ private void HandleDispatch(GatewayPacket p) ChannelGatewayData? data = p.Data.ToObject(FluxerClient._serializer); if (data != null) { - if (Channels.TryGetValue(data.Id, out SocketChannel channel)) + if (Channels.TryGetValue(data.Id, out Channel channel)) channel.Update(_client, data); + ChannelUpdate?.Invoke(data); } else @@ -855,20 +923,50 @@ private void HandleDispatch(GatewayPacket p) GuildIds.Add(data.Id); if (!data.Unavailable.GetValueOrDefault()) { - SocketGuildMember member = SocketGuildMember.Create(_client, data.Members.First(x => x.UserId == CurrentUser.Id)); + Gateway.Data.Guilds.GuildMemberGatewayData json = data.Members.First(x => x.UserId == CurrentUser.Id); + SocketGuildMember member = SocketGuildMember.Create(_client, json); + + // Add or update current member + if (!CurrentMembers.TryAdd(data.Id, member)) + { + member = CurrentMembers[data.Id]; + member.Update(_client, json); + } + SocketGuild guild = SocketGuild.Create(_client, data.Properties, member); - if (Guilds.TryAdd(data.Id, guild)) - CurrentMembers.TryAdd(data.Id, member); - foreach (ChannelGatewayData c in data.Channels) + // Ad or update guild + if (!Guilds.TryAdd(data.Id, guild)) { - Channels.TryAdd(c.Id, SocketChannel.Create(_client, c, data.Id)); + guild = Guilds[data.Id]; + guild.Update(_client, data.Properties); } + + // Add or update roles foreach (RoleJson r in data.Roles) { - Roles.TryAdd(r.Id, SocketRole.Create(_client, r, data.Id)); + var role = SocketRole.Create(_client, r, guild); + if (!Roles.TryAdd(r.Id, role)) + { + role = Roles[r.Id]; + role.Update(_client, r); + } + + guild.Roles.TryAdd(r.Id, role); } guild.UpdatePermissions(Roles[data.Id]); + + // Add or update channels + foreach (ChannelGatewayData c in data.Channels) + { + var channel = SocketUnknownChannel.Create(_client, c, data.Id); + if (!Channels.TryAdd(c.Id, channel)) + { + channel = Channels[c.Id]; + channel.Update(_client, c); + } + guild.Channels.TryAdd(c.Id, channel); + } } GuildCreate?.Invoke(data); @@ -884,6 +982,7 @@ private void HandleDispatch(GatewayPacket p) { if (Guilds.TryGetValue(data.Id, out SocketGuild guild)) guild.Update(_client, data.Properties); + GuildUpdate?.Invoke(data); } else @@ -904,15 +1003,13 @@ private void HandleDispatch(GatewayPacket p) { Guilds.TryRemove(data.Id, out _); CurrentMembers.TryRemove(data.Id, out _); - foreach (SocketChannel c in Channels.Values) + foreach (SocketUnknownChannel c in Channels.Values.Where(x => x.GuildId == data.Id)) { - if (c.GuildId == data.Id) - Channels.TryRemove(c.Id, out _); + Channels.TryRemove(c.Id, out _); } - foreach (SocketRole r in Roles.Values) + foreach (SocketRole r in Roles.Values.Where(x => x.GuildId == data.Id)) { - if (r.GuildId == data.Id) - Roles.TryRemove(r.Id, out _); + Roles.TryRemove(r.Id, out _); } GuildIds.Remove(data.Id); GuildDelete?.Invoke(data); @@ -926,7 +1023,12 @@ private void HandleDispatch(GatewayPacket p) { Gateway.Data.Guilds.GuildMemberGatewayData? data = p.Data.ToObject(FluxerClient._serializer); if (data != null) + { + if (Guilds.TryGetValue(data.GuildId, out SocketGuild guild)) + guild.AddOrUpdate(_client, data); + GuildMemberAdd?.Invoke(data); + } else _logger.Warning("GUILD_MEMBER_ADD event received but data could not be cast to GuildMemberGatewayData"); } @@ -936,7 +1038,7 @@ private void HandleDispatch(GatewayPacket p) Gateway.Data.Guilds.GuildMemberGatewayData? data = p.Data.ToObject(FluxerClient._serializer); if (data != null) { - if (CurrentMembers.TryGetValue(data.GuildId, out SocketGuildMember member) && member.UserId == data.UserId) + if (Guilds.TryGetValue(data.GuildId, out SocketGuild guild) && guild.Members.TryGetValue(data.UserId, out var member)) member.Update(_client, data); GuildMemberUpdate?.Invoke(data); @@ -949,17 +1051,55 @@ private void HandleDispatch(GatewayPacket p) { EntityRemovedGatewayData? data = p.Data.ToObject(FluxerClient._serializer); if (data != null) + { + if (Guilds.TryGetValue(data.GuildId.Value, out SocketGuild guild)) + guild.Members.TryRemove(data.Id.Value, out _); + GuildMemberRemove?.Invoke(data); + } else _logger.Warning("GUILD_MEMBER_REMOVE event received but data could not be cast to EntityRemovedGatewayData"); } return; + case "GUILD_MEMBERS_CHUNK": + { + GuildMembersChunkGatewayData? data = p.Data.ToObject(FluxerClient._serializer); + if (data != null) + { + if (Guilds.TryGetValue(data.GuildId, out var guild)) + { + foreach (var m in data.Members) + { + guild.AddOrUpdate(_client, m); + } + if ((data.ChunkIndex + 1) == data.ChunkCount) + { + guild.HasAllMembers = true; + if (guild._downloaderPromise != null) + guild._downloaderPromise.SetResult(true); + } + } + } + else + _logger.Warning("GUILD_MEMBERS_CHUNK event received but data could not be cast to GuildMembersChunkGatewayData"); + } + return; case "GUILD_ROLE_CREATE": { GuildRoleGatewayData? data = p.Data.ToObject(FluxerClient._serializer); if (data != null) { - Roles.TryAdd(data.Role.Id, SocketRole.Create(_client, data.Role, data.GuildId)); + if (Guilds.TryGetValue(data.GuildId, out var guild)) + { + SocketRole role = SocketRole.Create(_client, data.Role, guild); + if (!Roles.TryAdd(data.Role.Id, role)) + { + role = Roles[data.Role.Id]; + role.Update(_client, data.Role); + } + } + + GuildRoleCreate?.Invoke(data); } else @@ -972,11 +1112,11 @@ private void HandleDispatch(GatewayPacket p) if (data != null) { if (Roles.TryGetValue(data.Role.Id, out SocketRole role)) - { role.Update(_client, data.Role); - if (Guilds.TryGetValue(role.Id, out SocketGuild guild)) - guild.UpdatePermissions(role); - } + + if (data.Role.Id == data.GuildId && Guilds.TryGetValue(data.GuildId, out SocketGuild guild)) + guild.UpdatePermissions(role); + GuildRoleUpdate?.Invoke(data); } else @@ -989,6 +1129,9 @@ private void HandleDispatch(GatewayPacket p) if (data != null) { Roles.TryRemove(data.RoleId, out _); + if (Guilds.TryGetValue(data.GuildId, out SocketGuild guild)) + guild.Roles.TryRemove(data.RoleId, out _); + GuildRoleDelete?.Invoke(data); } else @@ -999,7 +1142,17 @@ private void HandleDispatch(GatewayPacket p) { GuildRoleUpdateBulkGatewayData? data = p.Data.ToObject(FluxerClient._serializer); if (data != null) + { + foreach (var r in data.Roles) + { + if (Roles.TryGetValue(r.Id, out SocketRole role)) + role.Update(_client, r); + + if (r.Id == data.GuildId && Guilds.TryGetValue(data.GuildId, out SocketGuild guild)) + guild.UpdatePermissions(role); + } GuildRoleUpdateBulk?.Invoke(data); + } else _logger.Warning("GUILD_ROLE_UPDATE_BULK event received but data could not be cast to GuildRoleUpdateBulkGatewayData"); } diff --git a/Fluxer.Net/Gateway/Packets/RequestMembersPacket.cs b/Fluxer.Net/Gateway/Packets/RequestMembersPacket.cs new file mode 100644 index 0000000..945d6ed --- /dev/null +++ b/Fluxer.Net/Gateway/Packets/RequestMembersPacket.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace Fluxer.Net.Gateway.Packets; + +public class RequestMembersPacket +{ + /// + /// The guild ID. + /// + [JsonProperty("guild_id")] + public string GuildId { get; set; } + + [JsonProperty("query")] + public string Query { get; set; } = ""; + + [JsonProperty("presences")] + public bool Presences { get; set; } = false; + + [JsonProperty("limit")] + public int Limit { get; set; } = 0; +}