From 5751f141022421e02c5aa278eec369254b8eb718 Mon Sep 17 00:00:00 2001 From: Builderb Date: Wed, 15 Apr 2026 16:05:50 +0100 Subject: [PATCH 1/6] test voice --- Fluxer.Net/Fluxer.Net.csproj | 3 +- Fluxer.Net/FluxerNet.xml | 458 ++++++++++++++++-- .../Data/Voice/VoiceStateUpdatePayload.cs | 54 +++ Fluxer.Net/Gateway/GatewayClient.cs | 26 + 4 files changed, 495 insertions(+), 46 deletions(-) create mode 100644 Fluxer.Net/Gateway/Data/Voice/VoiceStateUpdatePayload.cs 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..f62f6f3 100644 --- a/Fluxer.Net/FluxerNet.xml +++ b/Fluxer.Net/FluxerNet.xml @@ -7030,6 +7030,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 +8033,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. + @@ -8846,5 +8820,401 @@ Array of attachment objects to keep or add + + + Handles encryption of voice packets using XSalsa20_Poly1305. + Discord requires all voice data to be encrypted. + + + + + Encrypts an RTP packet using XSalsa20_Poly1305 encryption. + + The RTP packet to encrypt (12-byte header + Opus data). + The 32-byte secret key from Session Description. + Encrypted packet with nonce. + + + + Encrypts an RTP packet using XSalsa20_Poly1305_Suffix encryption mode. + + The RTP packet to encrypt. + The 32-byte secret key. + Encrypted packet with nonce suffix. + + + + Encrypts an RTP packet using XSalsa20_Poly1305_Lite encryption mode. + + The RTP packet to encrypt. + The 32-byte secret key. + Counter for generating nonce (incremented after each use). + Encrypted packet with nonce suffix (4 bytes). + + + + Plays audio files (MP3, WAV, etc.) through a voice connection. + Handles conversion to PCM and encoding to Opus format required by Discord. + + + + + Creates a new AudioPlayer instance. + + The voice client to send audio through. + Optional Serilog logger. + + + + Plays an audio file through the voice connection. + + Path to the audio file (MP3, WAV, etc.). + Optional cancellation token to stop playback. + + + + Stops the current playback. + + + + + Gets whether audio is currently playing. + + + + + Wrapper for Opus audio encoding. + Discord requires Opus-encoded audio at 48kHz, stereo, 20ms frames. + + + + + Encodes PCM audio data to Opus format. + + PCM audio data (16-bit, 48kHz, stereo). + Length of PCM data in bytes. + Output buffer for Opus-encoded data. + Number of bytes written to opusOutput. + + + + Gets the required PCM frame size in bytes. + + + + + Simple protobuf parser for LiveKit messages. + Parses only what we need without using complex protobuf APIs. + + + + + Tries to extract SDP from a LiveKit protobuf message. + LiveKit SignalResponse with Offer has structure: + - Field 3 (offer) → SessionDescription + - SessionDescription Field 2 (sdp) → string + + + + + Checks if the message is a JoinResponse (field 1 in SignalResponse). + + + + + Tries to extract ICE candidate from a LiveKit protobuf message. + LiveKit SignalResponse with Trickle has structure: + - Field 4 (trickle) → TrickleRequest + - TrickleRequest Field 1 (candidateInit) → string + + + + + Creates a protobuf SignalRequest with an Answer. + Structure: Field 2 (answer) → SessionDescription { field 1 (type)="answer", field 2 (sdp)=sdpAnswer } + + + + + Creates a trickle request for sending ICE candidates. + + + + + Creates a ping request. + + + + + Handles creation of RTP (Real-time Transport Protocol) packets for voice transmission. + Discord uses RTP for sending audio data over UDP. + + + + + Creates an RTP packet with the given Opus-encoded audio data. + + Opus-encoded audio data. + Length of Opus data. + Complete RTP packet ready for transmission. + + + + Gets the current sequence number. + + + + + Gets the current timestamp. + + + + + Client for communicating with the Fluxer Voice Bridge (Node.js service). + This bridge uses the official LiveKit JavaScript SDK to handle all WebRTC complexity. + + + + + Creates a new VoiceBridgeClient instance. + + WebSocket URL of the Node.js bridge (e.g., "ws://localhost:8765") + Guild/server ID + Voice channel ID + User ID + Connection session ID + Optional Serilog logger + + + + Connects to the voice bridge and joins the voice channel. + + LiveKit server endpoint (from VoiceServerUpdate) + JWT token for authentication (from VoiceServerUpdate) + + + + Disconnects from the voice channel. + + + + + Sets the mute state of the local microphone. + + + + + Sets the deafen state (mutes microphone and disables audio output). + + + + + Receive loop for processing messages from the bridge. + + + + + Handle incoming messages from the bridge. + + + + + Send a message to the bridge. + + + + + Cleanup resources. + + + + + Information about a participant in a voice channel. + + + + + Manages LiveKit voice connections for real-time audio streaming with Fluxer BETA API. + Uses minimal protobuf parsing and SIPSorcery for WebRTC. + + + + + Creates a new LiveKit VoiceClient instance. + + + + + Connects to the LiveKit server and joins the voice room. + + + + + Sends audio data over the voice connection. + + + + + Sets speaking state (LiveKit auto-manages speaking indicators). + + + + + Disconnects from the LiveKit server. + + + + + Operation codes used in the Voice Gateway WebSocket protocol. + Based on Discord's voice protocol specifications. + + + + + Client → Server: Begin a voice websocket connection. + + + + + Client → Server: Select the voice protocol. + + + + + Server → Client: Complete the websocket handshake. + Contains SSRC, UDP port, and encryption modes. + + + + + Client → Server: Keep the websocket connection alive. + + + + + Server → Client: Describe the session. + Contains secret key for encryption. + + + + + Client/Server: Indicate which users are speaking. + + + + + Server → Client: Sent to acknowledge a received client heartbeat. + + + + + Client → Server: Resume a connection. + + + + + Server → Client: Sent after a successful Resume. + + + + + Server → Client: Sent after a successful Resume. + + + + + Client → Server: Request member resource. + + + + + Represents a packet sent or received over the voice WebSocket connection. + + + + + Voice Ready payload data. + + + + + Voice Session Description payload data. + + + + + Voice Hello payload data. + + + + + Voice Identify payload data. + + + + + Voice Select Protocol payload data. + + + + + Voice protocol data for Select Protocol. + + + + + Voice Speaking payload data. + + + + + Manages voice connection state across the application. + + + + + Voice endpoint from VOICE_SERVER_UPDATE event. + + + + + Voice token from VOICE_SERVER_UPDATE event. + + + + + Voice session ID from VOICE_STATE_UPDATE event. + + + + + Voice guild ID from VOICE_SERVER_UPDATE event. + + + + + Voice channel ID from VOICE_STATE_UPDATE event. + + + + + Connection ID from VOICE_SERVER_UPDATE event. + + + + + Ready data containing bot user information. + + + + + Resets all voice state. + + + + + Checks if all required voice connection data is available. + + 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..6a07b5e 100644 --- a/Fluxer.Net/Gateway/GatewayClient.cs +++ b/Fluxer.Net/Gateway/GatewayClient.cs @@ -2364,6 +2364,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() From 1b1691a19a3da9f4dadba4681ae8235f172fa056 Mon Sep 17 00:00:00 2001 From: Builderb Date: Wed, 15 Apr 2026 19:02:43 +0100 Subject: [PATCH 2/6] Voice improvements --- .../Data/Guilds/Members/SocketGuildMember.cs | 6 +- Fluxer.Net/Data/Guilds/SocketGuild.cs | 2 +- Fluxer.Net/Data/Voice/IVoiceState.cs | 32 ++ Fluxer.Net/Data/Voice/SocketVoiceState.cs | 26 ++ Fluxer.Net/Data/Voice/VoiceState.cs | 78 ++++ Fluxer.Net/Data/Voice/VoiceStateJson.cs | 65 +++ Fluxer.Net/FluxerNet.xml | 441 ++---------------- .../Data/Voice/VoiceStateGatewayData.cs | 44 +- Fluxer.Net/Gateway/GatewayClient.cs | 21 +- 9 files changed, 272 insertions(+), 443 deletions(-) create mode 100644 Fluxer.Net/Data/Voice/IVoiceState.cs create mode 100644 Fluxer.Net/Data/Voice/SocketVoiceState.cs create mode 100644 Fluxer.Net/Data/Voice/VoiceState.cs create mode 100644 Fluxer.Net/Data/Voice/VoiceStateJson.cs 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/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/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/FluxerNet.xml b/Fluxer.Net/FluxerNet.xml index f62f6f3..065c072 100644 --- a/Fluxer.Net/FluxerNet.xml +++ b/Fluxer.Net/FluxerNet.xml @@ -8046,6 +8046,51 @@ VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE events containing connection information. + + + 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. + + Represents a sliding window rate limit bucket for API requests @@ -8820,401 +8865,5 @@ Array of attachment objects to keep or add - - - Handles encryption of voice packets using XSalsa20_Poly1305. - Discord requires all voice data to be encrypted. - - - - - Encrypts an RTP packet using XSalsa20_Poly1305 encryption. - - The RTP packet to encrypt (12-byte header + Opus data). - The 32-byte secret key from Session Description. - Encrypted packet with nonce. - - - - Encrypts an RTP packet using XSalsa20_Poly1305_Suffix encryption mode. - - The RTP packet to encrypt. - The 32-byte secret key. - Encrypted packet with nonce suffix. - - - - Encrypts an RTP packet using XSalsa20_Poly1305_Lite encryption mode. - - The RTP packet to encrypt. - The 32-byte secret key. - Counter for generating nonce (incremented after each use). - Encrypted packet with nonce suffix (4 bytes). - - - - Plays audio files (MP3, WAV, etc.) through a voice connection. - Handles conversion to PCM and encoding to Opus format required by Discord. - - - - - Creates a new AudioPlayer instance. - - The voice client to send audio through. - Optional Serilog logger. - - - - Plays an audio file through the voice connection. - - Path to the audio file (MP3, WAV, etc.). - Optional cancellation token to stop playback. - - - - Stops the current playback. - - - - - Gets whether audio is currently playing. - - - - - Wrapper for Opus audio encoding. - Discord requires Opus-encoded audio at 48kHz, stereo, 20ms frames. - - - - - Encodes PCM audio data to Opus format. - - PCM audio data (16-bit, 48kHz, stereo). - Length of PCM data in bytes. - Output buffer for Opus-encoded data. - Number of bytes written to opusOutput. - - - - Gets the required PCM frame size in bytes. - - - - - Simple protobuf parser for LiveKit messages. - Parses only what we need without using complex protobuf APIs. - - - - - Tries to extract SDP from a LiveKit protobuf message. - LiveKit SignalResponse with Offer has structure: - - Field 3 (offer) → SessionDescription - - SessionDescription Field 2 (sdp) → string - - - - - Checks if the message is a JoinResponse (field 1 in SignalResponse). - - - - - Tries to extract ICE candidate from a LiveKit protobuf message. - LiveKit SignalResponse with Trickle has structure: - - Field 4 (trickle) → TrickleRequest - - TrickleRequest Field 1 (candidateInit) → string - - - - - Creates a protobuf SignalRequest with an Answer. - Structure: Field 2 (answer) → SessionDescription { field 1 (type)="answer", field 2 (sdp)=sdpAnswer } - - - - - Creates a trickle request for sending ICE candidates. - - - - - Creates a ping request. - - - - - Handles creation of RTP (Real-time Transport Protocol) packets for voice transmission. - Discord uses RTP for sending audio data over UDP. - - - - - Creates an RTP packet with the given Opus-encoded audio data. - - Opus-encoded audio data. - Length of Opus data. - Complete RTP packet ready for transmission. - - - - Gets the current sequence number. - - - - - Gets the current timestamp. - - - - - Client for communicating with the Fluxer Voice Bridge (Node.js service). - This bridge uses the official LiveKit JavaScript SDK to handle all WebRTC complexity. - - - - - Creates a new VoiceBridgeClient instance. - - WebSocket URL of the Node.js bridge (e.g., "ws://localhost:8765") - Guild/server ID - Voice channel ID - User ID - Connection session ID - Optional Serilog logger - - - - Connects to the voice bridge and joins the voice channel. - - LiveKit server endpoint (from VoiceServerUpdate) - JWT token for authentication (from VoiceServerUpdate) - - - - Disconnects from the voice channel. - - - - - Sets the mute state of the local microphone. - - - - - Sets the deafen state (mutes microphone and disables audio output). - - - - - Receive loop for processing messages from the bridge. - - - - - Handle incoming messages from the bridge. - - - - - Send a message to the bridge. - - - - - Cleanup resources. - - - - - Information about a participant in a voice channel. - - - - - Manages LiveKit voice connections for real-time audio streaming with Fluxer BETA API. - Uses minimal protobuf parsing and SIPSorcery for WebRTC. - - - - - Creates a new LiveKit VoiceClient instance. - - - - - Connects to the LiveKit server and joins the voice room. - - - - - Sends audio data over the voice connection. - - - - - Sets speaking state (LiveKit auto-manages speaking indicators). - - - - - Disconnects from the LiveKit server. - - - - - Operation codes used in the Voice Gateway WebSocket protocol. - Based on Discord's voice protocol specifications. - - - - - Client → Server: Begin a voice websocket connection. - - - - - Client → Server: Select the voice protocol. - - - - - Server → Client: Complete the websocket handshake. - Contains SSRC, UDP port, and encryption modes. - - - - - Client → Server: Keep the websocket connection alive. - - - - - Server → Client: Describe the session. - Contains secret key for encryption. - - - - - Client/Server: Indicate which users are speaking. - - - - - Server → Client: Sent to acknowledge a received client heartbeat. - - - - - Client → Server: Resume a connection. - - - - - Server → Client: Sent after a successful Resume. - - - - - Server → Client: Sent after a successful Resume. - - - - - Client → Server: Request member resource. - - - - - Represents a packet sent or received over the voice WebSocket connection. - - - - - Voice Ready payload data. - - - - - Voice Session Description payload data. - - - - - Voice Hello payload data. - - - - - Voice Identify payload data. - - - - - Voice Select Protocol payload data. - - - - - Voice protocol data for Select Protocol. - - - - - Voice Speaking payload data. - - - - - Manages voice connection state across the application. - - - - - Voice endpoint from VOICE_SERVER_UPDATE event. - - - - - Voice token from VOICE_SERVER_UPDATE event. - - - - - Voice session ID from VOICE_STATE_UPDATE event. - - - - - Voice guild ID from VOICE_SERVER_UPDATE event. - - - - - Voice channel ID from VOICE_STATE_UPDATE event. - - - - - Connection ID from VOICE_SERVER_UPDATE event. - - - - - Ready data containing bot user information. - - - - - Resets all voice state. - - - - - Checks if all required voice connection data is available. - - 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/GatewayClient.cs b/Fluxer.Net/Gateway/GatewayClient.cs index 6a07b5e..ac00225 100644 --- a/Fluxer.Net/Gateway/GatewayClient.cs +++ b/Fluxer.Net/Gateway/GatewayClient.cs @@ -868,7 +868,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 +1042,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 +1087,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) { From c2855b2791395137cac15c7b3a6c021c3407d222 Mon Sep 17 00:00:00 2001 From: Builderb Date: Wed, 15 Apr 2026 19:34:28 +0100 Subject: [PATCH 3/6] Command context changes --- Fluxer.Net/Commands/CommandContext.cs | 41 +++- .../Data/Guilds/Members/GuildMemberJson.cs | 1 - Fluxer.Net/Data/Messages/SocketMessage.cs | 21 ++ Fluxer.Net/Data/Users/SocketUser.cs | 16 ++ Fluxer.Net/FluxerNet.xml | 196 +++++++++++++----- .../Data/Messages/MessageGatewayData.cs | 37 +--- Fluxer.Net/Gateway/GatewayClient.cs | 6 + 7 files changed, 221 insertions(+), 97 deletions(-) create mode 100644 Fluxer.Net/Data/Messages/SocketMessage.cs create mode 100644 Fluxer.Net/Data/Users/SocketUser.cs 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/Data/Guilds/Members/GuildMemberJson.cs b/Fluxer.Net/Data/Guilds/Members/GuildMemberJson.cs index 25fbc9c..852e6dc 100644 --- a/Fluxer.Net/Data/Guilds/Members/GuildMemberJson.cs +++ b/Fluxer.Net/Data/Guilds/Members/GuildMemberJson.cs @@ -88,5 +88,4 @@ public string GetAvatarOrDefaultUrl(int size = 160) } IUser IGuildMember.User => User; - } 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/FluxerNet.xml b/Fluxer.Net/FluxerNet.xml index 065c072..0481cc0 100644 --- a/Fluxer.Net/FluxerNet.xml +++ b/Fluxer.Net/FluxerNet.xml @@ -6495,6 +6495,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The unique identifier (snowflake) for the webhook. @@ -6959,11 +7046,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. @@ -8046,51 +8128,6 @@ VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE events containing connection information. - - - 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. - - Represents a sliding window rate limit bucket for API requests @@ -8835,6 +8872,65 @@ + + + + + + Link hostnames to filter by + + + + + Link hostnames to exclude + + + + + Attachment filenames to filter by + + + + + Attachment filenames to exclude + + + + + File extensions to filter by + + + + + File extensions to exclude + + + + + Field to sort results by + + + + + + + + Sort order for results + + + + + + + + Whether to include NSFW channel results + + + + + + + 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/GatewayClient.cs b/Fluxer.Net/Gateway/GatewayClient.cs index ac00225..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"); } From 42ce79be2507b818c08eb4ea218681660f3fe1f9 Mon Sep 17 00:00:00 2001 From: Builderb Date: Wed, 15 Apr 2026 20:00:30 +0100 Subject: [PATCH 4/6] New message extension methods --- .../Attributes/RequireContextAttribute.cs | 6 +- Fluxer.Net/Commands/ModuleBase.cs | 4 +- Fluxer.Net/FluxerNet.xml | 113 ++++++++---------- Fluxer.Net/Rest/Helpers/MessageHelpers.cs | 28 ++++- 4 files changed, 84 insertions(+), 67 deletions(-) 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/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/FluxerNet.xml b/Fluxer.Net/FluxerNet.xml index 0481cc0..9360169 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. @@ -8128,6 +8133,51 @@ VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE events containing connection information. + + + 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. + + Represents a sliding window rate limit bucket for API requests @@ -8872,65 +8922,6 @@ - - - - - - Link hostnames to filter by - - - - - Link hostnames to exclude - - - - - Attachment filenames to filter by - - - - - Attachment filenames to exclude - - - - - File extensions to filter by - - - - - File extensions to exclude - - - - - Field to sort results by - - - - - - - - Sort order for results - - - - - - - - Whether to include NSFW channel results - - - - - - - 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, + }); } From c2900fc56be3ec66c18051dbb98400eddcdc4865 Mon Sep 17 00:00:00 2001 From: Builderb Date: Wed, 15 Apr 2026 20:12:33 +0100 Subject: [PATCH 5/6] New member extension methods --- Fluxer.Net/Rest/Helpers/MemberHelpers.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 + }); } From acd6f3650ddff97ca4c253cab68b5cbc66b26b68 Mon Sep 17 00:00:00 2001 From: Builderb Date: Wed, 22 Apr 2026 03:34:03 +0100 Subject: [PATCH 6/6] Add image, member and attachment url methods --- Fluxer.Net.Example/Modules/BasicCommands.cs | 2 +- Fluxer.Net/Data/Expressions/GuildEmoji.cs | 6 + Fluxer.Net/Data/Expressions/GuildEmojiJson.cs | 6 + Fluxer.Net/Data/Expressions/GuildSticker.cs | 6 + .../Data/Expressions/GuildStickerJson.cs | 6 + Fluxer.Net/Data/Expressions/IGuildEmoji.cs | 5 + Fluxer.Net/Data/Expressions/IGuildSticker.cs | 5 + Fluxer.Net/Data/Guilds/IPartialGuild.cs | 26 ++- Fluxer.Net/Data/Guilds/Members/GuildMember.cs | 9 + .../Data/Guilds/Members/GuildMemberJson.cs | 11 +- .../Data/Guilds/Members/IGuildMember.cs | 5 + Fluxer.Net/Data/Guilds/PartialGuild.cs | 48 +++- Fluxer.Net/Data/Guilds/PartialGuildJson.cs | 42 +++- Fluxer.Net/Data/Messages/Attachment.cs | 13 +- Fluxer.Net/Data/Messages/Message.cs | 4 +- Fluxer.Net/Data/Messages/MessageSnapshot.cs | 7 +- Fluxer.Net/FluxerNet.xml | 208 +++++++++++++----- 17 files changed, 336 insertions(+), 73 deletions(-) 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/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 852e6dc..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,5 +87,14 @@ 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; } 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/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/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/FluxerNet.xml b/Fluxer.Net/FluxerNet.xml index 9360169..32ec644 100644 --- a/Fluxer.Net/FluxerNet.xml +++ b/Fluxer.Net/FluxerNet.xml @@ -2160,12 +2160,18 @@ + + + + + + @@ -2181,6 +2187,9 @@ + + + @@ -2198,6 +2207,9 @@ + + + The unique identifier for this emoji. @@ -2218,6 +2230,11 @@ The user that created the emoji. + + + Get the emoji's image. + + The description of the sticker. @@ -2233,6 +2250,11 @@ The user that created the sticker. + + + Get the sticker's image. + + The unique identifier for this sticker. @@ -3167,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 @@ -3198,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. + + @@ -3285,6 +3327,9 @@ + + + @@ -3339,6 +3384,9 @@ + + + When the member was banned. @@ -3449,6 +3497,11 @@ Get the members's avatar or fallback to default. + + + Get the member's banner. + + The unique identifier for this role. @@ -3592,13 +3645,13 @@ - + - + - + @@ -3607,6 +3660,18 @@ + + + + + + + + + + + + @@ -3637,13 +3702,13 @@ - + - + - + @@ -3652,6 +3717,18 @@ + + + + + + + + + + + + Represents guild and channel permissions in Fluxer. Multiple permissions can be combined using bitwise OR. @@ -4336,6 +4413,11 @@ + + + Get the attachment's url. + + Flags that describe properties of a message attachment. Multiple flags can be combined using bitwise OR. @@ -8133,51 +8215,6 @@ VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE events containing connection information. - - - 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. - - Represents a sliding window rate limit bucket for API requests @@ -8954,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 + + + +