From c06671c81d6655fb424805382dcf39a9b59fd3c0 Mon Sep 17 00:00:00 2001 From: NycroV <83246959+NycroV@users.noreply.github.com> Date: Fri, 5 Jul 2024 21:11:01 -0400 Subject: [PATCH 1/2] Needs testing --- .../DSharpPlusUtilities.cs | 57 +++++ .../DiscordClientWrapper.cs | 241 ++++++++++++++++++ .../Lavalink4NET.DSharpPlus.Nightly.csproj | 17 ++ .../ServiceCollectionExtensions.cs | 22 ++ .../VoiceStateUpdatePayload.cs | 18 ++ src/Lavalink4NET.sln | 15 +- 6 files changed, 366 insertions(+), 4 deletions(-) create mode 100644 src/Lavalink4NET.DSharpPlus.Nightly/DSharpPlusUtilities.cs create mode 100644 src/Lavalink4NET.DSharpPlus.Nightly/DiscordClientWrapper.cs create mode 100644 src/Lavalink4NET.DSharpPlus.Nightly/Lavalink4NET.DSharpPlus.Nightly.csproj create mode 100644 src/Lavalink4NET.DSharpPlus.Nightly/ServiceCollectionExtensions.cs create mode 100644 src/Lavalink4NET.DSharpPlus.Nightly/VoiceStateUpdatePayload.cs diff --git a/src/Lavalink4NET.DSharpPlus.Nightly/DSharpPlusUtilities.cs b/src/Lavalink4NET.DSharpPlus.Nightly/DSharpPlusUtilities.cs new file mode 100644 index 00000000..004e94b1 --- /dev/null +++ b/src/Lavalink4NET.DSharpPlus.Nightly/DSharpPlusUtilities.cs @@ -0,0 +1,57 @@ +namespace Lavalink4NET.DSharpPlus; + +using System; +using System.Collections.Concurrent; +using System.Reflection; +using global::DSharpPlus; +using global::DSharpPlus.AsyncEvents; + +/// +/// An utility for getting internal / private fields from DSharpPlus WebSocket Gateway Payloads. +/// +public static partial class DSharpPlusUtilities +{ + /// + /// The internal "events" property info in . + /// + // https://github.com/DSharpPlus/DSharpPlus/blob/master/DSharpPlus/Clients/DiscordClient.cs#L37 + private static readonly PropertyInfo eventsProperty = + typeof(DiscordClient).GetProperty("events", BindingFlags.NonPublic | BindingFlags.Instance)!; + + /// + /// Gets the internal "events" property value of the specified . + /// + /// the instance + /// the "events" value + public static ConcurrentDictionary GetEvents(this DiscordClient client) + => (ConcurrentDictionary)eventsProperty.GetValue(client)!; + + /// + /// The internal "errorHandler" property info in . + /// + // https://github.com/DSharpPlus/DSharpPlus/blob/master/DSharpPlus/Clients/DiscordClient.cs#L41 + private static readonly PropertyInfo errorHandlerProperty = + typeof(DiscordClient).GetProperty("errorHandler", BindingFlags.NonPublic | BindingFlags.Instance)!; + + /// + /// Gets the internal "errorHandler" property value of the specified . + /// + /// the instance + /// the "errorHandler" value + public static IClientErrorHandler GetErrorHandler(this DiscordClient client) + => (IClientErrorHandler)errorHandlerProperty.GetValue(client)!; + + /// + /// The internal "Register" method info in . + /// + // https://github.com/DSharpPlus/DSharpPlus/blob/master/DSharpPlus/AsyncEvents/AsyncEvent.cs#L14 + private static readonly MethodInfo asyncEventRegisterMethod = + typeof(AsyncEvent).GetMethod("Register", BindingFlags.NonPublic | BindingFlags.Instance, [typeof(Delegate)])!; + + /// + /// Calls the internal "Register" method of the spedificed + /// + /// the instance + /// the event to register + public static void Register(this AsyncEvent asyncEvent, Delegate @delegate) => asyncEventRegisterMethod.Invoke(asyncEvent, [@delegate]); +} diff --git a/src/Lavalink4NET.DSharpPlus.Nightly/DiscordClientWrapper.cs b/src/Lavalink4NET.DSharpPlus.Nightly/DiscordClientWrapper.cs new file mode 100644 index 00000000..58ea4a20 --- /dev/null +++ b/src/Lavalink4NET.DSharpPlus.Nightly/DiscordClientWrapper.cs @@ -0,0 +1,241 @@ +namespace Lavalink4NET.DSharpPlus; + +using System; +using System.Collections.Immutable; +using System.Threading; +using System.Threading.Tasks; +using global::DSharpPlus; +using global::DSharpPlus.AsyncEvents; +using global::DSharpPlus.Entities; +using global::DSharpPlus.EventArgs; +using global::DSharpPlus.Exceptions; +using global::DSharpPlus.Net.Abstractions; +using Lavalink4NET.Clients; +using L4N = Clients.Events; +using Lavalink4NET.Events; +using Microsoft.Extensions.Logging; +using System.Collections.Concurrent; + +/// +/// Wraps a or instance. +/// +public sealed class DiscordClientWrapper : IDiscordClientWrapper +{ + /// + public event AsyncEventHandler? VoiceServerUpdated; + + /// + public event AsyncEventHandler? VoiceStateUpdated; + + private readonly object _client; // either DiscordShardedClient or DiscordClient + private readonly ILogger _logger; + private readonly TaskCompletionSource _readyTaskCompletionSource; + private bool _disposed; + + private DiscordClientWrapper(object discordClient, ILogger logger) + { + ArgumentNullException.ThrowIfNull(discordClient); + ArgumentNullException.ThrowIfNull(logger); + + _client = discordClient; + _logger = logger; + + _readyTaskCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + + /// + /// Creates a new instance of . + /// + /// The Discord Client to wrap. + /// a logger associated with this wrapper. + public DiscordClientWrapper(DiscordClient discordClient, ILogger logger) + : this((object)discordClient, logger) + { + ArgumentNullException.ThrowIfNull(discordClient); + + void AddEventHandler(Type eventArgsType, Delegate eventHandler) + { + IClientErrorHandler errorHandler = discordClient.GetErrorHandler(); + ConcurrentDictionary events = discordClient.GetEvents(); + + Type asyncEventType = typeof(AsyncEvent<,>).MakeGenericType(discordClient.GetType(), eventArgsType); + AsyncEvent asyncEvent = events.GetOrAdd(eventArgsType, _ => (AsyncEvent)Activator.CreateInstance + ( + type: asyncEventType, + args: [errorHandler] + )!); + + asyncEvent.Register(eventHandler); + } + + AddEventHandler(typeof(VoiceStateUpdatedEventArgs), OnVoiceStateUpdated); + AddEventHandler(typeof(VoiceServerUpdatedEventArgs), OnVoiceServerUpdated); + AddEventHandler(typeof(GuildDownloadCompletedEventArgs), OnGuildDownloadCompleted); + } + + /// + /// Creates a new instance of . + /// + /// The Sharded Discord Client to wrap. + /// a logger associated with this wrapper. + public DiscordClientWrapper(DiscordShardedClient shardedDiscordClient, ILogger logger) + : this((object)shardedDiscordClient, logger) + { + ArgumentNullException.ThrowIfNull(shardedDiscordClient); + + shardedDiscordClient.VoiceStateUpdated += OnVoiceStateUpdated; + shardedDiscordClient.VoiceServerUpdated += OnVoiceServerUpdated; + shardedDiscordClient.GuildDownloadCompleted += OnGuildDownloadCompleted; + } + + /// + /// thrown if the instance is disposed + public async ValueTask> GetChannelUsersAsync( + ulong guildId, + ulong voiceChannelId, + bool includeBots = false, + CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + DiscordChannel channel; + try + { + channel = await GetClientForGuild(guildId) + .GetChannelAsync(voiceChannelId) + .ConfigureAwait(false); + + if (channel is null) + { + return ImmutableArray.Empty; + } + } + catch (DiscordException exception) + { + _logger.LogWarning( + exception, "An error occurred while retrieving the users for voice channel '{VoiceChannelId}' of the guild '{GuildId}'.", + voiceChannelId, guildId); + + return ImmutableArray.Empty; + } + + var filteredUsers = ImmutableArray.CreateBuilder(channel.Users.Count); + + foreach (var member in channel.Users) + { + // Always skip the current user. + // If we're not including bots and the member is a bot, skip them. + if (!member.IsCurrent || includeBots || !member.IsBot) + { + filteredUsers.Add(member.Id); + } + } + + return filteredUsers.ToImmutable(); + } + + /// + /// thrown if the instance is disposed + public async ValueTask SendVoiceUpdateAsync( + ulong guildId, + ulong? voiceChannelId, + bool selfDeaf = false, + bool selfMute = false, + CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var client = GetClientForGuild(guildId); + + var payload = new VoiceStateUpdatePayload + { + GuildId = guildId, + ChannelId = voiceChannelId, + IsSelfMuted = selfMute, + IsSelfDeafened = selfDeaf, + }; + +#pragma warning disable CS0618 // This method should not be used unless you know what you're doing. Instead, look towards the other explicitly implemented methods which come with client-side validation. + // Jan 23, 2024, OoLunar: We're telling Discord that we're joining a voice channel. + // At the time of writing, both DSharpPlus.VoiceNext and DSharpPlus.VoiceLink™ + // use this method to send voice state updates. + await client + .SendPayloadAsync(GatewayOpCode.VoiceStateUpdate, payload) + .ConfigureAwait(false); +#pragma warning restore CS0618 // This method should not be used unless you know what you're doing. Instead, look towards the other explicitly implemented methods which come with client-side validation. + } + + /// + public ValueTask WaitForReadyAsync(CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return new(_readyTaskCompletionSource.Task.WaitAsync(cancellationToken)); + } + + private DiscordClient GetClientForGuild(ulong guildId) => _client is DiscordClient discordClient + ? discordClient + : ((DiscordShardedClient)_client).GetShard(guildId); + + private Task OnGuildDownloadCompleted(DiscordClient discordClient, GuildDownloadCompletedEventArgs eventArgs) + { + ArgumentNullException.ThrowIfNull(discordClient); + ArgumentNullException.ThrowIfNull(eventArgs); + + var clientInformation = new ClientInformation( + Label: "DSharpPlus", + CurrentUserId: discordClient.CurrentUser.Id, + ShardCount: discordClient.ShardCount); + + _readyTaskCompletionSource.TrySetResult(clientInformation); + return Task.CompletedTask; + } + + private async Task OnVoiceServerUpdated(DiscordClient discordClient, VoiceServerUpdatedEventArgs voiceServerUpdateEventArgs) + { + ArgumentNullException.ThrowIfNull(discordClient); + ArgumentNullException.ThrowIfNull(voiceServerUpdateEventArgs); + + var server = new VoiceServer( + Token: voiceServerUpdateEventArgs.VoiceToken, + Endpoint: voiceServerUpdateEventArgs.Endpoint); + + var eventArgs = new L4N.VoiceServerUpdatedEventArgs( + guildId: voiceServerUpdateEventArgs.Guild.Id, + voiceServer: server); + + await VoiceServerUpdated + .InvokeAsync(this, eventArgs) + .ConfigureAwait(false); + } + + private async Task OnVoiceStateUpdated(DiscordClient discordClient, VoiceStateUpdatedEventArgs voiceStateUpdateEventArgs) + { + ArgumentNullException.ThrowIfNull(discordClient); + ArgumentNullException.ThrowIfNull(voiceStateUpdateEventArgs); + + // session id is the same as the resume key so DSharpPlus should be able to give us the + // session key in either before or after voice state + var sessionId = voiceStateUpdateEventArgs.Before?.SessionId ?? voiceStateUpdateEventArgs.After.SessionId; + + // create voice state + var voiceState = new VoiceState( + VoiceChannelId: voiceStateUpdateEventArgs.After?.Channel?.Id, + SessionId: sessionId); + + var oldVoiceState = new VoiceState( + VoiceChannelId: voiceStateUpdateEventArgs.Before?.Channel?.Id, + SessionId: sessionId); + + // invoke event + var eventArgs = new L4N.VoiceStateUpdatedEventArgs( + guildId: voiceStateUpdateEventArgs.Guild.Id, + userId: voiceStateUpdateEventArgs.User.Id, + isCurrentUser: voiceStateUpdateEventArgs.User.Id == discordClient.CurrentUser.Id, + oldVoiceState: oldVoiceState, + voiceState: voiceState); + + await VoiceStateUpdated + .InvokeAsync(this, eventArgs) + .ConfigureAwait(false); + } +} diff --git a/src/Lavalink4NET.DSharpPlus.Nightly/Lavalink4NET.DSharpPlus.Nightly.csproj b/src/Lavalink4NET.DSharpPlus.Nightly/Lavalink4NET.DSharpPlus.Nightly.csproj new file mode 100644 index 00000000..b127af68 --- /dev/null +++ b/src/Lavalink4NET.DSharpPlus.Nightly/Lavalink4NET.DSharpPlus.Nightly.csproj @@ -0,0 +1,17 @@ + + + Library + net8.0 + latest + + High performance Lavalink wrapper for .NET | Add powerful audio playback to your DSharpPlus-based applications with this integration for Lavalink4NET. Suitable for end users developing with DSharpPlus. + lavalink,lavalink-wrapper,discord,discord-music,discord-music-bot,dsharpplus + + true + + + + + + + \ No newline at end of file diff --git a/src/Lavalink4NET.DSharpPlus.Nightly/ServiceCollectionExtensions.cs b/src/Lavalink4NET.DSharpPlus.Nightly/ServiceCollectionExtensions.cs new file mode 100644 index 00000000..f127068f --- /dev/null +++ b/src/Lavalink4NET.DSharpPlus.Nightly/ServiceCollectionExtensions.cs @@ -0,0 +1,22 @@ +namespace Lavalink4NET.Extensions; + +using System; +using Lavalink4NET.DSharpPlus; +using Microsoft.Extensions.DependencyInjection; + +/// +/// A collection of extension methods for . +/// +public static class ServiceCollectionExtensions +{ + /// + /// Adds the Lavalink4NET DSharpPlus extension to the service collection. + /// + /// The service collection to add the extension to. + /// The service collection for chaining. + public static IServiceCollection AddLavalink(this IServiceCollection services) + { + ArgumentNullException.ThrowIfNull(services); + return services.AddLavalink(); + } +} diff --git a/src/Lavalink4NET.DSharpPlus.Nightly/VoiceStateUpdatePayload.cs b/src/Lavalink4NET.DSharpPlus.Nightly/VoiceStateUpdatePayload.cs new file mode 100644 index 00000000..49e329ab --- /dev/null +++ b/src/Lavalink4NET.DSharpPlus.Nightly/VoiceStateUpdatePayload.cs @@ -0,0 +1,18 @@ +namespace Lavalink4NET.DSharpPlus; + +using Newtonsoft.Json; + +internal sealed class VoiceStateUpdatePayload +{ + [JsonProperty("guild_id")] + public ulong GuildId { get; init; } + + [JsonProperty("channel_id")] + public ulong? ChannelId { get; init; } + + [JsonProperty("self_mute")] + public bool IsSelfMuted { get; init; } + + [JsonProperty("self_deaf")] + public bool IsSelfDeafened { get; init; } +} diff --git a/src/Lavalink4NET.sln b/src/Lavalink4NET.sln index 4aab41d1..035963e4 100644 --- a/src/Lavalink4NET.sln +++ b/src/Lavalink4NET.sln @@ -88,13 +88,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lavalink4NET.Integrations.L EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lavalink4NET.Integrations.Lavasrc.Tests", "..\tests\Lavalink4NET.Integrations.Lavasrc.Tests\Lavalink4NET.Integrations.Lavasrc.Tests.csproj", "{5779F765-5F0D-422C-984A-7E44EAE737C8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lavalink4NET.NetCord", "Lavalink4NET.NetCord\Lavalink4NET.NetCord.csproj", "{8587F98B-CFE1-4559-9614-ED3B2B0C4F4E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lavalink4NET.NetCord", "Lavalink4NET.NetCord\Lavalink4NET.NetCord.csproj", "{8587F98B-CFE1-4559-9614-ED3B2B0C4F4E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lavalink4NET.Samples.NetCord", "..\samples\Lavalink4NET.Samples.NetCord\Lavalink4NET.Samples.NetCord.csproj", "{02FE863F-D979-439A-9A51-C4EA69D58D29}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lavalink4NET.Samples.NetCord", "..\samples\Lavalink4NET.Samples.NetCord\Lavalink4NET.Samples.NetCord.csproj", "{02FE863F-D979-439A-9A51-C4EA69D58D29}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lavalink4NET.Integrations.LyricsJava", "Lavalink4NET.Integrations.LyricsJava\Lavalink4NET.Integrations.LyricsJava.csproj", "{9A30E985-6D67-41D4-A12F-F1ADCD2ED0FE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lavalink4NET.Integrations.LyricsJava", "Lavalink4NET.Integrations.LyricsJava\Lavalink4NET.Integrations.LyricsJava.csproj", "{9A30E985-6D67-41D4-A12F-F1ADCD2ED0FE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lavalink4NET.Integrations.LyricsJava.Tests", "Lavalink4NET.Integrations.LyricsJava.Tests\Lavalink4NET.Integrations.LyricsJava.Tests.csproj", "{176B0345-DF57-42B4-A8FD-4E6436D9554C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lavalink4NET.Integrations.LyricsJava.Tests", "Lavalink4NET.Integrations.LyricsJava.Tests\Lavalink4NET.Integrations.LyricsJava.Tests.csproj", "{176B0345-DF57-42B4-A8FD-4E6436D9554C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lavalink4NET.DSharpPlus.Nightly", "Lavalink4NET.DSharpPlus.Nightly\Lavalink4NET.DSharpPlus.Nightly.csproj", "{1A30629A-399B-4293-B5F4-B3909C1772D0}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -258,6 +260,10 @@ Global {176B0345-DF57-42B4-A8FD-4E6436D9554C}.Debug|Any CPU.Build.0 = Debug|Any CPU {176B0345-DF57-42B4-A8FD-4E6436D9554C}.Release|Any CPU.ActiveCfg = Release|Any CPU {176B0345-DF57-42B4-A8FD-4E6436D9554C}.Release|Any CPU.Build.0 = Release|Any CPU + {1A30629A-399B-4293-B5F4-B3909C1772D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A30629A-399B-4293-B5F4-B3909C1772D0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A30629A-399B-4293-B5F4-B3909C1772D0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A30629A-399B-4293-B5F4-B3909C1772D0}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -286,6 +292,7 @@ Global {02FE863F-D979-439A-9A51-C4EA69D58D29} = {B9402D29-5B12-4672-97B8-570A60C0F878} {9A30E985-6D67-41D4-A12F-F1ADCD2ED0FE} = {48ECDC71-B9E3-4086-8194-DA81B4667CA6} {176B0345-DF57-42B4-A8FD-4E6436D9554C} = {48ECDC71-B9E3-4086-8194-DA81B4667CA6} + {1A30629A-399B-4293-B5F4-B3909C1772D0} = {5FAEC63E-9752-48C4-8BC9-B101E0DBDBD3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {466A619D-5C4B-4A8F-9852-7A5322F160A2} From 1e258ac666438d75af0ef009e8a66de5bcc992bd Mon Sep 17 00:00:00 2001 From: NycroV <83246959+NycroV@users.noreply.github.com> Date: Tue, 9 Jul 2024 22:15:15 -0400 Subject: [PATCH 2/2] Cleanup and fixes --- .../DSharpPlusUtilities.cs | 12 ++++++------ .../DiscordClientWrapper.cs | 6 +++--- .../Lavalink4NET.DSharpPlus.Nightly.csproj | 5 +++-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/Lavalink4NET.DSharpPlus.Nightly/DSharpPlusUtilities.cs b/src/Lavalink4NET.DSharpPlus.Nightly/DSharpPlusUtilities.cs index 004e94b1..1085b93e 100644 --- a/src/Lavalink4NET.DSharpPlus.Nightly/DSharpPlusUtilities.cs +++ b/src/Lavalink4NET.DSharpPlus.Nightly/DSharpPlusUtilities.cs @@ -15,8 +15,8 @@ public static partial class DSharpPlusUtilities /// The internal "events" property info in . /// // https://github.com/DSharpPlus/DSharpPlus/blob/master/DSharpPlus/Clients/DiscordClient.cs#L37 - private static readonly PropertyInfo eventsProperty = - typeof(DiscordClient).GetProperty("events", BindingFlags.NonPublic | BindingFlags.Instance)!; + private static readonly FieldInfo eventsField = + typeof(DiscordClient).GetField("events", BindingFlags.NonPublic | BindingFlags.Instance)!; /// /// Gets the internal "events" property value of the specified . @@ -24,14 +24,14 @@ public static partial class DSharpPlusUtilities /// the instance /// the "events" value public static ConcurrentDictionary GetEvents(this DiscordClient client) - => (ConcurrentDictionary)eventsProperty.GetValue(client)!; + => (ConcurrentDictionary)eventsField.GetValue(client)!; /// /// The internal "errorHandler" property info in . /// // https://github.com/DSharpPlus/DSharpPlus/blob/master/DSharpPlus/Clients/DiscordClient.cs#L41 - private static readonly PropertyInfo errorHandlerProperty = - typeof(DiscordClient).GetProperty("errorHandler", BindingFlags.NonPublic | BindingFlags.Instance)!; + private static readonly FieldInfo errorHandlerField = + typeof(DiscordClient).GetField("errorHandler", BindingFlags.NonPublic | BindingFlags.Instance)!; /// /// Gets the internal "errorHandler" property value of the specified . @@ -39,7 +39,7 @@ public static ConcurrentDictionary GetEvents(this DiscordClien /// the instance /// the "errorHandler" value public static IClientErrorHandler GetErrorHandler(this DiscordClient client) - => (IClientErrorHandler)errorHandlerProperty.GetValue(client)!; + => (IClientErrorHandler)errorHandlerField.GetValue(client)!; /// /// The internal "Register" method info in . diff --git a/src/Lavalink4NET.DSharpPlus.Nightly/DiscordClientWrapper.cs b/src/Lavalink4NET.DSharpPlus.Nightly/DiscordClientWrapper.cs index 58ea4a20..57428a5c 100644 --- a/src/Lavalink4NET.DSharpPlus.Nightly/DiscordClientWrapper.cs +++ b/src/Lavalink4NET.DSharpPlus.Nightly/DiscordClientWrapper.cs @@ -68,9 +68,9 @@ void AddEventHandler(Type eventArgsType, Delegate eventHandler) asyncEvent.Register(eventHandler); } - AddEventHandler(typeof(VoiceStateUpdatedEventArgs), OnVoiceStateUpdated); - AddEventHandler(typeof(VoiceServerUpdatedEventArgs), OnVoiceServerUpdated); - AddEventHandler(typeof(GuildDownloadCompletedEventArgs), OnGuildDownloadCompleted); + AddEventHandler(typeof(VoiceStateUpdatedEventArgs), new AsyncEventHandler(OnVoiceStateUpdated)); + AddEventHandler(typeof(VoiceServerUpdatedEventArgs), new AsyncEventHandler(OnVoiceServerUpdated)); + AddEventHandler(typeof(GuildDownloadCompletedEventArgs), new AsyncEventHandler(OnGuildDownloadCompleted)); } /// diff --git a/src/Lavalink4NET.DSharpPlus.Nightly/Lavalink4NET.DSharpPlus.Nightly.csproj b/src/Lavalink4NET.DSharpPlus.Nightly/Lavalink4NET.DSharpPlus.Nightly.csproj index b127af68..c561a925 100644 --- a/src/Lavalink4NET.DSharpPlus.Nightly/Lavalink4NET.DSharpPlus.Nightly.csproj +++ b/src/Lavalink4NET.DSharpPlus.Nightly/Lavalink4NET.DSharpPlus.Nightly.csproj @@ -4,13 +4,14 @@ net8.0 latest - High performance Lavalink wrapper for .NET | Add powerful audio playback to your DSharpPlus-based applications with this integration for Lavalink4NET. Suitable for end users developing with DSharpPlus. + High performance Lavalink wrapper for .NET | Add powerful audio playback to your DSharpPlus-nightly-based applications with this integration for Lavalink4NET. Suitable for end users developing with DSharpPlus Nightly builds. lavalink,lavalink-wrapper,discord,discord-music,discord-music-bot,dsharpplus true + False - +