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

Deploy 6.0.3 #594

Merged
merged 4 commits into from
Nov 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## 6.0.3
__26.11.2023__

- bug: Fix incorrect serialization of autocompletion interaction responses (again)
- bug: Try to fix invalid sessions triggered by Gateway reconnects

## 6.0.2
__16.11.2023__

Expand Down
2 changes: 1 addition & 1 deletion lib/nyxx.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export 'src/http/managers/interaction_manager.dart' show InteractionManager;
export 'src/http/managers/entitlement_manager.dart' show EntitlementManager;

export 'src/gateway/gateway.dart' show Gateway;
export 'src/gateway/message.dart' show Disconnecting, Dispose, ErrorReceived, EventReceived, GatewayMessage, Send, ShardData, ShardMessage;
export 'src/gateway/message.dart' show Disconnecting, Dispose, ErrorReceived, EventReceived, GatewayMessage, Send, Sent, ShardData, ShardMessage;
export 'src/gateway/shard.dart' show Shard;

export 'src/models/discord_color.dart' show DiscordColor;
Expand Down
2 changes: 1 addition & 1 deletion lib/src/api_options.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:oauth2/oauth2.dart';
/// Options for connecting to the Discord API.
abstract class ApiOptions {
/// The version of nyxx used in [defaultUserAgent].
static const nyxxVersion = '6.0.2';
static const nyxxVersion = '6.0.3';

/// The URL to the nyxx repository used in [defaultUserAgent].
static const nyxxRepositoryUrl = 'https://github.com/nyxx-discord/nyxx';
Expand Down
2 changes: 1 addition & 1 deletion lib/src/builders/interaction_response.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class InteractionResponseBuilder extends CreateBuilder<InteractionResponseBuilde

factory InteractionResponseBuilder.autocompleteResult(List<CommandOptionChoiceBuilder<dynamic>> choices) => InteractionResponseBuilder(
type: InteractionCallbackType.applicationCommandAutocompleteResult,
data: {'choices': choices},
data: {'choices': choices.map((e) => e.build()).toList()},
);

factory InteractionResponseBuilder.modal(ModalBuilder modal) => InteractionResponseBuilder(type: InteractionCallbackType.modal, data: modal);
Expand Down
9 changes: 2 additions & 7 deletions lib/src/gateway/gateway.dart
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,7 @@ class Gateway extends GatewayManager with EventParser {
Gateway(this.client, this.gatewayBot, this.shards, this.totalShards, this.shardIds) : super.create() {
for (final shard in shards) {
shard.listen(
(message) {
if (message is ErrorReceived) {
shard.logger.warning('Received error: ${message.error}', message.error, message.stackTrace);
}

_messagesController.add(message);
},
_messagesController.add,
onError: _messagesController.addError,
onDone: () async {
if (_closing) {
Expand All @@ -109,6 +103,7 @@ class Gateway extends GatewayManager with EventParser {

throw ShardDisconnectedError(shard);
},
cancelOnError: false,
);
}

Expand Down
9 changes: 9 additions & 0 deletions lib/src/gateway/message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ class Disconnecting extends ShardMessage {
Disconnecting({required this.reason});
}

/// A shard message sent when the shard adds a payload to the connection.
class Sent extends ShardMessage {
/// The payload that was sent.
final Send payload;

/// Create a new [Sent].
Sent({required this.payload});
}

/// The base class for all control messages sent from the client to the shard.
abstract class GatewayMessage with ToStringHelper {}

Expand Down
10 changes: 7 additions & 3 deletions lib/src/gateway/shard.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ class Shard extends Stream<ShardMessage> implements StreamSink<GatewayMessage> {
/// Create a new [Shard].
Shard(this.id, this.isolate, this.receiveStream, this.sendPort, this.client) {
final subscription = listen((message) {
if (message is ErrorReceived) {
if (message is Sent) {
logger
..fine('Sent payload: ${message.payload.opcode.name}')
..finer('Opcode: ${message.payload.opcode.value}, Data: ${message.payload.data}');
} else if (message is ErrorReceived) {
logger.warning('Error: ${message.error}', message.error, message.stackTrace);
} else if (message is Disconnecting) {
logger.info('Disconnecting: ${message.reason}');
Expand All @@ -61,7 +65,7 @@ class Shard extends Stream<ShardMessage> implements StreamSink<GatewayMessage> {
if (isResumable) {
logger.info('Reconnecting: invalid session');
} else {
logger.severe('Unresumable invalid session, disconnecting');
logger.warning('Reconnecting: unresumable invalid session');
}
case HelloEvent(:final heartbeatInterval):
logger.finest('Heartbeat Interval: $heartbeatInterval');
Expand Down Expand Up @@ -141,7 +145,7 @@ class Shard extends Stream<ShardMessage> implements StreamSink<GatewayMessage> {
void add(GatewayMessage event) {
if (event is Send) {
logger
..fine('Send: ${event.opcode.name}')
..fine('Sending: ${event.opcode.name}')
..finer('Opcode: ${event.opcode.value}, Data: ${event.data}');
} else if (event is Dispose) {
logger.info('Disposing');
Expand Down
29 changes: 19 additions & 10 deletions lib/src/gateway/shard_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class ShardRunner {
final controller = StreamController<ShardMessage>();

// The subscription to the control messages stream.
// This subscription is paused whenever the shard is not successfully connected,.
// This subscription is paused whenever the shard is not successfully connected.
final controlSubscription = messages.listen((message) {
if (message is Send) {
connection!.add(message);
Expand Down Expand Up @@ -87,6 +87,7 @@ class ShardRunner {

// Open the websocket connection.
connection = await ShardConnection.connect(gatewayUri.toString(), this);
connection!.onSent.listen(controller.add);

// Obtain the heartbeat interval from the HELLO event and start heartbeating.
final hello = await connection!.first;
Expand All @@ -105,7 +106,7 @@ class ShardRunner {
sendIdentify();
}

canResume = false;
canResume = true;

// We are connected, start handling control messages.
controlSubscription.resume();
Expand All @@ -127,7 +128,7 @@ class ShardRunner {
}
} else if (event is ReconnectEvent) {
canResume = true;
connection!.close();
connection!.close(4000);
} else if (event is InvalidSessionEvent) {
if (event.isResumable) {
canResume = true;
Expand All @@ -136,7 +137,8 @@ class ShardRunner {
gatewayUri = originalGatewayUri;
}

connection!.close();
// Don't use 4000 as it will always try to resume
connection!.close(4999);
} else if (event is HeartbeatAckEvent) {
lastHeartbeatAcked = true;
heartbeatStopwatch = null;
Expand All @@ -159,8 +161,7 @@ class ShardRunner {
// Check if we can resume based on close code.
// A manual close where we set closeCode earlier would have a close code of 1000, so this
// doesn't change closeCode if we set it manually.
// 1001 is the close code used for a ping failure, so include it in the resumable codes.
const resumableCodes = [null, 1001, 4000, 4001, 4002, 4003, 4007, 4008, 4009];
const resumableCodes = [null, 4000, 4001, 4002, 4003, 4007, 4008, 4009];
final closeCode = connection!.websocket.closeCode;
canResume = canResume || resumableCodes.contains(closeCode);

Expand All @@ -171,9 +172,11 @@ class ShardRunner {
}
} catch (error, stackTrace) {
controller.add(ErrorReceived(error: error, stackTrace: stackTrace));
// Prevents the while-true loop from looping too often when no internet is available.
await Future.delayed(Duration(milliseconds: 100));
} finally {
// Reset connection properties.
connection?.close();
connection?.close(4000);
connection = null;
heartbeatTimer?.cancel();
heartbeatTimer = null;
Expand Down Expand Up @@ -246,11 +249,13 @@ class ShardConnection extends Stream<GatewayEvent> implements StreamSink<Send> {
final Stream<GatewayEvent> events;
final ShardRunner runner;

final StreamController<Sent> _sentController = StreamController();
Stream<Sent> get onSent => _sentController.stream;

ShardConnection(this.websocket, this.events, this.runner);

static Future<ShardConnection> connect(String gatewayUri, ShardRunner runner) async {
final connection = await WebSocket.connect(gatewayUri);
connection.pingInterval = const Duration(seconds: 20);

final uncompressedStream = switch (runner.data.apiOptions.compression) {
GatewayCompression.transport => decompressTransport(connection.cast<List<int>>()),
Expand Down Expand Up @@ -293,6 +298,7 @@ class ShardConnection extends Stream<GatewayEvent> implements StreamSink<Send> {
};

websocket.add(encoded);
_sentController.add(Sent(payload: event));
}

@override
Expand All @@ -302,10 +308,13 @@ class ShardConnection extends Stream<GatewayEvent> implements StreamSink<Send> {
Future<void> addStream(Stream<Send> stream) => stream.forEach(add);

@override
Future<void> close([int? code]) => websocket.close(code ?? 1000);
Future<void> close([int? code]) async {
await websocket.close(code ?? 1000);
await _sentController.close();
}

@override
Future<void> get done => websocket.done;
Future<void> get done => websocket.done.then((_) => _sentController.done);
}

Stream<dynamic> decompressTransport(Stream<List<int>> raw) {
Expand Down
30 changes: 27 additions & 3 deletions lib/src/models/permissions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ class Permissions extends Flags<Permissions> {
/// Allows management and editing of webhooks.
static const manageWebhooks = Flag<Permissions>.fromOffset(29);

/// Allows management and editing of emojis, stickers, and soundboard sounds.
/// Allows for editing and deleting emojis, stickers, and soundboard sounds created by all users.
static const manageEmojisAndStickers = Flag<Permissions>.fromOffset(30);

/// Allows members to use application commands, including slash commands and context menu commands..
Expand All @@ -104,7 +104,7 @@ class Permissions extends Flags<Permissions> {
/// Allows for requesting to speak in stage channels. (This permission is under active development and may be changed or removed.).
static const requestToSpeak = Flag<Permissions>.fromOffset(32);

/// Allows for creating, editing, and deleting scheduled events.
/// Allows for editing and deleting scheduled events created by all users.
static const manageEvents = Flag<Permissions>.fromOffset(33);

/// Allows for deleting and archiving threads, and viewing all private threads.
Expand Down Expand Up @@ -134,8 +134,20 @@ class Permissions extends Flags<Permissions> {
/// Allows for using soundboard in a voice channel.
static const useSoundboard = Flag<Permissions>.fromOffset(42);

/// Allows for creating emojis, stickers, and soundboard sounds, and editing and deleting those created by the current user.
static const createEmojiAndStickers = Flag<Permissions>.fromOffset(43);

/// Allows for creating scheduled events, and editing and deleting those created by the current user.
static const createEvents = Flag<Permissions>.fromOffset(44);

/// Allows the usage of custom soundboard sounds from other servers.
static const useExternalSounds = Flag<Permissions>.fromOffset(45);

/// Allows sending voice messages.
static const sendVoiceMessages = Flag<Permissions>.fromOffset(46);

/// A [Permissions] with all permissions enabled.
static const allPermissions = Permissions(1099511627775);
static const allPermissions = Permissions(140737488355327);

/// Whether this set of permissions has the [createInstantInvite] permission.
bool get canCreateInstantInvite => has(createInstantInvite);
Expand Down Expand Up @@ -266,6 +278,18 @@ class Permissions extends Flags<Permissions> {
/// Whether this set of permissions has the [useSoundboard] permission.
bool get canUseSoundboard => has(useSoundboard);

/// Whether this set of permissions has the [createEmojiAndStickers] permission.
bool get canCreateEmojiAndStickers => has(createEmojiAndStickers);

/// Whether this set of permissions has the [createEvents] permission.
bool get canCreateEvents => has(createEvents);

/// Whether this set of permissions has the [useExternalSounds] permission.
bool get canUseExternalSounds => has(useExternalSounds);

/// Whether this set of permissions has the [sendVoiceMessages] permission.
bool get canSendVoiceMessages => has(sendVoiceMessages);

/// Create a new [Permissions] from a permissions value.
const Permissions(super.value);
}
2 changes: 1 addition & 1 deletion lib/src/plugin/logging.dart
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ class Logging extends NyxxPlugin {
}
}

final outSink = rec.level > stderrLevel ? stderr : stdout;
final outSink = rec.level >= stderrLevel ? stderr : stdout;
outSink.write(messageString);
});
}
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: nyxx
version: 6.0.2
version: 6.0.3
description: A complete, robust and efficient wrapper around Discord's API for bots & applications.
homepage: https://github.com/nyxx-discord/nyxx
repository: https://github.com/nyxx-discord/nyxx
Expand Down
20 changes: 20 additions & 0 deletions test/unit/builders/interaction_response_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:nyxx/nyxx.dart';
import 'package:test/test.dart';

void main() {
group('InteractionResponseBuilder', () {
test('autocompleteResult', () {
expect(
InteractionResponseBuilder.autocompleteResult([CommandOptionChoiceBuilder(name: 'foo', value: 'bar')]).build(),
equals({
'type': 8,
'data': {
'choices': [
{'name': 'foo', 'value': 'bar'},
]
}
}),
);
});
});
}
Loading