Skip to content

Commit

Permalink
Merge pull request #582 from nyxx-discord/dev
Browse files Browse the repository at this point in the history
Deploy 6.0.1
  • Loading branch information
l7ssha authored Nov 1, 2023
2 parents 4ec02a3 + 5dac578 commit 5fa56e8
Show file tree
Hide file tree
Showing 38 changed files with 757 additions and 335 deletions.
1 change: 0 additions & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,3 @@ Please delete options that are not relevant.
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] I have checked my changes haven't lowered code coverage
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
## 6.0.1
__01.10.2023__

- bug: Fix incorrect serialization of CommandOptionBuilder.
- bug: Fix customId missing from ButtonBuilder constructor.
- bug: Fix voice states not being cached correctly.
- bug: Fix incorrect parsing of scheduled events.
- bug: Fix `ephemeral` parameter not working when responding to message component interactions.
- bug: Fix parsing button labels when they are not set.
- bug: Fix incorrect serialization of TextInputBuilder.
- bug: Fix some entities not being cached.
- bug: Fix entities getting "stuck" in cache due to momentary high use.
- feat: Add more places entities can be cached from.

## 6.0.0
__16.10.2023__

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.0';
static const nyxxVersion = '6.0.1';

/// 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/application_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ class CommandOptionBuilder extends CreateBuilder<CommandOption> {
if (nameLocalizations != null) 'name_localizations': {for (final MapEntry(:key, :value) in nameLocalizations!.entries) key.identifier: value},
'description': description,
if (descriptionLocalizations != null)
'description_localizations': {for (final MapEntry(:key, :value) in nameLocalizations!.entries) key.identifier: value},
'description_localizations': {for (final MapEntry(:key, :value) in descriptionLocalizations!.entries) key.identifier: value},
if (isRequired != null) 'required': isRequired,
if (choices != null) 'choices': choices!.map((e) => e.build()).toList(),
if (options != null) 'options': options!.map((e) => e.build()).toList(),
Expand Down
9 changes: 5 additions & 4 deletions lib/src/builders/message/component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -54,31 +54,31 @@ class ButtonBuilder extends MessageComponentBuilder {
ButtonBuilder.primary({
this.label,
this.emoji,
required String customId,
required String this.customId,
this.isDisabled,
}) : style = ButtonStyle.primary,
super(type: MessageComponentType.button);

ButtonBuilder.secondary({
this.label,
this.emoji,
required String customId,
required String this.customId,
this.isDisabled,
}) : style = ButtonStyle.secondary,
super(type: MessageComponentType.button);

ButtonBuilder.success({
this.label,
this.emoji,
required String customId,
required String this.customId,
this.isDisabled,
}) : style = ButtonStyle.success,
super(type: MessageComponentType.button);

ButtonBuilder.danger({
this.label,
this.emoji,
required String customId,
required String this.customId,
this.isDisabled,
}) : style = ButtonStyle.danger,
super(type: MessageComponentType.button);
Expand Down Expand Up @@ -291,6 +291,7 @@ class TextInputBuilder extends MessageComponentBuilder {
if (minLength != null) 'min_length': minLength,
if (maxLength != null) 'max_length': maxLength,
if (isRequired != null) 'required': isRequired,
if (value != null) 'value': value,
if (placeholder != null) 'placeholder': placeholder,
};
}
8 changes: 3 additions & 5 deletions lib/src/cache/cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ class Cache<T> with MapMixin<Snowflake, T> {

_store.update(
(identifier: identifier, key: key),
(entry) => entry..value = value,
(entry) => entry
..value = value
..accessCount ~/= 2,
ifAbsent: () => _CacheEntry(value),
);

Expand Down Expand Up @@ -146,7 +148,3 @@ class Cache<T> with MapMixin<Snowflake, T> {
return result;
}
}

