Skip to content

Commit

Permalink
Merge pull request #406 from nyxx-discord/dev
Browse files Browse the repository at this point in the history
Release 4.3.0
  • Loading branch information
l7ssha authored Nov 20, 2022
2 parents ce7ea8b + 338144b commit 0333157
Show file tree
Hide file tree
Showing 22 changed files with 276 additions and 101 deletions.
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,34 @@
## 4.3.0
__19.11.2022__

- feature: Add retry with backoff to network operations (gateway and http) (#395)
- feature: automoderation regexes (#393)
- feature: add support for interaction webhooks (#397)
- feature: Forward `RetryOptions`
- bug: Fixed bug when getting IInviteWithMeta (#398)
- bug: Emit bot start to plugins only when ready
- bug: fix builder not building when editing a guild member (#405)

## 4.3.0-dev.1
__15.11.2022__

- feature: add support for interaction webhooks (#397)
- bug: Fixed bug when getting IInviteWithMeta (#398)

This version also includes fixes from 4.2.1

## 4.2.1
__15.11.2022__

- hotfix: fix component deserialization failing when `customId` is `null`

## 4.3.0-dev.0
__14.11.2022__

- feature: Add retry with backoff to network operations (gateway and http) (#395)
- feature: automoderation regexes (#393)
- bug: Emit bot start to plugins only when ready

## 4.2.0
__13.11.2022__

Expand Down
2 changes: 1 addition & 1 deletion analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ linter:
implementation_imports: false

analyzer:
exclude: [build/**, example/**]
exclude: [build/**]
language:
strict-raw-types: true
strong-mode:
Expand Down
2 changes: 1 addition & 1 deletion example/embeds.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ void main() async {
final bot = NyxxFactory.createNyxxWebsocket("<TOKEN>", GatewayIntents.allUnprivileged | GatewayIntents.messageContent) // Here we use the privilegied intent message content to receive incoming messages.
..registerPlugin(Logging()) // Default logging plugin
..registerPlugin(CliIntegration()) // Cli integration for nyxx allows stopping application via SIGTERM and SIGKILl
..registerPlugin(IgnoreExceptions()) // Plugin that handles uncaught exceptions that may occur
..registerPlugin(IgnoreExceptions()); // Plugin that handles uncaught exceptions that may occur

// Listen to ready event. Invoked when bot is connected to all shards. Note that cache can be empty or not incomplete.
bot.eventsWs.onReady.listen((IReadyEvent e) {
Expand Down
3 changes: 2 additions & 1 deletion example/permissions.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// ignore_for_file: unused_local_variable
import "package:nyxx/nyxx.dart";

// Main function
Expand Down Expand Up @@ -34,7 +35,7 @@ void main() {
final permissions = await messageChannel.effectivePermissions(member);

// Get current member permissions as builder
final permissionsAsBuilder = permissions.toBuilder()..sendMessages = true;
final permissionsAsBuilder = permissions.toBuilder()..sendMessages = true; // @ig

// Get first channel override as builder and edit sendMessages property to allow sending messages for entities included in this override
final channelOverridesAsBuilder = messageChannel.permissionOverrides.first.toBuilder()..sendMessages = true;
Expand Down
32 changes: 32 additions & 0 deletions example/private_emoji.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//<:Pepega:547759324836003842>

import "package:nyxx/nyxx.dart";
import 'dart:io';

// Main function
void main() {
// Create new bot instance
final bot = NyxxFactory.createNyxxWebsocket(Platform.environment['BOT_TOKEN']!, GatewayIntents.allUnprivileged | GatewayIntents.messageContent)
..registerPlugin(Logging()) // Default logging plugin
..registerPlugin(CliIntegration()) // Cli integration for nyxx allows stopping application via SIGTERM and SIGKILl
..registerPlugin(IgnoreExceptions()) // Plugin that handles uncaught exceptions that may occur
..connect();

// Listen to all incoming messages
bot.eventsWs.onMessageReceived.listen((e) async {
// Check if message content equals "!ping"
if (e.message.content == "!ping") {
bot.httpEndpoints.fetchChannel(Snowflake(961916452967944223));

e.message.guild?.getFromCache()?.shard;
// Send "Pong!" to channel where message was received
e.message.channel.sendMessage(MessageBuilder.content(IBaseGuildEmoji.fromId(Snowflake(502563517774299156)).formatForMessage()));
}

print(await (await e.message.guild?.getOrDownload())! .getBans().toList());

if (e.message.content == "!create-thread") {
bot.httpEndpoints.startForumThread(Snowflake(961916452967944223), ForumThreadBuilder('test', MessageBuilder.content('this is test content <@${e.message.author.id}>')));
}
});
}
3 changes: 3 additions & 0 deletions lib/nyxx.dart
Original file line number Diff line number Diff line change
Expand Up @@ -197,3 +197,6 @@ export 'src/plugin/plugin_manager.dart' show IPluginManager;
export 'src/plugin/plugins/cli_integration.dart' show CliIntegration;
export 'src/plugin/plugins/ignore_exception.dart' show IgnoreExceptions;
export 'src/plugin/plugins/logging.dart' show Logging;

// Forward `RetryOptions` to allow the usage of the class without importing the package
export 'package:retry/retry.dart' show RetryOptions;
37 changes: 25 additions & 12 deletions lib/src/client_options.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:nyxx/nyxx.dart';
import 'package:nyxx/src/nyxx.dart';
import 'package:nyxx/src/internal/shard/shard.dart';
import 'package:retry/retry.dart';

/// Options for configuring cache. Allows to specify where and which entities should be cached and preserved in cache
class CacheOptions {
Expand Down Expand Up @@ -69,19 +70,31 @@ class ClientOptions {
/// Allows to enable receiving raw gateway event
bool dispatchRawShardEvent;

/// The [RetryOptions] to use when a shard fails to connect to the gateway.
RetryOptions shardReconnectOptions;

/// The [RetryOptions] to use when a HTTP request fails.
///
/// Note that this will not retry requests that fail because of their HTTP response code (e.g a 4xx response) but rather requests that fail due to native
/// errors (e.g failed host lookup) which can occur if there is no internet.
RetryOptions httpRetryOptions;

/// Makes a new `ClientOptions` object.
ClientOptions(
{this.allowedMentions,
this.shardCount,
this.messageCacheSize = 100,
this.largeThreshold = 50,
this.compressedGatewayPayloads = true,
this.guildSubscriptions = true,
this.initialPresence,
this.shutdownHook,
this.shutdownShardHook,
this.dispatchRawShardEvent = false,
this.shardIds});
ClientOptions({
this.allowedMentions,
this.shardCount,
this.messageCacheSize = 100,
this.largeThreshold = 50,
this.compressedGatewayPayloads = true,
this.guildSubscriptions = true,
this.initialPresence,
this.shutdownHook,
this.shutdownShardHook,
this.dispatchRawShardEvent = false,
this.shardIds,
this.shardReconnectOptions = const RetryOptions(),
this.httpRetryOptions = const RetryOptions(),
});
}

/// When identifying to the gateway, you can specify an intents parameter which
Expand Down
12 changes: 10 additions & 2 deletions lib/src/core/guild/auto_moderation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,11 @@ abstract class ITriggerMetadata {
/// The total number of mentions (either role and user) allowed per message.
/// (Maximum of 50)
/// The associated trigger type is [TriggerTypes.mentionSpam]
// Pr still not merged
int? get mentionLimit;

/// Regular expression patterns which will be matched against content
/// The associated trigger type is [TriggerTypes.keyword]
Iterable<String>? get regexPatterns;
}

abstract class IActionStructure {
Expand Down Expand Up @@ -218,16 +221,21 @@ class TriggerMetadata implements ITriggerMetadata {
@override
late final int? mentionLimit;

@override
late final Iterable<String>? regexPatterns;

/// Creates an instance of [TriggerMetadata]
TriggerMetadata(RawApiMap data) {
keywordsFilter = data['keyword_filter'] != null ? (data['keyword_filter'] as RawApiList).cast<String>() : null;
keywordPresets = data['presets'] != null ? (data['presets'] as RawApiList).map((p) => KeywordPresets._fromValue(p as int)) : null;
allowList = (data['allow_list'] as RawApiList?)?.cast<String>().toList();
mentionLimit = data['mention_total_limit'] as int?;
regexPatterns = data['regex_patterns'] != null ? (data['regex_patterns'] as RawApiList).cast<String>() : null;
}

@override
String toString() => 'ITriggerMetadata(keywordPresets: $keywordPresets, keywordFilter: $keywordsFilter, allowList: $allowList, mentionLimit: $mentionLimit)';
String toString() =>
'ITriggerMetadata(keywordPresets: $keywordPresets, keywordFilter: $keywordsFilter, allowList: $allowList, mentionLimit: $mentionLimit, regexPatterns: $regexPatterns)';
}

class ActionStructure implements IActionStructure {
Expand Down
11 changes: 9 additions & 2 deletions lib/src/core/guild/webhook.dart
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class Webhook extends SnowflakeEntity implements IWebhook {
String get username => name.toString();

@override
int get discriminator => -1;
late final int discriminator;

@override
bool get bot => true;
Expand All @@ -139,11 +139,18 @@ class Webhook extends SnowflakeEntity implements IWebhook {
@override
final INyxx client;

@override
bool get isInteractionWebhook => discriminator != -1;

@override
String get formattedDiscriminator => discriminator.toString().padLeft(4, "0");

/// Creates an instance of [Webhook]
Webhook(RawApiMap raw, this.client) : super(Snowflake(raw["id"] as String)) {
name = raw["name"] as String?;
name = raw["name"] as String? ?? raw['username'] as String?;
token = raw["token"] as String? ?? "";
avatarHash = raw["avatar"] as String?;
discriminator = int.tryParse(raw['discriminator'] as String? ?? '-1') ?? -1;

if (raw["type"] != null) {
type = WebhookType.from(raw["type"] as int);
Expand Down
6 changes: 3 additions & 3 deletions lib/src/core/user/user.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,6 @@ abstract class IUser implements SnowflakeEntity, ISend, Mentionable, IMessageAut
/// Reference to client
INyxx get client;

/// Formatted discriminator with leading zeros if needed
String get formattedDiscriminator;

/// The user's avatar hash.
String? get avatar;

Expand Down Expand Up @@ -118,6 +115,9 @@ class User extends SnowflakeEntity implements IUser {
@override
late final DiscordColor? accentColor;

@override
bool get isInteractionWebhook => false;

/// Creates an instance of [User]
User(this.client, RawApiMap raw) : super(Snowflake(raw["id"])) {
username = raw["username"] as String;
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 = 10;

/// Version of Nyxx
static const String version = "4.2.1";
static const String version = "4.3.0";

/// Url to Nyxx repo
static const String repoUrl = "https://github.com/nyxx-discord/nyxx";
Expand Down
1 change: 1 addition & 0 deletions lib/src/internal/exceptions/http_client_exception.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:http/http.dart' as http;

/// Exception of http client
@Deprecated('Unused, will be removed in the next major release')
class HttpClientException extends http.ClientException {
/// Raw response from server
final http.BaseResponse? response;
Expand Down
27 changes: 8 additions & 19 deletions lib/src/internal/http/http_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -78,25 +78,14 @@ class HttpHandler {
}

// Execute request
try {
currentBucket?.addInFlightRequest(request);
final response = await httpClient.send(await request.prepareRequest());
currentBucket?.removeInFlightRequest(request);
currentBucket = _upsertBucket(request, response);
return _handle(request, response);
} on HttpClientException catch (e) {
currentBucket?.removeInFlightRequest(request);
if (e.response == null) {
logger.warning("Http Error on endpoint: ${request.uri}. Error: [${e.message.toString()}].");
rethrow;
}

final response = e.response as http.StreamedResponse;
_upsertBucket(request, response);

// Return http error
return _handle(request, response);
}
currentBucket?.addInFlightRequest(request);
final response = await client.options.httpRetryOptions.retry(
() async => httpClient.send(await request.prepareRequest()),
onRetry: (ex) => logger.shout('Exception when sending HTTP request (retrying automatically): $ex'),
);
currentBucket?.removeInFlightRequest(request);
currentBucket = _upsertBucket(request, response);
return _handle(request, response);
}

Future<HttpResponse> _handle(HttpRequest request, http.StreamedResponse response) async {
Expand Down
2 changes: 1 addition & 1 deletion lib/src/internal/http/http_route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class HttpRoute implements IHttpRoute {
])
.toList();

String get path => "/" + pathSegments.join("/");
String get path => "/${pathSegments.join("/")}";

String get routeId => _httpRouteParts
.expand((part) => [
Expand Down
10 changes: 5 additions & 5 deletions lib/src/internal/http_endpoints.dart
Original file line number Diff line number Diff line change
Expand Up @@ -978,7 +978,7 @@ class HttpEndpoints implements IHttpEndpoints {
Future<IUser> fetchUser(Snowflake userId) async {
final response = await executeSafe(BasicRequest(HttpRoute()..users(id: userId.toString())));

final user = User(client, (response as HttpResponseSuccess).jsonBody as RawApiMap);
final user = User(client, response.jsonBody as RawApiMap);

if (client.cacheOptions.userCachePolicyLocation.http) {
client.users[user.id] = user;
Expand All @@ -995,7 +995,7 @@ class HttpEndpoints implements IHttpEndpoints {
..members(id: memberId.toString()),
method: "PATCH",
auditLog: auditReason,
body: builder));
body: builder.build()));
}

@override
Expand All @@ -1015,10 +1015,10 @@ class HttpEndpoints implements IHttpEndpoints {
..invites(),
));

final bodyValues = response.jsonBody.values.first;
final bodyValues = response.jsonBody;

for (final val in bodyValues as Iterable<RawApiMap>) {
yield InviteWithMeta(val, client);
for (final val in bodyValues) {
yield InviteWithMeta(val as RawApiMap, client);
}
}

Expand Down
6 changes: 6 additions & 0 deletions lib/src/internal/interfaces/message_author.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ abstract class IMessageAuthor implements SnowflakeEntity {
/// User tag: `l7ssha#6712`
String get tag;

/// Whether this [IMessageAuthor] is a webhook received by an interaction.
bool get isInteractionWebhook;

/// Formatted discriminator with leading zeros if needed
String get formattedDiscriminator;

/// Url to user avatar
String avatarURL({String format = "webp", int size = 128});
}
7 changes: 6 additions & 1 deletion lib/src/internal/shard/message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ class ShardMessage<T> {
final T type;
final dynamic data;

const ShardMessage(this.type, {this.data});
final int seq;

const ShardMessage(this.type, {required this.seq, this.data});
}

enum ShardToManager {
Expand All @@ -28,6 +30,9 @@ enum ShardToManager {
/// Sent when the shard is connected
connected,

/// Send when the shard successfully reconnects
reconnected,

/// Send when the shard is disconnected
///
/// Data payload includes:
Expand Down
Loading

0 comments on commit 0333157

Please sign in to comment.