From a81ee15c7cd90c0970a1aa1ac0885d2b13624caf Mon Sep 17 00:00:00 2001 From: Anton Date: Fri, 1 Dec 2023 21:52:59 +0200 Subject: [PATCH 01/14] Fix incorrect snowflake serialization (#595) * Update component.dart * Update component.dart --- lib/src/builders/message/component.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/builders/message/component.dart b/lib/src/builders/message/component.dart index 107d4ceff..62c2b34c7 100644 --- a/lib/src/builders/message/component.dart +++ b/lib/src/builders/message/component.dart @@ -98,7 +98,7 @@ class ButtonBuilder extends MessageComponentBuilder { if (label != null) 'label': label, if (emoji != null) 'emoji': { - 'id': emoji!.id == Snowflake.zero ? null : emoji!.id, + 'id': emoji!.id == Snowflake.zero ? null : emoji!.id.toString(), 'name': emoji!.name, if (emoji is GuildEmoji) 'animated': (emoji as GuildEmoji).isAnimated == true, }, @@ -223,7 +223,7 @@ class SelectMenuOptionBuilder extends CreateBuilder { if (description != null) 'description': description, if (emoji != null) 'emoji': { - 'id': emoji!.id.value, + 'id': emoji!.id.toString(), 'name': emoji!.name, 'animated': emoji is GuildEmoji && (emoji as GuildEmoji).isAnimated == true, }, From c1eab8883ceb34c1ab0822fe40ea8377a8e5d7d7 Mon Sep 17 00:00:00 2001 From: Anton Date: Sat, 2 Dec 2023 18:11:09 +0200 Subject: [PATCH 02/14] Fix incorrect GuildUpdateBuilder & overwrites serialization (#596) * fix guild update serialization and add safety alerts channel id * fix overwrites * missed id * annoying * changed PermissionOverwrite test * i forgor * Update guild.dart * Update guild.dart --- lib/src/builders/guild/guild.dart | 30 +++++++++++-------- lib/src/builders/permission_overwrite.dart | 1 + .../builders/permission_overwrite_test.dart | 3 +- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/lib/src/builders/guild/guild.dart b/lib/src/builders/guild/guild.dart index 374a45939..634518074 100644 --- a/lib/src/builders/guild/guild.dart +++ b/lib/src/builders/guild/guild.dart @@ -101,6 +101,8 @@ class GuildUpdateBuilder extends UpdateBuilder { bool? premiumProgressBarEnabled; + Snowflake? safetyAlertsChannelId; + GuildUpdateBuilder({ this.name, this.verificationLevel, @@ -121,28 +123,30 @@ class GuildUpdateBuilder extends UpdateBuilder { this.features, this.description = sentinelString, this.premiumProgressBarEnabled, + this.safetyAlertsChannelId, }); @override Map build() => { if (name != null) 'name': name, - if (verificationLevel != null) 'verificationLevel': verificationLevel!.value, - if (defaultMessageNotificationLevel != null) 'defaultMessageNotificationLevel': defaultMessageNotificationLevel!.value, - if (explicitContentFilterLevel != null) 'explicitContentFilterLevel': explicitContentFilterLevel!.value, - if (!identical(afkChannelId, sentinelSnowflake)) 'afkChannelId': afkChannelId?.toString(), - if (afkTimeout != null) 'afkTimeout': afkTimeout!.inSeconds, + if (verificationLevel != null) 'verification_level': verificationLevel!.value, + if (defaultMessageNotificationLevel != null) 'default_message_notification_level': defaultMessageNotificationLevel!.value, + if (explicitContentFilterLevel != null) 'explicit_content_filter_level': explicitContentFilterLevel!.value, + if (!identical(afkChannelId, sentinelSnowflake)) 'afk_channel_id': afkChannelId?.toString(), + if (afkTimeout != null) 'afk_timeout': afkTimeout!.inSeconds, if (!identical(icon, sentinelImageBuilder)) 'icon': icon?.buildDataString(), - if (newOwnerId != null) 'newOwnerId': newOwnerId!.toString(), + if (newOwnerId != null) 'owner_id': newOwnerId!.toString(), if (!identical(splash, sentinelImageBuilder)) 'splash': splash?.buildDataString(), - if (!identical(discoverySplash, sentinelImageBuilder)) 'discoverySplash': discoverySplash?.buildDataString(), + if (!identical(discoverySplash, sentinelImageBuilder)) 'discovery_splash': discoverySplash?.buildDataString(), if (!identical(banner, sentinelImageBuilder)) 'banner': banner?.buildDataString(), - if (systemChannelId != null) 'systemChannelId': systemChannelId!.toString(), - if (systemChannelFlags != null) 'systemChannelFlags': systemChannelFlags!.value, - if (rulesChannelId != null) 'rulesChannelId': rulesChannelId!.toString(), - if (publicUpdatesChannelId != null) 'publicUpdatesChannelId': publicUpdatesChannelId!.toString(), - if (preferredLocale != null) 'preferredLocale': preferredLocale!.identifier, + if (!identical(systemChannelId, sentinelSnowflake)) 'system_channel_id': systemChannelId?.toString(), + if (systemChannelFlags != null) 'system_channel_flags': systemChannelFlags!.value, + if (!identical(rulesChannelId, sentinelSnowflake)) 'rules_channel_id': rulesChannelId?.toString(), + if (!identical(publicUpdatesChannelId, sentinelSnowflake)) 'public_updates_channel_id': publicUpdatesChannelId?.toString(), + if (preferredLocale != null) 'preferred_locale': preferredLocale!.identifier, if (features != null) 'features': GuildManager.serializeGuildFeatures(features!), if (!identical(description, sentinelString)) 'description': description, - if (premiumProgressBarEnabled != null) 'premiumProgressBarEnabled': premiumProgressBarEnabled, + if (premiumProgressBarEnabled != null) 'premium_progress_bar_enabled': premiumProgressBarEnabled, + if (!identical(safetyAlertsChannelId, sentinelSnowflake)) 'safety_alerts_channel_id': safetyAlertsChannelId?.toString(), }; } diff --git a/lib/src/builders/permission_overwrite.dart b/lib/src/builders/permission_overwrite.dart index 8f55073fe..a6874d7dc 100644 --- a/lib/src/builders/permission_overwrite.dart +++ b/lib/src/builders/permission_overwrite.dart @@ -17,6 +17,7 @@ class PermissionOverwriteBuilder extends CreateBuilder { @override Map build() => { + 'id': id.toString(), 'type': type.value, if (allow != null) 'allow': allow!.value.toString(), if (deny != null) 'deny': deny!.value.toString(), diff --git a/test/unit/builders/permission_overwrite_test.dart b/test/unit/builders/permission_overwrite_test.dart index 67165395a..9a2af729a 100644 --- a/test/unit/builders/permission_overwrite_test.dart +++ b/test/unit/builders/permission_overwrite_test.dart @@ -7,7 +7,7 @@ void main() { expect( builder.build(), - equals({'type': 1}), + equals({'id': '0', 'type': 1}), ); final builder2 = PermissionOverwriteBuilder( @@ -20,6 +20,7 @@ void main() { expect( builder2.build(), equals({ + 'id': '0', 'type': 0, 'allow': '1048640', 'deny': '8', From 3ba7e7e71a39497863e8f43e7058939eaeb0a878 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 4 Dec 2023 15:52:17 +0200 Subject: [PATCH 03/14] add automod message types (#597) --- lib/src/models/message/message.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/src/models/message/message.dart b/lib/src/models/message/message.dart index 716f5a791..3c55aa65b 100644 --- a/lib/src/models/message/message.dart +++ b/lib/src/models/message/message.dart @@ -261,7 +261,11 @@ enum MessageType { stageEnd._(28), stageSpeaker._(29), stageTopic._(31), - guildApplicationPremiumSubscription._(32); + guildApplicationPremiumSubscription._(32), + guildIncidentAlertModeEnabled._(36), + guildIncidentAlertModeDisabled._(37), + guildIncidentReportRaid._(38), + guildIncidentReportFalseAlarm._(39); /// The value of this [MessageType]. final int value; From d04db1ae240ff1f47764f585b95edd5f477c2ba5 Mon Sep 17 00:00:00 2001 From: Anton Date: Thu, 7 Dec 2023 17:32:36 +0200 Subject: [PATCH 04/14] Fix unused `listBans` params (#598) * fix `listBans` not using limit/after/before params * fix sentinels not being set by default in `GuildUpdateBuilder` --- lib/src/builders/guild/guild.dart | 8 ++++---- lib/src/http/managers/guild_manager.dart | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/lib/src/builders/guild/guild.dart b/lib/src/builders/guild/guild.dart index 634518074..959f7f991 100644 --- a/lib/src/builders/guild/guild.dart +++ b/lib/src/builders/guild/guild.dart @@ -115,15 +115,15 @@ class GuildUpdateBuilder extends UpdateBuilder { this.splash = sentinelImageBuilder, this.discoverySplash = sentinelImageBuilder, this.banner = sentinelImageBuilder, - this.systemChannelId, + this.systemChannelId = sentinelSnowflake, this.systemChannelFlags, - this.rulesChannelId, - this.publicUpdatesChannelId, + this.rulesChannelId = sentinelSnowflake, + this.publicUpdatesChannelId = sentinelSnowflake, this.preferredLocale, this.features, this.description = sentinelString, this.premiumProgressBarEnabled, - this.safetyAlertsChannelId, + this.safetyAlertsChannelId = sentinelSnowflake, }); @override diff --git a/lib/src/http/managers/guild_manager.dart b/lib/src/http/managers/guild_manager.dart index 8fbfa7714..8532243fe 100644 --- a/lib/src/http/managers/guild_manager.dart +++ b/lib/src/http/managers/guild_manager.dart @@ -423,7 +423,11 @@ class GuildManager extends Manager { final route = HttpRoute() ..guilds(id: id.toString()) ..bans(); - final request = BasicRequest(route); + final request = BasicRequest(route, queryParameters: { + if (limit != null) 'limit': limit.toString(), + if (after != null) 'after': after.toString(), + if (before != null) 'before': before.toString(), + }); final response = await client.httpHandler.executeSafe(request); final bans = parseMany(response.jsonBody as List, parseBan); From 3cd4f5369edbf926c2070418399ea1e5fd6e505a Mon Sep 17 00:00:00 2001 From: Abitofevrything <54505189+abitofevrything@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:33:32 +0100 Subject: [PATCH 05/14] Add test validating ApiOptions.nyxxVersion (#605) --- test/unit/version_matches_test.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 test/unit/version_matches_test.dart diff --git a/test/unit/version_matches_test.dart b/test/unit/version_matches_test.dart new file mode 100644 index 000000000..2bee3b2f2 --- /dev/null +++ b/test/unit/version_matches_test.dart @@ -0,0 +1,18 @@ +import 'dart:io'; + +import 'package:nyxx/nyxx.dart'; +import 'package:test/test.dart'; + +void main() { + test('package version matches ApiOptions.nyxxVersion', () { + final pubspecFile = File('pubspec.yaml'); + + expect(pubspecFile.existsSync(), isTrue, reason: 'pubspec.yaml should exist'); + + final versionFromPubspec = RegExp(r'version: (.+)\n').firstMatch(pubspecFile.readAsStringSync())?.group(1); + + expect(versionFromPubspec, isNotNull, reason: 'version should be parsed from pubspec'); + + expect(versionFromPubspec, equals(ApiOptions.nyxxVersion)); + }); +} From f23cfd27b87560a4fe2da04d21c2be8bb46a3f06 Mon Sep 17 00:00:00 2001 From: Abitofevrything <54505189+abitofevrything@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:49:40 +0100 Subject: [PATCH 06/14] Export Credentials from package:oauth2 (#604) --- lib/nyxx.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/nyxx.dart b/lib/nyxx.dart index 870b1d66a..71fab6b54 100644 --- a/lib/nyxx.dart +++ b/lib/nyxx.dart @@ -316,3 +316,4 @@ export 'package:http/http.dart' StreamedResponse; export 'package:logging/logging.dart' show Logger, Level; export 'package:runtime_type/runtime_type.dart' show RuntimeType; +export 'package:oauth2/oauth2.dart' show Credentials; From c12ba8915f11845df869c981ee025aa12c5b750b Mon Sep 17 00:00:00 2001 From: Abitofevrything <54505189+abitofevrything@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:49:54 +0100 Subject: [PATCH 07/14] Provide guild ID when parsing message interactions (#603) --- lib/src/http/managers/message_manager.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/src/http/managers/message_manager.dart b/lib/src/http/managers/message_manager.dart index ecbf9782d..784648e58 100644 --- a/lib/src/http/managers/message_manager.dart +++ b/lib/src/http/managers/message_manager.dart @@ -79,7 +79,7 @@ class MessageManager extends Manager { referencedMessage: maybeParse(raw['referenced_message'], parse), interaction: maybeParse( raw['interaction'], - (Map raw) => parseMessageInteraction(raw), + (Map raw) => parseMessageInteraction(raw, guildId: guildId), ), thread: maybeParse(raw['thread'], client.channels.parse) as Thread?, components: maybeParseMany(raw['components'], parseMessageComponent), @@ -291,7 +291,7 @@ class MessageManager extends Manager { ); } - MessageInteraction parseMessageInteraction(Map raw) { + MessageInteraction parseMessageInteraction(Map raw, {Snowflake? guildId}) { final user = client.users.parse(raw['user'] as Map); return MessageInteraction( @@ -299,10 +299,9 @@ class MessageManager extends Manager { type: InteractionType.parse(raw['type'] as int), name: raw['name'] as String, user: user, - // TODO: Find a way to get the guild ID. member: maybeParse( raw['member'], - (Map raw) => client.guilds[Snowflake.zero].members[user.id], + (Map raw) => client.guilds[guildId ?? Snowflake.zero].members[user.id], ), ); } From 7a0f346677b5c7a8edf7064db722087765864722 Mon Sep 17 00:00:00 2001 From: Abitofevrything <54505189+abitofevrything@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:50:02 +0100 Subject: [PATCH 08/14] Add entitlement delete payload (#599) Co-authored-by: Szymon Uglis --- lib/src/gateway/gateway.dart | 7 ++++++- lib/src/models/gateway/events/entitlement.dart | 5 +++-- lib/src/utils/cache_helpers.dart | 3 +-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/src/gateway/gateway.dart b/lib/src/gateway/gateway.dart index e20a8cc2b..fa027c1be 100644 --- a/lib/src/gateway/gateway.dart +++ b/lib/src/gateway/gateway.dart @@ -1011,7 +1011,12 @@ class Gateway extends GatewayManager with EventParser { /// Parse an [EntitlementDeleteEvent] from [raw]. EntitlementDeleteEvent parseEntitlementDelete(Map raw) { - return EntitlementDeleteEvent(gateway: this); + final applicationId = Snowflake.parse(raw['application_id']!); + + return EntitlementDeleteEvent( + gateway: this, + entitlement: client.applications[applicationId].entitlements.parse(raw), + ); } /// Stream all members in a guild that match [query] or [userIds]. diff --git a/lib/src/models/gateway/events/entitlement.dart b/lib/src/models/gateway/events/entitlement.dart index 11fd7e6f8..e72f4d300 100644 --- a/lib/src/models/gateway/events/entitlement.dart +++ b/lib/src/models/gateway/events/entitlement.dart @@ -30,8 +30,9 @@ class EntitlementUpdateEvent extends DispatchEvent { /// Emitted when an entitlement is deleted. /// {@endtemplate} class EntitlementDeleteEvent extends DispatchEvent { - // TODO: What is the payload here? + /// The entitlement that was deleted. + final Entitlement entitlement; /// {@macro entitlement_delete_event} - EntitlementDeleteEvent({required super.gateway}); + EntitlementDeleteEvent({required super.gateway, required this.entitlement}); } diff --git a/lib/src/utils/cache_helpers.dart b/lib/src/utils/cache_helpers.dart index ddffe9aa8..0cbf7d288 100644 --- a/lib/src/utils/cache_helpers.dart +++ b/lib/src/utils/cache_helpers.dart @@ -278,8 +278,7 @@ extension CacheUpdates on NyxxRest { StageInstanceDeleteEvent(:final instance) => instance.manager.cache.remove(instance.id), EntitlementCreateEvent(:final entitlement) => updateCacheWith(entitlement), EntitlementUpdateEvent(:final entitlement) => updateCacheWith(entitlement), - // TODO: Remove entitlement from cache - EntitlementDeleteEvent() => null, + EntitlementDeleteEvent(:final entitlement) => entitlement.manager.cache.remove(entitlement.id), // null and unhandled entity types WebhookAuthor() => null, From 74bf50762025d35ae8f9dc31379fe81e4e0036d0 Mon Sep 17 00:00:00 2001 From: Abitofevrything <54505189+abitofevrything@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:50:15 +0100 Subject: [PATCH 09/14] Add SKU flags field (#602) --- lib/nyxx.dart | 2 +- .../http/managers/application_manager.dart | 1 + lib/src/models/sku.dart | 23 +++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/nyxx.dart b/lib/nyxx.dart index 71fab6b54..3d85fa595 100644 --- a/lib/nyxx.dart +++ b/lib/nyxx.dart @@ -295,7 +295,7 @@ export 'src/models/interaction.dart' ModalSubmitInteraction, PingInteraction; export 'src/models/entitlement.dart' show Entitlement, PartialEntitlement, EntitlementType; -export 'src/models/sku.dart' show Sku, SkuType; +export 'src/models/sku.dart' show Sku, SkuType, SkuFlags; export 'src/utils/flags.dart' show Flag, Flags; export 'src/intents.dart' show GatewayIntents; diff --git a/lib/src/http/managers/application_manager.dart b/lib/src/http/managers/application_manager.dart index b66eef8ec..f0ea7c497 100644 --- a/lib/src/http/managers/application_manager.dart +++ b/lib/src/http/managers/application_manager.dart @@ -117,6 +117,7 @@ class ApplicationManager { applicationId: Snowflake.parse(raw['application_id']!), name: raw['name'] as String, slug: raw['slug'] as String, + flags: SkuFlags(raw['flags'] as int), ); } diff --git a/lib/src/models/sku.dart b/lib/src/models/sku.dart index e8fe3cfc1..9d8558676 100644 --- a/lib/src/models/sku.dart +++ b/lib/src/models/sku.dart @@ -1,6 +1,7 @@ import 'package:nyxx/src/http/managers/application_manager.dart'; import 'package:nyxx/src/models/application.dart'; import 'package:nyxx/src/models/snowflake.dart'; +import 'package:nyxx/src/utils/flags.dart'; import 'package:nyxx/src/utils/to_string_helper/to_string_helper.dart'; /// {@template sku} @@ -25,6 +26,9 @@ class Sku with ToStringHelper { /// The URL slug for this SKU. final String slug; + /// This SKU's flags. + final SkuFlags flags; + /// {@macro sku} Sku({ required this.manager, @@ -33,6 +37,7 @@ class Sku with ToStringHelper { required this.applicationId, required this.name, required this.slug, + required this.flags, }); /// The application this SKU belongs to. @@ -59,3 +64,21 @@ enum SkuType { @override String toString() => 'SkuType($value)'; } + +/// Flags applied to an [Sku]. +class SkuFlags extends Flags { + /// The SKU is a guild subscription. + static const guildSubscription = Flag.fromOffset(7); + + /// The SKU is a user subscription. + static const userSubscription = Flag.fromOffset(8); + + /// Create a new [SkuFlags]. + SkuFlags(super.value); + + /// Whether this set of flags has the [guildSubscription] flag set. + bool get isGuildSubscription => has(guildSubscription); + + /// Whether this set of flags has the [userSubscription] flag set. + bool get isUserSubscription => has(userSubscription); +} From 5a833f3233763b7507c8526d74954db6e62877ac Mon Sep 17 00:00:00 2001 From: Abitofevrything <54505189+abitofevrything@users.noreply.github.com> Date: Sat, 9 Dec 2023 13:56:27 +0100 Subject: [PATCH 10/14] Add select menu default values (#601) Co-authored-by: Szymon Uglis --- lib/nyxx.dart | 2 + lib/src/http/managers/message_manager.dart | 8 ++ lib/src/models/message/component.dart | 95 ++++++++++++++++++++++ 3 files changed, 105 insertions(+) diff --git a/lib/nyxx.dart b/lib/nyxx.dart index 3d85fa595..c1b3b92cd 100644 --- a/lib/nyxx.dart +++ b/lib/nyxx.dart @@ -151,6 +151,8 @@ export 'src/models/message/component.dart' MessageComponent, SelectMenuComponent, SelectMenuOption, + SelectMenuDefaultValue, + SelectMenuDefaultValueType, TextInputComponent, ButtonStyle, MessageComponentType, diff --git a/lib/src/http/managers/message_manager.dart b/lib/src/http/managers/message_manager.dart index 784648e58..86a29422d 100644 --- a/lib/src/http/managers/message_manager.dart +++ b/lib/src/http/managers/message_manager.dart @@ -274,6 +274,7 @@ class MessageManager extends Manager { options: maybeParseMany(raw['options'], parseSelectMenuOption), channelTypes: maybeParseMany(raw['channel_types'], ChannelType.parse), placeholder: raw['placeholder'] as String?, + defaultValues: maybeParseMany(raw['default_values'], parseSelectMenuDefaultValue), minValues: raw['min_values'] as int?, maxValues: raw['max_values'] as int?, isDisabled: raw['disabled'] as bool?, @@ -291,6 +292,13 @@ class MessageManager extends Manager { ); } + SelectMenuDefaultValue parseSelectMenuDefaultValue(Map raw) { + return SelectMenuDefaultValue( + id: Snowflake.parse(raw['id']!), + type: SelectMenuDefaultValueType.parse(raw['type'] as String), + ); + } + MessageInteraction parseMessageInteraction(Map raw, {Snowflake? guildId}) { final user = client.users.parse(raw['user'] as Map); diff --git a/lib/src/models/message/component.dart b/lib/src/models/message/component.dart index 3b6ab40d2..68efa8f9f 100644 --- a/lib/src/models/message/component.dart +++ b/lib/src/models/message/component.dart @@ -1,7 +1,9 @@ import 'package:nyxx/src/models/channel/channel.dart'; import 'package:nyxx/src/models/emoji.dart'; +import 'package:nyxx/src/models/snowflake.dart'; import 'package:nyxx/src/utils/to_string_helper/to_string_helper.dart'; +/// The type of a [MessageComponent]. enum MessageComponentType { actionRow._(1), button._(2), @@ -12,10 +14,14 @@ enum MessageComponentType { mentionableSelect._(7), channelSelect._(8); + /// The value of this [MessageComponentType]. final int value; const MessageComponentType._(this.value); + /// Parse a [MessageComponentType] from an [int]. + /// + /// The [value] must be a valid message component type. factory MessageComponentType.parse(int value) => MessageComponentType.values.firstWhere( (type) => type.value == value, orElse: () => throw FormatException('Unknown message component type', value), @@ -25,35 +31,48 @@ enum MessageComponentType { String toString() => 'MessageComponentType($value)'; } +/// A component in a [Message]. abstract class MessageComponent with ToStringHelper { + /// The type of this component. MessageComponentType get type; } +/// A [MessageComponent] that contains multiple child [MessageComponent]s. class ActionRowComponent extends MessageComponent { @override MessageComponentType get type => MessageComponentType.actionRow; + /// The children of this [ActionRow]. final List components; + /// Create a new [ActionRowComponent]. ActionRowComponent({required this.components}); } +/// A clickable button. class ButtonComponent extends MessageComponent { @override MessageComponentType get type => MessageComponentType.button; + /// The style of this button. final ButtonStyle style; + /// The label displayed on this button. final String? label; + /// The [Emoji] displayed on this button. final Emoji? emoji; + /// This component's custom ID. final String? customId; + /// The URL this button redirects to, if this button is a URL button. final Uri? url; + /// Whether this button is disabled. final bool? isDisabled; + /// Create a new [ButtonComponent]. ButtonComponent({ required this.style, required this.label, @@ -64,6 +83,7 @@ class ButtonComponent extends MessageComponent { }); } +/// The style of a [ButtonComponent]. enum ButtonStyle { primary._(1), secondary._(2), @@ -71,10 +91,14 @@ enum ButtonStyle { danger._(4), link._(5); + /// The value of this [ButtonStyle]. final int value; const ButtonStyle._(this.value); + /// Parse a [ButtonStyle] from an [int]. + /// + /// The [value] must be a valid button style. factory ButtonStyle.parse(int value) => ButtonStyle.values.firstWhere( (style) => style.value == value, orElse: () => throw FormatException('Unknown button style', value), @@ -84,47 +108,103 @@ enum ButtonStyle { String toString() => 'ButtonStyle($value)'; } +/// A dropdown menu in which users can select from on or more choices. class SelectMenuComponent extends MessageComponent { @override final MessageComponentType type; + /// This component's custom ID. final String customId; + /// The options in this menu. + /// + /// Will be `null` if this menu is not a [MessageComponentType.stringSelect] menu. final List? options; + /// The channel types displayed in this select menu. + /// + /// Will be `null` if this menu is not a [MessageComponentType.channelSelect] menu. final List? channelTypes; + /// The placeholder shown when the user has not yet selected a value. final String? placeholder; + /// The default selected values in this menu. + final List? defaultValues; + + /// The minimum number of values the user must select. final int? minValues; + /// The maximum number of values the user must select. final int? maxValues; + /// Whether this component is disabled. final bool? isDisabled; + /// Create a new [SelectMenuComponent]. SelectMenuComponent({ required this.type, required this.customId, required this.options, required this.channelTypes, required this.placeholder, + required this.defaultValues, required this.minValues, required this.maxValues, required this.isDisabled, }); } +/// The type of a [SelectMenuDefaultValue]. +enum SelectMenuDefaultValueType { + user._('user'), + role._('role'), + channel._('channel'); + + /// The value of this [SelectMenuDefaultValue]. + final String value; + + const SelectMenuDefaultValueType._(this.value); + + /// Parse a [SelectMenuDefaultValueType] from a [String]. + /// + /// The [value] must be a valid select menu default value type. + factory SelectMenuDefaultValueType.parse(String value) => SelectMenuDefaultValueType.values.firstWhere( + (type) => type.value == value, + orElse: () => throw FormatException('Unknown select menu default value type', value), + ); +} + +/// A default value in a [SelectMenu]. +class SelectMenuDefaultValue { + /// The ID of this entity. + final Snowflake id; + + /// The type of this entity. + final SelectMenuDefaultValueType type; + + /// Create a new [SelectMenuDefaultValue]. + SelectMenuDefaultValue({required this.id, required this.type}); +} + +/// An option in a [SelectMenu]. class SelectMenuOption with ToStringHelper { + /// The label shown to the user. final String label; + /// The value sent to the application. final String value; + /// The description of this option. final String? description; + /// The emoji shown by this emoji. final Emoji? emoji; + /// Whether this [SelectMenuOption] is selected by default. final bool? isDefault; + /// Create a new [SelectMenuOption]. SelectMenuOption({ required this.label, required this.value, @@ -134,26 +214,36 @@ class SelectMenuOption with ToStringHelper { }); } +/// A text field in a modal. class TextInputComponent extends MessageComponent { @override MessageComponentType get type => MessageComponentType.textInput; + /// This component's custom ID. final String customId; + /// The style of this [TextInputComponent]. final TextInputStyle? style; + /// This component's label. final String? label; + /// The minimum number of characters the user must input. final int? minLength; + /// The maximum number of characters the user can input. final int? maxLength; + /// Whether this component requires input. final bool? isRequired; + /// The text contained in this component. final String? value; + /// Placeholder text shown when this component is empty. final String? placeholder; + /// Create a new [TextInputComponent]. TextInputComponent({ required this.customId, required this.style, @@ -166,14 +256,19 @@ class TextInputComponent extends MessageComponent { }); } +/// The type of a [TextInputComponent]. enum TextInputStyle { short._(1), paragraph._(2); + /// The value of this [TextInputStyle]. final int value; const TextInputStyle._(this.value); + /// Parse a [TextInputComponent] from an [int]. + /// + /// The [value] must beb a valid text input style. factory TextInputStyle.parse(int value) => TextInputStyle.values.firstWhere( (style) => style.value == value, orElse: () => throw FormatException('Unknown text input style', value), From 57ce193445f9b95304eb9771a9e44a165e423138 Mon Sep 17 00:00:00 2001 From: Abitofevrything <54505189+abitofevrything@users.noreply.github.com> Date: Sat, 9 Dec 2023 17:17:20 +0100 Subject: [PATCH 11/14] Enable dartdoc link to source (#607) * Enable dartdoc link-to-source * Correct unresolved documentation references --- dartdoc_options.yaml | 4 ++++ lib/src/client_options.dart | 2 +- lib/src/http/route.dart | 2 +- lib/src/models/gateway/event.dart | 2 +- lib/src/models/guild/onboarding.dart | 2 +- lib/src/models/message/component.dart | 6 +++--- 6 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 dartdoc_options.yaml diff --git a/dartdoc_options.yaml b/dartdoc_options.yaml new file mode 100644 index 000000000..991b6e67b --- /dev/null +++ b/dartdoc_options.yaml @@ -0,0 +1,4 @@ +dartdoc: + linkToSource: + root: . + uriTemplate: 'https://github.com/nyxx-discord/nyxx/blob/main/%f%#L%l%' diff --git a/lib/src/client_options.dart b/lib/src/client_options.dart index 173a4bfa0..7627fe449 100644 --- a/lib/src/client_options.dart +++ b/lib/src/client_options.dart @@ -122,7 +122,7 @@ class RestClientOptions extends ClientOptions { }); } -/// Options for controlling the behavior of a [NyxxWebsocket] client. +/// Options for controlling the behavior of a [NyxxGateway] client. class GatewayClientOptions extends RestClientOptions { /// The minimum number of session starts this client needs to connect. /// diff --git a/lib/src/http/route.dart b/lib/src/http/route.dart index 16d91b545..1b9894e11 100644 --- a/lib/src/http/route.dart +++ b/lib/src/http/route.dart @@ -67,7 +67,7 @@ class HttpRoutePart { /// A parameter in a [HttpRoutePart]. /// -/// {@template http_route_part} +/// {@template http_route_param} /// This is not a query parameter, it is a parameter encoded in the path of the request itself, such /// as the id of a guild in `/guilds/0123456789`. /// {@endtemplate} diff --git a/lib/src/models/gateway/event.dart b/lib/src/models/gateway/event.dart index 1f969c45e..32b7ffe7d 100644 --- a/lib/src/models/gateway/event.dart +++ b/lib/src/models/gateway/event.dart @@ -66,7 +66,7 @@ class HeartbeatEvent extends GatewayEvent { /// Emitted when the client receives a request to reconnect. /// {@endtemplate} class ReconnectEvent extends GatewayEvent { - /// {@macro reconnect_events} + /// {@macro reconnect_event} ReconnectEvent() : super(opcode: Opcode.reconnect); } diff --git a/lib/src/models/guild/onboarding.dart b/lib/src/models/guild/onboarding.dart index ec75336f3..3d4fd2397 100644 --- a/lib/src/models/guild/onboarding.dart +++ b/lib/src/models/guild/onboarding.dart @@ -9,7 +9,7 @@ import 'package:nyxx/src/utils/to_string_helper/to_string_helper.dart'; /// The configuration for a [Guild]'s onboarding process. /// {@endtemplate} class Onboarding with ToStringHelper { - /// The manager for this [onboarding]. + /// The manager for this [Onboarding]. final GuildManager manager; /// The ID of the guild this onboarding is for. diff --git a/lib/src/models/message/component.dart b/lib/src/models/message/component.dart index 68efa8f9f..cb3adff22 100644 --- a/lib/src/models/message/component.dart +++ b/lib/src/models/message/component.dart @@ -42,7 +42,7 @@ class ActionRowComponent extends MessageComponent { @override MessageComponentType get type => MessageComponentType.actionRow; - /// The children of this [ActionRow]. + /// The children of this [ActionRowComponent]. final List components; /// Create a new [ActionRowComponent]. @@ -175,7 +175,7 @@ enum SelectMenuDefaultValueType { ); } -/// A default value in a [SelectMenu]. +/// A default value in a [SelectMenuComponent]. class SelectMenuDefaultValue { /// The ID of this entity. final Snowflake id; @@ -187,7 +187,7 @@ class SelectMenuDefaultValue { SelectMenuDefaultValue({required this.id, required this.type}); } -/// An option in a [SelectMenu]. +/// An option in a [SelectMenuComponent]. class SelectMenuOption with ToStringHelper { /// The label shown to the user. final String label; From 73c813ac81d7ace38cfe957d6d96fc002430ebd9 Mon Sep 17 00:00:00 2001 From: Abitofevrything <54505189+abitofevrything@users.noreply.github.com> Date: Sun, 10 Dec 2023 00:39:22 +0100 Subject: [PATCH 12/14] Release 6.1.0 (#608) --- CHANGELOG.md | 24 ++++++++++++++++++++---- lib/src/api_options.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f150f1a1..0a3a23fd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,30 @@ +## 6.1.0 +__09.21.2023__ + +- feat: Add payload to `EntitlementDeleteEvent`. +- feat: Add `flags` field to `Sku`. +- feat: Add support for select menu default values. +- feat: Add `GuildUpdateBuilder.safetyAlertsChannelId`. +- docs: Enable link to source in package documentation. +- feat: Add AutoMod message types. +- bug: Fix `ButtonBuilder` serialization. +- bug: Fix `GuildUpdateBuilder` not being able to unset certain settings. +- bug: Fix incorrect `PermissionOverwriteBuilder` serialization when creating/updating channels. +- bug: Fix `GuildManager.listBans` ignoring the provided parameters. +- bug: Correctly export `Credentials` from `package:oauth2` for OAuth2 support. +- bug: Fix members in message interactions not having their guild set. + ## 6.0.3 __26.11.2023__ -- bug: Fix incorrect serialization of autocompletion interaction responses (again) -- bug: Try to fix invalid sessions triggered by Gateway reconnects +- bug: Fix incorrect serialization of autocompletion interaction responses (again). +- bug: Try to fix invalid sessions triggered by Gateway reconnects. ## 6.0.2 __16.11.2023__ -- bug: Fix incorrect assertions in interaction.respond -- bug: Fix incorrect serialization of autocompletion interaction responses +- bug: Fix incorrect assertions in interaction.respond. +- bug: Fix incorrect serialization of autocompletion interaction responses. ## 6.0.1 __01.11.2023__ diff --git a/lib/src/api_options.dart b/lib/src/api_options.dart index b1ce81d43..7e67acee8 100644 --- a/lib/src/api_options.dart +++ b/lib/src/api_options.dart @@ -6,7 +6,7 @@ import 'package:oauth2/oauth2.dart'; /// Options for connecting to the Discord API. abstract class ApiOptions { /// The version of nyxx used in [defaultUserAgent]. - static const nyxxVersion = '6.0.3'; + static const nyxxVersion = '6.1.0'; /// The URL to the nyxx repository used in [defaultUserAgent]. static const nyxxRepositoryUrl = 'https://github.com/nyxx-discord/nyxx'; diff --git a/pubspec.yaml b/pubspec.yaml index 4c7231eef..bc6e62949 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: nyxx -version: 6.0.3 +version: 6.1.0 description: A complete, robust and efficient wrapper around Discord's API for bots & applications. homepage: https://github.com/nyxx-discord/nyxx repository: https://github.com/nyxx-discord/nyxx From b082750c03c5d5c88836d10e949acc8eba75e400 Mon Sep 17 00:00:00 2001 From: Abitofevrything <54505189+abitofevrything@users.noreply.github.com> Date: Sun, 10 Dec 2023 00:39:50 +0100 Subject: [PATCH 13/14] Update README.md (#606) --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 21947bbf4..b0a53ad88 100644 --- a/README.md +++ b/README.md @@ -31,9 +31,8 @@ void main() async { ## Other nyxx packages - [nyxx_commands](https://pub.dev/packages/nyxx_commands): A command framework for handling both simple & complex commands. -- [nyxx_pagination](https://pub.dev/packages/nyxx_pagination): Pagination support for nyxx. +- [nyxx_extensions](https://pub.dev/packages/nyxx_extensions): Pagination, emoji utilities and other miscellaneous helpers for developing bots using nyxx. - [nyxx_lavalink](https://pub.dev/packages/nyxx_lavalink): Lavalink support for playing audio in voice channels. -- [nyxx_extensions](https://pub.dev/packages/nyxx_extensions): Miscellaneous helpers for common situations when developing bots. ## More examples From 6ed96579dea4ebe32f9aae1dbe846f2bcd4c0cc4 Mon Sep 17 00:00:00 2001 From: Anton Date: Mon, 11 Dec 2023 09:13:16 +0200 Subject: [PATCH 14/14] Implement gdm control endpoints (#600) * implement oauth2 gdm control * add `threadName`/`appliedTags` params to `WebhookManager.execute` * add DmRecipientBuilder --- lib/src/builders/channel/group_dm.dart | 14 ++++++++++++++ lib/src/http/managers/channel_manager.dart | 21 ++++++++++++++++++++- lib/src/http/managers/webhook_manager.dart | 15 ++++++++++++--- lib/src/http/route.dart | 3 +++ 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/lib/src/builders/channel/group_dm.dart b/lib/src/builders/channel/group_dm.dart index 99a40ee89..d60ee009b 100644 --- a/lib/src/builders/channel/group_dm.dart +++ b/lib/src/builders/channel/group_dm.dart @@ -16,3 +16,17 @@ class GroupDmUpdateBuilder extends UpdateBuilder { if (icon != null) 'icon': base64Encode(icon!), }; } + +class DmRecipientBuilder extends CreateBuilder { + String accessToken; + + String nick; + + DmRecipientBuilder({required this.accessToken, required this.nick}); + + @override + Map build() => { + 'access_token': accessToken, + 'nick': nick, + }; +} diff --git a/lib/src/http/managers/channel_manager.dart b/lib/src/http/managers/channel_manager.dart index 26c36686f..6021e3235 100644 --- a/lib/src/http/managers/channel_manager.dart +++ b/lib/src/http/managers/channel_manager.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:http/http.dart' show MultipartFile; import 'package:nyxx/src/builders/builder.dart'; +import 'package:nyxx/src/builders/channel/group_dm.dart'; import 'package:nyxx/src/builders/channel/stage_instance.dart'; import 'package:nyxx/src/builders/channel/thread.dart'; import 'package:nyxx/src/builders/invite.dart'; @@ -607,7 +608,7 @@ class ChannelManager extends ReadOnlyManager { request = MultipartRequest( route, - method: 'PATCH', + method: 'POST', jsonPayload: jsonEncode(payload), files: files, ); @@ -825,4 +826,22 @@ class ChannelManager extends ReadOnlyManager { stageInstanceCache.remove(channelId); } + + Future addRecipient(Snowflake channelId, Snowflake userId, DmRecipientBuilder builder) async { + final route = HttpRoute() + ..channels(id: channelId.toString()) + ..recipients(id: userId.toString()); + final request = BasicRequest(route, method: 'PUT', body: jsonEncode(builder.build())); + + await client.httpHandler.executeSafe(request); + } + + Future removeRecipient(Snowflake channelId, Snowflake userId) async { + final route = HttpRoute() + ..channels(id: channelId.toString()) + ..recipients(id: userId.toString()); + final request = BasicRequest(route, method: 'DELETE'); + + await client.httpHandler.executeSafe(request); + } } diff --git a/lib/src/http/managers/webhook_manager.dart b/lib/src/http/managers/webhook_manager.dart index 5a39dfec5..39e8450cf 100644 --- a/lib/src/http/managers/webhook_manager.dart +++ b/lib/src/http/managers/webhook_manager.dart @@ -151,7 +151,8 @@ class WebhookManager extends Manager { } /// Execute a webhook. - Future execute(Snowflake id, MessageBuilder builder, {required String token, bool? wait, Snowflake? threadId}) async { + Future execute(Snowflake id, MessageBuilder builder, + {required String token, bool? wait, Snowflake? threadId, String? threadName, List? appliedTags}) async { final route = HttpRoute() ..webhooks(id: id.toString()) ..add(HttpRoutePart(token)); @@ -160,7 +161,11 @@ class WebhookManager extends Manager { final HttpRequest request; if (!identical(builder.attachments, sentinelList) && builder.attachments?.isNotEmpty == true) { final attachments = builder.attachments!; - final payload = builder.build(); + final payload = { + ...builder.build(), + if (threadName != null) 'thread_name': threadName, + if (appliedTags != null) 'applied_tags': appliedTags.map((e) => e.toString()), + }; final files = []; for (int i = 0; i < attachments.length; i++) { @@ -185,7 +190,11 @@ class WebhookManager extends Manager { request = BasicRequest( route, method: 'POST', - body: jsonEncode(builder.build()), + body: jsonEncode({ + ...builder.build(), + if (threadName != null) 'thread_name': threadName, + if (appliedTags != null) 'applied_tags': appliedTags.map((e) => e.toString()), + }), queryParameters: queryParameters, authenticated: false, ); diff --git a/lib/src/http/route.dart b/lib/src/http/route.dart index 1b9894e11..751f79af8 100644 --- a/lib/src/http/route.dart +++ b/lib/src/http/route.dart @@ -311,4 +311,7 @@ extension RouteHelpers on HttpRoute { /// Adds the [`avatar-decorations`](https://discord.com/developers/docs/reference#image-formatting-cdn-endpoints) part to this [HttpRoute]. void avatarDecorations({String? id}) => add(HttpRoutePart('avatar-decorations', [if (id != null) HttpRouteParam(id)])); + + /// Adds the [`recipients`](https://discord.com/developers/docs/resources/channel#group-dm-add-recipient) part to this [HttpRoute]. + void recipients({String? id}) => add(HttpRoutePart('recipients', [if (id != null) HttpRouteParam(id)])); }