diff --git a/lib/api.dart b/lib/api.dart index 1d71cab9..81e60109 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -13,6 +13,7 @@ export 'src/api/managers/member_role_manager.dart' show MemberRoleManager; export 'src/api/managers/voice_manager.dart' show VoiceManager; export 'src/api/guilds/guild.dart' show Guild; +export 'src/api/guilds/guild_preview.dart' show GuildPreview; export 'src/api/moderation_rule.dart' show ModerationEventType, ModerationTriggerType, ModerationPresetType, ModerationActionType, ModerationTriggerMetadata, ModerationActionMetadata, ModerationAction, ModerationRule; export 'src/api/guilds/guild_scheduled_event.dart' show ScheduledEventStatus, ScheduledEventEntityType, GuildScheduledEvent, ScheduledEventUser; @@ -28,18 +29,18 @@ export 'src/api/channels/news_channel.dart' show NewsChannel; export 'src/api/channels/permission_overwrite.dart' show PermissionOverwrite, PermissionOverwriteType; export 'src/api/messages/message.dart' show Message; -export 'src/api/messages/message_embed.dart' show MessageEmbed, Footer, Image, Thumbnail, Author, Field; +export 'src/api/messages/embed_builder.dart' show EmbedBuilder, Footer, Image, Thumbnail, Author, Field; export 'src/api/color.dart' show Color; export 'src/api/emoji.dart' show EmojiBuilder, Emoji; export 'src/api/role.dart' show Role; -export 'src/api/components/row.dart' show Row; -export 'src/api/components/select_menu.dart' show SelectMenu, SelectMenuOption, EmojiOption; -export 'src/api/components/modal.dart' show Modal; -export 'src/api/components/text_input.dart' show TextInputStyle; -export 'src/api/components/button.dart' show Button, ButtonStyle; -export 'src/api/components/link.dart' show Link; +export 'src/api/components/row_builder.dart' show RowBuilder; +export 'src/api/components/select_menu_builder.dart' show SelectMenuBuilder, SelectMenuOption, EmojiOption; +export 'src/api/components/modal_builder.dart' show ModalBuilder; +export 'src/api/components/text_input_builder.dart' show TextInputBuilder, TextInputStyle; +export 'src/api/components/button_builder.dart' show ButtonBuilder, ButtonStyle; +export 'src/api/components/link_builder.dart' show LinkBuilder; export 'src/api/interactions/command_interaction.dart' show CommandInteraction; export 'src/api/interactions/button_interaction.dart' show ButtonInteraction; diff --git a/lib/src/api/channels/text_based_channel.dart b/lib/src/api/channels/text_based_channel.dart index ca182ce8..f4bc5034 100644 --- a/lib/src/api/channels/text_based_channel.dart +++ b/lib/src/api/channels/text_based_channel.dart @@ -45,7 +45,7 @@ class TextBasedChannel extends Channel { MessageManager get messages => _messages; ThreadManager get threads => _threads; - Future send ({ String? content, List? embeds, List? components, bool? tts }) async { + Future send ({ String? content, List? embeds, List? components, bool? tts }) async { MineralClient client = ioc.singleton(ioc.services.client); Response response = await client.sendMessage(this, diff --git a/lib/src/api/client/mineral_client.dart b/lib/src/api/client/mineral_client.dart index 9b9335c6..d457479d 100644 --- a/lib/src/api/client/mineral_client.dart +++ b/lib/src/api/client/mineral_client.dart @@ -77,6 +77,7 @@ class MineralClient { String _sessionId; Application _application; List _intents; + late DateTime uptime; MineralClient( this._user, @@ -96,9 +97,11 @@ class MineralClient { Application get application => _application; List get intents => _intents; + /// ### Returns the time the [MineralClient] is online + Duration get uptimeDuration => DateTime.now().difference(uptime); + /// ### Defines the presence that this should adopt /// - /// /// Example : /// ```dart /// client.setPresence( diff --git a/lib/src/api/components/button.dart b/lib/src/api/components/button_builder.dart similarity index 72% rename from lib/src/api/components/button.dart rename to lib/src/api/components/button_builder.dart index 81778f3b..379a62b2 100644 --- a/lib/src/api/components/button.dart +++ b/lib/src/api/components/button_builder.dart @@ -15,14 +15,14 @@ enum ButtonStyle { String toString () => value.toString(); } -class Button extends Component { +class ButtonBuilder extends Component { String customId; - String label; + String? label; ButtonStyle style; EmojiBuilder? emoji; bool disabled; - Button({ required this.customId, required this.label, required this.style, this.emoji, this.disabled = false }) : super(type: ComponentType.button); + ButtonBuilder({ required this.customId, this.label, required this.style, this.emoji, this.disabled = false }) : super(type: ComponentType.button); @override dynamic toJson () { @@ -36,8 +36,8 @@ class Button extends Component { }; } - factory Button.from({ required dynamic payload }) { - return Button( + factory ButtonBuilder.from({ required dynamic payload }) { + return ButtonBuilder( customId: payload['custom_id'], label: payload['label'], style: ButtonStyle.values.firstWhere((element) => element.value == payload['style']) diff --git a/lib/src/api/components/link.dart b/lib/src/api/components/link_builder.dart similarity index 60% rename from lib/src/api/components/link.dart rename to lib/src/api/components/link_builder.dart index f1fa0fb3..bd9f1d68 100644 --- a/lib/src/api/components/link.dart +++ b/lib/src/api/components/link_builder.dart @@ -1,11 +1,11 @@ import 'package:mineral/src/api/components/component.dart'; -class Link extends Component { +class LinkBuilder extends Component { String label; String url; int style = 5; - Link({ required this.label, required this.url }) : super(type: ComponentType.button); + LinkBuilder({ required this.label, required this.url }) : super(type: ComponentType.button); @override dynamic toJson () { @@ -17,8 +17,8 @@ class Link extends Component { }; } - factory Link.from({ required dynamic payload }) { - return Link( + factory LinkBuilder.from({ required dynamic payload }) { + return LinkBuilder( url: payload['url'], label: payload['label'], ); diff --git a/lib/src/api/components/modal.dart b/lib/src/api/components/modal_builder.dart similarity index 57% rename from lib/src/api/components/modal.dart rename to lib/src/api/components/modal_builder.dart index 909f61da..6b98cb18 100644 --- a/lib/src/api/components/modal.dart +++ b/lib/src/api/components/modal_builder.dart @@ -1,25 +1,24 @@ import 'package:mineral/src/api/components/component.dart'; -import 'package:mineral/src/api/components/text_input.dart'; import '../../../api.dart'; -class Modal extends Component { +class ModalBuilder extends Component { String label; String customId; - List components = []; + List? components = []; - Modal({ required this.customId, required this.label }) : super(type: ComponentType.selectMenu); + ModalBuilder({ required this.customId, required this.label, this.components }) : super(type: ComponentType.selectMenu); /// ### Created a input text field /// /// Example : /// ```dart - /// final Modal modal = Modal(customId: 'my_modal', label: 'My modal') + /// final ModalBuilder modal = ModalBuilder(customId: 'my_modal', label: 'My modal') /// .addInput(customId: 'my_text', label: 'Premier texte'); /// ``` - Modal addInput ({ required String customId, required String label, bool? required, int? minLength, int? maxLength, String? placeholder, String? value }) { - _addInput(customId: customId, label: label, style: TextInputStyle.short, required: required, minLength: minLength, maxLength: maxLength, placeholder: placeholder, value: value); + ModalBuilder addInput ({ required String customId, required String label, bool? required, int? minLength, int? maxLength, String? placeholder, String? value }) { + _addInput(customId: customId, label: label, style: TextInputStyle.input, required: required, minLength: minLength, maxLength: maxLength, placeholder: placeholder, value: value); return this; } @@ -27,16 +26,18 @@ class Modal extends Component { /// /// Example : /// ```dart - /// final Modal modal = Modal(customId: 'my_modal', label: 'My modal') + /// final ModalBuilder modal = ModalBuilder(customId: 'my_modal', label: 'My modal') /// .addParagraph(customId: 'my_paragraph', label: 'Second texte'); /// ``` - Modal addParagraph ({ required String customId, required String label, bool? required, int? minLength, int? maxLength, String? placeholder, String? value }) { + ModalBuilder addParagraph ({ required String customId, required String label, bool? required, int? minLength, int? maxLength, String? placeholder, String? value }) { _addInput(customId: customId, label: label, style: TextInputStyle.paragraph, required: required, minLength: minLength, maxLength: maxLength, placeholder: placeholder, value: value); return this; } void _addInput ({ required String customId, required String label, required TextInputStyle style, bool? required, int? minLength, int? maxLength, String? placeholder, String? value }) { - TextInput input = TextInput( + components ??= []; + + final TextInputBuilder input = TextInputBuilder( customId: customId, label: label, style: style, @@ -47,7 +48,7 @@ class Modal extends Component { value: value, ); - components.add(Row(components: [input])); + components?.add(RowBuilder.fromComponents([input])); } @override @@ -55,7 +56,7 @@ class Modal extends Component { return { 'title': label, 'custom_id': customId, - 'components': components.map((component) => component.toJson()).toList() + 'components': components?.map((component) => component.toJson()).toList() }; } } diff --git a/lib/src/api/components/row.dart b/lib/src/api/components/row.dart deleted file mode 100644 index 9b2a5545..00000000 --- a/lib/src/api/components/row.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:mineral/src/api/components/component.dart'; - -class Row extends Component { - List? components = []; - - Row({ this.components }) : super(type: ComponentType.actionRow); - - @override - Object toJson () { - return { - 'type': type.value, - 'components': components?.map((Component component) => component.toJson()).toList() - }; - } - - factory Row.from({ required dynamic payload }) { - return Row(); - } -} diff --git a/lib/src/api/components/row_builder.dart b/lib/src/api/components/row_builder.dart new file mode 100644 index 00000000..8f304448 --- /dev/null +++ b/lib/src/api/components/row_builder.dart @@ -0,0 +1,28 @@ +import 'package:mineral/api.dart'; +import 'package:mineral/src/api/components/component.dart'; + +class RowBuilder extends Component { + List components = []; + + RowBuilder() : super(type: ComponentType.actionRow); + + @override + Object toJson () { + return { + 'type': type.value, + 'components': components.map((Component component) => component.toJson()).toList() + }; + } + + factory RowBuilder.fromComponents(List components) { + return RowBuilder.fromComponents(components); + } + + factory RowBuilder.fromComponent(Component component) { + return RowBuilder.fromComponents([component]); + } + + factory RowBuilder.fromTextInput(TextInputBuilder input) { + return RowBuilder.fromComponents([input]); + } +} diff --git a/lib/src/api/components/select_menu.dart b/lib/src/api/components/select_menu_builder.dart similarity index 75% rename from lib/src/api/components/select_menu.dart rename to lib/src/api/components/select_menu_builder.dart index 1f0bd62c..5e194bdd 100644 --- a/lib/src/api/components/select_menu.dart +++ b/lib/src/api/components/select_menu_builder.dart @@ -1,7 +1,7 @@ import 'package:mineral/api.dart'; import 'package:mineral/src/api/components/component.dart'; -class SelectMenu extends Component { +class SelectMenuBuilder extends Component { String customId; List options = []; String? placeholder; @@ -9,7 +9,9 @@ class SelectMenu extends Component { int? maxValues = 25; bool? disabled = false; - SelectMenu({ required this.customId, required this.options, this.placeholder, this.minValues, this.maxValues, this.disabled }) : super (type: ComponentType.selectMenu); + SelectMenuBuilder({ required this.customId, required this.options, this.placeholder, this.minValues, this.maxValues, this.disabled }) : super (type: ComponentType.selectMenu); + + RowBuilder toRow () => RowBuilder.fromComponents([this]); @override Object toJson () { @@ -43,7 +45,7 @@ class SelectMenuOption { String label; String description; T value; - EmojiOption? emoji; + EmojiBuilder? emoji; SelectMenuOption({ required this.label, required this.description, required this.value, this.emoji }); @@ -52,7 +54,7 @@ class SelectMenuOption { 'label': label, 'description': description, 'value': value, - 'emoji': emoji?.toJson(), + 'emoji': emoji?.emoji.toJson(), }; } } diff --git a/lib/src/api/components/text_input.dart b/lib/src/api/components/text_input_builder.dart similarity index 87% rename from lib/src/api/components/text_input.dart rename to lib/src/api/components/text_input_builder.dart index e05c40bc..6c854845 100644 --- a/lib/src/api/components/text_input.dart +++ b/lib/src/api/components/text_input_builder.dart @@ -1,7 +1,7 @@ import 'package:mineral/src/api/components/component.dart'; enum TextInputStyle { - short(1), + input(1), paragraph(2); final int value; @@ -11,23 +11,23 @@ enum TextInputStyle { String toString () => value.toString(); } -class TextInput extends Component { +class TextInputBuilder extends Component { String customId; String label; TextInputStyle style; int? minLength; int? maxLength; - bool required; + bool required = false; String? placeholder; String? value; - TextInput({ + TextInputBuilder({ required this.customId, required this.label, required this.style, this.minLength, this.maxLength, - required this.required, + this.required = false, this.placeholder, this.value, }) : super(type: ComponentType.textInput); diff --git a/lib/src/api/emoji.dart b/lib/src/api/emoji.dart index eed62b0b..a8544743 100644 --- a/lib/src/api/emoji.dart +++ b/lib/src/api/emoji.dart @@ -111,7 +111,7 @@ class Emoji extends PartialEmoji { @override String toString () => isAnimated ? '' - : '<$label:$id>'; + : '<:$label:$id>'; factory Emoji.from({ required MemberManager memberManager, required EmojiManager emojiManager, required dynamic payload }) { return Emoji( diff --git a/lib/src/api/guilds/guild.dart b/lib/src/api/guilds/guild.dart index f490cfb1..03b89591 100644 --- a/lib/src/api/guilds/guild.dart +++ b/lib/src/api/guilds/guild.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:http/http.dart'; import 'package:mineral/api.dart'; import 'package:mineral/console.dart'; @@ -13,7 +15,6 @@ import 'package:mineral/src/api/managers/moderation_rule_manager.dart'; import 'package:mineral/src/api/managers/sticker_manager.dart'; import 'package:mineral/src/api/managers/webhook_manager.dart'; import 'package:mineral/src/api/managers/guild_scheduled_event_manager.dart'; -import 'package:mineral/src/api/sticker.dart'; import 'package:mineral/src/api/welcome_screen.dart'; import 'package:collection/collection.dart'; @@ -544,6 +545,16 @@ class Guild { } } + Future preview () async { + Http http = ioc.singleton(ioc.services.http); + Response response = await http.get(url: '/guilds/$id/preview'); + + return GuildPreview.from( + guild: this, + payload: jsonDecode(response.body) + ); + } + factory Guild.from({ required EmojiManager emojiManager, required MemberManager memberManager, @@ -555,10 +566,6 @@ class Guild { required dynamic payload }) { StickerManager stickerManager = StickerManager(); - for (dynamic element in payload['stickers']) { - Sticker sticker = Sticker.from(element); - stickerManager.cache.putIfAbsent(sticker.id, () => sticker); - } List features = []; for (String element in payload['features']) { diff --git a/lib/src/api/guilds/guild_member_reaction.dart b/lib/src/api/guilds/guild_member_reaction.dart index 2b7afc1e..578f3cac 100644 --- a/lib/src/api/guilds/guild_member_reaction.dart +++ b/lib/src/api/guilds/guild_member_reaction.dart @@ -20,7 +20,6 @@ class GuildMemberReaction { ? '${_partialEmoji.label}:${_partialEmoji.id}' : _partialEmoji.label; - print('/channels/${_message.channel.id}/messages/${_message.id}/reactions/$_emoji/${user.id}'); Response response = await http.destroy(url: '/channels/${_message.channel.id}/messages/${_message.id}/reactions/$_emoji/${user.id}'); if (response.statusCode == 200) { _manager.reactions.remove(_emoji); diff --git a/lib/src/api/guilds/guild_preview.dart b/lib/src/api/guilds/guild_preview.dart new file mode 100644 index 00000000..eea2bc7a --- /dev/null +++ b/lib/src/api/guilds/guild_preview.dart @@ -0,0 +1,75 @@ +import 'package:mineral/api.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/api/sticker.dart'; + +class GuildPreview { + final Snowflake _id; + final String _label; + final String? _description; + final String? _icon; + final String? _splash; + final String? _discoverySplash; + final Map _emojis; + final Map _stickers; + final List _features; + final int _approximateMemberCount; + final int _approximatePresenceCount; + + GuildPreview( + this._id, + this._label, + this._description, + this._icon, + this._splash, + this._discoverySplash, + this._emojis, + this._stickers, + this._features, + this._approximateMemberCount, + this._approximatePresenceCount, + ); + + Snowflake get id => _id; + String get label => _label; + String? get description => _description; + String? get icon => '${Constants.cdnUrl}/icons/$id/$_icon.png'; + String? get splash => '${Constants.cdnUrl}/splashes/$id/$_splash.png'; + String? get discoverySplash => '${Constants.cdnUrl}/discovery-splashes/$id/$_discoverySplash.png'; + Map get emojis => _emojis; + Map get stickers => _stickers; + List get features => _features; + int get approximateMemberCount => _approximateMemberCount; + int get approximatePresenceCount => _approximatePresenceCount; + + factory GuildPreview.from({ required Guild guild, required dynamic payload }) { + final Map emojis = {}; + for (final payload in payload['emojis']) { + emojis.putIfAbsent(payload['id'], () => guild.emojis.cache.getOrFail(payload['id'])); + } + + final Map stickers = {}; + for (final payload in payload['stickers']) { + stickers.putIfAbsent(payload['id'], () => guild.stickers.cache.getOrFail(payload['id'])); + } + + final List features = []; + for (final payload in payload['features']) { + final feature = GuildFeature.values.firstWhere((feature) => feature.value == payload); + features.add(feature); + } + + return GuildPreview( + payload['id'], + payload['name'], + payload['description'], + payload['icon'], + payload['splash'], + payload['discovery_splash'], + emojis, + stickers, + features, + payload['approximate_member_count'], + payload['approximate_presence_count'] + ); + } +} diff --git a/lib/src/api/interactions/interaction.dart b/lib/src/api/interactions/interaction.dart index 2c2c5a1f..28f08dd1 100644 --- a/lib/src/api/interactions/interaction.dart +++ b/lib/src/api/interactions/interaction.dart @@ -41,19 +41,19 @@ class Interaction { /// ```dart /// await interaction.reply(content: 'Hello ${interaction.user.username}'); /// ``` - Future reply ({ String? content, List? embeds, List? components, bool? tts, bool? private }) async { + Future reply ({ String? content, List? embeds, List? components, bool? tts, bool? private }) async { Http http = ioc.singleton(ioc.services.http); List embedList = []; if (embeds != null) { - for (MessageEmbed element in embeds) { + for (EmbedBuilder element in embeds) { embedList.add(element.toJson()); } } List componentList = []; if (components != null) { - for (Row element in components) { + for (RowBuilder element in components) { componentList.add(element.toJson()); } } @@ -80,7 +80,7 @@ class Interaction { /// /// await interaction.modal(modal); /// ``` - Future modal (Modal modal) async { + Future modal (ModalBuilder modal) async { Http http = ioc.singleton(ioc.services.http); await http.post(url: "/interactions/$id/$token/callback", payload: { diff --git a/lib/src/api/messages/dm_message.dart b/lib/src/api/messages/dm_message.dart index 9fc9ed5d..8b4ed2d5 100644 --- a/lib/src/api/messages/dm_message.dart +++ b/lib/src/api/messages/dm_message.dart @@ -33,7 +33,7 @@ class DmMessage extends PartialMessage { MineralClient client = ioc.singleton(ioc.services.client); User? user = client.users.cache.get(payload['author']['id']); - List embeds = []; + List embeds = []; for (dynamic element in payload['embeds']) { List fields = []; if (element['fields'] != null) { @@ -43,7 +43,7 @@ class DmMessage extends PartialMessage { } } - MessageEmbed embed = MessageEmbed( + EmbedBuilder embed = EmbedBuilder( title: element['title'], description: element['description'], url: element['url'], diff --git a/lib/src/api/messages/message_embed.dart b/lib/src/api/messages/embed_builder.dart similarity index 67% rename from lib/src/api/messages/message_embed.dart rename to lib/src/api/messages/embed_builder.dart index bc80608b..c6033cc2 100644 --- a/lib/src/api/messages/message_embed.dart +++ b/lib/src/api/messages/embed_builder.dart @@ -1,4 +1,5 @@ import 'package:mineral/api.dart'; +import 'package:mineral/core.dart'; class Footer { String text; @@ -76,7 +77,7 @@ class Field { }; } -class MessageEmbed { +class EmbedBuilder { String? title; String? description; String? url; @@ -88,7 +89,7 @@ class MessageEmbed { List? fields; Color? color; - MessageEmbed({ + EmbedBuilder({ this.title, this.description, this.url, @@ -105,10 +106,10 @@ class MessageEmbed { /// /// Example : /// ```dart - /// final embed = MessageEmbed() + /// final embed = EmbedBuilder() /// .setTitle('My title'); /// ``` - MessageEmbed setTitle (String value) { + EmbedBuilder setTitle (String value) { title = value; return this; } @@ -117,10 +118,10 @@ class MessageEmbed { /// /// Example : /// ```dart - /// final embed = MessageEmbed() + /// final embed = EmbedBuilder() /// .setDescription('My description'); /// ``` - MessageEmbed setDescription (String value) { + EmbedBuilder setDescription (String value) { description = value; return this; } @@ -129,10 +130,10 @@ class MessageEmbed { /// /// Example : /// ```dart - /// final embed = MessageEmbed() + /// final embed = EmbedBuilder() /// .setFooter(text: 'My title'); /// ``` - MessageEmbed setFooter ({ required String text, String? iconUrl, String? proxyIconUrl }) { + EmbedBuilder setFooter ({ required String text, String? iconUrl, String? proxyIconUrl }) { footer = Footer(text: text, iconUrl: iconUrl, proxyIconUrl: proxyIconUrl); return this; } @@ -141,10 +142,10 @@ class MessageEmbed { /// /// Example : /// ```dart - /// final embed = MessageEmbed() + /// final embed = EmbedBuilder() /// .setImage(url: 'https://..../images/my_picture.png'); /// ``` - MessageEmbed setImage ({ required String url, String? proxyUrl, int? width, int? height }) { + EmbedBuilder setImage ({ required String url, String? proxyUrl, int? width, int? height }) { image = Image(url: url, proxyUrl: proxyUrl, width: width, height: height); return this; } @@ -153,10 +154,10 @@ class MessageEmbed { /// /// Example : /// ```dart - /// final embed = MessageEmbed() + /// final embed = EmbedBuilder() /// .setThumbnail(url: 'https://..../images/my_picture.png'); /// ``` - MessageEmbed setThumbnail ({ required String url, String? proxyUrl, int? width, int? height }) { + EmbedBuilder setThumbnail ({ required String url, String? proxyUrl, int? width, int? height }) { thumbnail = Thumbnail(url: url, proxyUrl: proxyUrl, width: width, height: height); return this; } @@ -165,10 +166,10 @@ class MessageEmbed { /// /// Example : /// ```dart - /// final embed = MessageEmbed() + /// final embed = EmbedBuilder() /// .setAuthor(name: 'John Doe'); /// ``` - MessageEmbed setAuthor ({ required String name, String? url, String? iconUrl, String? proxyIconUrl }) { + EmbedBuilder setAuthor ({ required String name, String? url, String? iconUrl, String? proxyIconUrl }) { author = Author(name: name, url: url, iconUrl: iconUrl, proxyIconUrl: proxyIconUrl); return this; } @@ -177,17 +178,17 @@ class MessageEmbed { /// /// Example : /// ```dart - /// final embed = MessageEmbed() + /// final embed = EmbedBuilder() /// .setColor(Color.cyan_600); /// ``` /// Or with your custom color /// /// Example : /// ```dart - /// final embed = MessageEmbed() + /// final embed = EmbedBuilder() /// .setColor(Color('#FFFFFF')); /// ``` - MessageEmbed setColor (Color color) { + EmbedBuilder setColor (Color color) { this.color = color; return this; } @@ -196,15 +197,15 @@ class MessageEmbed { /// /// Example : /// ```dart - /// final embed = MessageEmbed() + /// final embed = EmbedBuilder() /// .setTimestamp(); /// ``` /// You can define an older or future timestamp /// DateTime date = DateTime.now().add(DateTime(days: 5)); - /// final embed = MessageEmbed() + /// final embed = EmbedBuilder() /// .setTimestamp(dateTime: date); /// ``` - MessageEmbed setTimestamp ({ DateTime? dateTime }) { + EmbedBuilder setTimestamp ({ DateTime? dateTime }) { timestamp = dateTime ?? DateTime.now(); return this; } @@ -213,10 +214,10 @@ class MessageEmbed { /// /// Example : /// ```dart - /// final embed = MessageEmbed() + /// final embed = EmbedBuilder() /// .setUrl('https://.....com'); /// ``` - MessageEmbed setUrl (String url) { + EmbedBuilder setUrl (String url) { this.url = url; return this; } @@ -225,10 +226,12 @@ class MessageEmbed { /// /// Example : /// ```dart - /// final embed = MessageEmbed() + /// final embed = EmbedBuilder() /// .addField(name: 'My field', value: 'My custom value'); /// ``` - MessageEmbed addField ({ required String name, required String value, bool? inline }) { + EmbedBuilder addField ({ required String name, required String value, bool? inline }) { + fields ??= []; + fields?.add(Field(name: name, value: value, inline: inline)); return this; } @@ -254,4 +257,32 @@ class MessageEmbed { 'author': author?.toJson(), }; } + + factory EmbedBuilder.fromGuildPreview(GuildPreview preview) { + MineralClient client = ioc.singleton(ioc.services.client); + + final EmbedBuilder embed = EmbedBuilder( + title: preview.label, + description: preview.description, + thumbnail: preview.icon != null ? Thumbnail(url: preview.icon!) : null, + image: preview.discoverySplash != null ? Image(url: preview.discoverySplash!) : null, + color: Color.invisible, + timestamp: DateTime.now(), + author: Author(name: client.user.username, iconUrl: client.user.getDisplayAvatarUrl()) + ); + + embed.addField(name: 'Identifier', value: preview.id); + embed.addField(name: 'Features', value: preview.features.map((feature) => '• $feature').join('\n'), inline: true); + + if (preview.stickers.isNotEmpty) { + embed.addField(name: 'Emojis', value: preview.emojis.values.map((emoji) => emoji).join(' '), inline: true); + } + + embed.addField(name: '\u200B', value: '\u200B'); + embed.addField(name: 'Online members', value: '${preview.approximatePresenceCount} members', inline: true); + embed.addField(name: 'Members', value: '${preview.approximateMemberCount} members', inline: true); + embed.addField(name: '\u200B', value: '\u200B', inline: true); + + return embed; + } } diff --git a/lib/src/api/messages/message.dart b/lib/src/api/messages/message.dart index d5b1be60..11d53559 100644 --- a/lib/src/api/messages/message.dart +++ b/lib/src/api/messages/message.dart @@ -32,7 +32,7 @@ class Message extends PartialMessage { GuildMember? get author => _author; - Future edit ({ String? content, List? embeds, List? components, bool? tts }) async { + Future edit ({ String? content, List? embeds, List? components, bool? tts }) async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch( @@ -87,7 +87,7 @@ class Message extends PartialMessage { factory Message.from({ required TextBasedChannel channel, required dynamic payload }) { GuildMember? guildMember = channel.guild?.members.cache.get(payload['author']['id']); - List embeds = []; + List embeds = []; for (dynamic element in payload['embeds']) { List fields = []; @@ -98,7 +98,7 @@ class Message extends PartialMessage { } } - MessageEmbed embed = MessageEmbed( + EmbedBuilder embed = EmbedBuilder( title: element['title'], description: element['description'], url: element['url'], diff --git a/lib/src/api/messages/partial_message.dart b/lib/src/api/messages/partial_message.dart index 26d772c7..7f99e3b8 100644 --- a/lib/src/api/messages/partial_message.dart +++ b/lib/src/api/messages/partial_message.dart @@ -9,7 +9,7 @@ class PartialMessage { final Snowflake _id; String content; final bool _tts; - List embeds; + List embeds; final bool _allowMentions; final PartialMessage? _reference; List components; diff --git a/lib/src/api/sticker.dart b/lib/src/api/sticker.dart index 1695ecf5..3aab4b58 100644 --- a/lib/src/api/sticker.dart +++ b/lib/src/api/sticker.dart @@ -30,7 +30,7 @@ enum FormatType { class Sticker { Snowflake _id; - Snowflake _packId; + Snowflake? _packId; String _name; String? _description; String _tags; @@ -53,7 +53,7 @@ class Sticker { ); Snowflake get id => _id; - Snowflake get packId => _packId; + Snowflake? get packId => _packId; String get name => _name; String? get description => _description; String get tags => _tags; @@ -110,7 +110,7 @@ class Sticker { payload['description'], payload['tags'], StickerType.values.firstWhere((element) => element.value == payload['type']), - payload['format_type'], + FormatType.values.firstWhere((format) => format.value == payload['format_type']), payload['sortValue'] ); diff --git a/lib/src/api/user.dart b/lib/src/api/user.dart index 88c478ed..6e678045 100644 --- a/lib/src/api/user.dart +++ b/lib/src/api/user.dart @@ -36,7 +36,7 @@ class User { /// GuildMember? member = guild.members.cache.get('240561194958716924'); /// await member.user.send(content: 'Hello World !'); /// ``` - Future send ({ String? content, List? embeds, List? components, bool? tts }) async { + Future send ({ String? content, List? embeds, List? components, bool? tts }) async { MineralClient client = ioc.singleton(ioc.services.client); Http http = ioc.singleton(ioc.services.http); @@ -70,7 +70,9 @@ class User { /// ### Returns the absolute url to the user's avatar String getDisplayAvatarUrl () { - return '${Constants.cdnUrl}/avatars/$id/$avatar'; + return avatar != null + ? '${Constants.cdnUrl}/avatars/$id/$avatar' + : '${Constants.cdnUrl}/embed/avatars/${int.parse(discriminator) % 5 }.png'; } @override diff --git a/lib/src/api/webhook.dart b/lib/src/api/webhook.dart index ea0af2b6..3a19daf2 100644 --- a/lib/src/api/webhook.dart +++ b/lib/src/api/webhook.dart @@ -102,19 +102,19 @@ class Webhook { /// ```dart /// await webhook.execute(content: 'Hello World !'); /// ``` - Future execute ({ String? content, String? username, String? avatarUrl, bool? tts, List? embeds, List? components, bool? suppressEmbed }) async { + Future execute ({ String? content, String? username, String? avatarUrl, bool? tts, List? embeds, List? components, bool? suppressEmbed }) async { Http http = ioc.singleton(ioc.services.http); List embedList = []; if (embeds != null) { - for (MessageEmbed element in embeds) { + for (EmbedBuilder element in embeds) { embedList.add(element.toJson()); } } List componentList = []; if (components != null) { - for (Row element in components) { + for (RowBuilder element in components) { componentList.add(element.toJson()); } } diff --git a/lib/src/exceptions/missing_method_exception.dart b/lib/src/exceptions/missing_method_exception.dart new file mode 100644 index 00000000..1abd6a41 --- /dev/null +++ b/lib/src/exceptions/missing_method_exception.dart @@ -0,0 +1,20 @@ +import 'package:mineral/console.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/internal/managers/reporter_manager.dart'; + +class MissingMethodException implements Exception { + String prefix = 'Missing method'; + String cause; + + MissingMethodException({ required this.cause }); + + @override + String toString () { + ReporterManager? reporter = ioc.singleton(ioc.services.reporter); + if (reporter != null) { + reporter.write('[ $prefix ] $cause'); + } + + return Console.getErrorMessage(prefix: prefix, message: cause); + } +} diff --git a/lib/src/internal/entities/command.dart b/lib/src/internal/entities/command.dart index 9f0e0e4d..489df60d 100644 --- a/lib/src/internal/entities/command.dart +++ b/lib/src/internal/entities/command.dart @@ -32,8 +32,9 @@ class Subcommand { final String description; final int type = 1; final String? group; + final String? bind; - const Subcommand ({ required this.name, required this.description, this.group }); + const Subcommand ({ required this.name, required this.description, this.group, this.bind }); } diff --git a/lib/src/internal/extensions/mineral_client.dart b/lib/src/internal/extensions/mineral_client.dart index 71fc2a4f..d5f859d8 100644 --- a/lib/src/internal/extensions/mineral_client.dart +++ b/lib/src/internal/extensions/mineral_client.dart @@ -3,19 +3,19 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; extension MineralClientExtension on MineralClient { - Future sendMessage (dynamic channel, { String? content, List? embeds, List? components, bool? tts }) async { + Future sendMessage (dynamic channel, { String? content, List? embeds, List? components, bool? tts }) async { Http http = ioc.singleton(ioc.services.http); List embedList = []; if (embeds != null) { - for (MessageEmbed element in embeds) { + for (EmbedBuilder element in embeds) { embedList.add(element.toJson()); } } List componentList = []; if (components != null) { - for (Row element in components) { + for (RowBuilder element in components) { componentList.add(element.toJson()); } } diff --git a/lib/src/internal/managers/command_manager.dart b/lib/src/internal/managers/command_manager.dart index 2d11c58f..3f1befad 100644 --- a/lib/src/internal/managers/command_manager.dart +++ b/lib/src/internal/managers/command_manager.dart @@ -12,6 +12,7 @@ class CommandManager { Map getHandlers () => _handlers; dynamic getHandler (String handler) => _handlers[handler]; + dynamic get handlers => _handlers; CommandManager add (MineralCommand mineralCommand) { SlashCommand command = SlashCommand(name: '', description: '', scope: '', everyone: true, dmChannel: false, options: []); @@ -62,7 +63,7 @@ class CommandManager { if (reflectee is CommandGroup) { SlashCommand group = SlashCommand(name: '', description: '', scope: '', everyone: true, dmChannel: false, options: []) ..type = 2 - ..name = reflectee.name.toLowerCase() + ..name = reflectee.name.snakeCase ..description = reflectee.description; command.groups.add(group); @@ -106,23 +107,23 @@ class CommandManager { if (reflectee is Subcommand) { String? groupName = reflectee.group; + String? bind = reflectee.bind; if (groupName != null) { SlashCommand group = command.groups.firstWhere((group) => group.name == groupName); group.subcommands.add(subcommand); _handlers.putIfAbsent("${command.name}.${group.name}.${subcommand.name}", () => { - 'symbol': Symbol(subcommand.name), + 'symbol': Symbol(bind ?? subcommand.name), 'commandClass': classCommand, }); } else { command.subcommands.add(subcommand); _handlers.putIfAbsent("${command.name}.${subcommand.name}", () => { - 'symbol': Symbol(subcommand.name), + 'symbol': Symbol(bind ?? subcommand.name), 'commandClass': classCommand, }); } - } if (reflectee is Option) { diff --git a/lib/src/internal/services/environment.dart b/lib/src/internal/services/environment.dart index 39adab93..dc57be78 100644 --- a/lib/src/internal/services/environment.dart +++ b/lib/src/internal/services/environment.dart @@ -26,6 +26,7 @@ class Environment { } return this; + } String? get (String key) { diff --git a/lib/src/internal/websockets/packets/guild_create.dart b/lib/src/internal/websockets/packets/guild_create.dart index 8b80f333..30be78b3 100644 --- a/lib/src/internal/websockets/packets/guild_create.dart +++ b/lib/src/internal/websockets/packets/guild_create.dart @@ -11,6 +11,7 @@ import 'package:mineral/src/api/managers/guild_scheduled_event_manager.dart'; import 'package:mineral/src/api/managers/member_manager.dart'; import 'package:mineral/src/api/managers/moderation_rule_manager.dart'; import 'package:mineral/src/api/managers/webhook_manager.dart'; +import 'package:mineral/src/api/sticker.dart'; import 'package:mineral/src/internal/managers/command_manager.dart'; import 'package:mineral/src/internal/managers/context_menu_manager.dart'; import 'package:mineral/src/internal/managers/event_manager.dart'; @@ -84,6 +85,11 @@ class GuildCreate implements WebsocketPacket { client.guilds.cache.putIfAbsent(guild.id, () => guild); + for (dynamic element in websocketResponse.payload['stickers']) { + Sticker sticker = Sticker.from(element); + guild.stickers.cache.putIfAbsent(sticker.id, () => sticker); + } + for (dynamic member in websocketResponse.payload['members']) { User user = User.from(member['user']); GuildMember guildMember = GuildMember.from( diff --git a/lib/src/internal/websockets/packets/interaction_create.dart b/lib/src/internal/websockets/packets/interaction_create.dart index 9b7d5d28..e31a9686 100644 --- a/lib/src/internal/websockets/packets/interaction_create.dart +++ b/lib/src/internal/websockets/packets/interaction_create.dart @@ -5,6 +5,7 @@ import 'package:http/http.dart'; import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; import 'package:mineral/src/api/components/component.dart'; +import 'package:mineral/src/exceptions/missing_method_exception.dart'; import 'package:mineral/src/internal/managers/command_manager.dart'; import 'package:mineral/src/internal/managers/context_menu_manager.dart'; import 'package:mineral/src/internal/managers/event_manager.dart'; @@ -82,8 +83,16 @@ class InteractionCreate implements WebsocketPacket { walk(payload['data']['options']); } - dynamic handle = manager.getHandler(identifier); - reflect(handle['commandClass']).invoke(handle['symbol'], [commandInteraction]); + final handle = manager.getHandler(identifier); + + try { + reflect(handle['commandClass']).invoke(handle['symbol'], [commandInteraction]); + } catch (err) { + final String command = identifier.split('.').first; + final String method = identifier.split('.').last; + + throw MissingMethodException(cause: 'The "$method" method does not exist on the "$command" command, please associate a valid method to your command or use the bind parameter of your subcommand'); + } } _executeContextMenuInteraction (Guild guild, GuildMember member, dynamic payload) async { diff --git a/lib/src/internal/websockets/packets/ready.dart b/lib/src/internal/websockets/packets/ready.dart index d5db1dd0..91a9ce25 100644 --- a/lib/src/internal/websockets/packets/ready.dart +++ b/lib/src/internal/websockets/packets/ready.dart @@ -17,8 +17,10 @@ class Ready implements WebsocketPacket { CommandManager commandManager = ioc.singleton(ioc.services.command); ShardManager shardManager = ioc.singleton(ioc.services.shards); - if(ioc.singleton(ioc.services.client) == null) { + if (ioc.singleton(ioc.services.client) == null) { MineralClient client = MineralClient.from(payload: websocketResponse.payload); + client.uptime = DateTime.now(); + ioc.bind(namespace: ioc.services.client, service: client); await client.registerGlobalCommands(commands: commandManager.getGlobals()); @@ -34,7 +36,10 @@ class Ready implements WebsocketPacket { ); } - final Shard shard = websocketResponse.payload['shard'] != null ? shardManager.shards[websocketResponse.payload['shard'][0]]! : shardManager.shards[0]!; + final Shard shard = websocketResponse.payload['shard'] != null + ? shardManager.shards[websocketResponse.payload['shard'][0]]! + : shardManager.shards[0]!; + shard.sessionId = websocketResponse.payload['session_id']; shard.initialize();