extension SnowflakeCache<T extends SnowflakeEntity<T>> on Cache<T> {
void addEntities(Iterable<T> entities) => addEntries(entities.map((e) => MapEntry(e.id, e)));
}
128 changes: 33 additions & 95 deletions lib/src/gateway/gateway.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:logging/logging.dart';
import 'package:nyxx/src/api_options.dart';
import 'package:nyxx/src/builders/presence.dart';
import 'package:nyxx/src/builders/voice.dart';
import 'package:nyxx/src/cache/cache.dart';
import 'package:nyxx/src/client.dart';
import 'package:nyxx/src/errors.dart';
import 'package:nyxx/src/gateway/event_parser.dart';
Expand Down Expand Up @@ -42,6 +41,7 @@ import 'package:nyxx/src/models/interaction.dart';
import 'package:nyxx/src/models/presence.dart';
import 'package:nyxx/src/models/snowflake.dart';
import 'package:nyxx/src/models/user/user.dart';
import 'package:nyxx/src/utils/cache_helpers.dart';
import 'package:nyxx/src/utils/iterable_extension.dart';
import 'package:nyxx/src/utils/parsing_helpers.dart';

Expand Down Expand Up @@ -113,83 +113,7 @@ class Gateway extends GatewayManager with EventParser {
}

// Handle all events which should update cache.
events.listen((event) => switch (event) {
ReadyEvent(:final user) => client.users.cache[user.id] = user,
ChannelCreateEvent(:final channel) || ChannelUpdateEvent(:final channel) => client.channels.cache[channel.id] = channel,
ChannelDeleteEvent(:final channel) => client.channels.cache.remove(channel.id),
ThreadCreateEvent(:final thread) || ThreadUpdateEvent(:final thread) => client.channels.cache[thread.id] = thread,
ThreadDeleteEvent(:final thread) => client.channels.cache.remove(thread.id),
ThreadListSyncEvent(:final threads) => client.channels.cache..addEntities(threads),
final GuildCreateEvent event => () {
client.guilds.cache[event.guild.id] = event.guild;

event.guild.members.cache.addEntities(event.members);
client.channels.cache.addEntities(event.channels);
client.channels.cache.addEntities(event.threads);
client.channels.stageInstanceCache.addEntities(event.stageInstances);
event.guild.scheduledEvents.cache.addEntities(event.scheduledEvents);
client.voice.cache.addEntries(event.voiceStates.map((e) => MapEntry(e.cacheKey, e)));
}(),
GuildUpdateEvent(:final guild) => client.guilds.cache[guild.id] = guild,
GuildDeleteEvent(:final guild, isUnavailable: false) => client.guilds.cache.remove(guild.id),
GuildMemberAddEvent(:final guildId, :final member) ||
GuildMemberUpdateEvent(:final guildId, :final member) =>
client.guilds[guildId].members.cache[member.id] = member,
GuildMemberRemoveEvent(:final guildId, :final user) => client.guilds[guildId].members.cache.remove(user.id),
GuildMembersChunkEvent(:final guildId, :final members) => client.guilds[guildId].members.cache..addEntities(members),
GuildRoleCreateEvent(:final guildId, :final role) ||
GuildRoleUpdateEvent(:final guildId, :final role) =>
client.guilds[guildId].roles.cache[role.id] = role,
GuildRoleDeleteEvent(:final guildId, :final roleId) => client.guilds[guildId].roles.cache.remove(roleId),
MessageCreateEvent(:final message) => (client.channels[message.channelId] as PartialTextChannel).messages.cache[message.id] = message,
MessageDeleteEvent(id: final messageId, :final channelId) =>
MessageManager(client.options.messageCacheConfig, client, channelId: channelId).cache.remove(messageId),
MessageBulkDeleteEvent(ids: final messageIds, :final channelId) =>
// ignore: avoid_function_literals_in_foreach_calls
messageIds..forEach((messageId) => MessageManager(client.options.messageCacheConfig, client, channelId: channelId).cache.remove(messageId)),
UserUpdateEvent(:final user) => client.users.cache[user.id] = user,
StageInstanceCreateEvent(:final instance) || StageInstanceUpdateEvent(:final instance) => client.channels.stageInstanceCache[instance.channelId] =
instance,
StageInstanceDeleteEvent(:final instance) => client.channels.stageInstanceCache.remove(instance.channelId),
GuildScheduledEventCreateEvent(:final event) ||
GuildScheduledEventUpdateEvent(:final event) =>
client.guilds[event.guildId].scheduledEvents.cache[event.id] = event,
GuildScheduledEventDeleteEvent(:final event) => client.guilds[event.guildId].scheduledEvents.cache.remove(event.id),
AutoModerationRuleCreateEvent(:final rule) ||
AutoModerationRuleUpdateEvent(:final rule) =>
client.guilds[rule.guildId].autoModerationRules.cache[rule.id] = rule,
AutoModerationRuleDeleteEvent(:final rule) => client.guilds[rule.guildId].autoModerationRules.cache.remove(rule.id),
IntegrationCreateEvent(:final guildId, :final integration) ||
IntegrationUpdateEvent(:final guildId, :final integration) =>
client.guilds[guildId].integrations.cache[integration.id] = integration,
IntegrationDeleteEvent(:final id, :final guildId) => client.guilds[guildId].integrations.cache.remove(id),
GuildAuditLogCreateEvent(:final entry, :final guildId) => client.guilds[guildId].auditLogs.cache[entry.id] = entry,
VoiceStateUpdateEvent(:final state) => client.voice.cache[state.cacheKey] = state,
GuildEmojisUpdateEvent(:final guildId, :final emojis) => client.guilds[guildId].emojis.cache
..clear()
..addEntities(emojis),
GuildStickersUpdateEvent(:final guildId, :final stickers) => client.guilds[guildId].stickers.cache.addEntities(stickers),
ApplicationCommandPermissionsUpdateEvent(:final permissions) => client.guilds[permissions.guildId].commands.permissionsCache[permissions.id] =
permissions,
InteractionCreateEvent(interaction: Interaction(:final guildId, data: ApplicationCommandInteractionData(resolved: final data?))) => () {
if (data.users != null) {
client.users.cache.addAll(data.users!);
}

if (data.members != null && guildId != null) {
client.guilds[guildId].members.cache.addAll(data.members!);
}

if (data.roles != null && guildId != null) {
client.guilds[guildId].roles.cache.addAll(data.roles!);
}
}(),
EntitlementCreateEvent(:final entitlement) ||
EntitlementUpdateEvent(:final entitlement) =>
client.applications[entitlement.applicationId].entitlements.cache[entitlement.id] = entitlement,
EntitlementDeleteEvent() => null, // TODO
_ => null,
});
events.listen(client.updateCacheWith);
}

