From b7e1bfff368b8b4ed93d7737b46f10d98166995d Mon Sep 17 00:00:00 2001 From: Misha133 Date: Fri, 31 Mar 2023 16:34:57 +0300 Subject: [PATCH 1/9] initial commit --- .../Entities/AnimationType.cs | 14 ++++++ .../API/Gateway/VoiceChannelEffectSend.cs | 24 +++++++++ .../BaseSocketClient.Events.cs | 50 ++++++++++++++++++- .../DiscordSocketClient.cs | 29 +++++++++++ 4 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 src/Discord.Net.Core/Entities/AnimationType.cs create mode 100644 src/Discord.Net.WebSocket/API/Gateway/VoiceChannelEffectSend.cs diff --git a/src/Discord.Net.Core/Entities/AnimationType.cs b/src/Discord.Net.Core/Entities/AnimationType.cs new file mode 100644 index 0000000000..be2dccd571 --- /dev/null +++ b/src/Discord.Net.Core/Entities/AnimationType.cs @@ -0,0 +1,14 @@ +namespace Discord; + +public enum AnimationType +{ + /// + /// A fun animation, sent by a Nitro subscriber. + /// + Premium = 0, + + /// + /// The standard animation. + /// + Basic = 1, +} diff --git a/src/Discord.Net.WebSocket/API/Gateway/VoiceChannelEffectSend.cs b/src/Discord.Net.WebSocket/API/Gateway/VoiceChannelEffectSend.cs new file mode 100644 index 0000000000..f6b71625ab --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/VoiceChannelEffectSend.cs @@ -0,0 +1,24 @@ +using Newtonsoft.Json; + +namespace Discord.WebSocket; + +internal class VoiceChannelEffectSend +{ + [JsonProperty("channel_id")] + public ulong ChannelId { get; set; } + + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + + [JsonProperty("user_id")] + public ulong UserId { get; set; } + + [JsonProperty("emoji")] + public Optional Emoji { get; set; } + + [JsonProperty("animation_type")] + public Optional AnimationType { get; set; } + + [JsonProperty("animation_id")] + public Optional AnimationId { get; set; } +} diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index bc97139e9d..b62162cc1a 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -934,7 +934,55 @@ public event Func _autoModActionExecuted.Remove(value); } internal readonly AsyncEvent> _autoModActionExecuted = new (); - + + #endregion + + #region Voice + + /// + /// Fired when a voice channel effect is sent. + /// + public event Func, Task> VoiceChannelEffectSend + { + add => _voiceChannelEffectSend.Add(value); + remove => _voiceChannelEffectSend.Remove(value); + } + internal readonly AsyncEvent, Task>> _voiceChannelEffectSend = new(); + + #endregion + + #region SoundBoard + + /// + /// Fired when a soundboard sound is created. + /// + public event Func SoundboardSoundCreated + { + add => _soundboardSoundCreated.Add(value); + remove => _soundboardSoundCreated.Remove(value); + } + internal readonly AsyncEvent> _soundboardSoundCreated = new(); + + /// + /// Fired when a soundboard sound is created. + /// + public event Func SoundboardSoundUpdated + { + add => _soundboardSoundUpdated.Add(value); + remove => _soundboardSoundUpdated.Remove(value); + } + internal readonly AsyncEvent> _soundboardSoundUpdated = new(); + + /// + /// Fired when a soundboard sound is created. + /// + public event Func SoundboardSoundDeleted + { + add => _soundboardSoundDeleted.Add(value); + remove => _soundboardSoundDeleted.Remove(value); + } + internal readonly AsyncEvent> _soundboardSoundDeleted = new(); + #endregion } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 219e4548f0..8336d20070 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -2289,6 +2289,13 @@ private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string ty } break; + + case "VOICE_CHANNEL_EFFECT_SEND": + { + + } + break; + #endregion #region Invites @@ -3019,6 +3026,28 @@ private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string ty #endregion + #region SoundBoard + + case "GUILD_SOUNDBOARD_SOUND_CREATE": + { + + } + break; + + case "GUILD_SOUNDBOARD_SOUND_UPDATE": + { + + } + break; + + case "GUILD_SOUNDBOARD_SOUND_DELETE ": + { + + } + break; + + #endregion + #region Ignored (User only) case "CHANNEL_PINS_ACK": await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); From 22a77a403b61cff8810fc26950f1180bcf55dede Mon Sep 17 00:00:00 2001 From: Misha133 Date: Sat, 1 Apr 2023 18:11:38 +0300 Subject: [PATCH 2/9] add `UseExternalSounds` permission --- .../Entities/Permissions/ChannelPermission.cs | 5 +++++ .../Entities/Permissions/ChannelPermissions.cs | 17 ++++++++++++----- .../Entities/Permissions/GuildPermission.cs | 5 +++++ .../Entities/Permissions/GuildPermissions.cs | 17 ++++++++++++----- .../ChannelPermissionsTests.cs | 1 + .../GuildPermissionsTests.cs | 4 +++- 6 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs index 21026b9936..d5678a3bb2 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs @@ -177,5 +177,10 @@ public enum ChannelPermission : ulong /// Allows members to edit and cancel events in this channel. /// CreateEvents = 1L << 44, + + /// + /// Allows members to use sounds from other servers. + /// + UseExternalSounds = 1L << 45, } } diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index 8ef01f267e..84001c5ba1 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -140,6 +140,8 @@ public static ChannelPermissions All(IChannel channel) public bool UseSoundboard => Permissions.GetValue(RawValue, ChannelPermission.UseSoundboard); /// If , a user can edit and cancel events in this channel. public bool CreateEvents => Permissions.GetValue(RawValue, ChannelPermission.CreateEvents); + /// If , a user can use sounds from other servers. + public bool UseExternalSounds => Permissions.GetValue(RawValue, ChannelPermission.UseExternalSounds); /// Creates a new with the provided packed value. public ChannelPermissions(ulong rawValue) { RawValue = rawValue; } @@ -176,7 +178,8 @@ private ChannelPermissions(ulong initialValue, bool? sendMessagesInThreads = null, bool? startEmbeddedActivities = null, bool? useSoundboard = null, - bool? createEvents = null) + bool? createEvents = null, + bool? useExternalSounds = null) { ulong value = initialValue; @@ -212,6 +215,7 @@ private ChannelPermissions(ulong initialValue, Permissions.SetValue(ref value, startEmbeddedActivities, ChannelPermission.StartEmbeddedActivities); Permissions.SetValue(ref value, useSoundboard, ChannelPermission.UseSoundboard); Permissions.SetValue(ref value, createEvents, ChannelPermission.CreateEvents); + Permissions.SetValue(ref value, useExternalSounds, ChannelPermission.UseExternalSounds); RawValue = value; } @@ -249,12 +253,13 @@ public ChannelPermissions( bool sendMessagesInThreads = false, bool startEmbeddedActivities = false, bool useSoundboard = false, - bool createEvents = false) + bool createEvents = false, + bool useExternalSounds = false) : this(0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, stream, manageRoles, manageWebhooks, useApplicationCommands, requestToSpeak, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads, - startEmbeddedActivities, useSoundboard, createEvents) + startEmbeddedActivities, useSoundboard, createEvents, useExternalSounds) { } /// Creates a new from this one, changing the provided non-null permissions. @@ -290,7 +295,8 @@ public ChannelPermissions Modify( bool? sendMessagesInThreads = null, bool? startEmbeddedActivities = null, bool? useSoundboard = null, - bool? createEvents = null) + bool? createEvents = null, + bool? useExternalSounds = null) => new ChannelPermissions(RawValue, createInstantInvite, manageChannel, @@ -323,7 +329,8 @@ public ChannelPermissions Modify( sendMessagesInThreads, startEmbeddedActivities, useSoundboard, - createEvents); + createEvents, + useExternalSounds); public bool Has(ChannelPermission permission) => Permissions.GetValue(RawValue, permission); diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs index 41f3be24ba..423f2de925 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs @@ -262,5 +262,10 @@ public enum GuildPermission : ulong /// Allows for using the soundboard. /// UseSoundboard = 1L << 42, + + /// + /// Allows members to use sounds from other servers. + /// + UseExternalSounds = 1L << 45, } } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index 4884a14e74..a5a52aacb9 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -108,6 +108,8 @@ public struct GuildPermissions public bool UseSoundboard => Permissions.GetValue(RawValue, GuildPermission.UseSoundboard); /// If , a user can view monetization analytics in this guild. public bool ViewMonetizationAnalytics => Permissions.GetValue(RawValue, GuildPermission.ViewMonetizationAnalytics); + /// If , a user can use sounds from other servers. + public bool UseExternalSounds => Permissions.GetValue(RawValue, GuildPermission.UseExternalSounds); /// Creates a new with the provided packed value. public GuildPermissions(ulong rawValue) { RawValue = rawValue; } @@ -158,7 +160,8 @@ private GuildPermissions(ulong initialValue, bool? startEmbeddedActivities = null, bool? moderateMembers = null, bool? useSoundboard = null, - bool? viewMonetizationAnalytics = null) + bool? viewMonetizationAnalytics = null, + bool? useExternalSounds = null) { ulong value = initialValue; @@ -205,6 +208,7 @@ private GuildPermissions(ulong initialValue, Permissions.SetValue(ref value, moderateMembers, GuildPermission.ModerateMembers); Permissions.SetValue(ref value, useSoundboard, GuildPermission.UseSoundboard); Permissions.SetValue(ref value, viewMonetizationAnalytics, GuildPermission.ViewMonetizationAnalytics); + Permissions.SetValue(ref value, useExternalSounds, GuildPermission.UseExternalSounds); RawValue = value; } @@ -253,7 +257,8 @@ public GuildPermissions( bool startEmbeddedActivities = false, bool moderateMembers = false, bool useSoundboard = false, - bool viewMonetizationAnalytics = false) + bool viewMonetizationAnalytics = false, + bool useExternalSounds = false) : this(0, createInstantInvite: createInstantInvite, manageRoles: manageRoles, @@ -297,7 +302,8 @@ public GuildPermissions( startEmbeddedActivities: startEmbeddedActivities, moderateMembers: moderateMembers, useSoundboard: useSoundboard, - viewMonetizationAnalytics: viewMonetizationAnalytics) + viewMonetizationAnalytics: viewMonetizationAnalytics, + useExternalSounds: useExternalSounds) { } /// Creates a new from this one, changing the provided non-null permissions. @@ -344,13 +350,14 @@ public GuildPermissions Modify( bool? startEmbeddedActivities = null, bool? moderateMembers = null, bool? useSoundboard = null, - bool? viewMonetizationAnalytics = null) + bool? viewMonetizationAnalytics = null, + bool? useExternalSounds = null) => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojisAndStickers, useApplicationCommands, requestToSpeak, manageEvents, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads, - startEmbeddedActivities, moderateMembers, useSoundboard, viewMonetizationAnalytics); + startEmbeddedActivities, moderateMembers, useSoundboard, viewMonetizationAnalytics, useExternalSounds); /// /// Returns a value that indicates if a specific is enabled diff --git a/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs b/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs index f8300cc6f4..a451fdccb1 100644 --- a/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs +++ b/test/Discord.Net.Tests.Unit/ChannelPermissionsTests.cs @@ -92,6 +92,7 @@ void AssertFlag(Func cstr, ChannelPermission flag) AssertFlag(() => new ChannelPermissions(startEmbeddedActivities: true), ChannelPermission.StartEmbeddedActivities); AssertFlag(() => new ChannelPermissions(useSoundboard: true), ChannelPermission.UseSoundboard); AssertFlag(() => new ChannelPermissions(createEvents: true), ChannelPermission.CreateEvents); + AssertFlag(() => new ChannelPermissions(useExternalSounds: true), ChannelPermission.UseExternalSounds); } /// diff --git a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs index e259cc013a..0e86de33b0 100644 --- a/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs +++ b/test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs @@ -102,10 +102,11 @@ void AssertFlag(Func cstr, GuildPermission flag) AssertFlag(() => new GuildPermissions(moderateMembers: true), GuildPermission.ModerateMembers); AssertFlag(() => new GuildPermissions(viewMonetizationAnalytics: true), GuildPermission.ViewMonetizationAnalytics); AssertFlag(() => new GuildPermissions(useSoundboard: true), GuildPermission.UseSoundboard); + AssertFlag(() => new GuildPermissions(useExternalSounds: true), GuildPermission.UseExternalSounds); } /// - /// Tests the behavior of + /// Tests the behavior of /// with each of the parameters. /// [Fact] @@ -180,6 +181,7 @@ void AssertUtil(GuildPermission permission, AssertUtil(GuildPermission.CreatePrivateThreads, x => x.CreatePrivateThreads, (p, enable) => p.Modify(createPrivateThreads: enable)); AssertUtil(GuildPermission.UseExternalStickers, x => x.UseExternalStickers, (p, enable) => p.Modify(useExternalStickers: enable)); AssertUtil(GuildPermission.ModerateMembers, x => x.ModerateMembers, (p, enable) => p.Modify(moderateMembers: enable)); + AssertUtil(GuildPermission.UseExternalSounds, x => x.UseExternalSounds, (p, enable) => p.Modify(useExternalSounds: enable)); } } } From 1117bba59d358e9b329be881caaa1d96061c5d59 Mon Sep 17 00:00:00 2001 From: Misha133 Date: Sun, 2 Apr 2023 22:14:14 +0300 Subject: [PATCH 3/9] add CDN method --- src/Discord.Net.Core/CDN.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs index b1ba99c4ca..f004c986f9 100644 --- a/src/Discord.Net.Core/CDN.cs +++ b/src/Discord.Net.Core/CDN.cs @@ -29,6 +29,16 @@ public static string GetTeamIconUrl(ulong teamId, string iconId) public static string GetApplicationIconUrl(ulong appId, string iconId) => iconId != null ? $"{DiscordConfig.CDNUrl}app-icons/{appId}/{iconId}.jpg" : null; + /// + /// Returns a soundboard sound URL. + /// + /// The sound identifier. + /// + /// A URL pointing to the soundboard sound. + /// + public static string GetApplicationIconUrl(ulong soundId) + => $"{DiscordConfig.CDNUrl}soundboard-sounds/{soundId}"; + /// /// Returns a user avatar URL. /// From c7be20c9e3714a2c53ec2fa3824ddde99fe44fd5 Mon Sep 17 00:00:00 2001 From: Misha133 Date: Sun, 2 Apr 2023 23:02:09 +0300 Subject: [PATCH 4/9] default sounds cdn endpoint --- src/Discord.Net.Core/CDN.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs index f004c986f9..b9d8453bb0 100644 --- a/src/Discord.Net.Core/CDN.cs +++ b/src/Discord.Net.Core/CDN.cs @@ -36,9 +36,19 @@ public static string GetApplicationIconUrl(ulong appId, string iconId) /// /// A URL pointing to the soundboard sound. /// - public static string GetApplicationIconUrl(ulong soundId) + public static string GetSoundboardSoundUrl(ulong soundId) => $"{DiscordConfig.CDNUrl}soundboard-sounds/{soundId}"; + /// + /// Returns a soundboard default sound URL. + /// + /// The name of the sound. + /// + /// A URL pointing to the soundboard default sound. + /// + public static string GetSoundboardDefaultSoundUrl(string soundName) + => soundName != null ? $"{DiscordConfig.CDNUrl}soundboard-default-sounds/{soundName}" : null; + /// /// Returns a user avatar URL. /// From 4e1db643fef2a473b50b2195919a1b7465aeda6b Mon Sep 17 00:00:00 2001 From: Misha133 Date: Sun, 16 Apr 2023 00:52:22 +0300 Subject: [PATCH 5/9] add `UseExternalSounds` perm --- src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index 9a1a0bfc4f..101d8b1b4b 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -23,7 +23,7 @@ public struct ChannelPermissions /// /// Gets a that grants all permissions for voice channels. /// - public static readonly ChannelPermissions Voice = new(0b10001_001010_001010_110011_111101_111111_111101_010001); + public static readonly ChannelPermissions Voice = new(0b11001_001010_001010_110011_111101_111111_111101_010001); /// /// Gets a that grants all permissions for stage channels. From 3dc85f39f480575343b16b883b80426360a8f276 Mon Sep 17 00:00:00 2001 From: Misha133 Date: Fri, 9 Jun 2023 19:42:22 +0300 Subject: [PATCH 6/9] create sounds --- .../Entities/Guilds/SoundboardSound.cs | 71 +++++++++++++++++ src/Discord.Net.Core/Entities/Sound.cs | 79 +++++++++++++++++++ .../API/Common/SoundboardSound.cs | 33 ++++++++ .../API/Rest/CreateSoundboardSoundParams.cs | 21 +++++ src/Discord.Net.Rest/API/Sound.cs | 20 +++++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 15 ++++ .../Entities/Guilds/GuildHelper.cs | 28 +++++++ .../Entities/Guilds/RestGuild.cs | 17 ++++ .../Extensions/EntityExtensions.cs | 5 ++ .../Net/Converters/DiscordContractResolver.cs | 2 + .../Net/Converters/SoundConverter.cs | 56 +++++++++++++ 11 files changed, 347 insertions(+) create mode 100644 src/Discord.Net.Core/Entities/Guilds/SoundboardSound.cs create mode 100644 src/Discord.Net.Core/Entities/Sound.cs create mode 100644 src/Discord.Net.Rest/API/Common/SoundboardSound.cs create mode 100644 src/Discord.Net.Rest/API/Rest/CreateSoundboardSoundParams.cs create mode 100644 src/Discord.Net.Rest/API/Sound.cs create mode 100644 src/Discord.Net.Rest/Net/Converters/SoundConverter.cs diff --git a/src/Discord.Net.Core/Entities/Guilds/SoundboardSound.cs b/src/Discord.Net.Core/Entities/Guilds/SoundboardSound.cs new file mode 100644 index 0000000000..815a0206db --- /dev/null +++ b/src/Discord.Net.Core/Entities/Guilds/SoundboardSound.cs @@ -0,0 +1,71 @@ +using System; + +namespace Discord; + +/// +/// Represents a soundboard sound. +/// +public class SoundboardSound : ISnowflakeEntity +{ + /// + public ulong Id { get; } + + /// + public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + + /// + /// + /// + public string Name { get; } + + /// + /// + /// + public ulong AuthorId { get; } + + /// + /// + /// + public IUser Author { get; } + + /// + /// + /// + /// + /// Custom emojis will only have Id property filled due to limited data returned by discord. + /// + public IEmote Emoji { get; } + + /// + /// + /// + public string OverridePath { get; } + + /// + /// + /// + public double Volume { get; } + + /// + /// + /// + public bool? Available { get; } + + internal SoundboardSound(ulong id, string name, ulong authorId, double volume, string overridePath = null, string emojiName = null, ulong? emojiId = null, IUser author = null, bool? available = null) + { + Id = id; + Name = name; + AuthorId = authorId; + Author = author; + Available = available; + OverridePath = overridePath; + Volume = volume; + + if (emojiId is not null) + Emoji = new Emote(emojiId.Value, emojiName, false); + else if (!string.IsNullOrWhiteSpace(emojiName)) + Emoji = new Emoji(emojiName); + else + Emoji = null; + } +} diff --git a/src/Discord.Net.Core/Entities/Sound.cs b/src/Discord.Net.Core/Entities/Sound.cs new file mode 100644 index 0000000000..9a3ce6db31 --- /dev/null +++ b/src/Discord.Net.Core/Entities/Sound.cs @@ -0,0 +1,79 @@ +using System; +using System.IO; + +namespace Discord +{ + /// + /// An sound that will be uploaded to Discord. + /// + public struct Sound : IDisposable + { + private bool _isDisposed; + + /// + /// Gets the stream to be uploaded to Discord. + /// +#pragma warning disable IDISP008 + public Stream Stream { get; } +#pragma warning restore IDISP008 + /// + /// Create the sound with a . + /// + /// + /// The to create the sound with. Note that this must be some type of stream + /// with the contents of a file in it. + /// + public Sound(Stream stream) + { + _isDisposed = false; + Stream = stream; + } + + /// + /// Create the sound from a file path. + /// + /// + /// This file path is NOT validated and is passed directly into a + /// . + /// + /// The path to the file. + /// + /// is a zero-length string, contains only white space, or contains one or more invalid + /// characters as defined by . + /// + /// is null. + /// + /// The specified path, file name, or both exceed the system-defined maximum length. For example, on + /// Windows-based platforms, paths must be less than 248 characters, and file names must be less than 260 + /// characters. + /// + /// is in an invalid format. + /// + /// The specified is invalid, (for example, it is on an unmapped drive). + /// + /// + /// specified a directory.-or- The caller does not have the required permission. + /// + /// The file specified in was not found. + /// + /// An I/O error occurred while opening the file. + public Sound(string path) + { + _isDisposed = false; + Stream = File.OpenRead(path); + } + + /// + public void Dispose() + { + if (!_isDisposed) + { +#pragma warning disable IDISP007 + Stream?.Dispose(); +#pragma warning restore IDISP007 + + _isDisposed = true; + } + } + } +} diff --git a/src/Discord.Net.Rest/API/Common/SoundboardSound.cs b/src/Discord.Net.Rest/API/Common/SoundboardSound.cs new file mode 100644 index 0000000000..90e59280ad --- /dev/null +++ b/src/Discord.Net.Rest/API/Common/SoundboardSound.cs @@ -0,0 +1,33 @@ +using Newtonsoft.Json; + +namespace Discord.API; + +internal class SoundboardSound +{ + [JsonProperty("id")] + public ulong Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("volume")] + public double Volume { get; set; } + + [JsonProperty("emoji_id")] + public Optional EmojiId { get; set; } + + [JsonProperty("emoji_name")] + public Optional EmojiName { get; set; } + + [JsonProperty("override_path")] + public string OverridePath { get; set; } + + [JsonProperty("user_id")] + public ulong UserId { get; set; } + + [JsonProperty("user")] + public Optional User { get; set; } + + [JsonProperty("available")] + public Optional Available { get; set; } +} diff --git a/src/Discord.Net.Rest/API/Rest/CreateSoundboardSoundParams.cs b/src/Discord.Net.Rest/API/Rest/CreateSoundboardSoundParams.cs new file mode 100644 index 0000000000..90cf205aa6 --- /dev/null +++ b/src/Discord.Net.Rest/API/Rest/CreateSoundboardSoundParams.cs @@ -0,0 +1,21 @@ +using Newtonsoft.Json; + +namespace Discord.API.Rest; + +internal class CreateSoundboardSoundParams +{ + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("emoji_id")] + public Optional EmojiId { get; set; } + + [JsonProperty("emoji_name")] + public Optional EmojiName { get; set; } + + [JsonProperty("volume")] + public double Volume { get; set; } + + [JsonProperty("sound")] + public Sound Sound { get; set; } +} diff --git a/src/Discord.Net.Rest/API/Sound.cs b/src/Discord.Net.Rest/API/Sound.cs new file mode 100644 index 0000000000..b29093616b --- /dev/null +++ b/src/Discord.Net.Rest/API/Sound.cs @@ -0,0 +1,20 @@ +using System.IO; + +namespace Discord.API; + +internal class Sound +{ + public Stream Stream { get; } + public string Hash { get; } + + public Sound(Stream stream) + { + Stream = stream; + Hash = null; + } + public Sound(string hash) + { + Stream = null; + Hash = hash; + } +} diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 375beb352c..ab5419a503 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -2676,5 +2676,20 @@ public async Task ModifyUserApplicationRoleConnectionAsync(ulong => await SendJsonAsync("PUT", () => $"users/@me/applications/{applicationId}/role-connection", connection, new BucketIds(), options: options); #endregion + + #region Soundboard + + public async Task CreateSoundboardSoundAsync(ulong guildId, CreateSoundboardSoundParams args, RequestOptions options = null) + { + Preconditions.NotEqual(guildId, 0, nameof(guildId)); + Preconditions.NotNull(args, nameof(args)); + + options = RequestOptions.CreateOrClone(options); + var ids = new BucketIds(guildId: guildId); + + return await SendJsonAsync("POST", () => $"guilds/{guildId}/soundboard-sounds", args, ids, options: options).ConfigureAwait(false); + } + + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 2dc687dcc6..839175b7b2 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -1261,5 +1261,33 @@ public static async Task GetGuildOnboardingAsync(IGuild guild, => await client.ApiClient.GetGuildOnboardingAsync(guild.Id, options); #endregion + + #region Soundboard + + public static async Task CreateSoundboardSoundAsync(BaseDiscordClient client, IGuild guild, Sound sound, string name, double volume, string emojiName = null, ulong? emojiId = null, RequestOptions options = null) + { + var args = new CreateSoundboardSoundParams() + { + Name = name, + Volume = volume, + EmojiName = emojiName ?? Optional.Unspecified, + EmojiId = emojiId ?? Optional.Unspecified, + Sound = sound.ToModel() + }; + + var model = await client.ApiClient.CreateSoundboardSoundAsync(guild.Id, args, options); + + return new SoundboardSound(model.Id, + model.Name, + model.UserId, + model.Volume, + model.OverridePath, + model.Name, + model.EmojiId.GetValueOrDefault(null), + model.User.IsSpecified ? RestUser.Create(client, model.User.Value) : null, + model.Available.IsSpecified ? model.Available.Value : null); + } + + #endregion } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index a6c2d2d998..fe55106ae7 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -1610,5 +1610,22 @@ async Task IGuild.GetOnboardingAsync(RequestOptions options) => await GetOnboardingAsync(options); #endregion + + #region Soundboard + + public Task CreateSoundboardSoundAsync(Sound sound, string name, IEmote emoji = null, double volume = 1.0, RequestOptions options = null) + { + string emojiName = null; + ulong? emojiId = null; + + if (emoji is Emoji emj) + emojiName = emj.Name; + else if (emoji is Emote emt) + emojiId = emt.Id; + + return GuildHelper.CreateSoundboardSoundAsync(Discord, this, sound, name, volume, emojiName, emojiId, options); + } + + #endregion } } diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs index f6ed582477..1440640a52 100644 --- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs @@ -169,6 +169,11 @@ public static API.Image ToModel(this Image entity) return new API.Image(entity.Stream); } + public static API.Sound ToModel(this Sound entity) + { + return new API.Sound(entity.Stream); + } + public static Overwrite ToEntity(this API.Overwrite model) { return new Overwrite(model.TargetId, model.TargetType, new OverwritePermissions(model.Allow, model.Deny)); diff --git a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs index ae16f19c29..e4dc462864 100644 --- a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs +++ b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs @@ -83,6 +83,8 @@ private static JsonConverter GetConverter(JsonProperty property, PropertyInfo pr //Special if (type == typeof(API.Image)) return ImageConverter.Instance; + if (type == typeof(API.Sound)) + return SoundConverter.Instance; if (typeof(IMessageComponent).IsAssignableFrom(type)) return MessageComponentConverter.Instance; if (type == typeof(API.Interaction)) diff --git a/src/Discord.Net.Rest/Net/Converters/SoundConverter.cs b/src/Discord.Net.Rest/Net/Converters/SoundConverter.cs new file mode 100644 index 0000000000..cbc7f23f82 --- /dev/null +++ b/src/Discord.Net.Rest/Net/Converters/SoundConverter.cs @@ -0,0 +1,56 @@ +using Newtonsoft.Json; + +using System; +using System.IO; +using Model = Discord.API.Sound; + +namespace Discord.Net.Converters; + +internal class SoundConverter : JsonConverter +{ + public static readonly SoundConverter Instance = new (); + + public override bool CanConvert(Type objectType) => true; + public override bool CanRead => true; + public override bool CanWrite => true; + + /// Cannot read from sound. + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + throw new InvalidOperationException(); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var sound = (Model)value; + + if (sound.Stream != null) + { + byte[] bytes; + int length; + if (sound.Stream.CanSeek) + { + bytes = new byte[sound.Stream.Length - sound.Stream.Position]; + length = sound.Stream.Read(bytes, 0, bytes.Length); + } + else + { + using (var cloneStream = new MemoryStream()) + { + sound.Stream.CopyTo(cloneStream); + bytes = new byte[cloneStream.Length]; + cloneStream.Position = 0; + cloneStream.Read(bytes, 0, bytes.Length); + length = (int)cloneStream.Length; + } + } + + string base64 = Convert.ToBase64String(bytes, 0, length); + writer.WriteValue($"data:audio/mp3;base64,{base64}"); + } + else if (sound.Hash != null) + writer.WriteValue(sound.Hash); + } + +} + From 4cb9aad82dcc78beed80b40b8c6ef38a50ceb4c3 Mon Sep 17 00:00:00 2001 From: Misha133 Date: Thu, 23 Nov 2023 11:16:58 +0300 Subject: [PATCH 7/9] updatesss --- src/Discord.Net.Core/CDN.cs | 12 +---- .../Entities/Guilds/SoundboardSound.cs | 47 +++++++++++++------ src/Discord.Net.Core/IDiscordClient.cs | 5 ++ .../API/Common/SoundboardSound.cs | 10 ++-- src/Discord.Net.Rest/BaseDiscordClient.cs | 4 ++ src/Discord.Net.Rest/ClientHelper.cs | 11 +++++ src/Discord.Net.Rest/DiscordRestApiClient.cs | 11 ++++- src/Discord.Net.Rest/DiscordRestClient.cs | 4 ++ .../Entities/Guilds/GuildHelper.cs | 10 +--- .../Extensions/EntityExtensions.cs | 15 ++++++ .../API/Gateway/ExtendedGuild.cs | 3 ++ .../DiscordShardedClient.cs | 4 ++ .../DiscordSocketClient.cs | 4 ++ .../Entities/Guilds/SocketGuild.cs | 20 ++++++++ 14 files changed, 119 insertions(+), 41 deletions(-) diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs index 0ff36b9581..16cdf30c0f 100644 --- a/src/Discord.Net.Core/CDN.cs +++ b/src/Discord.Net.Core/CDN.cs @@ -37,17 +37,7 @@ public static string GetApplicationIconUrl(ulong appId, string iconId) /// A URL pointing to the soundboard sound. /// public static string GetSoundboardSoundUrl(ulong soundId) - => $"{DiscordConfig.CDNUrl}soundboard-sounds/{soundId}"; - - /// - /// Returns a soundboard default sound URL. - /// - /// The name of the sound. - /// - /// A URL pointing to the soundboard default sound. - /// - public static string GetSoundboardDefaultSoundUrl(string soundName) - => soundName != null ? $"{DiscordConfig.CDNUrl}soundboard-default-sounds/{soundName}" : null; + => $"{DiscordConfig.CDNUrl}soundboard-sounds/{soundId}.mp3"; /// /// Returns a user avatar URL. diff --git a/src/Discord.Net.Core/Entities/Guilds/SoundboardSound.cs b/src/Discord.Net.Core/Entities/Guilds/SoundboardSound.cs index 815a0206db..a295032c6c 100644 --- a/src/Discord.Net.Core/Entities/Guilds/SoundboardSound.cs +++ b/src/Discord.Net.Core/Entities/Guilds/SoundboardSound.cs @@ -1,35 +1,45 @@ using System; +using System.Diagnostics; namespace Discord; /// /// Represents a soundboard sound. /// +[DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SoundboardSound : ISnowflakeEntity { /// - public ulong Id { get; } + public ulong Id => SoundId; + + /// + /// Gets the Id of the sound. + /// + public ulong SoundId { get; } /// + /// + /// May be inaccurate for default sounds. + /// public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); /// - /// + /// Gets the name of the sound. /// public string Name { get; } /// - /// + /// Gets the Id of the author of the sound. /// public ulong AuthorId { get; } /// - /// + /// Gets the author of the sound. /// public IUser Author { get; } /// - /// + /// Gets the icon of the sound. /// /// /// Custom emojis will only have Id property filled due to limited data returned by discord. @@ -37,28 +47,29 @@ public class SoundboardSound : ISnowflakeEntity public IEmote Emoji { get; } /// - /// + /// Gets the volume of the sound. /// - public string OverridePath { get; } + public double Volume { get; } /// - /// + /// Gets whether the sound is available or not. /// - public double Volume { get; } + public bool? Available { get; } /// - /// + /// Gets the Id of the guild this sound belongs to. if not available. /// - public bool? Available { get; } + public ulong? GuildId { get; } - internal SoundboardSound(ulong id, string name, ulong authorId, double volume, string overridePath = null, string emojiName = null, ulong? emojiId = null, IUser author = null, bool? available = null) + internal SoundboardSound(ulong soundId, string name, ulong authorId, double volume, ulong? guildId = null, + string emojiName = null, ulong? emojiId = null, IUser author = null, bool? available = null) { - Id = id; + GuildId = guildId; + SoundId = soundId; Name = name; AuthorId = authorId; Author = author; Available = available; - OverridePath = overridePath; Volume = volume; if (emojiId is not null) @@ -68,4 +79,12 @@ internal SoundboardSound(ulong id, string name, ulong authorId, double volume, s else Emoji = null; } + + private string DebuggerDisplay => $"{Name} ({SoundId})"; + + /// + /// Gets the url for the sound. + /// + public string GetUrl() + => CDN.GetSoundboardSoundUrl(SoundId); } diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index a87a25c7a9..1b995a5f09 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -349,5 +349,10 @@ IAsyncEnumerable> GetEntitlementsAsync(int? li /// Returns all SKUs for a given application. /// Task> GetSKUsAsync(RequestOptions options = null); + + /// + /// Returns all default soundboard sounds. + /// + Task> GetDefaultSoundboardSoundsAsync(RequestOptions options = null); } } diff --git a/src/Discord.Net.Rest/API/Common/SoundboardSound.cs b/src/Discord.Net.Rest/API/Common/SoundboardSound.cs index 90e59280ad..046b841710 100644 --- a/src/Discord.Net.Rest/API/Common/SoundboardSound.cs +++ b/src/Discord.Net.Rest/API/Common/SoundboardSound.cs @@ -4,8 +4,8 @@ namespace Discord.API; internal class SoundboardSound { - [JsonProperty("id")] - public ulong Id { get; set; } + [JsonProperty("sound_id")] + public ulong SoundId { get; set; } [JsonProperty("name")] public string Name { get; set; } @@ -19,8 +19,8 @@ internal class SoundboardSound [JsonProperty("emoji_name")] public Optional EmojiName { get; set; } - [JsonProperty("override_path")] - public string OverridePath { get; set; } + [JsonProperty("guild_id")] + public Optional GuildId { get; set; } [JsonProperty("user_id")] public ulong UserId { get; set; } @@ -29,5 +29,5 @@ internal class SoundboardSound public Optional User { get; set; } [JsonProperty("available")] - public Optional Available { get; set; } + public Optional IsAvailable { get; set; } } diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index 67a5baa72d..135a43a539 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -283,6 +283,10 @@ IAsyncEnumerable> IDiscordClient.GetEntitlemen /// Task> IDiscordClient.GetSKUsAsync(RequestOptions options) => Task.FromResult>(Array.Empty()); + /// + Task> IDiscordClient.GetDefaultSoundboardSoundsAsync(RequestOptions options) + => Task.FromResult>(ImmutableArray.Create()); + #endregion } } diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index 7d0c7b48ef..1643fde9e4 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -440,5 +440,16 @@ public static async Task> ListSKUsAsync(BaseDiscordClie } #endregion + + #region Soundboard + + public static async Task> GetDefaultSoundboardSoundsAsync(BaseDiscordClient client, RequestOptions options = null) + { + var models = await client.ApiClient.GetDefaultSoundboardSoundsAsync(options).ConfigureAwait(false); + + return models.Select(x => x.ToEntity(discord: client)).ToImmutableArray(); + } + + #endregion } } diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index d7511252f9..70ebbd0398 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -2814,7 +2814,7 @@ public Task ListSKUsAsync(RequestOptions options = null) #region Soundboard - public async Task CreateSoundboardSoundAsync(ulong guildId, CreateSoundboardSoundParams args, RequestOptions options = null) + public Task CreateSoundboardSoundAsync(ulong guildId, CreateSoundboardSoundParams args, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); @@ -2822,7 +2822,14 @@ public async Task CreateSoundboardSoundAsync(ulong guildId, Cre options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(guildId: guildId); - return await SendJsonAsync("POST", () => $"guilds/{guildId}/soundboard-sounds", args, ids, options: options).ConfigureAwait(false); + return SendJsonAsync("POST", () => $"guilds/{guildId}/soundboard-sounds", args, ids, options: options); + } + + public Task GetDefaultSoundboardSoundsAsync(RequestOptions options = null) + { + var ids = new BucketIds(); + + return SendAsync("GET", () => "soundboard-default-sounds", ids: ids, options: options); } #endregion diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index 4d78d6789e..76e4cc29fa 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -296,6 +296,10 @@ public IAsyncEnumerable> GetEntitlementsAsync( public Task> GetSKUsAsync(RequestOptions options = null) => ClientHelper.ListSKUsAsync(this, options); + /// + public Task> GetDefaultSoundboardSoundsAsync(RequestOptions options = null) + => ClientHelper.GetDefaultSoundboardSoundsAsync(this, options); + #endregion #region IDiscordClient diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index b51d2d115d..b0fa8238ff 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -1408,15 +1408,7 @@ public static async Task CreateSoundboardSoundAsync(BaseDiscord var model = await client.ApiClient.CreateSoundboardSoundAsync(guild.Id, args, options); - return new SoundboardSound(model.Id, - model.Name, - model.UserId, - model.Volume, - model.OverridePath, - model.Name, - model.EmojiId.GetValueOrDefault(null), - model.User.IsSpecified ? RestUser.Create(client, model.User.Value) : null, - model.Available.IsSpecified ? model.Available.Value : null); + return model.ToEntity(discord: client); } #endregion diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs index 182f58036c..a3a106be35 100644 --- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs @@ -222,5 +222,20 @@ public static API.Message ToMessage(this API.InteractionResponse model, IDiscord Id = interaction.Id, }; } + + public static SoundboardSound ToEntity(this API.SoundboardSound model, IUser cachedAuthor = null, BaseDiscordClient discord = null) + => new(model.SoundId, + model.Name, + model.UserId, + model.Volume, + model.GuildId.IsSpecified + ? model.GuildId.Value + : null, + model.EmojiName.GetValueOrDefault(null), + model.EmojiId.GetValueOrDefault(null), + cachedAuthor ?? (model.User.IsSpecified + ? RestUser.Create(discord, model.User.Value) + : null), + model.IsAvailable.GetValueOrDefault(true)); } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs b/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs index 04ee38c0bc..d98992f28a 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs @@ -31,5 +31,8 @@ internal class ExtendedGuild : Guild [JsonProperty("guild_scheduled_events")] public GuildScheduledEvent[] GuildScheduledEvents { get; set; } + + [JsonProperty("soundboard_sounds")] + public SoundboardSound[] SoundboardSounds { get; set; } } } diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 155d1fb14a..525396b63e 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -521,6 +521,10 @@ private void RegisterEvents(DiscordSocketClient client, bool isPrimary) client.EntitlementCreated += (arg1) => _entitlementCreated.InvokeAsync(arg1); client.EntitlementUpdated += (arg1, arg2) => _entitlementUpdated.InvokeAsync(arg1, arg2); client.EntitlementDeleted += (arg1) => _entitlementDeleted.InvokeAsync(arg1); + + client.SoundboardSoundCreated += (arg1) => _soundboardSoundCreated.InvokeAsync(arg1); + client.SoundboardSoundUpdated += (arg1) => _soundboardSoundUpdated.InvokeAsync(arg1); + client.SoundboardSoundDeleted += (arg1) => _soundboardSoundDeleted.InvokeAsync(arg1); } public async Task CreateGlobalApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 860f5b9e9c..61e0d7d25d 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -447,6 +447,10 @@ public IAsyncEnumerable> GetEntitlementsAsync( public Task> GetSKUsAsync(RequestOptions options = null) => ClientHelper.ListSKUsAsync(this, options); + /// + public Task> GetDefaultSoundboardSoundsAsync(RequestOptions options = null) + => ClientHelper.GetDefaultSoundboardSoundsAsync(this, options); + /// /// Gets entitlements from cache. /// diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 84c39d4751..a051c17fbf 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -47,6 +47,7 @@ public class SocketGuild : SocketEntity, IGuild, IDisposable private ConcurrentDictionary _stickers; private ConcurrentDictionary _events; private ConcurrentDictionary _automodRules; + private ConcurrentDictionary _soundboardSounds; private ImmutableArray _emotes; private readonly AuditLogCache _auditLogs; @@ -425,6 +426,14 @@ public IReadOnlyCollection Stickers /// public IReadOnlyCollection Events => _events.ToReadOnlyCollection(); + /// + /// Gets a collection of all soundboard sounds in this guild. + /// + /// + /// A read-only collection of soundboard sounds found within this guild. + /// + public IReadOnlyCollection SoundboardSounds => _soundboardSounds.ToReadOnlyCollection(); + internal SocketGuild(DiscordSocketClient client, ulong id) : base(client, id) { @@ -452,6 +461,8 @@ internal void Update(ClientState state, ExtendedModel model) _members = new ConcurrentDictionary(); if (_roles == null) _roles = new ConcurrentDictionary(); + if (_soundboardSounds == null) + _soundboardSounds = new ConcurrentDictionary(); /*if (Emojis == null) _emojis = ImmutableArray.Create(); if (Features == null) @@ -524,6 +535,15 @@ internal void Update(ClientState state, ExtendedModel model) } _events = events; + var soundboardSounds = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.SoundboardSounds.Length * 1.05)); + { + for (var i = 0; i < model.SoundboardSounds.Length; i++) + { + var sound = model.SoundboardSounds[i].ToEntity(GetUser(model.SoundboardSounds[i].UserId), Discord); + soundboardSounds.TryAdd(sound.SoundId, sound); + } + } + _soundboardSounds = soundboardSounds; _syncPromise = new TaskCompletionSource(); _downloaderPromise = new TaskCompletionSource(); From 6681c88e6de1f29f949af3ed8b3ab0774da4934e Mon Sep 17 00:00:00 2001 From: Misha133 Date: Thu, 23 Nov 2023 12:41:06 +0300 Subject: [PATCH 8/9] soundboard eventss --- .../Entities/Guilds/SoundboardSound.cs | 22 +++++----- .../Extensions/EntityExtensions.cs | 2 +- .../Gateway/SoundboardSoundDeletedEvent.cs | 12 ++++++ .../BaseSocketClient.Events.cs | 16 ++++---- .../DiscordShardedClient.cs | 6 +-- .../DiscordSocketClient.cs | 27 ++++++++++++- .../Entities/Guilds/SocketGuild.cs | 40 +++++++++++++++++++ 7 files changed, 102 insertions(+), 23 deletions(-) create mode 100644 src/Discord.Net.WebSocket/API/Gateway/SoundboardSoundDeletedEvent.cs diff --git a/src/Discord.Net.Core/Entities/Guilds/SoundboardSound.cs b/src/Discord.Net.Core/Entities/Guilds/SoundboardSound.cs index a295032c6c..72a94fcab9 100644 --- a/src/Discord.Net.Core/Entities/Guilds/SoundboardSound.cs +++ b/src/Discord.Net.Core/Entities/Guilds/SoundboardSound.cs @@ -15,7 +15,7 @@ public class SoundboardSound : ISnowflakeEntity /// /// Gets the Id of the sound. /// - public ulong SoundId { get; } + public ulong SoundId { get; internal set; } /// /// @@ -26,17 +26,17 @@ public class SoundboardSound : ISnowflakeEntity /// /// Gets the name of the sound. /// - public string Name { get; } + public string Name { get; internal set; } /// /// Gets the Id of the author of the sound. /// - public ulong AuthorId { get; } + public ulong AuthorId { get; internal set; } /// /// Gets the author of the sound. /// - public IUser Author { get; } + public IUser Author { get; internal set; } /// /// Gets the icon of the sound. @@ -44,32 +44,32 @@ public class SoundboardSound : ISnowflakeEntity /// /// Custom emojis will only have Id property filled due to limited data returned by discord. /// - public IEmote Emoji { get; } + public IEmote Emoji { get; internal set; } /// /// Gets the volume of the sound. /// - public double Volume { get; } + public double Volume { get; internal set; } /// /// Gets whether the sound is available or not. /// - public bool? Available { get; } + public bool? IsAvailable { get; internal set; } /// /// Gets the Id of the guild this sound belongs to. if not available. /// - public ulong? GuildId { get; } + public ulong? GuildId { get; internal set; } internal SoundboardSound(ulong soundId, string name, ulong authorId, double volume, ulong? guildId = null, - string emojiName = null, ulong? emojiId = null, IUser author = null, bool? available = null) + string emojiName = null, ulong? emojiId = null, IUser author = null, bool? isAvailable = null) { GuildId = guildId; SoundId = soundId; Name = name; AuthorId = authorId; Author = author; - Available = available; + IsAvailable = isAvailable; Volume = volume; if (emojiId is not null) @@ -82,6 +82,8 @@ internal SoundboardSound(ulong soundId, string name, ulong authorId, double volu private string DebuggerDisplay => $"{Name} ({SoundId})"; + internal SoundboardSound Clone() => (SoundboardSound)MemberwiseClone(); + /// /// Gets the url for the sound. /// diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs index a3a106be35..110917586a 100644 --- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs @@ -236,6 +236,6 @@ public static SoundboardSound ToEntity(this API.SoundboardSound model, IUser cac cachedAuthor ?? (model.User.IsSpecified ? RestUser.Create(discord, model.User.Value) : null), - model.IsAvailable.GetValueOrDefault(true)); + model.IsAvailable.GetValueOrDefault(false)); } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/SoundboardSoundDeletedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/SoundboardSoundDeletedEvent.cs new file mode 100644 index 0000000000..67aa31dd76 --- /dev/null +++ b/src/Discord.Net.WebSocket/API/Gateway/SoundboardSoundDeletedEvent.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Discord.API.Gateway; + +internal class SoundboardSoundDeletedEvent +{ + [JsonProperty("guild_id")] + public ulong GuildId { get; set; } + + [JsonProperty("sound_id")] + public ulong SoundId { get; set; } +} diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index df19d45381..9c426a15c5 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -1028,32 +1028,32 @@ public event Func, Task> VoiceCha /// /// Fired when a soundboard sound is created. /// - public event Func SoundboardSoundCreated + public event Func SoundboardSoundCreated { add => _soundboardSoundCreated.Add(value); remove => _soundboardSoundCreated.Remove(value); } - internal readonly AsyncEvent> _soundboardSoundCreated = new(); + internal readonly AsyncEvent> _soundboardSoundCreated = new(); /// - /// Fired when a soundboard sound is created. + /// Fired when a soundboard sound is updated. /// - public event Func SoundboardSoundUpdated + public event Func, SoundboardSound, Task> SoundboardSoundUpdated { add => _soundboardSoundUpdated.Add(value); remove => _soundboardSoundUpdated.Remove(value); } - internal readonly AsyncEvent> _soundboardSoundUpdated = new(); + internal readonly AsyncEvent, SoundboardSound, Task>> _soundboardSoundUpdated = new(); /// - /// Fired when a soundboard sound is created. + /// Fired when a soundboard sound is deleted. /// - public event Func SoundboardSoundDeleted + public event Func, Task> SoundboardSoundDeleted { add => _soundboardSoundDeleted.Add(value); remove => _soundboardSoundDeleted.Remove(value); } - internal readonly AsyncEvent> _soundboardSoundDeleted = new(); + internal readonly AsyncEvent, Task>> _soundboardSoundDeleted = new(); #endregion } diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index 525396b63e..2334507536 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -522,9 +522,9 @@ private void RegisterEvents(DiscordSocketClient client, bool isPrimary) client.EntitlementUpdated += (arg1, arg2) => _entitlementUpdated.InvokeAsync(arg1, arg2); client.EntitlementDeleted += (arg1) => _entitlementDeleted.InvokeAsync(arg1); - client.SoundboardSoundCreated += (arg1) => _soundboardSoundCreated.InvokeAsync(arg1); - client.SoundboardSoundUpdated += (arg1) => _soundboardSoundUpdated.InvokeAsync(arg1); - client.SoundboardSoundDeleted += (arg1) => _soundboardSoundDeleted.InvokeAsync(arg1); + client.SoundboardSoundCreated += (arg1, arg2) => _soundboardSoundCreated.InvokeAsync(arg1, arg2); + client.SoundboardSoundUpdated += (arg1, arg2, arg3) => _soundboardSoundUpdated.InvokeAsync(arg1, arg2, arg3); + client.SoundboardSoundDeleted += (arg1, arg2) => _soundboardSoundDeleted.InvokeAsync(arg1, arg2); } public async Task CreateGlobalApplicationCommandAsync(ApplicationCommandProperties properties, RequestOptions options = null) diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 61e0d7d25d..639ef579c1 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -3176,19 +3176,44 @@ private async Task ProcessMessageAsync(GatewayOpCode opCode, int? seq, string ty case "GUILD_SOUNDBOARD_SOUND_CREATE": { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SOUNDBOARD_SOUND_CREATE)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); + var guild = GetGuild(data.GuildId.Value); + + var sound = guild.AddOrUpdateSoundboardSound(data); + + await TimedInvokeAsync(_soundboardSoundCreated, nameof(SoundboardSoundCreated), guild, sound); } break; case "GUILD_SOUNDBOARD_SOUND_UPDATE": { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SOUNDBOARD_SOUND_UPDATE)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); + + var guild = GetGuild(data.GuildId.Value); + + var before = guild.GetSoundboardSound(data.SoundId)?.Clone(); + var beforeCacheable = new Cacheable(before, data.SoundId, before is not null, () => null); + var after = guild.AddOrUpdateSoundboardSound(data); + + await TimedInvokeAsync(_soundboardSoundUpdated, nameof(SoundboardSoundUpdated), guild, beforeCacheable, after); } break; - case "GUILD_SOUNDBOARD_SOUND_DELETE ": + case "GUILD_SOUNDBOARD_SOUND_DELETE": { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SOUNDBOARD_SOUND_DELETE)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); + + var guild = GetGuild(data.GuildId); + + var sound = guild.RemoveSoundboardSound(data.SoundId); + var soundCacheable = new Cacheable(sound, data.SoundId, sound is not null, () => null); + await TimedInvokeAsync(_soundboardSoundDeleted, nameof(SoundboardSoundDeleted), guild, soundCacheable); } break; diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index a051c17fbf..3877bf5435 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -26,6 +26,7 @@ using StickerModel = Discord.API.Sticker; using UserModel = Discord.API.User; using VoiceStateModel = Discord.API.VoiceState; +using SoundboardSoundModel = Discord.API.SoundboardSound; namespace Discord.WebSocket { @@ -2031,6 +2032,45 @@ public async Task ModifyOnboardingAsync(Action + /// Gets a soundboard cached sound from this guild. + /// + public SoundboardSound GetSoundboardSound(ulong id) + => _soundboardSounds.TryGetValue(id, out var sound) ? sound : null; + + internal SoundboardSound RemoveSoundboardSound(ulong id) + => _soundboardSounds.TryRemove(id, out var value) ? value : null; + + internal SoundboardSound AddOrUpdateSoundboardSound(SoundboardSoundModel model) + { + if (_soundboardSounds.TryGetValue(model.SoundId, out var value)) + { + value.Volume = model.Volume; + value.Name = model.Name; + value.IsAvailable = model.IsAvailable.GetValueOrDefault(false); + value.Author = (IUser)GetUser(model.UserId) ?? (model.User.IsSpecified + ? RestUser.Create(Discord, model.User.Value) + : null); + + if (model.EmojiId is { IsSpecified: true, Value: not null }) + value.Emoji = new Emote(model.EmojiId.Value.Value, model.EmojiName.GetValueOrDefault(null), false); + else if (!string.IsNullOrWhiteSpace(model.EmojiName.GetValueOrDefault(null))) + value.Emoji = new Emoji(model.EmojiName.GetValueOrDefault(null)); + else + value.Emoji = null; + } + else + { + value = model.ToEntity(GetUser(model.UserId), Discord); + _soundboardSounds[model.SoundId] = value; + } + return value; + } + + #endregion + #region IGuild /// ulong? IGuild.AFKChannelId => AFKChannelId; From 0d1b054d032743aebad8b7df02d21e26fceb534a Mon Sep 17 00:00:00 2001 From: Misha133 Date: Sun, 3 Dec 2023 19:39:03 +0300 Subject: [PATCH 9/9] typo --- src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 3877bf5435..9e5f9caa16 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -2035,7 +2035,7 @@ public async Task ModifyOnboardingAsync(Action - /// Gets a soundboard cached sound from this guild. + /// Gets a cached soundboard sound from this guild. /// public SoundboardSound GetSoundboardSound(ulong id) => _soundboardSounds.TryGetValue(id, out var sound) ? sound : null;