diff --git a/.pubignore b/.pubignore new file mode 100644 index 000000000..ee889666e --- /dev/null +++ b/.pubignore @@ -0,0 +1 @@ +assets/ diff --git a/README.md b/README.md index 8b55e735b..1aac3707b 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,19 @@ - +The Mineral framework currently allows you to use the latest Discord features: +- Slashcommands/Subcommands/Subcommand group +- Modals +- Scheduled events +- Auto-moderation +- Select menus +- Buttons +- Ephemeral messages -TODO: Put a short description of the package here that helps potential users -know whether this package might be useful for them. +And much more... -## Features +Join our ranks and add your contribution to the best discord framework for Dart ๐Ÿ’ช -TODO: List what your package can do. Maybe include images, gifs, or videos. - -## Getting started - -TODO: List prerequisites and provide or point to information on how to -start using the package. - -## Usage - -TODO: Include short and useful examples for package users. Add longer examples -to `/example` folder. - -```dart -const like = 'sample'; -``` - -## Additional information - -TODO: Tell users more about the package: where to find more information, how to -contribute to the package, how to file issues, what response they can expect -from the package authors, and more. +https://github.com/mineral-dart diff --git a/assets/images/banner.png b/assets/images/banner.png new file mode 100644 index 000000000..31935eaeb Binary files /dev/null and b/assets/images/banner.png differ diff --git a/assets/images/icon.png b/assets/images/icon.png new file mode 100644 index 000000000..7ad301fff Binary files /dev/null and b/assets/images/icon.png differ diff --git a/bin/mineral.dart b/bin/mineral.dart index 02707a007..561078882 100644 --- a/bin/mineral.dart +++ b/bin/mineral.dart @@ -1,10 +1,12 @@ import 'package:args/args.dart'; import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/cli_manager.dart'; +import 'package:mineral/src/internal/managers/cli_manager.dart'; Future main (List arguments) async { Kernel kernel = Kernel(); + kernel.loadConsole(); + final ArgParser parser = ArgParser(); final makeCommandParser = ArgParser(); diff --git a/dartdoc_options.yaml b/dartdoc_options.yaml new file mode 100644 index 000000000..c4a3d44a8 --- /dev/null +++ b/dartdoc_options.yaml @@ -0,0 +1,2 @@ +dartdoc: + favicon: assets/images/icon.png diff --git a/lib/api.dart b/lib/api.dart index 88e1ef225..48d1a3946 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -1,6 +1,4 @@ /// The api is the Map of all the classes, enumerations of the framework -/// -/// {@category Api} library api; export 'src/api/client/mineral_client.dart' show MineralClient, ClientActivity, ClientStatus, Intent; @@ -11,8 +9,7 @@ export 'src/api/user.dart' show User; export 'src/api/status.dart' show Status, StatusType; export 'src/api/activity.dart' show Activity; export 'src/api/guilds/guild_member.dart' show GuildMember; -export 'src/api/guilds/member_role_manager.dart' show MemberRoleManager; -export 'src/api/voice.dart' show Voice; +export 'src/api/managers/member_role_manager.dart' show MemberRoleManager; export 'src/api/guilds/guild.dart' show Guild; export 'src/api/moderation_rule.dart' show ModerationEventType, ModerationTriggerType, ModerationPresetType, ModerationActionType, ModerationTriggerMetadata, ModerationActionMetadata, ModerationAction, ModerationRule; @@ -27,8 +24,8 @@ export 'src/api/channels/text_based_channel.dart' show TextBasedChannel; export 'src/api/channels/text_channel.dart' show TextChannel; export 'src/api/channels/category_channel.dart' show CategoryChannel; -export 'src/api/message.dart' show Message; -export 'src/api/message_embed.dart' show MessageEmbed, Footer, Image, Author, Field; +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/color.dart' show Color; export 'src/api/emoji.dart' show Emoji; @@ -48,5 +45,6 @@ export 'src/api/interactions/select_menu_interaction.dart' show SelectMenuIntera export 'src/api/utils.dart'; export 'src/internal/extensions/collection.dart'; +export 'src/internal/extensions/string.dart'; typedef Snowflake = String; diff --git a/lib/console.dart b/lib/console.dart index 5f98a2c4c..ee781990e 100644 --- a/lib/console.dart +++ b/lib/console.dart @@ -1,56 +1,75 @@ +/// Console to print formatted and beautifully messages library console; import 'package:mineral/core.dart'; +import 'package:mineral/src/internal/managers/reporter_manager.dart'; class Console { - static void log ({ required String message, String level = "info"}) { - if(level == "debug") { + static void log ({ required String message, String level = 'info'}) { + if (level == 'debug') { final Environment environment = ioc.singleton(ioc.services.environment); final String? logLevel = environment.get('LOG_LEVEL'); - if (logLevel != "debug") return; + if (logLevel != 'debug') return; } print(message); } static debug ({ String prefix = 'debug', required String message }) { + ReporterManager? reporter = ioc.singleton(ioc.services.reporter); + + if (reporter != null && reporter.reportLevel == 'debug') { + _report('[ $prefix ] $message'); + } + String p = ColorList.white(prefix); - log(message: "[ $p ] $message", level: "debug"); + log(message: '[ $p ] $message', level: 'debug'); } static info ({ String prefix = 'info', required String message }) { String p = ColorList.blue(prefix); - log(message: "[ $p ] $message", level: "info"); + log(message: '[ $p ] $message', level: 'info'); + _report('[ $prefix ] $message'); } static success ({ String prefix = 'success', required String message }) { String p = ColorList.green(prefix); - log(message: "[ $p ] $message", level: "info"); + log(message: '[ $p ] $message', level: 'info'); + _report('[ $prefix ] $message'); } static error ({ String prefix = 'error', required String message }) { - log(message: getErrorMessage(prefix: ColorList.red(prefix), message: message), level: "error"); + log(message: getErrorMessage(prefix: ColorList.red(prefix), message: message), level: 'error'); + _report('[ $prefix ] $message'); } static warn ({ String prefix = 'warn', required String message }) { - log(message: getWarnMessage(prefix: ColorList.yellow(prefix), message: message), level: "warn"); + log(message: getWarnMessage(prefix: ColorList.yellow(prefix), message: message), level: 'warn'); + _report('[ $prefix ] $message'); } static String getWarnMessage ({ String? prefix = 'warn', required String message }) { String p = ColorList.yellow(prefix!); - return "[ $p ] $message"; + return '[ $p ] $message'; } static String getErrorMessage ({ String? prefix = 'error', required String message }) { String p = ColorList.red(prefix!); - return "[ $p ] $message"; + return '[ $p ] $message'; + } + + static _report (String message) { + ReporterManager? reporter = ioc.singleton(ioc.services.reporter); + if (reporter != null) { + reporter.write(message); + } } } class ColorList { static String black (String text) => '\x1B[30m$text\x1B[0m'; - static String red (String text) => '\x1B[31m$text\x1B[0m'; + static String red (String text) => '\x1B[186m$text\x1B[0m'; static String green (String text) => '\x1B[32m$text\x1B[0m'; static String yellow (String text) => '\x1B[33m$text\x1B[0m'; static String blue (String text) => '\x1B[34m$text\x1B[0m'; diff --git a/lib/core.dart b/lib/core.dart index d25630525..6babcd560 100644 --- a/lib/core.dart +++ b/lib/core.dart @@ -1,3 +1,4 @@ +/// ๐Ÿงก The neuralgic heart of the application, this module gathers all the functionalities of the framework. library core; export 'src/internal/environment.dart' show Environment; @@ -7,7 +8,7 @@ export 'src/internal/ioc.dart' show ioc, Service; export 'src/constants.dart'; export 'src/internal/http.dart'; -export 'src/internal/entities/event_manager.dart' show Event, Events, MineralEvent; -export 'src/internal/entities/command_manager.dart' show Command, MineralCommand, Option, OptionType, OptionChoice, Subcommand, CommandGroup; -export 'src/internal/entities/store_manager.dart' show Store, MineralStore; -export 'src/internal/entities/module_manager.dart' show Module, MineralModule; +export 'src/internal/managers/event_manager.dart' show Event, Events, MineralEvent; +export 'src/internal/managers/command_manager.dart' show Command, MineralCommand, Option, OptionType, OptionChoice, Subcommand, CommandGroup; +export 'src/internal/managers/store_manager.dart' show Store, MineralStore; +export 'src/internal/managers/module_manager.dart' show Module, MineralModule; diff --git a/lib/exception.dart b/lib/exception.dart index 46b556997..ae8592dfc 100644 --- a/lib/exception.dart +++ b/lib/exception.dart @@ -1,3 +1,4 @@ +/// Core exceptions library exception; export 'src/exceptions/token_exception.dart'; diff --git a/lib/helper.dart b/lib/helper.dart index de117d96f..869eccaf8 100644 --- a/lib/helper.dart +++ b/lib/helper.dart @@ -1,3 +1,4 @@ +/// Helper class to simplify actions library helper; import 'dart:convert'; @@ -37,17 +38,4 @@ class Helper { return _permissions; } - - static toPascalCase (String value) { - List words = value.split('_'); - return words.map((word) => "${word[0].toUpperCase()}${word.substring(1)}").join(''); - } - - static toCapitalCase (String value) { - return '${value[0].toUpperCase()}${value.substring(1)}'; - } - - static String toSnakeCase (String value) { - return value.split(RegExp(r"(?=[A-Z])")).join('_').toLowerCase(); - } } diff --git a/lib/internal.dart b/lib/internal.dart index b533cafff..912ea70a6 100644 --- a/lib/internal.dart +++ b/lib/internal.dart @@ -1,6 +1,5 @@ /// The internal library is the collection of all classes needed by the framework to work. -/// -/// {@category Internal} +/// @nodoc library internal; -export 'src/internal/websockets/websockets.dart'; \ No newline at end of file +export 'src/internal/websockets/websockets.dart'; diff --git a/lib/src/api/channels/category_channel.dart b/lib/src/api/channels/category_channel.dart index 475e5525d..70cee6aa8 100644 --- a/lib/src/api/channels/category_channel.dart +++ b/lib/src/api/channels/category_channel.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:http/http.dart'; import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/api/channels/channel.dart'; import 'package:mineral/src/api/managers/webhook_manager.dart'; class CategoryChannel extends Channel { diff --git a/lib/src/api/channels/channel.dart b/lib/src/api/channels/channel.dart index 977b5660a..18bf2e792 100644 --- a/lib/src/api/channels/channel.dart +++ b/lib/src/api/channels/channel.dart @@ -1,6 +1,7 @@ import 'package:http/http.dart'; import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; +import 'package:mineral/src/api/channels/partial_channel.dart'; import 'package:mineral/src/api/managers/webhook_manager.dart'; enum ChannelType { @@ -36,8 +37,7 @@ Map channels = { // 'GUILD_FORUM': () => , }; -class Channel { - Snowflake id; +class Channel extends PartialChannel { ChannelType type; Snowflake? guildId; late Guild? guild; @@ -50,7 +50,7 @@ class Channel { WebhookManager webhooks; Channel({ - required this.id, + required id, required this.type, required this.guildId, required this.position, @@ -59,7 +59,7 @@ class Channel { required this.parentId, required this.flags, required this.webhooks, - }); + }): super(id: id); Future setLabel (String label) async { Http http = ioc.singleton(ioc.services.http); @@ -95,6 +95,15 @@ class Channel { return this as T; } + Future delete () async { + Http http = ioc.singleton(ioc.services.http); + Response response = await http.destroy(url: "/channels/$id"); + + guild?.channels.cache.remove(id); + + return response.statusCode == 200; + } + @override String toString () => "<#$id>"; } diff --git a/lib/src/api/channels/dm_channel.dart b/lib/src/api/channels/dm_channel.dart new file mode 100644 index 000000000..bc186131e --- /dev/null +++ b/lib/src/api/channels/dm_channel.dart @@ -0,0 +1,38 @@ +import 'package:mineral/api.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/api/channels/partial_channel.dart'; +import 'package:mineral/src/api/managers/message_manager.dart'; + +class DmChannel extends PartialChannel { + Snowflake? lastMessageId; + MessageManager messages; + Map recipients; + + DmChannel({ + required id, + required this.lastMessageId, + required this.messages, + required this.recipients, + }): super(id: id); + + factory DmChannel.from({ required dynamic payload }) { + MineralClient client = ioc.singleton(ioc.services.client); + + Map users = {}; + if (payload['recipients'] != null) { + for (dynamic element in payload['recipients']) { + User? user = client.users.cache.get(element['id']); + user ??= User.from(element); + + users.putIfAbsent(user.id, () => user!); + } + } + + return DmChannel( + id: payload['id'], + lastMessageId: payload['last_message_id'], + messages: MessageManager(payload['id'], null), + recipients: users + ); + } +} diff --git a/lib/src/api/channels/partial_channel.dart b/lib/src/api/channels/partial_channel.dart new file mode 100644 index 000000000..a76088b16 --- /dev/null +++ b/lib/src/api/channels/partial_channel.dart @@ -0,0 +1,7 @@ +import 'package:mineral/api.dart'; + +class PartialChannel { + Snowflake id; + + PartialChannel({ required this.id }); +} diff --git a/lib/src/api/channels/public_thread.dart b/lib/src/api/channels/public_thread.dart index 0483d253a..59a2d2cf7 100644 --- a/lib/src/api/channels/public_thread.dart +++ b/lib/src/api/channels/public_thread.dart @@ -6,7 +6,7 @@ class PublicThread extends Channel { String? name; Snowflake? lastMessageId; - DateTime? lastPinTimstamp; + DateTime? lastPinTimestamp; PublicThread({ required Snowflake id, @@ -48,4 +48,4 @@ class PublicThread extends Channel { webhooks: payload['webhooks'], ); } -} \ No newline at end of file +} diff --git a/lib/src/api/channels/text_based_channel.dart b/lib/src/api/channels/text_based_channel.dart index 120ae81a1..dccc64b52 100644 --- a/lib/src/api/channels/text_based_channel.dart +++ b/lib/src/api/channels/text_based_channel.dart @@ -5,8 +5,9 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; import 'package:mineral/src/api/managers/message_manager.dart'; import 'package:mineral/src/api/managers/webhook_manager.dart'; +import 'package:mineral/src/api/managers/thread_manager.dart'; +import 'package:mineral/src/internal/extensions/mineral_client.dart'; -import '../managers/thread_manager.dart'; class TextBasedChannel extends Channel { String? description; @@ -43,28 +44,13 @@ class TextBasedChannel extends Channel { ); Future send ({ 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) { - embedList.add(element.toJson()); - } - } + MineralClient client = ioc.singleton(ioc.services.client); - List componentList = []; - if (components != null) { - for (Row element in components) { - componentList.add(element.toJson()); - } - } - - Response response = await http.post(url: "/channels/$id/messages", payload: { - 'tts': tts ?? false, - 'content': content, - 'embeds': embeds != null ? embedList : [], - 'components': components != null ? componentList : [], - }); + Response response = await client.sendMessage(this, + content: content, + embeds: embeds, + components: components + ); if (response.statusCode == 200) { dynamic payload = jsonDecode(response.body); @@ -125,13 +111,4 @@ class TextBasedChannel extends Channel { guild?.channels.cache.set(channel.id, channel); return channel; } - - Future delete () async { - Http http = ioc.singleton(ioc.services.http); - Response response = await http.destroy(url: "/channels/$id"); - - guild?.channels.cache.remove(id); - - return response.statusCode == 200; - } } diff --git a/lib/src/api/channels/text_channel.dart b/lib/src/api/channels/text_channel.dart index 7d558e539..bf78478ea 100644 --- a/lib/src/api/channels/text_channel.dart +++ b/lib/src/api/channels/text_channel.dart @@ -1,6 +1,5 @@ import 'package:mineral/api.dart'; import 'package:mineral/src/api/managers/message_manager.dart'; - import 'package:mineral/src/api/managers/thread_manager.dart'; class TextChannel extends TextBasedChannel { diff --git a/lib/src/api/channels/voice_channel.dart b/lib/src/api/channels/voice_channel.dart index 20ed86377..0869d7f80 100644 --- a/lib/src/api/channels/voice_channel.dart +++ b/lib/src/api/channels/voice_channel.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:http/http.dart'; import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/api/channels/channel.dart'; import 'package:mineral/src/api/managers/webhook_manager.dart'; class VoiceChannel extends Channel { diff --git a/lib/src/api/client/mineral_client.dart b/lib/src/api/client/mineral_client.dart index 03314a7de..7dfc91cca 100644 --- a/lib/src/api/client/mineral_client.dart +++ b/lib/src/api/client/mineral_client.dart @@ -1,10 +1,11 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; +import 'package:mineral/src/api/managers/dm_channel_manager.dart'; import 'package:mineral/src/api/managers/guild_manager.dart'; import 'package:mineral/src/api/managers/user_manager.dart'; import 'package:mineral/src/internal/websockets/sharding/shard_manager.dart'; -import '../../internal/entities/command_manager.dart'; +import 'package:mineral/src/internal/managers/command_manager.dart'; enum Intent { guilds(1 << 0), @@ -66,12 +67,13 @@ class ClientActivity { ClientActivity({ required this.name, required this.type }); - dynamic toJson () => { 'name': name, 'type': type.value }; + Object toJson () => { 'name': name, 'type': type.value }; } class MineralClient { User user; GuildManager guilds; + DmChannelManager dmChannels; UserManager users; String sessionId; Application application; @@ -80,13 +82,24 @@ class MineralClient { MineralClient({ required this.user, required this.guilds, + required this.dmChannels, required this.users, required this.sessionId, required this.application, required this.intents, }); - setPresence ({ ClientActivity? activity, ClientStatus? status, bool? afk }) { + /// ### Defines the presence that this should adopt + /// + /// + /// Example : + /// ```dart + /// client.setPresence( + /// activity: ClientActivity(name: 'My activity', type: GamePresence.listening), + /// status: ClientStatus.doNotDisturb + /// ); + /// ``` + void setPresence ({ ClientActivity? activity, ClientStatus? status, bool? afk }) { ShardManager manager = ioc.singleton(ioc.services.shards); manager.send(OpCode.statusUpdate, { 'since': DateTime.now().millisecond, @@ -96,6 +109,12 @@ class MineralClient { }); } + /// Sends a ping/pong to the APi websocket of discord and returns the latency + /// + /// Example : + /// ```dart + /// final int latency = client.getLatency(); + /// ``` int getLatency () { ShardManager manager = ioc.singleton(ioc.services.shards); return manager.getLatency(); @@ -128,7 +147,8 @@ class MineralClient { users: UserManager(), sessionId: payload['session_id'], application: Application.from(payload['application']), - intents: manager.intents + intents: manager.intents, + dmChannels: DmChannelManager() ); } } diff --git a/lib/src/api/color.dart b/lib/src/api/color.dart index f0aa0d5d7..54fc44d79 100644 --- a/lib/src/api/color.dart +++ b/lib/src/api/color.dart @@ -1,3 +1,17 @@ +/// Instance of colour. +/// You can use a pre-designed colour panel +/// +/// Example : +/// ```dart +/// import 'package:mineral/api.dart'; +/// print(Color.cyan_600); +/// ``` +/// Or create your own colour instances +/// Example : +/// ```dart +/// import 'package:mineral/api.dart'; +/// print(Color('#FFFFFF')); +// ``` class Color { static Color amber_50 = Color('#fff7ed'); static Color amber_100 = Color('#ffedd5'); diff --git a/lib/src/api/components/modal.dart b/lib/src/api/components/modal.dart index f41490dbc..909f61dad 100644 --- a/lib/src/api/components/modal.dart +++ b/lib/src/api/components/modal.dart @@ -11,11 +11,25 @@ class Modal extends Component { Modal({ required this.customId, required this.label }) : super(type: ComponentType.selectMenu); + /// ### Created a input text field + /// + /// Example : + /// ```dart + /// final Modal modal = Modal(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); return this; } + /// ### Created a input text field with multiple lines + /// + /// Example : + /// ```dart + /// final Modal modal = Modal(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 }) { _addInput(customId: customId, label: label, style: TextInputStyle.paragraph, required: required, minLength: minLength, maxLength: maxLength, placeholder: placeholder, value: value); return this; diff --git a/lib/src/api/emoji.dart b/lib/src/api/emoji.dart index 405be5d7a..680ee6d10 100644 --- a/lib/src/api/emoji.dart +++ b/lib/src/api/emoji.dart @@ -5,11 +5,9 @@ import 'package:mineral/src/api/managers/emoji_manager.dart'; import 'package:mineral/src/api/managers/member_manager.dart'; /// Represents an [Emoji] on [Guild] context. -/// {@category Api} class Emoji { Snowflake id; String label; - //List roles; GuildMember? creator; bool requireColons; bool managed; @@ -29,7 +27,9 @@ class Emoji { required this.manager, }); - /// Modifies the [label] of this. + /// ### Modifies the [label] of this. + /// + /// Example : /// ```dart /// final Emoji? emoji = guild.emojis.cache.get('240561194958716924'); /// if (emoji != null) { @@ -45,33 +45,9 @@ class Emoji { } } - /// Modifies the [roles] of this. - /// ```dart - /// final Emoji? emoji = guild.emojis.cache.get('240561194958716924'); - /// final Role? role = guild.roles.cache.get('240561194958716924'); + /// ### Removes the current this from the [EmojiManager]'s cache /// - /// if (role != null && emoji != null) { - /// await emoji.setRoles([role.id]); - /// } - /// ``` - /*Future setRoles (List roles) async { - Http http = ioc.singleton(ioc.services.http); - Response response = await http.patch(url: "/guilds/${manager.guildId}/emojis/$id", payload: { 'roles': roles }); - - if (response.statusCode == 200) { - List _roles = []; - for (Snowflake id in roles) { - Role? role = manager.guild?.roles.cache.get(id); - if (role != null) { - _roles.add(role); - } - } - - this.roles = _roles; - } - }*/ - - /// Removes the current this from the [EmojiManager]'s cache + /// Example : /// ```dart /// final Emoji? emoji = guild.emojis.cache.get('240561194958716924'); /// if (emoji != null) { @@ -79,6 +55,8 @@ class Emoji { /// } /// ``` /// You can specify a reason for this action + /// + /// Example : /// ```dart /// await emoji.delete(reason: 'I will destroy this..'); /// ``` @@ -91,7 +69,9 @@ class Emoji { } } - /// Returns this in discord notification format + /// ### Returns this in discord notification format + /// + /// Example : /// ```dart /// final Emoji? emoji = guild.emojis.cache.get('240561194958716924'); /// if (emoji != null) { @@ -108,7 +88,6 @@ class Emoji { return Emoji( id: payload['id'], label: payload['name'], - //roles: roles, creator: payload['user'] != null ? memberManager.cache.get(payload['user']['id']) : null, requireColons: payload['require_colons'] ?? false, managed: payload['managed'] ?? false, diff --git a/lib/src/api/guilds/guild.dart b/lib/src/api/guilds/guild.dart index 90bbf9313..e1c42c55e 100644 --- a/lib/src/api/guilds/guild.dart +++ b/lib/src/api/guilds/guild.dart @@ -4,7 +4,7 @@ import 'package:mineral/console.dart'; import 'package:mineral/core.dart'; import 'package:mineral/exception.dart'; import 'package:mineral/helper.dart'; -import 'package:mineral/src/api/guilds/guild_role_manager.dart'; +import 'package:mineral/src/api/managers/guild_role_manager.dart'; import 'package:mineral/src/api/managers/channel_manager.dart'; import 'package:mineral/src/api/managers/emoji_manager.dart'; import 'package:mineral/src/api/managers/guild_webhook_manager.dart'; @@ -16,6 +16,8 @@ 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'; + enum VerificationLevel { none(0), low(1), @@ -126,7 +128,9 @@ class Guild { required this.scheduledEvents, }); - /// Modifies the [name] of this. + /// ### Modifies the [name] of this. + /// + /// Example : /// ```dart /// await guild.setName('Guild name'); /// ``` @@ -139,7 +143,9 @@ class Guild { } } - /// Modifies the [verificationLevel] of the current [Guild]. + /// ### Modifies the [verificationLevel] of the current [Guild]. + /// + /// Example : /// ```dart /// import 'package:mineral/api.dart'; ๐Ÿ‘ˆ // then you can use VerificationLevel enum /// @@ -154,17 +160,33 @@ class Guild { } } - Future setMessageNotification (int level) async { + /// ### Defines the notification level of this + /// - 0 โ†’ All messages + /// - 1 โ†’ Only mentions + /// + /// Example : + /// ```dart + /// await guild.setMessageNotification(1); + /// ``` + Future setMessageNotification (int level) async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch(url: "/guilds/$id", payload: { 'default_message_notifications': level }); if (response.statusCode == 200) { defaultMessageNotifications = level; } - - return this; } + /// ### Defines the explicit content level of this + /// - 0 โ†’ Disabled + /// - 1 โ†’ Members without roles + /// - 2 โ†’ All members + /// + /// + /// Example : + /// ```dart + /// await guild.setExplicitContentFilter(2); + /// ``` Future setExplicitContentFilter (int level) async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch(url: "/guilds/$id", payload: { 'explicit_content_filter': level }); @@ -174,6 +196,16 @@ class Guild { } } + /// ### Update the afk channel + /// + /// Example : + /// ```dart + /// final voiceChannel = guild.channels.cache.get('240561194958716924'); + /// + /// if (voiceChannel != null) { + /// await guild.setAfkChannel(2); + /// } + /// ``` Future setAfkChannel (VoiceChannel channel) async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch(url: "/guilds/$id", payload: { 'afk_channel_id': channel.id }); @@ -184,6 +216,20 @@ class Guild { } } + /// ### Update the owner of this + /// + /// Warning : This method only works if the server was created via a discord bot and the bot is the current owner + /// + /// See [documentation](https://discord.com/developers/docs/resources/guild#modify-guild) + /// + /// Example : + /// ```dart + /// final member = guild.members.cache.get('240561194958716924'); + /// + /// if (member != null) { + /// await guild.setOwner(member); + /// } + /// ``` Future setOwner (GuildMember guildMember) async { MineralClient client = ioc.singleton(ioc.services.client); Http http = ioc.singleton(ioc.services.http); @@ -201,6 +247,14 @@ class Guild { } } + /// ### Update the splash banner of this + /// + /// This method requires the feature [GuildFeature.banner] of this + /// + /// Example : + /// ```dart + /// await guild.setSplash('assets/images/my_splash_banner.png'); + /// ``` Future setSplash (String filename) async { if (!features.contains(GuildFeature.banner)) { throw MissingFeatureException(cause: "The $name guild does not have the ${GuildFeature.inviteSplash} feature."); @@ -216,6 +270,14 @@ class Guild { } } + /// ### Remove the splash banner of this + /// + /// This method requires the feature [GuildFeature.banner] of this + /// + /// Example : + /// ```dart + /// await guild.removeSplash(); + /// ``` Future removeSplash () async { if (!features.contains(GuildFeature.banner)) { throw MissingFeatureException(cause: "The $name guild does not have the ${GuildFeature.inviteSplash} feature."); @@ -229,6 +291,14 @@ class Guild { } } + /// ### Update the discovery splash banner of this + /// + /// This method requires the feature [GuildFeature.banner] of this + /// + /// Example : + /// ```dart + /// await guild.setDiscoverySplash('assets/images/my_splash_discovery_banner.png'); + /// ``` Future setDiscoverySplash (String filename) async { if (!features.contains(GuildFeature.banner)) { throw MissingFeatureException(cause: "The $name guild does not have the ${GuildFeature.discoverable} feature."); @@ -244,6 +314,14 @@ class Guild { } } + /// ### Remove the discovery splash banner of this + /// + /// This method requires the feature [GuildFeature.banner] of this + /// + /// Example : + /// ```dart + /// await guild.removeDiscoverySplash(); + /// ``` Future removeDiscoverySplash () async { if (!features.contains(GuildFeature.banner)) { throw MissingFeatureException(cause: "The $name guild does not have the ${GuildFeature.discoverable} feature."); @@ -257,6 +335,14 @@ class Guild { } } + /// ### Update the banner of this + /// + /// This method requires the feature [GuildFeature.banner] of this + /// + /// Example : + /// ```dart + /// await guild.setBanner('assets/images/my_banner.png'); + /// ``` Future setBanner (String filename) async { if (!features.contains(GuildFeature.banner)) { throw MissingFeatureException(cause: "The $name guild does not have the ${GuildFeature.banner} feature."); @@ -272,6 +358,14 @@ class Guild { } } + /// ### Remove the banner of this + /// + /// This method requires the feature [GuildFeature.banner] of this + /// + /// Example : + /// ```dart + /// await guild.removeBanner(); + /// ``` Future removeBanner () async { if (!features.contains(GuildFeature.banner)) { throw MissingFeatureException(cause: "The $name guild does not have the ${GuildFeature.banner} feature."); @@ -285,6 +379,12 @@ class Guild { } } + /// ### Update the icon of this + /// + /// Example : + /// ```dart + /// await guild.setIcon('assets/images/my_guild_icon.png'); + /// ``` Future setIcon (String filename) async { String file = await Helper.getPicture(filename); @@ -296,6 +396,12 @@ class Guild { } } + /// ### Remove the icon of this + /// + /// Example : + /// ```dart + /// await guild.removeIcon(); + /// ``` Future removeIcon () async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch(url: "/guilds/$id", payload: { 'icon': null }); @@ -305,6 +411,16 @@ class Guild { } } + /// ### Update system channel of this + /// + /// Example : + /// ```dart + /// final channel = guild.channels.cache.get('240561194958716924'); + /// + /// if (channel != null) { + /// await guild.setSystemChannel(channel); + /// } + /// ``` Future setSystemChannel (TextChannel channel) async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch(url: "/guilds/$id", payload: { 'system_channel_id': channel.id }); @@ -315,6 +431,16 @@ class Guild { } } + /// ### Update rules channel of this + /// + /// Example : + /// ```dart + /// final channel = guild.channels.cache.get('240561194958716924'); + /// + /// if (channel != null) { + /// await guild.setRulesChannel(channel); + /// } + /// ``` Future setRulesChannel (TextChannel channel) async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch(url: "/guilds/$id", payload: { 'rules_channel_id': channel.id }); @@ -325,6 +451,16 @@ class Guild { } } + /// ### Update public updates channel of this + /// + /// Example : + /// ```dart + /// final channel = guild.channels.cache.get('240561194958716924'); + /// + /// if (channel != null) { + /// await guild.setPublicUpdateChannel(channel); + /// } + /// ``` Future setPublicUpdateChannel (TextChannel channel) async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch(url: "/guilds/$id", payload: { 'public_updates_channel_id': channel.id }); @@ -335,6 +471,14 @@ class Guild { } } + /// ### Update preferred language of this + /// + /// Example : + /// ```dart + /// import 'package:mineral/api.dart'; + /// + /// await guild.setPreferredLocale(Locale.fr); // ๐Ÿ‘ˆ Now you can use Lang enum + /// ``` Future setPreferredLocale (Locale locale) async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch(url: "/guilds/$id", payload: { 'public_updates_channel_id': locale }); @@ -344,6 +488,22 @@ class Guild { } } + /// ### Remove the discord client of this + /// + /// Example : + /// ```dart + /// await guild.leave(); + /// ``` + Future leave () async { + Http http = ioc.singleton(ioc.services.http); + Response response = await http.destroy(url: '/users/@me/guilds/$id'); + + if (response.statusCode == 204) { + MineralClient client = ioc.singleton(ioc.services.client); + client.guilds.cache.remove(this); + } + } + factory Guild.from({ required EmojiManager emojiManager, required MemberManager memberManager, @@ -362,8 +522,12 @@ class Guild { List features = []; for (String element in payload['features']) { - GuildFeature feature = GuildFeature.values.firstWhere((feature) => feature.value == element); - features.add(feature); + GuildFeature? feature = GuildFeature.values.firstWhereOrNull((feature) => feature.value == element); + if(feature == null) { + Console.warn(message: 'Guild feature $element don\'t exist! Please report this to our team.'); + } else { + features.add(feature); + } } return Guild( diff --git a/lib/src/api/guilds/guild_member.dart b/lib/src/api/guilds/guild_member.dart index 46bfbb05f..ef3e0cee0 100644 --- a/lib/src/api/guilds/guild_member.dart +++ b/lib/src/api/guilds/guild_member.dart @@ -1,7 +1,8 @@ import 'package:http/http.dart'; import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/api/guilds/guild_role_manager.dart'; +import 'package:mineral/src/api/managers/guild_role_manager.dart'; +import 'package:mineral/src/api/managers/voice_manager.dart'; class GuildMember { User user; @@ -10,10 +11,10 @@ class GuildMember { DateTime joinedAt; DateTime? premiumSince; String? permissions; - bool isPending; + bool pending; DateTime? timeoutDuration; MemberRoleManager roles; - Voice voice; + late VoiceManager voice; late Guild guild; GuildMember({ @@ -23,12 +24,18 @@ class GuildMember { required this.joinedAt, required this.premiumSince, required this.permissions, - required this.isPending, + required this.pending, required this.timeoutDuration, required this.roles, required this.voice, }); + /// ### Update the username of this + /// + /// Example : + /// ```dart + /// await member.setUsername('John Doe'); + /// ``` Future setUsername (String name) async { Http http = ioc.singleton(ioc.services.http); @@ -38,6 +45,17 @@ class GuildMember { } } + /// ### Excludes this for a pre-defined period + /// + /// Note: An exclusion cannot exceed 28 days + /// + /// See [documentation](https://discord.com/developers/docs/resources/guild#modify-guild-member) + /// + /// Example : + /// ```dart + /// final DateTime = DateTime.now().add(Duration(days: 28)); + /// await member.timeout(DateTime); + /// ``` Future timeout (DateTime expiration) async { // @Todo add ADMINISTRATOR permission or is the owner of the guild constraint Http http = ioc.singleton(ioc.services.http); @@ -48,6 +66,12 @@ class GuildMember { } } + /// ### Cancels the exclusion of this + /// + /// Example : + /// ```dart + /// await member.removeTimeout(); + /// ``` Future removeTimeout () async { Http http = ioc.singleton(ioc.services.http); @@ -57,6 +81,18 @@ class GuildMember { } } + /// ### banned this from the [Guild] and deleted its messages for a given period + /// + /// Example : + /// ```dart + /// await member.ban(); + /// ``` + /// With the deletion of his messages for 7 days + /// + /// Example : + /// ```dart + /// await member.ban(count: 7); + /// ``` Future ban ({ int? count, String? reason }) async { Http http = ioc.singleton(ioc.services.http); @@ -70,11 +106,33 @@ class GuildMember { } } + /// ### Kick this of [Guild] + /// + /// Example : + /// ```dart + /// await member.removeTimeout(); + /// ``` Future kick ({ int? count, String? reason }) async { Http http = ioc.singleton(ioc.services.http); await http.destroy(url: "/guilds/${guild.id}/members/${user.id}"); } + /// ### Returns whether of this is a bot + /// + /// Example : + /// ```dart + /// print(member.isBot()); + /// ``` + bool isBot () => user.bot; + + /// ### Returns whether of this is pending + /// + /// Example : + /// ```dart + /// print(member.isPending()); + /// ``` + bool isPending () => pending; + @override String toString () { return "<@${nickname != null ? '!' : ''}${user.id}>"; @@ -88,7 +146,7 @@ class GuildMember { joinedAt: joinedAt, premiumSince: premiumSince, permissions: permissions, - isPending: isPending, + pending: pending, timeoutDuration: timeoutDuration, roles: roles, voice: voice @@ -97,7 +155,7 @@ class GuildMember { ..roles = roles; } - factory GuildMember.from({ required user, required GuildRoleManager roles, dynamic member, required Snowflake guildId }) { + factory GuildMember.from({ required user, required GuildRoleManager roles, dynamic member, required Snowflake guildId, required VoiceManager voice }) { MemberRoleManager memberRoleManager = MemberRoleManager(manager: roles, memberId: user.id); for (var element in (member['roles'] as List)) { Role? role = roles.cache.get(element); @@ -113,10 +171,10 @@ class GuildMember { joinedAt: DateTime.parse(member['joined_at']), premiumSince: member['premium_since'] != null ? DateTime.parse(member['premium_since']) : null, permissions: member['permissions'], - isPending: member['pending'] == true, + pending: member['pending'] == true, timeoutDuration: member['communication_disabled_until'] != null ? DateTime.parse(member['communication_disabled_until']) : null, roles: memberRoleManager, - voice: Voice.from(payload: member), + voice: voice, ); } } diff --git a/lib/src/api/interactions/command_interaction.dart b/lib/src/api/interactions/command_interaction.dart index 28d2e1b21..5af1949dd 100644 --- a/lib/src/api/interactions/command_interaction.dart +++ b/lib/src/api/interactions/command_interaction.dart @@ -1,6 +1,7 @@ import 'dart:core'; import 'package:mineral/api.dart'; +import 'package:mineral/core.dart'; import 'package:mineral/src/api/interactions/interaction.dart'; class CommandInteraction extends Interaction { @@ -17,36 +18,96 @@ class CommandInteraction extends Interaction { required User user }) : super(id: id, version: version, token: token, type: type, user: user, applicationId: applicationId); + /// ### Returns an instance of [Channel] if the command has the designed option + /// + /// Example : + /// ```dart + /// Channel? channel = interaction.getChannel('option_name'); + /// ``` T? getChannel (String optionName) { - return guild?.channels.cache.get(data[optionName]['value']); + return guild?.channels.cache.get(data[optionName]?['value']); } + /// ### Returns an [int] if the command has the designed option + /// + /// Example : + /// ```dart + /// int? value = interaction.getInteger('option_name'); + /// ``` int? getInteger (String optionName) { - return data[optionName]['value']; + return data[optionName]?['value']; } + /// ### Returns an [String] if the command has the designed option + /// + /// Example : + /// ```dart + /// String? str = interaction.getString('option_name'); + /// ``` String? getString (String optionName) { - return data[optionName]['value']; + return data[optionName]?['value']; } + /// ### Returns an instance of [GuildMember] if the command has the designed option + /// + /// Example : + /// ```dart + /// GuildMember? member = interaction.getMember('option_name'); + /// ``` GuildMember? getMember (String optionName) { - return guild?.members.cache.get(data[optionName]['value']); + return guild?.members.cache.get(data[optionName]?['value']); } + /// ### Returns an instance of [User] if the command has the designed option + /// + /// Example : + /// ```dart + /// User? user = interaction.getUser('option_name'); + /// ``` + User? getUser (String optionName) { + final MineralClient client = ioc.singleton(ioc.services.client); + return client.users.cache.get(data[optionName]?['value']); + } + + /// ### Returns an [bool] if the command has the designed option + /// + /// Example : + /// ```dart + /// book? boolean = interaction.getBoolean('option_name'); + /// ``` bool? getBoolean (String optionName) { - return data[optionName]['value']; + return data[optionName]?['value']; } + /// ### Returns an instance of [Role] if the command has the designed option + /// + /// Example : + /// ```dart + /// Role? role = interaction.getRole('option_name'); + /// ``` Role? getRole (String optionName) { - return guild?.roles.cache.get(data[optionName]['value']); + return guild?.roles.cache.get(data[optionName]?['value']); } + /// ### Returns an [T] if the command has the designed option + /// + /// Example : + /// ```dart + /// String? str = interaction.getChoice('option_name'); + /// int? value = interaction.getChoice('option_name'); + /// ``` T? getChoice (String optionName) { - return data[optionName]['value']; + return data[optionName]?['value']; } + /// ### Returns an value if the command has the designed option + /// + /// Example : + /// ```dart + /// dynamic mentionable = interaction.getMentionable('option_name'); + /// ``` dynamic getMentionable (String optionName) { - return data[optionName]['value']; + return data[optionName]?['value']; } factory CommandInteraction.from({ required User user, required dynamic payload }) { diff --git a/lib/src/api/interactions/interaction.dart b/lib/src/api/interactions/interaction.dart index 922ed94fc..b1c4a3bc6 100644 --- a/lib/src/api/interactions/interaction.dart +++ b/lib/src/api/interactions/interaction.dart @@ -1,4 +1,3 @@ -import 'package:http/http.dart'; import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; @@ -27,6 +26,12 @@ class Interaction { Interaction({ required this.id, required this.applicationId, required this.version, required this.type, required this.token, required this.user }); + /// ### Responds to this by an [Message] + /// + /// Example : + /// ```dart + /// await interaction.reply(content: 'Hello ${interaction.user.username}'); + /// ``` Future reply ({ String? content, List? embeds, List? components, bool? tts, bool? private }) async { Http http = ioc.singleton(ioc.services.http); @@ -56,14 +61,22 @@ class Interaction { }); } + /// ### Responds to this by an [Modal] + /// + /// Example : + /// ```dart + /// Modal modal = Modal(customId: 'modal', label: 'My modal') + /// .addInput(customId: 'my_text', label: 'First text') + /// .addParagraph(customId: 'my_paragraph', label: 'Second text'); + /// + /// await interaction.modal(modal); + /// ``` Future modal (Modal modal) async { Http http = ioc.singleton(ioc.services.http); - Response response = await http.post(url: "/interactions/$id/$token/callback", payload: { + await http.post(url: "/interactions/$id/$token/callback", payload: { 'type': InteractionCallbackType.modal.value, 'data': modal.toJson(), }); - - print(response.body); } } diff --git a/lib/src/api/interactions/modal_interaction.dart b/lib/src/api/interactions/modal_interaction.dart index badc8bd80..099b9934a 100644 --- a/lib/src/api/interactions/modal_interaction.dart +++ b/lib/src/api/interactions/modal_interaction.dart @@ -19,6 +19,12 @@ class ModalInteraction extends Interaction { required User user }) : super(id: id, version: version, token: token, type: type, user: user, applicationId: applicationId); + /// ### Return an [String] if the modal has the designed field + /// + /// Example : + /// ```dart + /// String? field = interaction.getText('custom_field_id'); + /// ``` String? getText(String customId) => data.get(customId); factory ModalInteraction.from({ required User user, required Message? message, required dynamic payload }) { diff --git a/lib/src/api/interactions/select_menu_interaction.dart b/lib/src/api/interactions/select_menu_interaction.dart index 29e4a98d0..89752b52f 100644 --- a/lib/src/api/interactions/select_menu_interaction.dart +++ b/lib/src/api/interactions/select_menu_interaction.dart @@ -19,8 +19,22 @@ class SelectMenuInteraction extends Interaction { required User user }) : super(id: id, version: version, token: token, type: type, user: user, applicationId: applicationId); + /// ### Return an [List] of [T] if this has the designed field + /// + /// Example : + /// ```dart + /// List? fields = interaction.getValues(); + /// List? fields = interaction.getValues(); + /// ``` List getValues () => data as List; + /// ### Return the first [T] if this has the designed field + /// + /// Example : + /// ```dart + /// String? field = interaction.getValue(); + /// int? field = interaction.getValue(); + /// ``` T getValue () => data.first as T; factory SelectMenuInteraction.from({ required User user, required Message? message, required dynamic payload }) { diff --git a/lib/src/api/managers/channel_manager.dart b/lib/src/api/managers/channel_manager.dart index a096459f2..41df0ae0a 100644 --- a/lib/src/api/managers/channel_manager.dart +++ b/lib/src/api/managers/channel_manager.dart @@ -6,6 +6,8 @@ import 'package:mineral/core.dart'; import 'package:mineral/src/api/channels/channel.dart'; import 'package:mineral/src/api/managers/cache_manager.dart'; +import 'package:collection/collection.dart'; + class ChannelManager implements CacheManager { @override Map cache = {}; @@ -72,8 +74,9 @@ class ChannelManager implements CacheManager { Response response = await http.post(url: "/guilds/$guildId/channels", payload: data); dynamic payload = jsonDecode(response.body); - if (channels.containsKey(payload['type'])) { - Channel Function(dynamic payload) item = channels[payload['type']] as Channel Function(dynamic payload); + final ChannelType? type = ChannelType.values.firstWhereOrNull((element) => element.value == payload['type']); + if (type != null && channels.containsKey(type)) { + Channel Function(dynamic payload) item = channels[type] as Channel Function(dynamic payload); Channel channel = item(payload); // Define deep properties diff --git a/lib/src/api/managers/dm_channel_manager.dart b/lib/src/api/managers/dm_channel_manager.dart new file mode 100644 index 000000000..12bbd94d5 --- /dev/null +++ b/lib/src/api/managers/dm_channel_manager.dart @@ -0,0 +1,15 @@ +import 'package:mineral/api.dart'; +import 'package:mineral/src/api/channels/dm_channel.dart'; +import 'package:mineral/src/api/managers/cache_manager.dart'; + +class DmChannelManager implements CacheManager { + @override + Map cache = {}; + + DmChannelManager(); + + @override + Future> sync () async { + return cache; + } +} diff --git a/lib/src/api/guilds/guild_role_manager.dart b/lib/src/api/managers/guild_role_manager.dart similarity index 80% rename from lib/src/api/guilds/guild_role_manager.dart rename to lib/src/api/managers/guild_role_manager.dart index 229acc7d7..ea2d60faf 100644 --- a/lib/src/api/guilds/guild_role_manager.dart +++ b/lib/src/api/managers/guild_role_manager.dart @@ -16,6 +16,12 @@ class GuildRoleManager implements CacheManager { GuildRoleManager({ required this.guildId }); + /// Synchronise the cache from the Discord API + /// + /// Example : + /// ```dart + /// await guild.roles.sync(); + /// ``` @override Future> sync () async { Http http = ioc.singleton(ioc.services.http); @@ -35,6 +41,19 @@ class GuildRoleManager implements CacheManager { return cache; } + /// Create a this + /// + /// Warning: if you want to define an icon, the [Guid] must have the feature [GuildFeature.roleIcons] + /// + /// Example : + /// ```dart + /// await guild.roles.create( + /// label: 'My role', + /// color: Color.cyan_600, + /// permissions: [Permission.moderateMembers, Permission.banMembers], + /// hoist: true, + /// ); + /// ``` Future create ({ required String label, Color? color, bool? hoist, String? icon, String? unicode, bool? mentionable, List? permissions }) async { if ((icon != null || unicode != null) && !guild.features.contains('ROLE_ICONS')) { throw MissingFeatureException(cause: "Guild ${guild.name} has no 'ROLE_ICONS' feature."); diff --git a/lib/src/api/managers/member_manager.dart b/lib/src/api/managers/member_manager.dart index ca6e34643..e013a468f 100644 --- a/lib/src/api/managers/member_manager.dart +++ b/lib/src/api/managers/member_manager.dart @@ -4,6 +4,7 @@ import 'package:http/http.dart'; import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; import 'package:mineral/src/api/managers/cache_manager.dart'; +import 'package:mineral/src/api/managers/voice_manager.dart'; class MemberManager implements CacheManager { @override @@ -17,19 +18,26 @@ class MemberManager implements CacheManager { @override Future> sync () async { Http http = ioc.singleton(ioc.services.http); - cache.clear(); Response response = await http.get(url: "/guilds/$guildId/members"); - dynamic payload = jsonDecode(response.body); + if(response.statusCode == 200) { + dynamic payload = jsonDecode(response.body); + final Map voiceStateCache = cache.map((key, value) => MapEntry(key, value.voice)); - for(dynamic element in payload) { - GuildMember guildMember = GuildMember.from( - user: User.from(element['user']), - roles: guild.roles, - guildId: guild.id - ); + cache.clear(); - cache.putIfAbsent(guildMember.user.id, () => guildMember); + for(dynamic element in payload) { + VoiceManager? voiceManager = voiceStateCache.get(element['user']['id']); + + GuildMember guildMember = GuildMember.from( + user: User.from(element['user']), + roles: guild.roles, + guildId: guild.id, + voice: voiceManager ?? VoiceManager(isMute: element['mute'], isDeaf: element['deaf'], isSelfMute: false, isSelfDeaf: false, hasVideo: false, hasStream: false, channel: null) + ); + + cache.putIfAbsent(guildMember.user.id, () => guildMember); + } } return cache; diff --git a/lib/src/api/guilds/member_role_manager.dart b/lib/src/api/managers/member_role_manager.dart similarity index 91% rename from lib/src/api/guilds/member_role_manager.dart rename to lib/src/api/managers/member_role_manager.dart index d4b2062bd..e84c5b016 100644 --- a/lib/src/api/guilds/member_role_manager.dart +++ b/lib/src/api/managers/member_role_manager.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:http/http.dart'; import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/api/guilds/guild_role_manager.dart'; +import 'package:mineral/src/api/managers/guild_role_manager.dart'; import 'package:mineral/src/api/managers/cache_manager.dart'; import 'package:mineral/src/exceptions/not_exist.dart'; @@ -18,15 +18,19 @@ class MemberRoleManager implements CacheManager { MemberRoleManager({ required this.manager, required this.memberId }); /// Add a [Role] to the [GuildMember] + /// + /// Example : /// ```dart /// final Role? role = guild.roles.cache.get('446556480850755604'); /// final GuildMember? member = guild.members.cache.get('240561194958716924'); + /// /// if (member != null && role != null) { /// await member.roles.add(role.id) /// } /// ``` - /// /// You can pass a reason for the audit logs. + /// + /// Example : /// ```dart /// await member.roles.add('446556480850755604', reason: 'I love this user'); /// ``` @@ -44,7 +48,7 @@ class MemberRoleManager implements CacheManager { } Response response = await http.put( - url: '/guilds/{guild.id}/members/$memberId/roles/$id', + url: '/guilds/${manager.guildId}/members/$memberId/roles/$id', payload: {}, headers: headers ); @@ -55,6 +59,8 @@ class MemberRoleManager implements CacheManager { } /// Remove a [Role] from the [GuildMember] + /// + /// Example : /// ```dart /// final Role? role = guild.roles.cache.get('446556480850755604'); /// final GuildMember? member = guild.members.cache.get('240561194958716924'); @@ -64,6 +70,8 @@ class MemberRoleManager implements CacheManager { /// ``` /// /// You can pass a reason for the audit logs. + /// + /// Example : /// ```dart /// await member.roles.remove('446556480850755604', reason: 'Hello, World!'); /// ``` @@ -76,7 +84,7 @@ class MemberRoleManager implements CacheManager { } Response response = await http.destroy( - url: '/guilds/{guild.id}/members/$memberId/roles/$id', + url: '/guilds/${manager.guildId}/members/$memberId/roles/$id', headers: headers ); @@ -86,6 +94,8 @@ class MemberRoleManager implements CacheManager { } /// Toggle a [Role] from the [GuildMember]. If the user has the role, this method will remove the role, else this method will add the role. + /// + /// Example : /// ```dart /// final Role? role = guild.roles.cache.get('446556480850755604'); /// final GuildMember? member = guild.members.cache.get('240561194958716924'); @@ -95,6 +105,8 @@ class MemberRoleManager implements CacheManager { /// ``` /// /// You can pass a reason for the audit logs. + /// + /// Example : /// ```dart /// await member.roles.toggle('446556480850755604', reason: 'Hello, World!'); /// ``` diff --git a/lib/src/api/managers/message_manager.dart b/lib/src/api/managers/message_manager.dart index 10b105dc6..423e5cbad 100644 --- a/lib/src/api/managers/message_manager.dart +++ b/lib/src/api/managers/message_manager.dart @@ -4,10 +4,11 @@ import 'package:http/http.dart'; import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; import 'package:mineral/src/api/managers/cache_manager.dart'; +import 'package:mineral/src/api/messages/partial_message.dart'; -class MessageManager implements CacheManager { +class MessageManager implements CacheManager { @override - Map cache = {}; + Map cache = {}; final Snowflake? _guildId; final Snowflake _channelId; @@ -15,7 +16,7 @@ class MessageManager implements CacheManager { MessageManager(this._channelId, this._guildId); @override - Future> sync () async { + Future> sync () async { Http http = ioc.singleton(ioc.services.http); cache.clear(); diff --git a/lib/src/api/managers/voice_manager.dart b/lib/src/api/managers/voice_manager.dart new file mode 100644 index 000000000..a71ff1d78 --- /dev/null +++ b/lib/src/api/managers/voice_manager.dart @@ -0,0 +1,123 @@ +import 'package:http/http.dart'; +import 'package:mineral/api.dart'; +import 'package:mineral/core.dart'; + +class VoiceManager { + GuildMember? member; + bool isDeaf; + bool isMute; + bool isSelfMute; + bool isSelfDeaf; + bool hasVideo; + bool? hasStream; + VoiceChannel? channel; + + VoiceManager({ + required this.isMute, + required this.isDeaf, + required this.isSelfMute, + required this.isSelfDeaf, + required this.hasVideo, + required this.hasStream, + required this.channel + }); + + /// ### Mutes or unmute a server member + /// + /// Example : + /// ```dart + /// final member = guild.members.cache.get('240561194958716924'); + /// + /// if (member != null) { + /// await member.setMute(true); + /// } + Future setMute(bool value) async { + final Http http = ioc.singleton(ioc.services.http); + + final Response response = await http.patch( + url: '/guilds/${member!.guild.id}/members/${member!.user.id}', + payload: {'mute': value} + ); + + if (response.statusCode == 204 || response.statusCode == 200) { + isMute = value; + } + } + + /// ### Deafens or not a server member + /// + /// Example : + /// ```dart + /// final member = guild.members.cache.get('240561194958716924'); + /// + /// if (member != null) { + /// await member.setDeaf(true); + /// } + Future setDeaf(bool value) async { + final Http http = ioc.singleton(ioc.services.http); + final Response response = await http.patch( + url: '/guilds/${member!.guild.id}/members/${member!.user.id}', + payload: {'deaf': value} + ); + + if (response.statusCode == 204 || response.statusCode == 200) { + isDeaf = value; + } + } + + /// ### Moves a member from one voice channel to another + /// + /// Example : + /// ```dart + /// final member = guild.members.cache.get('240561194958716924'); + /// final voiceChannel = guild.channels.cache.get('240561194958716924'); + /// + /// if (member != null && voiceChannel != null) { + /// await member.move(voiceChannel.id); + /// } + Future move(Snowflake channelId) async { + _updateChannel(channelId); + } + + /// ### Disconnects the user from a voice channel + /// + /// Example : + /// ```dart + /// final member = guild.members.cache.get('240561194958716924'); + /// + /// if (member != null) { + /// await member.disconnect(); + /// } + Future disconnect() async { + _updateChannel(null); + } + + Future _updateChannel(Snowflake? channelId) async { + final Http http = ioc.singleton(ioc.services.http); + final Response response = await http.patch( + url: '/guilds/${member!.guild.id}/members/${member!.user.id}', + payload: {'channel_id': channelId} + ); + + if (response.statusCode == 204 || response.statusCode == 200) { + final VoiceChannel? channel = member!.guild.channels.cache.get(channelId); + if (channel != null) { + this.channel = channel; + } + } + } + + factory VoiceManager.from(dynamic payload, VoiceChannel? channel) { + return VoiceManager( + isMute: payload['mute'], + isDeaf: payload['deaf'], + isSelfMute: payload['self_mute'], + isSelfDeaf: payload['self_deaf'], + hasVideo: payload['self_video'], + hasStream: payload['self_stream'], + channel: channel + ); + } + + +} diff --git a/lib/src/api/messages/dm_message.dart b/lib/src/api/messages/dm_message.dart new file mode 100644 index 000000000..2e727ba51 --- /dev/null +++ b/lib/src/api/messages/dm_message.dart @@ -0,0 +1,124 @@ +import 'package:mineral/api.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/api/channels/dm_channel.dart'; +import 'package:mineral/src/api/components/component.dart'; +import 'package:mineral/src/api/messages/message_attachment.dart'; +import 'package:mineral/src/api/messages/message_sticker_item.dart'; +import 'package:mineral/src/api/messages/partial_message.dart'; + +class DmMessage extends PartialMessage { + User author; + + DmMessage({ + required id, + required content, + required tts, + required embeds, + required allowMentions, + required reference, + required components, + required stickers, + required payload, + required attachments, + required flags, + required channelId, + required channel, + required this.author, + }): super( + id: id, + content: content, + tts: tts, + embeds: embeds, + allowMentions: allowMentions, + reference: reference, + components: components, + stickers: stickers, + payload: payload, + attachments: attachments, + flags: flags, + channelId: channelId, + channel: channel, + ); + + factory DmMessage.from({ required DmChannel channel, required dynamic payload }) { + MineralClient client = ioc.singleton(ioc.services.client); + User? user = client.users.cache.get(payload['author']['id']); + List embeds = []; + + for (dynamic element in payload['embeds']) { + List fields = []; + if (element['fields'] != null) { + for (dynamic item in element['fields']) { + Field field = Field(name: item['name'], value: item['value'], inline: item['inline'] ?? false); + fields.add(field); + } + } + + MessageEmbed embed = MessageEmbed( + title: element['title'], + description: element['description'], + url: element['url'], + timestamp: element['timestamp'] != null ? DateTime.parse(element['timestamp']) : null, + footer: element['footer'] != null ? Footer( + text: element['footer']['text'], + iconUrl: element['footer']['icon_url'], + proxyIconUrl: element['footer']['proxy_icon_url'], + ) : null, + image: element['image'] != null ? Image( + url: element['image']['url'], + proxyUrl: element['image']['proxy_url'], + height: element['image']['height'], + width: element['image']['width'], + ) : null, + author: element['author'] != null ? Author( + name: element['author']['name'], + url: element['author']['url'], + proxyIconUrl: element['author']['proxy_icon_url'], + iconUrl: element['author']['icon_url'], + ) : null, + fields: fields, + ); + + embeds.add(embed); + } + + List stickers = []; + if (payload['sticker_items'] != null) { + for (dynamic element in payload['sticker_items']) { + MessageStickerItem sticker = MessageStickerItem.from(element); + stickers.add(sticker); + } + } + + List messageAttachments = []; + if (payload['attachments'] != null) { + for (dynamic element in payload['attachments']) { + MessageAttachment attachment = MessageAttachment.from(element); + messageAttachments.add(attachment); + } + } + + List components = []; + for (dynamic payload in payload['components']) { + Component component = Component.from(payload: payload); + components.add(component); + } + + return DmMessage( + id: payload['id'], + content: payload['content'], + tts: payload['tts'] ?? false, + allowMentions: payload['allow_mentions'] ?? false, + reference: payload['reference'], + flags: payload['flags'], + channelId: channel.id, + channel: channel, + author: user!, + embeds: embeds, + components: components, + payload: payload['payload'], + stickers: stickers, + attachments: messageAttachments, + ); + } +} diff --git a/lib/src/api/message.dart b/lib/src/api/messages/message.dart similarity index 67% rename from lib/src/api/message.dart rename to lib/src/api/messages/message.dart index f5ddbaad8..9ed1490ad 100644 --- a/lib/src/api/message.dart +++ b/lib/src/api/messages/message.dart @@ -1,56 +1,67 @@ -import 'dart:convert'; - 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/api/message_attachment.dart'; -import 'package:mineral/src/api/message_embed.dart'; -import 'package:mineral/src/api/message_sticker_item.dart'; +import 'package:mineral/src/api/messages/message_attachment.dart'; +import 'package:mineral/src/api/messages/message_sticker_item.dart'; +import 'package:mineral/src/api/messages/partial_message.dart'; -class Message { - Snowflake id; - String content; - bool tts; - List embeds; - bool allowMentions; - Message? reference; - List components; - List stickers; - dynamic payload; - List attachments; - int? flags; - Snowflake channelId; - TextBasedChannel channel; +class Message extends PartialMessage { GuildMember author; Message({ - required this.id, - required this.content, - required this.tts, - required this.embeds, - required this.allowMentions, - required this.reference, - required this.components, - required this.stickers, - required this.payload, - required this.attachments, - required this.flags, - required this.channelId, - required this.channel, + required id, + required content, + required tts, + required embeds, + required allowMentions, + required reference, + required components, + required stickers, + required payload, + required attachments, + required flags, + required channelId, + required channel, required this.author, - }); + }): super( + id: id, + content: content, + tts: tts, + embeds: embeds, + allowMentions: allowMentions, + reference: reference, + components: components, + stickers: stickers, + payload: payload, + attachments: attachments, + flags: flags, + channelId: channelId, + channel: channel, + ); - Future sync () async { + Future edit ({ String? content, List? embeds, List? components, bool? tts }) async { Http http = ioc.singleton(ioc.services.http); - Response response = await http.get(url: "/channels/${channel.id}"); - dynamic payload = jsonDecode(response.body); + Response response = await http.patch( + url: '/channels/$channelId/messages/$id', + payload: { + 'content': content, + 'embeds': embeds, + 'flags': flags, + 'allowed_mentions': allowMentions, + 'components': components, + } + ); - Message message = Message.from(channel: channel, payload: payload); - channel.messages.cache.set(message.id, message); + print(response.body); + if (response.statusCode == 200) { + this.content = content ?? this.content; + this.embeds = embeds ?? this.embeds; + this.components = components ?? this.components; + } - return message; + return this; } factory Message.from({ required TextBasedChannel channel, required dynamic payload }) { diff --git a/lib/src/api/message_attachment.dart b/lib/src/api/messages/message_attachment.dart similarity index 100% rename from lib/src/api/message_attachment.dart rename to lib/src/api/messages/message_attachment.dart diff --git a/lib/src/api/message_embed.dart b/lib/src/api/messages/message_embed.dart similarity index 54% rename from lib/src/api/message_embed.dart rename to lib/src/api/messages/message_embed.dart index 7199110f4..94345ea5f 100644 --- a/lib/src/api/message_embed.dart +++ b/lib/src/api/messages/message_embed.dart @@ -30,6 +30,22 @@ class Image { }; } +class Thumbnail { + String url; + String? proxyUrl; + int? height; + int? width; + + Thumbnail({ required this.url, this.proxyUrl, this.height, this.width }); + + Object toJson () => { + 'url': url, + 'proxy_url': proxyUrl, + 'width': width, + 'height': height, + }; +} + class Author { String name; String? url; @@ -67,7 +83,7 @@ class MessageEmbed { DateTime? timestamp; Footer? footer; Image? image; - // Thumbnail thumbnail; + Thumbnail? thumbnail; Author? author; List? fields; Color? color; @@ -79,51 +95,139 @@ class MessageEmbed { this.timestamp, this.footer, this.image, + this.thumbnail, this.author, this.fields, this.color, }); + /// ### Set the [title] field and return this + /// + /// Example : + /// ```dart + /// final embed = MessageEmbed() + /// .setTitle('My title'); + /// ``` MessageEmbed setTitle (String value) { title = value; return this; } + /// ### Set the [description] field and return this + /// + /// Example : + /// ```dart + /// final embed = MessageEmbed() + /// .setDescription('My description'); + /// ``` MessageEmbed setDescription (String value) { description = value; return this; } + /// ### Set the [footer] field and return this + /// + /// Example : + /// ```dart + /// final embed = MessageEmbed() + /// .setFooter(text: 'My title'); + /// ``` MessageEmbed setFooter ({ required String text, String? iconUrl, String? proxyIconUrl }) { footer = Footer(text: text, iconUrl: iconUrl, proxyIconUrl: proxyIconUrl); return this; } + /// ### Set the [image] field and return this + /// + /// Example : + /// ```dart + /// final embed = MessageEmbed() + /// .setImage(url: 'https://..../images/my_picture.png'); + /// ``` MessageEmbed setImage ({ required String url, String? proxyUrl, int? width, int? height }) { image = Image(url: url, proxyUrl: proxyUrl, width: width, height: height); return this; } + /// ### Set the [thumbnail] field and return this + /// + /// Example : + /// ```dart + /// final embed = MessageEmbed() + /// .setThumbnail(url: 'https://..../images/my_picture.png'); + /// ``` + MessageEmbed setThumbnail ({ required String url, String? proxyUrl, int? width, int? height }) { + thumbnail = Thumbnail(url: url, proxyUrl: proxyUrl, width: width, height: height); + return this; + } + + /// ### Set the [author] field and return this + /// + /// Example : + /// ```dart + /// final embed = MessageEmbed() + /// .setAuthor(name: 'John Doe'); + /// ``` MessageEmbed setAuthor ({ required String name, String? url, String? iconUrl, String? proxyIconUrl }) { author = Author(name: name, url: url, iconUrl: iconUrl, proxyIconUrl: proxyIconUrl); return this; } + /// ### Set the [color] field and return this + /// + /// Example : + /// ```dart + /// final embed = MessageEmbed() + /// .setColor(Color.cyan_600); + /// ``` + /// Or with your custom color + /// + /// Example : + /// ```dart + /// final embed = MessageEmbed() + /// .setColor(Color('#FFFFFF')); + /// ``` MessageEmbed setColor (Color color) { this.color = color; return this; } + /// ### Set the [timestamp] field and return this + /// + /// Example : + /// ```dart + /// final embed = MessageEmbed() + /// .setTimestamp(); + /// ``` + /// You can define an older or future timestamp + /// DateTime date = DateTime.now().add(DateTime(days: 5)); + /// final embed = MessageEmbed() + /// .setTimestamp(dateTime: date); + /// ``` MessageEmbed setTimestamp ({ DateTime? dateTime }) { timestamp = dateTime ?? DateTime.now(); return this; } + /// ### Set the [url] field and return this + /// + /// Example : + /// ```dart + /// final embed = MessageEmbed() + /// .setUrl('https://.....com'); + /// ``` MessageEmbed setUrl (String url) { this.url = url; return this; } + /// ### Add an field into [fields] and return this + /// + /// Example : + /// ```dart + /// final embed = MessageEmbed() + /// .addField(name: 'My field', value: 'My custom value'); + /// ``` MessageEmbed addField ({ required String name, required String value, bool? inline }) { fields?.add(Field(name: name, value: value, inline: inline)); return this; @@ -146,6 +250,7 @@ class MessageEmbed { 'fields': fields, 'color': color != null ? int.parse(color.toString().replaceAll('#', ''), radix: 16) : null, 'image': image?.toJson(), + 'thumbnail': thumbnail?.toJson(), }; } } diff --git a/lib/src/api/message_sticker_item.dart b/lib/src/api/messages/message_sticker_item.dart similarity index 96% rename from lib/src/api/message_sticker_item.dart rename to lib/src/api/messages/message_sticker_item.dart index 4b525bb37..cf8a7c4e9 100644 --- a/lib/src/api/message_sticker_item.dart +++ b/lib/src/api/messages/message_sticker_item.dart @@ -13,8 +13,6 @@ class MessageStickerItem { }); factory MessageStickerItem.from(dynamic payload) { - print(payload); - return MessageStickerItem( id: payload['id'], name: payload['name'], diff --git a/lib/src/api/messages/partial_message.dart b/lib/src/api/messages/partial_message.dart new file mode 100644 index 000000000..fa266711e --- /dev/null +++ b/lib/src/api/messages/partial_message.dart @@ -0,0 +1,37 @@ +import 'package:mineral/api.dart'; +import 'package:mineral/src/api/channels/partial_channel.dart'; +import 'package:mineral/src/api/components/component.dart'; +import 'package:mineral/src/api/messages/message_attachment.dart'; +import 'package:mineral/src/api/messages/message_sticker_item.dart'; + +class PartialMessage { + Snowflake id; + String content; + bool tts; + List embeds; + bool allowMentions; + PartialMessage? reference; + List components; + List stickers; + dynamic payload; + List attachments; + int? flags; + Snowflake channelId; + T channel; + + PartialMessage({ + required this.id, + required this.content, + required this.tts, + required this.embeds, + required this.allowMentions, + required this.reference, + required this.components, + required this.stickers, + required this.payload, + required this.attachments, + required this.flags, + required this.channelId, + required this.channel, + }); +} diff --git a/lib/src/api/moderation_rule.dart b/lib/src/api/moderation_rule.dart index 57063ec63..93b06b0dc 100644 --- a/lib/src/api/moderation_rule.dart +++ b/lib/src/api/moderation_rule.dart @@ -119,6 +119,12 @@ class ModerationRule { required this.exemptChannels, }); + /// ### Update the label of this + /// + /// Example : + /// ```dart + /// await rule.setLabel('My label'); + /// ``` Future setLabel(String label) async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch(url: "/guilds/$guildId/auto-moderation/rules/$id", payload: { 'label': label }); @@ -128,6 +134,12 @@ class ModerationRule { } } + /// ### Update the event of this + /// + /// Example : + /// ```dart + /// await rule.setEventType(ModerationEventType.messageSend); + /// ``` Future setEventType(ModerationEventType event) async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch(url: "/guilds/$guildId/auto-moderation/rules/$id", payload: { 'event_type': event.value }); @@ -137,6 +149,17 @@ class ModerationRule { } } + /// ### Update the trigger metadata of this + /// + /// Example : + /// ```dart + /// final metadata = ModerationTriggerMetadata( + /// keywordFilter: ['foo'], + /// presets: [ModerationPresetType.profanity] + /// ); + /// + /// await rule.setTriggerMetadata(metadata); + /// ``` Future setTriggerMetadata(ModerationTriggerMetadata triggerMetadata) async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch(url: "/guilds/$guildId/auto-moderation/rules/$id", payload: { 'trigger_metadata': triggerMetadata.toJson() }); @@ -146,6 +169,19 @@ class ModerationRule { } } + /// ### Update actions of this + /// + /// Example : + /// ```dart + /// final channel = guild.channels.cache.get('240561194958716924'); + /// + /// final action = ModerationAction( + /// metadata: ModerationActionMetadata(duration: 3000, channel: channel), + /// type: ModerationActionType.sendAlertMessage + /// ); + /// + /// await rule.setActions([action]); + /// ``` Future setActions(List actions) async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch(url: "/guilds/$guildId/auto-moderation/rules/$id", payload: { @@ -157,6 +193,12 @@ class ModerationRule { } } + /// ### Update enabled of this + /// + /// Example : + /// ```dart + /// await rule.setEnabled(true); + /// ``` Future setEnabled(bool value) async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch(url: "/guilds/$guildId/auto-moderation/rules/$id", payload: { 'value': value }); @@ -166,6 +208,16 @@ class ModerationRule { } } + /// ### Defines which roles will not be affected by this + /// + /// Example : + /// ```dart + /// final role = guild.roles.cache.get('240561194958716924'); + /// + /// if (role != null) { + /// await rule.setExemptRoles([role]); + /// } + /// ``` Future setExemptRoles(List roles) async { int maxItems = 50; if (roles.length > maxItems) { @@ -182,6 +234,16 @@ class ModerationRule { } } + /// ### Defines which roles will not be affected by this + /// + /// Example : + /// ```dart + /// final channel = guild.channels.cache.get('240561194958716924'); + /// + /// if (channel != null) { + /// await rule.setExemptChannels([channel]); + /// } + /// ``` Future setExemptChannels(List channels) async { int maxItems = 50; if (channels.length > maxItems) { @@ -197,7 +259,12 @@ class ModerationRule { exemptChannels = channels; } } - + /// ### Delete this + /// + /// Example : + /// ```dart + /// await rule.delete(); + /// ``` Future delete() async { Http http = ioc.singleton(ioc.services.http); Response response = await http.destroy(url: "/guilds/$guildId/auto-moderation/rules/$id"); diff --git a/lib/src/api/role.dart b/lib/src/api/role.dart index 6578f31b8..554b39739 100644 --- a/lib/src/api/role.dart +++ b/lib/src/api/role.dart @@ -3,7 +3,7 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; import 'package:mineral/exception.dart'; import 'package:mineral/helper.dart'; -import 'package:mineral/src/api/guilds/guild_role_manager.dart'; +import 'package:mineral/src/api/managers/guild_role_manager.dart'; class Tag { Snowflake? botId; @@ -50,7 +50,9 @@ class Role { required this.manager, }); - /// Modifies the [label] of the role. + /// ### Modifies the [label] of the role. + /// + /// Example : /// ```dart /// final Role? role = guild.roles.cache.get('240561194958716924'); /// if (role != null) { @@ -66,7 +68,9 @@ class Role { } } - /// Modifies the permissions associated with this + /// ### Modifies the permissions associated with this + /// + /// Example : /// ```dart /// import 'package:mineral/api.dart'; ๐Ÿ‘ˆ // then you can use Permission class /// @@ -86,7 +90,9 @@ class Role { } } - /// Modifies the [color] of the role. + /// ### Modifies the [color] of the role. + /// + /// Example : /// ```dart /// import 'package:mineral/api.dart'; ๐Ÿ‘ˆ // then you can use Color class /// @@ -96,6 +102,8 @@ class Role { /// } /// ``` /// You can use a custom colour from a hexadecimal format. + /// + /// Example : /// ```dart /// await role.setColor(Color('#ffffff')); /// ``` @@ -109,7 +117,9 @@ class Role { } } - /// Modifies the [hoist] of the role from [bool]. + /// ### Modifies the [hoist] of the role from [bool]. + /// + /// Example : /// ```dart /// final Role? role = guild.roles.cache.get('240561194958716924'); /// if (role != null) { @@ -125,7 +135,7 @@ class Role { } } - /// Modifies the [icon] of the role from [String] path. + /// ### Modifies the [icon] of the role from [String] path. /// /// We consider having the file structure /// ``` @@ -138,6 +148,8 @@ class Role { /// .env /// pubspec.yaml /// ``` + /// + /// Example : /// ```dart /// final Role? role = guild.roles.cache.get('240561194958716924'); /// if (role != null) { @@ -158,9 +170,11 @@ class Role { } } - /// Remove the [icon] of the role. + /// ### Remove the [icon] of the role. /// /// Your guild requires the [GuildFeature.roleIcons] to perform this action, otherwise throw [MissingFeatureException]. + /// + /// Example : /// ```dart /// import 'package:mineral/api.dart'; ๐Ÿ‘ˆ // then you can use GuildFeature enum /// @@ -183,9 +197,11 @@ class Role { } } - /// Define the [unicodeEmoji] of the role from [String]. + /// ### Define the [unicodeEmoji] of the role from [String]. /// /// Your guild requires the [GuildFeature.roleIcons] to perform this action, otherwise throw [MissingFeatureException]. + /// + /// Example : /// ```dart /// import 'package:mineral/api.dart'; ๐Ÿ‘ˆ // then you can use GuildFeature enum /// @@ -208,7 +224,9 @@ class Role { } } - /// Modifies the [mentionable] of the role from [bool]. + /// ### Modifies the [mentionable] of the role from [bool]. + /// + /// Example : /// ```dart /// final Role? role = guild.roles.cache.get('240561194958716924'); /// if (role != null) { @@ -225,6 +243,8 @@ class Role { } /// Removes the current this from the [MemberRoleManager]'s cache + /// + /// Example : /// ```dart /// final Role? role = guild.roles.cache.get('240561194958716924'); /// if (role != null) { @@ -232,6 +252,8 @@ class Role { /// } /// ``` /// You can specify a reason for this action + /// + /// Example : /// ```dart /// await role.delete(reason: 'I will destroy this..'); /// ``` @@ -251,6 +273,8 @@ class Role { } /// Returns this in discord notification format + /// + /// Example : /// ```dart /// final Role? role = guild.roles.cache.get('240561194958716924'); /// if (role != null) { diff --git a/lib/src/api/user.dart b/lib/src/api/user.dart index d791798ac..88c478ed9 100644 --- a/lib/src/api/user.dart +++ b/lib/src/api/user.dart @@ -1,5 +1,11 @@ +import 'dart:convert'; + +import 'package:http/http.dart'; import 'package:mineral/api.dart'; -import 'package:mineral/src/constants.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/api/channels/dm_channel.dart'; +import 'package:mineral/src/api/messages/dm_message.dart'; +import 'package:mineral/src/internal/extensions/mineral_client.dart'; class User { Snowflake id; @@ -9,6 +15,7 @@ class User { bool bot = false; int publicFlags; String? avatar; + String? avatarDecoration; late Status status; User({ @@ -19,10 +26,51 @@ class User { required this.bot, required this.publicFlags, required this.avatar, + required this.avatarDecoration, }); + /// ### Envoie un message en DM ร  l'utilisateur + /// + /// Example : + /// ```dart + /// 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 { + MineralClient client = ioc.singleton(ioc.services.client); + Http http = ioc.singleton(ioc.services.http); + + DmChannel? channel = client.dmChannels.cache.get(id); + + /// Get channel if exist or create + if (channel == null) { + Response response = await http.post(url: '/users/@me/channels', payload: { 'recipient_id': id }); + if (response.statusCode == 200) { + channel = DmChannel.from(payload: jsonDecode(response.body)); + client.dmChannels.cache.putIfAbsent(channel.id, () => channel!); + } + } + + Response response = await client.sendMessage(channel!, + content: content, + embeds: embeds, + components: components + ); + + if (response.statusCode == 200) { + dynamic payload = jsonDecode(response.body); + + DmMessage message = DmMessage.from(channel: channel, payload: payload); + channel.messages.cache.putIfAbsent(message.id, () => message); + + return message; + } + return null; + } + + /// ### Returns the absolute url to the user's avatar String getDisplayAvatarUrl () { - return "${Constants.cdnUrl}/avatars/$id/$avatar"; + return '${Constants.cdnUrl}/avatars/$id/$avatar'; } @override @@ -37,6 +85,7 @@ class User { bot: payload['bot'] == true, publicFlags: payload['public_flags'] ?? 0, avatar: payload['avatar'], + avatarDecoration: payload['avatar_decoration'] ); } } diff --git a/lib/src/api/utils.dart b/lib/src/api/utils.dart index 1e8b9dcec..387908910 100644 --- a/lib/src/api/utils.dart +++ b/lib/src/api/utils.dart @@ -143,6 +143,7 @@ enum GuildFeature { partnered('PARTNERED'), preview('PREVIEW_ENABLED'), privateThreads('PRIVATE_THREADS'), + threeDayThreadArchive('THREE_DAY_THREAD_ARCHIVE'), roleIcons('ROLE_ICONS'), ticketedEvents('TICKETED_EVENTS_ENABLED'), vanityUrl('VANITY_URL'), diff --git a/lib/src/api/voice.dart b/lib/src/api/voice.dart deleted file mode 100644 index 0e6b1c0e8..000000000 --- a/lib/src/api/voice.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'package:http/http.dart'; -import 'package:mineral/api.dart'; -import 'package:mineral/core.dart'; - -class Voice { - Snowflake? channelId; - VoiceChannel? channel; - bool isDeaf; - bool isMuted; - late GuildMember member; - - Voice({ - required this.isDeaf, - required this.isMuted, - required this.channelId, - }); - - Future mute () async { - Http http = ioc.singleton(ioc.services.http); - - Response response = await http.patch(url: "/guilds/${member.guild.id}/members/${member.user.id}", payload: { 'mute': true }); - if (response.statusCode == 200) { - isMuted = true; - } - } - - Future unmute () async { - Http http = ioc.singleton(ioc.services.http); - - Response response = await http.patch(url: "/guilds/${member.guild.id}/members/${member.user.id}", payload: { 'mute': false }); - if (response.statusCode == 200) { - isMuted = false; - } - } - - Future deaf () async { - Http http = ioc.singleton(ioc.services.http); - - Response response = await http.patch(url: "/guilds/${member.guild.id}/members/${member.user.id}", payload: { 'deaf': true }); - if (response.statusCode == 200) { - isDeaf = true; - } - } - - Future undeaf () async { - Http http = ioc.singleton(ioc.services.http); - - Response response = await http.patch(url: "/guilds/${member.guild.id}/members/${member.user.id}", payload: { 'deaf': false }); - if (response.statusCode == 200) { - isDeaf = false; - } - } - - Future move (VoiceChannel channel) async { - Http http = ioc.singleton(ioc.services.http); - - Response response = await http.patch(url: "/guilds/${member.guild.id}/members/${member.user.id}", payload: { 'channel_id': channel.id }); - if (response.statusCode == 200) { - channelId = channel.id; - this.channel = channel; - } - } - - Future disconnect () async { - Http http = ioc.singleton(ioc.services.http); - - Response response = await http.patch(url: "/guilds/${member.guild.id}/members/${member.user.id}", payload: { 'channel_id': null }); - if (response.statusCode == 200) { - channelId = null; - channel = null; - } - } - - factory Voice.from ({ required dynamic payload }) { - return Voice( - isDeaf: payload['deaf'] == false, - isMuted: payload['mute'] == false, - channelId: payload['channel_id'], - ); - } -} diff --git a/lib/src/api/webhook.dart b/lib/src/api/webhook.dart index 9e2f3c90d..ea0af2b6a 100644 --- a/lib/src/api/webhook.dart +++ b/lib/src/api/webhook.dart @@ -42,7 +42,13 @@ class Webhook { required this.url, }); - Future setName (String label) async { + /// ### Update the label of this + /// + /// Example : + /// ```dart + /// await webhook.setLabel('My webhook name'); + /// ``` + Future setLabel (String label) async { Http http = ioc.singleton(ioc.services.http); Response response = await http.patch(url: "/webhooks/$id", payload: { 'name': label }); @@ -51,6 +57,12 @@ class Webhook { } } + /// ### Update the avatar of this + /// + /// Example : + /// ```dart + /// await webhook.setAvatar('assets/images/my_picture.png'); + /// ``` Future setAvatar (String avatar) async { Http http = ioc.singleton(ioc.services.http); String path = await Helper.getPicture(avatar); @@ -61,6 +73,13 @@ class Webhook { } } + /// ### Updates multiple properties of this in a single request. + /// When you need to update more than 2 fields, we advise you to use this method to reduce the number of outgoing requests. + /// + /// Example : + /// ```dart + /// await webhook.update(label: 'My webhook name', avatar: 'assets/images/my_picture.png'); + /// ``` Future update ({ String? label, String? avatar }) async { Http http = ioc.singleton(ioc.services.http); String? path = avatar != null @@ -77,7 +96,12 @@ class Webhook { if (avatar != null) this.avatar = path; } } - + /// ### Send a message from the webhook + /// + /// Example : + /// ```dart + /// await webhook.execute(content: 'Hello World !'); + /// ``` Future execute ({ String? content, String? username, String? avatarUrl, bool? tts, List? embeds, List? components, bool? suppressEmbed }) async { Http http = ioc.singleton(ioc.services.http); @@ -106,6 +130,12 @@ class Webhook { }); } + /// ### Delete this + /// + /// Example : + /// ```dart + /// await webhook.delete(); + /// ``` Future delete () async { Http http = ioc.singleton(ioc.services.http); Response response = await http.destroy(url: "/webhooks/$id/$token"); diff --git a/lib/src/commands/create_project.dart b/lib/src/commands/create_project.dart index c53427c00..1bc8b6b0a 100644 --- a/lib/src/commands/create_project.dart +++ b/lib/src/commands/create_project.dart @@ -1,9 +1,9 @@ import 'dart:io'; import 'package:args/args.dart'; +import 'package:mineral/api.dart'; import 'package:mineral/console.dart'; -import 'package:mineral/helper.dart'; -import 'package:mineral/src/internal/entities/cli_manager.dart'; +import 'package:mineral/src/internal/managers/cli_manager.dart'; import 'package:path/path.dart'; class CreateProject extends MineralCliCommand { @@ -17,11 +17,11 @@ class CreateProject extends MineralCliCommand { return; } - String filename = Helper.toSnakeCase(args.arguments.elementAt(1)); + String filename = args.arguments.elementAt(1).snakeCase; final projectDirectory = Directory(join(Directory.current.path, filename)); - ProcessResult process = await Process.run('git', ['clone', 'https://github.com/mineral-dart/base-structure.git', Helper.toSnakeCase(filename)]); + ProcessResult process = await Process.run('git', ['clone', 'https://github.com/mineral-dart/base-structure.git', filename.snakeCase]); switch (process.exitCode) { case 0: diff --git a/lib/src/commands/make_command.dart b/lib/src/commands/make_command.dart index bc5f416ef..c4c990a8e 100644 --- a/lib/src/commands/make_command.dart +++ b/lib/src/commands/make_command.dart @@ -2,9 +2,9 @@ import 'dart:io'; import 'package:args/args.dart'; import 'package:interact/interact.dart'; +import 'package:mineral/api.dart'; import 'package:mineral/console.dart'; -import 'package:mineral/helper.dart'; -import 'package:mineral/src/internal/entities/cli_manager.dart'; +import 'package:mineral/src/internal/managers/cli_manager.dart'; import 'package:path/path.dart'; class MakeCommand extends MineralCliCommand { @@ -18,7 +18,7 @@ class MakeCommand extends MineralCliCommand { return; } - String filename = Helper.toCapitalCase(args.arguments.elementAt(1)); + String filename = args.arguments.elementAt(1).capitalCase; final useExistLocation = Confirm( prompt: 'Do you want to use an existing location on your disk ?', @@ -38,30 +38,32 @@ class MakeCommand extends MineralCliCommand { .toList(), ).interact(); - file = File(join(directories[selection].path, '${Helper.toSnakeCase(filename)}.dart')); + file = File(join(directories[selection].path, '${filename.snakeCase}.dart')); } else { final location = Input( prompt: 'Target folder location', defaultValue: 'App/folder', // optional, will provide the user as a hint ).interact(); - file = File(join(Directory.current.path, 'src', location.replaceAll('App/', ''), '${Helper.toSnakeCase(filename)}.dart')); + file = File(join(Directory.current.path, 'src', location.replaceAll('App/', ''), '${filename.snakeCase}.dart')); } await file.create(recursive: true); await writeFileContent(file, getTemplate(filename)); Console.success(message: 'The file was created in the location ${file.uri}'); + Console.success(message: 'Don\'t forget to add your file to the main.dart file'); } String getTemplate (String filename) => ''' import 'package:mineral/core.dart'; import 'package:mineral/api.dart'; -@Command(name: '${filename.toLowerCase()}', description: '${Helper.toCapitalCase(filename)} command description', scope: 'GUILD') -class ${Helper.toPascalCase(filename)} extends MineralCommand { +@Command(name: '${filename.toLowerCase()}', description: '${filename.capitalCase} command description', scope: 'GUILD') +class ${filename.pascalCase} extends MineralCommand { Future handle (CommandInteraction interaction) async { // Your code here + await interaction.reply(content: 'Hello World ! ๐Ÿ’ช'); } } '''; diff --git a/lib/src/commands/make_event.dart b/lib/src/commands/make_event.dart index a2bf42989..a8de414e4 100644 --- a/lib/src/commands/make_event.dart +++ b/lib/src/commands/make_event.dart @@ -1,13 +1,13 @@ import 'dart:io'; +import 'package:mineral/api.dart'; import 'package:mineral/console.dart'; import 'package:path/path.dart'; import 'package:args/args.dart'; import 'package:interact/interact.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/helper.dart'; -import 'package:mineral/src/internal/entities/cli_manager.dart'; +import 'package:mineral/src/internal/managers/cli_manager.dart'; class MakeEvent extends MineralCliCommand { @override @@ -20,7 +20,7 @@ class MakeEvent extends MineralCliCommand { return; } - String filename = Helper.toCapitalCase(args.arguments.elementAt(1)); + String filename = args.arguments.elementAt(1).capitalCase; final eventKey = Select( prompt: 'Which event would you like to use ?', @@ -45,20 +45,21 @@ class MakeEvent extends MineralCliCommand { .toList(), ).interact(); - file = File(join(directories[selection].path, '${Helper.toSnakeCase(filename)}.dart')); + file = File(join(directories[selection].path, '${filename.snakeCase}.dart')); } else { final location = Input( prompt: 'Target folder location', defaultValue: 'App/folder', // optional, will provide the user as a hint ).interact(); - file = File(join(Directory.current.path, 'src', location.replaceAll('App/', ''), '${Helper.toSnakeCase(filename)}.dart')); + file = File(join(Directory.current.path, 'src', location.replaceAll('App/', ''), '${filename.snakeCase}.dart')); } await file.create(recursive: true); await writeFileContent(file, getTemplate(filename, Events.values.elementAt(eventKey))); Console.success(message: 'The file was created in the location ${file.uri}'); + Console.success(message: 'Don\'t forget to add your file to the main.dart file'); } String getTemplate (String filename, Events event) { @@ -72,7 +73,7 @@ import 'package:mineral/core.dart'; import 'package:mineral/api.dart'; @Event(${event.toString()}) -class ${Helper.toCapitalCase(filename)} extends MineralEvent { +class ${filename.capitalCase} extends MineralEvent { Future handle (${params.join(', ')}) async { // Your code here } diff --git a/lib/src/commands/make_module.dart b/lib/src/commands/make_module.dart index 1b092ab89..a895fb910 100644 --- a/lib/src/commands/make_module.dart +++ b/lib/src/commands/make_module.dart @@ -2,9 +2,9 @@ import 'dart:io'; import 'package:args/args.dart'; import 'package:interact/interact.dart'; +import 'package:mineral/api.dart'; import 'package:mineral/console.dart'; -import 'package:mineral/helper.dart'; -import 'package:mineral/src/internal/entities/cli_manager.dart'; +import 'package:mineral/src/internal/managers/cli_manager.dart'; import 'package:path/path.dart'; class MakeModule extends MineralCliCommand { @@ -18,7 +18,7 @@ class MakeModule extends MineralCliCommand { return; } - String filename = Helper.toCapitalCase(args.arguments.elementAt(1)); + String filename = args.arguments.elementAt(1).capitalCase; final useExistLocation = Confirm( prompt: 'Do you want to use an existing location on your disk ?', @@ -39,16 +39,16 @@ class MakeModule extends MineralCliCommand { .toList(), ).interact(); - directory = Directory(join(directories[selection].path, Helper.toSnakeCase(filename))); - file = File(join(directory.path, '${Helper.toSnakeCase(filename)}.dart')); + directory = Directory(join(directories[selection].path, filename.snakeCase)); + file = File(join(directory.path, '${filename.snakeCase}.dart')); } else { final location = Input( prompt: 'Target folder location', defaultValue: 'App/folder', ).interact(); - directory = Directory(join(Directory.current.path, 'src', location.replaceAll('App/', ''), Helper.toSnakeCase(filename))); - file = File(join(directory.path, '${Helper.toSnakeCase(filename)}.dart')); + directory = Directory(join(Directory.current.path, 'src', location.replaceAll('App/', ''), filename.snakeCase)); + file = File(join(directory.path, '${filename.snakeCase}.dart')); } await Directory(join(directory.path, 'events')).create(recursive: true); @@ -59,13 +59,14 @@ class MakeModule extends MineralCliCommand { await writeFileContent(file, getTemplate(filename)); Console.success(message: 'The file was created in the location ${file.uri}'); + Console.success(message: 'Don\'t forget to add your file to the main.dart file'); } String getTemplate (String filename) => ''' import 'package:mineral/core.dart'; -@Module(identifier: '${Helper.toSnakeCase(filename)}', label: '${Helper.toCapitalCase(filename)} module') -class ${Helper.toPascalCase(filename)} extends MineralModule { +@Module(identifier: '${filename.snakeCase}', label: '${filename.capitalCase} module') +class ${filename.pascalCase} extends MineralModule { @override List commands = []; diff --git a/lib/src/commands/make_store.dart b/lib/src/commands/make_store.dart index 5c99f5f5b..30a1755a8 100644 --- a/lib/src/commands/make_store.dart +++ b/lib/src/commands/make_store.dart @@ -2,9 +2,9 @@ import 'dart:io'; import 'package:args/args.dart'; import 'package:interact/interact.dart'; +import 'package:mineral/api.dart'; import 'package:mineral/console.dart'; -import 'package:mineral/helper.dart'; -import 'package:mineral/src/internal/entities/cli_manager.dart'; +import 'package:mineral/src/internal/managers/cli_manager.dart'; import 'package:path/path.dart'; class MakeStore extends MineralCliCommand { @@ -18,7 +18,7 @@ class MakeStore extends MineralCliCommand { return; } - String filename = Helper.toCapitalCase(args.arguments.elementAt(1)); + String filename = args.arguments.elementAt(1).capitalCase; final useExistLocation = Confirm( prompt: 'Do you want to use an existing location on your disk ?', @@ -38,27 +38,28 @@ class MakeStore extends MineralCliCommand { .toList(), ).interact(); - file = File(join(directories[selection].path, '${Helper.toSnakeCase(filename)}.dart')); + file = File(join(directories[selection].path, '${filename.snakeCase}.dart')); } else { final location = Input( prompt: 'Target folder location', defaultValue: 'App/folder', ).interact(); - file = File(join(Directory.current.path, 'src', location.replaceAll('App/', ''), '${Helper.toSnakeCase(filename)}.dart')); + file = File(join(Directory.current.path, 'src', location.replaceAll('App/', ''), '${filename.snakeCase}.dart')); } await file.create(recursive: true); await writeFileContent(file, getTemplate(filename)); Console.success(message: 'The file was created in the location ${file.uri}'); + Console.success(message: 'Don\'t forget to add your file to the main.dart file'); } String getTemplate (String filename) => ''' import 'package:mineral/core.dart'; -@Store('${Helper.toSnakeCase(filename)}') -class ${Helper.toPascalCase(filename)} implements MineralStore { +@Store('${filename.snakeCase}') +class ${filename.pascalCase} implements MineralStore { @override dynamic state = []; } diff --git a/lib/src/constants.dart b/lib/src/constants.dart index 33d6b121d..eafa93af3 100644 --- a/lib/src/constants.dart +++ b/lib/src/constants.dart @@ -48,6 +48,12 @@ enum PacketType { interactionCreate('INTERACTION_CREATE'), memberUpdate('GUILD_MEMBER_UPDATE'), + memberRemove('GUILD_MEMBER_REMOVE'), + memberAdd('GUILD_MEMBER_ADD'), + + memberJoinRequest('GUILD_JOIN_REQUEST_UPDATE'), + + voiceStateUpdate('VOICE_STATE_UPDATE'), resumed('RESUMED'); diff --git a/lib/src/exceptions/already_exist.dart b/lib/src/exceptions/already_exist.dart index a74657756..6cd871ce4 100644 --- a/lib/src/exceptions/already_exist.dart +++ b/lib/src/exceptions/already_exist.dart @@ -1,4 +1,6 @@ import 'package:mineral/console.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/internal/managers/reporter_manager.dart'; class AlreadyExist implements Exception { String? prefix; @@ -7,6 +9,11 @@ class AlreadyExist implements Exception { @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/exceptions/empty_parameter_exception.dart b/lib/src/exceptions/empty_parameter_exception.dart index 1a590a11a..6a96cd090 100644 --- a/lib/src/exceptions/empty_parameter_exception.dart +++ b/lib/src/exceptions/empty_parameter_exception.dart @@ -1,4 +1,6 @@ import 'package:mineral/console.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/internal/managers/reporter_manager.dart'; class EmptyParameterException implements Exception { String prefix = 'INVALID PARAMETER'; @@ -7,6 +9,11 @@ class EmptyParameterException implements Exception { @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/exceptions/missing_feature_exception.dart b/lib/src/exceptions/missing_feature_exception.dart index 1a3bbffc1..846f225a0 100644 --- a/lib/src/exceptions/missing_feature_exception.dart +++ b/lib/src/exceptions/missing_feature_exception.dart @@ -1,4 +1,6 @@ import 'package:mineral/console.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/internal/managers/reporter_manager.dart'; class MissingFeatureException implements Exception { String prefix = 'MISSING FEATURE'; @@ -8,6 +10,11 @@ class MissingFeatureException implements Exception { @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/exceptions/missing_premium_subscription.dart b/lib/src/exceptions/missing_premium_subscription.dart index 759550938..b63811b8b 100644 --- a/lib/src/exceptions/missing_premium_subscription.dart +++ b/lib/src/exceptions/missing_premium_subscription.dart @@ -1,4 +1,6 @@ import 'package:mineral/console.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/internal/managers/reporter_manager.dart'; class MissingPremiumSubscription implements Exception { String? prefix; @@ -7,6 +9,11 @@ class MissingPremiumSubscription implements Exception { @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/exceptions/not_exist.dart b/lib/src/exceptions/not_exist.dart index 342aee1fc..7cd703def 100644 --- a/lib/src/exceptions/not_exist.dart +++ b/lib/src/exceptions/not_exist.dart @@ -1,4 +1,6 @@ import 'package:mineral/console.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/internal/managers/reporter_manager.dart'; class NotExist implements Exception { String? prefix; @@ -7,6 +9,11 @@ class NotExist implements Exception { @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/exceptions/shard_exception.dart b/lib/src/exceptions/shard_exception.dart index 37a2e6123..6b1bbc91a 100644 --- a/lib/src/exceptions/shard_exception.dart +++ b/lib/src/exceptions/shard_exception.dart @@ -1,4 +1,6 @@ import 'package:mineral/console.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/internal/managers/reporter_manager.dart'; class ShardException implements Exception { String? prefix; @@ -7,6 +9,11 @@ class ShardException implements Exception { @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/exceptions/token_exception.dart b/lib/src/exceptions/token_exception.dart index 9906eef3b..95cd4961a 100644 --- a/lib/src/exceptions/token_exception.dart +++ b/lib/src/exceptions/token_exception.dart @@ -1,4 +1,6 @@ import 'package:mineral/console.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/internal/managers/reporter_manager.dart'; class TokenException implements Exception { String? prefix; @@ -7,6 +9,11 @@ class TokenException implements Exception { @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/exceptions/too_many.dart b/lib/src/exceptions/too_many.dart index c37f040fc..474a507a7 100644 --- a/lib/src/exceptions/too_many.dart +++ b/lib/src/exceptions/too_many.dart @@ -1,4 +1,6 @@ import 'package:mineral/console.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/internal/managers/reporter_manager.dart'; class TooMany implements Exception { String? prefix; @@ -7,6 +9,11 @@ class TooMany implements Exception { @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/environment.dart b/lib/src/internal/environment.dart index 536b9670a..9e7715884 100644 --- a/lib/src/internal/environment.dart +++ b/lib/src/internal/environment.dart @@ -10,18 +10,19 @@ class Environment { List content = await file.readAsLines(encoding: utf8); for (String line in content) { - List content = line.split(':'); + if (line.isNotEmpty) { + List content = line.split(':'); + String key = content[0].trim(); + String value = content[1].trim(); - String key = content[0].trim(); - String value = content[1].trim(); - - _cache.putIfAbsent(key, () => value); + _cache.putIfAbsent(key, () => value); + } } return this; } - String? get (String key){ + String? get (String key) { return _cache[key]; } diff --git a/lib/src/internal/event_emitter.dart b/lib/src/internal/event_emitter.dart index 62d71403d..1c66b2a78 100644 --- a/lib/src/internal/event_emitter.dart +++ b/lib/src/internal/event_emitter.dart @@ -4,7 +4,7 @@ typedef CallbackMap = Set>; class EventEmitter { final Map> _listeners = >{}; - Future on (String event, EventCallback callback) async { + void on (String event, EventCallback callback) { if (_listeners.containsKey(event)) { _listeners[event]?.add((args) => callback); } else { @@ -17,9 +17,9 @@ class EventEmitter { void emit (String event, Object? args) { if (_listeners.containsKey(event)) { - CallbackMap? Map = _listeners[event]; + CallbackMap? callbacks = _listeners[event]; - Map?.forEach((callback) { + callbacks?.forEach((callback) { callback.call(args as T); }); } diff --git a/lib/src/internal/extensions/collection.dart b/lib/src/internal/extensions/collection.dart index 01a615d55..ebdbdd5ae 100644 --- a/lib/src/internal/extensions/collection.dart +++ b/lib/src/internal/extensions/collection.dart @@ -1,12 +1,35 @@ +import 'package:collection/collection.dart'; +import 'package:mineral/src/exceptions/not_exist.dart'; + extension Collection on Map { - /// Returns the value associated from the [key] parameter + /// Returns the value associated from the [K] parameter + /// + /// Example : /// ```dart /// Channel? channel = guild.channels.cache.get('991686152585232404'); /// print(channel); /// ``` T? get (K? key) => this[key] as T?; + /// Returns the value associated from the [K] parameter or defined value + /// + /// Example : + /// ```dart + /// Channel firstChannel = guild.channels.cache.getOrFail('991686152585232404', defaultValue: myChannel ); + /// Channel? secondChannel = guild.channels.cache.getOr('991686152585232404', defaultValue: firstChannel ); + /// print(secondChannel); + /// ``` + T? getOr (K? key, { T? defaultValue }) { + V? result = get(key); + if (result == null) { + return defaultValue; + } + return result as T; + } + /// Inserts or replaces data in the collection + /// + /// Example : /// ```dart /// Channel channel = Channel.from({...}); /// guild.channels.cache.set(channel.id, channel); @@ -14,6 +37,8 @@ extension Collection on Map { void set (K key, V value) => this[key] = value; /// Replaces the value associated with a key if it exists + /// + /// Example : /// ```dart /// Channel channel = Channel.from({...}); /// guild.channels.cache.overrideIfPresent(channel.id, () => channel); @@ -22,4 +47,62 @@ extension Collection on Map { if (this[key] != null) this[key] = ifPresent(); return this[key]; } + + /// Returns the value associated from the [K] parameter + /// + /// Example : + /// ```dart + /// Channel channel = guild.channels.cache.getOrFail('991686152585232404'); + /// print(channel); + /// ``` + /// You can define an error customized message + /// + /// Example : + /// ```dart + /// Channel channel = guild.channels.cache.getOrFail('991686152585232404', message: 'Channel is undefined'); + /// print(channel); + /// ``` + T getOrFail (K? key, { String? message }) { + final T? result = get(key); + if (result == null) { + throw NotExist(prefix: 'Invalid value', cause: message ?? 'No values are attached to $key key.'); + } + + return result; + } + + /// Returns the first element satisfying test, or null if there are none. + /// + /// Example : + /// ```dart + /// Channel? channel = guild.channels.cache.find((channel) => channel.id == '991686152585232404'); + /// print(channel); + /// ``` + T? find (bool Function(V element) callback) { + final MapEntry? result = entries.firstWhereOrNull((item) => callback(item.value)); + return result?.value as T?; + } + + /// Returns the first element satisfying test, or throw if there are none. + /// + /// Example : + /// ```dart + /// Channel? channel = guild.channels.cache.find((channel) => channel.id == '991686152585232404'); + /// print(channel); + /// ``` + /// You can define an error customized message + /// + /// Example : + /// ```dart + /// Channel channel = guild.channels.cache.find((channel) => channel.id == '991686152585232404', message: 'Channel is undefined'); + /// print(channel); + /// ``` + T findOrFail (bool Function(V element) callback, { String? message }) { + final V? result = find(callback); + if (result == null) { + throw NotExist(prefix: 'Invalid value', cause: message ?? 'No values were found.'); + } + + return result as T; + } } diff --git a/lib/src/internal/extensions/mineral_client.dart b/lib/src/internal/extensions/mineral_client.dart new file mode 100644 index 000000000..71fc2a4fc --- /dev/null +++ b/lib/src/internal/extensions/mineral_client.dart @@ -0,0 +1,30 @@ +import 'package:http/http.dart'; +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 { + Http http = ioc.singleton(ioc.services.http); + + List embedList = []; + if (embeds != null) { + for (MessageEmbed element in embeds) { + embedList.add(element.toJson()); + } + } + + List componentList = []; + if (components != null) { + for (Row element in components) { + componentList.add(element.toJson()); + } + } + + return await http.post(url: '/channels/${channel?.id}/messages', payload: { + 'tts': tts ?? false, + 'content': content, + 'embeds': embeds != null ? embedList : [], + 'components': components != null ? componentList : [], + }); + } +} diff --git a/lib/src/internal/extensions/string.dart b/lib/src/internal/extensions/string.dart new file mode 100644 index 000000000..6f19213ff --- /dev/null +++ b/lib/src/internal/extensions/string.dart @@ -0,0 +1,80 @@ +extension StringFormat on String { + + List _groupIntoWords(String text) { + RegExp _upperAlphaRegex = RegExp(r'[A-Z]'); + Set symbolSet = {' ', '.', '/', '_', '\\', '-'}; + + StringBuffer buffer = StringBuffer(); + List words = []; + bool isAllCaps = text.toUpperCase() == text; + + for (int i = 0; i < text.length; i++) { + String char = text[i]; + String? nextChar = i + 1 == text.length ? null : text[i + 1]; + + if (symbolSet.contains(char)) { + continue; + } + + buffer.write(char); + + bool isEndOfWord = nextChar == null + || (_upperAlphaRegex.hasMatch(nextChar) && !isAllCaps) + || symbolSet.contains(nextChar); + + if (isEndOfWord) { + words.add(buffer.toString()); + buffer.clear(); + } + } + + return words; + } + + String _getCamelCase({String separator = ''}) { + List words = _groupIntoWords(this).map(_upperCaseFirstLetter).toList(); + if (_groupIntoWords(this).isNotEmpty) { + words[0] = words[0].toLowerCase(); + } + + return words.join(separator); + } + + String _getConstantCase({String separator = '_'}) { + List words = _groupIntoWords(this).map((word) => word.toUpperCase()).toList(); + + return words.join(separator); + } + + String _getPascalCase({String separator = ''}) { + List words = _groupIntoWords(this).map(_upperCaseFirstLetter).toList(); + + return words.join(separator); + } + + String _getSentenceCase({String separator = ' '}) { + List words = _groupIntoWords(this).map((word) => word.toLowerCase()).toList(); + if (_groupIntoWords(this).isNotEmpty) { + words[0] = _upperCaseFirstLetter(words[0]); + } + + return words.join(separator); + } + + String _getSnakeCase({String separator = '_'}) { + List words = _groupIntoWords(this).map((word) => word.toLowerCase()).toList(); + + return words.join(separator); + } + + String _upperCaseFirstLetter(String word) { + return '${word.substring(0, 1).toUpperCase()}${word.substring(1).toLowerCase()}'; + } + + String get capitalCase => _upperCaseFirstLetter(this); + String get camelCase => _getCamelCase(); + String get constantCase => _getConstantCase(); + String get sentenceCase => _getSentenceCase(); + String get snakeCase => _getSnakeCase(); + String get pascalCase => _getPascalCase(); +} diff --git a/lib/src/internal/ioc.dart b/lib/src/internal/ioc.dart index a3470507e..4c223a7b9 100644 --- a/lib/src/internal/ioc.dart +++ b/lib/src/internal/ioc.dart @@ -8,6 +8,7 @@ class Service { final modules = 'Mineral/Core/Modules'; final cli = 'Mineral/Core/Cli'; final shards = 'Mineral/Core/Shards'; + final reporter = 'Mineral/Core/Reporter'; } class Ioc { diff --git a/lib/src/internal/kernel.dart b/lib/src/internal/kernel.dart index 33b2f7535..95ca48ae9 100644 --- a/lib/src/internal/kernel.dart +++ b/lib/src/internal/kernel.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:mineral/api.dart'; import 'package:mineral/exception.dart'; import 'package:mineral/src/commands/create_project.dart'; @@ -5,14 +7,15 @@ import 'package:mineral/src/commands/make_command.dart'; import 'package:mineral/src/commands/make_event.dart'; import 'package:mineral/src/commands/make_module.dart'; import 'package:mineral/src/commands/make_store.dart'; -import 'package:mineral/src/internal/entities/cli_manager.dart'; -import 'package:mineral/src/internal/entities/command_manager.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/cli_manager.dart'; +import 'package:mineral/src/internal/managers/command_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/module_manager.dart'; +import 'package:mineral/src/internal/managers/module_manager.dart'; +import 'package:mineral/src/internal/managers/reporter_manager.dart'; +import 'package:mineral/src/internal/managers/store_manager.dart'; import 'package:mineral/src/internal/websockets/sharding/shard_manager.dart'; - -import 'entities/store_manager.dart'; +import 'package:path/path.dart'; class Kernel { EventManager events = EventManager(); @@ -28,7 +31,9 @@ class Kernel { ioc.bind(namespace: ioc.services.store, service: stores); ioc.bind(namespace: ioc.services.modules, service: modules); ioc.bind(namespace: ioc.services.cli, service: cli); + } + void loadConsole () { cli.add(MakeCommand()); cli.add(MakeEvent()); cli.add(MakeModule()); @@ -43,6 +48,17 @@ class Kernel { http.defineHeader(header: 'Content-Type', value: 'application/json'); ioc.bind(namespace: ioc.services.http, service: http); + String? report = environment.get('REPORTER'); + if (report != null) { + ReporterManager reporter = ReporterManager(Directory(join(Directory.current.path, 'logs'))); + reporter.reportLevel = report; + + ioc.bind( + namespace: ioc.services.reporter, + service: reporter + ); + } + String? token = environment.get('APP_TOKEN'); if (token == null) { throw TokenException( diff --git a/lib/src/internal/entities/cli_manager.dart b/lib/src/internal/managers/cli_manager.dart similarity index 100% rename from lib/src/internal/entities/cli_manager.dart rename to lib/src/internal/managers/cli_manager.dart diff --git a/lib/src/internal/entities/command_manager.dart b/lib/src/internal/managers/command_manager.dart similarity index 96% rename from lib/src/internal/entities/command_manager.dart rename to lib/src/internal/managers/command_manager.dart index 9d608a161..2a75ca119 100644 --- a/lib/src/internal/entities/command_manager.dart +++ b/lib/src/internal/managers/command_manager.dart @@ -1,7 +1,8 @@ import 'dart:mirrors'; import 'package:mineral/api.dart'; -import 'package:mineral/src/internal/entities/store_manager.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/internal/managers/store_manager.dart'; class CommandManager { final Map _commands = {}; @@ -62,7 +63,7 @@ class CommandManager { if (reflectee is CommandGroup) { SlashCommand group = SlashCommand(name: '', description: '', scope: '', options: []) ..type = 2 - ..name = reflectee.name + ..name = reflectee.name.toLowerCase() ..description = reflectee.description; command.groups.add(group); @@ -70,7 +71,7 @@ class CommandManager { if (reflectee is Command) { command - ..name = reflectee.name + ..name = reflectee.name.toLowerCase() ..description = reflectee.description ..scope = reflectee.scope; @@ -135,6 +136,7 @@ class CommandManager { class MineralCommand { late MineralClient client; late StoreManager stores; + late Environment environment; } class Command { diff --git a/lib/src/internal/entities/event_manager.dart b/lib/src/internal/managers/event_manager.dart similarity index 74% rename from lib/src/internal/entities/event_manager.dart rename to lib/src/internal/managers/event_manager.dart index 3da661513..3cae1e934 100644 --- a/lib/src/internal/entities/event_manager.dart +++ b/lib/src/internal/managers/event_manager.dart @@ -1,7 +1,9 @@ import 'dart:mirrors'; import 'package:mineral/api.dart'; -import 'package:mineral/src/internal/entities/store_manager.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/api/managers/voice_manager.dart'; +import 'package:mineral/src/internal/managers/store_manager.dart'; class EventManager { final Map>> _events = {}; @@ -57,6 +59,7 @@ class Event { abstract class MineralEvent { late StoreManager stores; late MineralClient client; + late Environment environment; } enum Events { @@ -83,14 +86,28 @@ enum Events { channelUpdate('update::channel', { 'before': Channel, 'after': Channel }), channelDelete('delete::channel', { 'channel': Channel }), - memberUpdate('update::member', { 'member': GuildMember }), + memberJoin('join::member', { 'member': GuildMember }), + memberUpdate('update::member', { 'before': GuildMember, 'after': GuildMember }), + memberLeave('leave::member', { 'member': GuildMember }), memberRolesUpdate('update::roles-member', { 'before': Role, 'after': Role }), acceptRules('accept::rules', { 'member': GuildMember }), commandCreate('create::commandInteraction', { 'interaction': CommandInteraction }), buttonCreate('create::buttonInteraction', { 'interaction': ButtonInteraction }), modalCreate('create::modalInteraction', { 'interaction': ModalInteraction }), - selectMenuCreate('create::selectMenuInteraction', { 'interaction': SelectMenuInteraction }); + selectMenuCreate('create::selectMenuInteraction', { 'interaction': SelectMenuInteraction }), + + voiceStateUpdate('update::voice', { 'before': VoiceManager, 'after': VoiceManager }), + voiceConnect('connect::voice', { 'member': GuildMember, 'before': 'VoiceChannel?', 'after': VoiceChannel }), + voiceDisconnect('disconnect::voice', { 'member': GuildMember, 'channel': VoiceChannel }), + memberMuted('mute::voice', { 'member': GuildMember }), + memberUnMuted('unmute::voice', { 'member': GuildMember }), + memberDeaf('deaf::voice', { 'member': GuildMember }), + memberUnDeaf('undeaf::voice', { 'member': GuildMember }), + memberSelfMuted('self::mute::voice', { 'member': GuildMember }), + memberSelfUnMuted('self::unmute::voice', { 'member': GuildMember }), + memberSelfDeaf('self::deaf::voice', { 'member': GuildMember }), + memberSelfUnDeaf('self::undeaf::voice', { 'member': GuildMember }); final String event; final Map params; diff --git a/lib/src/internal/entities/module_manager.dart b/lib/src/internal/managers/module_manager.dart similarity index 94% rename from lib/src/internal/entities/module_manager.dart rename to lib/src/internal/managers/module_manager.dart index b0152f523..01f86b723 100644 --- a/lib/src/internal/entities/module_manager.dart +++ b/lib/src/internal/managers/module_manager.dart @@ -3,8 +3,8 @@ import 'dart:mirrors'; import 'package:mineral/console.dart'; import 'package:mineral/core.dart'; import 'package:mineral/src/exceptions/already_exist.dart'; -import 'package:mineral/src/internal/entities/command_manager.dart'; -import 'package:mineral/src/internal/entities/store_manager.dart'; +import 'package:mineral/src/internal/managers/command_manager.dart'; +import 'package:mineral/src/internal/managers/store_manager.dart'; import 'event_manager.dart'; diff --git a/lib/src/internal/managers/reporter_manager.dart b/lib/src/internal/managers/reporter_manager.dart new file mode 100644 index 000000000..00ef2c9fa --- /dev/null +++ b/lib/src/internal/managers/reporter_manager.dart @@ -0,0 +1,26 @@ +import 'dart:io'; + +import 'package:path/path.dart'; + +class ReporterManager { + late String reportLevel = 'info'; + final Directory _reportDirectory; + late File currentFile; + + ReporterManager(this._reportDirectory); + + void write (String message) { + final DateTime now = DateTime.now(); + final String filename = 'log-${now.day}-${now.month}-${now.year}.txt'; + + final file = File(join(_reportDirectory.path, filename)); + final bool fileExist = file.existsSync(); + + if (!fileExist) { + file.createSync(recursive: true); + } + + int timestamp = DateTime.now().millisecondsSinceEpoch; + file.writeAsStringSync('[$timestamp] $message\n', mode: FileMode.writeOnlyAppend); + } +} diff --git a/lib/src/internal/entities/store_manager.dart b/lib/src/internal/managers/store_manager.dart similarity index 85% rename from lib/src/internal/entities/store_manager.dart rename to lib/src/internal/managers/store_manager.dart index 5e7b310bc..4f2ba5f20 100644 --- a/lib/src/internal/entities/store_manager.dart +++ b/lib/src/internal/managers/store_manager.dart @@ -1,5 +1,6 @@ import 'dart:mirrors'; +import 'package:mineral/core.dart'; import 'package:mineral/src/exceptions/already_exist.dart'; import 'package:mineral/src/exceptions/not_exist.dart'; @@ -12,6 +13,8 @@ class StoreManager { throw AlreadyExist(cause: "A store named $name already exists."); } + store.environment = ioc.singleton(ioc.services.environment); + _stores[name] = store; return this; } @@ -33,5 +36,6 @@ class Store { } abstract class MineralStore { + late Environment environment; late T state; } diff --git a/lib/src/internal/websockets/packets/auto_moderation_rule_create.dart b/lib/src/internal/websockets/packets/auto_moderation_rule_create.dart index a308b3774..a5a477ffc 100644 --- a/lib/src/internal/websockets/packets/auto_moderation_rule_create.dart +++ b/lib/src/internal/websockets/packets/auto_moderation_rule_create.dart @@ -1,6 +1,6 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/auto_moderation_rule_delete.dart b/lib/src/internal/websockets/packets/auto_moderation_rule_delete.dart index 99464184a..710796ac7 100644 --- a/lib/src/internal/websockets/packets/auto_moderation_rule_delete.dart +++ b/lib/src/internal/websockets/packets/auto_moderation_rule_delete.dart @@ -1,6 +1,6 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/auto_moderation_rule_update.dart b/lib/src/internal/websockets/packets/auto_moderation_rule_update.dart index 99d40d204..bb408ae9e 100644 --- a/lib/src/internal/websockets/packets/auto_moderation_rule_update.dart +++ b/lib/src/internal/websockets/packets/auto_moderation_rule_update.dart @@ -1,6 +1,6 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/channel_create.dart b/lib/src/internal/websockets/packets/channel_create.dart index 247eaee49..da9b361a0 100644 --- a/lib/src/internal/websockets/packets/channel_create.dart +++ b/lib/src/internal/websockets/packets/channel_create.dart @@ -1,10 +1,12 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; import 'package:mineral/src/api/channels/channel.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; +import 'package:collection/collection.dart'; + class ChannelCreate implements WebsocketPacket { @override PacketType packetType = PacketType.channelCreate; @@ -37,8 +39,9 @@ class ChannelCreate implements WebsocketPacket { } Channel? _dispatch (Guild? guild, dynamic payload) { - if (channels.containsKey(payload['type'])) { - Channel Function(dynamic payload) item = channels[payload['type']] as Channel Function(dynamic payload); + final ChannelType? type = ChannelType.values.firstWhereOrNull((element) => element.value == payload['type']); + if (type != null && channels.containsKey(type)) { + Channel Function(dynamic payload) item = channels[type] as Channel Function(dynamic payload); return item(payload); } return null; diff --git a/lib/src/internal/websockets/packets/channel_delete.dart b/lib/src/internal/websockets/packets/channel_delete.dart index 13cf6386e..7a407e2ff 100644 --- a/lib/src/internal/websockets/packets/channel_delete.dart +++ b/lib/src/internal/websockets/packets/channel_delete.dart @@ -1,6 +1,6 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/channel_update.dart b/lib/src/internal/websockets/packets/channel_update.dart index 456d87571..8fa39bac3 100644 --- a/lib/src/internal/websockets/packets/channel_update.dart +++ b/lib/src/internal/websockets/packets/channel_update.dart @@ -1,7 +1,7 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; import 'package:mineral/src/api/channels/channel.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/guild_create.dart b/lib/src/internal/websockets/packets/guild_create.dart index 3faedd742..7c3a17e9d 100644 --- a/lib/src/internal/websockets/packets/guild_create.dart +++ b/lib/src/internal/websockets/packets/guild_create.dart @@ -4,16 +4,16 @@ import 'package:http/http.dart'; import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; import 'package:mineral/src/api/channels/channel.dart'; -import 'package:mineral/src/api/guilds/guild_role_manager.dart'; -import 'package:mineral/src/api/guilds/guild_scheduled_event.dart'; +import 'package:mineral/src/api/managers/guild_role_manager.dart'; import 'package:mineral/src/api/managers/channel_manager.dart'; import 'package:mineral/src/api/managers/emoji_manager.dart'; 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/voice_manager.dart'; import 'package:mineral/src/api/managers/webhook_manager.dart'; -import 'package:mineral/src/internal/entities/command_manager.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/command_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; @@ -33,6 +33,13 @@ class GuildCreate implements WebsocketPacket { roleManager.cache.putIfAbsent(role.id, () => role); } + Map voices = {}; + for(dynamic voiceMember in websocketResponse.payload['voice_states']) { + final VoiceManager voiceManager = VoiceManager.from(voiceMember, null); + voices.putIfAbsent(voiceMember['user_id'], () => voiceManager); + voices.putIfAbsent(voiceMember['channel_id'], () => voiceManager); + } + MemberManager memberManager = MemberManager(guildId: websocketResponse.payload['id']); for (dynamic member in websocketResponse.payload['members']) { User user = User.from(member['user']); @@ -41,7 +48,18 @@ class GuildCreate implements WebsocketPacket { roles: roleManager, user: user, member: member, - guildId: websocketResponse.payload['id'] + guildId: websocketResponse.payload['id'], + voice: voices.containsKey(user.id) + ? voices.get(user.id)! + : VoiceManager( + isMute: member['mute'], + isDeaf: member['deaf'], + isSelfMute: false, + isSelfDeaf: false, + hasVideo: false, + hasStream: false, + channel: null + ) ); memberManager.cache.putIfAbsent(guildMember.user.id, () => guildMember); @@ -100,7 +118,6 @@ class GuildCreate implements WebsocketPacket { guild.members.cache.forEach((Snowflake id, GuildMember member) { member.guild = guild; member.voice.member = member; - member.voice.channel = guild.channels.cache.get(member.voice.channelId); }); // Assign guild channels @@ -110,6 +127,10 @@ class GuildCreate implements WebsocketPacket { channel.guild = guild; channel.parent = channel.parentId != null ? guild.channels.cache.get(channel.parentId) : null; channel.webhooks.guild = guild; + + if(voices.containsKey(id)) { + voices.get(id)!.channel = channel as VoiceChannel; + } }); moderationManager.guild = guild; diff --git a/lib/src/internal/websockets/packets/guild_integrations_update.dart b/lib/src/internal/websockets/packets/guild_integrations_update.dart index 2586d3849..a04825b77 100644 --- a/lib/src/internal/websockets/packets/guild_integrations_update.dart +++ b/lib/src/internal/websockets/packets/guild_integrations_update.dart @@ -1,6 +1,6 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/guild_member_add.dart b/lib/src/internal/websockets/packets/guild_member_add.dart new file mode 100644 index 000000000..88c1a77d1 --- /dev/null +++ b/lib/src/internal/websockets/packets/guild_member_add.dart @@ -0,0 +1,36 @@ +import 'package:mineral/api.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/api/managers/voice_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; +import 'package:mineral/src/internal/websockets/websocket_packet.dart'; +import 'package:mineral/src/internal/websockets/websocket_response.dart'; + +class GuildMemberAdd implements WebsocketPacket { + @override + PacketType packetType = PacketType.memberAdd; + + @override + Future handle(WebsocketResponse websocketResponse) async { + EventManager manager = ioc.singleton(ioc.services.event); + MineralClient client = ioc.singleton(ioc.services.client); + + dynamic payload = websocketResponse.payload; + + Guild? guild = client.guilds.cache.get(payload['guild_id']); + + if(guild != null) { + User user = User.from(payload['user']); + GuildMember member = GuildMember.from( + user: user, + roles: guild.roles, + member: payload, + guildId: guild.id, + voice: VoiceManager(isMute: payload['mute'], isDeaf: payload['deaf'], isSelfMute: false, isSelfDeaf: false, hasVideo: false, hasStream: false, channel: null) + ); + member.guild = guild; + + guild.members.cache.putIfAbsent(member.user.id, () => member); + manager.emit(event: Events.memberJoin, params: [member]); + } + } +} diff --git a/lib/src/internal/websockets/packets/guild_member_remove.dart b/lib/src/internal/websockets/packets/guild_member_remove.dart new file mode 100644 index 000000000..da0bc3409 --- /dev/null +++ b/lib/src/internal/websockets/packets/guild_member_remove.dart @@ -0,0 +1,30 @@ +import 'package:mineral/api.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; +import 'package:mineral/src/internal/websockets/websocket_packet.dart'; +import 'package:mineral/src/internal/websockets/websocket_response.dart'; + +class GuildMemberRemove implements WebsocketPacket { + @override + PacketType packetType = PacketType.memberRemove; + + @override + Future handle(WebsocketResponse websocketResponse) async { + EventManager manager = ioc.singleton(ioc.services.event); + MineralClient client = ioc.singleton(ioc.services.client); + + dynamic payload = websocketResponse.payload; + + Guild? guild = client.guilds.cache.get(payload['guild_id']); + GuildMember? member = guild?.members.cache.get(payload['user']['id']); + + if(guild != null && member != null) { + manager.emit( + event: Events.memberLeave, + params: [member] + ); + + guild.members.cache.remove(member.user.id); + } + } +} diff --git a/lib/src/internal/websockets/packets/guild_member_update.dart b/lib/src/internal/websockets/packets/guild_member_update.dart index 2e85e70ee..4916a6439 100644 --- a/lib/src/internal/websockets/packets/guild_member_update.dart +++ b/lib/src/internal/websockets/packets/guild_member_update.dart @@ -1,6 +1,7 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/api/managers/voice_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; @@ -19,25 +20,29 @@ class GuildMemberUpdate implements WebsocketPacket { if (guild != null) { GuildMember? before = guild.members.cache.get(payload['user']['id']); + VoiceManager voice = before != null + ? before.voice + : VoiceManager(isMute: payload['mute'], isDeaf: payload['deaf'], isSelfMute: false, isSelfDeaf: false, hasVideo: false, hasStream: false, channel: null); + User user = User.from(payload['user']); - GuildMember after = GuildMember.from(user: user, roles: guild.roles, guildId: guild.id, member: payload); + GuildMember after = GuildMember.from( + user: user, + roles: guild.roles, + member: payload, + guildId: guild.id, + voice: voice + ); after.guild = guild; - after.voice.member = after; - after.voice.channel = guild.channels.cache.get(after.voice.channelId); + after.voice.member ??= after; + //after.voice.member = after; + //after.voice.channel = guild.channels.cache.get(after.voice.channelId); manager.emit( event: Events.memberUpdate, params: [before, after] ); - if (before?.isPending != after.isPending) { - manager.emit( - event: Events.acceptRules, - params: [after] - ); - } - if (before?.roles.cache.length != after.roles.cache.length) { manager.emit( event: Events.memberRolesUpdate, diff --git a/lib/src/internal/websockets/packets/guild_scheduled_event_create.dart b/lib/src/internal/websockets/packets/guild_scheduled_event_create.dart index 7bf8d0c6a..91a972e6f 100644 --- a/lib/src/internal/websockets/packets/guild_scheduled_event_create.dart +++ b/lib/src/internal/websockets/packets/guild_scheduled_event_create.dart @@ -1,6 +1,6 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/guild_scheduled_event_delete.dart b/lib/src/internal/websockets/packets/guild_scheduled_event_delete.dart index d4890c1fc..f4a9a906c 100644 --- a/lib/src/internal/websockets/packets/guild_scheduled_event_delete.dart +++ b/lib/src/internal/websockets/packets/guild_scheduled_event_delete.dart @@ -1,8 +1,6 @@ -import 'dart:convert'; - import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/guild_scheduled_event_update.dart b/lib/src/internal/websockets/packets/guild_scheduled_event_update.dart index 7f88a8168..2cd250b42 100644 --- a/lib/src/internal/websockets/packets/guild_scheduled_event_update.dart +++ b/lib/src/internal/websockets/packets/guild_scheduled_event_update.dart @@ -1,6 +1,6 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/guild_scheduled_event_user_add.dart b/lib/src/internal/websockets/packets/guild_scheduled_event_user_add.dart index 3347223de..f5a0e11d9 100644 --- a/lib/src/internal/websockets/packets/guild_scheduled_event_user_add.dart +++ b/lib/src/internal/websockets/packets/guild_scheduled_event_user_add.dart @@ -1,6 +1,6 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/guild_scheduled_event_user_remove.dart b/lib/src/internal/websockets/packets/guild_scheduled_event_user_remove.dart index 92b22fb3c..8af9423f2 100644 --- a/lib/src/internal/websockets/packets/guild_scheduled_event_user_remove.dart +++ b/lib/src/internal/websockets/packets/guild_scheduled_event_user_remove.dart @@ -1,6 +1,6 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/guild_update.dart b/lib/src/internal/websockets/packets/guild_update.dart index 8b9e39e0c..372b76af1 100644 --- a/lib/src/internal/websockets/packets/guild_update.dart +++ b/lib/src/internal/websockets/packets/guild_update.dart @@ -1,13 +1,13 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/api/guilds/guild_role_manager.dart'; +import 'package:mineral/src/api/managers/guild_role_manager.dart'; import 'package:mineral/src/api/managers/channel_manager.dart'; import 'package:mineral/src/api/managers/emoji_manager.dart'; 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/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/interaction_create.dart b/lib/src/internal/websockets/packets/interaction_create.dart index 96725c982..7b930d7b8 100644 --- a/lib/src/internal/websockets/packets/interaction_create.dart +++ b/lib/src/internal/websockets/packets/interaction_create.dart @@ -6,8 +6,8 @@ import 'package:mineral/core.dart'; import 'package:mineral/src/api/components/component.dart'; import 'package:mineral/src/api/interactions/button_interaction.dart'; import 'package:mineral/src/api/interactions/select_menu_interaction.dart'; -import 'package:mineral/src/internal/entities/command_manager.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/command_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/member_join_request.dart b/lib/src/internal/websockets/packets/member_join_request.dart new file mode 100644 index 000000000..0276a2b7c --- /dev/null +++ b/lib/src/internal/websockets/packets/member_join_request.dart @@ -0,0 +1,30 @@ +import 'package:mineral/api.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; +import 'package:mineral/src/internal/websockets/websocket_packet.dart'; +import 'package:mineral/src/internal/websockets/websocket_response.dart'; + +class MemberJoinRequest implements WebsocketPacket { + @override + PacketType packetType = PacketType.memberJoinRequest; + + @override + Future handle(WebsocketResponse websocketResponse) async { + EventManager manager = ioc.singleton(ioc.services.event); + MineralClient client = ioc.singleton(ioc.services.client); + + dynamic payload = websocketResponse.payload; + + Guild? guild = client.guilds.cache.get(payload['request']['guild_id']); + + if(payload['status'] == 'APPROVED' && guild != null) { + GuildMember? member = guild.members.cache.get(payload['request']['user_id']); + + manager.emit( + event: Events.acceptRules, + params: [member] + ); + } + + } +} diff --git a/lib/src/internal/websockets/packets/message_create.dart b/lib/src/internal/websockets/packets/message_create.dart index aa1be6deb..b6890e257 100644 --- a/lib/src/internal/websockets/packets/message_create.dart +++ b/lib/src/internal/websockets/packets/message_create.dart @@ -1,6 +1,6 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/message_delete.dart b/lib/src/internal/websockets/packets/message_delete.dart index e86cc4f12..32c6eecc7 100644 --- a/lib/src/internal/websockets/packets/message_delete.dart +++ b/lib/src/internal/websockets/packets/message_delete.dart @@ -3,7 +3,7 @@ import 'dart:convert'; import 'package:http/http.dart'; import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/message_update.dart b/lib/src/internal/websockets/packets/message_update.dart index 3b1f30b2a..2c531db12 100644 --- a/lib/src/internal/websockets/packets/message_update.dart +++ b/lib/src/internal/websockets/packets/message_update.dart @@ -1,6 +1,6 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/presence_update.dart b/lib/src/internal/websockets/packets/presence_update.dart index 364cc377f..dddc167a9 100644 --- a/lib/src/internal/websockets/packets/presence_update.dart +++ b/lib/src/internal/websockets/packets/presence_update.dart @@ -1,6 +1,6 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/ready.dart b/lib/src/internal/websockets/packets/ready.dart index 9e25e2d92..d5db1dd05 100644 --- a/lib/src/internal/websockets/packets/ready.dart +++ b/lib/src/internal/websockets/packets/ready.dart @@ -1,7 +1,7 @@ import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/command_manager.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/command_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/sharding/shard.dart'; import 'package:mineral/src/internal/websockets/sharding/shard_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; @@ -48,8 +48,10 @@ class Ready implements WebsocketPacket { Map>> events = manager.getRegisteredEvents(); events.forEach((_, events) { for (Map event in events) { - event['mineralEvent'].client = client; - event['mineralEvent'].stores = ioc.singleton(ioc.services.store); + event['mineralEvent'] + ..client = client + ..stores = ioc.singleton(ioc.services.store) + ..environment = ioc.singleton(ioc.services.environment); } }); } @@ -57,10 +59,10 @@ class Ready implements WebsocketPacket { void infuseClientIntoCommands ({required CommandManager manager, required MineralClient client}) { Map commands = manager.getHandlers(); commands.forEach((_, handler) { - MineralCommand command = handler['commandClass']; - - command.client = client; - command.stores = ioc.singleton(ioc.services.store); + handler['commandClass'] + ..client = client + ..stores = ioc.singleton(ioc.services.store) + ..environment = ioc.singleton(ioc.services.environment); }); } } diff --git a/lib/src/internal/websockets/packets/resumed.dart b/lib/src/internal/websockets/packets/resumed.dart index 1b6d63a1a..28a07bac2 100644 --- a/lib/src/internal/websockets/packets/resumed.dart +++ b/lib/src/internal/websockets/packets/resumed.dart @@ -1,5 +1,4 @@ import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/packets/voice_state_update.dart b/lib/src/internal/websockets/packets/voice_state_update.dart new file mode 100644 index 000000000..c32edbba0 --- /dev/null +++ b/lib/src/internal/websockets/packets/voice_state_update.dart @@ -0,0 +1,128 @@ +import 'package:mineral/api.dart'; +import 'package:mineral/core.dart'; +import 'package:mineral/src/api/managers/voice_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; +import 'package:mineral/src/internal/websockets/websocket_packet.dart'; +import 'package:mineral/src/internal/websockets/websocket_response.dart'; + +class VoiceStateUpdate implements WebsocketPacket { + @override + PacketType packetType = PacketType.voiceStateUpdate; + + @override + Future handle(WebsocketResponse websocketResponse) async { + EventManager manager = ioc.singleton(ioc.services.event); + MineralClient client = ioc.singleton(ioc.services.client); + + dynamic payload = websocketResponse.payload; + + Guild? guild = client.guilds.cache.get(payload['guild_id']); + GuildMember? member = guild?.members.cache.get(payload['user_id']); + VoiceChannel? voiceChannel = guild?.channels.cache.get(payload['channel_id']); + if (guild != null && member != null) { + VoiceManager before = member.voice; + VoiceManager after = VoiceManager.from(payload, voiceChannel); + + member.voice = after; + after.member = member; + + manager.emit( + event: Events.voiceStateUpdate, + params: [before, after] + ); + + //User move + if(before.channel != null && after.channel != null && before.channel != after.channel) { + _emitEvent(manager, Events.voiceDisconnect, [member, before.channel], before.channel!.id); + _emitEvent(manager, Events.voiceConnect, [member, before.channel, after.channel], after.channel!.id); + } + + //User connect + if(before.channel == null && after.channel != null) { + _emitEvent(manager, Events.voiceConnect, [member, before.channel, after.channel], after.channel!.id); + } + + //User leave + if(before.channel != null && after.channel == null) { + _emitEvent(manager, Events.voiceDisconnect, [member, before.channel], before.channel!.id); + } + + //User muted + if(!before.isMute && after.isMute) { + manager.emit( + event: Events.memberMuted, + params: [member] + ); + } + + //User unmute + if(before.isMute && !after.isMute) { + manager.emit( + event: Events.memberUnMuted, + params: [member] + ); + } + + //User undeaf + if(before.isDeaf && !after.isDeaf) { + manager.emit( + event: Events.memberUnDeaf, + params: [member] + ); + } + + //User deaf + if(!before.isDeaf && after.isDeaf) { + manager.emit( + event: Events.memberDeaf, + params: [member] + ); + } + + //User selfUnMute + if(before.isSelfMute && !after.isSelfMute) { + manager.emit( + event: Events.memberSelfUnMuted, + params: [member] + ); + } + + //User selfMute + if(!before.isSelfMute && after.isSelfMute) { + manager.emit( + event: Events.memberSelfMuted, + params: [member] + ); + } + + //User selfUnDeaf + if(before.isSelfDeaf && !after.isSelfDeaf) { + manager.emit( + event: Events.memberSelfUnDeaf, + params: [member] + ); + } + + //User selfDeaf + if(!before.isSelfDeaf && after.isSelfDeaf) { + manager.emit( + event: Events.memberSelfDeaf, + params: [member] + ); + } + } + } + + _emitEvent(EventManager manager, Events event, dynamic params, Snowflake customId) { + manager.emit( + event: event, + params: params, + ); + + manager.emit( + event: event, + params: params, + customId: customId + ); + } +} diff --git a/lib/src/internal/websockets/packets/webhook_update.dart b/lib/src/internal/websockets/packets/webhook_update.dart index 16920c384..0ad130904 100644 --- a/lib/src/internal/websockets/packets/webhook_update.dart +++ b/lib/src/internal/websockets/packets/webhook_update.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:mineral/api.dart'; import 'package:mineral/core.dart'; -import 'package:mineral/src/internal/entities/event_manager.dart'; +import 'package:mineral/src/internal/managers/event_manager.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; diff --git a/lib/src/internal/websockets/websocket_dispatcher.dart b/lib/src/internal/websockets/websocket_dispatcher.dart index aa91dbf35..452e50209 100644 --- a/lib/src/internal/websockets/websocket_dispatcher.dart +++ b/lib/src/internal/websockets/websocket_dispatcher.dart @@ -7,6 +7,8 @@ import 'package:mineral/src/internal/websockets/packets/channel_delete.dart'; import 'package:mineral/src/internal/websockets/packets/channel_update.dart'; import 'package:mineral/src/internal/websockets/packets/guild_create.dart'; import 'package:mineral/src/internal/websockets/packets/guild_integrations_update.dart'; +import 'package:mineral/src/internal/websockets/packets/guild_member_add.dart'; +import 'package:mineral/src/internal/websockets/packets/guild_member_remove.dart'; import 'package:mineral/src/internal/websockets/packets/guild_member_update.dart'; import 'package:mineral/src/internal/websockets/packets/guild_scheduled_event_create.dart'; import 'package:mineral/src/internal/websockets/packets/guild_scheduled_event_delete.dart'; @@ -15,12 +17,14 @@ import 'package:mineral/src/internal/websockets/packets/guild_scheduled_event_us import 'package:mineral/src/internal/websockets/packets/guild_scheduled_event_user_remove.dart'; import 'package:mineral/src/internal/websockets/packets/guild_update.dart'; import 'package:mineral/src/internal/websockets/packets/interaction_create.dart'; +import 'package:mineral/src/internal/websockets/packets/member_join_request.dart'; import 'package:mineral/src/internal/websockets/packets/message_create.dart'; import 'package:mineral/src/internal/websockets/packets/message_delete.dart'; import 'package:mineral/src/internal/websockets/packets/message_update.dart'; import 'package:mineral/src/internal/websockets/packets/presence_update.dart'; import 'package:mineral/src/internal/websockets/packets/ready.dart'; import 'package:mineral/src/internal/websockets/packets/resumed.dart'; +import 'package:mineral/src/internal/websockets/packets/voice_state_update.dart'; import 'package:mineral/src/internal/websockets/packets/webhook_update.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; @@ -43,6 +47,9 @@ class WebsocketDispatcher { register(PacketType.channelDelete, ChannelDelete()); register(PacketType.channelUpdate, ChannelUpdate()); register(PacketType.memberUpdate, GuildMemberUpdate()); + register(PacketType.memberRemove, GuildMemberRemove()); + register(PacketType.memberAdd, GuildMemberAdd()); + register(PacketType.memberJoinRequest, MemberJoinRequest()); register(PacketType.interactionCreate, InteractionCreate()); register(PacketType.autoModerationRuleCreate, AutoModerationRuleCreate()); register(PacketType.autoModerationRuleDelete, AutoModerationRuleDelete()); @@ -53,6 +60,7 @@ class WebsocketDispatcher { register(PacketType.guildScheduledEventUserRemove, GuildScheduledEventUserRemove()); register(PacketType.webhookUpdate, WebhookUpdate()); register(PacketType.guildIntegrationsUpdate, GuildIntegrationsUpdate()); + register(PacketType.voiceStateUpdate, VoiceStateUpdate()); } void register (PacketType type, WebsocketPacket packet) {