From d93aa122965b6d9b11f51dac377ee3c13eefac97 Mon Sep 17 00:00:00 2001 From: Lexie Date: Wed, 16 Oct 2024 13:08:06 +0200 Subject: [PATCH] add support for soundboard (#708) --- lib/src/builders/channel/forum_tag.dart | 4 +- lib/src/builders/channel/guild_channel.dart | 15 +- lib/src/builders/guild/welcome_screen.dart | 4 +- lib/src/builders/image.dart | 2 +- lib/src/builders/sentinels.dart | 2 + lib/src/builders/sound.dart | 38 ++++ lib/src/builders/soundboard.dart | 47 +++++ lib/src/client_options.dart | 11 +- lib/src/errors.dart | 7 + lib/src/event_mixin.dart | 16 ++ lib/src/gateway/gateway.dart | 69 +++++++ lib/src/http/managers/soundboard_manager.dart | 194 ++++++++++++++++++ lib/src/http/route.dart | 9 + lib/src/manager_mixin.dart | 4 + lib/src/models/gateway/events/soundboard.dart | 69 +++++++ lib/src/models/gateway/events/voice.dart | 64 ++++++ lib/src/models/guild/guild.dart | 4 + lib/src/models/soundboard/soundboard.dart | 54 +++++ lib/src/utils/building_helpers.dart | 6 + lib/src/utils/cache_helpers.dart | 11 + test/files/sound.ogg | Bin 0 -> 82375 bytes test/integration/rest_integration_test.dart | 25 +++ test/unit/builders/sound_test.dart | 15 ++ .../managers/soundboard_manager_test.dart | 98 +++++++++ 24 files changed, 750 insertions(+), 18 deletions(-) create mode 100644 lib/src/builders/sound.dart create mode 100644 lib/src/builders/soundboard.dart create mode 100644 lib/src/http/managers/soundboard_manager.dart create mode 100644 lib/src/models/gateway/events/soundboard.dart create mode 100644 lib/src/models/soundboard/soundboard.dart create mode 100644 lib/src/utils/building_helpers.dart create mode 100644 test/files/sound.ogg create mode 100644 test/unit/builders/sound_test.dart create mode 100644 test/unit/http/managers/soundboard_manager_test.dart diff --git a/lib/src/builders/channel/forum_tag.dart b/lib/src/builders/channel/forum_tag.dart index 3542fca88..a702e4ade 100644 --- a/lib/src/builders/channel/forum_tag.dart +++ b/lib/src/builders/channel/forum_tag.dart @@ -1,6 +1,7 @@ import 'package:nyxx/src/builders/builder.dart'; import 'package:nyxx/src/models/channel/types/forum.dart'; import 'package:nyxx/src/models/snowflake.dart'; +import 'package:nyxx/src/utils/building_helpers.dart'; class ForumTagBuilder extends CreateBuilder { String name; @@ -17,7 +18,6 @@ class ForumTagBuilder extends CreateBuilder { Map build() => { 'name': name, if (isModerated != null) 'moderated': isModerated, - if (emojiId != null) 'emoji_id': emojiId!.toString(), - if (emojiName != null) 'emoji_name': emojiName, + ...makeEmojiMap(emojiId: emojiId, emojiName: emojiName), }; } diff --git a/lib/src/builders/channel/guild_channel.dart b/lib/src/builders/channel/guild_channel.dart index 6016f440c..442f4fd6c 100644 --- a/lib/src/builders/channel/guild_channel.dart +++ b/lib/src/builders/channel/guild_channel.dart @@ -11,6 +11,7 @@ import 'package:nyxx/src/models/channel/types/guild_voice.dart'; import 'package:nyxx/src/models/channel/voice_channel.dart'; import 'package:nyxx/src/models/permission_overwrite.dart'; import 'package:nyxx/src/models/snowflake.dart'; +import 'package:nyxx/src/utils/building_helpers.dart'; import 'package:nyxx/src/utils/flags.dart'; class GuildChannelBuilder extends CreateBuilder { @@ -231,12 +232,7 @@ class ForumChannelBuilder extends GuildChannelBuilder { if (isNsfw != null) 'nsfw': isNsfw, if (defaultAutoArchiveDuration != null) 'default_auto_archive_duration': defaultAutoArchiveDuration!.inMinutes, if (!identical(defaultReaction, sentinelDefaultReaction)) - 'default_reaction_emoji': defaultReaction == null - ? null - : { - if (defaultReaction!.emojiId != null) 'emoji_id': defaultReaction!.emojiId!.toString(), - if (defaultReaction!.emojiName != null) 'emoji_name': defaultReaction!.emojiName, - }, + 'default_reaction_emoji': defaultReaction == null ? null : makeEmojiMap(emojiId: defaultReaction!.emojiId, emojiName: defaultReaction!.emojiName), if (tags != null) 'available_tags': tags!.map((e) => e.build()).toList(), if (defaultSortOrder != null) 'default_sort_order': defaultSortOrder!.value, }; @@ -293,12 +289,7 @@ class ForumChannelUpdateBuilder extends GuildChannelUpdateBuilder if (flags != null) 'flags': flags!.value, if (tags != null) 'available_tags': tags!.map((e) => e.build()).toList(), if (!identical(defaultReaction, sentinelDefaultReaction)) - 'default_reaction_emoji': defaultReaction == null - ? null - : { - if (defaultReaction!.emojiId != null) 'emoji_id': defaultReaction!.emojiId!.toString(), - if (defaultReaction!.emojiName != null) 'emoji_name': defaultReaction!.emojiName, - }, + 'default_reaction_emoji': defaultReaction == null ? null : makeEmojiMap(emojiId: defaultReaction!.emojiId, emojiName: defaultReaction!.emojiName), if (defaultThreadRateLimitPerUser != null) 'default_thread_rate_limit_per_user': defaultThreadRateLimitPerUser!.inSeconds, if (defaultSortOrder != null) 'default_sort_order': defaultSortOrder!.value, if (defaultLayout != null) 'default_forum_layout': defaultLayout!.value, diff --git a/lib/src/builders/guild/welcome_screen.dart b/lib/src/builders/guild/welcome_screen.dart index 8ea232fb0..f852d3feb 100644 --- a/lib/src/builders/guild/welcome_screen.dart +++ b/lib/src/builders/guild/welcome_screen.dart @@ -1,6 +1,7 @@ import 'package:nyxx/src/builders/builder.dart'; import 'package:nyxx/src/builders/sentinels.dart'; import 'package:nyxx/src/models/guild/welcome_screen.dart'; +import 'package:nyxx/src/utils/building_helpers.dart'; class WelcomeScreenUpdateBuilder extends UpdateBuilder { bool? isEnabled; @@ -20,8 +21,7 @@ class WelcomeScreenUpdateBuilder extends UpdateBuilder { { 'channel_id': channel.channelId.toString(), 'description': channel.description, - 'emoji_id': channel.emojiId?.toString(), - 'emoji_name': channel.emojiName, + ...makeEmojiMap(emojiId: channel.emojiId, emojiName: channel.emojiName), }, ], if (!identical(description, sentinelString)) 'description': description, diff --git a/lib/src/builders/image.dart b/lib/src/builders/image.dart index 4759df255..6e414a3e8 100644 --- a/lib/src/builders/image.dart +++ b/lib/src/builders/image.dart @@ -16,7 +16,7 @@ class ImageBuilder { ImageBuilder.gif(this.data) : format = 'gif'; static Future fromFile(File file, {String? format}) async { - format ??= p.extension(file.path); + format ??= p.extension(file.path).substring(1); const formats = { 'png': 'png', diff --git a/lib/src/builders/sentinels.dart b/lib/src/builders/sentinels.dart index 478c64c47..6e57ecf53 100644 --- a/lib/src/builders/sentinels.dart +++ b/lib/src/builders/sentinels.dart @@ -9,6 +9,8 @@ import 'package:nyxx/src/utils/flags.dart'; // ASCII encoded "nyxx" const sentinelInteger = 0x6E797878; +const sentinelDouble = sentinelInteger * 1.0; + // ESC-"nyxx" const sentinelString = '\u{1B}nyxx'; diff --git a/lib/src/builders/sound.dart b/lib/src/builders/sound.dart new file mode 100644 index 000000000..d3b26b34b --- /dev/null +++ b/lib/src/builders/sound.dart @@ -0,0 +1,38 @@ +import 'dart:convert'; +import 'dart:io'; +import 'package:path/path.dart' as p; + +class SoundBuilder { + List data; + String format; + + SoundBuilder({required this.data, required this.format}); + + SoundBuilder.mp3(this.data) : format = 'mpeg'; + + SoundBuilder.ogg(this.data) : format = 'ogg'; + + static Future fromFile(File file, {String? format}) async { + format ??= p.extension(file.path).substring(1); + + const formats = { + 'mp3': 'mpeg', + 'mpeg': 'mpeg', + 'ogg': 'ogg', + }; + + final actualFormat = formats[format]; + + if (actualFormat == null) { + throw ArgumentError.value(format, 'format', 'Unsupported format'); + } + + final data = await file.readAsBytes(); + + return SoundBuilder(data: data, format: actualFormat); + } + + List buildRawData() => data; + + String buildDataString() => 'data:audio/$format;base64,${base64Encode(data)}'; +} diff --git a/lib/src/builders/soundboard.dart b/lib/src/builders/soundboard.dart new file mode 100644 index 000000000..68fce32e2 --- /dev/null +++ b/lib/src/builders/soundboard.dart @@ -0,0 +1,47 @@ +import 'package:nyxx/src/builders/builder.dart'; +import 'package:nyxx/src/builders/sentinels.dart'; +import 'package:nyxx/src/builders/sound.dart'; +import 'package:nyxx/src/models/snowflake.dart'; +import 'package:nyxx/src/models/soundboard/soundboard.dart'; +import 'package:nyxx/src/utils/building_helpers.dart'; + +class SoundboardSoundBuilder extends CreateBuilder { + String name; + + SoundBuilder sound; + + double? volume; + + String? emojiName; + + Snowflake? emojiId; + + SoundboardSoundBuilder({required this.name, required this.sound, this.volume, this.emojiName, this.emojiId}); + + @override + Map build() => { + 'name': name, + 'sound': sound.buildDataString(), + if (volume != null) 'volume': volume, + ...makeEmojiMap(emojiId: emojiId, emojiName: emojiName), + }; +} + +class SoundboardSoundUpdateBuilder extends UpdateBuilder { + String name; + + double? volume; + + String? emojiName; + + Snowflake? emojiId; + + SoundboardSoundUpdateBuilder({required this.name, this.volume = sentinelDouble, this.emojiName = sentinelString, this.emojiId = sentinelSnowflake}); + + @override + Map build() => { + 'name': name, + if (volume != sentinelDouble) 'volume': volume, + if (!(identical(emojiName, sentinelString) || identical(emojiId, sentinelSnowflake))) ...makeEmojiMap(emojiId: emojiId, emojiName: emojiName), + }; +} diff --git a/lib/src/client_options.dart b/lib/src/client_options.dart index 6c601f9c3..399576c45 100644 --- a/lib/src/client_options.dart +++ b/lib/src/client_options.dart @@ -16,6 +16,7 @@ import 'package:nyxx/src/models/guild/scheduled_event.dart'; import 'package:nyxx/src/models/message/message.dart'; import 'package:nyxx/src/models/role.dart'; import 'package:nyxx/src/models/sku.dart'; +import 'package:nyxx/src/models/soundboard/soundboard.dart'; import 'package:nyxx/src/models/sticker/global_sticker.dart'; import 'package:nyxx/src/models/sticker/guild_sticker.dart'; import 'package:nyxx/src/models/subscription.dart'; @@ -143,9 +144,15 @@ class RestClientOptions extends ClientOptions { /// The [CacheConfig] to use for the [Application.skus] manager. final CacheConfig skuConfig; - /// Tje [CacheConfig] to use for the [Sku.subscriptions] manager. + /// The [CacheConfig] to use for the [Sku.subscriptions] manager. final CacheConfig subscriptionConfig; + /// The [CacheConfig] to use for the [NyxxRest.soundboard] manager. + final CacheConfig globalSoundboardCacheConfig; + + /// The [CacheConfig] to use for the [Guild.soundboard] manager. + final CacheConfig soundboardCacheConfig; + /// Create a new [RestClientOptions]. const RestClientOptions({ super.plugins, @@ -187,6 +194,8 @@ class RestClientOptions extends ClientOptions { this.entitlementConfig = _defaultCacheConfig, this.skuConfig = _defaultCacheConfig, this.subscriptionConfig = _defaultCacheConfig, + this.globalSoundboardCacheConfig = _smallCacheConfig, + this.soundboardCacheConfig = _smallCacheConfig, }); } diff --git a/lib/src/errors.dart b/lib/src/errors.dart index 62d01fb8c..2796b425d 100644 --- a/lib/src/errors.dart +++ b/lib/src/errors.dart @@ -157,3 +157,10 @@ class ClientClosedError extends Error { @override String toString() => 'Client is closed'; } + +class SoundboardSoundNotFoundException extends NyxxException { + /// The ID of the sound. + final Snowflake soundId; + + SoundboardSoundNotFoundException(this.soundId) : super('Soundboard sound $soundId not found'); +} diff --git a/lib/src/event_mixin.dart b/lib/src/event_mixin.dart index 5912958e5..742f7d7ff 100644 --- a/lib/src/event_mixin.dart +++ b/lib/src/event_mixin.dart @@ -13,6 +13,7 @@ import 'package:nyxx/src/models/gateway/events/invite.dart'; import 'package:nyxx/src/models/gateway/events/message.dart'; import 'package:nyxx/src/models/gateway/events/presence.dart'; import 'package:nyxx/src/models/gateway/events/ready.dart'; +import 'package:nyxx/src/models/gateway/events/soundboard.dart'; import 'package:nyxx/src/models/gateway/events/stage_instance.dart'; import 'package:nyxx/src/models/gateway/events/voice.dart'; import 'package:nyxx/src/models/gateway/events/webhook.dart'; @@ -261,4 +262,19 @@ mixin EventMixin implements Nyxx { /// A [Stream] of [MessagePollVoteRemoveEvent]s received by this client. Stream get onMessagePollVoteRemove => onEvent.whereType(); + + /// A [Stream] of [SoundboardSoundDeleteEvent]s received by this client. + Stream get onSoundboardSoundDelete => onEvent.whereType(); + + /// A [Stream] of [SoundboardSoundUpdateEvent]s received by this client. + Stream get onSoundboardSoundUpdate => onEvent.whereType(); + + /// A [Stream] of [SoundboardSoundCreateEvent]s received by this client. + Stream get onSoundboardSoundCreate => onEvent.whereType(); + + /// A [Stream] of [SoundboardSoundsUpdateEvent]s received by this client. + Stream get onSoundboardSoundsUpdate => onEvent.whereType(); + + /// A [Stream] of [VoiceChannelEffectSendEvent]s received by this client. + Stream get onVoiceChannelEffectSend => onEvent.whereType(); } diff --git a/lib/src/gateway/gateway.dart b/lib/src/gateway/gateway.dart index 226a8ca0c..9f03f9b3a 100644 --- a/lib/src/gateway/gateway.dart +++ b/lib/src/gateway/gateway.dart @@ -18,6 +18,7 @@ import 'package:nyxx/src/models/channel/guild_channel.dart'; import 'package:nyxx/src/models/channel/text_channel.dart'; import 'package:nyxx/src/models/channel/thread.dart'; import 'package:nyxx/src/models/gateway/events/entitlement.dart'; +import 'package:nyxx/src/models/gateway/events/soundboard.dart'; import 'package:nyxx/src/models/gateway/gateway.dart'; import 'package:nyxx/src/models/gateway/event.dart'; import 'package:nyxx/src/models/gateway/events/application_command.dart'; @@ -284,6 +285,7 @@ class Gateway extends GatewayManager with EventParser { 'PRESENCE_UPDATE': parsePresenceUpdate, 'TYPING_START': parseTypingStart, 'USER_UPDATE': parseUserUpdate, + 'VOICE_CHANNEL_EFFECT_SEND': parseVoiceChannelEffectSend, 'VOICE_STATE_UPDATE': parseVoiceStateUpdate, 'VOICE_SERVER_UPDATE': parseVoiceServerUpdate, 'WEBHOOKS_UPDATE': parseWebhooksUpdate, @@ -296,6 +298,10 @@ class Gateway extends GatewayManager with EventParser { 'ENTITLEMENT_DELETE': parseEntitlementDelete, 'MESSAGE_POLL_VOTE_ADD': parseMessagePollVoteAdd, 'MESSAGE_POLL_VOTE_REMOVE': parseMessagePollVoteRemove, + 'GUILD_SOUNDBOARD_SOUND_CREATE': parseSoundboardSoundCreate, + 'GUILD_SOUNDBOARD_SOUND_UPDATE': parseSoundboardSoundUpdate, + 'GUILD_SOUNDBOARD_SOUND_DELETE': parseSoundboardSoundDelete, + 'GUILD_SOUNDBOARD_SOUNDS_UPDATE': parseSoundboardSoundsUpdate, }; return mapping[raw.name]?.call(raw.payload) ?? UnknownDispatchEvent(gateway: this, raw: raw); @@ -984,6 +990,23 @@ class Gateway extends GatewayManager with EventParser { ); } + /// Parse a [VoiceChannelEffectSendEvent] from [raw]. + VoiceChannelEffectSendEvent parseVoiceChannelEffectSend(Map raw) { + final guildId = Snowflake.parse(raw['guild_id']!); + + return VoiceChannelEffectSendEvent( + gateway: this, + channelId: Snowflake.parse(raw['channel_id']!), + guildId: guildId, + userId: Snowflake.parse(raw['user_id']!), + emoji: maybeParse(raw['emoji'], client.guilds[guildId].emojis.parse), + animationType: maybeParse(raw['animation_type'], AnimationType.new), + animationId: raw['animation_id'] as int?, + soundId: maybeParse(raw['sound_id'], Snowflake.parse), + soundVolume: raw['sound_volume'] as double?, + ); + } + /// Parse a [VoiceStateUpdateEvent] from [raw]. VoiceStateUpdateEvent parseVoiceStateUpdate(Map raw) { final voiceState = client.voice.parseVoiceState(raw); @@ -1162,6 +1185,52 @@ class Gateway extends GatewayManager with EventParser { } } + SoundboardSoundCreateEvent parseSoundboardSoundCreate(Map raw) { + final guildId = maybeParse(raw['guild_id'], Snowflake.parse); + + return SoundboardSoundCreateEvent( + gateway: this, + sound: client.guilds[guildId ?? Snowflake.zero].soundboard.parse(raw), + ); + } + + SoundboardSoundUpdateEvent parseSoundboardSoundUpdate(Map raw) { + final guildId = maybeParse(raw['guild_id'], Snowflake.parse); + + return SoundboardSoundUpdateEvent( + gateway: this, + oldSound: client.guilds[guildId ?? Snowflake.zero].soundboard.cache[Snowflake.parse(raw['sound_id']!)], + sound: client.guilds[guildId ?? Snowflake.zero].soundboard.parse(raw), + ); + } + + SoundboardSoundDeleteEvent parseSoundboardSoundDelete(Map raw) { + final guildId = Snowflake.parse(raw['guild_id']!); + final soundId = Snowflake.parse(raw['sound_id']!); + + return SoundboardSoundDeleteEvent( + gateway: this, + sound: client.guilds[guildId].soundboard.cache[soundId], + guildId: guildId, + soundId: soundId, + ); + } + + SoundboardSoundsUpdateEvent parseSoundboardSoundsUpdate(Map raw) { + final guildId = Snowflake.parse(raw['guild_id']!); + + final sounds = parseMany(raw['sounds'] as List, client.guilds[guildId].soundboard.parse); + + final oldSounds = sounds.map((sound) => client.guilds[guildId].soundboard.cache[sound.id]).toList(); + + return SoundboardSoundsUpdateEvent( + gateway: this, + guildId: guildId, + sounds: sounds, + oldSounds: oldSounds, + ); + } + /// Update the client's voice state in the guild with ID [guildId]. void updateVoiceState(Snowflake guildId, GatewayVoiceStateBuilder builder) => shardFor(guildId).updateVoiceState(guildId, builder); diff --git a/lib/src/http/managers/soundboard_manager.dart b/lib/src/http/managers/soundboard_manager.dart new file mode 100644 index 000000000..d4cc96877 --- /dev/null +++ b/lib/src/http/managers/soundboard_manager.dart @@ -0,0 +1,194 @@ +import 'dart:convert'; + +import 'package:nyxx/src/builders/soundboard.dart'; +import 'package:nyxx/src/errors.dart'; +import 'package:nyxx/src/http/managers/manager.dart'; +import 'package:nyxx/src/http/request.dart'; +import 'package:nyxx/src/http/route.dart'; +import 'package:nyxx/src/models/emoji.dart'; +import 'package:nyxx/src/models/snowflake.dart'; +import 'package:nyxx/src/models/soundboard/soundboard.dart'; +import 'package:nyxx/src/utils/cache_helpers.dart'; +import 'package:nyxx/src/utils/parsing_helpers.dart'; + +abstract class SoundboardManager extends Manager { + SoundboardManager(super.config, super.client, {required super.identifier}); + + @override + SoundboardSound parse(Map raw) { + final guildId = maybeParse(raw['guild_id'], Snowflake.parse); + + Emoji? emoji = maybeParse(raw['emoji_name'], (emoji) => client.guilds[guildId ?? Snowflake.zero].emojis.parse({'id': null, 'name': emoji})); + + final emojiId = maybeParse(raw['emoji_id'], Snowflake.parse); + + emoji ??= client.guilds[guildId ?? Snowflake.zero].emojis.cache[emojiId]; + + return SoundboardSound( + id: Snowflake.parse(raw['sound_id']!), + manager: this, + name: raw['name'] as String, + volume: (raw['volume'] as num).toDouble(), + emoji: emoji, + emojiName: raw['emoji_name'] as String?, + emojiId: emojiId, + guildId: guildId, + isAvailable: raw['available'] as bool, + user: maybeParse(raw['user'], client.users.parse), + ); + } + + Future> list(); +} + +/// A [Manager] for guild [SoundboardSound]s +class GuildSoundboardManager extends SoundboardManager { + /// The guild this manager is for. + final Snowflake guildId; + + GuildSoundboardManager(super.config, super.client, {required this.guildId}) : super(identifier: '$guildId.soundboard'); + + @override + PartialSoundboardSound operator [](Snowflake id) => PartialSoundboardSound(id: id, manager: this); + + @override + Future fetch(Snowflake id) async { + final route = HttpRoute() + ..guilds(id: guildId.toString()) + ..soundboardSounds(id: id.toString()); + + final request = BasicRequest(route); + + final response = await client.httpHandler.executeSafe(request); + + final sound = parse(response.jsonBody as Map); + client.updateCacheWith(sound); + + return sound; + } + + @override + Future> list() async { + final route = HttpRoute() + ..guilds(id: guildId.toString()) + ..soundboardSounds(); + + final request = BasicRequest(route); + + final response = await client.httpHandler.executeSafe(request); + + final raw = (response.jsonBody as Map)['items'] as List; + + final sounds = parseMany(raw, parse); + + sounds.forEach(client.updateCacheWith); + + return sounds; + } + + @override + Future create(SoundboardSoundBuilder builder, {String? auditLogReason}) async { + final route = HttpRoute() + ..guilds(id: guildId.toString()) + ..soundboardSounds(); + + final request = BasicRequest(route, method: 'POST', auditLogReason: auditLogReason, body: jsonEncode(builder.build())); + + final response = await client.httpHandler.executeSafe(request); + + final soundboard = parse(response.jsonBody as Map); + + client.updateCacheWith(soundboard); + + return soundboard; + } + + @override + Future update(Snowflake id, SoundboardSoundUpdateBuilder builder, {String? auditLogReason}) async { + final route = HttpRoute() + ..guilds(id: guildId.toString()) + ..soundboardSounds(id: id.toString()); + + final request = BasicRequest(route, method: 'PATCH', auditLogReason: auditLogReason, body: jsonEncode(builder.build())); + + final response = await client.httpHandler.executeSafe(request); + + final soundboard = parse(response.jsonBody as Map); + + client.updateCacheWith(soundboard); + + return soundboard; + } + + @override + Future delete(Snowflake id, {String? auditLogReason}) async { + final route = HttpRoute() + ..guilds(id: guildId.toString()) + ..soundboardSounds(id: id.toString()); + + final request = BasicRequest(route, method: 'DELETE', auditLogReason: auditLogReason); + + await client.httpHandler.executeSafe(request); + cache.remove(id); + } + + /// Send a soundboard sound to a voice channel the user is connected to. [soundId] is the id of the soundboard sound to play, + /// while [sourceGuildId] is the id of the guild the soundboard sound is from. (Required to play sounds from different servers.) + Future sendSoundboardSound(Snowflake channelId, {required Snowflake soundId, Snowflake? sourceGuildId}) async { + final route = HttpRoute() + ..channels(id: channelId.toString()) + ..sendSoundboardSound(); + + final request = + BasicRequest(route, method: 'POST', body: jsonEncode({'sound_id': soundId.toString(), 'source_guild_id': (sourceGuildId ?? guildId).toString()})); + + await client.httpHandler.executeSafe(request); + } +} + +class GlobalSoundboardManager extends SoundboardManager implements ReadOnlyManager { + GlobalSoundboardManager(super.config, super.client) : super(identifier: 'soundboard'); + + @override + PartialSoundboardSound operator [](Snowflake id) => PartialSoundboardSound(id: id, manager: this); + + @override + Future fetch(Snowflake id) async { + final sounds = await list(); + + final sound = sounds.firstWhere( + (sound) => sound.id == id, + orElse: () => throw SoundboardSoundNotFoundException(id), + ); + + client.updateCacheWith(sound); + return sound; + } + + @override + Future> list() async { + final route = HttpRoute()..soundboardDefaultSounds(); + + final request = BasicRequest(route); + + final response = await client.httpHandler.executeSafe(request); + + final raw = response.jsonBody as List; + + final sounds = parseMany(raw, parse); + + sounds.forEach(client.updateCacheWith); + + return sounds; + } + + @override + Future delete(Snowflake id, {String? auditLogReason}) => throw UnsupportedError('Cannot delete a global soundboard sound'); + + @override + Future create(SoundboardSoundBuilder builder, {String? auditLogReason}) => throw UnsupportedError('Cannot create a global soundboard sound'); + + @override + Future update(Snowflake id, SoundboardSoundUpdateBuilder builder, {String? auditLogReason}) => + throw UnsupportedError('Cannot update a global soundboard sound'); +} diff --git a/lib/src/http/route.dart b/lib/src/http/route.dart index b7cebaf49..cb8686b5d 100644 --- a/lib/src/http/route.dart +++ b/lib/src/http/route.dart @@ -332,4 +332,13 @@ extension RouteHelpers on HttpRoute { /// Adds the [`subscriptions`](https://discord.com/developers/docs/resources/subscription#list-sku-subscriptions) part to this [HttpRoute]. void subscriptions({String? id}) => add(HttpRoutePart('subscriptions', [if (id != null) HttpRouteParam(id)])); + + /// Adds the [`sounboard-default-sounds`](https://discord.com/developers/docs/resources/soundboard#list-default-soundboard-sounds) part to this [HttpRoute]. + void soundboardDefaultSounds() => add(HttpRoutePart('soundboard-default-sounds')); + + /// Adds the [`soundboard-sounds`](https://discord.com/developers/docs/resources/soundboard#list-guild-soundboard-sounds) part to this [HttpRoute]. + void soundboardSounds({String? id}) => add(HttpRoutePart('soundboard-sounds', [if (id != null) HttpRouteParam(id)])); + + /// Adds the [`send-soundboard-sound`](https://discord.com/developers/docs/resources/soundboard#send-soundboard-sound) part to this [HttpRoute]. + void sendSoundboardSound() => add(HttpRoutePart('send-soundboard-sound')); } diff --git a/lib/src/manager_mixin.dart b/lib/src/manager_mixin.dart index 7ccec1de0..3aaf0d0fc 100644 --- a/lib/src/manager_mixin.dart +++ b/lib/src/manager_mixin.dart @@ -6,6 +6,7 @@ import 'package:nyxx/src/http/managers/interaction_manager.dart'; import 'package:nyxx/src/http/managers/invite_manager.dart'; import 'package:nyxx/src/http/managers/gateway_manager.dart'; import 'package:nyxx/src/http/managers/guild_manager.dart'; +import 'package:nyxx/src/http/managers/soundboard_manager.dart'; import 'package:nyxx/src/http/managers/sticker_manager.dart'; import 'package:nyxx/src/http/managers/user_manager.dart'; import 'package:nyxx/src/http/managers/webhook_manager.dart'; @@ -49,4 +50,7 @@ mixin ManagerMixin implements Nyxx { GlobalApplicationCommandManager(options.applicationCommandConfig, this as NyxxRest, applicationId: (this as NyxxRest).application.id); InteractionManager get interactions => InteractionManager(this as NyxxRest, applicationId: (this as NyxxRest).application.id); + + /// A [GlobalSoundboardManager] that manages global soundboard sounds. + GlobalSoundboardManager get soundboard => GlobalSoundboardManager(options.globalSoundboardCacheConfig, this as NyxxRest); } diff --git a/lib/src/models/gateway/events/soundboard.dart b/lib/src/models/gateway/events/soundboard.dart new file mode 100644 index 000000000..f6df2f1c6 --- /dev/null +++ b/lib/src/models/gateway/events/soundboard.dart @@ -0,0 +1,69 @@ +import 'package:nyxx/src/models/gateway/event.dart'; +import 'package:nyxx/src/models/guild/guild.dart'; +import 'package:nyxx/src/models/snowflake.dart'; +import 'package:nyxx/src/models/soundboard/soundboard.dart'; + +/// {@template soundboard_sound_create_event} +/// Emitted when a guild soundboard sound is created. +/// {@endtemplate} +class SoundboardSoundCreateEvent extends DispatchEvent { + /// The sound that was created. + final SoundboardSound sound; + + /// {@macro soundboard_sound_create_event} + /// @nodoc + SoundboardSoundCreateEvent({required super.gateway, required this.sound}); +} + +/// {@template soundboard_sound_update_event} +/// Emitted when a guild soundboard sound is updated. +/// {@endtemplate} +class SoundboardSoundUpdateEvent extends DispatchEvent { + /// The sound that was updated. + final SoundboardSound sound; + + /// The old sound. + final SoundboardSound? oldSound; + + /// {@macro soundboard_sound_update_event} + /// @nodoc + SoundboardSoundUpdateEvent({required super.gateway, required this.sound, required this.oldSound}); +} + +/// {@template soundboard_sound_delete_event} +/// Emitted when a guild soundboard sound is deleted. +/// {@endtemplate} +class SoundboardSoundDeleteEvent extends DispatchEvent { + /// The sound that was deleted. + final SoundboardSound? sound; + + /// The guild ID where the sound was deleted. + final Snowflake guildId; + + /// The sound ID that was deleted. + final Snowflake soundId; + + /// {@macro soundboard_sound_delete_event} + /// @nodoc + SoundboardSoundDeleteEvent({required super.gateway, required this.sound, required this.guildId, required this.soundId}); + + PartialGuild get guild => gateway.client.guilds[guildId]; +} + +/// {@template soundboard_sounds_update_event} +/// Emitted when multiple guild soundboard sounds are updated. +/// {@endtemplate} +class SoundboardSoundsUpdateEvent extends DispatchEvent { + /// The ID of the guild where the sounds were updated. + final Snowflake guildId; + + /// The sounds that were updated. + final List sounds; + + /// The old sounds. + final List oldSounds; + + /// {@macro soundboard_sounds_update_event} + /// @nodoc + SoundboardSoundsUpdateEvent({required super.gateway, required this.guildId, required this.sounds, required this.oldSounds}); +} diff --git a/lib/src/models/gateway/events/voice.dart b/lib/src/models/gateway/events/voice.dart index b470dcd66..538da93e6 100644 --- a/lib/src/models/gateway/events/voice.dart +++ b/lib/src/models/gateway/events/voice.dart @@ -1,7 +1,11 @@ +import 'package:nyxx/src/models/channel/channel.dart'; +import 'package:nyxx/src/models/emoji.dart'; import 'package:nyxx/src/models/gateway/event.dart'; import 'package:nyxx/src/models/guild/guild.dart'; import 'package:nyxx/src/models/snowflake.dart'; +import 'package:nyxx/src/models/user/user.dart'; import 'package:nyxx/src/models/voice/voice_state.dart'; +import 'package:nyxx/src/utils/enum_like.dart'; /// {@template voice_state_update_event} /// Emitted when a user's voice state is updated. @@ -38,3 +42,63 @@ class VoiceServerUpdateEvent extends DispatchEvent { /// The guild. PartialGuild get guild => gateway.client.guilds[guildId]; } + +/// {@template voice_channel_effect_send_event} +/// Emitted when someone sends an effect, such as an emoji reaction or a soundboard sound, in a voice channel the current user is connected to. +/// {@endtemplate} +class VoiceChannelEffectSendEvent extends DispatchEvent { + /// The ID of the channel this effect was sent in. + final Snowflake channelId; + + /// The ID of the guild this effect was sent in. + final Snowflake guildId; + + /// The ID of the user who sent this effect. + final Snowflake userId; + + /// The emoji sent, for emoji reaction and soundboard effects. + final Emoji? emoji; + + /// The type of emoji animation, for emoji reaction and soundboard effects. + final AnimationType? animationType; + + /// The ID of the emoji animation, for emoji reaction and soundboard effects. + final int? animationId; + + /// The ID of the soundboard sound, for soundboard effects. + final Snowflake? soundId; + + /// The volume of the soundboard sound, from 0 to 1, for soundboard effects. + final double? soundVolume; + + /// {@macro voice_channel_effect_send_event} + /// @nodoc + VoiceChannelEffectSendEvent({ + required super.gateway, + required this.channelId, + required this.guildId, + required this.userId, + required this.emoji, + required this.animationType, + required this.animationId, + required this.soundId, + required this.soundVolume, + }); + + /// The channel this effect was sent in. + PartialChannel get channel => gateway.client.channels[channelId]; + + /// The guild this effect was sent in. + PartialGuild get guild => gateway.client.guilds[guildId]; + + /// The user who sent this effect. + PartialUser get user => gateway.client.users[userId]; +} + +final class AnimationType extends EnumLike { + static const premium = AnimationType(1); + static const basic = AnimationType(2); + + /// @nodoc + const AnimationType(super.value); +} diff --git a/lib/src/models/guild/guild.dart b/lib/src/models/guild/guild.dart index 28d3e2a62..22b5b10b7 100644 --- a/lib/src/models/guild/guild.dart +++ b/lib/src/models/guild/guild.dart @@ -18,6 +18,7 @@ import 'package:nyxx/src/http/managers/integration_manager.dart'; import 'package:nyxx/src/http/managers/member_manager.dart'; import 'package:nyxx/src/http/managers/role_manager.dart'; import 'package:nyxx/src/http/managers/scheduled_event_manager.dart'; +import 'package:nyxx/src/http/managers/soundboard_manager.dart'; import 'package:nyxx/src/http/route.dart'; import 'package:nyxx/src/models/application.dart'; import 'package:nyxx/src/models/channel/channel.dart'; @@ -75,6 +76,9 @@ class PartialGuild extends WritableSnowflakeEntity { /// An [AuditLogManager] for the audit log of this guild. AuditLogManager get auditLogs => AuditLogManager(manager.client.options.auditLogEntryConfig, manager.client, guildId: id); + /// A [GuildSoundboardManager] for the soundboard sounds of this guild. + GuildSoundboardManager get soundboard => GuildSoundboardManager(manager.client.options.soundboardCacheConfig, manager.client, guildId: id); + /// A [Cache] for [VoiceState]s in this guild. Cache get voiceStates => manager.client.cache.getCache('$id.voiceStates', manager.client.options.voiceStateConfig); diff --git a/lib/src/models/soundboard/soundboard.dart b/lib/src/models/soundboard/soundboard.dart new file mode 100644 index 000000000..9ade9e6ab --- /dev/null +++ b/lib/src/models/soundboard/soundboard.dart @@ -0,0 +1,54 @@ +import 'package:nyxx/src/http/managers/soundboard_manager.dart'; +import 'package:nyxx/src/models/emoji.dart'; +import 'package:nyxx/src/models/guild/guild.dart'; +import 'package:nyxx/src/models/snowflake.dart'; +import 'package:nyxx/src/models/snowflake_entity/snowflake_entity.dart'; +import 'package:nyxx/src/models/user/user.dart'; + +class PartialSoundboardSound extends WritableSnowflakeEntity { + @override + final SoundboardManager manager; + + PartialSoundboardSound({required super.id, required this.manager}); +} + +class SoundboardSound extends PartialSoundboardSound { + /// The name of this sound. + final String name; + + /// The volume of this sound, from 0 to 1. + final double volume; + + /// The emoji this sound is associated with. + final Emoji? emoji; + + /// The emoji name this sound is associated with. + final String? emojiName; + + /// The emoji ID this sound is associated with. + final Snowflake? emojiId; + + /// The ID of the guild this sound is in. + final Snowflake? guildId; + + /// Whether this sound can be used, may be `false` due to loss of Server Boosts. + final bool isAvailable; + + /// The user who created this sound. + final User? user; + + SoundboardSound({ + required super.id, + required super.manager, + required this.name, + required this.volume, + required this.emoji, + required this.emojiName, + required this.emojiId, + required this.guildId, + required this.isAvailable, + required this.user, + }); + + PartialGuild get guild => manager.client.guilds[guildId!]; +} diff --git a/lib/src/utils/building_helpers.dart b/lib/src/utils/building_helpers.dart new file mode 100644 index 000000000..f7db5894d --- /dev/null +++ b/lib/src/utils/building_helpers.dart @@ -0,0 +1,6 @@ +import 'package:nyxx/nyxx.dart'; + +Map makeEmojiMap({Snowflake? emojiId, String? emojiName}) => { + if (emojiName case final String emojiName?) 'emoji_name': emojiName, + if (emojiId case final Snowflake emojiId?) 'emoji_id': emojiId.toString(), + }; diff --git a/lib/src/utils/cache_helpers.dart b/lib/src/utils/cache_helpers.dart index 3b343ce61..be32476ef 100644 --- a/lib/src/utils/cache_helpers.dart +++ b/lib/src/utils/cache_helpers.dart @@ -21,6 +21,7 @@ import 'package:nyxx/src/models/gateway/events/invite.dart'; import 'package:nyxx/src/models/gateway/events/message.dart'; import 'package:nyxx/src/models/gateway/events/presence.dart'; import 'package:nyxx/src/models/gateway/events/ready.dart'; +import 'package:nyxx/src/models/gateway/events/soundboard.dart'; import 'package:nyxx/src/models/gateway/events/stage_instance.dart'; import 'package:nyxx/src/models/gateway/events/voice.dart'; import 'package:nyxx/src/models/gateway/events/webhook.dart'; @@ -38,6 +39,7 @@ import 'package:nyxx/src/models/invite/invite.dart'; import 'package:nyxx/src/models/message/message.dart'; import 'package:nyxx/src/models/presence.dart'; import 'package:nyxx/src/models/role.dart'; +import 'package:nyxx/src/models/soundboard/soundboard.dart'; import 'package:nyxx/src/models/sticker/global_sticker.dart'; import 'package:nyxx/src/models/sticker/guild_sticker.dart'; import 'package:nyxx/src/models/sticker/sticker_pack.dart'; @@ -288,6 +290,15 @@ extension CacheUpdates on NyxxRest { EntitlementCreateEvent(:final entitlement) => updateCacheWith(entitlement), EntitlementUpdateEvent(:final entitlement) => updateCacheWith(entitlement), EntitlementDeleteEvent(:final entitlement) => entitlement.manager.cache.remove(entitlement.id), + SoundboardSound() => () { + updateCacheWith(entity.user); + entity.manager.cache[entity.id] = entity; + }(), + VoiceChannelEffectSendEvent() => null, + SoundboardSoundCreateEvent(:final sound) => updateCacheWith(sound), + SoundboardSoundUpdateEvent(:final sound) => updateCacheWith(sound), + SoundboardSoundDeleteEvent(:final sound?) => sound.manager.cache.remove(sound.id), + SoundboardSoundsUpdateEvent(:final sounds) => sounds.forEach(updateCacheWith), MessagePollVoteAddEvent() => null, MessagePollVoteRemoveEvent() => null, diff --git a/test/files/sound.ogg b/test/files/sound.ogg new file mode 100644 index 0000000000000000000000000000000000000000..0321a702dfd87793da7f2a81714908470f2b3b98 GIT binary patch literal 82375 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 z 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%0DIwWEcW6p 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�(ev4kiQ; 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 F{U5MXdR+hj literal 0 HcmV?d00001 diff --git a/test/integration/rest_integration_test.dart b/test/integration/rest_integration_test.dart index cce5f9b58..5c462566f 100644 --- a/test/integration/rest_integration_test.dart +++ b/test/integration/rest_integration_test.dart @@ -2,6 +2,9 @@ import 'dart:io'; import 'package:mocktail/mocktail.dart'; import 'package:nyxx/nyxx.dart'; +import 'package:nyxx/src/builders/sound.dart'; +import 'package:nyxx/src/builders/soundboard.dart'; +import 'package:nyxx/src/models/soundboard/soundboard.dart'; import 'package:test/test.dart' hide completes; import '../function_completes.dart'; @@ -432,5 +435,27 @@ void main() { await expectLater(command.delete(), completes); }); + + test('Soundboard', skip: testGuild != null ? false : 'No test guild provided', () async { + final guildId = Snowflake.parse(testGuild!); + final guild = client.guilds[guildId]; + + await expectLater(guild.soundboard.list(), completion(isEmpty)); + + late SoundboardSound sound; + await expectLater( + () async => sound = await guild.soundboard.create( + SoundboardSoundBuilder( + name: 'Test sound', + volume: 0.5, + sound: await SoundBuilder.fromFile(File('test/files/sound.ogg')), + ), + ), + completes, + ); + + await expectLater(sound.update(SoundboardSoundUpdateBuilder(name: 'New name')), completes); + await expectLater(sound.delete(), completes); + }); }); } diff --git a/test/unit/builders/sound_test.dart b/test/unit/builders/sound_test.dart new file mode 100644 index 000000000..3d82027df --- /dev/null +++ b/test/unit/builders/sound_test.dart @@ -0,0 +1,15 @@ +import 'package:nyxx/src/builders/sound.dart'; +import 'package:test/test.dart'; + +void main() { + group('SoundBuilder', () { + test('build', () { + final builder = SoundBuilder( + format: 'mp3', + data: [0, 0, 0, 255, 255, 255], + ); + + expect(builder.buildDataString(), equals('data:audio/mp3;base64,AAAA////')); + }); + }); +} diff --git a/test/unit/http/managers/soundboard_manager_test.dart b/test/unit/http/managers/soundboard_manager_test.dart new file mode 100644 index 000000000..bbee65a8b --- /dev/null +++ b/test/unit/http/managers/soundboard_manager_test.dart @@ -0,0 +1,98 @@ +import 'package:mocktail/mocktail.dart'; +import 'package:nyxx/src/api_options.dart'; +import 'package:nyxx/src/builders/sound.dart'; +import 'package:nyxx/src/builders/soundboard.dart'; +import 'package:nyxx/src/client.dart'; +import 'package:nyxx/src/client_options.dart'; +import 'package:nyxx/src/http/managers/soundboard_manager.dart'; +import 'package:nyxx/src/models/emoji.dart'; +import 'package:nyxx/src/models/snowflake.dart'; +import 'package:nyxx/src/models/soundboard/soundboard.dart'; +import 'package:test/test.dart'; +import '../../../mocks/client.dart'; +import '../../../test_manager.dart'; + +const sampleGlobalSoundboardSound = { + "name": "quack", + "sound_id": "1", + "volume": 1.0, + "emoji_id": null, + "emoji_name": "🦆", + "available": true, +}; + +const sampleGuildSoundboardSound = { + "name": "Yay", + "sound_id": "1106714396018884649", + "volume": 1, + "emoji_id": "989193655938064464", + "emoji_name": null, + "guild_id": "613425648685547541", + "available": true +}; + +void checkGlobalSoundboardSound(SoundboardSound sound, NyxxRest client) { + expect(sound.id, equals(Snowflake(1))); + expect(sound.name, equals('quack')); + expect(sound.volume, equals(1.0)); + expect(sound.emoji, equals(TextEmoji(id: Snowflake.zero, name: '🦆', manager: client.application.emojis))); + expect(sound.emojiName, equals('🦆')); + expect(sound.emojiId, isNull); + expect(sound.guildId, isNull); + expect(sound.isAvailable, isTrue); + expect(sound.user, isNull); +} + +void checkGuildSoundboardSound(SoundboardSound sound, NyxxRest client) { + expect(sound.id, equals(Snowflake(1106714396018884649))); + expect(sound.name, equals('Yay')); + expect(sound.volume, equals(1.0)); + expect(sound.emoji, equals(client.guilds[Snowflake(613425648685547541)].emojis.cache[Snowflake(989193655938064464)])); + expect(sound.emojiName, isNull); + expect(sound.emojiId, equals(Snowflake(989193655938064464))); + expect(sound.guildId, equals(Snowflake(613425648685547541))); + expect(sound.isAvailable, isTrue); + expect(sound.user, isNull); +} + +void main() { + final client = MockNyxx(); + when(() => client.apiOptions).thenReturn(RestApiOptions(token: 'TEST_TOKEN')); + when(() => client.options).thenReturn(RestClientOptions()); + testReadOnlyManager( + 'GlobalSoundboardManager', + GlobalSoundboardManager.new, + '/soundboard-default-sounds', + sampleObject: sampleGlobalSoundboardSound, + fetchObjectOverride: [sampleGlobalSoundboardSound], + sampleMatches: (source) => checkGlobalSoundboardSound(source, client), + additionalParsingTests: [], + additionalEndpointTests: [], + ); + + testManager( + 'GuildSoundboardManager', + (config, client) => GuildSoundboardManager(config, client, guildId: Snowflake.zero), + RegExp(r'/guilds/0/soundboard-sounds/\d+'), + '/guilds/0/soundboard-sounds', + sampleObject: sampleGuildSoundboardSound, + sampleMatches: (sound) => checkGuildSoundboardSound(sound, client), + additionalParsingTests: [], + additionalEndpointTests: [ + EndpointTest( + name: 'send-soundboard-sound', + source: null, + urlMatcher: '/channels/0/send-soundboard-sound', + execute: (manager) => manager.sendSoundboardSound(Snowflake.zero, soundId: Snowflake.zero), + check: (_) {}, + method: 'post'), + ], + createBuilder: SoundboardSoundBuilder( + name: 'cool', + sound: SoundBuilder.ogg([0, 1, 2, 3]), + volume: .5, + emojiName: '😎', + ), + updateBuilder: SoundboardSoundUpdateBuilder(name: 'cooler', volume: .7), + ); +}