Skip to content

Commit

Permalink
Release 3.1.0 (#271)
Browse files Browse the repository at this point in the history
* Feature/specific shards (#266)

* Update .gitignore

* Implement specific sharding

* Refactor propagateReady() method

* Throw exception when invalid guild shard is accessed

* Move logic to private method

* Rename shards -> shardIds

* feature/boost-progress-bar - implementation (#268)

* [feature] timeouts - implementation (#267)

* feature/timeouts - implementation

* feature/timeouts - analyzer and formatter fixes

* feature/timeouts - deprecate old edit member parameters

* Release 3.1.0

* Correctly initialise onDmReceived and onSelfMention (#270)

* Update 3.1.0 changelog

Co-authored-by: Abitofevrything <[email protected]>
  • Loading branch information
l7ssha and abitofevrything authored Dec 28, 2021
1 parent 6997546 commit 32b3d2d
Show file tree
Hide file tree
Showing 17 changed files with 191 additions and 35 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ test-*.dart
**/coverage/**
coverage.json
lcov.info
.vscode/
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
## 3.1.0
__28.12.2021__

- Implement patches needed for external sharding feature (#266)
- Implement boost progress bar (#266)
- Implement timeouts (#267)
- deprecation of edit method parameters in favor of `MemberBuilder` class. In next major release all parameters except `builder`
and `auditReason` will be removed
- Fix incorrectly initialised onDmReceived and onSelfMention streams (#270)

## 3.0.1
__21.12.2021__

Expand Down
1 change: 1 addition & 0 deletions lib/nyxx.dart
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export 'src/utils/builders/embed_footer_builder.dart' show EmbedFooterBuilder;
export 'src/utils/builders/guild_builder.dart' show GuildBuilder, RoleBuilder;
export 'src/utils/builders/channel_builder.dart';
export 'src/utils/builders/message_builder.dart' show MessageBuilder, MessageDecoration;
export 'src/utils/builders/member_builder.dart' show MemberBuilder;
export 'src/utils/builders/permissions_builder.dart' show PermissionOverrideBuilder, PermissionsBuilder;
export 'src/utils/builders/presence_builder.dart' show PresenceBuilder, ActivityBuilder;
export 'src/utils/builders/reply_builder.dart' show ReplyBuilder;
Expand Down
6 changes: 5 additions & 1 deletion lib/src/client_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ class ClientOptions {
/// The total number of shards.
int? shardCount;

/// A list of shards to spawn on this instance of nyxx.
List<int>? shardIds;

/// The number of messages to cache for each channel.
int messageCacheSize;

Expand Down Expand Up @@ -81,7 +84,8 @@ class ClientOptions {
this.initialPresence,
this.shutdownHook,
this.shutdownShardHook,
this.dispatchRawShardEvent = false});
this.dispatchRawShardEvent = false,
this.shardIds});
}

/// When identifying to the gateway, you can specify an intents parameter which
Expand Down
13 changes: 12 additions & 1 deletion lib/src/core/guild/guild.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:nyxx/src/core/guild/scheduled_event.dart';
import 'package:nyxx/src/internal/exceptions/invalid_shard_exception.dart';
import 'package:nyxx/src/nyxx.dart';
import 'package:nyxx/src/core/channel/invite.dart';
import 'package:nyxx/src/core/snowflake.dart';
Expand Down Expand Up @@ -147,6 +148,9 @@ abstract class IGuild implements SnowflakeEntity {
/// Returns this guilds shard
IShard get shard;

/// Whether the guild has the boost progress bar enabled
bool get boostProgressBarEnabled;

/// The guild's icon, represented as URL.
/// If guild doesn't have icon it returns null.
String? iconURL({String format = "webp", int size = 128});
Expand Down Expand Up @@ -427,6 +431,9 @@ class Guild extends SnowflakeEntity implements IGuild {
@override
late final Iterable<IGuildSticker> stickers;

@override
late final bool boostProgressBarEnabled;

/// Returns url to this guild.
@override
String get url => "https://discordapp.com/channels/${id.toString()}";
Expand Down Expand Up @@ -468,7 +475,10 @@ class Guild extends SnowflakeEntity implements IGuild {
throw UnsupportedError("Cannot use this property with NyxxRest");
}

return (client as NyxxWebsocket).shardManager.shards.firstWhere((_shard) => _shard.guilds.contains(id));
return (client as NyxxWebsocket).shardManager.shards.firstWhere(
(_shard) => _shard.guilds.contains(id),
orElse: throw InvalidShardException('Cannot find shard for this guild!'),
);
}

/// Creates an instance of [Guild]
Expand All @@ -489,6 +499,7 @@ class Guild extends SnowflakeEntity implements IGuild {
premiumTier = PremiumTier.from(raw["premium_tier"] as int);
premiumSubscriptionCount = raw["premium_subscription_count"] as int?;
preferredLocale = raw["preferred_locale"] as String;
boostProgressBarEnabled = raw['premium_progress_bar_enabled'] as bool;

owner = UserCacheable(client, Snowflake(raw["owner_id"]));

Expand Down
25 changes: 23 additions & 2 deletions lib/src/core/user/member.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';

import 'package:nyxx/nyxx.dart';
import 'package:nyxx/src/nyxx.dart';
import 'package:nyxx/src/core/snowflake.dart';
import 'package:nyxx/src/core/snowflake_entity.dart';
Expand Down Expand Up @@ -58,6 +59,12 @@ abstract class IMember implements SnowflakeEntity, Mentionable {
/// Returns total permissions of user.
Future<IPermissions> get effectivePermissions;

/// When the user's timeout will expire and the user will be able to communicate in the guild again, null or a time in the past if the user is not timed out
DateTime? get timeoutUntil;

/// True if user is timed out
bool get isTimedOut;

/// Returns url to member avatar
String? avatarURL({String format = "webp"});

Expand Down Expand Up @@ -136,6 +143,12 @@ class Member extends SnowflakeEntity implements IMember {
@override
String get mention => "<@$id>";

@override
bool get isTimedOut => timeoutUntil != null && timeoutUntil!.isAfter(DateTime.now());

@override
late final DateTime? timeoutUntil;

/// Returns total permissions of user.
@override
Future<IPermissions> get effectivePermissions async {
Expand Down Expand Up @@ -168,6 +181,7 @@ class Member extends SnowflakeEntity implements IMember {
guild = GuildCacheable(client, guildId);
boostingSince = DateTime.tryParse(raw["premium_since"] as String? ?? "");
avatarHash = raw["avatar"] as String?;
timeoutUntil = raw['communication_disabled_until'] != null ? DateTime.parse(raw['communication_disabled_until'] as String) : null;

roles = [for (var id in raw["roles"]) RoleCacheable(client, Snowflake(id), guild)];

Expand Down Expand Up @@ -224,8 +238,15 @@ class Member extends SnowflakeEntity implements IMember {
/// Edits members. Allows to move user in voice channel, mute or deaf, change nick, roles.
@override
Future<void> edit(
{String? nick = "", List<SnowflakeEntity>? roles, bool? mute, bool? deaf, Snowflake? channel = const Snowflake.zero(), String? auditReason}) =>
client.httpEndpoints.editGuildMember(guild.id, id, nick: nick, roles: roles, mute: mute, deaf: deaf, channel: channel, auditReason: auditReason);
{@Deprecated('Use "builder" parameter') String? nick = "",
@Deprecated('Use "builder" parameter') List<SnowflakeEntity>? roles,
@Deprecated('Use "builder" parameter') bool? mute,
@Deprecated('Use "builder" parameter') bool? deaf,
@Deprecated('Use "builder" parameter') Snowflake? channel = const Snowflake.zero(),
MemberBuilder? builder,
String? auditReason}) =>
client.httpEndpoints
.editGuildMember(guild.id, id, nick: nick, roles: roles, mute: mute, deaf: deaf, channel: channel, builder: builder, auditReason: auditReason);

void updateMember(String? nickname, List<Snowflake> roles, DateTime? boostingSince) {
if (this.nickname != nickname) {
Expand Down
7 changes: 3 additions & 4 deletions lib/src/internal/connection_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,12 @@ class ConnectionManager {

Future<void> propagateReady() async {
_shardsReady++;
if (client.ready || _shardsReady < (client.options.shardCount ?? 1)) {
if (client.ready || _shardsReady < client.shardManager.numShards) {
return;
}

if (!client.ready) {
(client.eventsWs as WebsocketEventController).onReadyController.add(ReadyEvent(client));
}
(client.eventsWs as WebsocketEventController).onReadyController.add(ReadyEvent(client));

client.ready = true;
_logger.info("Connected and ready! Logged as `${client.self.tag}`");
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/internal/constants.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class Constants {
static const int apiVersion = 9;

/// Version of Nyxx
static const String version = "3.0.1";
static const String version = "3.1.0";

/// Url to Nyxx repo
static const String repoUrl = "https://github.com/nyxx-discord/nyxx";
Expand Down
10 changes: 7 additions & 3 deletions lib/src/internal/event_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import 'package:nyxx/src/events/user_update_event.dart';
import 'package:nyxx/src/events/voice_server_update_event.dart';
import 'package:nyxx/src/events/voice_state_update_event.dart';
import 'package:nyxx/src/internal/interfaces/disposable.dart';
import 'package:nyxx/src/nyxx.dart';

abstract class IRestEventController implements Disposable {
/// Emitted when a successful HTTP response is received.
Expand Down Expand Up @@ -357,7 +358,7 @@ class WebsocketEventController extends RestEventController implements IWebsocket

/// Emitted when private message is received.
@override
late final Stream<IMessageReceivedEvent> onDmReceived;
late final Stream<IMessageReceivedEvent> onDmReceived = onMessageReceived.where((event) => event.message.guild == null);

/// Emitted when channel"s pins are updated.
@override
Expand Down Expand Up @@ -470,7 +471,8 @@ class WebsocketEventController extends RestEventController implements IWebsocket

/// Emitted when bot is mentioned
@override
late final Stream<IMessageReceivedEvent> onSelfMention;
late final Stream<IMessageReceivedEvent> onSelfMention =
onMessageReceived.where((event) => event.message.mentions.map((e) => e.id).contains(_client.self.id));

/// Emitted when invite is created
@override
Expand Down Expand Up @@ -524,8 +526,10 @@ class WebsocketEventController extends RestEventController implements IWebsocket
@override
late final Stream<IGuildEventUpdateEvent> onGuildEventUpdate;

final INyxxWebsocket _client;

/// Makes a new `EventController`.
WebsocketEventController() : super() {
WebsocketEventController(this._client) : super() {
onDisconnectController = StreamController.broadcast();
onDisconnect = onDisconnectController.stream;

Expand Down
34 changes: 23 additions & 11 deletions lib/src/internal/http_endpoints.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'package:nyxx/nyxx.dart';
import 'package:nyxx/src/core/guild/scheduled_event.dart';
import 'package:nyxx/src/nyxx.dart';
import 'package:nyxx/src/core/channel/invite.dart';
Expand Down Expand Up @@ -174,7 +175,13 @@ abstract class IHttpEndpoints {

/// "Edits" guild member. Allows to manipulate other guild users.
Future<void> editGuildMember(Snowflake guildId, Snowflake memberId,
{String? nick, List<SnowflakeEntity>? roles, bool? mute, bool? deaf, Snowflake? channel = const Snowflake.zero(), String? auditReason});
{@Deprecated('Use "builder" parameter') String? nick,
@Deprecated('Use "builder" parameter') List<SnowflakeEntity>? roles,
@Deprecated('Use "builder" parameter') bool? mute,
@Deprecated('Use "builder" parameter') bool? deaf,
@Deprecated('Use "builder" parameter') Snowflake? channel = const Snowflake.zero(),
MemberBuilder? builder,
String? auditReason});

/// Removes role from user
Future<void> removeRoleFromUser(Snowflake guildId, Snowflake roleId, Snowflake userId, {String? auditReason});
Expand Down Expand Up @@ -844,16 +851,21 @@ class HttpEndpoints implements IHttpEndpoints {

@override
Future<void> editGuildMember(Snowflake guildId, Snowflake memberId,
{String? nick = "", List<SnowflakeEntity>? roles, bool? mute, bool? deaf, Snowflake? channel = const Snowflake.zero(), String? auditReason}) {
final body = <String, dynamic>{
if (nick != "") "nick": nick,
if (roles != null) "roles": roles.map((f) => f.id.toString()).toList(),
if (mute != null) "mute": mute,
if (deaf != null) "deaf": deaf,
if (channel == null || !channel.isZero) "channel_id": channel.toString()
};

return executeSafe(BasicRequest("/guilds/$guildId/members/$memberId", method: "PATCH", auditLog: auditReason, body: body));
{String? nick = "",
List<SnowflakeEntity>? roles,
bool? mute,
bool? deaf,
Snowflake? channel = const Snowflake.zero(),
MemberBuilder? builder,
String? auditReason}) {
final finalBuilder = builder ?? MemberBuilder()
..nick = nick
..roles = roles?.map((e) => e.id).toList()
..mute = mute
..deaf = deaf
..channel = channel;

return executeSafe(BasicRequest("/guilds/$guildId/members/$memberId", method: "PATCH", auditLog: auditReason, body: finalBuilder));
}

@override
Expand Down
2 changes: 1 addition & 1 deletion lib/src/internal/shard/shard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,7 @@ class Shard implements IShard {
"guild_subscriptions": manager.connectionManager.client.options.guildSubscriptions,
"intents": manager.connectionManager.client.intents,
if (manager.connectionManager.client.options.initialPresence != null) "presence": manager.connectionManager.client.options.initialPresence!.build(),
"shard": <int>[id, manager.numShards]
"shard": <int>[id, manager.totalNumShards]
};

send(OPCodes.identify, identifyMsg);
Expand Down
47 changes: 39 additions & 8 deletions lib/src/internal/shard/shard_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ abstract class IShardManager implements Disposable {
/// Number of shards spawned
int get numShards;

/// Total number of shards for this client
int get totalNumShards;

/// Sets presences on every shard
void setPresence(PresenceBuilder presenceBuilder);
}
Expand Down Expand Up @@ -109,6 +112,10 @@ class ShardManager implements IShardManager {
@override
late final int numShards;

/// Total number of shards for this client
@override
late final int totalNumShards;

final Map<int, Shard> _shards = {};

Duration get _identifyDelay {
Expand All @@ -119,14 +126,36 @@ class ShardManager implements IShardManager {

/// Starts shard manager
ShardManager(this.connectionManager, this.maxConcurrency) {
numShards = connectionManager.client.options.shardCount != null ? connectionManager.client.options.shardCount! : connectionManager.recommendedShardsNum;
totalNumShards = connectionManager.client.options.shardCount ?? connectionManager.recommendedShardsNum;
numShards = connectionManager.client.options.shardIds?.length ?? totalNumShards;

if (numShards < 1) {
if (totalNumShards < 1) {
throw UnrecoverableNyxxError("Number of shards cannot be lower than 1.");
}

List<int> toSpawn = _getShardsToSpawn();

logger.fine("Starting shard manager. Number of shards to spawn: $numShards");
_connect(numShards - 1);
_connect(toSpawn);
}

List<int> _getShardsToSpawn() {
if (connectionManager.client.options.shardIds != null) {
if (connectionManager.client.options.shardCount == null) {
throw UnrecoverableNyxxError('Cannot specify shards to spawn without specifying total number of shards');
}

for (final id in connectionManager.client.options.shardIds!) {
if (id < 0 || id >= totalNumShards) {
throw UnrecoverableNyxxError('Invalid shard ID: $id');
}
}

// Clone list to prevent original list from being modified with removeLast()
return List.of(connectionManager.client.options.shardIds!);
} else {
return List.generate(totalNumShards, (id) => id);
}
}

/// Sets presences on every shard
Expand All @@ -137,16 +166,18 @@ class ShardManager implements IShardManager {
}
}

void _connect(int shardId) {
logger.fine("Setting up shard with id: $shardId");

if (shardId < 0) {
void _connect(List<int> toSpawn) {
if (toSpawn.isEmpty) {
return;
}

int shardId = toSpawn.removeLast();

logger.fine("Setting up shard with id: $shardId");

_shards[shardId] = Shard(shardId, this, connectionManager.gateway);

Future.delayed(_identifyDelay, () => _connect(shardId - 1));
Future.delayed(_identifyDelay, () => _connect(toSpawn));
}

@override
Expand Down
2 changes: 1 addition & 1 deletion lib/src/nyxx.dart
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ class NyxxWebsocket extends NyxxRest implements INyxxWebsocket {
ignoreExceptions: ignoreExceptions,
useDefaultLogger: useDefaultLogger,
) {
eventsWs = WebsocketEventController();
eventsWs = WebsocketEventController(this);
}

@override
Expand Down
1 change: 0 additions & 1 deletion lib/src/plugin/plugins/ignore_exception.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'dart:isolate';

import 'package:logging/logging.dart';
Expand Down
Loading

0 comments on commit 32b3d2d

Please sign in to comment.