/// Connect to the gateway using the provided [client] and [gatewayBot] configuration.
Expand Down Expand Up @@ -497,26 +421,31 @@ class Gateway extends GatewayManager with EventParser {
raw['threads'] as List<Object?>,
(Map<String, Object?> raw) => client.channels.parse(raw, guildId: guildId) as Thread,
),
members: parseMany(raw['members'] as List<Object?>, client.channels.parseThreadMember),
members: parseMany(raw['members'] as List<Object?>, (Map<String, Object?> raw) => client.channels.parseThreadMember(raw, guildId: guildId)),
);
}

/// Parse a [ThreadMemberUpdateEvent] from [raw].
ThreadMemberUpdateEvent parseThreadMemberUpdate(Map<String, Object?> raw) {
final guildId = Snowflake.parse(raw['guild_id']!);

return ThreadMemberUpdateEvent(
gateway: this,
member: client.channels.parseThreadMember(raw),
member: client.channels.parseThreadMember(raw, guildId: guildId),
guildId: guildId,
);
}

/// Parse a [ThreadMembersUpdateEvent] from [raw].
ThreadMembersUpdateEvent parseThreadMembersUpdate(Map<String, Object?> raw) {
final guildId = Snowflake.parse(raw['guild_id']!);

return ThreadMembersUpdateEvent(
gateway: this,
id: Snowflake.parse(raw['id']!),
guildId: Snowflake.parse(raw['guild_id']!),
guildId: guildId,
memberCount: raw['member_count'] as int,
addedMembers: maybeParseMany(raw['added_members'], client.channels.parseThreadMember),
addedMembers: maybeParseMany(raw['added_members'], (Map<String, Object?> raw) => client.channels.parseThreadMember(raw, guildId: guildId)),
removedMemberIds: maybeParseMany(raw['removed_member_ids'], Snowflake.parse),
);
}
Expand Down Expand Up @@ -545,7 +474,10 @@ class Gateway extends GatewayManager with EventParser {
joinedAt: DateTime.parse(raw['joined_at'] as String),
isLarge: raw['large'] as bool,
memberCount: raw['member_count'] as int,
voiceStates: parseMany(raw['voice_states'] as List<Object?>, client.voice.parseVoiceState),
voiceStates: parseMany(
raw['voice_states'] as List<Object?>,
(Map<String, Object?> raw) => client.voice.parseVoiceState(raw, guildId: guild.id),
),
members: parseMany(raw['members'] as List<Object?>, client.guilds[guild.id].members.parse),
channels: parseMany(raw['channels'] as List<Object?>, (Map<String, Object?> raw) => client.channels.parse(raw, guildId: guild.id) as GuildChannel),
threads: parseMany(raw['threads'] as List<Object?>, (Map<String, Object?> raw) => client.channels.parse(raw, guildId: guild.id) as Thread),
Expand Down Expand Up @@ -861,7 +793,7 @@ class Gateway extends GatewayManager with EventParser {
raw['member'],
(Map<String, Object?> _) => PartialMember(
id: Snowflake.parse((raw['author'] as Map<String, Object?>)['id']!),
manager: client.guilds[guildId ?? Snowflake.zero].members,
manager: client.guilds[guildId!].members,
),
),
mentions: maybeParseMany(raw['mentions'], client.users.parse),
Expand Down Expand Up @@ -893,16 +825,19 @@ class Gateway extends GatewayManager with EventParser {
/// Parse a [MessageReactionAddEvent] from [raw].
MessageReactionAddEvent parseMessageReactionAdd(Map<String, Object?> raw) {
final guildId = maybeParse(raw['guild_id'], Snowflake.parse);
final userId = Snowflake.parse(raw['user_id']!);

return MessageReactionAddEvent(
gateway: this,
userId: Snowflake.parse(raw['user_id']!),
channelId: Snowflake.parse(raw['channel_id']!),
messageId: Snowflake.parse(raw['message_id']!),
guildId: guildId,
member: maybeParse(raw['member'], client.guilds[guildId ?? Snowflake.zero].members.parse),
emoji: client.guilds[Snowflake.zero].emojis.parse(raw['emoji'] as Map<String, Object?>),
messageAuthorId: maybeParse(raw['message_author_id'], Snowflake.parse));
gateway: this,
userId: userId,
channelId: Snowflake.parse(raw['channel_id']!),
messageId: Snowflake.parse(raw['message_id']!),
guildId: guildId,
// Don't use a tearoff so we don't evaluate `guildId!` unless member is set.
member: maybeParse(raw['member'], (Map<String, Object?> raw) => client.guilds[guildId!].members.parse(raw, userId: userId)),
emoji: client.guilds[Snowflake.zero].emojis.parse(raw['emoji'] as Map<String, Object?>),
messageAuthorId: maybeParse(raw['message_author_id'], Snowflake.parse),
);
}

/// Parse a [MessageReactionRemoveEvent] from [raw].
Expand Down Expand Up @@ -958,14 +893,16 @@ class Gateway extends GatewayManager with EventParser {
/// Parse a [TypingStartEvent] from [raw].
TypingStartEvent parseTypingStart(Map<String, Object?> raw) {
var guildId = maybeParse(raw['guild_id'], Snowflake.parse);
final userId = Snowflake.parse(raw['user_id']!);

return TypingStartEvent(
gateway: this,
channelId: Snowflake.parse(raw['channel_id']!),
guildId: guildId,
userId: Snowflake.parse(raw['user_id']!),
userId: userId,
timestamp: DateTime.fromMillisecondsSinceEpoch((raw['timestamp'] as int) * Duration.millisecondsPerSecond),
member: maybeParse(raw['member'], client.guilds[guildId ?? Snowflake.zero].members.parse),
// Don't use a tearoff so we don't evaluate `guildId!` unless member is set.
member: maybeParse(raw['member'], (Map<String, Object?> raw) => client.guilds[guildId!].members.parse(raw, userId: userId)),
);
}

Expand All @@ -986,7 +923,8 @@ class Gateway extends GatewayManager with EventParser {

return VoiceStateUpdateEvent(
gateway: this,
oldState: client.voice.cache[voiceState.cacheKey],
// guildId should never be null in VOICE_STATE_UPDATE.
oldState: client.guilds[voiceState.guildId!].voiceStates[voiceState.userId],
state: voiceState,
);
}
Expand Down
18 changes: 9 additions & 9 deletions lib/src/http/managers/application_command_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:nyxx/src/models/commands/application_command_permissions.dart';
import 'package:nyxx/src/models/locale.dart';
import 'package:nyxx/src/models/permissions.dart';
import 'package:nyxx/src/models/snowflake.dart';
import 'package:nyxx/src/utils/cache_helpers.dart';
import 'package:nyxx/src/utils/parsing_helpers.dart';

/// A [Manager] for [ApplicationCommand]s.
Expand Down Expand Up @@ -114,7 +115,7 @@ abstract class ApplicationCommandManager extends Manager<ApplicationCommand> {
final response = await client.httpHandler.executeSafe(request);
final commands = parseMany(response.jsonBody as List, parse);

cache.addEntities(commands);
commands.forEach(client.updateCacheWith);
return commands;
}

Expand All @@ -129,7 +130,7 @@ abstract class ApplicationCommandManager extends Manager<ApplicationCommand> {
final response = await client.httpHandler.executeSafe(request);
final command = parse(response.jsonBody as Map<String, Object?>);

cache[command.id] = command;
client.updateCacheWith(command);
return command;
}

Expand All @@ -144,7 +145,7 @@ abstract class ApplicationCommandManager extends Manager<ApplicationCommand> {
final response = await client.httpHandler.executeSafe(request);
final command = parse(response.jsonBody as Map<String, Object?>);

cache[command.id] = command;
client.updateCacheWith(command);
return command;
}

Expand All @@ -159,7 +160,7 @@ abstract class ApplicationCommandManager extends Manager<ApplicationCommand> {
final response = await client.httpHandler.executeSafe(request);
final command = parse(response.jsonBody as Map<String, Object?>);

cache[command.id] = command;
client.updateCacheWith(command);
return command;
}

Expand All @@ -186,9 +187,8 @@ abstract class ApplicationCommandManager extends Manager<ApplicationCommand> {
final response = await client.httpHandler.executeSafe(request);
final commands = parseMany(response.jsonBody as List, parse);

cache
..clear()
..addEntities(commands);
cache.clear();
commands.forEach(client.updateCacheWith);
return commands;
}
}
Expand Down Expand Up @@ -246,7 +246,7 @@ class GuildApplicationCommandManager extends ApplicationCommandManager {
final response = await client.httpHandler.executeSafe(request);
final permissions = parseMany(response.jsonBody as List, parseCommandPermissions);

permissionsCache.addEntities(permissions);
permissions.forEach(client.updateCacheWith);
return permissions;
}

Expand All @@ -262,7 +262,7 @@ class GuildApplicationCommandManager extends ApplicationCommandManager {
final response = await client.httpHandler.executeSafe(request);
final permissions = parseCommandPermissions(response.jsonBody as Map<String, Object?>);

permissionsCache[permissions.id] = permissions;
client.updateCacheWith(permissions);
return permissions;
}

Expand Down
Loading

0 comments on commit 5fa56e8

Please sign in to comment.