Skip to content

Commit

Permalink
Merge pull request #10 from mineral-dart/commands
Browse files Browse the repository at this point in the history
feat : Implement commands
  • Loading branch information
LeadcodeDev authored Jun 16, 2022
2 parents 4a6d82d + e53d040 commit 149fd72
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 25 deletions.
5 changes: 4 additions & 1 deletion lib/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ 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;
export 'src/api/message_embed.dart' show MessageEmbed, Footer, Image, Author, Field;
export 'src/api/color.dart' show Color;

export 'src/api/emoji.dart' show Emoji;
Expand All @@ -31,6 +31,9 @@ export 'src/api/components/modal.dart' show Modal;
export 'src/api/components/text_input.dart' show TextInputStyle;
export 'src/api/components/button.dart' show Button, ButtonStyle;
export 'src/api/components/link.dart' show Link;

export 'src/api/interactions/command_interaction.dart' show CommandInteraction;

export 'src/api/utils.dart';

typedef Snowflake = String;
9 changes: 4 additions & 5 deletions lib/src/api/client/mineral_client.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'dart:convert';

import 'package:http/http.dart';
import 'package:mineral/api.dart';
import 'package:mineral/core.dart';
import 'package:mineral/src/api/managers/guild_manager.dart';
Expand Down Expand Up @@ -65,12 +64,12 @@ class MineralClient {
Future<void> registerGuildCommands ({ required Guild guild, required List<MineralCommand> commands}) async {
Http http = ioc.singleton(Service.http);

print(jsonEncode(commands.map((command) => command.toJson()).toList()));

await http.put(
Response response = await http.put(
url: "/applications/${application.id}/guilds/${guild.id}/commands",
payload: commands.map((command) => command.toJson()).toList()
);

print(response.body);
}

factory MineralClient.from({ required dynamic payload }) {
Expand Down
95 changes: 95 additions & 0 deletions lib/src/api/interactions/command_interaction.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import 'package:http/http.dart';
import 'package:mineral/api.dart';
import 'package:mineral/core.dart';
import 'package:mineral/src/api/interactions/interaction.dart';

class CommandInteraction extends Interaction {
Snowflake id;
String identifier;
Map<String, dynamic> data = {};

CommandInteraction({
required this.identifier,
required this.id,
required InteractionType type,
required Snowflake applicationId,
required int version,
required String token,
required User user
}) : super(version: version, token: token, type: type, user: user, applicationId: applicationId);

T? getChannel<T extends Channel> (String optionName) {
return guild?.channels.cache.get(data[optionName]['value']);
}

int? getInteger (String optionName) {
return data[optionName]['value'];
}

String? getString (String optionName) {
return data[optionName]['value'];
}

GuildMember? getMember (String optionName) {
return guild?.members.cache.get(data[optionName]['value']);
}

bool? getBoolean (String optionName) {
return data[optionName]['value'];
}

Role? getRole (String optionName) {
return guild?.roles.cache.get(data[optionName]['value']);
}

T? getChoice<T> (String optionName) {
return data[optionName]['value'];
}

dynamic getMentionable (String optionName) {
return data[optionName]['value'];
}

Future<void> reply ({ String? content, List<MessageEmbed>? embeds, List<Row>? components, bool? tts, bool? private }) async {
Http http = ioc.singleton(Service.http);

List<dynamic> embedList = [];
if (embeds != null) {
for (MessageEmbed element in embeds) {
embedList.add(element.toJson());
}
}

List<dynamic> componentList = [];
if (components != null) {
for (Row element in components) {
componentList.add(element.toJson());
}
}

Response response = await http.post(url: "/interactions/$id/$token/callback", payload: {
'type': InteractionCallbackType.channelMessageWithSource.value,
'data': {
'tts': tts ?? false,
'content': content,
'embeds': embeds != null ? embedList : [],
'components': components != null ? componentList : [],
'flags': private != null && private == true ? 1 << 6 : null,
}
});

print(response.body);
}

factory CommandInteraction.from({ required User user, required dynamic payload }) {
return CommandInteraction(
id: payload['id'],
applicationId: payload['application_id'],
type: InteractionType.values.firstWhere((type) => type.value == payload['type']),
identifier: payload['data']['name'],
version: payload['version'],
token: payload['token'],
user: user,
);
}
}
25 changes: 25 additions & 0 deletions lib/src/api/interactions/interaction.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import 'package:mineral/api.dart';

enum InteractionCallbackType {
pong(1),
channelMessageWithSource(4),
deferredChannelMessageWithSource(5),
deferredUpdateMessage(6),
updateMessage(7),
applicationCommandAutocompleteResult(8),
modal(9);

final int value;
const InteractionCallbackType(this.value);
}

class Interaction {
Snowflake applicationId;
int version;
InteractionType type;
String token;
User user;
Guild? guild;

Interaction({ required this.applicationId, required this.version, required this.type, required this.token, required this.user });
}
2 changes: 1 addition & 1 deletion lib/src/api/message_embed.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class Field {
String value;
bool? inline;

Field({ required this.name, required this.value, required this.inline });
Field({ required this.name, required this.value, this.inline });

Object toJson () => {
'name': name,
Expand Down
8 changes: 8 additions & 0 deletions lib/src/api/user.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:mineral/api.dart';
import 'package:mineral/src/constants.dart';

class User {
Snowflake id;
Expand All @@ -20,6 +21,13 @@ class User {
required this.avatar,
});

String getDisplayAvatarUrl () {
return "${Constants.cdnUrl}/avatars/$id/$avatar";
}

@override
String toString () => "<@$id>";

factory User.from(dynamic payload) {
return User(
id: payload['id'],
Expand Down
20 changes: 20 additions & 0 deletions lib/src/api/utils.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
enum InteractionType {
ping(1),
applicationCommand(2),
messageComponent(3),
applicationCommandAutocomplete(4),
modalSubmit(5);

final int value;
const InteractionType(this.value);
}

enum ApplicationCommandType {
chatInput(1),
user(2),
message(3);

final int value;
const ApplicationCommandType(this.value);
}

enum NotificationLevel {
allMessages(0),
onlyMentions(1);
Expand Down
2 changes: 2 additions & 0 deletions lib/src/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ enum PacketType {
channelUpdate('CHANNEL_UPDATE'),
channelDelete('CHANNEL_DELETE'),

interactionCreate('INTERACTION_CREATE'),

memberUpdate('GUILD_MEMBER_UPDATE');

final String _value;
Expand Down
44 changes: 29 additions & 15 deletions lib/src/internal/entities/command_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,9 @@ import 'dart:mirrors';
import 'package:mineral/api.dart';
import 'package:mineral/core.dart';

enum ApplicationCommandType {
chatInput(1),
user(2),
message(3);

final int value;
const ApplicationCommandType(this.value);
}

class CommandManager {
final Collection<String, MineralCommand> _commands = Collection();
final Collection<String, MineralCommand> commands = Collection();
final Map<String, dynamic> handlers = {};

CommandManager add (Object object) {
MineralCommand command = MineralCommand(name: '', description: '', scope: '', options: []);
Expand All @@ -28,13 +20,13 @@ class CommandManager {
object: object
);

_commands.set<MineralCommand>(command.name, command);
commands.set<MineralCommand>(command.name, command);
return this;
}

List<MineralCommand> getFromGuild (Guild guild) {
List<MineralCommand> commands = [];
_commands.forEach((name, command) {
this.commands.forEach((name, command) {
if (command.scope == guild.id || command.scope == 'GUILD') {
commands.add(command);
}
Expand All @@ -45,7 +37,7 @@ class CommandManager {

List<MineralCommand> getGlobals () {
List<MineralCommand> commands = [];
_commands.forEach((name, command) {
this.commands.forEach((name, command) {
if (command.scope == 'GLOBAL') {
commands.add(command);
}
Expand All @@ -55,7 +47,10 @@ class CommandManager {
}

void _registerCommands ({ required MineralCommand command, required Object object }) {
reflect(object).type.metadata.forEach((element) {
ClassMirror classMirror = reflect(object).type;
dynamic classCommand = reflect(object).reflectee;

for (InstanceMirror element in classMirror.metadata) {
dynamic reflectee = element.reflectee;

if (reflectee is CommandGroup) {
Expand All @@ -72,15 +67,24 @@ class CommandManager {
..name = reflectee.name
..description = reflectee.description
..scope = reflectee.scope;

if (classMirror.instanceMembers.values.toList().where((element) => element.simpleName == Symbol('handle')).isNotEmpty) {
MethodMirror handle = classMirror.instanceMembers.values.toList().firstWhere((element) => element.simpleName == Symbol('handle'));
handlers.putIfAbsent(command.name, () => {
'symbol': handle.simpleName,
'commandClass': classCommand,
});
}
}

if (reflectee is Option) {
command.options.add(reflectee);
}
});
}
}

void _registerCommandMethods ({ required MineralCommand command, required Object object }) {
dynamic classCommand = reflect(object).reflectee;
reflect(object).type.declarations.forEach((key, value) {
if (value.metadata.isEmpty) {
return;
Expand All @@ -99,9 +103,19 @@ class CommandManager {
if (groupName != null) {
MineralCommand group = command.groups.firstWhere((group) => group.name == groupName);
group.subcommands.add(subcommand);

handlers.putIfAbsent("${command.name}.${group.name}.${subcommand.name}", () => {
'symbol': Symbol(subcommand.name),
'commandClass': classCommand,
});
} else {
command.subcommands.add(subcommand);
handlers.putIfAbsent("${command.name}.${subcommand.name}", () => {
'symbol': Symbol(subcommand.name),
'commandClass': classCommand,
});
}

}

if (reflectee is Option) {
Expand Down
4 changes: 3 additions & 1 deletion lib/src/internal/entities/event_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ enum Events {

memberUpdate('update::member'),
memberRolesUpdate('update::roles-member'),
acceptRules('accept::rules');
acceptRules('accept::rules'),

commandCreate('create::commandInteraction');

final String event;
const Events(this.event);
Expand Down
5 changes: 3 additions & 2 deletions lib/src/internal/websockets/packets/guild_create.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ class GuildCreate implements WebsocketPacket {

ChannelManager channelManager = ChannelManager(guildId: websocketResponse.payload['id']);
for(dynamic payload in websocketResponse.payload['channels']) {
if (channels.containsKey(payload['type'])) {
Channel Function(dynamic payload) item = channels[payload['type']] as Channel Function(dynamic payload);
ChannelType channelType = ChannelType.values.firstWhere((type) => type.value == payload['type']);
if (channels.containsKey(channelType)) {
Channel Function(dynamic payload) item = channels[channelType] as Channel Function(dynamic payload);
Channel channel = item(payload);

channelManager.cache.putIfAbsent(channel.id, () => channel);
Expand Down
Loading

0 comments on commit 149fd72

Please sign in to comment.