Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement invitations #93

Merged
merged 7 commits into from
Mar 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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