Skip to content

Commit

Permalink
Merge pull request #93 from mineral-dart/feat-implement-invitations
Browse files Browse the repository at this point in the history
feat: Implement invitations
  • Loading branch information
LeadcodeDev authored Mar 26, 2023
2 parents dbf373a + 3bdc7b9 commit 39ca4b8
Show file tree
Hide file tree
Showing 23 changed files with 366 additions and 71 deletions.
2 changes: 2 additions & 0 deletions lib/core/api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,7 @@ export '../src/internal/services/intent_service.dart' show Intent;
export '../src/api/users/premium_type.dart' show PremiumType;
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;

typedef Snowflake = String;
2 changes: 2 additions & 0 deletions lib/core/events.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ export '../src/internal/websockets/events/voice_leave_event.dart';
export '../src/internal/websockets/events/voice_move_event.dart';
export '../src/internal/websockets/events/voice_state_update_event.dart';
export '../src/internal/websockets/events/webhook_update_event.dart';
export '../src/internal/websockets/events/invite_create_event.dart';
export '../src/internal/websockets/events/invite_delete_event.dart';
48 changes: 48 additions & 0 deletions lib/src/api/client/client_permission.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
enum ClientPermission {
createInstantInvite(1 << 0),
kickMembers(1 << 1),
banMembers(1 << 2),
administrator(1 << 3),
manageChannels(1 << 4),
manageGuilds(1 << 5),
addReactions(1 << 6),
viewAuditChannel(1 << 7),
prioritySpeaker(1 << 8),
stream(1 << 9),
viewChannel(1 << 10),
sendMessages(1 << 11),
sendTtsMessage(1 << 12),
manageMessages(1 << 13),
embedLinks(1 << 14),
attachFiles(1 << 15),
readMessageHistory(1 << 16),
mentionEveryone(1 << 17),
useExternalEmojis(1 << 18),
viewGuildInsights(1 << 19),
connect(1 << 20),
speak(1 << 21),
muteMembers(1 << 22),
deafenMembers(1 << 23),
moveMembers(1 << 24),
useVad(1 << 25),
changeUsername(1 << 26),
managerUsernames(1 << 27),
manageRoles(1 << 28),
manageWebhooks(1 << 29),
manageEmojisAndStickers(1 << 30),
useApplicationCommand(1 << 31),
requestToSpeak(1 << 32),
manageEvents(1 << 33),
manageThreads(1 << 34),
usePublicThreads(1 << 35),
createPublicThreads(1 << 35),
usePrivateThreads(1 << 36),
createPrivateThreads(1 << 36),
useExternalStickers(1 << 37),
sendMessageInThreads(1 << 38),
startEmbeddedActivities(1 << 39),
moderateMembers(1 << 40);

final int value;
const ClientPermission(this.value);
}
33 changes: 33 additions & 0 deletions lib/src/api/client/client_scope.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
enum ClientScope {
activitiesRead('activities.read'),
activitiesWrite('activities.write'),
applicationBuildsRead('applications.builds.read'),
applicationBuildsUpload('applications.builds.upload'),
applicationsCommands('applications.commands'),
applicationsCommandsUpdate('applications.commands.update'),
applicationsCommandsPermissionsUpdate('applications.commands.permissions.update'),
applicationEntitlements('applications.entitlements'),
applicationsStoreUpdate('applications.store.update'),
bot('bot'),
connections('connections'),
dmChannelsRead('dm_channels.read'),
email('email'),
gdmJoin('gdm.join'),
guilds('guilds'),
guildsJoin('guilds.join'),
guildsMembersRead('guilds.members.read'),
identify('identify'),
messagesRead('messages.read'),
relationships('relationships.read'),
roleConnectionsWrite('role_connections.write'),
rpc('rpc'),
rpcActivitiesWrite('rpc.activities.write'),
rpcNotificationsRead('rpc.notifications.read'),
rpcVoiceRead('rpc.voice.read'),
rpcVoiceWrite('rpc.voice.write'),
voice('voice'),
webhookIncoming('webhook.incoming');

final String value;
const ClientScope(this.value);
}
15 changes: 15 additions & 0 deletions lib/src/api/client/mineral_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,21 @@ class MineralClient extends MineralService {
.build();
}

