Fix dart analyzer minor problems; Add some docs; Allow Dart 2.13; rename onRatelimited -> onRateLimited

This commit is contained in:
Szymon Uglis 2021-04-27 00:37:04 +02:00
parent 9555a73447
commit cb2472a842
No known key found for this signature in database
GPG key ID: F68E85F9123D2EBC
29 changed files with 171 additions and 123 deletions

21
nyxx/example/example.dart Normal file
View file

@ -0,0 +1,21 @@
import "package:nyxx/nyxx.dart";
// Main function
void main() {
// Create new bot instance
final bot = Nyxx("<TOKEN>", GatewayIntents.none);
// Listen to ready event. Invoked when bot is connected to all shards. Note that cache can be empty or not incomplete.
bot.onReady.listen((e) {
print("Ready!");
});
// Listen to all incoming messages
bot.onMessageReceived.listen((e) {
// Check if message content equals "!ping"
if (e.message.content == "!ping") {
// Send "Pong!" to channel where message was received
e.message.channel.getFromCache()?.sendMessage(content: "Pong!");
}
});
}

View file

@ -1,5 +1,6 @@
part of nyxx;
/// Generic interface for Nyxx. Represents basic functionality of Nyxx that are always available.
abstract class INyxx implements Disposable {
_HttpHandler get _http;
_HttpEndpoints get _httpEndpoints;
@ -28,9 +29,9 @@ abstract class INyxx implements Disposable {
/// Emitted when a HTTP request failed.
late final StreamController<HttpErrorEvent> _onHttpError;
/// Sent when the client is ratelimited, either by the ratelimit handler itself,
/// Sent when the client is rate limited, either by the rate limit handler itself,
/// or when a 429 is received.
late final StreamController<RatelimitEvent> _onRatelimited;
late final StreamController<RatelimitEvent> _onRateLimited;
/// Emitted when a successful HTTP response is received.
late Stream<HttpResponseEvent> onHttpResponse;
@ -38,9 +39,9 @@ abstract class INyxx implements Disposable {
/// Emitted when a HTTP request failed.
late Stream<HttpErrorEvent> onHttpError;
/// Sent when the client is ratelimited, either by the ratelimit handler itself,
/// Sent when the client is rate limited, either by the rate limit handler itself,
/// or when a 429 is received.
late Stream<RatelimitEvent> onRatelimited;
late Stream<RatelimitEvent> onRateLimited;
}
/// Lightweight client which do not start ws connections.
@ -158,15 +159,15 @@ class NyxxRest extends INyxx {
this._onHttpResponse = StreamController.broadcast();
this.onHttpResponse = _onHttpResponse.stream;
this._onRatelimited = StreamController.broadcast();
this.onRatelimited = _onRatelimited.stream;
this._onRateLimited = StreamController.broadcast();
this.onRateLimited = _onRateLimited.stream;
}
@override
Future<void> dispose() async {
await this._onHttpResponse.close();
await this._onHttpError.close();
await this._onRatelimited.close();
await this._onRateLimited.close();
}
}

View file

@ -22,66 +22,70 @@ class AuditLogChange {
oldValue = raw["old_value"];
}
this.key = ChangeKeyType(raw["key"] as String);
this.key = ChangeKeyType.from(raw["key"] as String);
}
}
/// Type of change in audit log
class ChangeKeyType extends IEnum<String> {
static const ChangeKeyType name = ChangeKeyType._of("name");
static const ChangeKeyType iconHash = ChangeKeyType._of("icon_hash");
static const ChangeKeyType splashHash = ChangeKeyType._of("splash_hash");
static const ChangeKeyType ownerId = ChangeKeyType._of("owner_id");
static const ChangeKeyType region = ChangeKeyType._of("region");
static const ChangeKeyType afkChannelId = ChangeKeyType._of("afk_channel_id");
static const ChangeKeyType afkTimeout = ChangeKeyType._of("afk_timeout");
static const ChangeKeyType mfaLevel = ChangeKeyType._of("mfa_level");
static const ChangeKeyType verificationLevel = ChangeKeyType._of("verification_level");
static const ChangeKeyType explicitContentFilter = ChangeKeyType._of("explicit_content_filter");
static const ChangeKeyType defaultMessageNotifications = ChangeKeyType._of("default_message_notifications");
static const ChangeKeyType $add = ChangeKeyType._of("\$add");
static const ChangeKeyType $remove = ChangeKeyType._of("\$remove");
static const ChangeKeyType pruneDeleteDays = ChangeKeyType._of("prune_delete_days");
static const ChangeKeyType widgetEnabled = ChangeKeyType._of("widget_enabled");
static const ChangeKeyType widgetChannelId = ChangeKeyType._of("widget_channel_id");
static const ChangeKeyType position = ChangeKeyType._of("position");
static const ChangeKeyType topic = ChangeKeyType._of("topic");
static const ChangeKeyType bitrate = ChangeKeyType._of("bitrate");
static const ChangeKeyType slowmode = ChangeKeyType._of("rate_limit_per_user");
static const ChangeKeyType permissionOverwrites = ChangeKeyType._of("permission_overwrites");
static const ChangeKeyType nsfw = ChangeKeyType._of("nsfw");
static const ChangeKeyType applicationId = ChangeKeyType._of("application_id");
static const ChangeKeyType permissions = ChangeKeyType._of("permissions");
static const ChangeKeyType color = ChangeKeyType._of("color");
static const ChangeKeyType hoist = ChangeKeyType._of("hoist");
static const ChangeKeyType mentionable = ChangeKeyType._of("mentionable");
static const ChangeKeyType name = ChangeKeyType._create("name");
static const ChangeKeyType iconHash = ChangeKeyType._create("icon_hash");
static const ChangeKeyType splashHash = ChangeKeyType._create("splash_hash");
static const ChangeKeyType ownerId = ChangeKeyType._create("owner_id");
static const ChangeKeyType region = ChangeKeyType._create("region");
static const ChangeKeyType afkChannelId = ChangeKeyType._create("afk_channel_id");
static const ChangeKeyType afkTimeout = ChangeKeyType._create("afk_timeout");
static const ChangeKeyType mfaLevel = ChangeKeyType._create("mfa_level");
static const ChangeKeyType verificationLevel = ChangeKeyType._create("verification_level");
static const ChangeKeyType explicitContentFilter = ChangeKeyType._create("explicit_content_filter");
static const ChangeKeyType defaultMessageNotifications = ChangeKeyType._create("default_message_notifications");
static const ChangeKeyType $add = ChangeKeyType._create("\$add");
static const ChangeKeyType $remove = ChangeKeyType._create("\$remove");
static const ChangeKeyType pruneDeleteDays = ChangeKeyType._create("prune_delete_days");
static const ChangeKeyType widgetEnabled = ChangeKeyType._create("widget_enabled");
static const ChangeKeyType widgetChannelId = ChangeKeyType._create("widget_channel_id");
static const ChangeKeyType position = ChangeKeyType._create("position");
static const ChangeKeyType topic = ChangeKeyType._create("topic");
static const ChangeKeyType bitrate = ChangeKeyType._create("bitrate");
static const ChangeKeyType slowmode = ChangeKeyType._create("rate_limit_per_user");
static const ChangeKeyType permissionOverwrites = ChangeKeyType._create("permission_overwrites");
static const ChangeKeyType nsfw = ChangeKeyType._create("nsfw");
static const ChangeKeyType applicationId = ChangeKeyType._create("application_id");
static const ChangeKeyType permissions = ChangeKeyType._create("permissions");
static const ChangeKeyType color = ChangeKeyType._create("color");
static const ChangeKeyType hoist = ChangeKeyType._create("hoist");
static const ChangeKeyType mentionable = ChangeKeyType._create("mentionable");
static const ChangeKeyType allow = ChangeKeyType._of("allow");
static const ChangeKeyType deny = ChangeKeyType._of("deny");
static const ChangeKeyType code = ChangeKeyType._of("code");
static const ChangeKeyType channelId = ChangeKeyType._of("channel_id");
static const ChangeKeyType inviterId = ChangeKeyType._of("inviter_id");
static const ChangeKeyType maxUses = ChangeKeyType._of("max_uses");
static const ChangeKeyType uses = ChangeKeyType._of("uses");
static const ChangeKeyType maxAge = ChangeKeyType._of("max_age");
static const ChangeKeyType temporary = ChangeKeyType._of("temporary");
static const ChangeKeyType deaf = ChangeKeyType._of("deaf");
static const ChangeKeyType mute = ChangeKeyType._of("mute");
static const ChangeKeyType nick = ChangeKeyType._of("nick");
static const ChangeKeyType allow = ChangeKeyType._create("allow");
static const ChangeKeyType deny = ChangeKeyType._create("deny");
static const ChangeKeyType code = ChangeKeyType._create("code");
static const ChangeKeyType channelId = ChangeKeyType._create("channel_id");
static const ChangeKeyType inviterId = ChangeKeyType._create("inviter_id");
static const ChangeKeyType maxUses = ChangeKeyType._create("max_uses");
static const ChangeKeyType uses = ChangeKeyType._create("uses");
static const ChangeKeyType maxAge = ChangeKeyType._create("max_age");
static const ChangeKeyType temporary = ChangeKeyType._create("temporary");
static const ChangeKeyType deaf = ChangeKeyType._create("deaf");
static const ChangeKeyType mute = ChangeKeyType._create("mute");
static const ChangeKeyType nick = ChangeKeyType._create("nick");
static const ChangeKeyType avatarHash = ChangeKeyType._of("avatar_hash");
static const ChangeKeyType id = ChangeKeyType._of("id");
static const ChangeKeyType type = ChangeKeyType._of("type");
static const ChangeKeyType avatarHash = ChangeKeyType._create("avatar_hash");
static const ChangeKeyType id = ChangeKeyType._create("id");
static const ChangeKeyType type = ChangeKeyType._create("type");
const ChangeKeyType._of(String value) : super(value);
ChangeKeyType(String value) : super(value);
/// Creates instance of [ChangeKeyType] from [value]
ChangeKeyType.from(String value) : super(value);
const ChangeKeyType._create(String value) : super(value);
@override
bool operator ==(other) {
bool operator ==(dynamic other) {
if (other is String) {
return other == this._value;
}
return super == other;
}
@override
int get hashCode => this.value.hashCode;
}

View file

@ -78,11 +78,14 @@ class AuditLogEntryType extends IEnum<int> {
const AuditLogEntryType._create(int value) : super(value);
@override
bool operator ==(other) {
bool operator ==(dynamic other) {
if (other is int) {
return other == this._value;
}
return super == other;
}
@override
int get hashCode => this.value.hashCode;
}

View file

@ -60,15 +60,19 @@ class ChannelType extends IEnum<int> {
static const ChannelType guildNews = ChannelType._create(5);
static const ChannelType guildStore = ChannelType._create(6);
/// Creates instance of [ChannelType] from [value].
ChannelType.from(int value) : super(value);
const ChannelType._create(int value) : super(value);
@override
bool operator ==(other) {
bool operator ==(dynamic other) {
if (other is int) {
return this._value == other;
}
return super == other;
}
@override
int get hashCode => this.value.hashCode;
}

View file

@ -20,7 +20,7 @@ abstract class TextChannel implements IChannel, ISend {
/// await channel.sendMessage(content: "Very nice message!");
/// ```
///
/// Can be used in combination with [Emoji]. Just run `toString()` on [Emoji] instance:
/// Can be used in combination with Emoji. Just run `toString()` on Emoji instance:
/// ```
/// final emoji = guild.emojis.findOne((e) => e.name.startsWith("dart"));
/// await channel.send(content: "Dart is superb! ${emoji.toString()}");

View file

@ -124,7 +124,7 @@ abstract class GuildChannel extends IChannel {
Future<void> deleteChannelPermission(SnowflakeEntity entity, {String? auditReason}) =>
client._httpEndpoints.deleteChannelPermission(this.id, entity, auditReason: auditReason);
/// Creates new [Invite] for [Channel] and returns it"s instance
/// Creates new [Invite] for [IChannel] and returns it"s instance
///
/// ```
/// var invite = await channel.createInvite(maxUses: 2137);

View file

@ -253,7 +253,7 @@ class Guild extends SnowflakeEntity {
Future<IGuildEmoji> fetchEmoji(Snowflake emojiId) =>
client._httpEndpoints.fetchGuildEmoji(this.id, emojiId);
/// Allows to create new guild emoji. [name] is required and you have to specify one of two other parameters: [image] or [imageBytes].
/// Allows to create new guild emoji. [name] is required and you have to specify one of other parameters: [imageFile], [imageBytes] or [encodedImage].
/// [imageBytes] can be useful if you want to create image from http response.
///
/// ```

View file

@ -38,15 +38,19 @@ class GuildFeature extends IEnum<String> {
/// Guild has enabled the welcome screen
static const GuildFeature welcomeScreenEnabled = GuildFeature._create("WELCOME_SCREEN_ENABLED");
const GuildFeature._create(String? value) : super(value ?? "");
/// Creates instance of [GuildFeature] from [value].
GuildFeature.from(String? value) : super(value ?? "");
const GuildFeature._create(String? value) : super(value ?? "");
@override
bool operator ==(other) {
bool operator ==(dynamic other) {
if (other is String) {
return other == _value;
}
return super == other;
}
@override
int get hashCode => this.value.hashCode;
}

View file

@ -11,11 +11,14 @@ class PremiumTier extends IEnum<int> {
PremiumTier.from(int? value) : super(value ?? 0);
@override
bool operator ==(other) {
bool operator ==(dynamic other) {
if (other is int) {
return other == _value;
}
return super == other;
}
@override
int get hashCode => this.value.hashCode;
}

View file

@ -7,8 +7,9 @@ class UserStatus extends IEnum<String> {
static const UserStatus online = UserStatus._create("online");
static const UserStatus idle = UserStatus._create("idle");
const UserStatus._create(String? value) : super(value ?? "offline");
/// Creates instance of [UserStatus] from [value].
UserStatus.from(String? value) : super(value ?? "offline");
const UserStatus._create(String? value) : super(value ?? "offline");
/// Returns if user is online
bool get isOnline => this != UserStatus.offline;
@ -17,13 +18,16 @@ class UserStatus extends IEnum<String> {
String toString() => _value;
@override
bool operator ==(other) {
bool operator ==(dynamic other) {
if (other is String) {
return other.toString() == _value;
}
return super == other;
}
@override
int get hashCode => this.value.hashCode;
}
/// Provides status of user on different devices

View file

@ -20,6 +20,9 @@ class WebhookType extends IEnum<int> {
return super == other;
}
@override
int get hashCode => this.value.hashCode;
}
///Webhooks are a low-effort way to post messages to channels in Discord.

View file

@ -173,6 +173,9 @@ abstract class Message extends SnowflakeEntity implements Disposable {
return false;
}
@override
int get hashCode => this.id.hashCode;
}
/// Message that is sent in dm channel or group dm channel

View file

@ -2,7 +2,7 @@ part of nyxx;
/// Represents messgae type
class MessageType extends IEnum<int> {
static const MessageType Default = MessageType._create(0);
static const MessageType def = MessageType._create(0);
static const MessageType recipientAdd = MessageType._create(1);
static const MessageType recipientRemove = MessageType._create(2);
static const MessageType call = MessageType._create(3);
@ -19,15 +19,19 @@ class MessageType extends IEnum<int> {
static const MessageType guildStream = MessageType._create(13);
static const MessageType guildDiscoveryRequalified = MessageType._create(15);
const MessageType._create(int? value) : super(value ?? 0);
/// Creates instance of [MessageType] from [value].
MessageType.from(int? value) : super(value ?? 0);
const MessageType._create(int? value) : super(value ?? 0);
@override
bool operator ==(other) {
bool operator ==(dynamic other) {
if (other is int) {
return other == _value;
}
return super == other;
}
@override
int get hashCode => this.value.hashCode;
}

View file

@ -6,17 +6,19 @@ class NitroType extends IEnum<int> {
static const NitroType classic = NitroType._create(1);
static const NitroType nitro = NitroType._create(2);
const NitroType._create(int? value) : super(value ?? 0);
/// Creates [NitroType] from [value]. [value] is 0 by default.
NitroType.from(int? value) : super(value ?? 0);
const NitroType._create(int? value) : super(value ?? 0);
@override
bool operator ==(other) {
bool operator ==(dynamic other) {
if (other is int) {
return other == _value;
}
return super == other;
}
@override
int get hashCode => this.value.hashCode;
}

View file

@ -127,13 +127,13 @@ class ActivityTimestamps {
class ActivityType extends IEnum<int> {
///Status type when playing a game
static const ActivityType game = ActivityType._create(0);
///Status type when streaming a game. Only supports twitch.tv or youtube.com url
static const ActivityType streaming = ActivityType._create(1);
///Status type when listening to Spotify
static const ActivityType listening = ActivityType._create(2);
///Custom status, not supported for bot accounts
static const ActivityType custom = ActivityType._create(4);
@ -142,13 +142,16 @@ class ActivityType extends IEnum<int> {
const ActivityType._create(int value) : super(value);
@override
bool operator ==(other) {
bool operator ==(dynamic other) {
if (other is int) {
return other == this._value;
}
return super == other;
}
@override
int get hashCode => this.value.hashCode;
}
/// Represents party of game.

View file

@ -22,7 +22,7 @@ class _HttpBucket {
final waitTime = resetAt!.millisecondsSinceEpoch - now.millisecondsSinceEpoch;
if (waitTime > 0) {
_httpHandler.client._onRatelimited.add(RatelimitEvent._new(request, true));
_httpHandler.client._onRateLimited.add(RatelimitEvent._new(request, true));
_httpHandler._logger.warning(
"Rate limited internally on endpoint: ${request.uri}. Trying to send request again in $waitTime ms...");
@ -49,7 +49,7 @@ class _HttpBucket {
final responseBody = jsonDecode(await response.stream.bytesToString());
final retryAfter = ((responseBody["retry_after"] as double) * 1000).round();
_httpHandler.client._onRatelimited.add(RatelimitEvent._new(request, false, response));
_httpHandler.client._onRateLimited.add(RatelimitEvent._new(request, false, response));
_httpHandler._logger.warning(
"Rate limited via 429 on endpoint: ${request.uri}. Trying to send request again in $retryAfter ms...");

View file

@ -1,6 +1,7 @@
part of nyxx;
/// Marks entity to which message can be sent
// ignore: one_member_abstracts
abstract class ISend {
/// Sends message
Future<Message?> sendMessage({

View file

@ -1,5 +1,7 @@
part of nyxx;
/// Shard is single connection to discord gateway. Since bots can grow big, handling thousand of guild on same websocket connections would be very hand.
/// Traffic can be split into different connections which can be run on different processes or even different machines.
class Shard implements Disposable {
/// Id of shard
final int id;

View file

@ -17,7 +17,7 @@ abstract class IEnum<T> {
int get hashCode => _value.hashCode;
@override
bool operator ==(other) {
bool operator ==(dynamic other) {
if (other is IEnum<T>) {
return other._value == this._value;
}

View file

@ -1,13 +1,13 @@
name: nyxx
version: 2.0.0-rc.3
description: A Discord library for Dart.
description: A Discord library for Dart. Simple, robust framework for creating discord bots for Dart language.
homepage: https://github.com/l7ssha/nyxx
repository: https://github.com/l7ssha/nyxx
documentation: https://github.com/l7ssha/nyxx/wiki
issue_tracker: https://github.com/l7ssha/nyxx/issue
issue_tracker: https://github.com/l7ssha/nyxx/issues
environment:
sdk: '>=2.12.0 <2.13.0'
sdk: '>=2.12.0'
dependencies:
http: "^0.13.1"

View file

@ -26,7 +26,7 @@ typedef CommandExecutionError = FutureOr<void> Function(CommandContext context,
/// implement [PrefixHandlerFunction] for more fine control over where and in what conditions commands are executed.
///
/// Allows to specify callbacks which are executed before and after command - also on per command basis.
/// [beforeCommandHandler] callbacks are executed only command exists and is matched with message content.
/// beforeCommandHandler callbacks are executed only command exists and is matched with message content.
// ignore: prefer_mixin
class Commander with ICommandRegistrable {
late final PrefixHandlerFunction _prefixHandler;

View file

@ -1,13 +1,13 @@
name: nyxx_commander
version: 2.0.0-rc.3
description: A Discord library for Dart.
description: Nyxx Commander Module. Discord library for Dart. Simple, robust framework for creating discord bots for Dart language.
homepage: https://github.com/l7ssha/nyxx
repository: https://github.com/l7ssha/nyxx
documentation: https://github.com/l7ssha/nyxx/wiki
issue_tracker: https://github.com/l7ssha/nyxx/issue
issue_tracker: https://github.com/l7ssha/nyxx/issues
environment:
sdk: '>=2.12.0 <2.13.0'
sdk: '>=2.12.0'
dependencies:
http: "^0.13.1"

View file

@ -1,6 +1,6 @@
import "dart:async";
import "package:nyxx/nyxx.dart" show TextGuildChannel, Message, Nyxx, Role, Snowflake;
import "package:nyxx/nyxx.dart" show TextGuildChannel, Message, Nyxx, Snowflake;
import "Regexes.dart" show Regexes;
@ -143,10 +143,10 @@ class MessageResolver {
outputBuffer.write(await this._resolveEmoji(emojiMatch, part));
continue;
}
outputBuffer.write(part);
}
return outputBuffer.toString().trim();
}
@ -187,7 +187,7 @@ class MessageResolver {
if (channelTagHandling == TagHandling.sanitize) {
return "<#$_whiteSpaceCharacter${match.group(1)}>";
}
final channel = _client.channels.values.firstWhere((ch) => ch is TextGuildChannel && ch.id == match.group(1)) as TextGuildChannel?;
if (channelTagHandling == TagHandling.name || channelTagHandling == TagHandling.fullName) {
@ -197,10 +197,10 @@ class MessageResolver {
if (channelTagHandling == TagHandling.nameNoPrefix || channelTagHandling == TagHandling.fullNameNoPrefix) {
return channel != null ? channel.name : this.missingEntityHandler("channel");
}
return content;
}
FutureOr<String> _resolveEveryone(RegExpMatch match, String content) async {
if (roleTagHandling == TagHandling.remove) {
return "";

View file

@ -1,13 +1,13 @@
name: nyxx_extensions
version: 2.0.0-rc.3
description: Extensions for Nyxx library
description: Nyxx Extensions Module. Discord library for Dart. Simple, robust framework for creating discord bots for Dart language.
homepage: https://github.com/l7ssha/nyxx
repository: https://github.com/l7ssha/nyxx
documentation: https://github.com/l7ssha/nyxx/wiki
issue_tracker: https://github.com/l7ssha/nyxx/issue
issue_tracker: https://github.com/l7ssha/nyxx/issues
environment:
sdk: '>=2.12.0 <2.13.0'
sdk: '>=2.12.0'
dependencies:
http: "^0.13.1"

View file

@ -1,6 +1,6 @@
import "package:nyxx_extensions/emoji.dart";
main() async {
void main() async {
final emojis = await getAllEmojiDefinitions();
assert(emojis.isNotEmpty, "Emojis cannot be empty");
}

View file

@ -0,0 +1,13 @@
import "package:nyxx/nyxx.dart";
import "package:nyxx_interactions/interactions.dart";
void main() {
final bot = Nyxx("<TOKEN>", GatewayIntents.allUnprivileged);
Interactions(bot)
..registerSlashCommand(
SlashCommandBuilder("itest", "This is test command", [
CommandOptionBuilder(CommandOptionType.subCommand, "subtest", "This is sub test")
..registerHandler((event) => event.respond(content: "This is example command"))
], guild: 302360552993456135.toSnowflake())
)..syncOnReady();
}

View file

@ -1,27 +0,0 @@
import "package:nyxx/nyxx.dart";
import "package:nyxx_interactions/interactions.dart";
void main() {
// final bot = Nyxx("<%TOKEN%>", GatewayIntents.all);
//
// final interactions = Interactions(bot);
//
// interactions.registerCommand(SlashCommandBuilder(
// "echo", // The command name
// "echo a message", // The commands description
// [CommandOptionBuilder(CommandOptionType.string, "message", "the message to be echoed.")], // The commands arguments
// guild: Snowflake(""), // Replace with your guilds ID
// ));
//
// bot.onReady.listen((event) {
// interactions.sync(); // Sync commands with API
// // Listen to slash commands being triggered
// interactions.onSlashCommand.listen((event) async {
// // Check if the name of the command is echo
// if (event.interaction.name == "echo") {
// // Reply with the message the user sent, showSource makes discord show the command the user sent in the channel.
// await event.respond(content: event.interaction.getArg("message"));
// }
// });
// });
}

View file

@ -1,13 +1,13 @@
name: nyxx_interactions
version: 2.0.0-rc.4
description: Interactions for Nyxx library
description: Nyxx Interactions Module. Discord library for Dart. Simple, robust framework for creating discord bots for Dart language.
homepage: https://github.com/l7ssha/nyxx
repository: https://github.com/l7ssha/nyxx
documentation: https://github.com/l7ssha/nyxx/wiki
issue_tracker: https://github.com/l7ssha/nyxx/issue
issue_tracker: https://github.com/l7ssha/nyxx/issues
environment:
sdk: '>=2.12.0 <2.13.0'
sdk: '>=2.12.0'
dependencies:
logging: "^1.0.1"