[WIP] Added missing methods to shard manager, redone emojis, code formatting

[ci skip]
This commit is contained in:
Szymon Uglis 2020-06-03 00:15:17 +02:00
parent a628903f43
commit 472e65d8d4
8 changed files with 139 additions and 35 deletions

View file

@ -203,7 +203,7 @@ class Nyxx implements Disposable {
this._events = _EventController(this);
this.onSelfMention = this
.onMessageReceived
.where((event) => event.message.mentions.any((mentionedUser) => mentionedUser == this.self));
.where((event) => event.message.mentions.contains(this.self));
this.onDmReceived = this
.onMessageReceived
.where((event) => event.message.channel is DMChannel || event.message.channel is GroupDMChannel);
@ -230,7 +230,9 @@ class Nyxx implements Disposable {
/// Returns guild with given [guildId]
Future<Guild> getGuild(Snowflake guildId, [bool useCache = true]) async {
if (this.guilds.hasKey(guildId) && useCache) return this.guilds[guildId]!;
if (this.guilds.hasKey(guildId) && useCache) {
return this.guilds[guildId]!;
}
final response = await _http._execute(BasicRequest._new("/guilds/$guildId"));
@ -248,7 +250,9 @@ class Nyxx implements Disposable {
/// var channel = await client.getChannel<TextChannel>(Snowflake("473853847115137024"));
/// ```
Future<T> getChannel<T extends Channel>(Snowflake id, [bool useCache = true]) async {
if (this.channels.hasKey(id) && useCache) return this.channels[id] as T;
if (this.channels.hasKey(id) && useCache) {
return this.channels[id] as T;
}
final response = await this._http._execute(BasicRequest._new("/channels/${id.toString()}"));
@ -268,7 +272,9 @@ class Nyxx implements Disposable {
/// var user = client.getUser(Snowflake("302359032612651009"));
/// ``
Future<User?> getUser(Snowflake id, [bool useCache = true]) async {
if (this.users.hasKey(id) && useCache) return this.users[id];
if (this.users.hasKey(id) && useCache) {
return this.users[id];
}
final response = await this._http._execute(BasicRequest._new("/users/${id.toString()}"));
@ -321,19 +327,18 @@ class Nyxx implements Disposable {
/// var inv = client.getInvite("YMgffU8");
/// ```
Future<Invite> getInvite(String code) async {
final r = await this._http._execute(BasicRequest._new("/invites/$code"));
final response = await this._http._execute(BasicRequest._new("/invites/$code"));
if (r is HttpResponseSuccess) {
return Invite._new(r.jsonBody as Map<String, dynamic>, this);
if (response is HttpResponseSuccess) {
return Invite._new(response.jsonBody as Map<String, dynamic>, this);
}
return Future.error(r);
return Future.error(response);
}
/// Returns number of shards
int get shards => this.shardManager._shards.length;
/*
/// Sets presence for bot.
///
/// Code below will display bot presence as `Playing Super duper game`:
@ -349,9 +354,9 @@ class Nyxx implements Disposable {
/// ```
/// `url` property in `Activity` can be only set when type is set to `streaming`
void setPresence({UserStatus? status, bool? afk, Activity? game, DateTime? since}) {
this.shard.setPresence(status: status, afk: afk, game: game, since: since);
this.shardManager.setPresence(status: status, afk: afk, game: game, since: since);
}
*/
@override
Future<void> dispose() async {
await shardManager.dispose();

View file

@ -20,11 +20,21 @@ class ShardManager implements Disposable {
final int _numShards;
final Map<int, Shard> _shards = {};
/// List of shards
Iterable<Shard> get shards => List.unmodifiable(_shards.values);
/// Starts shard manager
ShardManager(this._ws, this._numShards) {
_connect(_numShards - 1);
}
/// Sets presences on every shard
void setPresence({UserStatus? status, bool? afk, Activity? game, DateTime? since}) {
for (final shard in shards) {
shard.setPresence(status: status, afk: afk, game: game, since: since);
}
}
void _connect(int shardId) {
if(shardId < 0) {
return;
@ -55,6 +65,9 @@ class Shard implements Disposable {
/// Reference to [ShardManager]
ShardManager manager;
/// List of handled guild ids
final List<Snowflake> guilds = [];
late final Isolate _shardIsolate; // Reference to isolate
late final Stream<dynamic> _receiveStream; // Broadcast stream on which data from isolate is received
late final ReceivePort _receivePort; // Port on which data from isolate is received
@ -86,6 +99,64 @@ class Shard implements Disposable {
this.sendPort.send({"cmd": "SEND", "data" : {"op": opCode, "d": d}});
}
/// Allows to set presence for current shard.
void setPresence({UserStatus? status, bool? afk, Activity? game, DateTime? since}) {
final packet = <String, dynamic> {
"status": (status != null) ? status.toString() : UserStatus.online.toString(),
"afk": (afk != null) ? afk : false,
if (game != null)
"game": <String, dynamic>{
"name": game.name,
"type": game.type.value,
if (game.type == ActivityType.streaming) "url": game.url
},
"since": (since != null) ? since.millisecondsSinceEpoch : null
};
this.send(OPCodes.statusUpdate, packet);
}
/// Syncs all guilds
void guildSync() => this.send(OPCodes.guildSync, this.guilds.map((e) => e.toString()));
/// Allows to request members objects from gateway
/// [guild] can be either Snowflake or Iterable<Snowflake>
void requestMembers(/* Snowflake|Iterable<Snowflake> */ dynamic guild,
{String? query, Iterable<Snowflake>? userIds, int limit = 0, bool presences = false, String? nonce}) {
if (query != null && userIds != null) {
throw Exception("Both `query` and userIds cannot be specified.");
}
dynamic guildPayload;
if (guild is Snowflake) {
if(!this.guilds.contains(guild)) {
throw Exception("Cannot request member for guild on wrong shard");
}
guildPayload = guild.toString();
} else if (guild is Iterable<Snowflake>) {
if(!this.guilds.any((element) => guild.contains(element))) {
throw Exception("Cannot request member for guild on wrong shard");
}
guildPayload = guild.map((e) => e.toString()).toList();
} else {
throw Exception("guild has to be either Snowflake or Iterable<Snowflake>");
}
final payload = <String, dynamic>{
"guild_id": guildPayload,
if (query != null) "query": query,
if (userIds != null) "user_ids": userIds.map((e) => e.toString()).toList(),
"limit": limit,
"presences": presences,
if (nonce != null) "nonce": nonce
};
this.send(OPCodes.requestGuildMember, payload);
}
void _heartbeat() {
this.send(OPCodes.heartbeat, _sequence);
}
@ -219,7 +290,9 @@ class Shard implements Disposable {
break;
case "GUILD_CREATE":
manager._ws._client._events.onGuildCreate.add(GuildCreateEvent._new(msg, manager._ws._client));
final event = GuildCreateEvent._new(msg, manager._ws._client);
this.guilds.add(event.guild.id);
manager._ws._client._events.onGuildCreate.add(event);
break;
case "GUILD_UPDATE":
@ -363,7 +436,11 @@ Future<void> _shardHandler(SendPort shardPort) async {
_socket = ws;
_socket!.listen((data) {
shardPort.send({ "cmd" : "DATA", "jsonData" : _decodeBytes(data), "resume" : resume});
}, onError: (err) => shardPort.send({ "cmd" : "ERROR", "error": err.toString()}));
}, onDone: () => print("DONE ----------------------- ${ws.closeCode}"),
onError: (err) {
print(err);
shardPort.send({ "cmd" : "ERROR", "error": err.toString()});
});
}, onError: (_, __) => Future.delayed(const Duration(seconds: 2), _connect));
}

View file

@ -63,7 +63,7 @@ class Guild extends SnowflakeEntity implements Disposable {
late final int systemChannelFlags;
/// Channel where "PUBLIC" guilds display rules and/or guidelines
late final CacheGuildChannel? rulesChannel;
late final IGuildChannel? rulesChannel;
/// The guild owner's ID
late final User? owner;
@ -92,7 +92,7 @@ class Guild extends SnowflakeEntity implements Disposable {
/// the id of the channel where admins and moderators
/// of "PUBLIC" guilds receive notices from Discord
late final CacheGuildChannel? publicUpdatesChannel;
late final IGuildChannel? publicUpdatesChannel;
/// Permission of current(bot) user in this guild
Permissions? currentUserPermissions;
@ -229,11 +229,11 @@ class Guild extends SnowflakeEntity implements Disposable {
}
if (raw["rules_channel_id"] != null) {
this.rulesChannel = this.channels[Snowflake(raw["rules_channel_id"])] as CacheGuildChannel?;
this.rulesChannel = this.channels[Snowflake(raw["rules_channel_id"])] as IGuildChannel;
}
if (raw["public_updates_channel_id"] != null) {
this.publicUpdatesChannel = this.channels[Snowflake(raw["public_updates_channel_id"])] as CacheGuildChannel?;
this.publicUpdatesChannel = this.channels[Snowflake(raw["public_updates_channel_id"])] as IGuildChannel?;
}
}

View file

@ -3,7 +3,7 @@ part of nyxx;
/// Represents emoji. Subclasses provides abstraction to custom emojis(like [GuildEmoji]).
abstract class Emoji {
/// Emojis name.
String name;
final String? name;
Emoji._new(this.name);

View file

@ -1,10 +1,38 @@
part of nyxx;
abstract class IGuildEmoji extends Emoji {
abstract class IGuildEmoji extends Emoji implements SnowflakeEntity {
/// True if emoji is partial.
final bool partial;
IGuildEmoji._new(String name, this.partial) : super._new(name);
/// Snowflake id of emoji
@override
late final Snowflake id;
@override
DateTime get createdAt => id.timestamp;
IGuildEmoji._new(Map<String, dynamic> raw, this.partial) : super._new(raw["name"] as String?) {
this.id = Snowflake(raw["id"] as String);
}
}
class PartialGuildEmoji extends IGuildEmoji {
PartialGuildEmoji._new(Map<String, dynamic> raw) : super._new(raw, true);
/// Encodes Emoji to API format
@override
String encode() => "$id";
/// Formats Emoji to message format
@override
String format() => "<:$id>";
/// Returns cdn url to emoji
String get cdnUrl => "https://cdn.discordapp.com/emojis/${this.id}.png";
/// Returns encoded string ready to send via message.
@override
String toString() => format();
}
/// Emoji object. Handles Unicode emojis and custom ones.
@ -22,10 +50,6 @@ class GuildEmoji extends IGuildEmoji implements SnowflakeEntity, GuildEntity {
@override
late final Snowflake guildId;
/// Snowflake id of emoji
@override
late final Snowflake id;
/// Roles which can use this emote
late final Iterable<IRole> roles;
@ -39,8 +63,7 @@ class GuildEmoji extends IGuildEmoji implements SnowflakeEntity, GuildEntity {
late final bool animated;
/// Creates full emoji object
GuildEmoji._new(Map<String, dynamic> raw, this.guildId, this.client) : super._new(raw["name"] as String, false) {
this.id = Snowflake(raw["id"] as String);
GuildEmoji._new(Map<String, dynamic> raw, this.guildId, this.client) : super._new(raw, false) {
this.guild = client.guilds[this.guildId];
this.requireColons = raw["require_colons"] as bool? ?? false;

View file

@ -6,14 +6,14 @@ class UnicodeEmoji extends Emoji {
UnicodeEmoji(String code) : super._new(code);
/// Returns Emoji
String get code => this.name;
String get code => this.name!;
/// Returns runes of emoji
Runes get runes => this.name.runes;
Runes get runes => this.name!.runes;
/// Encodes Emoji so that can be used in messages.
@override
String encode() => this.name;
String encode() => this.name!;
/// Returns encoded string ready to send via message.
@override

View file

@ -85,7 +85,7 @@ class GuildMemberRemoveEvent {
/// Sent when a member is updated.
class GuildMemberUpdateEvent {
/// The member after the update if member is updated.
late final CacheMember? member;
late final IMember? member;
/// User if user is updated. Will be null if member is not null.
late final User? user;
@ -97,16 +97,16 @@ class GuildMemberUpdateEvent {
return;
}
final member = guild.members[Snowflake(raw["d"]["user"]["id"])];
this.member = guild.members[Snowflake(raw["d"]["user"]["id"])];
if (member == null) {
if (this.member == null || this.member is! CacheMember) {
return;
}
final nickname = raw["d"]["nickname"] as String?;
final roles = (raw["d"]["roles"] as List<dynamic>).map((str) => guild.roles[Snowflake(str)]!).toList();
if (this.member!._updateMember(nickname, roles)) {
if ((this.member as CacheMember)._updateMember(nickname, roles)) {
return;
}

View file

@ -104,8 +104,7 @@ abstract class MessageReactionEvent {
if (json["d"]["emoji"]["id"] == null) {
this.emoji = UnicodeEmoji(json["d"]["emoji"]["name"] as String);
} else {
// TODO: emojis stuff
//this.emoji = GuildEmoji._partial(json["d"]["emoji"] as Map<String, dynamic>);
this.emoji = PartialGuildEmoji._new(json["d"]["emoji"] as Map<String, dynamic>);
}
}
}