diff --git a/Fluxer.Net.Example/Modules/BasicCommands.cs b/Fluxer.Net.Example/Modules/BasicCommands.cs index add8d8a..6866507 100644 --- a/Fluxer.Net.Example/Modules/BasicCommands.cs +++ b/Fluxer.Net.Example/Modules/BasicCommands.cs @@ -81,7 +81,7 @@ public async Task EmbedCommand() .WithCurrentTimestamp() .Build(); - await Context.Rest.SendMessageAsync(Context.ChannelId, "Here's an example of a rich embed:", + await Context.Rest.SendMessageAsync(Context.Channel.Id, "Here's an example of a rich embed:", embeds: new List { embed } ); } diff --git a/Fluxer.Net/Commands/Attributes/RequireContextAttribute.cs b/Fluxer.Net/Commands/Attributes/RequireContextAttribute.cs index b8eca7b..be6b321 100644 --- a/Fluxer.Net/Commands/Attributes/RequireContextAttribute.cs +++ b/Fluxer.Net/Commands/Attributes/RequireContextAttribute.cs @@ -51,13 +51,13 @@ public override Task CheckPermissionsAsync( { bool isValid = false; - if (Contexts.HasFlag(ContextType.Guild) && context.GuildId.HasValue) + if (Contexts.HasFlag(ContextType.Guild) && context.Guild != null) isValid = true; - if (Contexts.HasFlag(ContextType.DM) && !context.GuildId.HasValue) + if (Contexts.HasFlag(ContextType.DM) && context.Guild == null) isValid = true; - if (Contexts.HasFlag(ContextType.Group) && context.Message.ChannelType == ChannelType.GroupDm) + if (Contexts.HasFlag(ContextType.Group) && context.Channel.Type == ChannelType.GroupDm) isValid = true; if (isValid) diff --git a/Fluxer.Net/Commands/CommandContext.cs b/Fluxer.Net/Commands/CommandContext.cs index 8f01aa4..7307eb0 100644 --- a/Fluxer.Net/Commands/CommandContext.cs +++ b/Fluxer.Net/Commands/CommandContext.cs @@ -10,37 +10,42 @@ public class CommandContext /// /// Gets the API client. /// - public ApiClient Rest { get; } + public FluxerClient Client { get; } + + /// + /// Gets the API client. + /// + public ApiClient Rest => Client.Rest; /// /// Gets the gateway client. /// - public GatewayClient Gateway { get; } + public GatewayClient Gateway => Client.Gateway; /// /// Gets the message that triggered the command. /// - public MessageGatewayData Message { get; } + public SocketMessage Message { get; } /// /// Gets the channel the command was executed in. /// - public ulong ChannelId => Message.ChannelId; + public Channel Channel => Message.Channel; /// /// Gets the guild the command was executed in, if any. /// - public ulong? GuildId => Message.GuildId; + public SocketGuild? Guild { get; } /// /// Gets the user who executed the command. /// - public User User { get; internal set; } + public SocketUser User { get; internal set; } /// /// Gets the member who executed the command. /// - public GuildMemberGatewayData? Member => Message.Member; + public SocketGuildMember? Member { get; } /// /// Creates a new command context. @@ -49,9 +54,23 @@ public class CommandContext /// The message that triggered the command. public CommandContext(FluxerClient client, MessageGatewayData message) { - User = User.Create(client, message.Author); - Rest = client.Rest; - Gateway = client.Gateway; - Message = message; + Client = client; + Message = SocketMessage.Create(client, message); + User = SocketUser.Create(client, message.Author); + if (message.GuildId.HasValue) + { + + Guild = client.Gateway.GetGuild(message.GuildId.Value); + Member = Guild.GetMember(User.Id); + if (Member == null) + { + message.Member.User = message.Author; + Guild.AddOrUpdateMember(Client, message.Member); + Member = Guild.GetMember(User.Id); + } + + if (Member.Guild == null) + Member.Guild = Guild; + } } } diff --git a/Fluxer.Net/Commands/ModuleBase.cs b/Fluxer.Net/Commands/ModuleBase.cs index 3d4785c..31e888f 100644 --- a/Fluxer.Net/Commands/ModuleBase.cs +++ b/Fluxer.Net/Commands/ModuleBase.cs @@ -34,7 +34,7 @@ protected async Task ReplyAsync(string? content = null, List? stickerIds = null, List? attachments = null) { - return await Context.Rest.SendMessageAsync(Context.ChannelId, content, embeds, reference, allowedMentions, flags, nonce, favoruteMemeId, tts, stickerIds, attachments); + return await Context.Rest.SendMessageAsync(Context.Channel.Id, content, embeds, reference, allowedMentions, flags, nonce, favoruteMemeId, tts, stickerIds, attachments); } /// @@ -42,7 +42,7 @@ protected async Task ReplyAsync(string? content = null, List protected async Task ReplyAsync(List attachments, string? content = null) { - return await Context.Rest.SendMessageAsync(Context.ChannelId, content, attachments: attachments); + return await Context.Rest.SendMessageAsync(Context.Channel.Id, content, attachments: attachments); } } diff --git a/Fluxer.Net/Data/Expressions/GuildEmoji.cs b/Fluxer.Net/Data/Expressions/GuildEmoji.cs index 7c1bccf..bc12f57 100644 --- a/Fluxer.Net/Data/Expressions/GuildEmoji.cs +++ b/Fluxer.Net/Data/Expressions/GuildEmoji.cs @@ -12,6 +12,12 @@ public class GuildEmoji : Emoji, IGuildEmoji [JsonProperty("user")] public User? Creator { get; set; } + /// + public string? GetEmojiUrl(int size = 160) + { + return $"https://fluxerusercontent.com/emojis/{Id}.webp?size={size}"; + } + IUser? IGuildEmoji.Creator => Creator; internal GuildEmoji(FluxerBaseClient client) : base(client) diff --git a/Fluxer.Net/Data/Expressions/GuildEmojiJson.cs b/Fluxer.Net/Data/Expressions/GuildEmojiJson.cs index 3e8bda3..93723ff 100644 --- a/Fluxer.Net/Data/Expressions/GuildEmojiJson.cs +++ b/Fluxer.Net/Data/Expressions/GuildEmojiJson.cs @@ -9,5 +9,11 @@ public class GuildEmojiJson : EmojiJson, IGuildEmoji [JsonProperty("user")] public UserJson? Creator { get; set; } + /// + public string? GetEmojiUrl(int size = 160) + { + return $"https://fluxerusercontent.com/emojis/{Id}.webp?size={size}"; + } + IUser? IGuildEmoji.Creator => Creator; } diff --git a/Fluxer.Net/Data/Expressions/GuildSticker.cs b/Fluxer.Net/Data/Expressions/GuildSticker.cs index d7fed77..03155da 100644 --- a/Fluxer.Net/Data/Expressions/GuildSticker.cs +++ b/Fluxer.Net/Data/Expressions/GuildSticker.cs @@ -15,6 +15,12 @@ public class GuildSticker : Sticker, IGuildSticker /// public User? Creator { get; internal set; } + /// + public string? GetStickerUrl(int size = 320) + { + return $"https://fluxerusercontent.com/stickers/{Id}.webp?size={size}"; + } + IUser? IGuildSticker.Creator => Creator; internal GuildSticker(FluxerBaseClient client) : base(client) diff --git a/Fluxer.Net/Data/Expressions/GuildStickerJson.cs b/Fluxer.Net/Data/Expressions/GuildStickerJson.cs index b55b9c0..e99b8b0 100644 --- a/Fluxer.Net/Data/Expressions/GuildStickerJson.cs +++ b/Fluxer.Net/Data/Expressions/GuildStickerJson.cs @@ -17,5 +17,11 @@ public class GuildStickerJson : StickerJson, IGuildSticker [JsonProperty("user")] public UserJson? Creator { get; set; } + /// + public string? GetStickerUrl(int size = 320) + { + return $"https://fluxerusercontent.com/stickers/{Id}.webp?size={size}"; + } + IUser? IGuildSticker.Creator => Creator; } diff --git a/Fluxer.Net/Data/Expressions/IGuildEmoji.cs b/Fluxer.Net/Data/Expressions/IGuildEmoji.cs index 8ef257d..6a7411a 100644 --- a/Fluxer.Net/Data/Expressions/IGuildEmoji.cs +++ b/Fluxer.Net/Data/Expressions/IGuildEmoji.cs @@ -6,4 +6,9 @@ public interface IGuildEmoji : IEmoji /// The user that created the emoji. /// IUser? Creator { get; } + + /// + /// Get the emoji's image. + /// + string GetEmojiUrl(int size); } diff --git a/Fluxer.Net/Data/Expressions/IGuildSticker.cs b/Fluxer.Net/Data/Expressions/IGuildSticker.cs index 2ec6041..2ffd675 100644 --- a/Fluxer.Net/Data/Expressions/IGuildSticker.cs +++ b/Fluxer.Net/Data/Expressions/IGuildSticker.cs @@ -16,4 +16,9 @@ public interface IGuildSticker : ISticker /// The user that created the sticker. /// IUser? Creator { get; } + + /// + /// Get the sticker's image. + /// + string GetStickerUrl(int size); } diff --git a/Fluxer.Net/Data/Guilds/IPartialGuild.cs b/Fluxer.Net/Data/Guilds/IPartialGuild.cs index db92b51..323e616 100644 --- a/Fluxer.Net/Data/Guilds/IPartialGuild.cs +++ b/Fluxer.Net/Data/Guilds/IPartialGuild.cs @@ -50,17 +50,17 @@ public interface IPartialGuild /// /// The hash of the guild splash screen. /// - string? SplashHash { get; } + string? InviteSplashHash { get; } /// /// The width of the guild splash in pixels. /// - int? SplashWidth { get; } + int? InviteSplashWidth { get; } /// /// The height of the guild splash in pixels /// - int? SplashHeight { get; } + int? InviteSplashHeight { get; } /// /// The alignment of the splash card. @@ -77,4 +77,24 @@ public interface IPartialGuild /// UNAVAILABLE_FOR_EVERYONE_BUT_STAFF, VISIONARY, OPERATOR, LARGE_GUILD_OVERRIDE, VERY_LARGE_GUILD (other values allowed) /// string[]? Features { get; } + + /// + /// Get the guild's icon. + /// + string? GetIconUrl(int size); + + /// + /// Get the guild's banner. + /// + string? GetBannerUrl(int size); + + /// + /// Get the guild's invite splash. + /// + string? GetInviteSplashUrl(int size); + + /// + /// Get the guild's embed splash. + /// + string? GetEmbedSplashUrl(int size); } diff --git a/Fluxer.Net/Data/Guilds/Members/GuildMember.cs b/Fluxer.Net/Data/Guilds/Members/GuildMember.cs index 52e4f62..37190bd 100644 --- a/Fluxer.Net/Data/Guilds/Members/GuildMember.cs +++ b/Fluxer.Net/Data/Guilds/Members/GuildMember.cs @@ -72,6 +72,15 @@ public string GetAvatarOrDefaultUrl(int size = 160) return GetAvatarUrl(size); } + /// + public string? GetBannerUrl(int size = 1024) + { + if (string.IsNullOrEmpty(BannerHash)) + return null; + + return $"https://fluxerusercontent.com/guilds/{GuildId}/users/{UserId}/banners/{BannerHash}.webp?size={size}"; + } + IUser IGuildMember.User => User; internal GuildMember(FluxerBaseClient client) : base(client) diff --git a/Fluxer.Net/Data/Guilds/Members/GuildMemberJson.cs b/Fluxer.Net/Data/Guilds/Members/GuildMemberJson.cs index 25fbc9c..d11c4bf 100644 --- a/Fluxer.Net/Data/Guilds/Members/GuildMemberJson.cs +++ b/Fluxer.Net/Data/Guilds/Members/GuildMemberJson.cs @@ -75,7 +75,7 @@ public string GetDefaultAvatarUrl() if (string.IsNullOrEmpty(AvatarHash)) return User.GetAvatarUrl(); - return $"https://fluxerusercontent.com/avatars/{UserId}/{AvatarHash}.png?size={size}"; + return $"https://fluxerusercontent.com/guilds/{GuildId}/users/{UserId}/avatars/{AvatarHash}.png?size={size}"; } /// @@ -87,6 +87,14 @@ public string GetAvatarOrDefaultUrl(int size = 160) return GetAvatarUrl(size); } - IUser IGuildMember.User => User; + /// + public string? GetBannerUrl(int size = 1024) + { + if (string.IsNullOrEmpty(BannerHash)) + return null; + return $"https://fluxerusercontent.com/guilds/{GuildId}/users/{UserId}/banners/{BannerHash}.webp?size={size}"; + } + + IUser IGuildMember.User => User; } diff --git a/Fluxer.Net/Data/Guilds/Members/IGuildMember.cs b/Fluxer.Net/Data/Guilds/Members/IGuildMember.cs index 8fb0fdf..ea5cfb6 100644 --- a/Fluxer.Net/Data/Guilds/Members/IGuildMember.cs +++ b/Fluxer.Net/Data/Guilds/Members/IGuildMember.cs @@ -86,4 +86,9 @@ public interface IGuildMember /// Get the members's avatar or fallback to default. /// string GetAvatarOrDefaultUrl(int size); + + /// + /// Get the member's banner. + /// + string? GetBannerUrl(int size); } diff --git a/Fluxer.Net/Data/Guilds/Members/SocketGuildMember.cs b/Fluxer.Net/Data/Guilds/Members/SocketGuildMember.cs index 9291664..dc89a3c 100644 --- a/Fluxer.Net/Data/Guilds/Members/SocketGuildMember.cs +++ b/Fluxer.Net/Data/Guilds/Members/SocketGuildMember.cs @@ -1,9 +1,13 @@ -namespace Fluxer.Net; +using System.Collections.Concurrent; + +namespace Fluxer.Net; public class SocketGuildMember : GuildMember { public SocketGuild Guild { get; internal set; } + public ConcurrentDictionary VoiceStates { get; internal set; } = new ConcurrentDictionary(); + public IEnumerable Roles => RoleIds.Select(id => Guild.Roles[id]).Where(x => x != null); diff --git a/Fluxer.Net/Data/Guilds/PartialGuild.cs b/Fluxer.Net/Data/Guilds/PartialGuild.cs index 189a890..c0a7bc9 100644 --- a/Fluxer.Net/Data/Guilds/PartialGuild.cs +++ b/Fluxer.Net/Data/Guilds/PartialGuild.cs @@ -31,13 +31,13 @@ public class PartialGuild : Entity, IPartialGuild public int? EmbedSplashHeight { get; internal set; } /// - public string? SplashHash { get; internal set; } + public string? InviteSplashHash { get; internal set; } /// - public int? SplashWidth { get; internal set; } + public int? InviteSplashWidth { get; internal set; } /// - public int? SplashHeight { get; internal set; } + public int? InviteSplashHeight { get; internal set; } /// public GuildSplashCardAlignment SplashCardAligment { get; internal set; } @@ -70,10 +70,46 @@ internal void Update(FluxerBaseClient client, PartialGuildJson json) EmbedSplashHash = json.EmbedSplashHash; EmbedSplashWidth = json.EmbedSplashWidth; EmbedSplashHeight = json.EmbedSplashHeight; - SplashHash = json.SplashHash; - SplashWidth = json.SplashWidth; - SplashHeight = json.SplashHeight; + InviteSplashHash = json.InviteSplashHash; + InviteSplashWidth = json.InviteSplashWidth; + InviteSplashHeight = json.InviteSplashHeight; SplashCardAligment = json.SplashCardAligment; Features = GuildFeatures.FromGuild(json); } + + /// + public string? GetIconUrl(int size = 160) + { + if (string.IsNullOrEmpty(IconHash)) + return null; + + return $"https://fluxerusercontent.com/icons/{Id}/{IconHash}.png?size={size}"; + } + + /// + public string? GetBannerUrl(int size = 1024) + { + if (string.IsNullOrEmpty(BannerHash)) + return null; + + return $"https://fluxerusercontent.com/banners/{Id}/{BannerHash}.webp?size={size}"; + } + + /// + public string? GetInviteSplashUrl(int size = 1024) + { + if (string.IsNullOrEmpty(InviteSplashHash)) + return null; + + return $"https://fluxerusercontent.com/splashes/{Id}/{InviteSplashHash}.webp?size={size}"; + } + + /// + public string? GetEmbedSplashUrl(int size = 1024) + { + if (string.IsNullOrEmpty(EmbedSplashHash)) + return null; + + return $"https://fluxerusercontent.com/embed-splashes/{Id}/{EmbedSplashHash}.webp?size={size}"; + } } diff --git a/Fluxer.Net/Data/Guilds/PartialGuildJson.cs b/Fluxer.Net/Data/Guilds/PartialGuildJson.cs index c61c537..d9a779d 100644 --- a/Fluxer.Net/Data/Guilds/PartialGuildJson.cs +++ b/Fluxer.Net/Data/Guilds/PartialGuildJson.cs @@ -43,15 +43,15 @@ public class PartialGuildJson : IPartialGuild /// [JsonProperty("splash")] - public string? SplashHash { get; set; } + public string? InviteSplashHash { get; set; } /// [JsonProperty("splash_width")] - public int? SplashWidth { get; set; } + public int? InviteSplashWidth { get; set; } /// [JsonProperty("splash_height")] - public int? SplashHeight { get; set; } + public int? InviteSplashHeight { get; set; } /// [JsonProperty("splash_card_alignment")] @@ -60,4 +60,40 @@ public class PartialGuildJson : IPartialGuild /// [JsonProperty("features")] public string[]? Features { get; set; } + + /// + public string? GetIconUrl(int size = 160) + { + if (string.IsNullOrEmpty(IconHash)) + return null; + + return $"https://fluxerusercontent.com/icons/{Id}/{IconHash}.png?size={size}"; + } + + /// + public string? GetBannerUrl(int size = 1024) + { + if (string.IsNullOrEmpty(BannerHash)) + return null; + + return $"https://fluxerusercontent.com/banners/{Id}/{BannerHash}.webp?size={size}"; + } + + /// + public string? GetInviteSplashUrl(int size = 1024) + { + if (string.IsNullOrEmpty(InviteSplashHash)) + return null; + + return $"https://fluxerusercontent.com/splashes/{Id}/{InviteSplashHash}.webp?size={size}"; + } + + /// + public string? GetEmbedSplashUrl(int size = 1024) + { + if (string.IsNullOrEmpty(EmbedSplashHash)) + return null; + + return $"https://fluxerusercontent.com/embed-splashes/{Id}/{EmbedSplashHash}.webp?size={size}"; + } } diff --git a/Fluxer.Net/Data/Guilds/SocketGuild.cs b/Fluxer.Net/Data/Guilds/SocketGuild.cs index 6af7ffe..7c987ac 100644 --- a/Fluxer.Net/Data/Guilds/SocketGuild.cs +++ b/Fluxer.Net/Data/Guilds/SocketGuild.cs @@ -67,7 +67,7 @@ internal void UpdatePermissions(SocketRole role) Permissions = role.Permissions; } - internal void AddOrUpdate(FluxerClient client, GuildMemberJson member) + internal void AddOrUpdateMember(FluxerClient client, GuildMemberJson member) { var mem = SocketGuildMember.Create(client, member); mem.Guild = this; diff --git a/Fluxer.Net/Data/Messages/Attachment.cs b/Fluxer.Net/Data/Messages/Attachment.cs index 42d9838..05ca8d8 100644 --- a/Fluxer.Net/Data/Messages/Attachment.cs +++ b/Fluxer.Net/Data/Messages/Attachment.cs @@ -57,14 +57,25 @@ public class Attachment : Entity, IAttachment /// public bool? IsExpired { get; set; } + internal ulong ChannelId { get; set; } + + /// + /// Get the attachment's url. + /// + public string? GetAttachmentUrl() + { + return $"https://fluxerusercontent.com/attachments/{ChannelId}/{Id}/{Filename}"; + } + internal Attachment(FluxerBaseClient client) : base(client) { } - public static Attachment Create(FluxerBaseClient client, AttachmentJson json) + public static Attachment Create(FluxerBaseClient client, AttachmentJson json, ulong channelId) { Attachment data = new Attachment(client); + data.ChannelId = channelId; data.Update(client, json); return data; } diff --git a/Fluxer.Net/Data/Messages/Message.cs b/Fluxer.Net/Data/Messages/Message.cs index ef93761..a0d256b 100644 --- a/Fluxer.Net/Data/Messages/Message.cs +++ b/Fluxer.Net/Data/Messages/Message.cs @@ -121,7 +121,7 @@ internal void Update(FluxerBaseClient client, MessageJson json) Embeds = json.Embeds.Select(x => Embed.Create(client, x)); if (json.Attachments != null) - Attachments = json.Attachments.Select(x => Attachment.Create(client, x)).ToArray(); + Attachments = json.Attachments.Select(x => Attachment.Create(client, x, ChannelId)).ToArray(); if (json.Stickers != null) Stickers = json.Stickers.Select(x => Sticker.Create(client, x)).ToArray(); @@ -133,7 +133,7 @@ internal void Update(FluxerBaseClient client, MessageJson json) MessageReference = MessageReference.Create(client, json.MessageReference); if (json.MessageSnapshots != null) - MessageSnapshots = json.MessageSnapshots.Select(x => MessageSnapshot.Create(client, x)).ToArray(); + MessageSnapshots = json.MessageSnapshots.Select(x => MessageSnapshot.Create(client, x, ChannelId)).ToArray(); Nonce = json.Nonce; diff --git a/Fluxer.Net/Data/Messages/MessageSnapshot.cs b/Fluxer.Net/Data/Messages/MessageSnapshot.cs index 611c54a..c41eed0 100644 --- a/Fluxer.Net/Data/Messages/MessageSnapshot.cs +++ b/Fluxer.Net/Data/Messages/MessageSnapshot.cs @@ -39,14 +39,17 @@ public class MessageSnapshot : Entity, IMessageSnapshot ISticker[]? IMessageSnapshot.Stickers => Stickers; + internal ulong ChannelId { get; set; } + internal MessageSnapshot(FluxerBaseClient client) : base(client) { } - public static MessageSnapshot Create(FluxerBaseClient client, MessageSnapshotJson json) + public static MessageSnapshot Create(FluxerBaseClient client, MessageSnapshotJson json, ulong channelId) { var data = new MessageSnapshot(client); + data.ChannelId = channelId; data.Update(client, json); return data; } @@ -63,7 +66,7 @@ internal void Update(FluxerBaseClient client, MessageSnapshotJson json) Embeds = json.Embeds.Select(x => Embed.Create(client, x)).ToArray(); if (json.Attachments != null) - Attachments = json.Attachments.Select(x => Attachment.Create(client, x)).ToArray(); + Attachments = json.Attachments.Select(x => Attachment.Create(client, x, ChannelId)).ToArray(); if (json.Stickers != null) Stickers = json.Stickers.Select(x => Sticker.Create(client, x)).ToArray(); diff --git a/Fluxer.Net/Data/Messages/SocketMessage.cs b/Fluxer.Net/Data/Messages/SocketMessage.cs new file mode 100644 index 0000000..1629674 --- /dev/null +++ b/Fluxer.Net/Data/Messages/SocketMessage.cs @@ -0,0 +1,21 @@ +using Fluxer.Net.Gateway.Data.Messages; + +namespace Fluxer.Net; + +public class SocketMessage : Message +{ + public Channel Channel { get; internal set; } + + internal SocketMessage(FluxerBaseClient client) : base(client) + { + + } + + public static SocketMessage Create(FluxerBaseClient client, MessageGatewayData json) + { + var data = new SocketMessage(client); + data.Channel = (client as FluxerClient).Gateway.GetChannel(json.ChannelId); + data.Update(client, json); + return data; + } +} diff --git a/Fluxer.Net/Data/Users/SocketUser.cs b/Fluxer.Net/Data/Users/SocketUser.cs new file mode 100644 index 0000000..d2b15d4 --- /dev/null +++ b/Fluxer.Net/Data/Users/SocketUser.cs @@ -0,0 +1,16 @@ +namespace Fluxer.Net; + +public class SocketUser : User +{ + internal SocketUser(FluxerBaseClient client) : base(client) + { + + } + + public static SocketUser Create(FluxerBaseClient client, UserJson json) + { + var data = new SocketUser(client); + data.Update(json); + return data; + } +} diff --git a/Fluxer.Net/Data/Voice/IVoiceState.cs b/Fluxer.Net/Data/Voice/IVoiceState.cs new file mode 100644 index 0000000..b1353e7 --- /dev/null +++ b/Fluxer.Net/Data/Voice/IVoiceState.cs @@ -0,0 +1,32 @@ +namespace Fluxer.Net; + +public interface IVoiceState +{ + string SessionId { get; } + + bool UserId { get; } + + ulong? ChannelId { get; } + + string? ConnectionId { get; } + + bool IsDeaf { get; } + + bool IsMute { get; } + + ulong? GuildId { get; } + + bool IsMobile { get; } + + bool IsSelfDeaf { get; } + + bool IsSelfMute { get; } + + bool IsSelfStream { get; } + + bool IsSelfVideo { get; } + + string[] ViewerStreamKeys { get; } + + IGuildMember Member { get; } +} diff --git a/Fluxer.Net/Data/Voice/SocketVoiceState.cs b/Fluxer.Net/Data/Voice/SocketVoiceState.cs new file mode 100644 index 0000000..38d5944 --- /dev/null +++ b/Fluxer.Net/Data/Voice/SocketVoiceState.cs @@ -0,0 +1,26 @@ +namespace Fluxer.Net; + +public class SocketVoiceState : VoiceState +{ + public SocketGuild Guild { get; internal set; } + public SocketVoiceChannel? Channel { get; internal set; } + + internal SocketVoiceState(FluxerBaseClient client) : base(client) + { + + } + + public static SocketVoiceState Create(FluxerBaseClient client, VoiceStateJson json, SocketGuild guild) + { + var data = new SocketVoiceState(client); + data.Update(client, json); + data.Guild = guild; + return data; + } + + internal override void Update(FluxerBaseClient client, VoiceStateJson json) + { + base.Update(client, json); + Channel = json.ChannelId.HasValue ? (SocketVoiceChannel)(client as FluxerClient).Gateway.GetChannel(json.ChannelId.Value) : null; + } +} diff --git a/Fluxer.Net/Data/Voice/VoiceState.cs b/Fluxer.Net/Data/Voice/VoiceState.cs new file mode 100644 index 0000000..d3831ef --- /dev/null +++ b/Fluxer.Net/Data/Voice/VoiceState.cs @@ -0,0 +1,78 @@ +namespace Fluxer.Net; + +public class VoiceState : Entity, IVoiceState +{ + /// + public string SessionId { get; set; } + + /// + public bool UserId { get; set; } + + /// + public ulong? ChannelId { get; set; } + + /// + public string? ConnectionId { get; set; } + + /// + public bool IsDeaf { get; set; } + + /// + public bool IsMute { get; set; } + + /// + public ulong? GuildId { get; set; } + + /// + public bool IsMobile { get; set; } + + /// + public bool IsSelfDeaf { get; set; } + + /// + public bool IsSelfMute { get; set; } + + /// + public bool IsSelfStream { get; set; } + + /// + public bool IsSelfVideo { get; set; } + + /// + public string[] ViewerStreamKeys { get; set; } + + /// + public GuildMember Member { get; set; } + + IGuildMember IVoiceState.Member => Member; + + internal VoiceState(FluxerBaseClient client) : base(client) + { + + } + + public static VoiceState Create(FluxerBaseClient client, VoiceStateJson json) + { + var data = new VoiceState(client); + data.Update(client, json); + return data; + } + + internal virtual void Update(FluxerBaseClient client, VoiceStateJson json) + { + SessionId = json.SessionId; + UserId = json.UserId; + ChannelId = json.ChannelId; + ConnectionId = json.ConnectionId; + IsDeaf = json.IsDeaf; + IsMute = json.IsMute; + GuildId = json.GuildId; + IsMobile = json.IsMobile; + IsSelfDeaf = json.IsSelfDeaf; + IsSelfMute = json.IsSelfMute; + IsSelfStream = json.IsSelfStream; + IsSelfVideo = json.IsSelfVideo; + ViewerStreamKeys = json.ViewerStreamKeys; + Member = GuildMember.Create(client, json.Member); + } +} diff --git a/Fluxer.Net/Data/Voice/VoiceStateJson.cs b/Fluxer.Net/Data/Voice/VoiceStateJson.cs new file mode 100644 index 0000000..756b488 --- /dev/null +++ b/Fluxer.Net/Data/Voice/VoiceStateJson.cs @@ -0,0 +1,65 @@ +using Newtonsoft.Json; + +namespace Fluxer.Net; + +/// +public class VoiceStateJson : IVoiceState +{ + /// + [JsonProperty("session_id")] + public string SessionId { get; set; } + + /// + [JsonProperty("user_id")] + public bool UserId { get; set; } + + /// + [JsonProperty("channel_id")] + public ulong? ChannelId { get; set; } + + /// + [JsonProperty("connection_id")] + public string? ConnectionId { get; set; } + + /// + [JsonProperty("deaf")] + public bool IsDeaf { get; set; } + + /// + [JsonProperty("mute")] + public bool IsMute { get; set; } + + /// + [JsonProperty("guild_id")] + public ulong? GuildId { get; set; } + + /// + [JsonProperty("is_mobile")] + public bool IsMobile { get; set; } + + /// + [JsonProperty("self_deaf")] + public bool IsSelfDeaf { get; set; } + + /// + [JsonProperty("self_mute")] + public bool IsSelfMute { get; set; } + + /// + [JsonProperty("self_stream")] + public bool IsSelfStream { get; set; } + + /// + [JsonProperty("self_video")] + public bool IsSelfVideo { get; set; } + + /// + [JsonProperty("viewer_stream_keys")] + public string[] ViewerStreamKeys { get; set; } + + /// + [JsonProperty("member")] + public GuildMemberJson Member { get; set; } + + IGuildMember IVoiceState.Member => Member; +} diff --git a/Fluxer.Net/Fluxer.Net.csproj b/Fluxer.Net/Fluxer.Net.csproj index 95105fc..ed8d52c 100644 --- a/Fluxer.Net/Fluxer.Net.csproj +++ b/Fluxer.Net/Fluxer.Net.csproj @@ -39,7 +39,6 @@ - @@ -53,6 +52,6 @@ - + \ No newline at end of file diff --git a/Fluxer.Net/FluxerNet.xml b/Fluxer.Net/FluxerNet.xml index c6aedfe..32ec644 100644 --- a/Fluxer.Net/FluxerNet.xml +++ b/Fluxer.Net/FluxerNet.xml @@ -173,6 +173,11 @@ Represents the context of a command execution. + + + Gets the API client. + + Gets the API client. @@ -188,12 +193,12 @@ Gets the message that triggered the command. - + Gets the channel the command was executed in. - + Gets the guild the command was executed in, if any. @@ -2155,12 +2160,18 @@ + + + + + + @@ -2176,6 +2187,9 @@ + + + @@ -2193,6 +2207,9 @@ + + + The unique identifier for this emoji. @@ -2213,6 +2230,11 @@ The user that created the emoji. + + + Get the emoji's image. + + The description of the sticker. @@ -2228,6 +2250,11 @@ The user that created the sticker. + + + Get the sticker's image. + + The unique identifier for this sticker. @@ -3162,17 +3189,17 @@ The height of the embedded invite splash in pixels - + The hash of the guild splash screen. - + The width of the guild splash in pixels. - + The height of the guild splash in pixels @@ -3193,6 +3220,26 @@ UNAVAILABLE_FOR_EVERYONE_BUT_STAFF, VISIONARY, OPERATOR, LARGE_GUILD_OVERRIDE, VERY_LARGE_GUILD (other values allowed) + + + Get the guild's icon. + + + + + Get the guild's banner. + + + + + Get the guild's invite splash. + + + + + Get the guild's embed splash. + + @@ -3280,6 +3327,9 @@ + + + @@ -3334,6 +3384,9 @@ + + + When the member was banned. @@ -3444,6 +3497,11 @@ Get the members's avatar or fallback to default. + + + Get the member's banner. + + The unique identifier for this role. @@ -3587,13 +3645,13 @@ - + - + - + @@ -3602,6 +3660,18 @@ + + + + + + + + + + + + @@ -3632,13 +3702,13 @@ - + - + - + @@ -3647,6 +3717,18 @@ + + + + + + + + + + + + Represents guild and channel permissions in Fluxer. Multiple permissions can be combined using bitwise OR. @@ -4331,6 +4413,11 @@ + + + Get the attachment's url. + + Flags that describe properties of a message attachment. Multiple flags can be combined using bitwise OR. @@ -6495,6 +6582,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The unique identifier (snowflake) for the webhook. @@ -6959,11 +7133,6 @@ ID of the guild where the message was sent (null for DMs) - - - Partial guild member response for message context - - Gateway data for RECENT_MENTION_DELETE event when a recent mention is deleted. @@ -7030,6 +7199,12 @@ Gateway data for CALL_CREATE, CALL_UPDATE, and CALL_DELETE events. + + + Gateway data for updating the current user's voice state. + Sent as part of VOICE_STATE_UPDATE opcode (4). + + WebSocket close codes specific to the Fluxer Gateway protocol. @@ -8027,50 +8202,18 @@ Occurs when an invite is deleted or expires. - - - Extension methods for handling fluxer oauth client. - - - - - Add Fluxer oauth client on dependency injection service. - - - - - Get Fluxer specific claims for a claim user. - - - - - Add Fluxer oauth client on dependency injection service. - - - - - Add Fluxer oauth client on dependency injection service. - - - - - Add Fluxer oauth client on dependency injection service. - - - - - Add Fluxer oauth client on dependency injection service. - - - - - Asp.net oauth handler for Fluxer. - - - - - Create asp.net oauth handler for Fluxer. - + + + Updates the current user's voice state (join/leave voice channels, mute, deafen). + + The guild ID containing the voice channel. + The voice channel ID to join, or null to disconnect. + Whether the user should be self-muted. + Whether the user should be self-deafened. + + This sends a VOICE_STATE_UPDATE packet to the gateway. The server will respond with + VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE events containing connection information. + @@ -8848,3 +8991,66 @@ +ions"> + + File extensions to exclude + + + + + Field to sort results by + + + + + + + + Sort order for results + + + + + + + + Whether to include NSFW channel results + + + + + + + + + + + + + + + The message content (up to 2000 characters) + + + + + Array of embed objects to include in the message + + + + + Controls which mentions trigger notifications + + + + + Message flags bitfield + + + + + Array of attachment objects to keep or add + + + + diff --git a/Fluxer.Net/Gateway/Data/Messages/MessageGatewayData.cs b/Fluxer.Net/Gateway/Data/Messages/MessageGatewayData.cs index f576af8..fe56d26 100644 --- a/Fluxer.Net/Gateway/Data/Messages/MessageGatewayData.cs +++ b/Fluxer.Net/Gateway/Data/Messages/MessageGatewayData.cs @@ -17,44 +17,11 @@ public class MessageGatewayData : MessageJson /// Guild member data for the author (only present in guild messages) /// [JsonProperty("member")] - public GuildMemberGatewayData? Member { get; set; } + public GuildMemberJson? Member { get; set; } /// /// ID of the guild where the message was sent (null for DMs) /// [JsonProperty("guild_id")] public ulong? GuildId { get; set; } -} - -/// -/// Partial guild member response for message context -/// -public class GuildMemberGatewayData -{ - [JsonProperty("avatar")] - public string? AvatarHash { get; set; } - - [JsonProperty("banner")] - public string? BannerHash { get; set; } - - [JsonProperty("communication_disabled_until")] - public DateTime? CommunicationDisabledUntil { get; set; } - - [JsonProperty("nick")] - public string? Nick { get; set; } - - [JsonProperty("roles")] - public List Roles { get; set; } = new(); - - [JsonProperty("joined_at")] - public DateTime JoinedAt { get; set; } - - [JsonProperty("guild_id")] - public ulong GuildId { get; set; } - - [JsonProperty("deaf")] - public bool IsDeaf { get; set; } - - [JsonProperty("mute")] - public bool IsMute { get; set; } -} +} \ No newline at end of file diff --git a/Fluxer.Net/Gateway/Data/Voice/VoiceStateGatewayData.cs b/Fluxer.Net/Gateway/Data/Voice/VoiceStateGatewayData.cs index 321614d..1f9c68e 100644 --- a/Fluxer.Net/Gateway/Data/Voice/VoiceStateGatewayData.cs +++ b/Fluxer.Net/Gateway/Data/Voice/VoiceStateGatewayData.cs @@ -1,48 +1,6 @@ -using Newtonsoft.Json; - namespace Fluxer.Net.Gateway.Data.Voice; -public class VoiceStateGatewayData +public class VoiceStateGatewayData : VoiceStateJson { - [JsonProperty("guild_id")] - public ulong? GuildId { get; set; } - - [JsonProperty("channel_id")] - public ulong? ChannelId { get; set; } - - [JsonProperty("user_id")] - public ulong UserId { get; set; } - - [JsonProperty("connection_id")] - public string ConnectionId { get; set; } - - [JsonProperty("session_id")] - public string SessionId { get; set; } - - [JsonProperty("member")] - public GuildMemberJson Member { get; set; } - - [JsonProperty("deaf")] - public bool Deaf { get; set; } - - [JsonProperty("mute")] - public bool Mute { get; set; } - - [JsonProperty("self_deaf")] - public bool SelfDeaf { get; set; } - - [JsonProperty("self_mute")] - public bool SelfMute { get; set; } - - [JsonProperty("self_video")] - public bool SelfVideo { get; set; } - - [JsonProperty("self_stream")] - public bool? SelfStream { get; set; } - - [JsonProperty("is_mobile")] - public bool IsMobile { get; set; } - [JsonProperty("viewer_stream_keys")] - public string[]? ViewerStreamKeys { get; set; } } diff --git a/Fluxer.Net/Gateway/Data/Voice/VoiceStateUpdatePayload.cs b/Fluxer.Net/Gateway/Data/Voice/VoiceStateUpdatePayload.cs new file mode 100644 index 0000000..f4781fe --- /dev/null +++ b/Fluxer.Net/Gateway/Data/Voice/VoiceStateUpdatePayload.cs @@ -0,0 +1,54 @@ +using Newtonsoft.Json; + +namespace Fluxer.Net.Gateway.Data.Voice; + +/// +/// Gateway data for updating the current user's voice state. +/// Sent as part of VOICE_STATE_UPDATE opcode (4). +/// +public class VoiceStateUpdatePayload +{ + [JsonProperty("guild_id")] + public string GuildId { get; set; } + + [JsonProperty("channel_id")] + public string? ChannelId { get; set; } + + [JsonProperty("connection_id")] + public string? ConnectionId { get; set; } + + [JsonProperty("self_mute")] + public bool SelfMute { get; set; } + + [JsonProperty("self_deaf")] + public bool SelfDeaf { get; set; } + + [JsonProperty("self_stream")] + public bool SelfStream { get; set; } + + [JsonProperty("self_video")] + public bool SelfVideo { get; set; } + + [JsonProperty("is_mobile")] + public bool IsMobile { get; set; } + + [JsonProperty("latitude")] + public string? Latitude { get; set; } + + [JsonProperty("longitude")] + public string? Longitude { get; set; } + + public VoiceStateUpdatePayload(string? guildId, string? channelId, bool selfMute, bool selfDeaf) + { + GuildId = guildId; + ChannelId = channelId; + SelfMute = selfMute; + SelfDeaf = selfDeaf; + SelfStream = false; + SelfVideo = false; + IsMobile = false; + ConnectionId = null; + Latitude = null; + Longitude = null; + } +} \ No newline at end of file diff --git a/Fluxer.Net/Gateway/GatewayClient.cs b/Fluxer.Net/Gateway/GatewayClient.cs index fb150df..1aadc25 100644 --- a/Fluxer.Net/Gateway/GatewayClient.cs +++ b/Fluxer.Net/Gateway/GatewayClient.cs @@ -623,7 +623,10 @@ private void HandleDispatch(GatewayPacket p) { MessageGatewayData? data = p.Data.ToObject(FluxerClient._serializer); if (data != null) + { + data.Member.User = data.Author; MessageCreate?.Invoke(data); + } else _logger.Warning("MESSAGE_CREATE event received but data could not be cast to MessageGatewayData"); } @@ -632,7 +635,10 @@ private void HandleDispatch(GatewayPacket p) { MessageGatewayData? data = p.Data.ToObject(FluxerClient._serializer); if (data != null) + { + data.Member.User = data.Author; MessageUpdate?.Invoke(data); + } else _logger.Warning("MESSAGE_UPDATE event received but data could not be cast to MessageGatewayData"); } @@ -868,7 +874,24 @@ private void HandleDispatch(GatewayPacket p) { VoiceStateGatewayData? data = p.Data.ToObject(FluxerClient._serializer); if (data != null) + { + if (data.GuildId.HasValue && Guilds.TryGetValue(data.GuildId.Value, out SocketGuild guild)) + { + guild.AddOrUpdateMember(_client, data.Member); + var member = guild.GetMember(data.Member.UserId); + if (data.ChannelId.HasValue) + { + if (!member.VoiceStates.TryAdd(data.SessionId, SocketVoiceState.Create(_client, data, guild))) + member.VoiceStates[data.SessionId].Update(_client, data); + } + else + { + member.VoiceStates.TryRemove(data.SessionId, out _); + } + } + VoiceStateUpdate?.Invoke(data); + } else _logger.Warning("VOICE_STATE_UPDATE event received but data could not be cast to VoiceStateGatewayData"); } @@ -1025,7 +1048,7 @@ private void HandleDispatch(GatewayPacket p) if (data != null) { if (Guilds.TryGetValue(data.GuildId, out SocketGuild guild)) - guild.AddOrUpdate(_client, data); + guild.AddOrUpdateMember(_client, data); GuildMemberAdd?.Invoke(data); } @@ -1070,7 +1093,7 @@ private void HandleDispatch(GatewayPacket p) { foreach (var m in data.Members) { - guild.AddOrUpdate(_client, m); + guild.AddOrUpdateMember(_client, m); } if ((data.ChunkIndex + 1) == data.ChunkCount) { @@ -2364,6 +2387,32 @@ public void SetStatus(Status status, UserCustomStatusJson? custom) #endregion + #region Voice + + /// + /// Updates the current user's voice state (join/leave voice channels, mute, deafen). + /// + /// The guild ID containing the voice channel. + /// The voice channel ID to join, or null to disconnect. + /// Whether the user should be self-muted. + /// Whether the user should be self-deafened. + /// + /// This sends a VOICE_STATE_UPDATE packet to the gateway. The server will respond with + /// VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE events containing connection information. + /// + public void UpdateVoiceState(ulong? guildId, ulong? channelId, bool selfMute, bool selfDeaf) + { + var packet = new GatewayPacket() + { + // TODO: Technically should be ulong, but fluxer expects strings. Fluxer will eventually be lenient and accept both. + Data = JToken.FromObject(new VoiceStateUpdatePayload(guildId.ToString(), channelId.ToString(), selfMute, selfDeaf)), + OpCode = FluxerOpCode.VoiceStateUpdate + }; + SendGatewayPacket(packet); + } + + #endregion + #region IDisposable public void Dispose() diff --git a/Fluxer.Net/Rest/Helpers/MemberHelpers.cs b/Fluxer.Net/Rest/Helpers/MemberHelpers.cs index 7d476ef..3aea8f8 100644 --- a/Fluxer.Net/Rest/Helpers/MemberHelpers.cs +++ b/Fluxer.Net/Rest/Helpers/MemberHelpers.cs @@ -22,4 +22,16 @@ public static Task RemoveRoleAsync(this GuildMember member, ulong roleId) public static Task RemoveRoleAsync(this GuildMember member, Role role) => member.Client.Rest.RemoveMemberRoleAsync(member.GuildId, member.UserId, role.Id); + + public static Task SetNicknameAsync(this GuildMember member, string? name) + => member.Client.Rest.UpdateMemberAsync(member.GuildId, member.UserId, new GuildMemberJson + { + Nickname = name + }); + + public static Task SetTimeoutAsync(this GuildMember member, DateTime? date) + => member.Client.Rest.UpdateMemberAsync(member.GuildId, member.UserId, new GuildMemberJson + { + CommunicationDisabledUntil = date + }); } diff --git a/Fluxer.Net/Rest/Helpers/MessageHelpers.cs b/Fluxer.Net/Rest/Helpers/MessageHelpers.cs index e296181..91cfc6b 100644 --- a/Fluxer.Net/Rest/Helpers/MessageHelpers.cs +++ b/Fluxer.Net/Rest/Helpers/MessageHelpers.cs @@ -1,4 +1,7 @@ -namespace Fluxer.Net; +using Fluxer.Net.Rest; +using Fluxer.Net.Rest.Requests; + +namespace Fluxer.Net; public static class MessageHelpers { @@ -37,4 +40,27 @@ public static Task RemoveOwnReactionAsync(this Message message, string emoji) public static Task RemoveUserReactionAsync(this Message message, string emoji, ulong userId) => message.Client.Rest.RemoveUserReactionAsync(message.ChannelId, message.Id, emoji, userId); + + public static Task ReplyAsync(this Message message, string? content = null, List? embeds = null, + AllowedMentionsRequest? allowedMentions = null, MessageFlag flags = MessageFlag.None, + string? nonce = null, ulong? favoruteMemeId = null, bool? tts = null, List? stickerIds = null, List? attachments = null) + => message.Client.Rest.SendMessageAsync(message.ChannelId, content, embeds, new MessageReferenceRequest + { + MessageId = message.Id, + }, allowedMentions, flags, nonce, favoruteMemeId, tts, stickerIds, attachments); + + public static Task ForwardAsync(this Message message, Channel channel, MessageFlag flags = MessageFlag.None, string? nonce = null) + => message.Client.Rest.SendMessageAsync(channel.Id, null, null, new MessageReferenceRequest + { + Type = MessageReferenceType.Forward, + MessageId = message.Id, + ChannelId = message.ChannelId, + }, null, flags, nonce); + + public static Task SuppressEmbedsAsync(this Message message, AllowedMentionsRequest? allowedMentions = null) + => message.Client.Rest.EditMessageAsync(message.ChannelId, message.Id, new UpdateMessageRequest + { + AllowedMentions = allowedMentions, + Flags = message.Flags |= MessageFlag.SuppressEmbeds, + }); }