diff --git a/lib/core/api.dart b/lib/core/api.dart index fd46f76d..8e4b060e 100644 --- a/lib/core/api.dart +++ b/lib/core/api.dart @@ -50,5 +50,6 @@ export '../src/api/users/user_decoration.dart' show UserDecoration; export '../src/api/users/user_flags/user_flag_contract.dart' show UserFlagContract; export '../src/api/client/client_scope.dart' show ClientScope; export '../src/api/client/client_permission.dart' show ClientPermission; +export '../src/api/guilds/activities/activity_type.dart' show ActivityType; typedef Snowflake = String; diff --git a/lib/core/builders.dart b/lib/core/builders.dart index 1e1ca515..867c9ebf 100644 --- a/lib/core/builders.dart +++ b/lib/core/builders.dart @@ -23,4 +23,6 @@ export '../src/api/builders/menus/mentionable_select_menu_builder.dart' show Men export '../src/api/builders/modal/text_builder.dart' show TextBuilder; export '../src/api/builders/modal/paragraph_builder.dart' show ParagraphBuilder; -export '../src/api/builders/attachment_builder.dart' show AttachmentBuilder; \ No newline at end of file +export '../src/api/builders/attachment_builder.dart' show AttachmentBuilder; +export '../src/api/builders/markdown/markdown.dart' show Md; +export '../src/api/builders/markdown/timestamp_style.dart' show TimestampStyle; \ No newline at end of file diff --git a/lib/src/api/builders/markdown/markdown.dart b/lib/src/api/builders/markdown/markdown.dart new file mode 100644 index 00000000..cff8bf6b --- /dev/null +++ b/lib/src/api/builders/markdown/markdown.dart @@ -0,0 +1,21 @@ +import 'package:mineral/core/builders.dart'; + +class Md { + Md._(); + + static String bold (dynamic value) => '**$value**'; + + static String underline (dynamic value) => '__${value}__'; + + static String strikethrough (dynamic value) => '~~$value~~'; + + static String code (String value) => '```$value```'; + + static String italic (String value) => '*$value*'; + + static String spoil (dynamic value) => '||$value||'; + + static String timestamp (DateTime value, { TimestampStyle? style }) => style != null + ? '' + : ''; +} \ No newline at end of file diff --git a/lib/src/api/builders/markdown/timestamp_style.dart b/lib/src/api/builders/markdown/timestamp_style.dart new file mode 100644 index 00000000..214323ac --- /dev/null +++ b/lib/src/api/builders/markdown/timestamp_style.dart @@ -0,0 +1,12 @@ +enum TimestampStyle { + shortTime('t'), + longTime('T'), + shortDate('d'), + longDate('D'), + shortDateTime('f*'), + longDateTime('F'), + relativeTime('R'); + + final String value; + const TimestampStyle(this.value); +} \ No newline at end of file diff --git a/lib/src/api/emoji.dart b/lib/src/api/emoji.dart index dbbdc423..9bbb9430 100644 --- a/lib/src/api/emoji.dart +++ b/lib/src/api/emoji.dart @@ -8,7 +8,7 @@ import 'package:mineral_ioc/ioc.dart'; class PartialEmoji { final Snowflake _id; - String _label; + final String _label; final bool _animated; PartialEmoji(this._id, this._label, this._animated); @@ -59,14 +59,10 @@ class Emoji extends PartialEmoji { /// } /// ``` Future setLabel (String label, { String? reason }) async { - Response response = await ioc.use().patch(url: "/guilds/${manager.guild.id}/emojis/$id") + await ioc.use().patch(url: "/guilds/${manager.guild.id}/emojis/$id") .payload({ 'name': label }) .auditLog(reason) .build(); - - if (response.statusCode == 200) { - _label = label; - } } Future setAllowedRoles (List roles, { String? reason }) async { diff --git a/lib/src/api/guilds/activities/activity_type.dart b/lib/src/api/guilds/activities/activity_type.dart new file mode 100644 index 00000000..3b638007 --- /dev/null +++ b/lib/src/api/guilds/activities/activity_type.dart @@ -0,0 +1,11 @@ +enum ActivityType { + game(0), + streaming(1), + listening(2), + watching(3), + custom(4), + competing(5); + + final int value; + const ActivityType(this.value); +} \ No newline at end of file diff --git a/lib/src/api/guilds/activities/assets_activity.dart b/lib/src/api/guilds/activities/assets_activity.dart new file mode 100644 index 00000000..237841d3 --- /dev/null +++ b/lib/src/api/guilds/activities/assets_activity.dart @@ -0,0 +1,15 @@ +import 'package:mineral/core/api.dart'; + +class AssetsActivity { + final ImageFormater? _smallImage; + final String? _smallText; + final ImageFormater? _largeImage; + final String? _largeText; + + AssetsActivity(this._smallImage, this._smallText, this._largeImage, this._largeText); + + ImageFormater? get smallImage => _smallImage; + String? get smallText => _smallText; + ImageFormater? get largeImage => _largeImage; + String? get largeText => _largeText; +} \ No newline at end of file diff --git a/lib/src/api/guilds/activities/custom_activity.dart b/lib/src/api/guilds/activities/custom_activity.dart new file mode 100644 index 00000000..a4dcd41e --- /dev/null +++ b/lib/src/api/guilds/activities/custom_activity.dart @@ -0,0 +1,42 @@ +import 'package:mineral/core/api.dart'; +import 'package:mineral/src/api/emoji.dart'; +import 'package:mineral/src/api/guilds/activities/guild_member_activity.dart'; +import 'package:mineral/src/api/guilds/activities/secret_activity.dart'; + +class CustomActivity extends GuildMemberActivity { + final String _id; + final String? _state; + final PartialEmoji? _emoji; + final String _createdAt; + final SecretActivity _secrets; + + CustomActivity( + String name, + this._id, + this._state, + this._emoji, + this._createdAt, + this._secrets, + ): super(ActivityType.custom, name); + + String get id => _id; + String? get state => _state; + PartialEmoji? get emoji => _emoji; + String get createdAt => _createdAt; + SecretActivity get secrets => _secrets; + + factory CustomActivity.from(Snowflake guildId, dynamic payload) => CustomActivity( + payload['name'], + payload['id'], + payload['state'], + payload['emoji'] != null + ? PartialEmoji(payload['emoji']['id'], payload['emoji']['name'], payload['emoji']['animated']) + : null, + payload['created_at'], + SecretActivity( + payload['secrets']?['join'], + payload['secrets']?['spectate'], + payload['secrets']?['match'], + ) + ); +} \ No newline at end of file diff --git a/lib/src/api/guilds/activities/game_activity.dart b/lib/src/api/guilds/activities/game_activity.dart new file mode 100644 index 00000000..7bc01f30 --- /dev/null +++ b/lib/src/api/guilds/activities/game_activity.dart @@ -0,0 +1,75 @@ +import 'package:mineral/core/api.dart'; +import 'package:mineral/src/api/guilds/activities/assets_activity.dart'; +import 'package:mineral/src/api/guilds/activities/guild_member_activity.dart'; +import 'package:mineral/src/api/guilds/activities/secret_activity.dart'; + +class GameActivity extends GuildMemberActivity { + final String _id; + final String? _state; + final int? _startingAt; + final int? _endAt; + final int _createdAt; + final String? _details; + final String? _applicationId; + final AssetsActivity _assets; + final SecretActivity _secrets; + + GameActivity( + String name, + this._id, + this._state, + this._startingAt, + this._endAt, + this._createdAt, + this._details, + this._applicationId, + this._assets, + this._secrets, + ): super(ActivityType.game, name); + + String get id => _id; + + String? get state => _state; + + int? get startingAt => _startingAt; + + DateTime? get endAt => _endAt != null + ? DateTime.fromMicrosecondsSinceEpoch(_endAt!) + : null; + + DateTime get createdAt => DateTime.fromMicrosecondsSinceEpoch(_createdAt); + + String? get details => _details; + + String? get applicationId => _applicationId; + + AssetsActivity get assets => _assets; + + SecretActivity get secrets => _secrets; + + factory GameActivity.from(Snowflake guildId, dynamic payload) => GameActivity( + payload['name'], + payload['id'], + payload['state'], + payload['timestamps']?['start'], + payload['timestamps']?['end'], + payload['created_at'], + payload['details'], + payload['application_id'], + AssetsActivity( + payload['assets']?['small_image'] != null + ? ImageFormater(payload['assets']['large_image'], 'app-assets/${payload['application_id']}') + : null, + payload['assets']?['small_text'], + payload['assets']?['large_image'] != null + ? ImageFormater(payload['assets']['large_image'], 'app-assets/${payload['application_id']}') + : null, + payload['assets']?['large_text'], + ), + SecretActivity( + payload['secrets']?['join'], + payload['secrets']?['spectate'], + payload['secrets']?['match'], + ) + ); +} \ No newline at end of file diff --git a/lib/src/api/guilds/activities/guild_member_activity.dart b/lib/src/api/guilds/activities/guild_member_activity.dart new file mode 100644 index 00000000..47d871dc --- /dev/null +++ b/lib/src/api/guilds/activities/guild_member_activity.dart @@ -0,0 +1,11 @@ +import 'package:mineral/core/api.dart'; + +class GuildMemberActivity { + final ActivityType _type; + final String _name; + + GuildMemberActivity(this._type, this._name); + + ActivityType get type => _type; + String get name => _name; +} \ No newline at end of file diff --git a/lib/src/api/guilds/activities/secret_activity.dart b/lib/src/api/guilds/activities/secret_activity.dart new file mode 100644 index 00000000..44d7c5c1 --- /dev/null +++ b/lib/src/api/guilds/activities/secret_activity.dart @@ -0,0 +1,11 @@ +class SecretActivity { + final String? _join; + final String? _spectate; + final String? _match; + + SecretActivity(this._join, this._spectate, this._match); + + String? get join => _join; + String? get spectate => _spectate; + String? get match => _match; +} \ No newline at end of file diff --git a/lib/src/api/guilds/activities/streaming_activity.dart b/lib/src/api/guilds/activities/streaming_activity.dart new file mode 100644 index 00000000..11ba2f55 --- /dev/null +++ b/lib/src/api/guilds/activities/streaming_activity.dart @@ -0,0 +1,26 @@ +import 'package:mineral/core/api.dart'; +import 'package:mineral/src/api/guilds/activities/guild_member_activity.dart'; + +class StreamingActivity extends GuildMemberActivity { + final String _state; + final String? _details; + final String _url; + + StreamingActivity( + String name, + this._state, + this._details, + this._url, + ): super(ActivityType.streaming, name); + + String get state => _state; + String? get details => _details; + String get url => _url; + + factory StreamingActivity.from(Snowflake guildId, dynamic payload) => StreamingActivity( + payload['name'], + payload['state'], + payload['details'], + payload['url'], + ); +} \ No newline at end of file diff --git a/lib/src/api/guilds/client_status_bucket.dart b/lib/src/api/guilds/client_status_bucket.dart new file mode 100644 index 00000000..574ef4a3 --- /dev/null +++ b/lib/src/api/guilds/client_status_bucket.dart @@ -0,0 +1,11 @@ +class ClientStatusBucket { + final String? _desktop; + final String? _web; + final String? _mobile; + + ClientStatusBucket(this._desktop, this._web, this._mobile); + + String? get desktop => _desktop; + String? get web => _web; + String? get mobile => _mobile; +} \ No newline at end of file diff --git a/lib/src/api/guilds/guild_member.dart b/lib/src/api/guilds/guild_member.dart index 73f5ab11..e1ea93aa 100644 --- a/lib/src/api/guilds/guild_member.dart +++ b/lib/src/api/guilds/guild_member.dart @@ -2,6 +2,7 @@ import 'package:http/http.dart'; import 'package:mineral/core.dart'; import 'package:mineral/core/api.dart'; import 'package:mineral/framework.dart'; +import 'package:mineral/src/api/guilds/guild_member_presence.dart'; import 'package:mineral/src/api/managers/guild_role_manager.dart'; import 'package:mineral_ioc/ioc.dart'; @@ -17,6 +18,7 @@ class GuildMember { MemberRoleManager _roles; VoiceManager voice; Guild _guild; + GuildMemberPresence? presence; GuildMember( this._user, @@ -30,6 +32,7 @@ class GuildMember { this._roles, this.voice, this._guild, + this.presence, ); Snowflake get id => _user.id; @@ -157,7 +160,7 @@ class GuildMember { @override String toString () => '<@${_nickname != null ? '!' : ''}${user.id}>'; - GuildMember clone () => GuildMember(user, nickname, avatar, joinedAt, premiumSince, permissions, pending, timeoutDuration, roles, voice, guild); + GuildMember clone () => GuildMember(user, nickname, avatar, joinedAt, premiumSince, permissions, pending, timeoutDuration, roles, voice, guild, presence); factory GuildMember.from({ required user, required GuildRoleManager roles, required Guild guild, dynamic member, required VoiceManager voice }) { MemberRoleManager memberRoleManager = MemberRoleManager(manager: roles, memberId: user.id); @@ -179,7 +182,8 @@ class GuildMember { member['communication_disabled_until'] != null ? DateTime.parse(member['communication_disabled_until']) : null, memberRoleManager, voice, - guild + guild, + null, ); } } diff --git a/lib/src/api/guilds/guild_member_presence.dart b/lib/src/api/guilds/guild_member_presence.dart new file mode 100644 index 00000000..560433b6 --- /dev/null +++ b/lib/src/api/guilds/guild_member_presence.dart @@ -0,0 +1,20 @@ +import 'package:mineral/core/api.dart'; +import 'package:mineral/src/api/guilds/activities/guild_member_activity.dart'; +import 'package:mineral/src/api/guilds/client_status_bucket.dart'; + +class GuildMemberPresence { + final Snowflake _guildId; + final String _status; + final String? _premiumSince; + final ClientStatusBucket _clientStatus; + final List _activities; + + GuildMemberPresence(this._guildId, this._status, this._premiumSince, this._clientStatus, this._activities); + + StatusType get status => StatusType.values.firstWhere((element) => element.value == _status); + DateTime? get premiumSince => _premiumSince != null + ? DateTime.parse(_premiumSince!) + : null; + ClientStatusBucket get clientStatus => _clientStatus; + List get activities => _activities; +} \ No newline at end of file diff --git a/lib/src/api/status.dart b/lib/src/api/status.dart index 0954cfde..fb2d63f7 100644 --- a/lib/src/api/status.dart +++ b/lib/src/api/status.dart @@ -7,11 +7,8 @@ enum StatusType { doNotDisturb('dnd'), offline('offline'); - final String _value; - const StatusType(this._value); - - @override - String toString () => _value; + final String value; + const StatusType(this.value); } class Status { diff --git a/lib/src/api/users/user.dart b/lib/src/api/users/user.dart index 728d387f..0e09549a 100644 --- a/lib/src/api/users/user.dart +++ b/lib/src/api/users/user.dart @@ -21,7 +21,6 @@ class User { UserDecoration _decoration; String _lang; PremiumType premiumType; - // late Status status; User( this._id, diff --git a/lib/src/internal/websockets/packets/guild_remove_packet.dart b/lib/src/internal/websockets/packets/guild_remove_packet.dart index 30f913b4..1b186b5f 100644 --- a/lib/src/internal/websockets/packets/guild_remove_packet.dart +++ b/lib/src/internal/websockets/packets/guild_remove_packet.dart @@ -11,9 +11,7 @@ class GuildRemovePacket with Container implements WebsocketPacket { EventService eventService = container.use(); MineralClient client = container.use(); - dynamic payload = websocketResponse.payload; - - Guild? guild = client.guilds.cache.getOrFail(payload['guild_id']); + Guild guild = client.guilds.cache.getOrFail(websocketResponse.payload['id']); eventService.controller.add(GuildDeleteEvent(guild)); client.guilds.cache.remove(guild.id); diff --git a/lib/src/internal/websockets/packets/presence_update.dart b/lib/src/internal/websockets/packets/presence_update.dart index 29ec8f90..1993430b 100644 --- a/lib/src/internal/websockets/packets/presence_update.dart +++ b/lib/src/internal/websockets/packets/presence_update.dart @@ -1,11 +1,18 @@ import 'package:mineral/core/api.dart'; import 'package:mineral/core/events.dart'; import 'package:mineral/framework.dart'; +import 'package:mineral/src/api/guilds/activities/game_activity.dart'; +import 'package:mineral/src/api/guilds/activities/guild_member_activity.dart'; +import 'package:mineral/src/api/guilds/activities/streaming_activity.dart'; +import 'package:mineral/src/api/guilds/client_status_bucket.dart'; +import 'package:mineral/src/api/guilds/guild_member_presence.dart'; import 'package:mineral/src/internal/mixins/container.dart'; import 'package:mineral/src/internal/services/event_service.dart'; import 'package:mineral/src/internal/websockets/websocket_packet.dart'; import 'package:mineral/src/internal/websockets/websocket_response.dart'; +import '../../../api/guilds/activities/custom_activity.dart'; + class PresenceUpdatePacket with Container implements WebsocketPacket { @override Future handle(WebsocketResponse websocketResponse) async { @@ -14,11 +21,44 @@ class PresenceUpdatePacket with Container implements WebsocketPacket { dynamic payload = websocketResponse.payload; - Guild? guild = client.guilds.cache.get(payload['guild_id']); - String userId = payload['user']['id']; + if (payload['guild_id'] == null) { + return; + } + + Guild? guild = client.guilds.cache.getOrFail(payload['guild_id']); + + GuildMember beforeMember = guild.members.cache.getOrFail(payload['user']['id']).clone(); + GuildMember afterMember = guild.members.cache.getOrFail(payload['user']['id']); + + final List activities = List.from(payload['activities']).map((activity) { + switch (activity['type']) { + case ActivityType.custom: + return CustomActivity.from(payload['guild_id'], activity); + case ActivityType.game: + return GameActivity.from(payload['guild_id'], activity); + case ActivityType.streaming: + return StreamingActivity.from(payload['guild_id'], activity); + default: + return GuildMemberActivity( + ActivityType.values.firstWhere((type) => type.value == activity['type']), + activity['name'] + ); + } + }).toList(); + + + afterMember.presence = GuildMemberPresence( + payload['guild_id'], + payload['status'], + payload['premium_since'], + ClientStatusBucket( + payload['client_status']?['desktop'], + payload['client_status']?['web'], + payload['client_status']?['mobile'], + ), + activities + ); - GuildMember? beforeMember = guild?.members.cache.get(userId)?.clone(); - GuildMember? afterMember = guild?.members.cache.get(userId); // afterMember?.user.status = Status.from(guild: guild!, payload: payload);