Uri createInvitation ({ List<ClientPermission> permissions = const [], List<ClientScope> scope = const [] }) {
int _permissions = permissions.fold(0, (acc, element) => acc += element.value);

return Uri(
host: 'discord.com',
scheme: 'https',
path: '/api/oauth2/authorize',
queryParameters: {
'client_id': user.id,
'permissions': _permissions.toString(),
'scope': scope.map((scope) => scope.value).join(' ')
}
);
}

factory MineralClient.from({ required dynamic payload }) {
return MineralClient(
User.from(payload['user']),
Expand Down
5 changes: 5 additions & 0 deletions lib/src/api/guilds/guild.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:mineral/framework.dart';
import 'package:mineral/src/api/managers/channel_manager.dart';
import 'package:mineral/src/api/managers/command_manager.dart';
import 'package:mineral/src/api/managers/emoji_manager.dart';
import 'package:mineral/src/api/managers/guild_invite_manager.dart';
import 'package:mineral/src/api/managers/guild_role_manager.dart';
import 'package:mineral/src/api/managers/guild_scheduled_event_manager.dart';
import 'package:mineral/src/api/managers/guild_webhook_manager.dart';
Expand Down Expand Up @@ -94,6 +95,7 @@ class Guild {
GuildWebhookManager _webhooks;
GuildScheduledEventService _scheduledEvents;
CommandService _commands;
GuildInviteManager _invites;

Guild(
this._id,
Expand Down Expand Up @@ -141,6 +143,7 @@ class Guild {
this._webhooks,
this._scheduledEvents,
this._commands,
this._invites,
);

Snowflake get id => _id;
Expand Down Expand Up @@ -189,6 +192,7 @@ class Guild {
GuildScheduledEventService get scheduledEvents => _scheduledEvents;
Map<Snowflake, GuildMember> get bots => _members.cache.where((element) => element.isBot);
CommandService get commands => _commands;
GuildInviteManager get invites => _invites;

/// ### Modifies the [name] of this.
///
Expand Down Expand Up @@ -674,6 +678,7 @@ class Guild {
GuildWebhookManager.fromManager(webhookManager: webhookManager),
guildScheduledEventService,
CommandService(payload['id']),
GuildInviteManager(payload['id'])
);
}
}
98 changes: 98 additions & 0 deletions lib/src/api/invites/invite.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import 'package:mineral/core.dart';
import 'package:mineral/core/api.dart';
import 'package:mineral/framework.dart';
import 'package:mineral/src/api/invites/invite_target_type.dart';
import 'package:mineral/src/api/invites/wrapped_inviter.dart';
import 'package:mineral_ioc/ioc.dart';

class Invite {
final int _type;
final int _uses;
final bool _temporary;
final int _maxUses;
final int _maxAge;
final Snowflake? _inviterId;
final Snowflake? _targetUserId;
final Snowflake? _guildId;
final String? _expiresAt;
final String _createdAt;
final String _code;
final Snowflake? _channelId;
final int? _targetType;

Invite(
this._type,
this._uses,
this._temporary,
this._maxUses,
this._maxAge,
this._inviterId,
this._targetUserId,
this._guildId,
this._expiresAt,
this._createdAt,
this._code,
this._channelId,
this._targetType,
);

Guild get guild => ioc.use<MineralClient>().guilds.cache.getOrFail(_guildId);

int get type => _type;

int get uses => _uses;

bool get isTemporary => _temporary;

int get maxUses => _maxUses;

DateTime get maxAge => DateTime.fromMillisecondsSinceEpoch(_maxAge);

DateTime? get expiresAt => _expiresAt != null
? DateTime.parse(_expiresAt!)
: null;

DateTime get createdAt => DateTime.parse(_createdAt);

String get code => _code;

GuildChannel? get channel => guild.channels.cache.get(_channelId);

InviteTargetType get targetType => InviteTargetType.values.firstWhere((element) => element.value == _targetType);

WrappedInviter? getInviter () => _inviterId != null && _guildId != null
? WrappedInviter(_guildId!, _inviterId!)
: null;

Future<User>? getTargetUser () => _targetUserId != null
? ioc.use<MineralClient>().users.resolve(_targetUserId!)
: null;

String get url => '${Constants.discordInviteHost}/$_code';

Future<void> delete ({ String? reason }) async {
await ioc.use<DiscordApiHttpService>()
.destroy(url: '/invites/$_code')
.auditLog(reason)
.build();
}

@override
String toString () => url;

factory Invite.from(dynamic payload) => Invite(
payload['type'],
payload['uses'],
payload['temporary'],
payload['max_uses'],
payload['max_age'],
payload['inviter']?['id'],
payload['target_user']?['id'],
payload['guild_id'],
payload['expires_at'],
payload['created_at'],
payload['code'],
payload['channel_id'],
payload['target_type']
);
}
7 changes: 7 additions & 0 deletions lib/src/api/invites/invite_target_type.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
enum InviteTargetType {
stream(1),
embeddedApplication(2);

final int value;
const InviteTargetType(this.value);
}
16 changes: 16 additions & 0 deletions lib/src/api/invites/wrapped_inviter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import 'package:mineral/core/api.dart';
import 'package:mineral/framework.dart';
import 'package:mineral_ioc/ioc.dart';

class WrappedInviter {
final Snowflake _guildId;
final Snowflake _userId;

WrappedInviter(this._guildId, this._userId);

GuildMember? toMember () => ioc.use<MineralClient>().guilds.cache
.get(_guildId)?.members.cache
.get(_userId);

Future<User> toUser () async => await ioc.use<MineralClient>().users.resolve(_userId);
}
48 changes: 48 additions & 0 deletions lib/src/api/managers/guild_invite_manager.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'dart:convert';

import 'package:http/http.dart';
import 'package:mineral/core.dart';
import 'package:mineral/core/api.dart';
import 'package:mineral/exception.dart';
import 'package:mineral/framework.dart';
import 'package:mineral/src/api/invites/invite.dart';
import 'package:mineral/src/api/managers/cache_manager.dart';
import 'package:mineral_ioc/ioc.dart';

class GuildInviteManager extends CacheManager<Invite> {
final Snowflake? _guildId;

GuildInviteManager(this._guildId);

Future<Map<String, Invite>> sync () async {
Response response = await ioc.use<DiscordApiHttpService>()
.get(url: '/guilds/$_guildId/invites')
.build();

for (dynamic element in jsonDecode(response.body)) {
Invite invite = Invite.from(element);
cache.set(invite.code, invite);
}

return cache;
}

Future<Invite> resolve (Snowflake id) async {
if(cache.containsKey(id)) {
return cache.getOrFail(id);
}

final Response response = await ioc.use<DiscordApiHttpService>()
.get(url: '/webhooks/$id')
.build();

if(response.statusCode == 200) {
final Invite invite = Invite.from(jsonDecode(response.body));

cache.putIfAbsent(invite.code, () => invite);
return invite;
}

throw ApiException('Unable to fetch invite with code #$id');
}
}
2 changes: 1 addition & 1 deletion lib/src/api/managers/guild_role_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class GuildRoleManager extends CacheManager<Role> {
/// hoist: true,
/// );
/// ```
Future<Role> create ({ required String label, Color? color, bool? hoist, String? icon, String? unicode, bool? mentionable, List<Permission>? permissions }) async {
Future<Role> create ({ required String label, Color? color, bool? hoist, String? icon, String? unicode, bool? mentionable, List<ClientPermission>? permissions }) async {
if ((icon != null || unicode != null) && !guild.features.contains(GuildFeature.roleIcons)) {
throw MissingFeatureException("Guild ${guild.name} has no 'ROLE_ICONS' feature.");
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/api/managers/user_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ class UserManager extends CacheManager<User> {
return user;
}

throw ApiException('Unable to fetch channel with id #$id');
throw ApiException('Unable to fetch user with id #$id');
}
}
4 changes: 2 additions & 2 deletions lib/src/api/permission_overwrite.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ enum PermissionOverwriteType {
class PermissionOverwrite {
Snowflake id;
PermissionOverwriteType type;
List<Permission>? allow;
List<Permission>? deny;
List<ClientPermission>? allow;
List<ClientPermission>? deny;

PermissionOverwrite({
required this.id,
Expand Down
2 changes: 1 addition & 1 deletion lib/src/api/role.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class Role {
/// await role.setPermissions([Permission.kickMembers, Permission.banMembers]);
/// }
///
Future<void> setPermissions (List<Permission> permissions, { String? reason }) async {
Future<void> setPermissions (List<ClientPermission> permissions, { String? reason }) async {
int _permissions = Helper.reduceRolePermissions(permissions);
Response response = await ioc.use<DiscordApiHttpService>().patch(url: "/guilds/${manager.guild.id}/roles/$id")
Expand Down
Loading

0 comments on commit 39ca4b8

Please sign in to comment.