From 45ea9c66c50652ad6bc2c8ac37043ab9aa3be66b Mon Sep 17 00:00:00 2001 From: Sleepy Boyy Date: Sun, 19 Aug 2018 21:12:49 +0200 Subject: [PATCH 1/2] Code Cleanup --- samples/01_basic_ping_bot/Program.cs | 4 +- samples/02_commands_framework/Program.cs | 25 +- .../Services/CommandHandlingService.cs | 7 +- .../Services/PictureService.cs | 4 +- .../GuildAccessAnalyzer.cs | 39 +- src/Discord.Net.Analyzers/SymbolExtensions.cs | 9 +- src/Discord.Net.Commands/AssemblyInfo.cs | 2 +- .../Attributes/AliasAttribute.cs | 10 +- .../Attributes/CommandAttribute.cs | 11 +- .../Attributes/DontAutoLoadAttribute.cs | 2 +- .../Attributes/DontInjectAttribute.cs | 12 +- .../Attributes/GroupAttribute.cs | 7 +- .../Attributes/NameAttribute.cs | 6 +- .../Attributes/OverrideTypeReaderAttribute.cs | 11 +- .../ParameterPreconditionAttribute.cs | 5 +- .../Attributes/PreconditionAttribute.cs | 12 +- .../RequireBotPermissionAttribute.cs | 49 +- .../Preconditions/RequireContextAttribute.cs | 30 +- .../Preconditions/RequireNsfwAttribute.cs | 11 +- .../Preconditions/RequireOwnerAttribute.cs | 10 +- .../RequireUserPermissionAttribute.cs | 56 +- .../Attributes/PriorityAttribute.cs | 10 +- .../Attributes/RemainderAttribute.cs | 2 +- .../Attributes/RemarksAttribute.cs | 6 +- .../Attributes/SummaryAttribute.cs | 6 +- .../Builders/CommandBuilder.cs | 82 +- .../Builders/ModuleBuilder.cs | 67 +- .../Builders/ModuleClassBuilder.cs | 104 +- .../Builders/ParameterBuilder.cs | 47 +- src/Discord.Net.Commands/CommandContext.cs | 15 +- src/Discord.Net.Commands/CommandException.cs | 6 +- src/Discord.Net.Commands/CommandMatch.cs | 13 +- src/Discord.Net.Commands/CommandParser.cs | 153 +- src/Discord.Net.Commands/CommandService.cs | 223 +- .../CommandServiceConfig.cs | 14 +- .../EmptyServiceProvider.cs | 2 +- .../Extensions/IEnumerableExtensions.cs | 11 +- .../Extensions/MessageExtensions.cs | 37 +- src/Discord.Net.Commands/IModuleBase.cs | 2 +- src/Discord.Net.Commands/Info/CommandInfo.cs | 191 +- src/Discord.Net.Commands/Info/ModuleInfo.cs | 54 +- .../Info/ParameterInfo.cs | 42 +- src/Discord.Net.Commands/Map/CommandMap.cs | 14 +- .../Map/CommandMapNode.cs | 133 +- src/Discord.Net.Commands/ModuleBase.cs | 39 +- src/Discord.Net.Commands/PrimitiveParsers.cs | 11 +- .../Readers/ChannelTypeReader.cs | 38 +- .../Readers/EnumTypeReader.cs | 32 +- .../Readers/MessageTypeReader.cs | 14 +- .../Readers/NullableTypeReader.cs | 11 +- .../Readers/PrimitiveTypeReader.cs | 13 +- .../Readers/RoleTypeReader.cs | 36 +- .../Readers/TimeSpanTypeReader.cs | 37 +- .../Readers/TypeReader.cs | 3 +- .../Readers/UserTypeReader.cs | 52 +- .../Results/ExecuteResult.cs | 7 +- .../Results/ParseResult.cs | 42 +- .../Results/PreconditionGroupResult.cs | 20 +- .../Results/PreconditionResult.cs | 17 +- .../Results/RuntimeResult.cs | 12 +- .../Results/SearchResult.cs | 5 +- .../Results/TypeReaderResult.cs | 14 +- .../Utilities/QuotationAliasUtils.cs | 153 +- .../Utilities/ReflectionUtils.cs | 32 +- src/Discord.Net.Core/AssemblyInfo.cs | 2 +- .../Audio/AudioApplication.cs | 4 +- src/Discord.Net.Core/Audio/AudioInStream.cs | 4 +- src/Discord.Net.Core/Audio/AudioOutStream.cs | 6 +- src/Discord.Net.Core/Audio/AudioStream.cs | 42 +- src/Discord.Net.Core/Audio/IAudioClient.cs | 22 +- src/Discord.Net.Core/Audio/RTPFrame.cs | 2 +- src/Discord.Net.Core/CDN.cs | 17 +- src/Discord.Net.Core/DiscordConfig.cs | 17 +- .../Entities/Activities/Game.cs | 15 +- .../Entities/Activities/GameAsset.cs | 8 +- .../Entities/Activities/GameParty.cs | 4 +- .../Entities/Activities/GameSecrets.cs | 10 +- .../Entities/Activities/GameTimestamps.cs | 8 +- .../Entities/Activities/RichGame.cs | 10 +- .../Entities/Activities/SpotifyGame.cs | 10 +- .../Entities/Activities/StreamingGame.cs | 10 +- .../Entities/AuditLogs/ActionType.cs | 10 +- .../Entities/AuditLogs/IAuditLogData.cs | 13 +- .../Entities/AuditLogs/IAuditLogEntry.cs | 16 +- .../Channels/GuildChannelProperties.cs | 16 +- .../Entities/Channels/IAudioChannel.cs | 3 +- .../Entities/Channels/ICategoryChannel.cs | 6 - .../Entities/Channels/IChannel.cs | 7 +- .../Entities/Channels/IDMChannel.cs | 2 +- .../Entities/Channels/IGroupChannel.cs | 2 +- .../Entities/Channels/IGuildChannel.cs | 23 +- .../Entities/Channels/IMessageChannel.cs | 32 +- .../Entities/Channels/INestedChannel.cs | 8 +- .../Entities/Channels/ITextChannel.cs | 3 + .../Entities/Channels/IVoiceChannel.cs | 1 + .../Channels/ReorderChannelProperties.cs | 11 +- .../Channels/TextChannelProperties.cs | 5 +- .../Channels/VoiceChannelProperties.cs | 5 +- src/Discord.Net.Core/Entities/Emotes/Emoji.cs | 21 +- src/Discord.Net.Core/Entities/Emotes/Emote.cs | 68 +- .../Entities/Emotes/GuildEmote.cs | 15 +- .../Entities/Emotes/IEmote.cs | 4 +- .../Guilds/DefaultMessageNotifications.cs | 1 + .../Entities/Guilds/GuildEmbedProperties.cs | 10 +- .../Entities/Guilds/GuildProperties.cs | 50 +- .../Entities/Guilds/IGuild.cs | 137 +- .../Entities/Guilds/IUserGuild.cs | 3 + .../Entities/Guilds/IVoiceRegion.cs | 5 + .../Entities/Guilds/IntegrationAccount.cs | 2 +- .../Entities/Guilds/MfaLevel.cs | 1 + .../Entities/Guilds/VerificationLevel.cs | 4 + src/Discord.Net.Core/Entities/IApplication.cs | 2 +- src/Discord.Net.Core/Entities/IEntity.cs | 1 - src/Discord.Net.Core/Entities/Image.cs | 11 +- src/Discord.Net.Core/Entities/ImageFormat.cs | 2 +- .../Entities/Invites/IInvite.cs | 8 + .../Entities/Invites/IInviteMetadata.cs | 6 + .../Entities/Messages/Embed.cs | 55 +- .../Entities/Messages/EmbedAuthor.cs | 5 +- .../Entities/Messages/EmbedBuilder.cs | 122 +- .../Entities/Messages/EmbedField.cs | 2 +- .../Entities/Messages/EmbedFooter.cs | 5 +- .../Entities/Messages/EmbedImage.cs | 7 +- .../Entities/Messages/EmbedProvider.cs | 5 +- .../Entities/Messages/EmbedThumbnail.cs | 7 +- .../Entities/Messages/EmbedType.cs | 2 +- .../Entities/Messages/EmbedVideo.cs | 7 +- .../Entities/Messages/IMessage.cs | 14 +- .../Entities/Messages/IUserMessage.cs | 14 +- .../Entities/Messages/MessageProperties.cs | 13 +- src/Discord.Net.Core/Entities/Messages/Tag.cs | 16 +- .../Entities/Messages/TagHandling.cs | 14 +- .../Entities/Permissions/ChannelPermission.cs | 45 +- .../Permissions/ChannelPermissions.cs | 47 +- .../Entities/Permissions/GuildPermission.cs | 61 +- .../Entities/Permissions/GuildPermissions.cs | 53 +- .../Entities/Permissions/Overwrite.cs | 2 + .../Permissions/OverwritePermissions.cs | 94 +- src/Discord.Net.Core/Entities/Roles/Color.cs | 35 +- src/Discord.Net.Core/Entities/Roles/IRole.cs | 6 + .../Entities/Roles/ReorderRoleProperties.cs | 11 +- .../Entities/Roles/RoleProperties.cs | 33 +- .../Entities/Users/GuildUserProperties.cs | 45 +- .../Entities/Users/IGuildUser.cs | 13 +- .../Entities/Users/IPresence.cs | 3 +- .../Entities/Users/ISelfUser.cs | 4 +- src/Discord.Net.Core/Entities/Users/IUser.cs | 15 +- .../Entities/Users/IVoiceState.cs | 6 + .../Entities/Users/SelfUserProperties.cs | 11 +- .../Entities/Users/UserStatus.cs | 2 +- .../Entities/Webhooks/IWebhook.cs | 8 +- .../Entities/Webhooks/WebhookProperties.cs | 21 +- .../Entities/Webhooks/WebhookType.cs | 4 +- .../Extensions/AsyncEnumerableExtensions.cs | 14 +- .../Extensions/CollectionExtensions.cs | 14 +- .../Extensions/DiscordClientExtensions.cs | 8 +- .../Extensions/MessageExtensions.cs | 3 +- .../TaskCompletionSourceExtensions.cs | 3 + .../Extensions/UserExtensions.cs | 29 +- src/Discord.Net.Core/Format.cs | 16 +- src/Discord.Net.Core/IDiscordClient.cs | 25 +- src/Discord.Net.Core/Logging/LogManager.cs | 46 +- src/Discord.Net.Core/Logging/LogMessage.cs | 50 +- src/Discord.Net.Core/Logging/Logger.cs | 14 +- src/Discord.Net.Core/Net/HttpException.cs | 23 +- .../Net/RateLimitedException.cs | 4 +- src/Discord.Net.Core/Net/Rest/IRestClient.cs | 12 +- src/Discord.Net.Core/Net/RpcException.cs | 6 +- src/Discord.Net.Core/Net/Udp/IUdpSocket.cs | 3 +- .../Net/WebSocketClosedException.cs | 7 +- src/Discord.Net.Core/RequestOptions.cs | 26 +- src/Discord.Net.Core/RetryMode.cs | 11 +- src/Discord.Net.Core/TokenType.cs | 4 +- src/Discord.Net.Core/Utils/AsyncEvent.cs | 35 +- src/Discord.Net.Core/Utils/Cacheable.cs | 26 +- src/Discord.Net.Core/Utils/Comparers.cs | 33 +- .../Utils/ConcurrentHashSet.cs | 374 +-- src/Discord.Net.Core/Utils/DateTimeUtils.cs | 2 +- src/Discord.Net.Core/Utils/MentionUtils.cs | 230 +- src/Discord.Net.Core/Utils/Optional.cs | 7 +- src/Discord.Net.Core/Utils/Paging/Page.cs | 3 +- src/Discord.Net.Core/Utils/Paging/PageInfo.cs | 12 +- .../Utils/Paging/PagedEnumerator.cs | 37 +- src/Discord.Net.Core/Utils/Permissions.cs | 119 +- src/Discord.Net.Core/Utils/Preconditions.cs | 650 ++++-- src/Discord.Net.Core/Utils/SnowflakeUtils.cs | 1 + .../WS4NetClient.cs | 156 +- .../API/Common/Application.cs | 26 +- src/Discord.Net.Rest/API/Common/Attachment.cs | 27 +- src/Discord.Net.Rest/API/Common/AuditLog.cs | 9 +- .../API/Common/AuditLogChange.cs | 9 +- .../API/Common/AuditLogEntry.cs | 23 +- .../API/Common/AuditLogOptions.cs | 25 +- src/Discord.Net.Rest/API/Common/Ban.cs | 7 +- src/Discord.Net.Rest/API/Common/Channel.cs | 53 +- src/Discord.Net.Rest/API/Common/Connection.cs | 20 +- src/Discord.Net.Rest/API/Common/Embed.cs | 51 +- .../API/Common/EmbedAuthor.cs | 18 +- src/Discord.Net.Rest/API/Common/EmbedField.cs | 11 +- .../API/Common/EmbedFooter.cs | 14 +- src/Discord.Net.Rest/API/Common/EmbedImage.cs | 16 +- .../API/Common/EmbedProvider.cs | 8 +- .../API/Common/EmbedThumbnail.cs | 16 +- src/Discord.Net.Rest/API/Common/EmbedVideo.cs | 12 +- src/Discord.Net.Rest/API/Common/Emoji.cs | 23 +- src/Discord.Net.Rest/API/Common/Game.cs | 58 +- src/Discord.Net.Rest/API/Common/GameAssets.cs | 15 +- src/Discord.Net.Rest/API/Common/GameParty.cs | 7 +- .../API/Common/GameSecrets.cs | 13 +- .../API/Common/GameTimestamps.cs | 7 +- src/Discord.Net.Rest/API/Common/Guild.cs | 68 +- src/Discord.Net.Rest/API/Common/GuildEmbed.cs | 7 +- .../API/Common/GuildMember.cs | 25 +- .../API/Common/Integration.cs | 45 +- .../API/Common/IntegrationAccount.cs | 7 +- src/Discord.Net.Rest/API/Common/Invite.cs | 13 +- .../API/Common/InviteChannel.cs | 11 +- .../API/Common/InviteGuild.cs | 11 +- .../API/Common/InviteMetadata.cs | 29 +- src/Discord.Net.Rest/API/Common/Message.cs | 73 +- src/Discord.Net.Rest/API/Common/Overwrite.cs | 15 +- src/Discord.Net.Rest/API/Common/Presence.cs | 22 +- src/Discord.Net.Rest/API/Common/Reaction.cs | 11 +- src/Discord.Net.Rest/API/Common/ReadState.cs | 11 +- .../API/Common/Relationship.cs | 11 +- src/Discord.Net.Rest/API/Common/Role.cs | 31 +- src/Discord.Net.Rest/API/Common/User.cs | 30 +- src/Discord.Net.Rest/API/Common/UserGuild.cs | 19 +- .../API/Common/VoiceRegion.cs | 23 +- src/Discord.Net.Rest/API/Common/VoiceState.cs | 39 +- src/Discord.Net.Rest/API/Common/Webhook.cs | 25 +- src/Discord.Net.Rest/API/EntityOrId.cs | 1 + src/Discord.Net.Rest/API/Image.cs | 1 + src/Discord.Net.Rest/API/Int53Attribute.cs | 4 +- .../API/Rest/CreateChannelInviteParams.cs | 15 +- .../API/Rest/CreateDMChannelParams.cs | 5 +- .../API/Rest/CreateGuildBanParams.cs | 2 +- .../API/Rest/CreateGuildChannelParams.cs | 35 +- .../API/Rest/CreateGuildEmoteParams.cs | 11 +- .../API/Rest/CreateGuildIntegrationParams.cs | 9 +- .../API/Rest/CreateGuildParams.cs | 14 +- .../API/Rest/CreateMessageParams.cs | 18 +- .../API/Rest/CreateWebhookMessageParams.cs | 26 +- .../API/Rest/CreateWebhookParams.cs | 7 +- .../API/Rest/DeleteMessagesParams.cs | 5 +- .../API/Rest/GetAuditLogsParams.cs | 2 +- .../API/Rest/GetBotGatewayResponse.cs | 7 +- .../API/Rest/GetGatewayResponse.cs | 3 +- .../API/Rest/GetGuildPruneCountResponse.cs | 3 +- .../API/Rest/GuildPruneParams.cs | 5 +- .../Rest/ModifyChannelPermissionsParams.cs | 13 +- .../API/Rest/ModifyCurrentUserNickParams.cs | 5 +- .../API/Rest/ModifyCurrentUserParams.cs | 7 +- .../API/Rest/ModifyGuildChannelParams.cs | 11 +- .../API/Rest/ModifyGuildChannelsParams.cs | 9 +- .../API/Rest/ModifyGuildEmbedParams.cs | 9 +- .../API/Rest/ModifyGuildEmoteParams.cs | 7 +- .../API/Rest/ModifyGuildIntegrationParams.cs | 11 +- .../API/Rest/ModifyGuildMemberParams.cs | 19 +- .../API/Rest/ModifyGuildParams.cs | 40 +- .../API/Rest/ModifyGuildRoleParams.cs | 19 +- .../API/Rest/ModifyGuildRolesParams.cs | 9 +- .../API/Rest/ModifyMessageParams.cs | 7 +- .../API/Rest/ModifyTextChannelParams.cs | 7 +- .../API/Rest/ModifyVoiceChannelParams.cs | 7 +- .../API/Rest/ModifyWebhookParams.cs | 11 +- .../API/Rest/UploadFileParams.cs | 20 +- .../API/Rest/UploadWebhookFileParams.cs | 10 +- .../API/UnixTimestampAttribute.cs | 6 +- src/Discord.Net.Rest/AssemblyInfo.cs | 29 +- src/Discord.Net.Rest/BaseDiscordClient.cs | 181 +- src/Discord.Net.Rest/ClientHelper.cs | 130 +- src/Discord.Net.Rest/DiscordRestApiClient.cs | 931 +++++--- src/Discord.Net.Rest/DiscordRestClient.cs | 194 +- .../Entities/AuditLogs/AuditLogHelper.cs | 83 +- .../AuditLogs/DataTypes/BanAuditLogData.cs | 5 +- .../DataTypes/ChannelCreateAuditLogData.cs | 36 +- .../DataTypes/ChannelDeleteAuditLogData.cs | 19 +- .../DataTypes/ChannelUpdateAuditLogData.cs | 9 +- .../DataTypes/EmoteCreateAuditLogData.cs | 13 +- .../DataTypes/EmoteDeleteAuditLogData.cs | 7 +- .../DataTypes/EmoteUpdateAuditLogData.cs | 9 +- .../DataTypes/GuildUpdateAuditLogData.cs | 13 +- .../DataTypes/InviteCreateAuditLogData.cs | 20 +- .../DataTypes/InviteDeleteAuditLogData.cs | 20 +- .../DataTypes/InviteUpdateAuditLogData.cs | 7 +- .../AuditLogs/DataTypes/KickAuditLogData.cs | 5 +- .../DataTypes/MemberRoleAuditLogData.cs | 19 +- .../DataTypes/MemberUpdateAuditLogData.cs | 9 +- .../DataTypes/MessageDeleteAuditLogData.cs | 9 +- .../DataTypes/OverwriteCreateAuditLogData.cs | 7 +- .../DataTypes/OverwriteDeleteAuditLogData.cs | 13 +- .../DataTypes/OverwriteUpdateAuditLogData.cs | 21 +- .../AuditLogs/DataTypes/PruneAuditLogData.cs | 8 +- .../DataTypes/RoleCreateAuditLogData.cs | 17 +- .../DataTypes/RoleDeleteAuditLogData.cs | 17 +- .../DataTypes/RoleUpdateAuditLogData.cs | 9 +- .../AuditLogs/DataTypes/UnbanAuditLogData.cs | 5 +- .../DataTypes/WebhookCreateAuditLogData.cs | 17 +- .../DataTypes/WebhookDeleteAuditLogData.cs | 19 +- .../DataTypes/WebhookUpdateAuditLogData.cs | 21 +- .../Entities/AuditLogs/RestAuditLogEntry.cs | 27 +- .../Entities/Channels/ChannelHelper.cs | 227 +- .../Entities/Channels/IRestMessageChannel.cs | 23 +- .../Entities/Channels/RestCategoryChannel.cs | 22 +- .../Entities/Channels/RestChannel.cs | 30 +- .../Entities/Channels/RestDMChannel.cs | 172 +- .../Entities/Channels/RestGroupChannel.cs | 212 +- .../Entities/Channels/RestGuildChannel.cs | 173 +- .../Entities/Channels/RestTextChannel.cs | 225 +- .../Entities/Channels/RestVoiceChannel.cs | 62 +- .../Channels/RpcVirtualMessageChannel.cs | 124 +- .../Entities/Guilds/GuildHelper.cs | 213 +- .../Entities/Guilds/RestBan.cs | 17 +- .../Entities/Guilds/RestGuild.cs | 549 +++-- .../Entities/Guilds/RestGuildEmbed.cs | 12 +- .../Entities/Guilds/RestGuildIntegration.cs | 55 +- .../Entities/Guilds/RestUserGuild.cs | 33 +- .../Entities/Guilds/RestVoiceRegion.cs | 21 +- .../Entities/Invites/InviteHelper.cs | 6 +- .../Entities/Invites/RestInvite.cs | 78 +- .../Entities/Invites/RestInviteMetadata.cs | 20 +- .../Entities/Messages/Attachment.cs | 31 +- .../Entities/Messages/MessageHelper.cs | 110 +- .../Entities/Messages/RestMessage.cs | 57 +- .../Entities/Messages/RestReaction.cs | 9 +- .../Entities/Messages/RestSystemMessage.cs | 14 +- .../Entities/Messages/RestUserMessage.cs | 131 +- .../Entities/RestApplication.cs | 19 +- src/Discord.Net.Rest/Entities/RestEntity.cs | 6 +- .../Entities/Roles/RestRole.cs | 58 +- .../Entities/Roles/RoleHelper.cs | 18 +- .../Entities/Users/RestConnection.cs | 26 +- .../Entities/Users/RestGroupUser.cs | 15 +- .../Entities/Users/RestGuildUser.cs | 102 +- .../Entities/Users/RestSelfUser.cs | 22 +- .../Entities/Users/RestUser.cs | 53 +- .../Entities/Users/RestWebhookUser.cs | 63 +- .../Entities/Users/UserHelper.cs | 35 +- .../Entities/Webhooks/RestWebhook.cs | 76 +- .../Entities/Webhooks/WebhookHelper.cs | 6 +- .../Extensions/EntityExtensions.cs | 153 +- .../Net/Converters/ArrayConverter.cs | 22 +- .../Net/Converters/DiscordContractResolver.cs | 65 +- .../Net/Converters/ImageConverter.cs | 12 +- .../Net/Converters/NullableConverter.cs | 31 +- .../Net/Converters/OptionalConverter.cs | 16 +- .../Converters/PermissionTargetConverter.cs | 11 +- .../Net/Converters/StringEntityConverter.cs | 14 +- .../Net/Converters/UInt64Converter.cs | 19 +- .../Net/Converters/UInt64EntityConverter.cs | 14 +- .../Converters/UInt64EntityOrIdConverter.cs | 20 +- .../Net/Converters/UnixTimestampConverter.cs | 30 +- .../Net/Converters/UserStatusConverter.cs | 11 +- src/Discord.Net.Rest/Net/DefaultRestClient.cs | 99 +- .../Net/DefaultRestClientProvider.cs | 22 +- .../Net/Queue/ClientBucket.cs | 3 +- .../Net/Queue/RequestQueue.cs | 82 +- .../Net/Queue/RequestQueueBucket.cs | 94 +- .../Net/Queue/Requests/JsonRestRequest.cs | 15 +- .../Queue/Requests/MultipartRestRequest.cs | 17 +- .../Net/Queue/Requests/RestRequest.cs | 27 +- .../Net/Queue/Requests/WebSocketRequest.cs | 32 +- src/Discord.Net.Rest/Net/RateLimitInfo.cs | 30 +- src/Discord.Net.Rest/Utils/TypingNotifier.cs | 18 +- .../API/Gateway/ExtendedGuild.cs | 28 +- .../API/Gateway/GatewayOpCode.cs | 12 + .../API/Gateway/GuildBanEvent.cs | 7 +- .../API/Gateway/GuildEmojiUpdateEvent.cs | 7 +- .../API/Gateway/GuildMemberAddEvent.cs | 3 +- .../API/Gateway/GuildMemberRemoveEvent.cs | 7 +- .../API/Gateway/GuildMemberUpdateEvent.cs | 3 +- .../API/Gateway/GuildMembersChunkEvent.cs | 7 +- .../API/Gateway/GuildRoleCreateEvent.cs | 7 +- .../API/Gateway/GuildRoleDeleteEvent.cs | 7 +- .../API/Gateway/GuildRoleUpdateEvent.cs | 7 +- .../API/Gateway/GuildSyncEvent.cs | 14 +- .../API/Gateway/HelloEvent.cs | 3 +- .../API/Gateway/IdentifyParams.cs | 17 +- .../API/Gateway/MessageDeleteBulkEvent.cs | 9 +- .../API/Gateway/Reaction.cs | 15 +- .../API/Gateway/ReadyEvent.cs | 40 +- .../API/Gateway/RecipientEvent.cs | 7 +- .../API/Gateway/RemoveAllReactionsEvent.cs | 7 +- .../API/Gateway/RequestMembersParams.cs | 12 +- .../API/Gateway/ResumeParams.cs | 11 +- .../API/Gateway/ResumedEvent.cs | 7 +- .../API/Gateway/StatusUpdateParams.cs | 15 +- .../API/Gateway/TypingStartEvent.cs | 19 +- .../API/Gateway/VoiceServerUpdateEvent.cs | 13 +- .../API/Gateway/VoiceStateUpdateParams.cs | 14 +- .../API/Gateway/WebhookUpdateEvent.cs | 7 +- src/Discord.Net.WebSocket/API/SocketFrame.cs | 9 +- .../API/Voice/HelloEvent.cs | 3 +- .../API/Voice/IdentifyParams.cs | 15 +- .../API/Voice/ReadyEvent.cs | 18 +- .../API/Voice/SelectProtocolParams.cs | 7 +- .../API/Voice/SessionDescriptionEvent.cs | 7 +- .../API/Voice/SpeakingEvent.cs | 11 +- .../API/Voice/SpeakingParams.cs | 7 +- .../API/Voice/UdpProtocolInfo.cs | 11 +- .../API/Voice/VoiceOpCode.cs | 12 +- src/Discord.Net.WebSocket/AssemblyInfo.cs | 2 +- .../Audio/AudioClient.Events.cs | 56 +- .../Audio/AudioClient.cs | 361 +-- .../Audio/Opus/OpusApplication.cs | 2 +- .../Audio/Opus/OpusConverter.cs | 22 +- .../Audio/Opus/OpusCtl.cs | 2 +- .../Audio/Opus/OpusDecoder.cs | 33 +- .../Audio/Opus/OpusEncoder.cs | 44 +- .../Audio/Opus/OpusError.cs | 2 +- .../Audio/Opus/OpusSignal.cs | 4 +- .../Audio/Sodium/SecretBox.cs | 20 +- .../Audio/Streams/BufferedWriteStream.cs | 101 +- .../Audio/Streams/InputStream.cs | 38 +- .../Audio/Streams/JitterBuffer.cs | 4 +- .../Audio/Streams/OpusDecodeStream.cs | 18 +- .../Audio/Streams/OpusEncodeStream.cs | 23 +- .../Audio/Streams/OutputStream.cs | 15 +- .../Audio/Streams/RTPReadStream.cs | 52 +- .../Audio/Streams/RTPWriteStream.cs | 21 +- .../Audio/Streams/SodiumDecryptStream.cs | 17 +- .../Audio/Streams/SodiumEncryptStream.cs | 14 +- .../BaseSocketClient.Events.cs | 357 ++- src/Discord.Net.WebSocket/BaseSocketClient.cs | 100 +- src/Discord.Net.WebSocket/ClientState.cs | 103 +- .../Commands/ShardedCommandContext.cs | 8 +- .../Commands/SocketCommandContext.cs | 16 +- .../ConnectionManager.cs | 104 +- .../DiscordShardedClient.Events.cs | 45 +- .../DiscordShardedClient.cs | 322 +-- .../DiscordSocketApiClient.cs | 171 +- .../DiscordSocketClient.Events.cs | 31 +- .../DiscordSocketClient.cs | 2051 +++++++++-------- .../DiscordSocketConfig.cs | 31 +- .../DiscordVoiceApiClient.cs | 198 +- .../Channels/ISocketMessageChannel.cs | 22 +- .../Channels/SocketCategoryChannel.cs | 70 +- .../Entities/Channels/SocketChannel.cs | 28 +- .../Entities/Channels/SocketChannelHelper.cs | 65 +- .../Entities/Channels/SocketDMChannel.cs | 213 +- .../Entities/Channels/SocketGroupChannel.cs | 274 ++- .../Entities/Channels/SocketGuildChannel.cs | 154 +- .../Entities/Channels/SocketTextChannel.cs | 228 +- .../Entities/Channels/SocketVoiceChannel.cs | 71 +- .../Entities/Guilds/SocketGuild.cs | 601 ++--- .../Entities/Messages/MessageCache.cs | 31 +- .../Entities/Messages/SocketMessage.cs | 71 +- .../Entities/Messages/SocketReaction.cs | 24 +- .../Entities/Messages/SocketSystemMessage.cs | 18 +- .../Entities/Messages/SocketUserMessage.cs | 153 +- .../Entities/Roles/SocketRole.cs | 51 +- .../Entities/SocketEntity.cs | 6 +- .../Entities/Users/SocketGlobalUser.cs | 22 +- .../Entities/Users/SocketGroupUser.cs | 55 +- .../Entities/Users/SocketGuildUser.cs | 158 +- .../Entities/Users/SocketPresence.cs | 12 +- .../Entities/Users/SocketSelfUser.cs | 71 +- .../Entities/Users/SocketUnknownUser.cs | 16 +- .../Entities/Users/SocketUser.cs | 48 +- .../Entities/Users/SocketVoiceState.cs | 21 +- .../Entities/Users/SocketWebhookUser.cs | 74 +- .../Entities/Voice/SocketVoiceServer.cs | 10 +- .../Extensions/EntityExtensions.cs | 62 +- .../Net/DefaultUdpSocket.cs | 111 +- .../Net/DefaultUdpSocketProvider.cs | 5 +- .../Net/DefaultWebSocketClient.cs | 192 +- .../Net/DefaultWebSocketClientProvider.cs | 22 +- src/Discord.Net.Webhook/AssemblyInfo.cs | 2 +- .../DiscordWebhookClient.cs | 63 +- .../Entities/Webhooks/RestInternalWebhook.cs | 50 +- .../WebhookClientHelper.cs | 38 +- .../Extensions/AppDomainPolyfill.cs | 12 +- .../AnalyzerTests/GuildAccessTests.cs | 46 +- .../Helpers/CodeFixVerifier.Helper.cs | 46 +- .../AnalyzerTests/Helpers/DiagnosticResult.cs | 62 +- .../Helpers/DiagnosticVerifier.Helper.cs | 157 +- .../Verifiers/CodeFixVerifier.cs | 99 +- .../Verifiers/DiagnosticVerifier.cs | 260 +-- test/Discord.Net.Tests/Net/CacheInfo.cs | 9 +- .../Discord.Net.Tests/Net/CachedRestClient.cs | 93 +- .../Net/FilesystemProvider.cs | 65 +- test/Discord.Net.Tests/Net/HttpMixin.cs | 94 +- test/Discord.Net.Tests/TestConfig.cs | 32 +- .../Tests.ChannelPermissions.cs | 46 +- test/Discord.Net.Tests/Tests.Channels.cs | 59 +- test/Discord.Net.Tests/Tests.Colors.cs | 64 +- test/Discord.Net.Tests/Tests.Emotes.cs | 33 +- .../Tests.GuildPermissions.cs | 13 +- test/Discord.Net.Tests/Tests.Migrations.cs | 12 +- test/Discord.Net.Tests/Tests.Permissions.cs | 927 ++++---- test/Discord.Net.Tests/Tests.cs | 6 +- 492 files changed, 12665 insertions(+), 10339 deletions(-) diff --git a/samples/01_basic_ping_bot/Program.cs b/samples/01_basic_ping_bot/Program.cs index 0fcc52b85..e57bf3b40 100644 --- a/samples/01_basic_ping_bot/Program.cs +++ b/samples/01_basic_ping_bot/Program.cs @@ -14,13 +14,13 @@ namespace _01_basic_ping_bot // - Here, under the 02_commands_framework sample // - https://github.com/foxbot/DiscordBotBase - a barebones bot template // - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library - class Program + internal class Program { private DiscordSocketClient _client; // Discord.Net heavily utilizes TAP for async, so we create // an asynchronous context from the beginning. - static void Main(string[] args) + private static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); public async Task MainAsync() diff --git a/samples/02_commands_framework/Program.cs b/samples/02_commands_framework/Program.cs index 3fed652d3..8195cd76d 100644 --- a/samples/02_commands_framework/Program.cs +++ b/samples/02_commands_framework/Program.cs @@ -1,10 +1,10 @@ using System; using System.Net.Http; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Discord; -using Discord.WebSocket; using Discord.Commands; +using Discord.WebSocket; +using Microsoft.Extensions.DependencyInjection; using _02_commands_framework.Services; namespace _02_commands_framework @@ -17,9 +17,9 @@ namespace _02_commands_framework // - Here, under the 02_commands_framework sample // - https://github.com/foxbot/DiscordBotBase - a barebones bot template // - https://github.com/foxbot/patek - a more feature-filled bot, utilizing more aspects of the library - class Program + internal class Program { - static void Main(string[] args) + private static void Main(string[] args) => new Program().MainAsync().GetAwaiter().GetResult(); public async Task MainAsync() @@ -46,15 +46,12 @@ namespace _02_commands_framework return Task.CompletedTask; } - private IServiceProvider ConfigureServices() - { - return new ServiceCollection() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .BuildServiceProvider(); - } + private IServiceProvider ConfigureServices() => new ServiceCollection() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() + .BuildServiceProvider(); } } diff --git a/samples/02_commands_framework/Services/CommandHandlingService.cs b/samples/02_commands_framework/Services/CommandHandlingService.cs index fc253eed3..7f6b9e94a 100644 --- a/samples/02_commands_framework/Services/CommandHandlingService.cs +++ b/samples/02_commands_framework/Services/CommandHandlingService.cs @@ -1,10 +1,10 @@ using System; using System.Reflection; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; using Discord; using Discord.Commands; using Discord.WebSocket; +using Microsoft.Extensions.DependencyInjection; namespace _02_commands_framework.Services { @@ -23,10 +23,7 @@ namespace _02_commands_framework.Services _discord.MessageReceived += MessageReceivedAsync; } - public async Task InitializeAsync() - { - await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); - } + public async Task InitializeAsync() => await _commands.AddModulesAsync(Assembly.GetEntryAssembly(), _services); public async Task MessageReceivedAsync(SocketMessage rawMessage) { diff --git a/samples/02_commands_framework/Services/PictureService.cs b/samples/02_commands_framework/Services/PictureService.cs index dda818cc3..3b284d0ab 100644 --- a/samples/02_commands_framework/Services/PictureService.cs +++ b/samples/02_commands_framework/Services/PictureService.cs @@ -9,7 +9,9 @@ namespace _02_commands_framework.Services private readonly HttpClient _http; public PictureService(HttpClient http) - => _http = http; + { + _http = http; + } public async Task GetCatPictureAsync() { diff --git a/src/Discord.Net.Analyzers/GuildAccessAnalyzer.cs b/src/Discord.Net.Analyzers/GuildAccessAnalyzer.cs index 0760d019f..f577f5bdf 100644 --- a/src/Discord.Net.Analyzers/GuildAccessAnalyzer.cs +++ b/src/Discord.Net.Analyzers/GuildAccessAnalyzer.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Immutable; using System.Linq; +using Discord.Commands; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -using Discord.Commands; namespace Discord.Analyzers { @@ -14,18 +14,25 @@ namespace Discord.Analyzers { private const string DiagnosticId = "DNET0001"; private const string Title = "Limit command to Guild contexts."; - private const string MessageFormat = "Command method '{0}' is accessing 'Context.Guild' but is not restricted to Guild contexts."; - private const string Description = "Accessing 'Context.Guild' in a command without limiting the command to run only in guilds."; + + private const string MessageFormat = + "Command method '{0}' is accessing 'Context.Guild' but is not restricted to Guild contexts."; + + private const string Description = + "Accessing 'Context.Guild' in a command without limiting the command to run only in guilds."; + private const string Category = "API Usage"; - private static DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, Category, DiagnosticSeverity.Warning, isEnabledByDefault: true, description: Description); + private static readonly DiagnosticDescriptor Rule = new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, + Category, DiagnosticSeverity.Warning, true, Description); + + private static readonly Func AttributeDataPredicate = + a => a.AttributeClass.Name == nameof(RequireContextAttribute); public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - public override void Initialize(AnalysisContext context) - { + public override void Initialize(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeMemberAccess, SyntaxKind.SimpleMemberAccessExpression); - } private static void AnalyzeMemberAccess(SyntaxNodeAnalysisContext context) { @@ -53,18 +60,14 @@ namespace Discord.Analyzers // Is the '[RequireContext]' attribute not applied to either the // method or the class, or its argument isn't 'ContextType.Guild'? - var ctxAttribute = methodAttributes.SingleOrDefault(_attributeDataPredicate) - ?? typeSymbol.GetAttributes().SingleOrDefault(_attributeDataPredicate); + var ctxAttribute = methodAttributes.SingleOrDefault(AttributeDataPredicate) + ?? typeSymbol.GetAttributes().SingleOrDefault(AttributeDataPredicate); - if (ctxAttribute == null || ctxAttribute.ConstructorArguments.Any(arg => !arg.Value.Equals((int)ContextType.Guild))) - { - // Report the diagnostic - var diagnostic = Diagnostic.Create(Rule, context.Node.GetLocation(), methodSymbol.Name); - context.ReportDiagnostic(diagnostic); - } + if (ctxAttribute != null && + !ctxAttribute.ConstructorArguments.Any(arg => !arg.Value.Equals((int)ContextType.Guild))) return; + // Report the diagnostic + var diagnostic = Diagnostic.Create(Rule, context.Node.GetLocation(), methodSymbol.Name); + context.ReportDiagnostic(diagnostic); } - - private static readonly Func _attributeDataPredicate = - (a => a.AttributeClass.Name == nameof(RequireContextAttribute)); } } diff --git a/src/Discord.Net.Analyzers/SymbolExtensions.cs b/src/Discord.Net.Analyzers/SymbolExtensions.cs index 680de66b5..7be656bfe 100644 --- a/src/Discord.Net.Analyzers/SymbolExtensions.cs +++ b/src/Discord.Net.Analyzers/SymbolExtensions.cs @@ -1,20 +1,17 @@ -using System; +using Discord.Commands; using Microsoft.CodeAnalysis; -using Discord.Commands; namespace Discord.Analyzers { internal static class SymbolExtensions { - private static readonly string _moduleBaseName = typeof(ModuleBase<>).Name; + private static readonly string ModuleBaseName = typeof(ModuleBase<>).Name; public static bool DerivesFromModuleBase(this ITypeSymbol symbol) { for (var bType = symbol.BaseType; bType != null; bType = bType.BaseType) - { - if (bType.MetadataName == _moduleBaseName) + if (bType.MetadataName == ModuleBaseName) return true; - } return false; } } diff --git a/src/Discord.Net.Commands/AssemblyInfo.cs b/src/Discord.Net.Commands/AssemblyInfo.cs index c6b5997b4..a18f8049b 100644 --- a/src/Discord.Net.Commands/AssemblyInfo.cs +++ b/src/Discord.Net.Commands/AssemblyInfo.cs @@ -1,3 +1,3 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Discord.Net.Tests")] \ No newline at end of file +[assembly: InternalsVisibleTo("Discord.Net.Tests")] diff --git a/src/Discord.Net.Commands/Attributes/AliasAttribute.cs b/src/Discord.Net.Commands/Attributes/AliasAttribute.cs index 6cd0abbb7..342c029d6 100644 --- a/src/Discord.Net.Commands/Attributes/AliasAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/AliasAttribute.cs @@ -3,16 +3,16 @@ using System; namespace Discord.Commands { /// Provides aliases for a command. - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class AliasAttribute : Attribute { - /// The aliases which have been defined for the command. - public string[] Aliases { get; } - - /// Creates a new with the given aliases. + /// Creates a new with the given aliases. public AliasAttribute(params string[] aliases) { Aliases = aliases; } + + /// The aliases which have been defined for the command. + public string[] Aliases { get; } } } diff --git a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs index a0fcf3e4a..298d7eb64 100644 --- a/src/Discord.Net.Commands/Attributes/CommandAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/CommandAttribute.cs @@ -2,20 +2,21 @@ using System; namespace Discord.Commands { - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + [AttributeUsage(AttributeTargets.Method)] public class CommandAttribute : Attribute { - public string Text { get; } - public RunMode RunMode { get; set; } = RunMode.Default; - public bool? IgnoreExtraArgs { get; set; } - public CommandAttribute() { Text = null; } + public CommandAttribute(string text) { Text = text; } + + public string Text { get; } + public RunMode RunMode { get; set; } = RunMode.Default; + public bool? IgnoreExtraArgs { get; set; } } } diff --git a/src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs b/src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs index cc23a6d15..f1dd3680d 100644 --- a/src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/DontAutoLoadAttribute.cs @@ -2,7 +2,7 @@ using System; namespace Discord.Commands { - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + [AttributeUsage(AttributeTargets.Class)] public class DontAutoLoadAttribute : Attribute { } diff --git a/src/Discord.Net.Commands/Attributes/DontInjectAttribute.cs b/src/Discord.Net.Commands/Attributes/DontInjectAttribute.cs index c982d93a1..1fde86c7b 100644 --- a/src/Discord.Net.Commands/Attributes/DontInjectAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/DontInjectAttribute.cs @@ -1,9 +1,9 @@ using System; -namespace Discord.Commands { - - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] - public class DontInjectAttribute : Attribute { - } - +namespace Discord.Commands +{ + [AttributeUsage(AttributeTargets.Property)] + public class DontInjectAttribute : Attribute + { + } } diff --git a/src/Discord.Net.Commands/Attributes/GroupAttribute.cs b/src/Discord.Net.Commands/Attributes/GroupAttribute.cs index b1760d149..d43e39db3 100644 --- a/src/Discord.Net.Commands/Attributes/GroupAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/GroupAttribute.cs @@ -2,18 +2,19 @@ using System; namespace Discord.Commands { - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + [AttributeUsage(AttributeTargets.Class)] public class GroupAttribute : Attribute { - public string Prefix { get; } - public GroupAttribute() { Prefix = null; } + public GroupAttribute(string prefix) { Prefix = prefix; } + + public string Prefix { get; } } } diff --git a/src/Discord.Net.Commands/Attributes/NameAttribute.cs b/src/Discord.Net.Commands/Attributes/NameAttribute.cs index 4a4b2bfed..f72af322f 100644 --- a/src/Discord.Net.Commands/Attributes/NameAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/NameAttribute.cs @@ -3,14 +3,14 @@ using System; namespace Discord.Commands { // Override public name of command/module - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Parameter)] public class NameAttribute : Attribute { - public string Text { get; } - public NameAttribute(string text) { Text = text; } + + public string Text { get; } } } diff --git a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs index 44ab6d214..85b2fc70f 100644 --- a/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/OverrideTypeReaderAttribute.cs @@ -1,22 +1,21 @@ using System; - using System.Reflection; namespace Discord.Commands { - [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + [AttributeUsage(AttributeTargets.Parameter)] public class OverrideTypeReaderAttribute : Attribute { private static readonly TypeInfo _typeReaderTypeInfo = typeof(TypeReader).GetTypeInfo(); - public Type TypeReader { get; } - public OverrideTypeReaderAttribute(Type overridenTypeReader) { if (!_typeReaderTypeInfo.IsAssignableFrom(overridenTypeReader.GetTypeInfo())) throw new ArgumentException($"{nameof(overridenTypeReader)} must inherit from {nameof(TypeReader)}"); - + TypeReader = overridenTypeReader; } - } + + public Type TypeReader { get; } + } } diff --git a/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs index 3c5e8cf92..cab8ff5e9 100644 --- a/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/ParameterPreconditionAttribute.cs @@ -3,9 +3,10 @@ using System.Threading.Tasks; namespace Discord.Commands { - [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true, Inherited = true)] + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = true)] public abstract class ParameterPreconditionAttribute : Attribute { - public abstract Task CheckPermissionsAsync(ICommandContext context, ParameterInfo parameter, object value, IServiceProvider services); + public abstract Task CheckPermissionsAsync(ICommandContext context, ParameterInfo parameter, + object value, IServiceProvider services); } } diff --git a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs index 367adebf0..4929289a8 100644 --- a/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PreconditionAttribute.cs @@ -3,16 +3,18 @@ using System.Threading.Tasks; namespace Discord.Commands { - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)] public abstract class PreconditionAttribute : Attribute { /// - /// Specify a group that this precondition belongs to. Preconditions of the same group require only one - /// of the preconditions to pass in order to be successful (A || B). Specifying = - /// or not at all will require *all* preconditions to pass, just like normal (A && B). + /// Specify a group that this precondition belongs to. Preconditions of the same group require only one + /// of the preconditions to pass in order to be successful (A || B). Specifying = + /// + /// or not at all will require *all* preconditions to pass, just like normal (A && B). /// public string Group { get; set; } = null; - public abstract Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services); + public abstract Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, + IServiceProvider services); } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs index 104252799..2db7afeb4 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs @@ -4,30 +4,34 @@ using System.Threading.Tasks; namespace Discord.Commands { /// - /// This attribute requires that the bot has a specified permission in the channel a command is invoked in. + /// This attribute requires that the bot has a specified permission in the channel a command is invoked in. /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public class RequireBotPermissionAttribute : PreconditionAttribute { - public GuildPermission? GuildPermission { get; } - public ChannelPermission? ChannelPermission { get; } - /// - /// Require that the bot account has a specified GuildPermission + /// Require that the bot account has a specified GuildPermission /// /// This precondition will always fail if the command is being invoked in a private channel. - /// The GuildPermission that the bot must have. Multiple permissions can be specified by ORing the permissions together. + /// + /// The GuildPermission that the bot must have. Multiple permissions can be specified by ORing the + /// permissions together. + /// public RequireBotPermissionAttribute(GuildPermission permission) { GuildPermission = permission; ChannelPermission = null; } + /// - /// Require that the bot account has a specified ChannelPermission. + /// Require that the bot account has a specified ChannelPermission. /// - /// The ChannelPermission that the bot must have. Multiple permissions can be specified by ORing the permissions together. + /// + /// The ChannelPermission that the bot must have. Multiple permissions can be specified by ORing + /// the permissions together. + /// /// - /// + /// /// [Command("permission")] /// [RequireBotPermission(ChannelPermission.ManageMessages)] /// public async Task Purge() @@ -41,7 +45,11 @@ namespace Discord.Commands GuildPermission = null; } - public override async Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) + public GuildPermission? GuildPermission { get; } + public ChannelPermission? ChannelPermission { get; } + + public override async Task CheckPermissionsAsync(ICommandContext context, + CommandInfo command, IServiceProvider services) { IGuildUser guildUser = null; if (context.Guild != null) @@ -55,19 +63,14 @@ namespace Discord.Commands return PreconditionResult.FromError($"Bot requires guild permission {GuildPermission.Value}"); } - if (ChannelPermission.HasValue) - { - ChannelPermissions perms; - if (context.Channel is IGuildChannel guildChannel) - perms = guildUser.GetPermissions(guildChannel); - else - perms = ChannelPermissions.All(context.Channel); - - if (!perms.Has(ChannelPermission.Value)) - return PreconditionResult.FromError($"Bot requires channel permission {ChannelPermission.Value}"); - } + if (!ChannelPermission.HasValue) return PreconditionResult.FromSuccess(); + ChannelPermissions perms; + if (context.Channel is IGuildChannel guildChannel) + perms = guildUser.GetPermissions(guildChannel); + else + perms = ChannelPermissions.All(context.Channel); - return PreconditionResult.FromSuccess(); + return !perms.Has(ChannelPermission.Value) ? PreconditionResult.FromError($"Bot requires channel permission {ChannelPermission.Value}") : PreconditionResult.FromSuccess(); } } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs index 90af035e4..3f1988d80 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; namespace Discord.Commands { @@ -13,19 +12,20 @@ namespace Discord.Commands } /// - /// Require that the command be invoked in a specified context. + /// Require that the command be invoked in a specified context. /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public class RequireContextAttribute : PreconditionAttribute { - public ContextType Contexts { get; } - /// - /// Require that the command be invoked in a specified context. + /// Require that the command be invoked in a specified context. /// - /// The type of context the command can be invoked in. Multiple contexts can be specified by ORing the contexts together. + /// + /// The type of context the command can be invoked in. Multiple contexts can be specified by ORing + /// the contexts together. + /// /// - /// + /// /// [Command("private_only")] /// [RequireContext(ContextType.DM | ContextType.Group)] /// public async Task PrivateOnly() @@ -38,21 +38,21 @@ namespace Discord.Commands Contexts = contexts; } - public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) + public ContextType Contexts { get; } + + public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, + IServiceProvider services) { - bool isValid = false; + var isValid = false; if ((Contexts & ContextType.Guild) != 0) - isValid = isValid || context.Channel is IGuildChannel; + isValid = context.Channel is IGuildChannel; if ((Contexts & ContextType.DM) != 0) isValid = isValid || context.Channel is IDMChannel; if ((Contexts & ContextType.Group) != 0) isValid = isValid || context.Channel is IGroupChannel; - if (isValid) - return Task.FromResult(PreconditionResult.FromSuccess()); - else - return Task.FromResult(PreconditionResult.FromError($"Invalid context for command; accepted contexts: {Contexts}")); + return Task.FromResult(isValid ? PreconditionResult.FromSuccess() : PreconditionResult.FromError($"Invalid context for command; accepted contexts: {Contexts}")); } } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs index 273c764bd..aca3a8166 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireNsfwAttribute.cs @@ -4,17 +4,18 @@ using System.Threading.Tasks; namespace Discord.Commands { /// - /// Require that the command is invoked in a channel marked NSFW + /// Require that the command is invoked in a channel marked NSFW /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public class RequireNsfwAttribute : PreconditionAttribute { - public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) + public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, + IServiceProvider services) { if (context.Channel is ITextChannel text && text.IsNsfw) return Task.FromResult(PreconditionResult.FromSuccess()); - else - return Task.FromResult(PreconditionResult.FromError("This command may only be invoked in an NSFW channel.")); + return Task.FromResult( + PreconditionResult.FromError("This command may only be invoked in an NSFW channel.")); } } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs index 93e3cbe18..55d4eb594 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireOwnerAttribute.cs @@ -4,13 +4,14 @@ using System.Threading.Tasks; namespace Discord.Commands { /// - /// Require that the command is invoked by the owner of the bot. + /// Require that the command is invoked by the owner of the bot. /// /// This precondition will only work if the bot is a bot account. - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public class RequireOwnerAttribute : PreconditionAttribute { - public override async Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) + public override async Task CheckPermissionsAsync(ICommandContext context, + CommandInfo command, IServiceProvider services) { switch (context.Client.TokenType) { @@ -20,7 +21,8 @@ namespace Discord.Commands return PreconditionResult.FromError("Command can only be run by the owner of the bot"); return PreconditionResult.FromSuccess(); default: - return PreconditionResult.FromError($"{nameof(RequireOwnerAttribute)} is not supported by this {nameof(TokenType)}."); + return PreconditionResult.FromError( + $"{nameof(RequireOwnerAttribute)} is not supported by this {nameof(TokenType)}."); } } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs index 14121f35b..38d222397 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs @@ -4,30 +4,34 @@ using System.Threading.Tasks; namespace Discord.Commands { /// - /// This attribute requires that the user invoking the command has a specified permission. + /// This attribute requires that the user invoking the command has a specified permission. /// - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)] public class RequireUserPermissionAttribute : PreconditionAttribute { - public GuildPermission? GuildPermission { get; } - public ChannelPermission? ChannelPermission { get; } - /// - /// Require that the user invoking the command has a specified GuildPermission + /// Require that the user invoking the command has a specified GuildPermission /// /// This precondition will always fail if the command is being invoked in a private channel. - /// The GuildPermission that the user must have. Multiple permissions can be specified by ORing the permissions together. + /// + /// The GuildPermission that the user must have. Multiple permissions can be specified by ORing + /// the permissions together. + /// public RequireUserPermissionAttribute(GuildPermission permission) { GuildPermission = permission; ChannelPermission = null; } + /// - /// Require that the user invoking the command has a specified ChannelPermission. + /// Require that the user invoking the command has a specified ChannelPermission. /// - /// The ChannelPermission that the user must have. Multiple permissions can be specified by ORing the permissions together. + /// + /// The ChannelPermission that the user must have. Multiple permissions can be specified by ORing + /// the permissions together. + /// /// - /// + /// /// [Command("permission")] /// [RequireUserPermission(ChannelPermission.ReadMessageHistory | ChannelPermission.ReadMessages)] /// public async Task HasPermission() @@ -41,32 +45,32 @@ namespace Discord.Commands ChannelPermission = permission; GuildPermission = null; } - - public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, IServiceProvider services) + + public GuildPermission? GuildPermission { get; } + public ChannelPermission? ChannelPermission { get; } + + public override Task CheckPermissionsAsync(ICommandContext context, CommandInfo command, + IServiceProvider services) { var guildUser = context.User as IGuildUser; if (GuildPermission.HasValue) { if (guildUser == null) - return Task.FromResult(PreconditionResult.FromError("Command must be used in a guild channel")); + return Task.FromResult(PreconditionResult.FromError("Command must be used in a guild channel")); if (!guildUser.GuildPermissions.Has(GuildPermission.Value)) - return Task.FromResult(PreconditionResult.FromError($"User requires guild permission {GuildPermission.Value}")); + return Task.FromResult( + PreconditionResult.FromError($"User requires guild permission {GuildPermission.Value}")); } - if (ChannelPermission.HasValue) - { - ChannelPermissions perms; - if (context.Channel is IGuildChannel guildChannel) - perms = guildUser.GetPermissions(guildChannel); - else - perms = ChannelPermissions.All(context.Channel); - - if (!perms.Has(ChannelPermission.Value)) - return Task.FromResult(PreconditionResult.FromError($"User requires channel permission {ChannelPermission.Value}")); - } + if (!ChannelPermission.HasValue) return Task.FromResult(PreconditionResult.FromSuccess()); + ChannelPermissions perms; + if (context.Channel is IGuildChannel guildChannel) + perms = guildUser.GetPermissions(guildChannel); + else + perms = ChannelPermissions.All(context.Channel); - return Task.FromResult(PreconditionResult.FromSuccess()); + return Task.FromResult(!perms.Has(ChannelPermission.Value) ? PreconditionResult.FromError($"User requires channel permission {ChannelPermission.Value}") : PreconditionResult.FromSuccess()); } } } diff --git a/src/Discord.Net.Commands/Attributes/PriorityAttribute.cs b/src/Discord.Net.Commands/Attributes/PriorityAttribute.cs index 353e96e41..c43389e16 100644 --- a/src/Discord.Net.Commands/Attributes/PriorityAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/PriorityAttribute.cs @@ -3,16 +3,16 @@ using System; namespace Discord.Commands { /// Sets priority of commands - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + [AttributeUsage(AttributeTargets.Method)] public class PriorityAttribute : Attribute { - /// The priority which has been set for the command - public int Priority { get; } - - /// Creates a new with the given priority. + /// Creates a new with the given priority. public PriorityAttribute(int priority) { Priority = priority; } + + /// The priority which has been set for the command + public int Priority { get; } } } diff --git a/src/Discord.Net.Commands/Attributes/RemainderAttribute.cs b/src/Discord.Net.Commands/Attributes/RemainderAttribute.cs index 56938f167..774ca4ce0 100644 --- a/src/Discord.Net.Commands/Attributes/RemainderAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/RemainderAttribute.cs @@ -2,7 +2,7 @@ using System; namespace Discord.Commands { - [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + [AttributeUsage(AttributeTargets.Parameter)] public class RemainderAttribute : Attribute { } diff --git a/src/Discord.Net.Commands/Attributes/RemarksAttribute.cs b/src/Discord.Net.Commands/Attributes/RemarksAttribute.cs index c11f790a7..2705b5aa1 100644 --- a/src/Discord.Net.Commands/Attributes/RemarksAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/RemarksAttribute.cs @@ -3,14 +3,14 @@ using System; namespace Discord.Commands { // Extension of the Cosmetic Summary, for Groups, Commands, and Parameters - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class RemarksAttribute : Attribute { - public string Text { get; } - public RemarksAttribute(string text) { Text = text; } + + public string Text { get; } } } diff --git a/src/Discord.Net.Commands/Attributes/SummaryAttribute.cs b/src/Discord.Net.Commands/Attributes/SummaryAttribute.cs index 641163408..6a413ae77 100644 --- a/src/Discord.Net.Commands/Attributes/SummaryAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/SummaryAttribute.cs @@ -3,14 +3,14 @@ using System; namespace Discord.Commands { // Cosmetic Summary, for Groups and Commands - [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Parameter)] public class SummaryAttribute : Attribute { - public string Text { get; } - public SummaryAttribute(string text) { Text = text; } + + public string Text { get; } } } diff --git a/src/Discord.Net.Commands/Builders/CommandBuilder.cs b/src/Discord.Net.Commands/Builders/CommandBuilder.cs index 17a170775..24f8c2149 100644 --- a/src/Discord.Net.Commands/Builders/CommandBuilder.cs +++ b/src/Discord.Net.Commands/Builders/CommandBuilder.cs @@ -1,32 +1,16 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using System.Collections.Generic; namespace Discord.Commands.Builders { public class CommandBuilder { - private readonly List _preconditions; - private readonly List _parameters; - private readonly List _attributes; private readonly List _aliases; - - public ModuleBuilder Module { get; } - internal Func Callback { get; set; } - - public string Name { get; set; } - public string Summary { get; set; } - public string Remarks { get; set; } - public string PrimaryAlias { get; set; } - public RunMode RunMode { get; set; } - public int Priority { get; set; } - public bool IgnoreExtraArgs { get; set; } - - public IReadOnlyList Preconditions => _preconditions; - public IReadOnlyList Parameters => _parameters; - public IReadOnlyList Attributes => _attributes; - public IReadOnlyList Aliases => _aliases; + private readonly List _attributes; + private readonly List _parameters; + private readonly List _preconditions; //Automatic internal CommandBuilder(ModuleBuilder module) @@ -38,8 +22,10 @@ namespace Discord.Commands.Builders _attributes = new List(); _aliases = new List(); } + //User-defined - internal CommandBuilder(ModuleBuilder module, string primaryAlias, Func callback) + internal CommandBuilder(ModuleBuilder module, string primaryAlias, + Func callback) : this(module) { Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); @@ -50,26 +36,46 @@ namespace Discord.Commands.Builders _aliases.Add(primaryAlias); } + public ModuleBuilder Module { get; } + internal Func Callback { get; set; } + + public string Name { get; set; } + public string Summary { get; set; } + public string Remarks { get; set; } + public string PrimaryAlias { get; set; } + public RunMode RunMode { get; set; } + public int Priority { get; set; } + public bool IgnoreExtraArgs { get; set; } + + public IReadOnlyList Preconditions => _preconditions; + public IReadOnlyList Parameters => _parameters; + public IReadOnlyList Attributes => _attributes; + public IReadOnlyList Aliases => _aliases; + public CommandBuilder WithName(string name) { Name = name; return this; } + public CommandBuilder WithSummary(string summary) { Summary = summary; return this; } + public CommandBuilder WithRemarks(string remarks) { Remarks = remarks; return this; } + public CommandBuilder WithRunMode(RunMode runMode) { RunMode = runMode; return this; } + public CommandBuilder WithPriority(int priority) { Priority = priority; @@ -78,24 +84,28 @@ namespace Discord.Commands.Builders public CommandBuilder AddAliases(params string[] aliases) { - for (int i = 0; i < aliases.Length; i++) + foreach (var t in aliases) { - string alias = aliases[i] ?? ""; + var alias = t ?? ""; if (!_aliases.Contains(alias)) _aliases.Add(alias); } + return this; } + public CommandBuilder AddAttributes(params Attribute[] attributes) { _attributes.AddRange(attributes); return this; } + public CommandBuilder AddPrecondition(PreconditionAttribute precondition) { _preconditions.Add(precondition); return this; } + public CommandBuilder AddParameter(string name, Action createFunc) { var param = new ParameterBuilder(this, name, typeof(T)); @@ -103,6 +113,7 @@ namespace Discord.Commands.Builders _parameters.Add(param); return this; } + public CommandBuilder AddParameter(string name, Type type, Action createFunc) { var param = new ParameterBuilder(this, name, type); @@ -110,6 +121,7 @@ namespace Discord.Commands.Builders _parameters.Add(param); return this; } + internal CommandBuilder AddParameter(Action createFunc) { var param = new ParameterBuilder(this); @@ -124,18 +136,18 @@ namespace Discord.Commands.Builders if (Name == null) Name = PrimaryAlias; - if (_parameters.Count > 0) - { - var lastParam = _parameters[_parameters.Count - 1]; - - var firstMultipleParam = _parameters.FirstOrDefault(x => x.IsMultiple); - if ((firstMultipleParam != null) && (firstMultipleParam != lastParam)) - throw new InvalidOperationException($"Only the last parameter in a command may have the Multiple flag. Parameter: {firstMultipleParam.Name} in {PrimaryAlias}"); - - var firstRemainderParam = _parameters.FirstOrDefault(x => x.IsRemainder); - if ((firstRemainderParam != null) && (firstRemainderParam != lastParam)) - throw new InvalidOperationException($"Only the last parameter in a command may have the Remainder flag. Parameter: {firstRemainderParam.Name} in {PrimaryAlias}"); - } + if (_parameters.Count <= 0) return new CommandInfo(this, info, service); + var lastParam = _parameters[_parameters.Count - 1]; + + var firstMultipleParam = _parameters.FirstOrDefault(x => x.IsMultiple); + if (firstMultipleParam != null && firstMultipleParam != lastParam) + throw new InvalidOperationException( + $"Only the last parameter in a command may have the Multiple flag. Parameter: {firstMultipleParam.Name} in {PrimaryAlias}"); + + var firstRemainderParam = _parameters.FirstOrDefault(x => x.IsRemainder); + if (firstRemainderParam != null && firstRemainderParam != lastParam) + throw new InvalidOperationException( + $"Only the last parameter in a command may have the Remainder flag. Parameter: {firstRemainderParam.Name} in {PrimaryAlias}"); return new CommandInfo(this, info, service); } diff --git a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs index 6dc50db31..3771dae3d 100644 --- a/src/Discord.Net.Commands/Builders/ModuleBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleBuilder.cs @@ -7,26 +7,11 @@ namespace Discord.Commands.Builders { public class ModuleBuilder { + private readonly List _aliases; + private readonly List _attributes; private readonly List _commands; - private readonly List _submodules; private readonly List _preconditions; - private readonly List _attributes; - private readonly List _aliases; - - public CommandService Service { get; } - public ModuleBuilder Parent { get; } - public string Name { get; set; } - public string Summary { get; set; } - public string Remarks { get; set; } - public string Group { get; set; } - - public IReadOnlyList Commands => _commands; - public IReadOnlyList Modules => _submodules; - public IReadOnlyList Preconditions => _preconditions; - public IReadOnlyList Attributes => _attributes; - public IReadOnlyList Aliases => _aliases; - - internal TypeInfo TypeInfo { get; set; } + private readonly List _submodules; //Automatic internal ModuleBuilder(CommandService service, ModuleBuilder parent) @@ -40,25 +25,43 @@ namespace Discord.Commands.Builders _attributes = new List(); _aliases = new List(); } + //User-defined internal ModuleBuilder(CommandService service, ModuleBuilder parent, string primaryAlias) : this(service, parent) { Discord.Preconditions.NotNull(primaryAlias, nameof(primaryAlias)); - _aliases = new List { primaryAlias }; + _aliases = new List {primaryAlias}; } + public CommandService Service { get; } + public ModuleBuilder Parent { get; } + public string Name { get; set; } + public string Summary { get; set; } + public string Remarks { get; set; } + public string Group { get; set; } + + public IReadOnlyList Commands => _commands; + public IReadOnlyList Modules => _submodules; + public IReadOnlyList Preconditions => _preconditions; + public IReadOnlyList Attributes => _attributes; + public IReadOnlyList Aliases => _aliases; + + internal TypeInfo TypeInfo { get; set; } + public ModuleBuilder WithName(string name) { Name = name; return this; } + public ModuleBuilder WithSummary(string summary) { Summary = summary; return this; } + public ModuleBuilder WithRemarks(string remarks) { Remarks = remarks; @@ -67,31 +70,38 @@ namespace Discord.Commands.Builders public ModuleBuilder AddAliases(params string[] aliases) { - for (int i = 0; i < aliases.Length; i++) + foreach (var t in aliases) { - string alias = aliases[i] ?? ""; + var alias = t ?? ""; if (!_aliases.Contains(alias)) _aliases.Add(alias); } + return this; } + public ModuleBuilder AddAttributes(params Attribute[] attributes) { _attributes.AddRange(attributes); return this; } + public ModuleBuilder AddPrecondition(PreconditionAttribute precondition) { _preconditions.Add(precondition); return this; } - public ModuleBuilder AddCommand(string primaryAlias, Func callback, Action createFunc) + + public ModuleBuilder AddCommand(string primaryAlias, + Func callback, + Action createFunc) { var builder = new CommandBuilder(this, primaryAlias, callback); createFunc(builder); _commands.Add(builder); return this; } + internal ModuleBuilder AddCommand(Action createFunc) { var builder = new CommandBuilder(this); @@ -99,6 +109,7 @@ namespace Discord.Commands.Builders _commands.Add(builder); return this; } + public ModuleBuilder AddModule(string primaryAlias, Action createFunc) { var builder = new ModuleBuilder(Service, this, primaryAlias); @@ -106,6 +117,7 @@ namespace Discord.Commands.Builders _submodules.Add(builder); return this; } + internal ModuleBuilder AddModule(Action createFunc) { var builder = new ModuleBuilder(Service, this); @@ -120,17 +132,16 @@ namespace Discord.Commands.Builders if (Name == null) Name = _aliases[0]; - if (TypeInfo != null && !TypeInfo.IsAbstract) - { - var moduleInstance = ReflectionUtils.CreateObject(TypeInfo, service, services); - moduleInstance.OnModuleBuilding(service, this); - } + if (TypeInfo == null || TypeInfo.IsAbstract) return new ModuleInfo(this, service, services, parent); + var moduleInstance = ReflectionUtils.CreateObject(TypeInfo, service, services); + moduleInstance.OnModuleBuilding(service, this); return new ModuleInfo(this, service, services, parent); } public ModuleInfo Build(CommandService service, IServiceProvider services) => BuildImpl(service, services); - internal ModuleInfo Build(CommandService service, IServiceProvider services, ModuleInfo parent) => BuildImpl(service, services, parent); + internal ModuleInfo Build(CommandService service, IServiceProvider services, ModuleInfo parent) => + BuildImpl(service, services, parent); } } diff --git a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs index cbe02aafb..71a088a93 100644 --- a/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ModuleClassBuilder.cs @@ -1,9 +1,8 @@ using System; -using System.Linq; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Threading.Tasks; - using Discord.Commands.Builders; namespace Discord.Commands @@ -17,38 +16,37 @@ namespace Discord.Commands bool IsLoadableModule(TypeInfo info) { return info.DeclaredMethods.Any(x => x.GetCustomAttribute() != null) && - info.GetCustomAttribute() == null; + info.GetCustomAttribute() == null; } var result = new List(); foreach (var typeInfo in assembly.DefinedTypes) - { if (typeInfo.IsPublic || typeInfo.IsNestedPublic) { if (IsValidModuleDefinition(typeInfo) && !typeInfo.IsDefined(typeof(DontAutoLoadAttribute))) - { result.Add(typeInfo); - } } else if (IsLoadableModule(typeInfo)) - { - await service._cmdLogger.WarningAsync($"Class {typeInfo.FullName} is not public and cannot be loaded. To suppress this message, mark the class with {nameof(DontAutoLoadAttribute)}."); - } - } + await service._cmdLogger.WarningAsync( + $"Class {typeInfo.FullName} is not public and cannot be loaded. To suppress this message, mark the class with {nameof(DontAutoLoadAttribute)}."); return result; } - public static Task> BuildAsync(CommandService service, IServiceProvider services, params TypeInfo[] validTypes) => BuildAsync(validTypes, service, services); - public static async Task> BuildAsync(IEnumerable validTypes, CommandService service, IServiceProvider services) + public static Task> BuildAsync(CommandService service, IServiceProvider services, + params TypeInfo[] validTypes) => BuildAsync(validTypes, service, services); + + public static async Task> BuildAsync(IEnumerable validTypes, + CommandService service, IServiceProvider services) { /*if (!validTypes.Any()) throw new InvalidOperationException("Could not find any valid modules from the given selection");*/ - var topLevelGroups = validTypes.Where(x => x.DeclaringType == null || !IsValidModuleDefinition(x.DeclaringType.GetTypeInfo())); + var topLevelGroups = validTypes.Where(x => + x.DeclaringType == null || !IsValidModuleDefinition(x.DeclaringType.GetTypeInfo())); var builtTypes = new List(); @@ -69,22 +67,24 @@ namespace Discord.Commands result[typeInfo.AsType()] = module.Build(service, services); } - await service._cmdLogger.DebugAsync($"Successfully built {builtTypes.Count} modules.").ConfigureAwait(false); + await service._cmdLogger.DebugAsync($"Successfully built {builtTypes.Count} modules.") + .ConfigureAwait(false); return result; } - private static void BuildSubTypes(ModuleBuilder builder, IEnumerable subTypes, List builtTypes, CommandService service, IServiceProvider services) + private static void BuildSubTypes(ModuleBuilder builder, IEnumerable subTypes, + List builtTypes, CommandService service, IServiceProvider services) { foreach (var typeInfo in subTypes) { if (!IsValidModuleDefinition(typeInfo)) continue; - + if (builtTypes.Contains(typeInfo)) continue; - - builder.AddModule((module) => + + builder.AddModule(module => { BuildModule(module, typeInfo, service, services); BuildSubTypes(module, typeInfo.DeclaredNestedTypes, builtTypes, service, services); @@ -94,13 +94,13 @@ namespace Discord.Commands } } - private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, CommandService service, IServiceProvider services) + private static void BuildModule(ModuleBuilder builder, TypeInfo typeInfo, CommandService service, + IServiceProvider services) { var attributes = typeInfo.GetCustomAttributes(); builder.TypeInfo = typeInfo; foreach (var attribute in attributes) - { switch (attribute) { case NameAttribute name: @@ -127,7 +127,6 @@ namespace Discord.Commands builder.AddAttributes(attribute); break; } - } //Check for unspecified info if (builder.Aliases.Count == 0) @@ -138,20 +137,15 @@ namespace Discord.Commands var validCommands = typeInfo.DeclaredMethods.Where(x => IsValidCommandDefinition(x)); foreach (var method in validCommands) - { - builder.AddCommand((command) => - { - BuildCommand(command, typeInfo, method, service, services); - }); - } + builder.AddCommand(command => { BuildCommand(command, typeInfo, method, service, services); }); } - private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, CommandService service, IServiceProvider serviceprovider) + private static void BuildCommand(CommandBuilder builder, TypeInfo typeInfo, MethodInfo method, + CommandService service, IServiceProvider serviceprovider) { var attributes = method.GetCustomAttributes(); - + foreach (var attribute in attributes) - { switch (attribute) { case CommandAttribute command: @@ -182,7 +176,6 @@ namespace Discord.Commands builder.AddAttributes(attribute); break; } - } if (builder.Name == null) builder.Name = method.Name; @@ -190,16 +183,15 @@ namespace Discord.Commands var parameters = method.GetParameters(); int pos = 0, count = parameters.Length; foreach (var paramInfo in parameters) - { - builder.AddParameter((parameter) => + builder.AddParameter(parameter => { BuildParameter(parameter, paramInfo, pos++, count, service, serviceprovider); }); - } var createInstance = ReflectionUtils.CreateBuilder(typeInfo, service); - async Task ExecuteCallback(ICommandContext context, object[] args, IServiceProvider services, CommandInfo cmd) + async Task ExecuteCallback(ICommandContext context, object[] args, IServiceProvider services, + CommandInfo cmd) { var instance = createInstance(services); instance.SetContext(context); @@ -210,9 +202,7 @@ namespace Discord.Commands var task = method.Invoke(instance, args) as Task ?? Task.Delay(0); if (task is Task resultTask) - { return await resultTask.ConfigureAwait(false); - } else { await task.ConfigureAwait(false); @@ -229,7 +219,8 @@ namespace Discord.Commands builder.Callback = ExecuteCallback; } - private static void BuildParameter(ParameterBuilder builder, System.Reflection.ParameterInfo paramInfo, int position, int count, CommandService service, IServiceProvider services) + private static void BuildParameter(ParameterBuilder builder, System.Reflection.ParameterInfo paramInfo, + int position, int count, CommandService service, IServiceProvider services) { var attributes = paramInfo.GetCustomAttributes(); var paramType = paramInfo.ParameterType; @@ -240,7 +231,6 @@ namespace Discord.Commands builder.DefaultValue = paramInfo.HasDefaultValue ? paramInfo.DefaultValue : null; foreach (var attribute in attributes) - { switch (attribute) { case SummaryAttribute summary: @@ -261,7 +251,8 @@ namespace Discord.Commands break; case RemainderAttribute _: if (position != count - 1) - throw new InvalidOperationException($"Remainder parameters must be the last parameter in a command. Parameter: {paramInfo.Name} in {paramInfo.Member.DeclaringType.Name}.{paramInfo.Member.Name}"); + throw new InvalidOperationException( + $"Remainder parameters must be the last parameter in a command. Parameter: {paramInfo.Name} in {paramInfo.Member.DeclaringType.Name}.{paramInfo.Member.Name}"); builder.IsRemainder = true; break; @@ -269,26 +260,22 @@ namespace Discord.Commands builder.AddAttributes(attribute); break; } - } builder.ParameterType = paramType; if (builder.TypeReader == null) - { builder.TypeReader = service.GetDefaultTypeReader(paramType) - ?? service.GetTypeReaders(paramType)?.FirstOrDefault().Value; - } + ?? service.GetTypeReaders(paramType)?.FirstOrDefault().Value; } - private static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType, IServiceProvider services) + private static TypeReader GetTypeReader(CommandService service, Type paramType, Type typeReaderType, + IServiceProvider services) { var readers = service.GetTypeReaders(paramType); - TypeReader reader = null; + TypeReader reader; if (readers != null) - { if (readers.TryGetValue(typeReaderType, out reader)) return reader; - } //We dont have a cached type reader, create one reader = ReflectionUtils.CreateObject(typeReaderType.GetTypeInfo(), service, services); @@ -297,19 +284,14 @@ namespace Discord.Commands return reader; } - private static bool IsValidModuleDefinition(TypeInfo typeInfo) - { - return _moduleTypeInfo.IsAssignableFrom(typeInfo) && - !typeInfo.IsAbstract && - !typeInfo.ContainsGenericParameters; - } + private static bool IsValidModuleDefinition(TypeInfo typeInfo) => _moduleTypeInfo.IsAssignableFrom(typeInfo) && + !typeInfo.IsAbstract && + !typeInfo.ContainsGenericParameters; - private static bool IsValidCommandDefinition(MethodInfo methodInfo) - { - return methodInfo.IsDefined(typeof(CommandAttribute)) && - (methodInfo.ReturnType == typeof(Task) || methodInfo.ReturnType == typeof(Task)) && - !methodInfo.IsStatic && - !methodInfo.IsGenericMethod; - } + private static bool IsValidCommandDefinition(MethodInfo methodInfo) => + methodInfo.IsDefined(typeof(CommandAttribute)) && + (methodInfo.ReturnType == typeof(Task) || methodInfo.ReturnType == typeof(Task)) && + !methodInfo.IsStatic && + !methodInfo.IsGenericMethod; } } diff --git a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs index 8a59c247c..54cd279f3 100644 --- a/src/Discord.Net.Commands/Builders/ParameterBuilder.cs +++ b/src/Discord.Net.Commands/Builders/ParameterBuilder.cs @@ -1,29 +1,14 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Collections.Generic; - namespace Discord.Commands.Builders { public class ParameterBuilder { - private readonly List _preconditions; private readonly List _attributes; - - public CommandBuilder Command { get; } - public string Name { get; internal set; } - public Type ParameterType { get; internal set; } - - public TypeReader TypeReader { get; set; } - public bool IsOptional { get; set; } - public bool IsRemainder { get; set; } - public bool IsMultiple { get; set; } - public object DefaultValue { get; set; } - public string Summary { get; set; } - - public IReadOnlyList Preconditions => _preconditions; - public IReadOnlyList Attributes => _attributes; + private readonly List _preconditions; //Automatic internal ParameterBuilder(CommandBuilder command) @@ -33,6 +18,7 @@ namespace Discord.Commands.Builders Command = command; } + //User-defined internal ParameterBuilder(CommandBuilder command, string name, Type type) : this(command) @@ -43,6 +29,20 @@ namespace Discord.Commands.Builders SetType(type); } + public CommandBuilder Command { get; } + public string Name { get; internal set; } + public Type ParameterType { get; internal set; } + + public TypeReader TypeReader { get; set; } + public bool IsOptional { get; set; } + public bool IsRemainder { get; set; } + public bool IsMultiple { get; set; } + public object DefaultValue { get; set; } + public string Summary { get; set; } + + public IReadOnlyList Preconditions => _preconditions; + public IReadOnlyList Attributes => _attributes; + internal void SetType(Type type) { TypeReader = GetReader(type); @@ -57,10 +57,7 @@ namespace Discord.Commands.Builders private TypeReader GetReader(Type type) { var readers = Command.Module.Service.GetTypeReaders(type); - if (readers != null) - return readers.FirstOrDefault().Value; - else - return Command.Module.Service.GetDefaultTypeReader(type); + return readers != null ? readers.FirstOrDefault().Value : Command.Module.Service.GetDefaultTypeReader(type); } public ParameterBuilder WithSummary(string summary) @@ -68,21 +65,25 @@ namespace Discord.Commands.Builders Summary = summary; return this; } + public ParameterBuilder WithDefault(object defaultValue) { DefaultValue = defaultValue; return this; } + public ParameterBuilder WithIsOptional(bool isOptional) { IsOptional = isOptional; return this; } + public ParameterBuilder WithIsRemainder(bool isRemainder) { IsRemainder = isRemainder; return this; } + public ParameterBuilder WithIsMultiple(bool isMultiple) { IsMultiple = isMultiple; @@ -94,6 +95,7 @@ namespace Discord.Commands.Builders _attributes.AddRange(attributes); return this; } + public ParameterBuilder AddPrecondition(ParameterPreconditionAttribute precondition) { _preconditions.Add(precondition); @@ -103,7 +105,8 @@ namespace Discord.Commands.Builders internal ParameterInfo Build(CommandInfo info) { if ((TypeReader ?? (TypeReader = GetReader(ParameterType))) == null) - throw new InvalidOperationException($"No type reader found for type {ParameterType.Name}, one must be specified"); + throw new InvalidOperationException( + $"No type reader found for type {ParameterType.Name}, one must be specified"); return new ParameterInfo(this, info, Command.Module.Service); } diff --git a/src/Discord.Net.Commands/CommandContext.cs b/src/Discord.Net.Commands/CommandContext.cs index 05bde56b1..5c2ee9ec8 100644 --- a/src/Discord.Net.Commands/CommandContext.cs +++ b/src/Discord.Net.Commands/CommandContext.cs @@ -2,14 +2,6 @@ { public class CommandContext : ICommandContext { - public IDiscordClient Client { get; } - public IGuild Guild { get; } - public IMessageChannel Channel { get; } - public IUser User { get; } - public IUserMessage Message { get; } - - public bool IsPrivate => Channel is IPrivateChannel; - public CommandContext(IDiscordClient client, IUserMessage msg) { Client = client; @@ -18,5 +10,12 @@ User = msg.Author; Message = msg; } + + public bool IsPrivate => Channel is IPrivateChannel; + public IDiscordClient Client { get; } + public IGuild Guild { get; } + public IMessageChannel Channel { get; } + public IUser User { get; } + public IUserMessage Message { get; } } } diff --git a/src/Discord.Net.Commands/CommandException.cs b/src/Discord.Net.Commands/CommandException.cs index d5300841a..2200e441d 100644 --- a/src/Discord.Net.Commands/CommandException.cs +++ b/src/Discord.Net.Commands/CommandException.cs @@ -4,14 +4,14 @@ namespace Discord.Commands { public class CommandException : Exception { - public CommandInfo Command { get; } - public ICommandContext Context { get; } - public CommandException(CommandInfo command, ICommandContext context, Exception ex) : base($"Error occurred executing {command.GetLogText(context)}.", ex) { Command = command; Context = context; } + + public CommandInfo Command { get; } + public ICommandContext Context { get; } } } diff --git a/src/Discord.Net.Commands/CommandMatch.cs b/src/Discord.Net.Commands/CommandMatch.cs index d922a2229..581cb4d47 100644 --- a/src/Discord.Net.Commands/CommandMatch.cs +++ b/src/Discord.Net.Commands/CommandMatch.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; namespace Discord.Commands { @@ -16,12 +15,18 @@ namespace Discord.Commands Alias = alias; } - public Task CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null) + public Task CheckPreconditionsAsync(ICommandContext context, + IServiceProvider services = null) => Command.CheckPreconditionsAsync(context, services); - public Task ParseAsync(ICommandContext context, SearchResult searchResult, PreconditionResult preconditionResult = null, IServiceProvider services = null) + + public Task ParseAsync(ICommandContext context, SearchResult searchResult, + PreconditionResult preconditionResult = null, IServiceProvider services = null) => Command.ParseAsync(context, Alias.Length, searchResult, preconditionResult, services); - public Task ExecuteAsync(ICommandContext context, IEnumerable argList, IEnumerable paramList, IServiceProvider services) + + public Task ExecuteAsync(ICommandContext context, IEnumerable argList, + IEnumerable paramList, IServiceProvider services) => Command.ExecuteAsync(context, argList, paramList, services); + public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) => Command.ExecuteAsync(context, parseResult, services); } diff --git a/src/Discord.Net.Commands/CommandParser.cs b/src/Discord.Net.Commands/CommandParser.cs index 9ce4e1469..19af516c5 100644 --- a/src/Discord.Net.Commands/CommandParser.cs +++ b/src/Discord.Net.Commands/CommandParser.cs @@ -8,22 +8,18 @@ namespace Discord.Commands { internal static class CommandParser { - private enum ParserPart - { - None, - Parameter, - QuotedParameter - } - public static async Task ParseArgsAsync(CommandInfo command, ICommandContext context, bool ignoreExtraArgs, IServiceProvider services, string input, int startPos, IReadOnlyDictionary aliasMap) + public static async Task ParseArgsAsync(CommandInfo command, ICommandContext context, + bool ignoreExtraArgs, IServiceProvider services, string input, int startPos, + IReadOnlyDictionary aliasMap) { ParameterInfo curParam = null; - StringBuilder argBuilder = new StringBuilder(input.Length); - int endPos = input.Length; + var argBuilder = new StringBuilder(input.Length); + var endPos = input.Length; var curPart = ParserPart.None; - int lastArgEndPos = int.MinValue; + var lastArgEndPos = int.MinValue; var argList = ImmutableArray.CreateBuilder(); var paramList = ImmutableArray.CreateBuilder(); - bool isEscaping = false; + var isEscaping = false; char c, matchQuote = '\0'; // local helper functions @@ -46,23 +42,19 @@ namespace Discord.Commands return '\"'; } - for (int curPos = startPos; curPos <= endPos; curPos++) + for (var curPos = startPos; curPos <= endPos; curPos++) { - if (curPos < endPos) - c = input[curPos]; - else - c = '\0'; + c = curPos < endPos ? input[curPos] : '\0'; //If this character is escaped, skip it if (isEscaping) - { if (curPos != endPos) { argBuilder.Append(c); isEscaping = false; continue; } - } + //Are we escaping the next character? if (c == '\\' && (curParam == null || !curParam.IsRemainder)) { @@ -82,98 +74,96 @@ namespace Discord.Commands { if (char.IsWhiteSpace(c) || curPos == endPos) continue; //Skip whitespace between arguments - else if (curPos == lastArgEndPos) - return ParseResult.FromError(CommandError.ParseFailed, "There must be at least one character of whitespace between arguments."); - else + if (curPos == lastArgEndPos) + return ParseResult.FromError(CommandError.ParseFailed, + "There must be at least one character of whitespace between arguments."); + if (curParam == null) + curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null; + + if (curParam != null && curParam.IsRemainder) { - if (curParam == null) - curParam = command.Parameters.Count > argList.Count ? command.Parameters[argList.Count] : null; - - if (curParam != null && curParam.IsRemainder) - { - argBuilder.Append(c); - continue; - } - - if (IsOpenQuote(aliasMap, c)) - { - curPart = ParserPart.QuotedParameter; - matchQuote = GetMatch(aliasMap, c); - continue; - } - curPart = ParserPart.Parameter; + argBuilder.Append(c); + continue; + } + + if (IsOpenQuote(aliasMap, c)) + { + curPart = ParserPart.QuotedParameter; + matchQuote = GetMatch(aliasMap, c); + continue; } + + curPart = ParserPart.Parameter; } //Has this parameter ended yet? string argString = null; - if (curPart == ParserPart.Parameter) + switch (curPart) { - if (curPos == endPos || char.IsWhiteSpace(c)) - { + case ParserPart.Parameter when curPos == endPos || char.IsWhiteSpace(c): argString = argBuilder.ToString(); lastArgEndPos = curPos; - } - else + break; + case ParserPart.Parameter: argBuilder.Append(c); - } - else if (curPart == ParserPart.QuotedParameter) - { - if (c == matchQuote) - { + break; + case ParserPart.QuotedParameter when c == matchQuote: argString = argBuilder.ToString(); //Remove quotes lastArgEndPos = curPos + 1; - } - else + break; + case ParserPart.QuotedParameter: argBuilder.Append(c); + break; } - - if (argString != null) + + if (argString == null) continue; + if (curParam == null) { - if (curParam == null) - { - if (command.IgnoreExtraArgs) - break; - else - return ParseResult.FromError(CommandError.BadArgCount, "The input text has too many parameters."); - } + if (command.IgnoreExtraArgs) + break; + return ParseResult.FromError(CommandError.BadArgCount, + "The input text has too many parameters."); + } - var typeReaderResult = await curParam.ParseAsync(context, argString, services).ConfigureAwait(false); - if (!typeReaderResult.IsSuccess && typeReaderResult.Error != CommandError.MultipleMatches) - return ParseResult.FromError(typeReaderResult); + var typeReaderResult = + await curParam.ParseAsync(context, argString, services).ConfigureAwait(false); + if (!typeReaderResult.IsSuccess && typeReaderResult.Error != CommandError.MultipleMatches) + return ParseResult.FromError(typeReaderResult); - if (curParam.IsMultiple) - { - paramList.Add(typeReaderResult); + if (curParam.IsMultiple) + { + paramList.Add(typeReaderResult); - curPart = ParserPart.None; - } - else - { - argList.Add(typeReaderResult); + curPart = ParserPart.None; + } + else + { + argList.Add(typeReaderResult); - curParam = null; - curPart = ParserPart.None; - } - argBuilder.Clear(); + curParam = null; + curPart = ParserPart.None; } + + argBuilder.Clear(); } if (curParam != null && curParam.IsRemainder) { - var typeReaderResult = await curParam.ParseAsync(context, argBuilder.ToString(), services).ConfigureAwait(false); + var typeReaderResult = await curParam.ParseAsync(context, argBuilder.ToString(), services) + .ConfigureAwait(false); if (!typeReaderResult.IsSuccess) return ParseResult.FromError(typeReaderResult); argList.Add(typeReaderResult); } if (isEscaping) - return ParseResult.FromError(CommandError.ParseFailed, "Input text may not end on an incomplete escape."); + return ParseResult.FromError(CommandError.ParseFailed, + "Input text may not end on an incomplete escape."); if (curPart == ParserPart.QuotedParameter) return ParseResult.FromError(CommandError.ParseFailed, "A quoted parameter is incomplete"); - + //Add missing optionals - for (int i = argList.Count; i < command.Parameters.Count; i++) + for (var i = argList.Count; i < command.Parameters.Count; i++) { var param = command.Parameters[i]; if (param.IsMultiple) @@ -182,8 +172,15 @@ namespace Discord.Commands return ParseResult.FromError(CommandError.BadArgCount, "The input text has too few parameters."); argList.Add(TypeReaderResult.FromSuccess(param.DefaultValue)); } - + return ParseResult.FromSuccess(argList.ToImmutable(), paramList.ToImmutable()); } + + private enum ParserPart + { + None, + Parameter, + QuotedParameter + } } } diff --git a/src/Discord.Net.Commands/CommandService.cs b/src/Discord.Net.Commands/CommandService.cs index 6fd5d38ad..9a8a79afe 100644 --- a/src/Discord.Net.Commands/CommandService.cs +++ b/src/Discord.Net.Commands/CommandService.cs @@ -13,40 +13,39 @@ namespace Discord.Commands { public class CommandService { - public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } - internal readonly AsyncEvent> _logEvent = new AsyncEvent>(); + internal readonly bool CaseSensitive, _throwOnError, _ignoreExtraArgs; + internal readonly Logger _cmdLogger; - public event Func CommandExecuted { add { _commandExecutedEvent.Add(value); } remove { _commandExecutedEvent.Remove(value); } } - internal readonly AsyncEvent> _commandExecutedEvent = new AsyncEvent>(); + internal readonly AsyncEvent> _commandExecutedEvent = + new AsyncEvent>(); - private readonly SemaphoreSlim _moduleLock; - private readonly ConcurrentDictionary _typedModuleDefs; - private readonly ConcurrentDictionary> _typeReaders; + internal readonly RunMode _defaultRunMode; private readonly ConcurrentDictionary _defaultTypeReaders; private readonly ImmutableList> _entityTypeReaders; //TODO: Candidate for C#7 Tuple - private readonly HashSet _moduleDefs; + internal readonly AsyncEvent> _logEvent = new AsyncEvent>(); + internal readonly LogManager _logManager; private readonly CommandMap _map; + private readonly HashSet _moduleDefs; - internal readonly bool _caseSensitive, _throwOnError, _ignoreExtraArgs; - internal readonly char _separatorChar; - internal readonly RunMode _defaultRunMode; - internal readonly Logger _cmdLogger; - internal readonly LogManager _logManager; + private readonly SemaphoreSlim _moduleLock; internal readonly IReadOnlyDictionary _quotationMarkAliasMap; + internal readonly char _separatorChar; + private readonly ConcurrentDictionary _typedModuleDefs; + private readonly ConcurrentDictionary> _typeReaders; - public IEnumerable Modules => _moduleDefs.Select(x => x); - public IEnumerable Commands => _moduleDefs.SelectMany(x => x.Commands); - public ILookup TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new { y.Key, y.Value })).ToLookup(x => x.Key, x => x.Value); + public CommandService() : this(new CommandServiceConfig()) + { + } - public CommandService() : this(new CommandServiceConfig()) { } public CommandService(CommandServiceConfig config) { - _caseSensitive = config.CaseSensitiveCommands; + CaseSensitive = config.CaseSensitiveCommands; _throwOnError = config.ThrowOnError; _ignoreExtraArgs = config.IgnoreExtraArgs; _separatorChar = config.SeparatorChar; _defaultRunMode = config.DefaultRunMode; - _quotationMarkAliasMap = (config.QuotationMarkAliasMap ?? new Dictionary()).ToImmutableDictionary(); + _quotationMarkAliasMap = + (config.QuotationMarkAliasMap ?? new Dictionary()).ToImmutableDictionary(); if (_defaultRunMode == RunMode.Default) throw new InvalidOperationException("The default run mode cannot be set to Default."); @@ -64,7 +63,8 @@ namespace Discord.Commands foreach (var type in PrimitiveParsers.SupportedTypes) { _defaultTypeReaders[type] = PrimitiveTypeReader.Create(type); - _defaultTypeReaders[typeof(Nullable<>).MakeGenericType(type)] = NullableTypeReader.Create(type, _defaultTypeReaders[type]); + _defaultTypeReaders[typeof(Nullable<>).MakeGenericType(type)] = + NullableTypeReader.Create(type, _defaultTypeReaders[type]); } var tsreader = new TimeSpanTypeReader(); @@ -72,7 +72,11 @@ namespace Discord.Commands _defaultTypeReaders[typeof(TimeSpan?)] = NullableTypeReader.Create(typeof(TimeSpan), tsreader); _defaultTypeReaders[typeof(string)] = - new PrimitiveTypeReader((string x, out string y) => { y = x; return true; }, 0); + new PrimitiveTypeReader((string x, out string y) => + { + y = x; + return true; + }, 0); var entityTypeReaders = ImmutableList.CreateBuilder>(); entityTypeReaders.Add(new Tuple(typeof(IMessage), typeof(MessageTypeReader<>))); @@ -82,6 +86,27 @@ namespace Discord.Commands _entityTypeReaders = entityTypeReaders.ToImmutable(); } + public IEnumerable Modules => _moduleDefs.Select(x => x); + public IEnumerable Commands => _moduleDefs.SelectMany(x => x.Commands); + + public ILookup TypeReaders => _typeReaders.SelectMany(x => x.Value.Select(y => new + { + y.Key, + y.Value + })).ToLookup(x => x.Key, x => x.Value); + + public event Func Log + { + add => _logEvent.Add(value); + remove => _logEvent.Remove(value); + } + + public event Func CommandExecuted + { + add => _commandExecutedEvent.Add(value); + remove => _commandExecutedEvent.Remove(value); + } + //Modules public async Task CreateModuleAsync(string primaryAlias, Action buildFunc) { @@ -102,12 +127,13 @@ namespace Discord.Commands } /// - /// Add a command module from a type + /// Add a command module from a type /// /// The type of module /// An IServiceProvider for your dependency injection solution, if using one - otherwise, pass null /// A built module public Task AddModuleAsync(IServiceProvider services) => AddModuleAsync(typeof(T), services); + public async Task AddModuleAsync(Type type, IServiceProvider services) { services = services ?? EmptyServiceProvider.Instance; @@ -118,14 +144,13 @@ namespace Discord.Commands var typeInfo = type.GetTypeInfo(); if (_typedModuleDefs.ContainsKey(type)) - throw new ArgumentException($"This module has already been added."); - - var module = (await ModuleClassBuilder.BuildAsync(this, services, typeInfo).ConfigureAwait(false)).FirstOrDefault(); + throw new ArgumentException("This module has already been added."); - if (module.Value == default(ModuleInfo)) - throw new InvalidOperationException($"Could not build the module {type.FullName}, did you pass an invalid type?"); + var module = (await ModuleClassBuilder.BuildAsync(this, services, typeInfo).ConfigureAwait(false)) + .FirstOrDefault(); - _typedModuleDefs[module.Key] = module.Value; + _typedModuleDefs[module.Key] = module.Value ?? throw new InvalidOperationException( + $"Could not build the module {type.FullName}, did you pass an invalid type?"); return LoadModuleInternal(module.Value); } @@ -134,8 +159,9 @@ namespace Discord.Commands _moduleLock.Release(); } } + /// - /// Add command modules from an assembly + /// Add command modules from an assembly /// /// The assembly containing command modules /// An IServiceProvider for your dependency injection solution, if using one - otherwise, pass null @@ -163,6 +189,7 @@ namespace Discord.Commands _moduleLock.Release(); } } + private ModuleInfo LoadModuleInternal(ModuleInfo module) { _moduleDefs.Add(module); @@ -188,22 +215,22 @@ namespace Discord.Commands _moduleLock.Release(); } } + public Task RemoveModuleAsync() => RemoveModuleAsync(typeof(T)); + public async Task RemoveModuleAsync(Type type) { await _moduleLock.WaitAsync().ConfigureAwait(false); try { - if (!_typedModuleDefs.TryRemove(type, out var module)) - return false; - - return RemoveModuleInternal(module); + return _typedModuleDefs.TryRemove(type, out var module) && RemoveModuleInternal(module); } finally { _moduleLock.Release(); } } + private bool RemoveModuleInternal(ModuleInfo module) { if (!_moduleDefs.Remove(module)) @@ -212,60 +239,73 @@ namespace Discord.Commands foreach (var cmd in module.Commands) _map.RemoveCommand(cmd); - foreach (var submodule in module.Submodules) - { - RemoveModuleInternal(submodule); - } + foreach (var submodule in module.Submodules) RemoveModuleInternal(submodule); return true; } //Type Readers /// - /// Adds a custom to this for the supplied object type. - /// If is a , a will also be added. - /// If a default exists for , a warning will be logged and the default will be replaced. + /// Adds a custom to this for the supplied object type. + /// If is a , a will also be + /// added. + /// If a default exists for , a warning will be logged and the + /// default will be replaced. /// - /// The object type to be read by the . - /// An instance of the to be added. + /// The object type to be read by the . + /// An instance of the to be added. public void AddTypeReader(TypeReader reader) => AddTypeReader(typeof(T), reader); + /// - /// Adds a custom to this for the supplied object type. - /// If is a , a for the value type will also be added. - /// If a default exists for , a warning will be logged and the default will be replaced. + /// Adds a custom to this for the supplied object type. + /// If is a , a for the value + /// type will also be added. + /// If a default exists for , a warning will be logged and the + /// default will be replaced. /// - /// A instance for the type to be read. - /// An instance of the to be added. + /// A instance for the type to be read. + /// An instance of the to be added. public void AddTypeReader(Type type, TypeReader reader) { if (_defaultTypeReaders.ContainsKey(type)) - _ = _cmdLogger.WarningAsync($"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}." + - $"To suppress this message, use AddTypeReader(reader, true)."); + _ = _cmdLogger.WarningAsync( + $"The default TypeReader for {type.FullName} was replaced by {reader.GetType().FullName}." + + "To suppress this message, use AddTypeReader(reader, true)."); AddTypeReader(type, reader, true); } + /// - /// Adds a custom to this for the supplied object type. - /// If is a , a will also be added. + /// Adds a custom to this for the supplied object type. + /// If is a , a will also be + /// added. /// - /// The object type to be read by the . - /// An instance of the to be added. - /// If should replace the default for if one exists. + /// The object type to be read by the . + /// An instance of the to be added. + /// + /// If should replace the default for + /// if one exists. + /// public void AddTypeReader(TypeReader reader, bool replaceDefault) => AddTypeReader(typeof(T), reader, replaceDefault); + /// - /// Adds a custom to this for the supplied object type. - /// If is a , a for the value type will also be added. + /// Adds a custom to this for the supplied object type. + /// If is a , a for the value + /// type will also be added. /// - /// A instance for the type to be read. - /// An instance of the to be added. - /// If should replace the default for if one exists. + /// A instance for the type to be read. + /// An instance of the to be added. + /// + /// If should replace the default for + /// if one exists. + /// public void AddTypeReader(Type type, TypeReader reader, bool replaceDefault) { if (replaceDefault && HasDefaultTypeReader(type)) { _defaultTypeReaders.AddOrUpdate(type, reader, (k, v) => reader); - if (type.GetTypeInfo().IsValueType) + if (!type.GetTypeInfo().IsValueType) return; { var nullableType = typeof(Nullable<>).MakeGenericType(type); var nullableReader = NullableTypeReader.Create(type, reader); @@ -281,28 +321,29 @@ namespace Discord.Commands AddNullableTypeReader(type, reader); } } + internal bool HasDefaultTypeReader(Type type) { if (_defaultTypeReaders.ContainsKey(type)) return true; var typeInfo = type.GetTypeInfo(); - if (typeInfo.IsEnum) - return true; - return _entityTypeReaders.Any(x => type == x.Item1 || typeInfo.ImplementedInterfaces.Contains(x.Item2)); + return typeInfo.IsEnum || _entityTypeReaders.Any(x => type == x.Item1 || typeInfo.ImplementedInterfaces.Contains(x.Item2)); } + internal void AddNullableTypeReader(Type valueType, TypeReader valueTypeReader) { - var readers = _typeReaders.GetOrAdd(typeof(Nullable<>).MakeGenericType(valueType), x => new ConcurrentDictionary()); + var readers = _typeReaders.GetOrAdd(typeof(Nullable<>).MakeGenericType(valueType), + x => new ConcurrentDictionary()); var nullableReader = NullableTypeReader.Create(valueType, valueTypeReader); readers[nullableReader.GetType()] = nullableReader; } + internal IDictionary GetTypeReaders(Type type) { - if (_typeReaders.TryGetValue(type, out var definedTypeReaders)) - return definedTypeReaders; - return null; + return _typeReaders.TryGetValue(type, out var definedTypeReaders) ? definedTypeReaders : null; } + internal TypeReader GetDefaultTypeReader(Type type) { if (_defaultTypeReaders.TryGetValue(type, out var reader)) @@ -318,37 +359,39 @@ namespace Discord.Commands } //Is this an entity? - for (int i = 0; i < _entityTypeReaders.Count; i++) - { - if (type == _entityTypeReaders[i].Item1 || typeInfo.ImplementedInterfaces.Contains(_entityTypeReaders[i].Item1)) + foreach (var t in _entityTypeReaders) + if (type == t.Item1 || + typeInfo.ImplementedInterfaces.Contains(t.Item1)) { - reader = Activator.CreateInstance(_entityTypeReaders[i].Item2.MakeGenericType(type)) as TypeReader; + reader = Activator.CreateInstance(t.Item2.MakeGenericType(type)) as TypeReader; _defaultTypeReaders[type] = reader; return reader; } - } + return null; } //Execution public SearchResult Search(ICommandContext context, int argPos) => Search(context.Message.Content.Substring(argPos)); + public SearchResult Search(ICommandContext context, string input) => Search(input); + public SearchResult Search(string input) { - string searchInput = _caseSensitive ? input : input.ToLowerInvariant(); + var searchInput = CaseSensitive ? input : input.ToLowerInvariant(); var matches = _map.GetCommands(searchInput).OrderByDescending(x => x.Command.Priority).ToImmutableArray(); - if (matches.Length > 0) - return SearchResult.FromSuccess(input, matches); - else - return SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); + return matches.Length > 0 ? SearchResult.FromSuccess(input, matches) : SearchResult.FromError(CommandError.UnknownCommand, "Unknown command."); } - public Task ExecuteAsync(ICommandContext context, int argPos, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + public Task ExecuteAsync(ICommandContext context, int argPos, IServiceProvider services, + MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) => ExecuteAsync(context, context.Message.Content.Substring(argPos), services, multiMatchHandling); - public async Task ExecuteAsync(ICommandContext context, string input, IServiceProvider services, MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) + + public async Task ExecuteAsync(ICommandContext context, string input, IServiceProvider services, + MultiMatchHandling multiMatchHandling = MultiMatchHandling.Exception) { services = services ?? EmptyServiceProvider.Instance; @@ -360,9 +403,8 @@ namespace Discord.Commands var preconditionResults = new Dictionary(); foreach (var match in commands) - { - preconditionResults[match] = await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false); - } + preconditionResults[match] = + await match.Command.CheckPreconditionsAsync(context, services).ConfigureAwait(false); var successfulPreconditions = preconditionResults .Where(x => x.Value.IsSuccess) @@ -382,16 +424,18 @@ namespace Discord.Commands var parseResultsDict = new Dictionary(); foreach (var pair in successfulPreconditions) { - var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services).ConfigureAwait(false); + var parseResult = await pair.Key.ParseAsync(context, searchResult, pair.Value, services) + .ConfigureAwait(false); if (parseResult.Error == CommandError.MultipleMatches) { - IReadOnlyList argList, paramList; switch (multiMatchHandling) { case MultiMatchHandling.Best: - argList = parseResult.ArgValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); - paramList = parseResult.ParamValues.Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); + IReadOnlyList argList = parseResult.ArgValues + .Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); + IReadOnlyList paramList = parseResult.ParamValues + .Select(x => x.Values.OrderByDescending(y => y.Score).First()).ToImmutableArray(); parseResult = ParseResult.FromSuccess(argList, paramList); break; } @@ -407,8 +451,11 @@ namespace Discord.Commands if (match.Command.Parameters.Count > 0) { - var argValuesSum = parseResult.ArgValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; - var paramValuesSum = parseResult.ParamValues?.Sum(x => x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; + var argValuesSum = + parseResult.ArgValues?.Sum(x => + x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; + var paramValuesSum = parseResult.ParamValues?.Sum(x => + x.Values.OrderByDescending(y => y.Score).FirstOrDefault().Score) ?? 0; argValuesScore = argValuesSum / match.Command.Parameters.Count; paramValuesScore = paramValuesSum / match.Command.Parameters.Count; diff --git a/src/Discord.Net.Commands/CommandServiceConfig.cs b/src/Discord.Net.Commands/CommandServiceConfig.cs index 00091634d..555b28697 100644 --- a/src/Discord.Net.Commands/CommandServiceConfig.cs +++ b/src/Discord.Net.Commands/CommandServiceConfig.cs @@ -1,15 +1,17 @@ -using System; using System.Collections.Generic; namespace Discord.Commands { public class CommandServiceConfig { - /// Gets or sets the default RunMode commands should have, if one is not specified on the Command attribute or builder. + /// + /// Gets or sets the default RunMode commands should have, if one is not specified on the Command attribute or + /// builder. + /// public RunMode DefaultRunMode { get; set; } = RunMode.Sync; public char SeparatorChar { get; set; } = ' '; - + /// Determines whether commands should be case-sensitive. public bool CaseSensitiveCommands { get; set; } = false; @@ -19,8 +21,10 @@ namespace Discord.Commands /// Determines whether RunMode.Sync commands should push exceptions up to the caller. public bool ThrowOnError { get; set; } = true; - /// Collection of aliases that can wrap strings for command parsing. - /// represents the opening quotation mark and the value is the corresponding closing mark. + /// + /// Collection of aliases that can wrap strings for command parsing. + /// represents the opening quotation mark and the value is the corresponding closing mark. + /// public Dictionary QuotationMarkAliasMap { get; set; } = QuotationAliasUtils.GetDefaultAliasMap; /// Determines whether extra parameters should be ignored. diff --git a/src/Discord.Net.Commands/EmptyServiceProvider.cs b/src/Discord.Net.Commands/EmptyServiceProvider.cs index 0bef3760e..f9cda2a70 100644 --- a/src/Discord.Net.Commands/EmptyServiceProvider.cs +++ b/src/Discord.Net.Commands/EmptyServiceProvider.cs @@ -5,7 +5,7 @@ namespace Discord.Commands internal class EmptyServiceProvider : IServiceProvider { public static readonly EmptyServiceProvider Instance = new EmptyServiceProvider(); - + public object GetService(Type serviceType) => null; } } diff --git a/src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs b/src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs index b922dd903..14d9b379c 100644 --- a/src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs +++ b/src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Discord.Commands { @@ -10,13 +11,7 @@ namespace Discord.Commands IEnumerable others, Func func) { - foreach (TFirst elem in set) - { - foreach (TSecond elem2 in others) - { - yield return func(elem, elem2); - } - } + return from elem in set from elem2 in others select func(elem, elem2); } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs index a27c5f322..0d994fd80 100644 --- a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs +++ b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs @@ -7,40 +7,37 @@ namespace Discord.Commands public static bool HasCharPrefix(this IUserMessage msg, char c, ref int argPos) { var text = msg.Content; - if (text.Length > 0 && text[0] == c) - { - argPos = 1; - return true; - } - return false; + if (text.Length <= 0 || text[0] != c) return false; + argPos = 1; + return true; + } - public static bool HasStringPrefix(this IUserMessage msg, string str, ref int argPos, StringComparison comparisonType = StringComparison.Ordinal) + + public static bool HasStringPrefix(this IUserMessage msg, string str, ref int argPos, + StringComparison comparisonType = StringComparison.Ordinal) { var text = msg.Content; - if (text.StartsWith(str, comparisonType)) - { - argPos = str.Length; - return true; - } - return false; + if (!text.StartsWith(str, comparisonType)) return false; + argPos = str.Length; + return true; + } + public static bool HasMentionPrefix(this IUserMessage msg, IUser user, ref int argPos) { var text = msg.Content; if (text.Length <= 3 || text[0] != '<' || text[1] != '@') return false; - int endPos = text.IndexOf('>'); + var endPos = text.IndexOf('>'); if (endPos == -1) return false; if (text.Length < endPos + 2 || text[endPos + 1] != ' ') return false; //Must end in "> " ulong userId; if (!MentionUtils.TryParseUser(text.Substring(0, endPos + 1), out userId)) return false; - if (userId == user.Id) - { - argPos = endPos + 2; - return true; - } - return false; + if (userId != user.Id) return false; + argPos = endPos + 2; + return true; + } } } diff --git a/src/Discord.Net.Commands/IModuleBase.cs b/src/Discord.Net.Commands/IModuleBase.cs index 3b641ec5f..db0556027 100644 --- a/src/Discord.Net.Commands/IModuleBase.cs +++ b/src/Discord.Net.Commands/IModuleBase.cs @@ -7,7 +7,7 @@ namespace Discord.Commands void SetContext(ICommandContext context); void BeforeExecute(CommandInfo command); - + void AfterExecute(CommandInfo command); void OnModuleBuilding(CommandService commandService, ModuleBuilder builder); diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index df0bb297b..acf648ca0 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -1,39 +1,28 @@ -using Discord.Commands.Builders; -using System; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; -using System.Collections.Concurrent; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.ExceptionServices; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; +using Discord.Commands.Builders; namespace Discord.Commands { - [DebuggerDisplay("{Name,nq}")] + [DebuggerDisplay("{" + nameof(Name) + ",nq}")] public class CommandInfo { - private static readonly System.Reflection.MethodInfo _convertParamsMethod = typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); - private static readonly ConcurrentDictionary, object>> _arrayConverters = new ConcurrentDictionary, object>>(); + private static readonly MethodInfo _convertParamsMethod = + typeof(CommandInfo).GetTypeInfo().GetDeclaredMethod(nameof(ConvertParamsList)); - private readonly CommandService _commandService; - private readonly Func _action; + private static readonly ConcurrentDictionary, object>> _arrayConverters = + new ConcurrentDictionary, object>>(); - public ModuleInfo Module { get; } - public string Name { get; } - public string Summary { get; } - public string Remarks { get; } - public int Priority { get; } - public bool HasVarArgs { get; } - public bool IgnoreExtraArgs { get; } - public RunMode RunMode { get; } + private readonly Func _action; - public IReadOnlyList Aliases { get; } - public IReadOnlyList Parameters { get; } - public IReadOnlyList Preconditions { get; } - public IReadOnlyList Attributes { get; } + private readonly CommandService _commandService; internal CommandInfo(CommandBuilder builder, ModuleInfo module, CommandService service) { @@ -43,7 +32,7 @@ namespace Discord.Commands Summary = builder.Summary; Remarks = builder.Remarks; - RunMode = (builder.RunMode == RunMode.Default ? service._defaultRunMode : builder.RunMode); + RunMode = builder.RunMode == RunMode.Default ? service._defaultRunMode : builder.RunMode; Priority = builder.Priority; Aliases = module.Aliases @@ -51,52 +40,66 @@ namespace Discord.Commands { if (first == "") return second; - else if (second == "") + if (second == "") return first; - else - return first + service._separatorChar + second; + return first + service._separatorChar + second; }) - .Select(x => service._caseSensitive ? x : x.ToLowerInvariant()) + .Select(x => service.CaseSensitive ? x : x.ToLowerInvariant()) .ToImmutableArray(); Preconditions = builder.Preconditions.ToImmutableArray(); Attributes = builder.Attributes.ToImmutableArray(); Parameters = builder.Parameters.Select(x => x.Build(this)).ToImmutableArray(); - HasVarArgs = builder.Parameters.Count > 0 ? builder.Parameters[builder.Parameters.Count - 1].IsMultiple : false; + HasVarArgs = builder.Parameters.Count > 0 && builder.Parameters[builder.Parameters.Count - 1].IsMultiple; IgnoreExtraArgs = builder.IgnoreExtraArgs; _action = builder.Callback; _commandService = service; } - public async Task CheckPreconditionsAsync(ICommandContext context, IServiceProvider services = null) + public ModuleInfo Module { get; } + public string Name { get; } + public string Summary { get; } + public string Remarks { get; } + public int Priority { get; } + public bool HasVarArgs { get; } + public bool IgnoreExtraArgs { get; } + public RunMode RunMode { get; } + + public IReadOnlyList Aliases { get; } + public IReadOnlyList Parameters { get; } + public IReadOnlyList Preconditions { get; } + public IReadOnlyList Attributes { get; } + + public async Task CheckPreconditionsAsync(ICommandContext context, + IServiceProvider services = null) { services = services ?? EmptyServiceProvider.Instance; async Task CheckGroups(IEnumerable preconditions, string type) { - foreach (IGrouping preconditionGroup in preconditions.GroupBy(p => p.Group, StringComparer.Ordinal)) - { + foreach (var preconditionGroup in preconditions.GroupBy(p => p.Group, StringComparer.Ordinal)) if (preconditionGroup.Key == null) - { - foreach (PreconditionAttribute precondition in preconditionGroup) + foreach (var precondition in preconditionGroup) { - var result = await precondition.CheckPermissionsAsync(context, this, services).ConfigureAwait(false); + var result = await precondition.CheckPermissionsAsync(context, this, services) + .ConfigureAwait(false); if (!result.IsSuccess) return result; } - } else { var results = new List(); - foreach (PreconditionAttribute precondition in preconditionGroup) - results.Add(await precondition.CheckPermissionsAsync(context, this, services).ConfigureAwait(false)); + foreach (var precondition in preconditionGroup) + results.Add(await precondition.CheckPermissionsAsync(context, this, services) + .ConfigureAwait(false)); if (!results.Any(p => p.IsSuccess)) - return PreconditionGroupResult.FromError($"{type} precondition group {preconditionGroup.Key} failed.", results); + return PreconditionGroupResult.FromError( + $"{type} precondition group {preconditionGroup.Key} failed.", results); } - } + return PreconditionGroupResult.FromSuccess(); } @@ -105,13 +108,11 @@ namespace Discord.Commands return moduleResult; var commandResult = await CheckGroups(Preconditions, "Command"); - if (!commandResult.IsSuccess) - return commandResult; - - return PreconditionResult.FromSuccess(); + return !commandResult.IsSuccess ? commandResult : PreconditionResult.FromSuccess(); } - public async Task ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, PreconditionResult preconditionResult = null, IServiceProvider services = null) + public async Task ParseAsync(ICommandContext context, int startIndex, SearchResult searchResult, + PreconditionResult preconditionResult = null, IServiceProvider services = null) { services = services ?? EmptyServiceProvider.Instance; @@ -120,9 +121,10 @@ namespace Discord.Commands if (preconditionResult != null && !preconditionResult.IsSuccess) return ParseResult.FromError(preconditionResult); - string input = searchResult.Text.Substring(startIndex); + var input = searchResult.Text.Substring(startIndex); - return await CommandParser.ParseArgsAsync(this, context, _commandService._ignoreExtraArgs, services, input, 0, _commandService._quotationMarkAliasMap).ConfigureAwait(false); + return await CommandParser.ParseArgsAsync(this, context, _commandService._ignoreExtraArgs, services, input, + 0, _commandService._quotationMarkAliasMap).ConfigureAwait(false); } public Task ExecuteAsync(ICommandContext context, ParseResult parseResult, IServiceProvider services) @@ -131,7 +133,7 @@ namespace Discord.Commands return Task.FromResult((IResult)ExecuteResult.FromError(parseResult)); var argList = new object[parseResult.ArgValues.Count]; - for (int i = 0; i < parseResult.ArgValues.Count; i++) + for (var i = 0; i < parseResult.ArgValues.Count; i++) { if (!parseResult.ArgValues[i].IsSuccess) return Task.FromResult((IResult)ExecuteResult.FromError(parseResult.ArgValues[i])); @@ -139,7 +141,7 @@ namespace Discord.Commands } var paramList = new object[parseResult.ParamValues.Count]; - for (int i = 0; i < parseResult.ParamValues.Count; i++) + for (var i = 0; i < parseResult.ParamValues.Count; i++) { if (!parseResult.ParamValues[i].IsSuccess) return Task.FromResult((IResult)ExecuteResult.FromError(parseResult.ParamValues[i])); @@ -148,19 +150,22 @@ namespace Discord.Commands return ExecuteAsync(context, argList, paramList, services); } - public async Task ExecuteAsync(ICommandContext context, IEnumerable argList, IEnumerable paramList, IServiceProvider services) + + public async Task ExecuteAsync(ICommandContext context, IEnumerable argList, + IEnumerable paramList, IServiceProvider services) { services = services ?? EmptyServiceProvider.Instance; try { - object[] args = GenerateArgs(argList, paramList); + var args = GenerateArgs(argList, paramList); - for (int position = 0; position < Parameters.Count; position++) + for (var position = 0; position < Parameters.Count; position++) { var parameter = Parameters[position]; - object argument = args[position]; - var result = await parameter.CheckPreconditionsAsync(context, argument, services).ConfigureAwait(false); + var argument = args[position]; + var result = await parameter.CheckPreconditionsAsync(context, argument, services) + .ConfigureAwait(false); if (!result.IsSuccess) return ExecuteResult.FromError(result); } @@ -176,6 +181,7 @@ namespace Discord.Commands }); break; } + return ExecuteResult.FromSuccess(); } catch (Exception ex) @@ -184,30 +190,36 @@ namespace Discord.Commands } } - private async Task ExecuteInternalAsync(ICommandContext context, object[] args, IServiceProvider services) + private async Task ExecuteInternalAsync(ICommandContext context, object[] args, + IServiceProvider services) { await Module.Service._cmdLogger.DebugAsync($"Executing {GetLogText(context)}").ConfigureAwait(false); try { var task = _action(context, args, services, this); - if (task is Task resultTask) - { - var result = await resultTask.ConfigureAwait(false); - await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); - if (result is RuntimeResult execResult) - return execResult; - } - else if (task is Task execTask) + switch (task) { - var result = await execTask.ConfigureAwait(false); - await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); - return result; - } - else - { - await task.ConfigureAwait(false); - var result = ExecuteResult.FromSuccess(); - await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); + case Task resultTask: + { + var result = await resultTask.ConfigureAwait(false); + await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); + if (result is RuntimeResult execResult) + return execResult; + break; + } + case Task execTask: + { + var result = await execTask.ConfigureAwait(false); + await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); + return result; + } + default: + { + await task.ConfigureAwait(false); + var result = ExecuteResult.FromSuccess(); + await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); + break; + } } var executeResult = ExecuteResult.FromSuccess(); @@ -221,13 +233,11 @@ namespace Discord.Commands var wrappedEx = new CommandException(this, context, ex); await Module.Service._cmdLogger.ErrorAsync(wrappedEx).ConfigureAwait(false); - if (Module.Service._throwOnError) - { - if (ex == originalEx) - throw; - else - ExceptionDispatchInfo.Capture(ex).Throw(); - } + if (!Module.Service._throwOnError) return ExecuteResult.FromError(CommandError.Exception, ex.Message); + if (ex == originalEx) + throw; + else + ExceptionDispatchInfo.Capture(ex).Throw(); return ExecuteResult.FromError(CommandError.Exception, ex.Message); } @@ -239,30 +249,30 @@ namespace Discord.Commands private object[] GenerateArgs(IEnumerable argList, IEnumerable paramsList) { - int argCount = Parameters.Count; + var argCount = Parameters.Count; var array = new object[Parameters.Count]; if (HasVarArgs) argCount--; - int i = 0; - foreach (object arg in argList) + var i = 0; + foreach (var arg in argList) { if (i == argCount) throw new InvalidOperationException("Command was invoked with too many parameters"); array[i++] = arg; } + if (i < argCount) throw new InvalidOperationException("Command was invoked with too few parameters"); - if (HasVarArgs) + if (!HasVarArgs) return array; + var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].Type, t => { - var func = _arrayConverters.GetOrAdd(Parameters[Parameters.Count - 1].Type, t => - { - var method = _convertParamsMethod.MakeGenericMethod(t); - return (Func, object>)method.CreateDelegate(typeof(Func, object>)); - }); - array[i] = func(paramsList); - } + var method = _convertParamsMethod.MakeGenericMethod(t); + return (Func, object>)method.CreateDelegate( + typeof(Func, object>)); + }); + array[i] = func(paramsList); return array; } @@ -272,10 +282,7 @@ namespace Discord.Commands internal string GetLogText(ICommandContext context) { - if (context.Guild != null) - return $"\"{Name}\" for {context.User} in {context.Guild}/{context.Channel}"; - else - return $"\"{Name}\" for {context.User} in {context.Channel}"; + return context.Guild != null ? $"\"{Name}\" for {context.User} in {context.Guild}/{context.Channel}" : $"\"{Name}\" for {context.User} in {context.Channel}"; } } } diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index 7c144599b..7ef7e60be 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -1,28 +1,15 @@ using System; -using System.Linq; using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; using Discord.Commands.Builders; namespace Discord.Commands { public class ModuleInfo { - public CommandService Service { get; } - public string Name { get; } - public string Summary { get; } - public string Remarks { get; } - public string Group { get; } - - public IReadOnlyList Aliases { get; } - public IReadOnlyList Commands { get; } - public IReadOnlyList Preconditions { get; } - public IReadOnlyList Attributes { get; } - public IReadOnlyList Submodules { get; } - public ModuleInfo Parent { get; } - public bool IsSubmodule => Parent != null; - - internal ModuleInfo(ModuleBuilder builder, CommandService service, IServiceProvider services, ModuleInfo parent = null) + internal ModuleInfo(ModuleBuilder builder, CommandService service, IServiceProvider services, + ModuleInfo parent = null) { Service = service; @@ -40,6 +27,20 @@ namespace Discord.Commands Submodules = BuildSubmodules(builder, service, services).ToImmutableArray(); } + public CommandService Service { get; } + public string Name { get; } + public string Summary { get; } + public string Remarks { get; } + public string Group { get; } + + public IReadOnlyList Aliases { get; } + public IReadOnlyList Commands { get; } + public IReadOnlyList Preconditions { get; } + public IReadOnlyList Attributes { get; } + public IReadOnlyList Submodules { get; } + public ModuleInfo Parent { get; } + public bool IsSubmodule => Parent != null; + private static IEnumerable BuildAliases(ModuleBuilder builder, CommandService service) { var result = builder.Aliases.ToList(); @@ -57,31 +58,26 @@ namespace Discord.Commands { if (first == "") return second; - else if (second == "") + if (second == "") return first; - else - return first + service._separatorChar + second; + return first + service._separatorChar + second; }).ToList(); } return result; } - private List BuildSubmodules(ModuleBuilder parent, CommandService service, IServiceProvider services) + private IEnumerable BuildSubmodules(ModuleBuilder parent, CommandService service, + IServiceProvider services) { - var result = new List(); - - foreach (var submodule in parent.Modules) - result.Add(submodule.Build(service, services, this)); - - return result; + return parent.Modules.Select(submodule => submodule.Build(service, services, this)).ToList(); } - private static List BuildPreconditions(ModuleBuilder builder) + private static IEnumerable BuildPreconditions(ModuleBuilder builder) { var result = new List(); - ModuleBuilder parent = builder; + var parent = builder; while (parent != null) { result.AddRange(parent.Preconditions); @@ -95,7 +91,7 @@ namespace Discord.Commands { var result = new List(); - ModuleBuilder parent = builder; + var parent = builder; while (parent != null) { result.AddRange(parent.Attributes); diff --git a/src/Discord.Net.Commands/Info/ParameterInfo.cs b/src/Discord.Net.Commands/Info/ParameterInfo.cs index 4a56415e5..f9c14677c 100644 --- a/src/Discord.Net.Commands/Info/ParameterInfo.cs +++ b/src/Discord.Net.Commands/Info/ParameterInfo.cs @@ -1,9 +1,8 @@ -using Discord.Commands.Builders; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; +using Discord.Commands.Builders; namespace Discord.Commands { @@ -11,18 +10,6 @@ namespace Discord.Commands { private readonly TypeReader _reader; - public CommandInfo Command { get; } - public string Name { get; } - public string Summary { get; } - public bool IsOptional { get; } - public bool IsRemainder { get; } - public bool IsMultiple { get; } - public Type Type { get; } - public object DefaultValue { get; } - - public IReadOnlyList Preconditions { get; } - public IReadOnlyList Attributes { get; } - internal ParameterInfo(ParameterBuilder builder, CommandInfo command, CommandService service) { Command = command; @@ -42,13 +29,30 @@ namespace Discord.Commands _reader = builder.TypeReader; } - public async Task CheckPreconditionsAsync(ICommandContext context, object arg, IServiceProvider services = null) + public CommandInfo Command { get; } + public string Name { get; } + public string Summary { get; } + public bool IsOptional { get; } + public bool IsRemainder { get; } + public bool IsMultiple { get; } + public Type Type { get; } + public object DefaultValue { get; } + + public IReadOnlyList Preconditions { get; } + public IReadOnlyList Attributes { get; } + + private string DebuggerDisplay => + $"{Name}{(IsOptional ? " (Optional)" : "")}{(IsRemainder ? " (Remainder)" : "")}"; + + public async Task CheckPreconditionsAsync(ICommandContext context, object arg, + IServiceProvider services = null) { services = services ?? EmptyServiceProvider.Instance; foreach (var precondition in Preconditions) { - var result = await precondition.CheckPermissionsAsync(context, this, arg, services).ConfigureAwait(false); + var result = await precondition.CheckPermissionsAsync(context, this, arg, services) + .ConfigureAwait(false); if (!result.IsSuccess) return result; } @@ -56,13 +60,13 @@ namespace Discord.Commands return PreconditionResult.FromSuccess(); } - public async Task ParseAsync(ICommandContext context, string input, IServiceProvider services = null) + public async Task ParseAsync(ICommandContext context, string input, + IServiceProvider services = null) { services = services ?? EmptyServiceProvider.Instance; return await _reader.ReadAsync(context, input, services).ConfigureAwait(false); } public override string ToString() => Name; - private string DebuggerDisplay => $"{Name}{(IsOptional ? " (Optional)" : "")}{(IsRemainder ? " (Remainder)" : "")}"; } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Commands/Map/CommandMap.cs b/src/Discord.Net.Commands/Map/CommandMap.cs index bcff800d3..1c7c4e9da 100644 --- a/src/Discord.Net.Commands/Map/CommandMap.cs +++ b/src/Discord.Net.Commands/Map/CommandMap.cs @@ -4,9 +4,9 @@ namespace Discord.Commands { internal class CommandMap { - private readonly CommandService _service; + private static readonly string[] _blankAliases = {""}; private readonly CommandMapNode _root; - private static readonly string[] _blankAliases = new[] { "" }; + private readonly CommandService _service; public CommandMap(CommandService service) { @@ -16,18 +16,16 @@ namespace Discord.Commands public void AddCommand(CommandInfo command) { - foreach (string text in command.Aliases) + foreach (var text in command.Aliases) _root.AddCommand(_service, text, 0, command); } + public void RemoveCommand(CommandInfo command) { - foreach (string text in command.Aliases) + foreach (var text in command.Aliases) _root.RemoveCommand(_service, text, 0, command); } - public IEnumerable GetCommands(string text) - { - return _root.GetCommands(_service, text, 0, text != ""); - } + public IEnumerable GetCommands(string text) => _root.GetCommands(_service, text, 0, text != ""); } } diff --git a/src/Discord.Net.Commands/Map/CommandMapNode.cs b/src/Discord.Net.Commands/Map/CommandMapNode.cs index 863409207..59c9ef0fa 100644 --- a/src/Discord.Net.Commands/Map/CommandMapNode.cs +++ b/src/Discord.Net.Commands/Map/CommandMapNode.cs @@ -7,15 +7,13 @@ namespace Discord.Commands { internal class CommandMapNode { - private static readonly char[] _whitespaceChars = new[] { ' ', '\r', '\n' }; + private static readonly char[] _whitespaceChars = {' ', '\r', '\n'}; + private readonly object _lockObj = new object(); + private readonly string _name; private readonly ConcurrentDictionary _nodes; - private readonly string _name; - private readonly object _lockObj = new object(); private ImmutableArray _commands; - public bool IsEmpty => _commands.Length == 0 && _nodes.Count == 0; - public CommandMapNode(string name) { _name = name; @@ -23,113 +21,90 @@ namespace Discord.Commands _commands = ImmutableArray.Create(); } + public bool IsEmpty => _commands.Length == 0 && _nodes.Count == 0; + public void AddCommand(CommandService service, string text, int index, CommandInfo command) { - int nextSegment = NextSegment(text, index, service._separatorChar); - string name; + var nextSegment = NextSegment(text, index, service._separatorChar); lock (_lockObj) - { - if (text == "") + switch (text) { - if (_name == "") + case "" when _name == "": throw new InvalidOperationException("Cannot add commands to the root node."); - _commands = _commands.Add(command); - } - else - { - if (nextSegment == -1) - name = text.Substring(index); - else - name = text.Substring(index, nextSegment - index); - - string fullName = _name == "" ? name : _name + service._separatorChar + name; - var nextNode = _nodes.GetOrAdd(name, x => new CommandMapNode(fullName)); - nextNode.AddCommand(service, nextSegment == -1 ? "" : text, nextSegment + 1, command); + case "": + _commands = _commands.Add(command); + break; + default: + var name = nextSegment == -1 ? text.Substring(index) : text.Substring(index, nextSegment - index); + + var fullName = _name == "" ? name : _name + service._separatorChar + name; + var nextNode = _nodes.GetOrAdd(name, x => new CommandMapNode(fullName)); + nextNode.AddCommand(service, nextSegment == -1 ? "" : text, nextSegment + 1, command); + break; } - } } + public void RemoveCommand(CommandService service, string text, int index, CommandInfo command) { - int nextSegment = NextSegment(text, index, service._separatorChar); - string name; + var nextSegment = NextSegment(text, index, service._separatorChar); lock (_lockObj) - { if (text == "") _commands = _commands.Remove(command); else { - if (nextSegment == -1) - name = text.Substring(index); - else - name = text.Substring(index, nextSegment - index); - - CommandMapNode nextNode; - if (_nodes.TryGetValue(name, out nextNode)) - { - nextNode.RemoveCommand(service, nextSegment == -1 ? "" : text, nextSegment + 1, command); - if (nextNode.IsEmpty) - _nodes.TryRemove(name, out nextNode); - } + var name = nextSegment == -1 ? text.Substring(index) : text.Substring(index, nextSegment - index); + + if (!_nodes.TryGetValue(name, out var nextNode)) return; + nextNode.RemoveCommand(service, nextSegment == -1 ? "" : text, nextSegment + 1, command); + if (nextNode.IsEmpty) + _nodes.TryRemove(name, out nextNode); } - } } - public IEnumerable GetCommands(CommandService service, string text, int index, bool visitChildren = true) + public IEnumerable GetCommands(CommandService service, string text, int index, + bool visitChildren = true) { var commands = _commands; - for (int i = 0; i < commands.Length; i++) + for (var i = 0; i < commands.Length; i++) yield return new CommandMatch(_commands[i], _name); - if (visitChildren) - { - string name; - CommandMapNode nextNode; - - //Search for next segment - int nextSegment = NextSegment(text, index, service._separatorChar); - if (nextSegment == -1) - name = text.Substring(index); - else - name = text.Substring(index, nextSegment - index); - if (_nodes.TryGetValue(name, out nextNode)) - { - foreach (var cmd in nextNode.GetCommands(service, nextSegment == -1 ? "" : text, nextSegment + 1, true)) - yield return cmd; - } + if (!visitChildren) yield break; + //Search for next segment + var nextSegment = NextSegment(text, index, service._separatorChar); + var name = nextSegment == -1 ? text.Substring(index) : text.Substring(index, nextSegment - index); + if (_nodes.TryGetValue(name, out var nextNode)) + foreach (var cmd in nextNode.GetCommands(service, nextSegment == -1 ? "" : text, nextSegment + 1)) + yield return cmd; - //Check if this is the last command segment before args - nextSegment = NextSegment(text, index, _whitespaceChars, service._separatorChar); - if (nextSegment != -1) - { - name = text.Substring(index, nextSegment - index); - if (_nodes.TryGetValue(name, out nextNode)) - { - foreach (var cmd in nextNode.GetCommands(service, nextSegment == -1 ? "" : text, nextSegment + 1, false)) - yield return cmd; - } - } + //Check if this is the last command segment before args + nextSegment = NextSegment(text, index, _whitespaceChars, service._separatorChar); + if (nextSegment == -1) yield break; + { + name = text.Substring(index, nextSegment - index); + if (!_nodes.TryGetValue(name, out nextNode)) yield break; + foreach (var cmd in nextNode.GetCommands(service, nextSegment == -1 ? "" : text, + nextSegment + 1, false)) + yield return cmd; } } - private static int NextSegment(string text, int startIndex, char separator) - { - return text.IndexOf(separator, startIndex); - } + private static int NextSegment(string text, int startIndex, char separator) => + text.IndexOf(separator, startIndex); + private static int NextSegment(string text, int startIndex, char[] separators, char except) { - int lowest = int.MaxValue; - for (int i = 0; i < separators.Length; i++) - { - if (separators[i] != except) + var lowest = int.MaxValue; + foreach (var t in separators) + if (t != except) { - int index = text.IndexOf(separators[i], startIndex); + var index = text.IndexOf(t, startIndex); if (index != -1 && index < lowest) lowest = index; } - } - return (lowest != int.MaxValue) ? lowest : -1; + + return lowest != int.MaxValue ? lowest : -1; } } } diff --git a/src/Discord.Net.Commands/ModuleBase.cs b/src/Discord.Net.Commands/ModuleBase.cs index 3e6fbbd9b..164235661 100644 --- a/src/Discord.Net.Commands/ModuleBase.cs +++ b/src/Discord.Net.Commands/ModuleBase.cs @@ -4,23 +4,38 @@ using Discord.Commands.Builders; namespace Discord.Commands { - public abstract class ModuleBase : ModuleBase { } + public abstract class ModuleBase : ModuleBase + { + } public abstract class ModuleBase : IModuleBase where T : class, ICommandContext { public T Context { get; private set; } + //IModuleBase + void IModuleBase.SetContext(ICommandContext context) + { + var newValue = context as T; + Context = newValue ?? throw new InvalidOperationException( + $"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}"); + } + + void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command); + void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command); + + void IModuleBase.OnModuleBuilding(CommandService commandService, ModuleBuilder builder) => + OnModuleBuilding(commandService, builder); + /// - /// Sends a message to the source channel + /// Sends a message to the source channel /// - /// Contents of the message; optional only if is specified + /// Contents of the message; optional only if is specified /// Specifies if Discord should read this message aloud using TTS /// An embed to be displayed alongside the message - protected virtual async Task ReplyAsync(string message = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) - { - return await Context.Channel.SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false); - } + protected virtual async Task ReplyAsync(string message = null, bool isTTS = false, + Embed embed = null, RequestOptions options = null) => await Context.Channel + .SendMessageAsync(message, isTTS, embed, options).ConfigureAwait(false); protected virtual void BeforeExecute(CommandInfo command) { @@ -33,15 +48,5 @@ namespace Discord.Commands protected virtual void OnModuleBuilding(CommandService commandService, ModuleBuilder builder) { } - - //IModuleBase - void IModuleBase.SetContext(ICommandContext context) - { - var newValue = context as T; - Context = newValue ?? throw new InvalidOperationException($"Invalid context type. Expected {typeof(T).Name}, got {context.GetType().Name}"); - } - void IModuleBase.BeforeExecute(CommandInfo command) => BeforeExecute(command); - void IModuleBase.AfterExecute(CommandInfo command) => AfterExecute(command); - void IModuleBase.OnModuleBuilding(CommandService commandService, ModuleBuilder builder) => OnModuleBuilding(commandService, builder); } } diff --git a/src/Discord.Net.Commands/PrimitiveParsers.cs b/src/Discord.Net.Commands/PrimitiveParsers.cs index bf0622c28..c9a408153 100644 --- a/src/Discord.Net.Commands/PrimitiveParsers.cs +++ b/src/Discord.Net.Commands/PrimitiveParsers.cs @@ -8,11 +8,12 @@ namespace Discord.Commands internal static class PrimitiveParsers { - private static readonly Lazy> _parsers = new Lazy>(CreateParsers); + private static readonly Lazy> Parsers = + new Lazy>(CreateParsers); - public static IEnumerable SupportedTypes = _parsers.Value.Keys; + public static IEnumerable SupportedTypes = Parsers.Value.Keys; - static IReadOnlyDictionary CreateParsers() + private static IReadOnlyDictionary CreateParsers() { var parserBuilder = ImmutableDictionary.CreateBuilder(); parserBuilder[typeof(bool)] = (TryParseDelegate)bool.TryParse; @@ -34,7 +35,7 @@ namespace Discord.Commands return parserBuilder.ToImmutable(); } - public static TryParseDelegate Get() => (TryParseDelegate)_parsers.Value[typeof(T)]; - public static Delegate Get(Type type) => _parsers.Value[type]; + public static TryParseDelegate Get() => (TryParseDelegate)Parsers.Value[typeof(T)]; + public static Delegate Get(Type type) => Parsers.Value[type]; } } diff --git a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs index cd7a9d744..f9f4bc21b 100644 --- a/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/ChannelTypeReader.cs @@ -9,29 +9,31 @@ namespace Discord.Commands public class ChannelTypeReader : TypeReader where T : class, IChannel { - public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) + public override async Task ReadAsync(ICommandContext context, string input, + IServiceProvider services) { - if (context.Guild != null) - { - var results = new Dictionary(); - var channels = await context.Guild.GetChannelsAsync(CacheMode.CacheOnly).ConfigureAwait(false); - ulong id; + if (context.Guild == null) + return TypeReaderResult.FromError(CommandError.ObjectNotFound, "Channel not found."); + var results = new Dictionary(); + var channels = await context.Guild.GetChannelsAsync(CacheMode.CacheOnly).ConfigureAwait(false); - //By Mention (1.0) - if (MentionUtils.TryParseChannel(input, out id)) - AddResult(results, await context.Guild.GetChannelAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); + //By Mention (1.0) + if (MentionUtils.TryParseChannel(input, out var id)) + AddResult(results, + await context.Guild.GetChannelAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); - //By Id (0.9) - if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - AddResult(results, await context.Guild.GetChannelAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f); + //By Id (0.9) + if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + AddResult(results, + await context.Guild.GetChannelAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f); - //By Name (0.7-0.8) - foreach (var channel in channels.Where(x => string.Equals(input, x.Name, StringComparison.OrdinalIgnoreCase))) - AddResult(results, channel as T, channel.Name == input ? 0.80f : 0.70f); + //By Name (0.7-0.8) + foreach (var channel in channels.Where(x => + string.Equals(input, x.Name, StringComparison.OrdinalIgnoreCase))) + AddResult(results, channel as T, channel.Name == input ? 0.80f : 0.70f); - if (results.Count > 0) - return TypeReaderResult.FromSuccess(results.Values.ToReadOnlyCollection()); - } + if (results.Count > 0) + return TypeReaderResult.FromSuccess(results.Values.ToReadOnlyCollection()); return TypeReaderResult.FromError(CommandError.ObjectNotFound, "Channel not found."); } diff --git a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs index c097e6189..dd0133aa6 100644 --- a/src/Discord.Net.Commands/Readers/EnumTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/EnumTypeReader.cs @@ -11,9 +11,10 @@ namespace Discord.Commands { public static TypeReader GetReader(Type type) { - Type baseType = Enum.GetUnderlyingType(type); - var constructor = typeof(EnumTypeReader<>).MakeGenericType(baseType).GetTypeInfo().DeclaredConstructors.First(); - return (TypeReader)constructor.Invoke(new object[] { type, PrimitiveParsers.Get(baseType) }); + var baseType = Enum.GetUnderlyingType(type); + var constructor = typeof(EnumTypeReader<>).MakeGenericType(baseType).GetTypeInfo().DeclaredConstructors + .First(); + return (TypeReader)constructor.Invoke(new object[] {type, PrimitiveParsers.Get(baseType)}); } } @@ -23,7 +24,7 @@ namespace Discord.Commands private readonly IReadOnlyDictionary _enumsByValue; private readonly Type _enumType; private readonly TryParseDelegate _tryParse; - + public EnumTypeReader(Type type, TryParseDelegate parser) { _enumType = type; @@ -33,7 +34,7 @@ namespace Discord.Commands var byValueBuilder = ImmutableDictionary.CreateBuilder(); foreach (var v in Enum.GetNames(_enumType)) - { + { var parsedValue = Enum.Parse(_enumType, v); byNameBuilder.Add(v.ToLower(), parsedValue); if (!byValueBuilder.ContainsKey((T)parsedValue)) @@ -44,24 +45,23 @@ namespace Discord.Commands _enumsByValue = byValueBuilder.ToImmutable(); } - public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) + public override Task ReadAsync(ICommandContext context, string input, + IServiceProvider services) { object enumValue; - if (_tryParse(input, out T baseValue)) + if (_tryParse(input, out var baseValue)) { if (_enumsByValue.TryGetValue(baseValue, out enumValue)) return Task.FromResult(TypeReaderResult.FromSuccess(enumValue)); - else - return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Value is not a {_enumType.Name}")); - } - else - { - if (_enumsByName.TryGetValue(input.ToLower(), out enumValue)) - return Task.FromResult(TypeReaderResult.FromSuccess(enumValue)); - else - return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Value is not a {_enumType.Name}")); + return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, + $"Value is not a {_enumType.Name}")); } + + if (_enumsByName.TryGetValue(input.ToLower(), out enumValue)) + return Task.FromResult(TypeReaderResult.FromSuccess(enumValue)); + return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, + $"Value is not a {_enumType.Name}")); } } } diff --git a/src/Discord.Net.Commands/Readers/MessageTypeReader.cs b/src/Discord.Net.Commands/Readers/MessageTypeReader.cs index a87cfbe43..adfa8e300 100644 --- a/src/Discord.Net.Commands/Readers/MessageTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/MessageTypeReader.cs @@ -7,16 +7,14 @@ namespace Discord.Commands public class MessageTypeReader : TypeReader where T : class, IMessage { - public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) + public override async Task ReadAsync(ICommandContext context, string input, + IServiceProvider services) { - ulong id; - //By Id (1.0) - if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - { - if (await context.Channel.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) is T msg) - return TypeReaderResult.FromSuccess(msg); - } + if (!ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out var id)) + return TypeReaderResult.FromError(CommandError.ObjectNotFound, "Message not found."); + if (await context.Channel.GetMessageAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) is T msg) + return TypeReaderResult.FromSuccess(msg); return TypeReaderResult.FromError(CommandError.ObjectNotFound, "Message not found."); } diff --git a/src/Discord.Net.Commands/Readers/NullableTypeReader.cs b/src/Discord.Net.Commands/Readers/NullableTypeReader.cs index 109689e15..e1595d053 100644 --- a/src/Discord.Net.Commands/Readers/NullableTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/NullableTypeReader.cs @@ -9,8 +9,9 @@ namespace Discord.Commands { public static TypeReader Create(Type type, TypeReader reader) { - var constructor = typeof(NullableTypeReader<>).MakeGenericType(type).GetTypeInfo().DeclaredConstructors.First(); - return (TypeReader)constructor.Invoke(new object[] { reader }); + var constructor = typeof(NullableTypeReader<>).MakeGenericType(type).GetTypeInfo().DeclaredConstructors + .First(); + return (TypeReader)constructor.Invoke(new object[] {reader}); } } @@ -24,9 +25,11 @@ namespace Discord.Commands _baseTypeReader = baseTypeReader; } - public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) + public override async Task ReadAsync(ICommandContext context, string input, + IServiceProvider services) { - if (string.Equals(input, "null", StringComparison.OrdinalIgnoreCase) || string.Equals(input, "nothing", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(input, "null", StringComparison.OrdinalIgnoreCase) || + string.Equals(input, "nothing", StringComparison.OrdinalIgnoreCase)) return TypeReaderResult.FromSuccess(new T?()); return await _baseTypeReader.ReadAsync(context, input, services); } diff --git a/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs b/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs index b19a6bd69..a7a86e8e8 100644 --- a/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/PrimitiveTypeReader.cs @@ -14,12 +14,13 @@ namespace Discord.Commands internal class PrimitiveTypeReader : TypeReader { - private readonly TryParseDelegate _tryParse; private readonly float _score; + private readonly TryParseDelegate _tryParse; public PrimitiveTypeReader() : this(PrimitiveParsers.Get(), 1) - { } + { + } public PrimitiveTypeReader(TryParseDelegate tryParse, float score) { @@ -30,11 +31,13 @@ namespace Discord.Commands _score = score; } - public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) + public override Task ReadAsync(ICommandContext context, string input, + IServiceProvider services) { - if (_tryParse(input, out T value)) + if (_tryParse(input, out var value)) return Task.FromResult(TypeReaderResult.FromSuccess(new TypeReaderValue(value, _score))); - return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, $"Failed to parse {typeof(T).Name}")); + return Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, + $"Failed to parse {typeof(T).Name}")); } } } diff --git a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs index 508624103..7e3d0a096 100644 --- a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs @@ -9,31 +9,27 @@ namespace Discord.Commands public class RoleTypeReader : TypeReader where T : class, IRole { - public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) + public override Task ReadAsync(ICommandContext context, string input, + IServiceProvider services) { - ulong id; + if (context.Guild == null) + return Task.FromResult(TypeReaderResult.FromError(CommandError.ObjectNotFound, "Role not found.")); + var results = new Dictionary(); + var roles = context.Guild.Roles; - if (context.Guild != null) - { - var results = new Dictionary(); - var roles = context.Guild.Roles; + //By Mention (1.0) + if (MentionUtils.TryParseRole(input, out var id)) + AddResult(results, context.Guild.GetRole(id) as T, 1.00f); - //By Mention (1.0) - if (MentionUtils.TryParseRole(input, out id)) - AddResult(results, context.Guild.GetRole(id) as T, 1.00f); + //By Id (0.9) + if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) + AddResult(results, context.Guild.GetRole(id) as T, 0.90f); - //By Id (0.9) - if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) - AddResult(results, context.Guild.GetRole(id) as T, 0.90f); + //By Name (0.7-0.8) + foreach (var role in roles.Where(x => string.Equals(input, x.Name, StringComparison.OrdinalIgnoreCase))) + AddResult(results, role as T, role.Name == input ? 0.80f : 0.70f); - //By Name (0.7-0.8) - foreach (var role in roles.Where(x => string.Equals(input, x.Name, StringComparison.OrdinalIgnoreCase))) - AddResult(results, role as T, role.Name == input ? 0.80f : 0.70f); - - if (results.Count > 0) - return Task.FromResult(TypeReaderResult.FromSuccess(results.Values.ToReadOnlyCollection())); - } - return Task.FromResult(TypeReaderResult.FromError(CommandError.ObjectNotFound, "Role not found.")); + return Task.FromResult(results.Count > 0 ? TypeReaderResult.FromSuccess(results.Values.ToReadOnlyCollection()) : TypeReaderResult.FromError(CommandError.ObjectNotFound, "Role not found.")); } private void AddResult(Dictionary results, T role, float score) diff --git a/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs b/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs index 31ab9d821..a0f9fab01 100644 --- a/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/TimeSpanTypeReader.cs @@ -6,30 +6,29 @@ namespace Discord.Commands { internal class TimeSpanTypeReader : TypeReader { - private static readonly string[] _formats = new[] + private static readonly string[] _formats = { "%d'd'%h'h'%m'm'%s's'", //4d3h2m1s - "%d'd'%h'h'%m'm'", //4d3h2m - "%d'd'%h'h'%s's'", //4d3h 1s - "%d'd'%h'h'", //4d3h - "%d'd'%m'm'%s's'", //4d 2m1s - "%d'd'%m'm'", //4d 2m - "%d'd'%s's'", //4d 1s - "%d'd'", //4d - "%h'h'%m'm'%s's'", // 3h2m1s - "%h'h'%m'm'", // 3h2m - "%h'h'%s's'", // 3h 1s - "%h'h'", // 3h - "%m'm'%s's'", // 2m1s - "%m'm'", // 2m - "%s's'", // 1s + "%d'd'%h'h'%m'm'", //4d3h2m + "%d'd'%h'h'%s's'", //4d3h 1s + "%d'd'%h'h'", //4d3h + "%d'd'%m'm'%s's'", //4d 2m1s + "%d'd'%m'm'", //4d 2m + "%d'd'%s's'", //4d 1s + "%d'd'", //4d + "%h'h'%m'm'%s's'", // 3h2m1s + "%h'h'%m'm'", // 3h2m + "%h'h'%s's'", // 3h 1s + "%h'h'", // 3h + "%m'm'%s's'", // 2m1s + "%m'm'", // 2m + "%s's'" // 1s }; - public override Task ReadAsync(ICommandContext context, string input, IServiceProvider services) - { - return (TimeSpan.TryParseExact(input.ToLowerInvariant(), _formats, CultureInfo.InvariantCulture, out var timeSpan)) + public override Task + ReadAsync(ICommandContext context, string input, IServiceProvider services) => + TimeSpan.TryParseExact(input.ToLowerInvariant(), _formats, CultureInfo.InvariantCulture, out var timeSpan) ? Task.FromResult(TypeReaderResult.FromSuccess(timeSpan)) : Task.FromResult(TypeReaderResult.FromError(CommandError.ParseFailed, "Failed to parse TimeSpan")); - } } } diff --git a/src/Discord.Net.Commands/Readers/TypeReader.cs b/src/Discord.Net.Commands/Readers/TypeReader.cs index af45a0aac..1648c8a75 100644 --- a/src/Discord.Net.Commands/Readers/TypeReader.cs +++ b/src/Discord.Net.Commands/Readers/TypeReader.cs @@ -5,6 +5,7 @@ namespace Discord.Commands { public abstract class TypeReader { - public abstract Task ReadAsync(ICommandContext context, string input, IServiceProvider services); + public abstract Task ReadAsync(ICommandContext context, string input, + IServiceProvider services); } } diff --git a/src/Discord.Net.Commands/Readers/UserTypeReader.cs b/src/Discord.Net.Commands/Readers/UserTypeReader.cs index 425c2ccb7..242db5fe5 100644 --- a/src/Discord.Net.Commands/Readers/UserTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/UserTypeReader.cs @@ -10,47 +10,53 @@ namespace Discord.Commands public class UserTypeReader : TypeReader where T : class, IUser { - public override async Task ReadAsync(ICommandContext context, string input, IServiceProvider services) + public override async Task ReadAsync(ICommandContext context, string input, + IServiceProvider services) { var results = new Dictionary(); - IAsyncEnumerable channelUsers = context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten(); // it's better + var channelUsers = context.Channel.GetUsersAsync(CacheMode.CacheOnly).Flatten(); // it's better IReadOnlyCollection guildUsers = ImmutableArray.Create(); - ulong id; if (context.Guild != null) guildUsers = await context.Guild.GetUsersAsync(CacheMode.CacheOnly).ConfigureAwait(false); //By Mention (1.0) - if (MentionUtils.TryParseUser(input, out id)) + if (MentionUtils.TryParseUser(input, out var id)) { if (context.Guild != null) - AddResult(results, await context.Guild.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); + AddResult(results, + await context.Guild.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); else - AddResult(results, await context.Channel.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); + AddResult(results, + await context.Channel.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 1.00f); } //By Id (0.9) if (ulong.TryParse(input, NumberStyles.None, CultureInfo.InvariantCulture, out id)) { if (context.Guild != null) - AddResult(results, await context.Guild.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f); + AddResult(results, + await context.Guild.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f); else - AddResult(results, await context.Channel.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f); + AddResult(results, + await context.Channel.GetUserAsync(id, CacheMode.CacheOnly).ConfigureAwait(false) as T, 0.90f); } //By Username + Discriminator (0.7-0.85) - int index = input.LastIndexOf('#'); + var index = input.LastIndexOf('#'); if (index >= 0) { - string username = input.Substring(0, index); - if (ushort.TryParse(input.Substring(index + 1), out ushort discriminator)) + var username = input.Substring(0, index); + if (ushort.TryParse(input.Substring(index + 1), out var discriminator)) { var channelUser = await channelUsers.FirstOrDefault(x => x.DiscriminatorValue == discriminator && - string.Equals(username, x.Username, StringComparison.OrdinalIgnoreCase)); + string.Equals(username, x.Username, + StringComparison.OrdinalIgnoreCase)); AddResult(results, channelUser as T, channelUser?.Username == username ? 0.85f : 0.75f); var guildUser = guildUsers.FirstOrDefault(x => x.DiscriminatorValue == discriminator && - string.Equals(username, x.Username, StringComparison.OrdinalIgnoreCase)); + string.Equals(username, x.Username, + StringComparison.OrdinalIgnoreCase)); AddResult(results, guildUser as T, guildUser?.Username == username ? 0.80f : 0.70f); } } @@ -59,9 +65,11 @@ namespace Discord.Commands { await channelUsers .Where(x => string.Equals(input, x.Username, StringComparison.OrdinalIgnoreCase)) - .ForEachAsync(channelUser => AddResult(results, channelUser as T, channelUser.Username == input ? 0.65f : 0.55f)); - - foreach (var guildUser in guildUsers.Where(x => string.Equals(input, x.Username, StringComparison.OrdinalIgnoreCase))) + .ForEachAsync(channelUser => + AddResult(results, channelUser as T, channelUser.Username == input ? 0.65f : 0.55f)); + + foreach (var guildUser in guildUsers.Where(x => + string.Equals(input, x.Username, StringComparison.OrdinalIgnoreCase))) AddResult(results, guildUser as T, guildUser.Username == input ? 0.60f : 0.50f); } @@ -69,15 +77,15 @@ namespace Discord.Commands { await channelUsers .Where(x => string.Equals(input, (x as IGuildUser)?.Nickname, StringComparison.OrdinalIgnoreCase)) - .ForEachAsync(channelUser => AddResult(results, channelUser as T, (channelUser as IGuildUser).Nickname == input ? 0.65f : 0.55f)); + .ForEachAsync(channelUser => AddResult(results, channelUser as T, + (channelUser as IGuildUser).Nickname == input ? 0.65f : 0.55f)); - foreach (var guildUser in guildUsers.Where(x => string.Equals(input, (x as IGuildUser).Nickname, StringComparison.OrdinalIgnoreCase))) - AddResult(results, guildUser as T, (guildUser as IGuildUser).Nickname == input ? 0.60f : 0.50f); + foreach (var guildUser in guildUsers.Where(x => + string.Equals(input, x.Nickname, StringComparison.OrdinalIgnoreCase))) + AddResult(results, guildUser as T, guildUser.Nickname == input ? 0.60f : 0.50f); } - if (results.Count > 0) - return TypeReaderResult.FromSuccess(results.Values.ToImmutableArray()); - return TypeReaderResult.FromError(CommandError.ObjectNotFound, "User not found."); + return results.Count > 0 ? TypeReaderResult.FromSuccess(results.Values.ToImmutableArray()) : TypeReaderResult.FromError(CommandError.ObjectNotFound, "User not found."); } private void AddResult(Dictionary results, T user, float score) diff --git a/src/Discord.Net.Commands/Results/ExecuteResult.cs b/src/Discord.Net.Commands/Results/ExecuteResult.cs index bad39e230..f82eb16af 100644 --- a/src/Discord.Net.Commands/Results/ExecuteResult.cs +++ b/src/Discord.Net.Commands/Results/ExecuteResult.cs @@ -3,7 +3,7 @@ using System.Diagnostics; namespace Discord.Commands { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public struct ExecuteResult : IResult { public Exception Exception { get; } @@ -22,13 +22,16 @@ namespace Discord.Commands public static ExecuteResult FromSuccess() => new ExecuteResult(null, null, null); + public static ExecuteResult FromError(CommandError error, string reason) => new ExecuteResult(null, error, reason); + public static ExecuteResult FromError(Exception ex) => new ExecuteResult(ex, CommandError.Exception, ex.Message); + public static ExecuteResult FromError(IResult result) => new ExecuteResult(null, result.Error, result.ErrorReason); - + public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; } diff --git a/src/Discord.Net.Commands/Results/ParseResult.cs b/src/Discord.Net.Commands/Results/ParseResult.cs index 3a0692b2d..96aa1648c 100644 --- a/src/Discord.Net.Commands/Results/ParseResult.cs +++ b/src/Discord.Net.Commands/Results/ParseResult.cs @@ -1,10 +1,11 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; namespace Discord.Commands { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public struct ParseResult : IResult { public IReadOnlyList ArgValues { get; } @@ -15,7 +16,8 @@ namespace Discord.Commands public bool IsSuccess => !Error.HasValue; - private ParseResult(IReadOnlyList argValues, IReadOnlyList paramValues, CommandError? error, string errorReason) + private ParseResult(IReadOnlyList argValues, IReadOnlyList paramValues, + CommandError? error, string errorReason) { ArgValues = argValues; ParamValues = paramValues; @@ -23,43 +25,53 @@ namespace Discord.Commands ErrorReason = errorReason; } - public static ParseResult FromSuccess(IReadOnlyList argValues, IReadOnlyList paramValues) + public static ParseResult FromSuccess(IReadOnlyList argValues, + IReadOnlyList paramValues) { - for (int i = 0; i < argValues.Count; i++) + if (argValues.Any(t => t.Values.Count > 1)) { - if (argValues[i].Values.Count > 1) - return new ParseResult(argValues, paramValues, CommandError.MultipleMatches, "Multiple matches found."); + return new ParseResult(argValues, paramValues, CommandError.MultipleMatches, + "Multiple matches found."); } - for (int i = 0; i < paramValues.Count; i++) + if (paramValues.Any(t => t.Values.Count > 1)) { - if (paramValues[i].Values.Count > 1) - return new ParseResult(argValues, paramValues, CommandError.MultipleMatches, "Multiple matches found."); + return new ParseResult(argValues, paramValues, CommandError.MultipleMatches, + "Multiple matches found."); } + return new ParseResult(argValues, paramValues, null, null); } - public static ParseResult FromSuccess(IReadOnlyList argValues, IReadOnlyList paramValues) + + public static ParseResult FromSuccess(IReadOnlyList argValues, + IReadOnlyList paramValues) { var argList = new TypeReaderResult[argValues.Count]; - for (int i = 0; i < argValues.Count; i++) + for (var i = 0; i < argValues.Count; i++) argList[i] = TypeReaderResult.FromSuccess(argValues[i]); - TypeReaderResult[] paramList = null; - if (paramValues != null) + TypeReaderResult[] paramList; + if (paramValues == null) return new ParseResult(argList, null, null, null); { paramList = new TypeReaderResult[paramValues.Count]; - for (int i = 0; i < paramValues.Count; i++) + for (var i = 0; i < paramValues.Count; i++) paramList[i] = TypeReaderResult.FromSuccess(paramValues[i]); } + return new ParseResult(argList, paramList, null, null); } public static ParseResult FromError(CommandError error, string reason) => new ParseResult(null, null, error, reason); + public static ParseResult FromError(Exception ex) => FromError(CommandError.Exception, ex.Message); + public static ParseResult FromError(IResult result) => new ParseResult(null, null, result.Error, result.ErrorReason); public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; - private string DebuggerDisplay => IsSuccess ? $"Success ({ArgValues.Count}{(ParamValues.Count > 0 ? $" +{ParamValues.Count} Values" : "")})" : $"{Error}: {ErrorReason}"; + + private string DebuggerDisplay => IsSuccess + ? $"Success ({ArgValues.Count}{(ParamValues.Count > 0 ? $" +{ParamValues.Count} Values" : "")})" + : $"{Error}: {ErrorReason}"; } } diff --git a/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs b/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs index ee650600a..5997145a8 100644 --- a/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs +++ b/src/Discord.Net.Commands/Results/PreconditionGroupResult.cs @@ -4,27 +4,31 @@ using System.Diagnostics; namespace Discord.Commands { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class PreconditionGroupResult : PreconditionResult { - public IReadOnlyCollection PreconditionResults { get; } - - protected PreconditionGroupResult(CommandError? error, string errorReason, ICollection preconditions) + protected PreconditionGroupResult(CommandError? error, string errorReason, + ICollection preconditions) : base(error, errorReason) { PreconditionResults = (preconditions ?? new List(0)).ToReadOnlyCollection(); } - public static new PreconditionGroupResult FromSuccess() + public IReadOnlyCollection PreconditionResults { get; } + private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; + + public new static PreconditionGroupResult FromSuccess() => new PreconditionGroupResult(null, null, null); + public static PreconditionGroupResult FromError(string reason, ICollection preconditions) => new PreconditionGroupResult(CommandError.UnmetPrecondition, reason, preconditions); - public static new PreconditionGroupResult FromError(Exception ex) + + public new static PreconditionGroupResult FromError(Exception ex) => new PreconditionGroupResult(CommandError.Exception, ex.Message, null); - public static new PreconditionGroupResult FromError(IResult result) //needed? + + public new static PreconditionGroupResult FromError(IResult result) //needed? => new PreconditionGroupResult(result.Error, result.ErrorReason, null); public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; - private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; } } diff --git a/src/Discord.Net.Commands/Results/PreconditionResult.cs b/src/Discord.Net.Commands/Results/PreconditionResult.cs index 01fc1a3fd..e51ca5869 100644 --- a/src/Discord.Net.Commands/Results/PreconditionResult.cs +++ b/src/Discord.Net.Commands/Results/PreconditionResult.cs @@ -3,30 +3,33 @@ using System.Diagnostics; namespace Discord.Commands { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class PreconditionResult : IResult { - public CommandError? Error { get; } - public string ErrorReason { get; } - - public bool IsSuccess => !Error.HasValue; - protected PreconditionResult(CommandError? error, string errorReason) { Error = error; ErrorReason = errorReason; } + private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; + public CommandError? Error { get; } + public string ErrorReason { get; } + + public bool IsSuccess => !Error.HasValue; + public static PreconditionResult FromSuccess() => new PreconditionResult(null, null); + public static PreconditionResult FromError(string reason) => new PreconditionResult(CommandError.UnmetPrecondition, reason); + public static PreconditionResult FromError(Exception ex) => new PreconditionResult(CommandError.Exception, ex.Message); + public static PreconditionResult FromError(IResult result) => new PreconditionResult(result.Error, result.ErrorReason); public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; - private string DebuggerDisplay => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; } } diff --git a/src/Discord.Net.Commands/Results/RuntimeResult.cs b/src/Discord.Net.Commands/Results/RuntimeResult.cs index 2a326a7a3..b69078646 100644 --- a/src/Discord.Net.Commands/Results/RuntimeResult.cs +++ b/src/Discord.Net.Commands/Results/RuntimeResult.cs @@ -1,11 +1,8 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; +using System.Diagnostics; namespace Discord.Commands { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public abstract class RuntimeResult : IResult { protected RuntimeResult(CommandError? error, string reason) @@ -14,14 +11,15 @@ namespace Discord.Commands Reason = reason; } - public CommandError? Error { get; } public string Reason { get; } + private string DebuggerDisplay => IsSuccess ? $"Success: {Reason ?? "No Reason"}" : $"{Error}: {Reason}"; + + public CommandError? Error { get; } public bool IsSuccess => !Error.HasValue; string IResult.ErrorReason => Reason; public override string ToString() => Reason ?? (IsSuccess ? "Successful" : "Unsuccessful"); - private string DebuggerDisplay => IsSuccess ? $"Success: {Reason ?? "No Reason"}" : $"{Error}: {Reason}"; } } diff --git a/src/Discord.Net.Commands/Results/SearchResult.cs b/src/Discord.Net.Commands/Results/SearchResult.cs index 6a5878ea2..5a0d0fa50 100644 --- a/src/Discord.Net.Commands/Results/SearchResult.cs +++ b/src/Discord.Net.Commands/Results/SearchResult.cs @@ -4,7 +4,7 @@ using System.Diagnostics; namespace Discord.Commands { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public struct SearchResult : IResult { public string Text { get; } @@ -25,10 +25,13 @@ namespace Discord.Commands public static SearchResult FromSuccess(string text, IReadOnlyList commands) => new SearchResult(text, commands, null, null); + public static SearchResult FromError(CommandError error, string reason) => new SearchResult(null, null, error, reason); + public static SearchResult FromError(Exception ex) => FromError(CommandError.Exception, ex.Message); + public static SearchResult FromError(IResult result) => new SearchResult(null, null, result.Error, result.ErrorReason); diff --git a/src/Discord.Net.Commands/Results/TypeReaderResult.cs b/src/Discord.Net.Commands/Results/TypeReaderResult.cs index e696dbc17..dc553e375 100644 --- a/src/Discord.Net.Commands/Results/TypeReaderResult.cs +++ b/src/Discord.Net.Commands/Results/TypeReaderResult.cs @@ -6,7 +6,7 @@ using System.Linq; namespace Discord.Commands { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public struct TypeReaderValue { public object Value { get; } @@ -22,7 +22,7 @@ namespace Discord.Commands private string DebuggerDisplay => $"[{Value}, {Math.Round(Score, 2)}]"; } - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public struct TypeReaderResult : IResult { public IReadOnlyCollection Values { get; } @@ -31,6 +31,7 @@ namespace Discord.Commands public string ErrorReason { get; } public bool IsSuccess => !Error.HasValue; + public object BestMatch => IsSuccess ? (Values.Count == 1 ? Values.Single().Value : Values.OrderByDescending(v => v.Score).First().Value) : throw new InvalidOperationException("TypeReaderResult was not successful."); @@ -44,18 +45,25 @@ namespace Discord.Commands public static TypeReaderResult FromSuccess(object value) => new TypeReaderResult(ImmutableArray.Create(new TypeReaderValue(value, 1.0f)), null, null); + public static TypeReaderResult FromSuccess(TypeReaderValue value) => new TypeReaderResult(ImmutableArray.Create(value), null, null); + public static TypeReaderResult FromSuccess(IReadOnlyCollection values) => new TypeReaderResult(values, null, null); + public static TypeReaderResult FromError(CommandError error, string reason) => new TypeReaderResult(null, error, reason); + public static TypeReaderResult FromError(Exception ex) => FromError(CommandError.Exception, ex.Message); + public static TypeReaderResult FromError(IResult result) => new TypeReaderResult(null, result.Error, result.ErrorReason); public override string ToString() => IsSuccess ? "Success" : $"{Error}: {ErrorReason}"; - private string DebuggerDisplay => IsSuccess ? $"Success ({string.Join(", ", Values)})" : $"{Error}: {ErrorReason}"; + + private string DebuggerDisplay => + IsSuccess ? $"Success ({string.Join(", ", Values)})" : $"{Error}: {ErrorReason}"; } } diff --git a/src/Discord.Net.Commands/Utilities/QuotationAliasUtils.cs b/src/Discord.Net.Commands/Utilities/QuotationAliasUtils.cs index 15a08b9b3..abb43ef14 100644 --- a/src/Discord.Net.Commands/Utilities/QuotationAliasUtils.cs +++ b/src/Discord.Net.Commands/Utilities/QuotationAliasUtils.cs @@ -1,95 +1,84 @@ -using System; using System.Collections.Generic; -using System.Text; -using System.Globalization; namespace Discord.Commands { /// - /// Utility methods for generating matching pairs of unicode quotation marks for CommandServiceConfig + /// Utility methods for generating matching pairs of unicode quotation marks for CommandServiceConfig /// internal static class QuotationAliasUtils { /// - /// Generates an IEnumerable of characters representing open-close pairs of - /// quotation punctuation. + /// Generates an IEnumerable of characters representing open-close pairs of + /// quotation punctuation. /// - internal static Dictionary GetDefaultAliasMap + internal static Dictionary GetDefaultAliasMap => new Dictionary { - get - { - // Output of a gist provided by https://gist.github.com/ufcpp - // https://gist.github.com/ufcpp/5b2cf9a9bf7d0b8743714a0b88f7edc5 - // This was not used for the implementation because of incompatibility with netstandard1.1 - return new Dictionary { - {'\"', '\"' }, - {'«', '»' }, - {'‘', '’' }, - {'“', '”' }, - {'„', '‟' }, - {'‹', '›' }, - {'‚', '‛' }, - {'《', '》' }, - {'〈', '〉' }, - {'「', '」' }, - {'『', '』' }, - {'〝', '〞' }, - {'﹁', '﹂' }, - {'﹃', '﹄' }, - {'"', '"' }, - {''', ''' }, - {'「', '」' }, - {'(', ')' }, - {'༺', '༻' }, - {'༼', '༽' }, - {'᚛', '᚜' }, - {'⁅', '⁆' }, - {'⌈', '⌉' }, - {'⌊', '⌋' }, - {'❨', '❩' }, - {'❪', '❫' }, - {'❬', '❭' }, - {'❮', '❯' }, - {'❰', '❱' }, - {'❲', '❳' }, - {'❴', '❵' }, - {'⟅', '⟆' }, - {'⟦', '⟧' }, - {'⟨', '⟩' }, - {'⟪', '⟫' }, - {'⟬', '⟭' }, - {'⟮', '⟯' }, - {'⦃', '⦄' }, - {'⦅', '⦆' }, - {'⦇', '⦈' }, - {'⦉', '⦊' }, - {'⦋', '⦌' }, - {'⦍', '⦎' }, - {'⦏', '⦐' }, - {'⦑', '⦒' }, - {'⦓', '⦔' }, - {'⦕', '⦖' }, - {'⦗', '⦘' }, - {'⧘', '⧙' }, - {'⧚', '⧛' }, - {'⧼', '⧽' }, - {'⸂', '⸃' }, - {'⸄', '⸅' }, - {'⸉', '⸊' }, - {'⸌', '⸍' }, - {'⸜', '⸝' }, - {'⸠', '⸡' }, - {'⸢', '⸣' }, - {'⸤', '⸥' }, - {'⸦', '⸧' }, - {'⸨', '⸩' }, - {'【', '】'}, - {'〔', '〕' }, - {'〖', '〗' }, - {'〘', '〙' }, - {'〚', '〛' } - }; - } - } + {'\"', '\"'}, + {'«', '»'}, + {'‘', '’'}, + {'“', '”'}, + {'„', '‟'}, + {'‹', '›'}, + {'‚', '‛'}, + {'《', '》'}, + {'〈', '〉'}, + {'「', '」'}, + {'『', '』'}, + {'〝', '〞'}, + {'﹁', '﹂'}, + {'﹃', '﹄'}, + {'"', '"'}, + {''', '''}, + {'「', '」'}, + {'(', ')'}, + {'༺', '༻'}, + {'༼', '༽'}, + {'᚛', '᚜'}, + {'⁅', '⁆'}, + {'⌈', '⌉'}, + {'⌊', '⌋'}, + {'❨', '❩'}, + {'❪', '❫'}, + {'❬', '❭'}, + {'❮', '❯'}, + {'❰', '❱'}, + {'❲', '❳'}, + {'❴', '❵'}, + {'⟅', '⟆'}, + {'⟦', '⟧'}, + {'⟨', '⟩'}, + {'⟪', '⟫'}, + {'⟬', '⟭'}, + {'⟮', '⟯'}, + {'⦃', '⦄'}, + {'⦅', '⦆'}, + {'⦇', '⦈'}, + {'⦉', '⦊'}, + {'⦋', '⦌'}, + {'⦍', '⦎'}, + {'⦏', '⦐'}, + {'⦑', '⦒'}, + {'⦓', '⦔'}, + {'⦕', '⦖'}, + {'⦗', '⦘'}, + {'⧘', '⧙'}, + {'⧚', '⧛'}, + {'⧼', '⧽'}, + {'⸂', '⸃'}, + {'⸄', '⸅'}, + {'⸉', '⸊'}, + {'⸌', '⸍'}, + {'⸜', '⸝'}, + {'⸠', '⸡'}, + {'⸢', '⸣'}, + {'⸤', '⸥'}, + {'⸦', '⸧'}, + {'⸨', '⸩'}, + {'【', '】'}, + {'〔', '〕'}, + {'〖', '〗'}, + {'〘', '〙'}, + {'〚', '〛'} + }; } } diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index 30dd7c36b..a92888105 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; -using Microsoft.Extensions.DependencyInjection; namespace Discord.Commands { @@ -12,24 +11,26 @@ namespace Discord.Commands internal static T CreateObject(TypeInfo typeInfo, CommandService commands, IServiceProvider services = null) => CreateBuilder(typeInfo, commands)(services); + internal static Func CreateBuilder(TypeInfo typeInfo, CommandService commands) { var constructor = GetConstructor(typeInfo); var parameters = constructor.GetParameters(); var properties = GetProperties(typeInfo); - return (services) => + return services => { var args = new object[parameters.Length]; - for (int i = 0; i < parameters.Length; i++) + for (var i = 0; i < parameters.Length; i++) args[i] = GetMember(commands, services, parameters[i].ParameterType, typeInfo); var obj = InvokeConstructor(constructor, args, typeInfo); - foreach(var property in properties) + foreach (var property in properties) property.SetValue(obj, GetMember(commands, services, property.PropertyType, typeInfo)); return obj; }; } + private static T InvokeConstructor(ConstructorInfo constructor, object[] args, TypeInfo ownerType) { try @@ -47,34 +48,35 @@ namespace Discord.Commands var constructors = ownerType.DeclaredConstructors.Where(x => !x.IsStatic).ToArray(); if (constructors.Length == 0) throw new InvalidOperationException($"No constructor found for \"{ownerType.FullName}\""); - else if (constructors.Length > 1) + if (constructors.Length > 1) throw new InvalidOperationException($"Multiple constructors found for \"{ownerType.FullName}\""); return constructors[0]; } - private static System.Reflection.PropertyInfo[] GetProperties(TypeInfo ownerType) + + private static PropertyInfo[] GetProperties(TypeInfo ownerType) { - var result = new List(); + var result = new List(); while (ownerType != _objectTypeInfo) { - foreach (var prop in ownerType.DeclaredProperties) - { - if (prop.SetMethod?.IsStatic == false && prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute() == null) - result.Add(prop); - } + result.AddRange(ownerType.DeclaredProperties.Where(prop => prop.SetMethod?.IsStatic == false && prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute() == null)); ownerType = ownerType.BaseType.GetTypeInfo(); } + return result.ToArray(); } - private static object GetMember(CommandService commands, IServiceProvider services, Type memberType, TypeInfo ownerType) + + private static object GetMember(CommandService commands, IServiceProvider services, Type memberType, + TypeInfo ownerType) { if (memberType == typeof(CommandService)) return commands; if (memberType == typeof(IServiceProvider) || memberType == services.GetType()) return services; - var service = services?.GetService(memberType); + var service = services.GetService(memberType); if (service != null) return service; - throw new InvalidOperationException($"Failed to create \"{ownerType.FullName}\", dependency \"{memberType.Name}\" was not found."); + throw new InvalidOperationException( + $"Failed to create \"{ownerType.FullName}\", dependency \"{memberType.Name}\" was not found."); } } } diff --git a/src/Discord.Net.Core/AssemblyInfo.cs b/src/Discord.Net.Core/AssemblyInfo.cs index 116bc3850..9705b5882 100644 --- a/src/Discord.Net.Core/AssemblyInfo.cs +++ b/src/Discord.Net.Core/AssemblyInfo.cs @@ -6,4 +6,4 @@ [assembly: InternalsVisibleTo("Discord.Net.WebSocket")] [assembly: InternalsVisibleTo("Discord.Net.Webhook")] [assembly: InternalsVisibleTo("Discord.Net.Commands")] -[assembly: InternalsVisibleTo("Discord.Net.Tests")] \ No newline at end of file +[assembly: InternalsVisibleTo("Discord.Net.Tests")] diff --git a/src/Discord.Net.Core/Audio/AudioApplication.cs b/src/Discord.Net.Core/Audio/AudioApplication.cs index 276d934b2..402892218 100644 --- a/src/Discord.Net.Core/Audio/AudioApplication.cs +++ b/src/Discord.Net.Core/Audio/AudioApplication.cs @@ -1,9 +1,9 @@ namespace Discord.Audio { - public enum AudioApplication : int + public enum AudioApplication { Voice, Music, Mixed } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Audio/AudioInStream.cs b/src/Discord.Net.Core/Audio/AudioInStream.cs index 656c0bc48..c14a66c71 100644 --- a/src/Discord.Net.Core/Audio/AudioInStream.cs +++ b/src/Discord.Net.Core/Audio/AudioInStream.cs @@ -9,11 +9,11 @@ namespace Discord.Audio public abstract int AvailableFrames { get; } public override bool CanRead => true; - public override bool CanWrite => true; + public override bool CanWrite => true; public abstract Task ReadFrameAsync(CancellationToken cancelToken); public abstract bool TryReadFrame(CancellationToken cancelToken, out RTPFrame frame); - public override Task FlushAsync(CancellationToken cancelToken) { throw new NotSupportedException(); } + public override Task FlushAsync(CancellationToken cancelToken) => throw new NotSupportedException(); } } diff --git a/src/Discord.Net.Core/Audio/AudioOutStream.cs b/src/Discord.Net.Core/Audio/AudioOutStream.cs index 7019ba8cd..632781a1e 100644 --- a/src/Discord.Net.Core/Audio/AudioOutStream.cs +++ b/src/Discord.Net.Core/Audio/AudioOutStream.cs @@ -7,8 +7,8 @@ namespace Discord.Audio { public override bool CanWrite => true; - public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } - public override void SetLength(long value) { throw new NotSupportedException(); } - public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); } } diff --git a/src/Discord.Net.Core/Audio/AudioStream.cs b/src/Discord.Net.Core/Audio/AudioStream.cs index 97820ea73..30b0e7f0a 100644 --- a/src/Discord.Net.Core/Audio/AudioStream.cs +++ b/src/Discord.Net.Core/Audio/AudioStream.cs @@ -11,34 +11,28 @@ namespace Discord.Audio public override bool CanSeek => false; public override bool CanWrite => false; - public virtual void WriteHeader(ushort seq, uint timestamp, bool missed) - { - throw new InvalidOperationException("This stream does not accept headers"); - } - public override void Write(byte[] buffer, int offset, int count) - { - WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); - } - public override void Flush() - { - FlushAsync(CancellationToken.None).GetAwaiter().GetResult(); - } - public void Clear() - { - ClearAsync(CancellationToken.None).GetAwaiter().GetResult(); - } + public override long Length => throw new NotSupportedException(); - public virtual Task ClearAsync(CancellationToken cancellationToken) { return Task.Delay(0); } - - public override long Length { get { throw new NotSupportedException(); } } public override long Position { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); } - public override int Read(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } - public override void SetLength(long value) { throw new NotSupportedException(); } - public override long Seek(long offset, SeekOrigin origin) { throw new NotSupportedException(); } + public virtual void WriteHeader(ushort seq, uint timestamp, bool missed) => + throw new InvalidOperationException("This stream does not accept headers"); + + public override void Write(byte[] buffer, int offset, int count) => + WriteAsync(buffer, offset, count, CancellationToken.None).GetAwaiter().GetResult(); + + public override void Flush() => FlushAsync(CancellationToken.None).GetAwaiter().GetResult(); + + public void Clear() => ClearAsync(CancellationToken.None).GetAwaiter().GetResult(); + + public virtual Task ClearAsync(CancellationToken cancellationToken) => Task.Delay(0); + + public override int Read(byte[] buffer, int offset, int count) => throw new NotSupportedException(); + public override void SetLength(long value) => throw new NotSupportedException(); + public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException(); } } diff --git a/src/Discord.Net.Core/Audio/IAudioClient.cs b/src/Discord.Net.Core/Audio/IAudioClient.cs index 9be8ceef5..d991b370d 100644 --- a/src/Discord.Net.Core/Audio/IAudioClient.cs +++ b/src/Discord.Net.Core/Audio/IAudioClient.cs @@ -5,6 +5,15 @@ namespace Discord.Audio { public interface IAudioClient : IDisposable { + /// Gets the current connection state of this client. + ConnectionState ConnectionState { get; } + + /// Gets the estimated round-trip latency, in milliseconds, to the voice websocket server. + int Latency { get; } + + /// Gets the estimated round-trip latency, in milliseconds, to the voice UDP server. + int UdpLatency { get; } + event Func Connected; event Func Disconnected; event Func LatencyUpdated; @@ -13,22 +22,19 @@ namespace Discord.Audio event Func StreamDestroyed; event Func SpeakingUpdated; - /// Gets the current connection state of this client. - ConnectionState ConnectionState { get; } - /// Gets the estimated round-trip latency, in milliseconds, to the voice websocket server. - int Latency { get; } - /// Gets the estimated round-trip latency, in milliseconds, to the voice UDP server. - int UdpLatency { get; } - Task StopAsync(); Task SetSpeakingAsync(bool value); /// Creates a new outgoing stream accepting Opus-encoded data. AudioOutStream CreateOpusStream(int bufferMillis = 1000); + /// Creates a new outgoing stream accepting Opus-encoded data. This is a direct stream with no internal timer. AudioOutStream CreateDirectOpusStream(); + /// Creates a new outgoing stream accepting PCM (raw) data. - AudioOutStream CreatePCMStream(AudioApplication application, int? bitrate = null, int bufferMillis = 1000, int packetLoss = 30); + AudioOutStream CreatePCMStream(AudioApplication application, int? bitrate = null, int bufferMillis = 1000, + int packetLoss = 30); + /// Creates a new direct outgoing stream accepting PCM (raw) data. This is a direct stream with no internal timer. AudioOutStream CreateDirectPCMStream(AudioApplication application, int? bitrate = null, int packetLoss = 30); } diff --git a/src/Discord.Net.Core/Audio/RTPFrame.cs b/src/Discord.Net.Core/Audio/RTPFrame.cs index 6254b7173..3c5211a1b 100644 --- a/src/Discord.Net.Core/Audio/RTPFrame.cs +++ b/src/Discord.Net.Core/Audio/RTPFrame.cs @@ -15,4 +15,4 @@ namespace Discord.Audio Missed = missed; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/CDN.cs b/src/Discord.Net.Core/CDN.cs index 6bac241aa..7f6785eda 100644 --- a/src/Discord.Net.Core/CDN.cs +++ b/src/Discord.Net.Core/CDN.cs @@ -6,34 +6,39 @@ namespace Discord { public static string GetApplicationIconUrl(ulong appId, string iconId) => iconId != null ? $"{DiscordConfig.CDNUrl}app-icons/{appId}/{iconId}.jpg" : null; + public static string GetUserAvatarUrl(ulong userId, string avatarId, ushort size, ImageFormat format) { if (avatarId == null) return null; - string extension = FormatToExtension(format, avatarId); + var extension = FormatToExtension(format, avatarId); return $"{DiscordConfig.CDNUrl}avatars/{userId}/{avatarId}.{extension}?size={size}"; } - public static string GetDefaultUserAvatarUrl(ushort discriminator) - { - return $"{DiscordConfig.CDNUrl}embed/avatars/{discriminator % 5}.png"; - } + + public static string GetDefaultUserAvatarUrl(ushort discriminator) => + $"{DiscordConfig.CDNUrl}embed/avatars/{discriminator % 5}.png"; + public static string GetGuildIconUrl(ulong guildId, string iconId) => iconId != null ? $"{DiscordConfig.CDNUrl}icons/{guildId}/{iconId}.jpg" : null; + public static string GetGuildSplashUrl(ulong guildId, string splashId) => splashId != null ? $"{DiscordConfig.CDNUrl}splashes/{guildId}/{splashId}.jpg" : null; + public static string GetChannelIconUrl(ulong channelId, string iconId) => iconId != null ? $"{DiscordConfig.CDNUrl}channel-icons/{channelId}/{iconId}.jpg" : null; + public static string GetEmojiUrl(ulong emojiId, bool animated) => $"{DiscordConfig.CDNUrl}emojis/{emojiId}.{(animated ? "gif" : "png")}"; public static string GetRichAssetUrl(ulong appId, string assetId, ushort size, ImageFormat format) { - string extension = FormatToExtension(format, ""); + var extension = FormatToExtension(format, ""); return $"{DiscordConfig.CDNUrl}app-assets/{appId}/{assetId}.{extension}?size={size}"; } public static string GetSpotifyAlbumArtUrl(string albumArtId) => $"https://i.scdn.co/image/{albumArtId}"; + public static string GetSpotifyDirectUrl(string trackId) => $"https://open.spotify.com/track/{trackId}"; diff --git a/src/Discord.Net.Core/DiscordConfig.cs b/src/Discord.Net.Core/DiscordConfig.cs index 8c6cfc28c..b8f26f5bb 100644 --- a/src/Discord.Net.Core/DiscordConfig.cs +++ b/src/Discord.Net.Core/DiscordConfig.cs @@ -6,13 +6,6 @@ namespace Discord { public const int APIVersion = 6; public const int VoiceAPIVersion = 3; - public static string Version { get; } = - typeof(DiscordConfig).GetTypeInfo().Assembly.GetCustomAttribute()?.InformationalVersion ?? - typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ?? - "Unknown"; - - public static string UserAgent { get; } = $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})"; - public static readonly string APIUrl = $"https://discordapp.com/api/v{APIVersion}/"; public const string CDNUrl = "https://cdn.discordapp.com/"; public const string InviteUrl = "https://discord.gg/"; @@ -23,6 +16,16 @@ namespace Discord public const int MaxGuildsPerBatch = 100; public const int MaxUserReactionsPerBatch = 100; public const int MaxAuditLogEntriesPerBatch = 100; + public static readonly string APIUrl = $"https://discordapp.com/api/v{APIVersion}/"; + + public static string Version { get; } = + typeof(DiscordConfig).GetTypeInfo().Assembly.GetCustomAttribute() + ?.InformationalVersion ?? + typeof(DiscordConfig).GetTypeInfo().Assembly.GetName().Version.ToString(3) ?? + "Unknown"; + + public static string UserAgent { get; } = + $"DiscordBot (https://github.com/RogueException/Discord.Net, v{Version})"; /// Gets or sets how a request should act in the case of an error, by default. public RetryMode DefaultRetryMode { get; set; } = RetryMode.AlwaysRetry; diff --git a/src/Discord.Net.Core/Entities/Activities/Game.cs b/src/Discord.Net.Core/Entities/Activities/Game.cs index 179ad4eaa..346fe262f 100644 --- a/src/Discord.Net.Core/Entities/Activities/Game.cs +++ b/src/Discord.Net.Core/Entities/Activities/Game.cs @@ -2,20 +2,23 @@ using System.Diagnostics; namespace Discord { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class Game : IActivity { - public string Name { get; internal set; } - public ActivityType Type { get; internal set; } + internal Game() + { + } - internal Game() { } public Game(string name, ActivityType type = ActivityType.Playing) { Name = name; Type = type; } - - public override string ToString() => Name; + private string DebuggerDisplay => Name; + public string Name { get; internal set; } + public ActivityType Type { get; internal set; } + + public override string ToString() => Name; } } diff --git a/src/Discord.Net.Core/Entities/Activities/GameAsset.cs b/src/Discord.Net.Core/Entities/Activities/GameAsset.cs index 02c29ba41..7234b7621 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameAsset.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameAsset.cs @@ -2,13 +2,15 @@ namespace Discord { public class GameAsset { - internal GameAsset() { } + internal GameAsset() + { + } internal ulong? ApplicationId { get; set; } - + public string Text { get; internal set; } public string ImageId { get; internal set; } - + public string GetImageUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) => ApplicationId.HasValue ? CDN.GetRichAssetUrl(ApplicationId.Value, ImageId, size, format) : null; } diff --git a/src/Discord.Net.Core/Entities/Activities/GameParty.cs b/src/Discord.Net.Core/Entities/Activities/GameParty.cs index 54e6deef4..40797ad85 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameParty.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameParty.cs @@ -2,7 +2,9 @@ namespace Discord { public class GameParty { - internal GameParty() { } + internal GameParty() + { + } public string Id { get; internal set; } public long Members { get; internal set; } diff --git a/src/Discord.Net.Core/Entities/Activities/GameSecrets.cs b/src/Discord.Net.Core/Entities/Activities/GameSecrets.cs index e9d988ba9..bb3426222 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameSecrets.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameSecrets.cs @@ -2,15 +2,15 @@ { public class GameSecrets { - public string Match { get; } - public string Join { get; } - public string Spectate { get; } - internal GameSecrets(string match, string join, string spectate) { Match = match; Join = join; Spectate = spectate; } + + public string Match { get; } + public string Join { get; } + public string Spectate { get; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Activities/GameTimestamps.cs b/src/Discord.Net.Core/Entities/Activities/GameTimestamps.cs index 8c8c992fa..38886a04c 100644 --- a/src/Discord.Net.Core/Entities/Activities/GameTimestamps.cs +++ b/src/Discord.Net.Core/Entities/Activities/GameTimestamps.cs @@ -4,13 +4,13 @@ namespace Discord { public class GameTimestamps { - public DateTimeOffset? Start { get; } - public DateTimeOffset? End { get; } - internal GameTimestamps(DateTimeOffset? start, DateTimeOffset? end) { Start = start; End = end; } + + public DateTimeOffset? Start { get; } + public DateTimeOffset? End { get; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Activities/RichGame.cs b/src/Discord.Net.Core/Entities/Activities/RichGame.cs index fc3f68cf0..e97ebf618 100644 --- a/src/Discord.Net.Core/Entities/Activities/RichGame.cs +++ b/src/Discord.Net.Core/Entities/Activities/RichGame.cs @@ -2,10 +2,12 @@ using System.Diagnostics; namespace Discord { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RichGame : Game { - internal RichGame() { } + internal RichGame() + { + } public string Details { get; internal set; } public string State { get; internal set; } @@ -15,8 +17,8 @@ namespace Discord public GameParty Party { get; internal set; } public GameSecrets Secrets { get; internal set; } public GameTimestamps Timestamps { get; internal set; } - - public override string ToString() => Name; private string DebuggerDisplay => $"{Name} (Rich)"; + + public override string ToString() => Name; } } diff --git a/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs b/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs index 8b966d68b..ade040300 100644 --- a/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs +++ b/src/Discord.Net.Core/Entities/Activities/SpotifyGame.cs @@ -4,9 +4,13 @@ using System.Diagnostics; namespace Discord { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class SpotifyGame : Game { + internal SpotifyGame() + { + } + public IReadOnlyCollection Artists { get; internal set; } public string AlbumTitle { get; internal set; } public string TrackTitle { get; internal set; } @@ -17,10 +21,8 @@ namespace Discord public string AlbumArtUrl { get; internal set; } public string TrackUrl { get; internal set; } - - internal SpotifyGame() { } + private string DebuggerDisplay => $"{Name} (Spotify)"; public override string ToString() => $"{string.Join(", ", Artists)} - {TrackTitle} ({Duration})"; - private string DebuggerDisplay => $"{Name} (Spotify)"; } } diff --git a/src/Discord.Net.Core/Entities/Activities/StreamingGame.cs b/src/Discord.Net.Core/Entities/Activities/StreamingGame.cs index afbc24cd9..09c41c0c8 100644 --- a/src/Discord.Net.Core/Entities/Activities/StreamingGame.cs +++ b/src/Discord.Net.Core/Entities/Activities/StreamingGame.cs @@ -2,11 +2,9 @@ namespace Discord { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class StreamingGame : Game { - public string Url { get; internal set; } - public StreamingGame(string name, string url) { Name = name; @@ -14,7 +12,9 @@ namespace Discord Type = ActivityType.Streaming; } - public override string ToString() => Name; + public string Url { get; internal set; } private string DebuggerDisplay => $"{Name} ({Url})"; + + public override string ToString() => Name; } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs index e5a4ff30a..afd379787 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/ActionType.cs @@ -1,13 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Discord +namespace Discord { /// - /// The action type within a + /// The action type within a /// public enum ActionType { diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs index 47aaffb26..667297129 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogData.cs @@ -1,14 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Discord +namespace Discord { /// - /// Represents data applied to an + /// Represents data applied to an /// public interface IAuditLogData - { } + { + } } diff --git a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs index 150c59a42..3b733196a 100644 --- a/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs +++ b/src/Discord.Net.Core/Entities/AuditLogs/IAuditLogEntry.cs @@ -1,33 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { /// - /// Represents an entry in an audit log + /// Represents an entry in an audit log /// public interface IAuditLogEntry : ISnowflakeEntity { /// - /// The action which occured to create this entry + /// The action which occured to create this entry /// ActionType Action { get; } /// - /// The data for this entry. May be if no data was available. + /// The data for this entry. May be if no data was available. /// IAuditLogData Data { get; } /// - /// The user responsible for causing the changes + /// The user responsible for causing the changes /// IUser User { get; } /// - /// The reason behind the change. May be if no reason was provided. + /// The reason behind the change. May be if no reason was provided. /// string Reason { get; } } diff --git a/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs index 2ac6c8d52..9203bc8d3 100644 --- a/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/GuildChannelProperties.cs @@ -1,10 +1,10 @@ namespace Discord { /// - /// Modify an IGuildChannel with the specified changes. + /// Modify an IGuildChannel with the specified changes. /// /// - /// + /// /// await (Context.Channel as ITextChannel)?.ModifyAsync(x => /// { /// x.Name = "do-not-enter"; @@ -14,20 +14,22 @@ public class GuildChannelProperties { /// - /// Set the channel to this name + /// Set the channel to this name /// /// - /// When modifying an ITextChannel, the Name MUST be alphanumeric with dashes. - /// It must match the following RegEx: [a-z0-9-_]{2,100} + /// When modifying an ITextChannel, the Name MUST be alphanumeric with dashes. + /// It must match the following RegEx: [a-z0-9-_]{2,100} /// /// A BadRequest will be thrown if the name does not match the above RegEx. public Optional Name { get; set; } + /// - /// Move the channel to the following position. This is 0-based! + /// Move the channel to the following position. This is 0-based! /// public Optional Position { get; set; } + /// - /// Sets the category for this channel + /// Sets the category for this channel /// public Optional CategoryId { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs b/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs index a152ff744..449f58808 100644 --- a/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IAudioChannel.cs @@ -1,6 +1,5 @@ -using Discord.Audio; -using System; using System.Threading.Tasks; +using Discord.Audio; namespace Discord { diff --git a/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs b/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs index c004cafd5..0e3256acc 100644 --- a/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ICategoryChannel.cs @@ -1,9 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Discord { public interface ICategoryChannel : IGuildChannel diff --git a/src/Discord.Net.Core/Entities/Channels/IChannel.cs b/src/Discord.Net.Core/Entities/Channels/IChannel.cs index ea930e112..dd5d52f27 100644 --- a/src/Discord.Net.Core/Entities/Channels/IChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IChannel.cs @@ -7,10 +7,11 @@ namespace Discord { /// Gets the name of this channel. string Name { get; } - + /// Gets a collection of all users in this channel. - IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - + IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + /// Gets a user in this channel with the provided id. Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); } diff --git a/src/Discord.Net.Core/Entities/Channels/IDMChannel.cs b/src/Discord.Net.Core/Entities/Channels/IDMChannel.cs index 1608d1543..08adb2f7c 100644 --- a/src/Discord.Net.Core/Entities/Channels/IDMChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IDMChannel.cs @@ -10,4 +10,4 @@ namespace Discord /// Closes this private channel, removing it from your channel list. Task CloseAsync(RequestOptions options = null); } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs index d6cb2c182..3da364797 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGroupChannel.cs @@ -7,4 +7,4 @@ namespace Discord /// Leaves this group. Task LeaveAsync(RequestOptions options = null); } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs index 6514d46cd..4561ce0df 100644 --- a/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IGuildChannel.cs @@ -11,16 +11,23 @@ namespace Discord /// Gets the guild this channel is a member of. IGuild Guild { get; } + /// Gets the id of the guild this channel is a member of. ulong GuildId { get; } + /// Gets a collection of permission overwrites for this channel. IReadOnlyCollection PermissionOverwrites { get; } /// Creates a new invite to this channel. /// The time (in seconds) until the invite expires. Set to null to never expire. /// The max amount of times this invite may be used. Set to null to have unlimited uses. - /// If true, a user accepting this invite will be kicked from the guild after closing their client. - Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = default(int?), bool isTemporary = false, bool isUnique = false, RequestOptions options = null); + /// + /// If true, a user accepting this invite will be kicked from the guild after closing their + /// client. + /// + Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = default(int?), + bool isTemporary = false, bool isUnique = false, RequestOptions options = null); + /// Returns a collection of all invites to this channel. Task> GetInvitesAsync(RequestOptions options = null); @@ -29,20 +36,28 @@ namespace Discord /// Gets the permission overwrite for a specific role, or null if one does not exist. OverwritePermissions? GetPermissionOverwrite(IRole role); + /// Gets the permission overwrite for a specific user, or null if one does not exist. OverwritePermissions? GetPermissionOverwrite(IUser user); + /// Removes the permission overwrite for the given role, if one exists. Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null); + /// Removes the permission overwrite for the given user, if one exists. Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null); + /// Adds or updates the permission overwrite for the given role. Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options = null); + /// Adds or updates the permission overwrite for the given user. Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options = null); /// Gets a collection of all users in this channel. - new IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + new IAsyncEnumerable> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + /// Gets a user in this channel with the provided id. - new Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + new Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs index ef5a6fa7a..1edd0824d 100644 --- a/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IMessageChannel.cs @@ -8,35 +8,51 @@ namespace Discord public interface IMessageChannel : IChannel { /// Sends a message to this message channel. - Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); + Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, + RequestOptions options = null); + /// Sends a file to this text channel, with an optional caption. - Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); + Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, + RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. - Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); + Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, + Embed embed = null, RequestOptions options = null); /// Gets a message from this message channel with the given id, or null if not found. - Task GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + Task GetMessageAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + /// Gets the last N messages from this message channel. - IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, + IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// Gets a collection of messages in this channel. - IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, + IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// Gets a collection of messages in this channel. - IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, + IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// Gets a collection of pinned messages in this channel. Task> GetPinnedMessagesAsync(RequestOptions options = null); /// Deletes a message based on the message ID in this channel. Task DeleteMessageAsync(ulong messageId, RequestOptions options = null); + /// Deletes a message based on the provided message in this channel. Task DeleteMessageAsync(IMessage message, RequestOptions options = null); /// Broadcasts the "user is typing" message to all users in this channel, lasting 10 seconds. Task TriggerTypingAsync(RequestOptions options = null); - /// Continuously broadcasts the "user is typing" message to all users in this channel until the returned object is disposed. + + /// + /// Continuously broadcasts the "user is typing" message to all users in this channel until the returned object + /// is disposed. + /// IDisposable EnterTypingState(RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs b/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs index c8d2bcaaf..d35e75916 100644 --- a/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/INestedChannel.cs @@ -3,14 +3,16 @@ using System.Threading.Tasks; namespace Discord { /// - /// A type of guild channel that can be nested within a category. - /// Contains a CategoryId that is set to the parent category, if it is set. + /// A type of guild channel that can be nested within a category. + /// Contains a CategoryId that is set to the parent category, if it is set. /// public interface INestedChannel : IGuildChannel { /// Gets the parentid (category) of this channel in the guild's channel list. ulong? CategoryId { get; } + /// Gets the parent channel (category) of this channel, if it is set. If unset, returns null. - Task GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + Task GetCategoryAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); } } diff --git a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs index 2aa070b03..3f67ddf9a 100644 --- a/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/ITextChannel.cs @@ -15,6 +15,7 @@ namespace Discord /// Bulk deletes multiple messages. Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null); + /// Bulk deletes multiple messages. Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null); @@ -23,8 +24,10 @@ namespace Discord /// Creates a webhook in this text channel. Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null); + /// Gets the webhook in this text channel with the provided id, or null if not found. Task GetWebhookAsync(ulong id, RequestOptions options = null); + /// Gets the webhooks for this text channel. Task> GetWebhooksAsync(RequestOptions options = null); } diff --git a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs index 2e345bfda..d5342a01d 100644 --- a/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs +++ b/src/Discord.Net.Core/Entities/Channels/IVoiceChannel.cs @@ -7,6 +7,7 @@ namespace Discord { /// Gets the bitrate, in bits per second, clients in this voice channel are requested to use. int Bitrate { get; } + /// Gets the max amount of users allowed to be connected to this channel at one time. int? UserLimit { get; } diff --git a/src/Discord.Net.Core/Entities/Channels/ReorderChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/ReorderChannelProperties.cs index 31f814334..62e456af0 100644 --- a/src/Discord.Net.Core/Entities/Channels/ReorderChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/ReorderChannelProperties.cs @@ -2,15 +2,16 @@ { public class ReorderChannelProperties { - /// The id of the channel to apply this position to. - public ulong Id { get; } - /// The new zero-based position of this channel. - public int Position { get; } - public ReorderChannelProperties(ulong id, int position) { Id = id; Position = position; } + + /// The id of the channel to apply this position to. + public ulong Id { get; } + + /// The new zero-based position of this channel. + public int Position { get; } } } diff --git a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs index b7b568133..c2650bd3e 100644 --- a/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/TextChannelProperties.cs @@ -4,11 +4,12 @@ public class TextChannelProperties : GuildChannelProperties { /// - /// What the topic of the channel should be set to. + /// What the topic of the channel should be set to. /// public Optional Topic { get; set; } + /// - /// Should this channel be flagged as NSFW? + /// Should this channel be flagged as NSFW? /// public Optional IsNsfw { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs b/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs index 81dd8063e..98e946880 100644 --- a/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs +++ b/src/Discord.Net.Core/Entities/Channels/VoiceChannelProperties.cs @@ -4,11 +4,12 @@ public class VoiceChannelProperties : GuildChannelProperties { /// - /// The bitrate of the voice connections in this channel. Must be greater than 8000 + /// The bitrate of the voice connections in this channel. Must be greater than 8000 /// public Optional Bitrate { get; set; } + /// - /// The maximum number of users that can be present in a channel. + /// The maximum number of users that can be present in a channel. /// public Optional UserLimit { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs index c2dfc31ad..023ae463e 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emoji.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emoji.cs @@ -1,27 +1,26 @@ namespace Discord { /// - /// A unicode emoji + /// A unicode emoji /// public class Emoji : IEmote { - // TODO: need to constrain this to unicode-only emojis somehow - - /// - /// The unicode representation of this emote. - /// - public string Name { get; } - - public override string ToString() => Name; - /// - /// Creates a unicode emoji. + /// Creates a unicode emoji. /// /// The pure UTF-8 encoding of an emoji public Emoji(string unicode) { Name = unicode; } + // TODO: need to constrain this to unicode-only emojis somehow + + /// + /// The unicode representation of this emote. + /// + public string Name { get; } + + public override string ToString() => Name; public override bool Equals(object other) { diff --git a/src/Discord.Net.Core/Entities/Emotes/Emote.cs b/src/Discord.Net.Core/Entities/Emotes/Emote.cs index e3a228c83..8719a652e 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emote.cs @@ -4,31 +4,37 @@ using System.Globalization; namespace Discord { /// - /// A custom image-based emote + /// A custom image-based emote /// public class Emote : IEmote, ISnowflakeEntity { + internal Emote(ulong id, string name, bool animated) + { + Id = id; + Name = name; + Animated = animated; + } + /// - /// The display name (tooltip) of this emote + /// Is this emote animated? /// - public string Name { get; } + public bool Animated { get; } + + public string Url => CDN.GetEmojiUrl(Id, Animated); + + private string DebuggerDisplay => $"{Name} ({Id})"; + /// - /// The ID of this emote + /// The display name (tooltip) of this emote /// - public ulong Id { get; } + public string Name { get; } + /// - /// Is this emote animated? + /// The ID of this emote /// - public bool Animated { get; } - public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - public string Url => CDN.GetEmojiUrl(Id, Animated); + public ulong Id { get; } - internal Emote(ulong id, string name, bool animated) - { - Id = id; - Name = name; - Animated = animated; - } + public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public override bool Equals(object other) { @@ -50,13 +56,13 @@ namespace Discord } /// - /// Parse an Emote from its raw format + /// Parse an Emote from its raw format /// /// The raw encoding of an emote; for example, <:dab:277855270321782784> /// An emote public static Emote Parse(string text) { - if (TryParse(text, out Emote result)) + if (TryParse(text, out var result)) return result; throw new ArgumentException("Invalid emote format", nameof(text)); } @@ -64,27 +70,25 @@ namespace Discord public static bool TryParse(string text, out Emote result) { result = null; - if (text.Length >= 4 && text[0] == '<' && (text[1] == ':' || (text[1] == 'a' && text[2] == ':')) && text[text.Length - 1] == '>') - { - bool animated = text[1] == 'a'; - int startIndex = animated ? 3 : 2; + if (text.Length < 4 || text[0] != '<' || text[1] != ':' && (text[1] != 'a' || text[2] != ':') || + text[text.Length - 1] != '>') return false; + var animated = text[1] == 'a'; + var startIndex = animated ? 3 : 2; - int splitIndex = text.IndexOf(':', startIndex); - if (splitIndex == -1) - return false; + var splitIndex = text.IndexOf(':', startIndex); + if (splitIndex == -1) + return false; - if (!ulong.TryParse(text.Substring(splitIndex + 1, text.Length - splitIndex - 2), NumberStyles.None, CultureInfo.InvariantCulture, out ulong id)) - return false; + if (!ulong.TryParse(text.Substring(splitIndex + 1, text.Length - splitIndex - 2), NumberStyles.None, + CultureInfo.InvariantCulture, out var id)) + return false; - string name = text.Substring(startIndex, splitIndex - startIndex); - result = new Emote(id, name, animated); - return true; - } - return false; + var name = text.Substring(startIndex, splitIndex - startIndex); + result = new Emote(id, name, animated); + return true; } - private string DebuggerDisplay => $"{Name} ({Id})"; public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>"; } } diff --git a/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs b/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs index 95b062bd2..8926e097f 100644 --- a/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/GuildEmote.cs @@ -4,22 +4,23 @@ using System.Diagnostics; namespace Discord { /// - /// An image-based emote that is attached to a guild + /// An image-based emote that is attached to a guild /// - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class GuildEmote : Emote { - public bool IsManaged { get; } - public bool RequireColons { get; } - public IReadOnlyList RoleIds { get; } - - internal GuildEmote(ulong id, string name, bool animated, bool isManaged, bool requireColons, IReadOnlyList roleIds) : base(id, name, animated) + internal GuildEmote(ulong id, string name, bool animated, bool isManaged, bool requireColons, + IReadOnlyList roleIds) : base(id, name, animated) { IsManaged = isManaged; RequireColons = requireColons; RoleIds = roleIds; } + public bool IsManaged { get; } + public bool RequireColons { get; } + public IReadOnlyList RoleIds { get; } + private string DebuggerDisplay => $"{Name} ({Id})"; public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>"; } diff --git a/src/Discord.Net.Core/Entities/Emotes/IEmote.cs b/src/Discord.Net.Core/Entities/Emotes/IEmote.cs index fac61402a..a9f3cc07a 100644 --- a/src/Discord.Net.Core/Entities/Emotes/IEmote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/IEmote.cs @@ -1,12 +1,12 @@ namespace Discord { /// - /// A general container for any type of emote in a message. + /// A general container for any type of emote in a message. /// public interface IEmote { /// - /// The display name or unicode representation of this emote + /// The display name or unicode representation of this emote /// string Name { get; } } diff --git a/src/Discord.Net.Core/Entities/Guilds/DefaultMessageNotifications.cs b/src/Discord.Net.Core/Entities/Guilds/DefaultMessageNotifications.cs index a5cabc117..3b00fe797 100644 --- a/src/Discord.Net.Core/Entities/Guilds/DefaultMessageNotifications.cs +++ b/src/Discord.Net.Core/Entities/Guilds/DefaultMessageNotifications.cs @@ -4,6 +4,7 @@ { /// By default, all messages will trigger notifications. AllMessages = 0, + /// By default, only mentions will trigger notifications. MentionsOnly = 1 } diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs b/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs index a2b2ec4fc..57c4cdac5 100644 --- a/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs +++ b/src/Discord.Net.Core/Entities/Guilds/GuildEmbedProperties.cs @@ -1,20 +1,22 @@ namespace Discord { /// - /// Modify the widget of an IGuild with the specified parameters + /// Modify the widget of an IGuild with the specified parameters /// public class GuildEmbedProperties { /// - /// Should the widget be enabled? + /// Should the widget be enabled? /// public Optional Enabled { get; set; } + /// - /// What channel should the invite place users in, if not null. + /// What channel should the invite place users in, if not null. /// public Optional Channel { get; set; } + /// - /// What channel should the invite place users in, if not null. + /// What channel should the invite place users in, if not null. /// public Optional ChannelId { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs b/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs index 1b406ef7f..e46720463 100644 --- a/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs +++ b/src/Discord.Net.Core/Entities/Guilds/GuildProperties.cs @@ -1,10 +1,10 @@ namespace Discord { /// - /// Modify an IGuild with the specified changes + /// Modify an IGuild with the specified changes /// /// - /// + /// /// await Context.Guild.ModifyAsync(async x => /// { /// x.Name = "aaaaaah"; @@ -12,67 +12,81 @@ /// }); /// /// - /// + /// public class GuildProperties { public Optional Username { get; set; } + /// - /// The name of the Guild + /// The name of the Guild /// public Optional Name { get; set; } + /// - /// The region for the Guild's voice connections + /// The region for the Guild's voice connections /// public Optional Region { get; set; } + /// - /// The ID of the region for the Guild's voice connections + /// The ID of the region for the Guild's voice connections /// public Optional RegionId { get; set; } + /// - /// What verification level new users need to achieve before speaking + /// What verification level new users need to achieve before speaking /// public Optional VerificationLevel { get; set; } + /// - /// The default message notification state for the guild + /// The default message notification state for the guild /// public Optional DefaultMessageNotifications { get; set; } + /// - /// How many seconds before a user is sent to AFK. This value MUST be one of: (60, 300, 900, 1800, 3600). + /// How many seconds before a user is sent to AFK. This value MUST be one of: (60, 300, 900, 1800, 3600). /// public Optional AfkTimeout { get; set; } + /// - /// The icon of the guild + /// The icon of the guild /// public Optional Icon { get; set; } + /// - /// The guild's splash image + /// The guild's splash image /// /// - /// The guild must be partnered for this value to have any effect. + /// The guild must be partnered for this value to have any effect. /// public Optional Splash { get; set; } + /// - /// The IVoiceChannel where AFK users should be sent. + /// The IVoiceChannel where AFK users should be sent. /// public Optional AfkChannel { get; set; } + /// - /// The ID of the IVoiceChannel where AFK users should be sent. + /// The ID of the IVoiceChannel where AFK users should be sent. /// public Optional AfkChannelId { get; set; } + /// - /// The ITextChannel where System messages should be sent. + /// The ITextChannel where System messages should be sent. /// public Optional SystemChannel { get; set; } + /// - /// The ID of the ITextChannel where System messages should be sent. + /// The ID of the ITextChannel where System messages should be sent. /// public Optional SystemChannelId { get; set; } + /// - /// The owner of this guild. + /// The owner of this guild. /// public Optional Owner { get; set; } + /// - /// The ID of the owner of this guild. + /// The ID of the owner of this guild. /// public Optional OwnerId { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs index bbe7051cb..fe69e6087 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IGuild.cs @@ -1,7 +1,7 @@ -using Discord.Audio; using System; using System.Collections.Generic; using System.Threading.Tasks; +using Discord.Audio; namespace Discord { @@ -9,109 +9,170 @@ namespace Discord { /// Gets the name of this guild. string Name { get; } - /// Gets the amount of time (in seconds) a user must be inactive in a voice channel for until they are automatically moved to the AFK voice channel, if one is set. + + /// + /// Gets the amount of time (in seconds) a user must be inactive in a voice channel for until they are + /// automatically moved to the AFK voice channel, if one is set. + /// int AFKTimeout { get; } + /// Returns true if this guild is embeddable (e.g. widget) bool IsEmbeddable { get; } + /// Gets the default message notifications for users who haven't explicitly set their notification settings. DefaultMessageNotifications DefaultMessageNotifications { get; } - /// Gets the level of mfa requirements a user must fulfill before being allowed to perform administrative actions in this guild. + + /// + /// Gets the level of mfa requirements a user must fulfill before being allowed to perform administrative actions + /// in this guild. + /// MfaLevel MfaLevel { get; } + /// Gets the level of requirements a user must fulfill before being allowed to post messages in this guild. VerificationLevel VerificationLevel { get; } + /// Returns the id of this guild's icon, or null if one is not set. string IconId { get; } + /// Returns the url to this guild's icon, or null if one is not set. string IconUrl { get; } + /// Returns the id of this guild's splash image, or null if one is not set. string SplashId { get; } + /// Returns the url to this guild's splash image, or null if one is not set. string SplashUrl { get; } + /// Returns true if this guild is currently connected and ready to be used. Only applies to the WebSocket client. bool Available { get; } /// Gets the id of the AFK voice channel for this guild if set, or null if not. ulong? AFKChannelId { get; } + /// Gets the id of the the default channel for this guild. ulong DefaultChannelId { get; } + /// Gets the id of the embed channel for this guild if set, or null if not. ulong? EmbedChannelId { get; } + /// Gets the id of the channel where randomized welcome messages are sent, or null if not. ulong? SystemChannelId { get; } + /// Gets the id of the user that created this guild. ulong OwnerId { get; } + /// Gets the id of the region hosting this guild's voice channels. string VoiceRegionId { get; } + /// Gets the IAudioClient currently associated with this guild. IAudioClient AudioClient { get; } + /// Gets the built-in role containing all users in this guild. IRole EveryoneRole { get; } + /// Gets a collection of all custom emojis for this guild. IReadOnlyCollection Emotes { get; } + /// Gets a collection of all extra features added to this guild. IReadOnlyCollection Features { get; } + /// Gets a collection of all roles in this guild. IReadOnlyCollection Roles { get; } /// Modifies this guild. Task ModifyAsync(Action func, RequestOptions options = null); + /// Modifies this guild's embed. Task ModifyEmbedAsync(Action func, RequestOptions options = null); + /// Bulk modifies the channels of this guild. Task ReorderChannelsAsync(IEnumerable args, RequestOptions options = null); + /// Bulk modifies the roles of this guild. Task ReorderRolesAsync(IEnumerable args, RequestOptions options = null); + /// Leaves this guild. If you are the owner, use Delete instead. Task LeaveAsync(RequestOptions options = null); /// Gets a collection of all users banned on this guild. Task> GetBansAsync(RequestOptions options = null); + /// /// Gets a ban object for a banned user. /// /// The banned user. /// - /// An awaitable containing the ban object, which contains the user information and the - /// reason for the ban; if the ban entry cannot be found. + /// An awaitable containing the ban object, which contains the user information and the + /// reason for the ban; if the ban entry cannot be found. /// Task GetBanAsync(IUser user, RequestOptions options = null); + /// /// Gets a ban object for a banned user. /// /// The snowflake identifier for the banned user. /// - /// An awaitable containing the ban object, which contains the user information and the - /// reason for the ban; if the ban entry cannot be found. + /// An awaitable containing the ban object, which contains the user information and the + /// reason for the ban; if the ban entry cannot be found. /// Task GetBanAsync(ulong userId, RequestOptions options = null); + /// Bans the provided user from this guild and optionally prunes their recent messages. /// The number of days to remove messages from this user for - must be between [0, 7] Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null); + /// Bans the provided user id from this guild and optionally prunes their recent messages. /// The number of days to remove messages from this user for - must be between [0, 7] Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null); + /// Unbans the provided user if it is currently banned. Task RemoveBanAsync(IUser user, RequestOptions options = null); + /// Unbans the provided user id if it is currently banned. Task RemoveBanAsync(ulong userId, RequestOptions options = null); /// Gets a collection of all channels in this guild. - Task> GetChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + Task> GetChannelsAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + /// Gets the channel in this guild with the provided id, or null if not found. - Task GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - Task> GetTextChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - Task GetTextChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - Task> GetVoiceChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - Task> GetCategoriesAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - Task GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + Task GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + + Task> GetTextChannelsAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + + Task GetTextChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + + Task> GetVoiceChannelsAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + + Task> GetCategoriesAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + + Task GetVoiceChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + Task GetAFKChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - Task GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - Task GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - Task GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + + Task GetSystemChannelAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + + Task GetDefaultChannelAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + + Task GetEmbedChannelAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + /// Creates a new text channel. - Task CreateTextChannelAsync(string name, Action func = null, RequestOptions options = null); + Task CreateTextChannelAsync(string name, Action func = null, + RequestOptions options = null); + /// Creates a new voice channel. - Task CreateVoiceChannelAsync(string name, Action func = null, RequestOptions options = null); + Task CreateVoiceChannelAsync(string name, Action func = null, + RequestOptions options = null); + /// Creates a new channel category. Task CreateCategoryAsync(string name, RequestOptions options = null); @@ -120,49 +181,69 @@ namespace Discord /// Gets a collection of all invites to this guild. Task> GetInvitesAsync(RequestOptions options = null); + /// /// Gets the vanity invite URL of this guild. /// /// The options to be used when sending the request. /// - /// An awaitable containing the partial metadata of the vanity invite found within + /// An awaitable containing the partial metadata of the vanity invite found within /// this guild. /// Task GetVanityInviteAsync(RequestOptions options = null); /// Gets the role in this guild with the provided id, or null if not found. IRole GetRole(ulong id); + /// Creates a new role. - Task CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, bool isHoisted = false, RequestOptions options = null); + Task CreateRoleAsync(string name, GuildPermissions? permissions = null, Color? color = null, + bool isHoisted = false, RequestOptions options = null); /// Gets a collection of all users in this guild. - Task> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); //TODO: shouldnt this be paged? + Task> GetUsersAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); //TODO: shouldnt this be paged? + /// Gets the user in this guild with the provided id, or null if not found. - Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + /// Gets the current user for this guild. Task GetCurrentUserAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// Gets the owner of this guild. Task GetOwnerAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + /// Downloads all users for this guild if the current list is incomplete. Task DownloadUsersAsync(); - /// Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is true, returns the number of users that would be removed. + + /// + /// Removes all users from this guild if they have not logged on in a provided number of days or, if simulate is + /// true, returns the number of users that would be removed. + /// Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null); /// Gets the specified number of audit log entries for this guild. - Task> GetAuditLogsAsync(int limit = DiscordConfig.MaxAuditLogEntriesPerBatch, + Task> GetAuditLogsAsync( + int limit = DiscordConfig.MaxAuditLogEntriesPerBatch, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); /// Gets the webhook in this guild with the provided id, or null if not found. Task GetWebhookAsync(ulong id, RequestOptions options = null); + /// Gets a collection of all webhooks for this guild. Task> GetWebhooksAsync(RequestOptions options = null); - + /// Gets a specific emote from this guild. Task GetEmoteAsync(ulong id, RequestOptions options = null); + /// Creates a new emote in this guild. - Task CreateEmoteAsync(string name, Image image, Optional> roles = default(Optional>), RequestOptions options = null); + Task CreateEmoteAsync(string name, Image image, + Optional> roles = default(Optional>), RequestOptions options = null); + /// Modifies an existing emote in this guild. - Task ModifyEmoteAsync(GuildEmote emote, Action func, RequestOptions options = null); + Task ModifyEmoteAsync(GuildEmote emote, Action func, + RequestOptions options = null); + /// Deletes an existing emote from this guild. Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null); } diff --git a/src/Discord.Net.Core/Entities/Guilds/IUserGuild.cs b/src/Discord.Net.Core/Entities/Guilds/IUserGuild.cs index b27db9377..2df8171a4 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IUserGuild.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IUserGuild.cs @@ -4,10 +4,13 @@ { /// Gets the name of this guild. string Name { get; } + /// Returns the url to this guild's icon, or null if one is not set. string IconUrl { get; } + /// Returns true if the current user owns this guild. bool IsOwner { get; } + /// Returns the current user's permissions for this guild. GuildPermissions Permissions { get; } } diff --git a/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs b/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs index 67bedbc3d..8cb3b75fc 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IVoiceRegion.cs @@ -4,14 +4,19 @@ namespace Discord { /// Gets the unique identifier for this voice region. string Id { get; } + /// Gets the name of this voice region. string Name { get; } + /// Returns true if this voice region is exclusive to VIP accounts. bool IsVip { get; } + /// Returns true if this voice region is the closest to your machine. bool IsOptimal { get; } + /// Returns true if this is a deprecated voice region (avoid switching to these). bool IsDeprecated { get; } + /// Returns true if this is a custom voice region (used for events/etc) bool IsCustom { get; } } diff --git a/src/Discord.Net.Core/Entities/Guilds/IntegrationAccount.cs b/src/Discord.Net.Core/Entities/Guilds/IntegrationAccount.cs index 71bcf10ed..a3f930695 100644 --- a/src/Discord.Net.Core/Entities/Guilds/IntegrationAccount.cs +++ b/src/Discord.Net.Core/Entities/Guilds/IntegrationAccount.cs @@ -2,7 +2,7 @@ namespace Discord { - [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public struct IntegrationAccount { public string Id { get; } diff --git a/src/Discord.Net.Core/Entities/Guilds/MfaLevel.cs b/src/Discord.Net.Core/Entities/Guilds/MfaLevel.cs index 1dfef17d5..f268ae94e 100644 --- a/src/Discord.Net.Core/Entities/Guilds/MfaLevel.cs +++ b/src/Discord.Net.Core/Entities/Guilds/MfaLevel.cs @@ -4,6 +4,7 @@ { /// Users have no additional MFA restriction on this guild. Disabled = 0, + /// Users must have MFA enabled on their account to perform administrative actions. Enabled = 1 } diff --git a/src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs b/src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs index ac51fe927..30b496658 100644 --- a/src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs +++ b/src/Discord.Net.Core/Entities/Guilds/VerificationLevel.cs @@ -4,12 +4,16 @@ { /// Users have no additional restrictions on sending messages to this guild. None = 0, + /// Users must have a verified email on their account. Low = 1, + /// Users must fulfill the requirements of Low, and be registered on Discord for at least 5 minutes. Medium = 2, + /// Users must fulfill the requirements of Medium, and be a member of this guild for at least 10 minutes. High = 3, + /// Users must fulfill the requirements of High, and must have a verified phone on their Discord account. Extreme = 4 } diff --git a/src/Discord.Net.Core/Entities/IApplication.cs b/src/Discord.Net.Core/Entities/IApplication.cs index 4fb1e4b91..2a309b8e3 100644 --- a/src/Discord.Net.Core/Entities/IApplication.cs +++ b/src/Discord.Net.Core/Entities/IApplication.cs @@ -6,7 +6,7 @@ string Description { get; } string[] RPCOrigins { get; } ulong Flags { get; } - string IconUrl { get; } + string IconUrl { get; } IUser Owner { get; } } diff --git a/src/Discord.Net.Core/Entities/IEntity.cs b/src/Discord.Net.Core/Entities/IEntity.cs index 711fd0555..2483f5b7e 100644 --- a/src/Discord.Net.Core/Entities/IEntity.cs +++ b/src/Discord.Net.Core/Entities/IEntity.cs @@ -10,6 +10,5 @@ namespace Discord /// Gets the unique identifier for this object. TId Id { get; } - } } diff --git a/src/Discord.Net.Core/Entities/Image.cs b/src/Discord.Net.Core/Entities/Image.cs index 3b946ce80..29b6d205f 100644 --- a/src/Discord.Net.Core/Entities/Image.cs +++ b/src/Discord.Net.Core/Entities/Image.cs @@ -1,14 +1,16 @@ using System.IO; + namespace Discord { /// - /// An image that will be uploaded to Discord. + /// An image that will be uploaded to Discord. /// public struct Image { public Stream Stream { get; } + /// - /// Create the image with a Stream. + /// Create the image with a Stream. /// /// This must be some type of stream with the contents of a file in it. public Image(Stream stream) @@ -17,16 +19,15 @@ namespace Discord } /// - /// Create the image from a file path. + /// Create the image from a file path. /// /// - /// This file path is NOT validated, and is passed directly into a + /// This file path is NOT validated, and is passed directly into a /// /// The path to the file. public Image(string path) { Stream = File.OpenRead(path); } - } } diff --git a/src/Discord.Net.Core/Entities/ImageFormat.cs b/src/Discord.Net.Core/Entities/ImageFormat.cs index 302da79c8..e704c6c03 100644 --- a/src/Discord.Net.Core/Entities/ImageFormat.cs +++ b/src/Discord.Net.Core/Entities/ImageFormat.cs @@ -6,6 +6,6 @@ WebP, Png, Jpeg, - Gif, + Gif } } diff --git a/src/Discord.Net.Core/Entities/Invites/IInvite.cs b/src/Discord.Net.Core/Entities/Invites/IInvite.cs index 1ab26de8f..eff94bb91 100644 --- a/src/Discord.Net.Core/Entities/Invites/IInvite.cs +++ b/src/Discord.Net.Core/Entities/Invites/IInvite.cs @@ -4,26 +4,34 @@ namespace Discord { /// Gets the unique identifier for this invite. string Code { get; } + /// Gets the url used to accept this invite, using Code. string Url { get; } /// Gets the channel this invite is linked to. IChannel Channel { get; } + /// Gets the type of the channel this invite is linked to. ChannelType ChannelType { get; } + /// Gets the id of the channel this invite is linked to. ulong ChannelId { get; } + /// Gets the name of the channel this invite is linked to. string ChannelName { get; } /// Gets the guild this invite is linked to. IGuild Guild { get; } + /// Gets the id of the guild this invite is linked to. ulong? GuildId { get; } + /// Gets the name of the guild this invite is linked to. string GuildName { get; } + /// Gets the approximated count of online members in the guild. int? PresenceCount { get; } + /// Gets the approximated count of total members in the guild. int? MemberCount { get; } } diff --git a/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs b/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs index 0e026ab62..f7e425927 100644 --- a/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs +++ b/src/Discord.Net.Core/Entities/Invites/IInviteMetadata.cs @@ -6,16 +6,22 @@ namespace Discord { /// Gets the user that created this invite. IUser Inviter { get; } + /// Returns true if this invite was revoked. bool IsRevoked { get; } + /// Returns true if users accepting this invite will be removed from the guild when they log off. bool IsTemporary { get; } + /// Gets the time (in seconds) until the invite expires, or null if it never expires. int? MaxAge { get; } + /// Gets the max amount of times this invite may be used, or null if there is no limit. int? MaxUses { get; } + /// Gets the amount of times this invite has been used. int? Uses { get; } + /// Gets when this invite was created. DateTimeOffset? CreatedAt { get; } } diff --git a/src/Discord.Net.Core/Entities/Messages/Embed.cs b/src/Discord.Net.Core/Entities/Messages/Embed.cs index dc62066eb..1ef853b07 100644 --- a/src/Discord.Net.Core/Entities/Messages/Embed.cs +++ b/src/Discord.Net.Core/Entities/Messages/Embed.cs @@ -5,30 +5,16 @@ using System.Linq; namespace Discord { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class Embed : IEmbed { - public EmbedType Type { get; } - - public string Description { get; internal set; } - public string Url { get; internal set; } - public string Title { get; internal set; } - public DateTimeOffset? Timestamp { get; internal set; } - public Color? Color { get; internal set; } - public EmbedImage? Image { get; internal set; } - public EmbedVideo? Video { get; internal set; } - public EmbedAuthor? Author { get; internal set; } - public EmbedFooter? Footer { get; internal set; } - public EmbedProvider? Provider { get; internal set; } - public EmbedThumbnail? Thumbnail { get; internal set; } - public ImmutableArray Fields { get; internal set; } - internal Embed(EmbedType type) { Type = type; Fields = ImmutableArray.Create(); } - internal Embed(EmbedType type, + + internal Embed(EmbedType type, string title, string description, string url, @@ -36,10 +22,10 @@ namespace Discord Color? color, EmbedImage? image, EmbedVideo? video, - EmbedAuthor? author, - EmbedFooter? footer, - EmbedProvider? provider, - EmbedThumbnail? thumbnail, + EmbedAuthor? author, + EmbedFooter? footer, + EmbedProvider? provider, + EmbedThumbnail? thumbnail, ImmutableArray fields) { Type = type; @@ -61,16 +47,31 @@ namespace Discord { get { - int titleLength = Title?.Length ?? 0; - int authorLength = Author?.Name?.Length ?? 0; - int descriptionLength = Description?.Length ?? 0; - int footerLength = Footer?.Text?.Length ?? 0; - int fieldSum = Fields.Sum(f => f.Name?.Length + f.Value?.ToString().Length) ?? 0; + var titleLength = Title?.Length ?? 0; + var authorLength = Author?.Name?.Length ?? 0; + var descriptionLength = Description?.Length ?? 0; + var footerLength = Footer?.Text?.Length ?? 0; + var fieldSum = Fields.Sum(f => f.Name?.Length + f.Value?.ToString().Length) ?? 0; return titleLength + authorLength + descriptionLength + footerLength + fieldSum; } } - public override string ToString() => Title; private string DebuggerDisplay => $"{Title} ({Type})"; + public EmbedType Type { get; } + + public string Description { get; internal set; } + public string Url { get; internal set; } + public string Title { get; internal set; } + public DateTimeOffset? Timestamp { get; internal set; } + public Color? Color { get; internal set; } + public EmbedImage? Image { get; internal set; } + public EmbedVideo? Video { get; internal set; } + public EmbedAuthor? Author { get; internal set; } + public EmbedFooter? Footer { get; internal set; } + public EmbedProvider? Provider { get; internal set; } + public EmbedThumbnail? Thumbnail { get; internal set; } + public ImmutableArray Fields { get; internal set; } + + public override string ToString() => Title; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs b/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs index c59473704..07ffdd52c 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedAuthor.cs @@ -1,9 +1,8 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; namespace Discord { - [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public struct EmbedAuthor { public string Name { get; internal set; } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs index 04f4f6884..30c694bfd 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedBuilder.cs @@ -7,17 +7,16 @@ namespace Discord { public class EmbedBuilder { - private string _title; - private string _description; - private string _url; - private EmbedImage? _image; - private EmbedThumbnail? _thumbnail; - private List _fields; - public const int MaxFieldCount = 25; public const int MaxTitleLength = 256; public const int MaxDescriptionLength = 2048; public const int MaxEmbedLength = 6000; + private string _description; + private List _fields; + private EmbedImage? _image; + private EmbedThumbnail? _thumbnail; + private string _title; + private string _url; public EmbedBuilder() { @@ -29,16 +28,22 @@ namespace Discord get => _title; set { - if (value?.Length > MaxTitleLength) throw new ArgumentException($"Title length must be less than or equal to {MaxTitleLength}.", nameof(Title)); + if (value?.Length > MaxTitleLength) + throw new ArgumentException($"Title length must be less than or equal to {MaxTitleLength}.", + nameof(Title)); _title = value; } } + public string Description { get => _description; set { - if (value?.Length > MaxDescriptionLength) throw new ArgumentException($"Description length must be less than or equal to {MaxDescriptionLength}.", nameof(Description)); + if (value?.Length > MaxDescriptionLength) + throw new ArgumentException( + $"Description length must be less than or equal to {MaxDescriptionLength}.", + nameof(Description)); _description = value; } } @@ -52,31 +57,40 @@ namespace Discord _url = value; } } + public string ThumbnailUrl { get => _thumbnail?.Url; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl)); + if (!value.IsNullOrUri()) + throw new ArgumentException("Url must be a well-formed URI", nameof(ThumbnailUrl)); _thumbnail = new EmbedThumbnail(value, null, null, null); } } + public string ImageUrl { get => _image?.Url; set { - if (!value.IsNullOrUri()) throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl)); + if (!value.IsNullOrUri()) + throw new ArgumentException("Url must be a well-formed URI", nameof(ImageUrl)); _image = new EmbedImage(value, null, null, null); } } + public List Fields { get => _fields; set { - if (value == null) throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", nameof(Fields)); - if (value.Count > MaxFieldCount) throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(Fields)); + if (value == null) + throw new ArgumentNullException("Cannot set an embed builder's fields collection to null", + nameof(Fields)); + if (value.Count > MaxFieldCount) + throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", + nameof(Fields)); _fields = value; } } @@ -90,11 +104,11 @@ namespace Discord { get { - int titleLength = Title?.Length ?? 0; - int authorLength = Author?.Name?.Length ?? 0; - int descriptionLength = Description?.Length ?? 0; - int footerLength = Footer?.Text?.Length ?? 0; - int fieldSum = Fields.Sum(f => f.Name.Length + f.Value.ToString().Length); + var titleLength = Title?.Length ?? 0; + var authorLength = Author?.Name?.Length ?? 0; + var descriptionLength = Description?.Length ?? 0; + var footerLength = Footer?.Text?.Length ?? 0; + var fieldSum = Fields.Sum(f => f.Name.Length + f.Value.ToString().Length); return titleLength + authorLength + descriptionLength + footerLength + fieldSum; } @@ -105,36 +119,43 @@ namespace Discord Title = title; return this; } + public EmbedBuilder WithDescription(string description) { Description = description; return this; } + public EmbedBuilder WithUrl(string url) { Url = url; return this; } + public EmbedBuilder WithThumbnailUrl(string thumbnailUrl) { ThumbnailUrl = thumbnailUrl; return this; } + public EmbedBuilder WithImageUrl(string imageUrl) { ImageUrl = imageUrl; return this; } + public EmbedBuilder WithCurrentTimestamp() { Timestamp = DateTimeOffset.UtcNow; return this; } + public EmbedBuilder WithTimestamp(DateTimeOffset dateTimeOffset) { Timestamp = dateTimeOffset; return this; } + public EmbedBuilder WithColor(Color color) { Color = color; @@ -146,6 +167,7 @@ namespace Discord Author = author; return this; } + public EmbedBuilder WithAuthor(Action action) { var author = new EmbedAuthorBuilder(); @@ -153,6 +175,7 @@ namespace Discord Author = author; return this; } + public EmbedBuilder WithAuthor(string name, string iconUrl = null, string url = null) { var author = new EmbedAuthorBuilder @@ -164,11 +187,13 @@ namespace Discord Author = author; return this; } + public EmbedBuilder WithFooter(EmbedFooterBuilder footer) { Footer = footer; return this; } + public EmbedBuilder WithFooter(Action action) { var footer = new EmbedFooterBuilder(); @@ -176,6 +201,7 @@ namespace Discord Footer = footer; return this; } + public EmbedBuilder WithFooter(string text, string iconUrl = null) { var footer = new EmbedFooterBuilder @@ -196,16 +222,17 @@ namespace Discord AddField(field); return this; } + public EmbedBuilder AddField(EmbedFieldBuilder field) { if (Fields.Count >= MaxFieldCount) - { - throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", nameof(field)); - } + throw new ArgumentException($"Field count must be less than or equal to {MaxFieldCount}.", + nameof(field)); Fields.Add(field); return this; } + public EmbedBuilder AddField(Action action) { var field = new EmbedFieldBuilder(); @@ -217,30 +244,36 @@ namespace Discord public Embed Build() { if (Length > MaxEmbedLength) - throw new InvalidOperationException($"Total embed length must be less than or equal to {MaxEmbedLength}"); + throw new InvalidOperationException( + $"Total embed length must be less than or equal to {MaxEmbedLength}"); var fields = ImmutableArray.CreateBuilder(Fields.Count); - for (int i = 0; i < Fields.Count; i++) + for (var i = 0; i < Fields.Count; i++) fields.Add(Fields[i].Build()); - return new Embed(EmbedType.Rich, Title, Description, Url, Timestamp, Color, _image, null, Author?.Build(), Footer?.Build(), null, _thumbnail, fields.ToImmutable()); + return new Embed(EmbedType.Rich, Title, Description, Url, Timestamp, Color, _image, null, Author?.Build(), + Footer?.Build(), null, _thumbnail, fields.ToImmutable()); } } public class EmbedFieldBuilder { - private string _name; - private string _value; public const int MaxFieldNameLength = 256; public const int MaxFieldValueLength = 1024; + private string _name; + private string _value; public string Name { get => _name; set { - if (string.IsNullOrWhiteSpace(value)) throw new ArgumentException($"Field name must not be null, empty or entirely whitespace.", nameof(Name)); - if (value.Length > MaxFieldNameLength) throw new ArgumentException($"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name)); + if (string.IsNullOrWhiteSpace(value)) + throw new ArgumentException("Field name must not be null, empty or entirely whitespace.", + nameof(Name)); + if (value.Length > MaxFieldNameLength) + throw new ArgumentException( + $"Field name length must be less than or equal to {MaxFieldNameLength}.", nameof(Name)); _name = value; } } @@ -251,11 +284,15 @@ namespace Discord set { var stringValue = value?.ToString(); - if (string.IsNullOrEmpty(stringValue)) throw new ArgumentException($"Field value must not be null or empty.", nameof(Value)); - if (stringValue.Length > MaxFieldValueLength) throw new ArgumentException($"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); + if (string.IsNullOrEmpty(stringValue)) + throw new ArgumentException("Field value must not be null or empty.", nameof(Value)); + if (stringValue.Length > MaxFieldValueLength) + throw new ArgumentException( + $"Field value length must be less than or equal to {MaxFieldValueLength}.", nameof(Value)); _value = stringValue; } } + public bool IsInline { get; set; } public EmbedFieldBuilder WithName(string name) @@ -263,11 +300,13 @@ namespace Discord Name = name; return this; } + public EmbedFieldBuilder WithValue(object value) { Value = value; return this; } + public EmbedFieldBuilder WithIsInline(bool isInline) { IsInline = isInline; @@ -280,20 +319,23 @@ namespace Discord public class EmbedAuthorBuilder { + public const int MaxAuthorNameLength = 256; + private string _iconUrl; private string _name; private string _url; - private string _iconUrl; - public const int MaxAuthorNameLength = 256; public string Name { get => _name; set { - if (value?.Length > MaxAuthorNameLength) throw new ArgumentException($"Author name length must be less than or equal to {MaxAuthorNameLength}.", nameof(Name)); + if (value?.Length > MaxAuthorNameLength) + throw new ArgumentException( + $"Author name length must be less than or equal to {MaxAuthorNameLength}.", nameof(Name)); _name = value; } } + public string Url { get => _url; @@ -303,6 +345,7 @@ namespace Discord _url = value; } } + public string IconUrl { get => _iconUrl; @@ -318,11 +361,13 @@ namespace Discord Name = name; return this; } + public EmbedAuthorBuilder WithUrl(string url) { Url = url; return this; } + public EmbedAuthorBuilder WithIconUrl(string iconUrl) { IconUrl = iconUrl; @@ -335,20 +380,22 @@ namespace Discord public class EmbedFooterBuilder { - private string _text; - private string _iconUrl; - public const int MaxFooterTextLength = 2048; + private string _iconUrl; + private string _text; public string Text { get => _text; set { - if (value?.Length > MaxFooterTextLength) throw new ArgumentException($"Footer text length must be less than or equal to {MaxFooterTextLength}.", nameof(Text)); + if (value?.Length > MaxFooterTextLength) + throw new ArgumentException( + $"Footer text length must be less than or equal to {MaxFooterTextLength}.", nameof(Text)); _text = value; } } + public string IconUrl { get => _iconUrl; @@ -364,6 +411,7 @@ namespace Discord Text = text; return this; } + public EmbedFooterBuilder WithIconUrl(string iconUrl) { IconUrl = iconUrl; diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedField.cs b/src/Discord.Net.Core/Entities/Messages/EmbedField.cs index f7c1f8348..d74f61818 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedField.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedField.cs @@ -2,7 +2,7 @@ namespace Discord { - [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public struct EmbedField { public string Name { get; internal set; } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs b/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs index 29d85cd90..a15235939 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedFooter.cs @@ -1,9 +1,8 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; namespace Discord { - [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public struct EmbedFooter { public string Text { get; internal set; } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs index f21d42c0c..39b199afc 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedImage.cs @@ -1,9 +1,8 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; namespace Discord { - [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public struct EmbedImage { public string Url { get; } @@ -20,6 +19,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url.ToString(); + public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs index 24722b158..9848e35cf 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedProvider.cs @@ -1,9 +1,8 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; namespace Discord { - [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public struct EmbedProvider { public string Name { get; } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs index 209a93e37..8f8ea4bff 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedThumbnail.cs @@ -1,9 +1,8 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; namespace Discord { - [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public struct EmbedThumbnail { public string Url { get; } @@ -20,6 +19,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url.ToString(); + public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedType.cs b/src/Discord.Net.Core/Entities/Messages/EmbedType.cs index 5bb2653e2..4e3abde28 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedType.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedType.cs @@ -10,6 +10,6 @@ namespace Discord Gifv, Article, Tweet, - Html, + Html } } diff --git a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs index f00681d89..5bc23cd2a 100644 --- a/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs +++ b/src/Discord.Net.Core/Entities/Messages/EmbedVideo.cs @@ -1,9 +1,8 @@ -using System; -using System.Diagnostics; +using System.Diagnostics; namespace Discord { - [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public struct EmbedVideo { public string Url { get; } @@ -18,6 +17,6 @@ namespace Discord } private string DebuggerDisplay => $"{Url} ({(Width != null && Height != null ? $"{Width}x{Height}" : "0x0")})"; - public override string ToString() => Url.ToString(); + public override string ToString() => Url; } } diff --git a/src/Discord.Net.Core/Entities/Messages/IMessage.cs b/src/Discord.Net.Core/Entities/Messages/IMessage.cs index 4266f893a..abb266d88 100644 --- a/src/Discord.Net.Core/Entities/Messages/IMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IMessage.cs @@ -7,34 +7,46 @@ namespace Discord { /// Gets the type of this system message. MessageType Type { get; } + /// Gets the source of this message. MessageSource Source { get; } + /// Returns true if this message was sent as a text-to-speech message. bool IsTTS { get; } + /// Returns true if this message was added to its channel's pinned messages. bool IsPinned { get; } + /// Returns the content for this message. string Content { get; } + /// Gets the time this message was sent. DateTimeOffset Timestamp { get; } + /// Gets the time of this message's last edit, if any. DateTimeOffset? EditedTimestamp { get; } - + /// Gets the channel this message was sent to. IMessageChannel Channel { get; } + /// Gets the author of this message. IUser Author { get; } /// Returns all attachments included in this message. IReadOnlyCollection Attachments { get; } + /// Returns all embeds included in this message. IReadOnlyCollection Embeds { get; } + /// Returns all tags included in this message's content. IReadOnlyCollection Tags { get; } + /// Returns the ids of channels mentioned in this message. IReadOnlyCollection MentionedChannelIds { get; } + /// Returns the ids of roles mentioned in this message. IReadOnlyCollection MentionedRoleIds { get; } + /// Returns the ids of users mentioned in this message. IReadOnlyCollection MentionedUserIds { get; } } diff --git a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs index 36ee725ff..954bcea56 100644 --- a/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs +++ b/src/Discord.Net.Core/Entities/Messages/IUserMessage.cs @@ -6,24 +6,30 @@ namespace Discord { public interface IUserMessage : IMessage { + /// Returns all reactions included in this message. + IReadOnlyDictionary Reactions { get; } + /// Modifies this message. Task ModifyAsync(Action func, RequestOptions options = null); + /// Adds this message to its channel's pinned messages. Task PinAsync(RequestOptions options = null); + /// Removes this message from its channel's pinned messages. Task UnpinAsync(RequestOptions options = null); - /// Returns all reactions included in this message. - IReadOnlyDictionary Reactions { get; } - /// Adds a reaction to this message. Task AddReactionAsync(IEmote emote, RequestOptions options = null); + /// Removes a reaction from message. Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null); + /// Removes all reactions from this message. Task RemoveAllReactionsAsync(RequestOptions options = null); + /// Gets all users that reacted to a message with a given emote - IAsyncEnumerable> GetReactionUsersAsync(IEmote emoji, int limit, RequestOptions options = null); + IAsyncEnumerable> GetReactionUsersAsync(IEmote emoji, int limit, + RequestOptions options = null); /// Transforms this message's text into a human readable form by resolving its tags. string Resolve( diff --git a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs index b3f3a9c89..a3a541258 100644 --- a/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs +++ b/src/Discord.Net.Core/Entities/Messages/MessageProperties.cs @@ -1,13 +1,13 @@ namespace Discord { /// - /// Modify a message with the specified parameters. + /// Modify a message with the specified parameters. /// /// - /// The content of a message can be cleared with String.Empty; if and only if an Embed is present. + /// The content of a message can be cleared with String.Empty; if and only if an Embed is present. /// /// - /// + /// /// var message = await ReplyAsync("abc"); /// await message.ModifyAsync(x => /// { @@ -23,14 +23,15 @@ public class MessageProperties { /// - /// The content of the message + /// The content of the message /// /// - /// This must be less than 2000 characters. + /// This must be less than 2000 characters. /// public Optional Content { get; set; } + /// - /// The embed the message should display + /// The embed the message should display /// public Optional Embed { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Messages/Tag.cs b/src/Discord.Net.Core/Entities/Messages/Tag.cs index 06d995e73..f08e3dc44 100644 --- a/src/Discord.Net.Core/Entities/Messages/Tag.cs +++ b/src/Discord.Net.Core/Entities/Messages/Tag.cs @@ -2,15 +2,9 @@ namespace Discord { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class Tag : ITag { - public TagType Type { get; } - public int Index { get; } - public int Length { get; } - public ulong Key { get; } - public T Value { get; } - internal Tag(TagType type, int index, int length, ulong key, T value) { Type = type; @@ -20,9 +14,15 @@ namespace Discord Value = value; } + public T Value { get; } + private string DebuggerDisplay => $"{Value?.ToString() ?? "null"} ({Type})"; - public override string ToString() => $"{Value?.ToString() ?? "null"} ({Type})"; + public TagType Type { get; } + public int Index { get; } + public int Length { get; } + public ulong Key { get; } object ITag.Value => Value; + public override string ToString() => $"{Value?.ToString() ?? "null"} ({Type})"; } } diff --git a/src/Discord.Net.Core/Entities/Messages/TagHandling.cs b/src/Discord.Net.Core/Entities/Messages/TagHandling.cs index 492f05879..e42345bfa 100644 --- a/src/Discord.Net.Core/Entities/Messages/TagHandling.cs +++ b/src/Discord.Net.Core/Entities/Messages/TagHandling.cs @@ -2,12 +2,12 @@ { public enum TagHandling { - Ignore = 0, //<@53905483156684800> -> <@53905483156684800> - Remove, //<@53905483156684800> -> - Name, //<@53905483156684800> -> @Voltana - NameNoPrefix, //<@53905483156684800> -> Voltana - FullName, //<@53905483156684800> -> @Voltana#8252 - FullNameNoPrefix, //<@53905483156684800> -> Voltana#8252 - Sanitize //<@53905483156684800> -> <@53905483156684800> (w/ nbsp) + Ignore = 0, //<@53905483156684800> -> <@53905483156684800> + Remove, //<@53905483156684800> -> + Name, //<@53905483156684800> -> @Voltana + NameNoPrefix, //<@53905483156684800> -> Voltana + FullName, //<@53905483156684800> -> @Voltana#8252 + FullNameNoPrefix, //<@53905483156684800> -> Voltana#8252 + Sanitize //<@53905483156684800> -> <@53905483156684800> (w/ nbsp) } } diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs index 0fbd22c4e..f2cf81391 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermission.cs @@ -2,38 +2,37 @@ using System; namespace Discord { - [FlagsAttribute] + [Flags] public enum ChannelPermission : ulong { // General CreateInstantInvite = 0x00_00_00_01, - ManageChannels = 0x00_00_00_10, + ManageChannels = 0x00_00_00_10, // Text - AddReactions = 0x00_00_00_40, - [Obsolete("Use ViewChannel instead.")] - ReadMessages = ViewChannel, - ViewChannel = 0x00_00_04_00, - SendMessages = 0x00_00_08_00, - SendTTSMessages = 0x00_00_10_00, - ManageMessages = 0x00_00_20_00, - EmbedLinks = 0x00_00_40_00, - AttachFiles = 0x00_00_80_00, - ReadMessageHistory = 0x00_01_00_00, - MentionEveryone = 0x00_02_00_00, - UseExternalEmojis = 0x00_04_00_00, + AddReactions = 0x00_00_00_40, + [Obsolete("Use ViewChannel instead.")] ReadMessages = ViewChannel, + ViewChannel = 0x00_00_04_00, + SendMessages = 0x00_00_08_00, + SendTTSMessages = 0x00_00_10_00, + ManageMessages = 0x00_00_20_00, + EmbedLinks = 0x00_00_40_00, + AttachFiles = 0x00_00_80_00, + ReadMessageHistory = 0x00_01_00_00, + MentionEveryone = 0x00_02_00_00, + UseExternalEmojis = 0x00_04_00_00, // Voice - Connect = 0x00_10_00_00, - Speak = 0x00_20_00_00, - MuteMembers = 0x00_40_00_00, - DeafenMembers = 0x00_80_00_00, - MoveMembers = 0x01_00_00_00, - UseVAD = 0x02_00_00_00, - PrioritySpeaker = 0x00_00_01_00, + Connect = 0x00_10_00_00, + Speak = 0x00_20_00_00, + MuteMembers = 0x00_40_00_00, + DeafenMembers = 0x00_80_00_00, + MoveMembers = 0x01_00_00_00, + UseVAD = 0x02_00_00_00, + PrioritySpeaker = 0x00_00_01_00, // More General - ManageRoles = 0x10_00_00_00, - ManageWebhooks = 0x20_00_00_00, + ManageRoles = 0x10_00_00_00, + ManageWebhooks = 0x20_00_00_00 } } diff --git a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs index 61d588f8a..7ab5c9f5d 100644 --- a/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/ChannelPermissions.cs @@ -4,21 +4,28 @@ using System.Diagnostics; namespace Discord { - [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public struct ChannelPermissions { /// Gets a blank ChannelPermissions that grants no permissions. public static readonly ChannelPermissions None = new ChannelPermissions(); + /// Gets a ChannelPermissions that grants all permissions for text channels. public static readonly ChannelPermissions Text = new ChannelPermissions(0b01100_0000000_1111111110001_010001); + /// Gets a ChannelPermissions that grants all permissions for voice channels. public static readonly ChannelPermissions Voice = new ChannelPermissions(0b00100_1111110_0000000010100_010001); + /// Gets a ChannelPermissions that grants all permissions for category channels. - public static readonly ChannelPermissions Category = new ChannelPermissions(0b01100_1111110_1111111110001_010001); + public static readonly ChannelPermissions Category = + new ChannelPermissions(0b01100_1111110_1111111110001_010001); + /// Gets a ChannelPermissions that grants all permissions for direct message channels. public static readonly ChannelPermissions DM = new ChannelPermissions(0b00000_1000110_1011100110000_000000); + /// Gets a ChannelPermissions that grants all permissions for group channels. public static readonly ChannelPermissions Group = new ChannelPermissions(0b00000_1000110_0001101100000_000000); + /// Gets a ChannelPermissions that grants all permissions for a given channelType. public static ChannelPermissions All(IChannel channel) { @@ -38,56 +45,76 @@ namespace Discord /// If True, a user may create invites. public bool CreateInstantInvite => Permissions.GetValue(RawValue, ChannelPermission.CreateInstantInvite); + /// If True, a user may create, delete and modify this channel. public bool ManageChannel => Permissions.GetValue(RawValue, ChannelPermission.ManageChannels); /// If true, a user may add reactions. public bool AddReactions => Permissions.GetValue(RawValue, ChannelPermission.AddReactions); + /// If True, a user may join channels. [Obsolete("Use ViewChannel instead.")] public bool ReadMessages => ViewChannel; + /// If True, a user may view channels. public bool ViewChannel => Permissions.GetValue(RawValue, ChannelPermission.ViewChannel); /// If True, a user may send messages. public bool SendMessages => Permissions.GetValue(RawValue, ChannelPermission.SendMessages); + /// If True, a user may send text-to-speech messages. public bool SendTTSMessages => Permissions.GetValue(RawValue, ChannelPermission.SendTTSMessages); + /// If True, a user may delete messages. public bool ManageMessages => Permissions.GetValue(RawValue, ChannelPermission.ManageMessages); + /// If True, Discord will auto-embed links sent by this user. public bool EmbedLinks => Permissions.GetValue(RawValue, ChannelPermission.EmbedLinks); + /// If True, a user may send files. public bool AttachFiles => Permissions.GetValue(RawValue, ChannelPermission.AttachFiles); + /// If True, a user may read previous messages. public bool ReadMessageHistory => Permissions.GetValue(RawValue, ChannelPermission.ReadMessageHistory); + /// If True, a user may mention @everyone. public bool MentionEveryone => Permissions.GetValue(RawValue, ChannelPermission.MentionEveryone); + /// If True, a user may use custom emoji from other guilds. public bool UseExternalEmojis => Permissions.GetValue(RawValue, ChannelPermission.UseExternalEmojis); /// If True, a user may connect to a voice channel. public bool Connect => Permissions.GetValue(RawValue, ChannelPermission.Connect); + /// If True, a user may speak in a voice channel. public bool Speak => Permissions.GetValue(RawValue, ChannelPermission.Speak); + /// If True, a user may mute users. public bool MuteMembers => Permissions.GetValue(RawValue, ChannelPermission.MuteMembers); + /// If True, a user may deafen users. public bool DeafenMembers => Permissions.GetValue(RawValue, ChannelPermission.DeafenMembers); + /// If True, a user may move other users between voice channels. public bool MoveMembers => Permissions.GetValue(RawValue, ChannelPermission.MoveMembers); + /// If True, a user may use voice-activity-detection rather than push-to-talk. public bool UseVAD => Permissions.GetValue(RawValue, ChannelPermission.UseVAD); + /// If True, a user may use priority speaker in a voice channel. public bool PrioritySpeaker => Permissions.GetValue(RawValue, ChannelPermission.PrioritySpeaker); /// If True, a user may adjust role permissions. This also implictly grants all other permissions. public bool ManageRoles => Permissions.GetValue(RawValue, ChannelPermission.ManageRoles); + /// If True, a user may edit the webhooks for this channel. public bool ManageWebhooks => Permissions.GetValue(RawValue, ChannelPermission.ManageWebhooks); /// Creates a new ChannelPermissions with the provided packed value. - public ChannelPermissions(ulong rawValue) { RawValue = rawValue; } + public ChannelPermissions(ulong rawValue) + { + RawValue = rawValue; + } private ChannelPermissions(ulong initialValue, bool? createInstantInvite = null, @@ -112,7 +139,7 @@ namespace Discord bool? manageRoles = null, bool? manageWebhooks = null) { - ulong value = initialValue; + var value = initialValue; Permissions.SetValue(ref value, createInstantInvite, ChannelPermission.CreateInstantInvite); Permissions.SetValue(ref value, manageChannel, ChannelPermission.ManageChannels); @@ -162,10 +189,13 @@ namespace Discord bool prioritySpeaker = false, bool manageRoles = false, bool manageWebhooks = false) - : this(0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, + : this(0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, + manageMessages, embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, - speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, manageRoles, manageWebhooks) - { } + speak, muteMembers, deafenMembers, moveMembers, useVoiceActivation, prioritySpeaker, manageRoles, + manageWebhooks) + { + } /// Creates a new ChannelPermissions from this one, changing the provided non-null permissions. public ChannelPermissions Modify( @@ -220,10 +250,11 @@ namespace Discord var perms = new List(); for (byte i = 0; i < Permissions.MaxBits; i++) { - ulong flag = ((ulong)1 << i); + var flag = (ulong)1 << i; if ((RawValue & flag) != 0) perms.Add((ChannelPermission)flag); } + return perms; } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs index 13a9e32b1..4f4c43be3 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs @@ -2,46 +2,45 @@ using System; namespace Discord { - [FlagsAttribute] + [Flags] public enum GuildPermission : ulong { // General CreateInstantInvite = 0x00_00_00_01, - KickMembers = 0x00_00_00_02, - BanMembers = 0x00_00_00_04, - Administrator = 0x00_00_00_08, - ManageChannels = 0x00_00_00_10, - ManageGuild = 0x00_00_00_20, + KickMembers = 0x00_00_00_02, + BanMembers = 0x00_00_00_04, + Administrator = 0x00_00_00_08, + ManageChannels = 0x00_00_00_10, + ManageGuild = 0x00_00_00_20, // Text - AddReactions = 0x00_00_00_40, - ViewAuditLog = 0x00_00_00_80, - [Obsolete("Use ViewChannel instead.")] - ReadMessages = ViewChannel, - ViewChannel = 0x00_00_04_00, - SendMessages = 0x00_00_08_00, - SendTTSMessages = 0x00_00_10_00, - ManageMessages = 0x00_00_20_00, - EmbedLinks = 0x00_00_40_00, - AttachFiles = 0x00_00_80_00, - ReadMessageHistory = 0x00_01_00_00, - MentionEveryone = 0x00_02_00_00, - UseExternalEmojis = 0x00_04_00_00, + AddReactions = 0x00_00_00_40, + ViewAuditLog = 0x00_00_00_80, + [Obsolete("Use ViewChannel instead.")] ReadMessages = ViewChannel, + ViewChannel = 0x00_00_04_00, + SendMessages = 0x00_00_08_00, + SendTTSMessages = 0x00_00_10_00, + ManageMessages = 0x00_00_20_00, + EmbedLinks = 0x00_00_40_00, + AttachFiles = 0x00_00_80_00, + ReadMessageHistory = 0x00_01_00_00, + MentionEveryone = 0x00_02_00_00, + UseExternalEmojis = 0x00_04_00_00, // Voice - Connect = 0x00_10_00_00, - Speak = 0x00_20_00_00, - MuteMembers = 0x00_40_00_00, - DeafenMembers = 0x00_80_00_00, - MoveMembers = 0x01_00_00_00, - UseVAD = 0x02_00_00_00, - PrioritySpeaker = 0x00_00_01_00, + Connect = 0x00_10_00_00, + Speak = 0x00_20_00_00, + MuteMembers = 0x00_40_00_00, + DeafenMembers = 0x00_80_00_00, + MoveMembers = 0x01_00_00_00, + UseVAD = 0x02_00_00_00, + PrioritySpeaker = 0x00_00_01_00, // General 2 - ChangeNickname = 0x04_00_00_00, - ManageNicknames = 0x08_00_00_00, - ManageRoles = 0x10_00_00_00, - ManageWebhooks = 0x20_00_00_00, - ManageEmojis = 0x40_00_00_00 + ChangeNickname = 0x04_00_00_00, + ManageNicknames = 0x08_00_00_00, + ManageRoles = 0x10_00_00_00, + ManageWebhooks = 0x20_00_00_00, + ManageEmojis = 0x40_00_00_00 } } diff --git a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs index c9cb90ec8..e8a1834f9 100644 --- a/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs @@ -4,13 +4,15 @@ using System.Diagnostics; namespace Discord { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public struct GuildPermissions { /// Gets a blank GuildPermissions that grants no permissions. public static readonly GuildPermissions None = new GuildPermissions(); + /// Gets a GuildPermissions that grants all guild permissions for webhook users. public static readonly GuildPermissions Webhook = new GuildPermissions(0b00000_0000000_0001101100000_000000); + /// Gets a GuildPermissions that grants all guild permissions. public static readonly GuildPermissions All = new GuildPermissions(0b11111_1111110_1111111110111_111111); @@ -19,72 +21,100 @@ namespace Discord /// If True, a user may create invites. public bool CreateInstantInvite => Permissions.GetValue(RawValue, GuildPermission.CreateInstantInvite); + /// If True, a user may ban users from the guild. public bool BanMembers => Permissions.GetValue(RawValue, GuildPermission.BanMembers); + /// If True, a user may kick users from the guild. public bool KickMembers => Permissions.GetValue(RawValue, GuildPermission.KickMembers); + /// If True, a user is granted all permissions, and cannot have them revoked via channel permissions. public bool Administrator => Permissions.GetValue(RawValue, GuildPermission.Administrator); + /// If True, a user may create, delete and modify channels. public bool ManageChannels => Permissions.GetValue(RawValue, GuildPermission.ManageChannels); + /// If True, a user may adjust guild properties. public bool ManageGuild => Permissions.GetValue(RawValue, GuildPermission.ManageGuild); /// If true, a user may add reactions. public bool AddReactions => Permissions.GetValue(RawValue, GuildPermission.AddReactions); + /// If true, a user may view the audit log. public bool ViewAuditLog => Permissions.GetValue(RawValue, GuildPermission.ViewAuditLog); /// If True, a user may join channels. [Obsolete("Use ViewChannel instead.")] public bool ReadMessages => ViewChannel; + /// If True, a user may view channels. public bool ViewChannel => Permissions.GetValue(RawValue, GuildPermission.ViewChannel); + /// If True, a user may send messages. public bool SendMessages => Permissions.GetValue(RawValue, GuildPermission.SendMessages); + /// If True, a user may send text-to-speech messages. public bool SendTTSMessages => Permissions.GetValue(RawValue, GuildPermission.SendTTSMessages); + /// If True, a user may delete messages. public bool ManageMessages => Permissions.GetValue(RawValue, GuildPermission.ManageMessages); + /// If True, Discord will auto-embed links sent by this user. public bool EmbedLinks => Permissions.GetValue(RawValue, GuildPermission.EmbedLinks); + /// If True, a user may send files. public bool AttachFiles => Permissions.GetValue(RawValue, GuildPermission.AttachFiles); + /// If True, a user may read previous messages. public bool ReadMessageHistory => Permissions.GetValue(RawValue, GuildPermission.ReadMessageHistory); + /// If True, a user may mention @everyone. public bool MentionEveryone => Permissions.GetValue(RawValue, GuildPermission.MentionEveryone); + /// If True, a user may use custom emoji from other guilds. public bool UseExternalEmojis => Permissions.GetValue(RawValue, GuildPermission.UseExternalEmojis); /// If True, a user may connect to a voice channel. public bool Connect => Permissions.GetValue(RawValue, GuildPermission.Connect); + /// If True, a user may speak in a voice channel. public bool Speak => Permissions.GetValue(RawValue, GuildPermission.Speak); + /// If True, a user may mute users. public bool MuteMembers => Permissions.GetValue(RawValue, GuildPermission.MuteMembers); + /// If True, a user may deafen users. public bool DeafenMembers => Permissions.GetValue(RawValue, GuildPermission.DeafenMembers); + /// If True, a user may move other users between voice channels. public bool MoveMembers => Permissions.GetValue(RawValue, GuildPermission.MoveMembers); + /// If True, a user may use voice-activity-detection rather than push-to-talk. public bool UseVAD => Permissions.GetValue(RawValue, GuildPermission.UseVAD); + /// If True, a user may use priority speaker in a voice channel. public bool PrioritySpeaker => Permissions.GetValue(RawValue, ChannelPermission.PrioritySpeaker); /// If True, a user may change their own nickname. public bool ChangeNickname => Permissions.GetValue(RawValue, GuildPermission.ChangeNickname); + /// If True, a user may change the nickname of other users. public bool ManageNicknames => Permissions.GetValue(RawValue, GuildPermission.ManageNicknames); + /// If True, a user may adjust roles. public bool ManageRoles => Permissions.GetValue(RawValue, GuildPermission.ManageRoles); + /// If True, a user may edit the webhooks for this guild. public bool ManageWebhooks => Permissions.GetValue(RawValue, GuildPermission.ManageWebhooks); + /// If True, a user may edit the emojis for this guild. public bool ManageEmojis => Permissions.GetValue(RawValue, GuildPermission.ManageEmojis); /// Creates a new GuildPermissions with the provided packed value. - public GuildPermissions(ulong rawValue) { RawValue = rawValue; } + public GuildPermissions(ulong rawValue) + { + RawValue = rawValue; + } private GuildPermissions(ulong initialValue, bool? createInstantInvite = null, @@ -117,7 +147,7 @@ namespace Discord bool? manageWebhooks = null, bool? manageEmojis = null) { - ulong value = initialValue; + var value = initialValue; Permissions.SetValue(ref value, createInstantInvite, GuildPermission.CreateInstantInvite); Permissions.SetValue(ref value, banMembers, GuildPermission.BanMembers); @@ -184,7 +214,7 @@ namespace Discord bool manageWebhooks = false, bool manageEmojis = false) : this(0, - createInstantInvite: createInstantInvite, + createInstantInvite, manageRoles: manageRoles, kickMembers: kickMembers, banMembers: banMembers, @@ -213,7 +243,8 @@ namespace Discord manageNicknames: manageNicknames, manageWebhooks: manageWebhooks, manageEmojis: manageEmojis) - { } + { + } /// Creates a new GuildPermissions from this one, changing the provided non-null permissions. public GuildPermissions Modify( @@ -246,10 +277,13 @@ namespace Discord bool? manageRoles = null, bool? manageWebhooks = null, bool? manageEmojis = null) - => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions, + => new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, + manageChannels, manageGuild, addReactions, viewAuditLog, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles, - readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers, - useVoiceActivation, prioritySpeaker, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojis); + readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, + moveMembers, + useVoiceActivation, prioritySpeaker, changeNickname, manageNicknames, manageRoles, manageWebhooks, + manageEmojis); public bool Has(GuildPermission permission) => Permissions.GetValue(RawValue, permission); @@ -261,10 +295,11 @@ namespace Discord // each of the GuildPermissions increments by 2^i from 0 to MaxBits for (byte i = 0; i < Permissions.MaxBits; i++) { - ulong flag = ((ulong)1 << i); + var flag = (ulong)1 << i; if ((RawValue & flag) != 0) perms.Add((GuildPermission)flag); } + return perms; } diff --git a/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs b/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs index bda67a870..335158644 100644 --- a/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs +++ b/src/Discord.Net.Core/Entities/Permissions/Overwrite.cs @@ -4,8 +4,10 @@ { /// Gets the unique identifier for the object this overwrite is targeting. public ulong TargetId { get; } + /// Gets the type of object this overwrite is targeting. public PermissionTarget TargetType { get; } + /// Gets the permissions associated with this overwrite entry. public OverwritePermissions Permissions { get; } diff --git a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs index b8b4b83e2..f58a74ca7 100644 --- a/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs +++ b/src/Discord.Net.Core/Entities/Permissions/OverwritePermissions.cs @@ -4,68 +4,96 @@ using System.Diagnostics; namespace Discord { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public struct OverwritePermissions { /// Gets a blank OverwritePermissions that inherits all permissions. public static OverwritePermissions InheritAll { get; } = new OverwritePermissions(); + /// Gets a OverwritePermissions that grants all permissions for a given channelType. - public static OverwritePermissions AllowAll(IChannel channel) + public static OverwritePermissions AllowAll(IChannel channel) => new OverwritePermissions(ChannelPermissions.All(channel).RawValue, 0); + /// Gets a OverwritePermissions that denies all permissions for a given channelType. public static OverwritePermissions DenyAll(IChannel channel) => new OverwritePermissions(0, ChannelPermissions.All(channel).RawValue); /// Gets a packed value representing all the allowed permissions in this OverwritePermissions. public ulong AllowValue { get; } + /// Gets a packed value representing all the denied permissions in this OverwritePermissions. public ulong DenyValue { get; } /// If Allowed, a user may create invites. - public PermValue CreateInstantInvite => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.CreateInstantInvite); + public PermValue CreateInstantInvite => + Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.CreateInstantInvite); + /// If Allowed, a user may create, delete and modify this channel. public PermValue ManageChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageChannels); + /// If Allowed, a user may add reactions. public PermValue AddReactions => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AddReactions); + /// If Allowed, a user may join channels. [Obsolete("Use ViewChannel instead.")] public PermValue ReadMessages => ViewChannel; + /// If Allowed, a user may join channels. public PermValue ViewChannel => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ViewChannel); + /// If Allowed, a user may send messages. public PermValue SendMessages => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.SendMessages); + /// If Allowed, a user may send text-to-speech messages. - public PermValue SendTTSMessages => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.SendTTSMessages); + public PermValue SendTTSMessages => + Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.SendTTSMessages); + /// If Allowed, a user may delete messages. - public PermValue ManageMessages => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageMessages); + public PermValue ManageMessages => + Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageMessages); + /// If Allowed, Discord will auto-embed links sent by this user. public PermValue EmbedLinks => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.EmbedLinks); + /// If Allowed, a user may send files. public PermValue AttachFiles => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.AttachFiles); + /// If Allowed, a user may read previous messages. - public PermValue ReadMessageHistory => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ReadMessageHistory); + public PermValue ReadMessageHistory => + Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ReadMessageHistory); + /// If Allowed, a user may mention @everyone. - public PermValue MentionEveryone => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.MentionEveryone); + public PermValue MentionEveryone => + Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.MentionEveryone); + /// If Allowed, a user may use custom emoji from other guilds. - public PermValue UseExternalEmojis => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseExternalEmojis); + public PermValue UseExternalEmojis => + Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseExternalEmojis); /// If Allowed, a user may connect to a voice channel. public PermValue Connect => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.Connect); + /// If Allowed, a user may speak in a voice channel. public PermValue Speak => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.Speak); + /// If Allowed, a user may mute users. public PermValue MuteMembers => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.MuteMembers); + /// If Allowed, a user may deafen users. public PermValue DeafenMembers => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.DeafenMembers); + /// If Allowed, a user may move other users between voice channels. public PermValue MoveMembers => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.MoveMembers); + /// If Allowed, a user may use voice-activity-detection rather than push-to-talk. public PermValue UseVAD => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.UseVAD); /// If Allowed, a user may adjust role permissions. This also implictly grants all other permissions. public PermValue ManageRoles => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageRoles); + /// If True, a user may edit the webhooks for this channel. - public PermValue ManageWebhooks => Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageWebhooks); + public PermValue ManageWebhooks => + Permissions.GetValue(AllowValue, DenyValue, ChannelPermission.ManageWebhooks); /// Creates a new OverwritePermissions with the provided allow and deny packed values. public OverwritePermissions(ulong allowValue, ulong denyValue) @@ -76,27 +104,28 @@ namespace Discord private OverwritePermissions(ulong allowValue, ulong denyValue, PermValue? createInstantInvite = null, - PermValue? manageChannel = null, + PermValue? manageChannel = null, PermValue? addReactions = null, PermValue? viewChannel = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, - PermValue? manageMessages = null, + PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, - PermValue? mentionEveryone = null, + PermValue? mentionEveryone = null, PermValue? useExternalEmojis = null, PermValue? connect = null, PermValue? speak = null, - PermValue? muteMembers = null, + PermValue? muteMembers = null, PermValue? deafenMembers = null, PermValue? moveMembers = null, PermValue? useVoiceActivation = null, - PermValue? manageRoles = null, + PermValue? manageRoles = null, PermValue? manageWebhooks = null) { - Permissions.SetValue(ref allowValue, ref denyValue, createInstantInvite, ChannelPermission.CreateInstantInvite); + Permissions.SetValue(ref allowValue, ref denyValue, createInstantInvite, + ChannelPermission.CreateInstantInvite); Permissions.SetValue(ref allowValue, ref denyValue, manageChannel, ChannelPermission.ManageChannels); Permissions.SetValue(ref allowValue, ref denyValue, addReactions, ChannelPermission.AddReactions); Permissions.SetValue(ref allowValue, ref denyValue, viewChannel, ChannelPermission.ViewChannel); @@ -105,7 +134,8 @@ namespace Discord Permissions.SetValue(ref allowValue, ref denyValue, manageMessages, ChannelPermission.ManageMessages); Permissions.SetValue(ref allowValue, ref denyValue, embedLinks, ChannelPermission.EmbedLinks); Permissions.SetValue(ref allowValue, ref denyValue, attachFiles, ChannelPermission.AttachFiles); - Permissions.SetValue(ref allowValue, ref denyValue, readMessageHistory, ChannelPermission.ReadMessageHistory); + Permissions.SetValue(ref allowValue, ref denyValue, readMessageHistory, + ChannelPermission.ReadMessageHistory); Permissions.SetValue(ref allowValue, ref denyValue, mentionEveryone, ChannelPermission.MentionEveryone); Permissions.SetValue(ref allowValue, ref denyValue, useExternalEmojis, ChannelPermission.UseExternalEmojis); Permissions.SetValue(ref allowValue, ref denyValue, connect, ChannelPermission.Connect); @@ -129,7 +159,7 @@ namespace Discord PermValue viewChannel = PermValue.Inherit, PermValue sendMessages = PermValue.Inherit, PermValue sendTTSMessages = PermValue.Inherit, - PermValue manageMessages = PermValue.Inherit, + PermValue manageMessages = PermValue.Inherit, PermValue embedLinks = PermValue.Inherit, PermValue attachFiles = PermValue.Inherit, PermValue readMessageHistory = PermValue.Inherit, @@ -143,9 +173,13 @@ namespace Discord PermValue useVoiceActivation = PermValue.Inherit, PermValue manageRoles = PermValue.Inherit, PermValue manageWebhooks = PermValue.Inherit) - : this(0, 0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, - embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, - moveMembers, useVoiceActivation, manageRoles, manageWebhooks) { } + : this(0, 0, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, + manageMessages, + embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, + muteMembers, deafenMembers, + moveMembers, useVoiceActivation, manageRoles, manageWebhooks) + { + } /// Creates a new OverwritePermissions from this one, changing the provided non-null permissions. public OverwritePermissions Modify( @@ -155,11 +189,11 @@ namespace Discord PermValue? viewChannel = null, PermValue? sendMessages = null, PermValue? sendTTSMessages = null, - PermValue? manageMessages = null, + PermValue? manageMessages = null, PermValue? embedLinks = null, PermValue? attachFiles = null, PermValue? readMessageHistory = null, - PermValue? mentionEveryone = null, + PermValue? mentionEveryone = null, PermValue? useExternalEmojis = null, PermValue? connect = null, PermValue? speak = null, @@ -169,8 +203,10 @@ namespace Discord PermValue? useVoiceActivation = null, PermValue? manageRoles = null, PermValue? manageWebhooks = null) - => new OverwritePermissions(AllowValue, DenyValue, createInstantInvite, manageChannel, addReactions, viewChannel, sendMessages, sendTTSMessages, manageMessages, - embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, + => new OverwritePermissions(AllowValue, DenyValue, createInstantInvite, manageChannel, addReactions, + viewChannel, sendMessages, sendTTSMessages, manageMessages, + embedLinks, attachFiles, readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, + muteMembers, deafenMembers, moveMembers, useVoiceActivation, manageRoles, manageWebhooks); public List ToAllowList() @@ -179,26 +215,30 @@ namespace Discord for (byte i = 0; i < Permissions.MaxBits; i++) { // first operand must be long or ulong to shift >31 bits - ulong flag = ((ulong)1 << i); + var flag = (ulong)1 << i; if ((AllowValue & flag) != 0) perms.Add((ChannelPermission)flag); } + return perms; } + public List ToDenyList() { var perms = new List(); for (byte i = 0; i < Permissions.MaxBits; i++) { - ulong flag = ((ulong)1 << i); + var flag = (ulong)1 << i; if ((DenyValue & flag) != 0) perms.Add((ChannelPermission)flag); } + return perms; } public override string ToString() => $"Allow {AllowValue}, Deny {DenyValue}"; - private string DebuggerDisplay => + + private string DebuggerDisplay => $"Allow {string.Join(", ", ToAllowList())}, " + $"Deny {string.Join(", ", ToDenyList())}"; } diff --git a/src/Discord.Net.Core/Entities/Roles/Color.cs b/src/Discord.Net.Core/Entities/Roles/Color.cs index 0bb04d339..04932fedf 100644 --- a/src/Discord.Net.Core/Entities/Roles/Color.cs +++ b/src/Discord.Net.Core/Entities/Roles/Color.cs @@ -1,54 +1,75 @@ using System; using System.Diagnostics; + #if NETSTANDARD2_0 || NET45 using StandardColor = System.Drawing.Color; #endif namespace Discord { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public struct Color { /// Gets the default user color value. public static readonly Color Default = new Color(0); + /// Gets the teal color value public static readonly Color Teal = new Color(0x1ABC9C); + /// Gets the dark teal color value public static readonly Color DarkTeal = new Color(0x11806A); + /// Gets the green color value public static readonly Color Green = new Color(0x2ECC71); + /// Gets the dark green color value public static readonly Color DarkGreen = new Color(0x1F8B4C); + /// Gets the blue color value public static readonly Color Blue = new Color(0x3498DB); + /// Gets the dark blue color value public static readonly Color DarkBlue = new Color(0x206694); + /// Gets the purple color value public static readonly Color Purple = new Color(0x9B59B6); + /// Gets the dark purple color value public static readonly Color DarkPurple = new Color(0x71368A); + /// Gets the magenta color value public static readonly Color Magenta = new Color(0xE91E63); + /// Gets the dark magenta color value public static readonly Color DarkMagenta = new Color(0xAD1457); + /// Gets the gold color value public static readonly Color Gold = new Color(0xF1C40F); + /// Gets the light orange color value public static readonly Color LightOrange = new Color(0xC27C0E); + /// Gets the orange color value public static readonly Color Orange = new Color(0xE67E22); + /// Gets the dark orange color value public static readonly Color DarkOrange = new Color(0xA84300); + /// Gets the red color value public static readonly Color Red = new Color(0xE74C3C); + /// Gets the dark red color value - public static readonly Color DarkRed = new Color(0x992D22); + public static readonly Color DarkRed = new Color(0x992D22); + /// Gets the light grey color value public static readonly Color LightGrey = new Color(0x979C9F); + /// Gets the lighter grey color value public static readonly Color LighterGrey = new Color(0x95A5A6); + /// Gets the dark grey color value public static readonly Color DarkGrey = new Color(0x607D8B); + /// Gets the darker grey color value public static readonly Color DarkerGrey = new Color(0x546E7A); @@ -57,22 +78,26 @@ namespace Discord /// Gets the red component for this color. public byte R => (byte)(RawValue >> 16); + /// Gets the green component for this color. public byte G => (byte)(RawValue >> 8); + /// Gets the blue component for this color. - public byte B => (byte)(RawValue); + public byte B => (byte)RawValue; public Color(uint rawValue) { RawValue = rawValue; } + public Color(byte r, byte g, byte b) { RawValue = ((uint)r << 16) | ((uint)g << 8) | - (uint)b; + b; } + public Color(int r, int g, int b) { if (r < 0 || r > 255) @@ -86,6 +111,7 @@ namespace Discord ((uint)g << 8) | (uint)b; } + public Color(float r, float g, float b) { if (r < 0.0f || r > 1.0f) @@ -109,6 +135,7 @@ namespace Discord public override string ToString() => $"#{Convert.ToString(RawValue, 16)}"; + private string DebuggerDisplay => $"#{Convert.ToString(RawValue, 16)} ({RawValue})"; } diff --git a/src/Discord.Net.Core/Entities/Roles/IRole.cs b/src/Discord.Net.Core/Entities/Roles/IRole.cs index c40e0d716..27a258baa 100644 --- a/src/Discord.Net.Core/Entities/Roles/IRole.cs +++ b/src/Discord.Net.Core/Entities/Roles/IRole.cs @@ -10,16 +10,22 @@ namespace Discord /// Gets the color given to users of this role. Color Color { get; } + /// Returns true if users of this role are separated in the user list. bool IsHoisted { get; } + /// Returns true if this role is automatically managed by Discord. bool IsManaged { get; } + /// Returns true if this role may be mentioned in messages. bool IsMentionable { get; } + /// Gets the name of this role. string Name { get; } + /// Gets the permissions granted to members of this role. GuildPermissions Permissions { get; } + /// Gets this role's position relative to other roles in the same guild. int Position { get; } diff --git a/src/Discord.Net.Core/Entities/Roles/ReorderRoleProperties.cs b/src/Discord.Net.Core/Entities/Roles/ReorderRoleProperties.cs index 0c8afa24c..66fb35f1e 100644 --- a/src/Discord.Net.Core/Entities/Roles/ReorderRoleProperties.cs +++ b/src/Discord.Net.Core/Entities/Roles/ReorderRoleProperties.cs @@ -2,15 +2,16 @@ { public class ReorderRoleProperties { - /// The id of the role to be edited - public ulong Id { get; } - /// The new zero-based position of the role. - public int Position { get; } - public ReorderRoleProperties(ulong id, int pos) { Id = id; Position = pos; } + + /// The id of the role to be edited + public ulong Id { get; } + + /// The new zero-based position of the role. + public int Position { get; } } } diff --git a/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs b/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs index 8950a2634..218d88e8c 100644 --- a/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs +++ b/src/Discord.Net.Core/Entities/Roles/RoleProperties.cs @@ -1,10 +1,10 @@ namespace Discord { /// - /// Modify an IRole with the specified parameters + /// Modify an IRole with the specified parameters /// /// - /// + /// /// await role.ModifyAsync(x => /// { /// x.Color = new Color(180, 15, 40); @@ -12,46 +12,51 @@ /// }); /// /// - /// + /// public class RoleProperties { /// - /// The name of the role + /// The name of the role /// /// - /// If this role is the EveryoneRole, this value may not be set. + /// If this role is the EveryoneRole, this value may not be set. /// public Optional Name { get; set; } + /// - /// The role's GuildPermissions + /// The role's GuildPermissions /// public Optional Permissions { get; set; } + /// - /// The position of the role. This is 0-based! + /// The position of the role. This is 0-based! /// /// - /// If this role is the EveryoneRole, this value may not be set. + /// If this role is the EveryoneRole, this value may not be set. /// public Optional Position { get; set; } + /// - /// The color of the Role. + /// The color of the Role. /// /// - /// If this role is the EveryoneRole, this value may not be set. + /// If this role is the EveryoneRole, this value may not be set. /// public Optional Color { get; set; } + /// - /// Whether or not this role should be displayed independently in the userlist. + /// Whether or not this role should be displayed independently in the userlist. /// /// - /// If this role is the EveryoneRole, this value may not be set. + /// If this role is the EveryoneRole, this value may not be set. /// public Optional Hoist { get; set; } + /// - /// Whether or not this role can be mentioned. + /// Whether or not this role can be mentioned. /// /// - /// If this role is the EveryoneRole, this value may not be set. + /// If this role is the EveryoneRole, this value may not be set. /// public Optional Mentionable { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs b/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs index 1c5e5482c..c4ab8ac34 100644 --- a/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs +++ b/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs @@ -3,68 +3,75 @@ namespace Discord { /// - /// Modify an IGuildUser with the following parameters. + /// Modify an IGuildUser with the following parameters. /// /// - /// + /// /// await (Context.User as IGuildUser)?.ModifyAsync(x => /// { /// x.Nickname = $"festive {Context.User.Username}"; /// }); /// /// - /// + /// public class GuildUserProperties { /// - /// Should the user be guild-muted in a voice channel? + /// Should the user be guild-muted in a voice channel? /// /// - /// If this value is set to true, no user will be able to hear this user speak in the guild. + /// If this value is set to true, no user will be able to hear this user speak in the guild. /// public Optional Mute { get; set; } + /// - /// Should the user be guild-deafened in a voice channel? + /// Should the user be guild-deafened in a voice channel? /// /// - /// If this value is set to true, this user will not be able to hear anyone speak in the guild. + /// If this value is set to true, this user will not be able to hear anyone speak in the guild. /// public Optional Deaf { get; set; } + /// - /// Should the user have a nickname set? + /// Should the user have a nickname set? /// /// - /// To clear the user's nickname, this value can be set to or . + /// To clear the user's nickname, this value can be set to or . /// public Optional Nickname { get; set; } + /// - /// What roles should the user have? + /// What roles should the user have? /// /// - /// To add a role to a user: - /// To remove a role from a user: + /// To add a role to a user: + /// + /// To remove a role from a user: /// public Optional> Roles { get; set; } + /// - /// What roles should the user have? + /// What roles should the user have? /// /// - /// To add a role to a user: - /// To remove a role from a user: + /// To add a role to a user: + /// To remove a role from a user: /// public Optional> RoleIds { get; set; } + /// - /// Move a user to a voice channel. + /// Move a user to a voice channel. /// /// - /// This user MUST already be in a Voice Channel for this to work. + /// This user MUST already be in a Voice Channel for this to work. /// public Optional Channel { get; set; } + /// - /// Move a user to a voice channel. + /// Move a user to a voice channel. /// /// - /// This user MUST already be in a Voice Channel for this to work. + /// This user MUST already be in a Voice Channel for this to work. /// public Optional ChannelId { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs index 57cad1333..00fb8b6a9 100644 --- a/src/Discord.Net.Core/Entities/Users/IGuildUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IGuildUser.cs @@ -9,16 +9,23 @@ namespace Discord { /// Gets when this user joined this guild. DateTimeOffset? JoinedAt { get; } + /// Gets the nickname for this user. string Nickname { get; } + /// Gets the guild-level permissions for this user. GuildPermissions GuildPermissions { get; } /// Gets the guild for this user. IGuild Guild { get; } + /// Gets the id of the guild for this user. ulong GuildId { get; } - /// Returns a collection of the ids of the roles this user is a member of in this guild, including the guild's @everyone role. + + /// + /// Returns a collection of the ids of the roles this user is a member of in this guild, including the guild's + /// @everyone role. + /// IReadOnlyCollection RoleIds { get; } /// Gets the level permissions granted to this user to a given channel. @@ -26,15 +33,19 @@ namespace Discord /// Kicks this user from this guild. Task KickAsync(string reason = null, RequestOptions options = null); + /// Modifies this user's properties in this guild. Task ModifyAsync(Action func, RequestOptions options = null); /// Adds a role to this user in this guild. Task AddRoleAsync(IRole role, RequestOptions options = null); + /// Adds roles to this user in this guild. Task AddRolesAsync(IEnumerable roles, RequestOptions options = null); + /// Removes a role from this user in this guild. Task RemoveRoleAsync(IRole role, RequestOptions options = null); + /// Removes roles from this user in this guild. Task RemoveRolesAsync(IEnumerable roles, RequestOptions options = null); } diff --git a/src/Discord.Net.Core/Entities/Users/IPresence.cs b/src/Discord.Net.Core/Entities/Users/IPresence.cs index 25adcc9c4..0d20b569f 100644 --- a/src/Discord.Net.Core/Entities/Users/IPresence.cs +++ b/src/Discord.Net.Core/Entities/Users/IPresence.cs @@ -4,7 +4,8 @@ { /// Gets the activity this user is currently doing. IActivity Activity { get; } + /// Gets the current status of this user. UserStatus Status { get; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Users/ISelfUser.cs b/src/Discord.Net.Core/Entities/Users/ISelfUser.cs index 7b91d4e3a..616d57bce 100644 --- a/src/Discord.Net.Core/Entities/Users/ISelfUser.cs +++ b/src/Discord.Net.Core/Entities/Users/ISelfUser.cs @@ -7,11 +7,13 @@ namespace Discord { /// Gets the email associated with this user. string Email { get; } + /// Returns true if this user's email has been verified. bool IsVerified { get; } + /// Returns true if this user has enabled MFA on their account. bool IsMfaEnabled { get; } Task ModifyAsync(Action func, RequestOptions options = null); } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Entities/Users/IUser.cs b/src/Discord.Net.Core/Entities/Users/IUser.cs index c5cce7a25..85538c09a 100644 --- a/src/Discord.Net.Core/Entities/Users/IUser.cs +++ b/src/Discord.Net.Core/Entities/Users/IUser.cs @@ -6,21 +6,28 @@ namespace Discord { /// Gets the id of this user's avatar. string AvatarId { get; } - /// Gets the url to this user's avatar. - string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); - /// Gets the url to this user's default avatar. - string GetDefaultAvatarUrl(); + /// Gets the per-username unique id for this user. string Discriminator { get; } + /// Gets the per-username unique id for this user. ushort DiscriminatorValue { get; } + /// Returns true if this user is a bot user. bool IsBot { get; } + /// Returns true if this user is a webhook user. bool IsWebhook { get; } + /// Gets the username for this user. string Username { get; } + /// Gets the url to this user's avatar. + string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); + + /// Gets the url to this user's default avatar. + string GetDefaultAvatarUrl(); + /// Returns a private message channel to this user, creating one if it does not already exist. Task GetOrCreateDMChannelAsync(RequestOptions options = null); } diff --git a/src/Discord.Net.Core/Entities/Users/IVoiceState.cs b/src/Discord.Net.Core/Entities/Users/IVoiceState.cs index 428601f2a..c9c91e180 100644 --- a/src/Discord.Net.Core/Entities/Users/IVoiceState.cs +++ b/src/Discord.Net.Core/Entities/Users/IVoiceState.cs @@ -4,16 +4,22 @@ { /// Returns true if the guild has deafened this user. bool IsDeafened { get; } + /// Returns true if the guild has muted this user. bool IsMuted { get; } + /// Returns true if this user has marked themselves as deafened. bool IsSelfDeafened { get; } + /// Returns true if this user has marked themselves as muted. bool IsSelfMuted { get; } + /// Returns true if the guild is temporarily blocking audio to/from this user. bool IsSuppressed { get; } + /// Gets the voice channel this user is currently in, if any. IVoiceChannel VoiceChannel { get; } + /// Gets the unique identifier for this user's voice session. string VoiceSessionId { get; } } diff --git a/src/Discord.Net.Core/Entities/Users/SelfUserProperties.cs b/src/Discord.Net.Core/Entities/Users/SelfUserProperties.cs index 9c4162780..8d16683f7 100644 --- a/src/Discord.Net.Core/Entities/Users/SelfUserProperties.cs +++ b/src/Discord.Net.Core/Entities/Users/SelfUserProperties.cs @@ -1,25 +1,26 @@ namespace Discord { /// - /// Modify the current user with the specified arguments + /// Modify the current user with the specified arguments /// /// - /// + /// /// await Context.Client.CurrentUser.ModifyAsync(x => /// { /// x.Avatar = new Image(File.OpenRead("avatar.jpg")); /// }); /// /// - /// + /// public class SelfUserProperties { /// - /// Your username + /// Your username /// public Optional Username { get; set; } + /// - /// Your avatar + /// Your avatar /// public Optional Avatar { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Users/UserStatus.cs b/src/Discord.Net.Core/Entities/Users/UserStatus.cs index 74a52a0fa..1e4ad8d4d 100644 --- a/src/Discord.Net.Core/Entities/Users/UserStatus.cs +++ b/src/Discord.Net.Core/Entities/Users/UserStatus.cs @@ -7,6 +7,6 @@ Idle, AFK, DoNotDisturb, - Invisible, + Invisible } } diff --git a/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs b/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs index ef56f72b9..b0ab8fea0 100644 --- a/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs +++ b/src/Discord.Net.Core/Entities/Webhooks/IWebhook.cs @@ -10,24 +10,28 @@ namespace Discord /// Gets the default name of this webhook. string Name { get; } + /// Gets the id of this webhook's default avatar. string AvatarId { get; } - /// Gets the url to this webhook's default avatar. - string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); /// Gets the channel for this webhook. ITextChannel Channel { get; } + /// Gets the id of the channel for this webhook. ulong ChannelId { get; } /// Gets the guild owning this webhook. IGuild Guild { get; } + /// Gets the id of the guild owning this webhook. ulong? GuildId { get; } /// Gets the user that created this webhook. IUser Creator { get; } + /// Gets the url to this webhook's default avatar. + string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128); + /// Modifies this webhook. Task ModifyAsync(Action func, RequestOptions options = null); } diff --git a/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs b/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs index 8759a1729..6c09d4f6e 100644 --- a/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs +++ b/src/Discord.Net.Core/Entities/Webhooks/WebhookProperties.cs @@ -1,10 +1,10 @@ namespace Discord { /// - /// Modify an with the specified parameters. + /// Modify an with the specified parameters. /// /// - /// + /// /// await webhook.ModifyAsync(x => /// { /// x.Name = "Bob"; @@ -12,29 +12,32 @@ /// }); /// /// - /// + /// public class WebhookProperties { /// - /// The default name of the webhook. + /// The default name of the webhook. /// public Optional Name { get; set; } + /// - /// The default avatar of the webhook. + /// The default avatar of the webhook. /// public Optional Image { get; set; } + /// - /// The channel for this webhook. + /// The channel for this webhook. /// /// - /// This field is not used when authenticated with . + /// This field is not used when authenticated with . /// public Optional Channel { get; set; } + /// - /// The channel id for this webhook. + /// The channel id for this webhook. /// /// - /// This field is not used when authenticated with . + /// This field is not used when authenticated with . /// public Optional ChannelId { get; set; } } diff --git a/src/Discord.Net.Core/Entities/Webhooks/WebhookType.cs b/src/Discord.Net.Core/Entities/Webhooks/WebhookType.cs index 0ddfa393d..385fd124a 100644 --- a/src/Discord.Net.Core/Entities/Webhooks/WebhookType.cs +++ b/src/Discord.Net.Core/Entities/Webhooks/WebhookType.cs @@ -1,10 +1,10 @@ namespace Discord { /// - /// Represents the type of a webhook. + /// Represents the type of a webhook. /// /// - /// This type is currently unused, and is only returned in audit log responses. + /// This type is currently unused, and is only returned in audit log responses. /// public enum WebhookType { diff --git a/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs b/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs index dd16d2943..65f95b708 100644 --- a/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs +++ b/src/Discord.Net.Core/Extensions/AsyncEnumerableExtensions.cs @@ -7,19 +7,15 @@ namespace Discord public static class AsyncEnumerableExtensions { /// - /// Flattens the specified pages into one asynchronously + /// Flattens the specified pages into one asynchronously /// /// /// /// - public static async Task> FlattenAsync(this IAsyncEnumerable> source) - { - return await source.Flatten().ToArray().ConfigureAwait(false); - } + public static async Task> FlattenAsync(this IAsyncEnumerable> source) => + await source.Flatten().ToArray().ConfigureAwait(false); - public static IAsyncEnumerable Flatten(this IAsyncEnumerable> source) - { - return source.SelectMany(enumerable => enumerable.ToAsyncEnumerable()); - } + public static IAsyncEnumerable Flatten(this IAsyncEnumerable> source) => + source.SelectMany(enumerable => enumerable.ToAsyncEnumerable()); } } diff --git a/src/Discord.Net.Core/Extensions/CollectionExtensions.cs b/src/Discord.Net.Core/Extensions/CollectionExtensions.cs index e5d6025c2..b9ef3f74e 100644 --- a/src/Discord.Net.Core/Extensions/CollectionExtensions.cs +++ b/src/Discord.Net.Core/Extensions/CollectionExtensions.cs @@ -12,17 +12,23 @@ namespace Discord // => new CollectionWrapper(source, () => source.Count); public static IReadOnlyCollection ToReadOnlyCollection(this ICollection source) => new CollectionWrapper(source, () => source.Count); + //public static IReadOnlyCollection ToReadOnlyCollection(this IReadOnlyDictionary source) // => new CollectionWrapper(source.Select(x => x.Value), () => source.Count); - public static IReadOnlyCollection ToReadOnlyCollection(this IDictionary source) + public static IReadOnlyCollection ToReadOnlyCollection( + this IDictionary source) => new CollectionWrapper(source.Select(x => x.Value), () => source.Count); - public static IReadOnlyCollection ToReadOnlyCollection(this IEnumerable query, IReadOnlyCollection source) + + public static IReadOnlyCollection ToReadOnlyCollection(this IEnumerable query, + IReadOnlyCollection source) => new CollectionWrapper(query, () => source.Count); - public static IReadOnlyCollection ToReadOnlyCollection(this IEnumerable query, Func countFunc) + + public static IReadOnlyCollection ToReadOnlyCollection(this IEnumerable query, + Func countFunc) => new CollectionWrapper(query, countFunc); } - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] internal struct CollectionWrapper : IReadOnlyCollection { private readonly IEnumerable _query; diff --git a/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs b/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs index ff3c7caf7..ea5f87431 100644 --- a/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs +++ b/src/Discord.Net.Core/Extensions/DiscordClientExtensions.cs @@ -11,13 +11,17 @@ namespace Discord public static async Task GetDMChannelAsync(this IDiscordClient client, ulong id) => await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IDMChannel; + public static async Task> GetDMChannelsAsync(this IDiscordClient client) - => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).Select(x => x as IDMChannel).Where(x => x != null); + => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).Select(x => x as IDMChannel) + .Where(x => x != null); public static async Task GetGroupChannelAsync(this IDiscordClient client, ulong id) => await client.GetPrivateChannelAsync(id).ConfigureAwait(false) as IGroupChannel; + public static async Task> GetGroupChannelsAsync(this IDiscordClient client) - => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).Select(x => x as IGroupChannel).Where(x => x != null); + => (await client.GetPrivateChannelsAsync().ConfigureAwait(false)).Select(x => x as IGroupChannel) + .Where(x => x != null); public static async Task GetOptimalVoiceRegionAsync(this IDiscordClient discord) { diff --git a/src/Discord.Net.Core/Extensions/MessageExtensions.cs b/src/Discord.Net.Core/Extensions/MessageExtensions.cs index c53ef9053..5ba4b296e 100644 --- a/src/Discord.Net.Core/Extensions/MessageExtensions.cs +++ b/src/Discord.Net.Core/Extensions/MessageExtensions.cs @@ -5,7 +5,8 @@ namespace Discord public static string GetJumpUrl(this IMessage msg) { var channel = msg.Channel; - return $"https://discordapp.com/channels/{(channel is IDMChannel ? "@me" : $"{(channel as ITextChannel).GuildId}")}/{channel.Id}/{msg.Id}"; + return + $"https://discordapp.com/channels/{(channel is IDMChannel ? "@me" : $"{(channel as ITextChannel).GuildId}")}/{channel.Id}/{msg.Id}"; } } } diff --git a/src/Discord.Net.Core/Extensions/TaskCompletionSourceExtensions.cs b/src/Discord.Net.Core/Extensions/TaskCompletionSourceExtensions.cs index a5a715b4c..b572e15dd 100644 --- a/src/Discord.Net.Core/Extensions/TaskCompletionSourceExtensions.cs +++ b/src/Discord.Net.Core/Extensions/TaskCompletionSourceExtensions.cs @@ -7,16 +7,19 @@ namespace Discord { public static Task SetResultAsync(this TaskCompletionSource source, T result) => Task.Run(() => source.SetResult(result)); + public static Task TrySetResultAsync(this TaskCompletionSource source, T result) => Task.Run(() => source.TrySetResult(result)); public static Task SetExceptionAsync(this TaskCompletionSource source, Exception ex) => Task.Run(() => source.SetException(ex)); + public static Task TrySetExceptionAsync(this TaskCompletionSource source, Exception ex) => Task.Run(() => source.TrySetException(ex)); public static Task SetCanceledAsync(this TaskCompletionSource source) => Task.Run(() => source.SetCanceled()); + public static Task TrySetCanceledAsync(this TaskCompletionSource source) => Task.Run(() => source.TrySetCanceled()); } diff --git a/src/Discord.Net.Core/Extensions/UserExtensions.cs b/src/Discord.Net.Core/Extensions/UserExtensions.cs index 951e8ca4b..c898cc07c 100644 --- a/src/Discord.Net.Core/Extensions/UserExtensions.cs +++ b/src/Discord.Net.Core/Extensions/UserExtensions.cs @@ -1,24 +1,22 @@ -using System.Threading.Tasks; using System.IO; +using System.Threading.Tasks; namespace Discord { public static class UserExtensions { /// - /// Sends a message to the user via DM. + /// Sends a message to the user via DM. /// public static async Task SendMessageAsync(this IUser user, string text = null, bool isTTS = false, Embed embed = null, - RequestOptions options = null) - { - return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); - } + RequestOptions options = null) => await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)) + .SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); /// - /// Sends a file to the user via DM. + /// Sends a file to the user via DM. /// public static async Task SendFileAsync(this IUser user, Stream stream, @@ -27,25 +25,22 @@ namespace Discord bool isTTS = false, Embed embed = null, RequestOptions options = null - ) - { - return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); - } + ) => await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)) + .SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); /// - /// Sends a file to the user via DM. + /// Sends a file to the user via DM. /// public static async Task SendFileAsync(this IUser user, string filePath, string text = null, bool isTTS = false, Embed embed = null, - RequestOptions options = null) - { - return await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)).SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); - } + RequestOptions options = null) => await (await user.GetOrCreateDMChannelAsync().ConfigureAwait(false)) + .SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); - public static Task BanAsync(this IGuildUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) + public static Task BanAsync(this IGuildUser user, int pruneDays = 0, string reason = null, + RequestOptions options = null) => user.Guild.AddBanAsync(user, pruneDays, reason, options); } } diff --git a/src/Discord.Net.Core/Format.cs b/src/Discord.Net.Core/Format.cs index aa822f99e..b9ebb5d3e 100644 --- a/src/Discord.Net.Core/Format.cs +++ b/src/Discord.Net.Core/Format.cs @@ -1,16 +1,21 @@ -namespace Discord +using System.Linq; + +namespace Discord { public static class Format { // Characters which need escaping - private static string[] SensitiveCharacters = { "\\", "*", "_", "~", "`" }; + private static readonly string[] SensitiveCharacters = {"\\", "*", "_", "~", "`"}; /// Returns a markdown-formatted string with bold formatting. public static string Bold(string text) => $"**{text}**"; + /// Returns a markdown-formatted string with italics formatting. public static string Italics(string text) => $"*{text}*"; + /// Returns a markdown-formatted string with underline formatting. public static string Underline(string text) => $"__{text}__"; + /// Returns a markdown-formatted string with strikethrough formatting. public static string Strikethrough(string text) => $"~~{text}~~"; @@ -19,16 +24,13 @@ { if (language != null || text.Contains("\n")) return $"```{language ?? ""}\n{text}\n```"; - else - return $"`{text}`"; + return $"`{text}`"; } /// Sanitizes the string, safely escaping any Markdown sequences. public static string Sanitize(string text) { - foreach (string unsafeChar in SensitiveCharacters) - text = text.Replace(unsafeChar, $"\\{unsafeChar}"); - return text; + return SensitiveCharacters.Aggregate(text, (current, unsafeChar) => current.Replace(unsafeChar, $"\\{unsafeChar}")); } } } diff --git a/src/Discord.Net.Core/IDiscordClient.cs b/src/Discord.Net.Core/IDiscordClient.cs index a383c37da..ec699b702 100644 --- a/src/Discord.Net.Core/IDiscordClient.cs +++ b/src/Discord.Net.Core/IDiscordClient.cs @@ -16,17 +16,28 @@ namespace Discord Task GetApplicationInfoAsync(RequestOptions options = null); - Task GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - Task> GetPrivateChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - Task> GetDMChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - Task> GetGroupChannelsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); + Task GetChannelAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + + Task> GetPrivateChannelsAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + + Task> GetDMChannelsAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + + Task> GetGroupChannelsAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); Task> GetConnectionsAsync(RequestOptions options = null); Task GetGuildAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - Task> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); - Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null); - + + Task> GetGuildsAsync(CacheMode mode = CacheMode.AllowDownload, + RequestOptions options = null); + + Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, + RequestOptions options = null); + Task GetInviteAsync(string inviteId, RequestOptions options = null); Task GetUserAsync(ulong id, CacheMode mode = CacheMode.AllowDownload, RequestOptions options = null); diff --git a/src/Discord.Net.Core/Logging/LogManager.cs b/src/Discord.Net.Core/Logging/LogManager.cs index a69519fa2..461579590 100644 --- a/src/Discord.Net.Core/Logging/LogManager.cs +++ b/src/Discord.Net.Core/Logging/LogManager.cs @@ -5,10 +5,6 @@ namespace Discord.Logging { internal class LogManager { - public LogSeverity Level { get; } - private Logger ClientLogger { get; } - - public event Func Message { add { _messageEvent.Add(value); } remove { _messageEvent.Remove(value); } } private readonly AsyncEvent> _messageEvent = new AsyncEvent>(); public LogManager(LogSeverity minSeverity) @@ -17,6 +13,15 @@ namespace Discord.Logging ClientLogger = new Logger(this, "Discord"); } + public LogSeverity Level { get; } + private Logger ClientLogger { get; } + + public event Func Message + { + add => _messageEvent.Add(value); + remove => _messageEvent.Remove(value); + } + public async Task LogAsync(LogSeverity severity, string source, Exception ex) { try @@ -24,16 +29,22 @@ namespace Discord.Logging if (severity <= Level) await _messageEvent.InvokeAsync(new LogMessage(severity, source, null, ex)).ConfigureAwait(false); } - catch { } + catch + { + } } + public async Task LogAsync(LogSeverity severity, string source, string message, Exception ex = null) { try { if (severity <= Level) - await _messageEvent.InvokeAsync(new LogMessage(severity, source, message, ex)).ConfigureAwait(false); + await _messageEvent.InvokeAsync(new LogMessage(severity, source, message, ex)) + .ConfigureAwait(false); + } + catch + { } - catch { } } public async Task LogAsync(LogSeverity severity, string source, FormattableString message, Exception ex = null) @@ -41,14 +52,18 @@ namespace Discord.Logging try { if (severity <= Level) - await _messageEvent.InvokeAsync(new LogMessage(severity, source, message.ToString(), ex)).ConfigureAwait(false); + await _messageEvent.InvokeAsync(new LogMessage(severity, source, message.ToString(), ex)) + .ConfigureAwait(false); + } + catch + { } - catch { } } public Task ErrorAsync(string source, Exception ex) => LogAsync(LogSeverity.Error, source, ex); + public Task ErrorAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Error, source, message, ex); @@ -58,6 +73,7 @@ namespace Discord.Logging public Task WarningAsync(string source, Exception ex) => LogAsync(LogSeverity.Warning, source, ex); + public Task WarningAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Warning, source, message, ex); @@ -67,33 +83,37 @@ namespace Discord.Logging public Task InfoAsync(string source, Exception ex) => LogAsync(LogSeverity.Info, source, ex); + public Task InfoAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Info, source, message, ex); + public Task InfoAsync(string source, FormattableString message, Exception ex = null) => LogAsync(LogSeverity.Info, source, message, ex); public Task VerboseAsync(string source, Exception ex) => LogAsync(LogSeverity.Verbose, source, ex); + public Task VerboseAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Verbose, source, message, ex); + public Task VerboseAsync(string source, FormattableString message, Exception ex = null) => LogAsync(LogSeverity.Verbose, source, message, ex); public Task DebugAsync(string source, Exception ex) => LogAsync(LogSeverity.Debug, source, ex); + public Task DebugAsync(string source, string message, Exception ex = null) => LogAsync(LogSeverity.Debug, source, message, ex); + public Task DebugAsync(string source, FormattableString message, Exception ex = null) => LogAsync(LogSeverity.Debug, source, message, ex); public Logger CreateLogger(string name) => new Logger(this, name); - public async Task WriteInitialLog() - { - await ClientLogger.InfoAsync($"Discord.Net v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion})").ConfigureAwait(false); - } + public async Task WriteInitialLog() => await ClientLogger + .InfoAsync($"Discord.Net v{DiscordConfig.Version} (API v{DiscordConfig.APIVersion})").ConfigureAwait(false); } } diff --git a/src/Discord.Net.Core/Logging/LogMessage.cs b/src/Discord.Net.Core/Logging/LogMessage.cs index d1b3782be..77f6d78fb 100644 --- a/src/Discord.Net.Core/Logging/LogMessage.cs +++ b/src/Discord.Net.Core/Logging/LogMessage.cs @@ -18,18 +18,20 @@ namespace Discord Exception = exception; } - public override string ToString() => ToString(null); - public string ToString(StringBuilder builder = null, bool fullException = true, bool prependTimestamp = true, DateTimeKind timestampKind = DateTimeKind.Local, int? padSource = 11) + public override string ToString() => ToString(); + + public string ToString(StringBuilder builder = null, bool fullException = true, bool prependTimestamp = true, + DateTimeKind timestampKind = DateTimeKind.Local, int? padSource = 11) { - string sourceName = Source; - string message = Message; - string exMessage = fullException ? Exception?.ToString() : Exception?.Message; + var sourceName = Source; + var message = Message; + var exMessage = fullException ? Exception?.ToString() : Exception?.Message; - int maxLength = 1 + - (prependTimestamp ? 8 : 0) + 1 + - (padSource.HasValue ? padSource.Value : sourceName?.Length ?? 0) + 1 + - (message?.Length ?? 0) + - (exMessage?.Length ?? 0) + 3; + var maxLength = 1 + + (prependTimestamp ? 8 : 0) + 1 + + (padSource.HasValue ? padSource.Value : sourceName?.Length ?? 0) + 1 + + (message?.Length ?? 0) + + (exMessage?.Length ?? 0) + 3; if (builder == null) builder = new StringBuilder(maxLength); @@ -42,10 +44,7 @@ namespace Discord if (prependTimestamp) { DateTime now; - if (timestampKind == DateTimeKind.Utc) - now = DateTime.UtcNow; - else - now = DateTime.Now; + now = timestampKind == DateTimeKind.Utc ? DateTime.UtcNow : DateTime.Now; if (now.Hour < 10) builder.Append('0'); builder.Append(now.Hour); @@ -59,6 +58,7 @@ namespace Discord builder.Append(now.Second); builder.Append(' '); } + if (sourceName != null) { if (padSource.HasValue) @@ -73,28 +73,28 @@ namespace Discord else builder.Append(sourceName); } + builder.Append(' '); } + if (!string.IsNullOrEmpty(Message)) - { - for (int i = 0; i < message.Length; i++) + for (var i = 0; i < message.Length; i++) { //Strip control chars - char c = message[i]; + var c = message[i]; if (!char.IsControl(c)) builder.Append(c); } - } - if (exMessage != null) + + if (exMessage == null) return builder.ToString(); + if (!string.IsNullOrEmpty(Message)) { - if (!string.IsNullOrEmpty(Message)) - { - builder.Append(':'); - builder.AppendLine(); - } - builder.Append(exMessage); + builder.Append(':'); + builder.AppendLine(); } + builder.Append(exMessage); + return builder.ToString(); } } diff --git a/src/Discord.Net.Core/Logging/Logger.cs b/src/Discord.Net.Core/Logging/Logger.cs index e71c56992..d4ac59dc2 100644 --- a/src/Discord.Net.Core/Logging/Logger.cs +++ b/src/Discord.Net.Core/Logging/Logger.cs @@ -7,25 +7,28 @@ namespace Discord.Logging { private readonly LogManager _manager; - public string Name { get; } - public LogSeverity Level => _manager.Level; - public Logger(LogManager manager, string name) { _manager = manager; Name = name; } + public string Name { get; } + public LogSeverity Level => _manager.Level; + public Task LogAsync(LogSeverity severity, Exception exception = null) => _manager.LogAsync(severity, Name, exception); + public Task LogAsync(LogSeverity severity, string message, Exception exception = null) => _manager.LogAsync(severity, Name, message, exception); + public Task LogAsync(LogSeverity severity, FormattableString message, Exception exception = null) => _manager.LogAsync(severity, Name, message, exception); public Task ErrorAsync(Exception exception) => _manager.ErrorAsync(Name, exception); + public Task ErrorAsync(string message, Exception exception = null) => _manager.ErrorAsync(Name, message, exception); @@ -35,6 +38,7 @@ namespace Discord.Logging public Task WarningAsync(Exception exception) => _manager.WarningAsync(Name, exception); + public Task WarningAsync(string message, Exception exception = null) => _manager.WarningAsync(Name, message, exception); @@ -44,6 +48,7 @@ namespace Discord.Logging public Task InfoAsync(Exception exception) => _manager.InfoAsync(Name, exception); + public Task InfoAsync(string message, Exception exception = null) => _manager.InfoAsync(Name, message, exception); @@ -53,6 +58,7 @@ namespace Discord.Logging public Task VerboseAsync(Exception exception) => _manager.VerboseAsync(Name, exception); + public Task VerboseAsync(string message, Exception exception = null) => _manager.VerboseAsync(Name, message, exception); @@ -62,11 +68,11 @@ namespace Discord.Logging public Task DebugAsync(Exception exception) => _manager.DebugAsync(Name, exception); + public Task DebugAsync(string message, Exception exception = null) => _manager.DebugAsync(Name, message, exception); public Task DebugAsync(FormattableString message, Exception exception = null) => _manager.DebugAsync(Name, message, exception); - } } diff --git a/src/Discord.Net.Core/Net/HttpException.cs b/src/Discord.Net.Core/Net/HttpException.cs index d0ee65b23..1db2c3d3e 100644 --- a/src/Discord.Net.Core/Net/HttpException.cs +++ b/src/Discord.Net.Core/Net/HttpException.cs @@ -5,11 +5,6 @@ namespace Discord.Net { public class HttpException : Exception { - public HttpStatusCode HttpCode { get; } - public int? DiscordCode { get; } - public string Reason { get; } - public IRequest Request { get; } - public HttpException(HttpStatusCode httpCode, IRequest request, int? discordCode = null, string reason = null) : base(CreateMessage(httpCode, discordCode, reason)) { @@ -19,23 +14,23 @@ namespace Discord.Net Reason = reason; } + public HttpStatusCode HttpCode { get; } + public int? DiscordCode { get; } + public string Reason { get; } + public IRequest Request { get; } + private static string CreateMessage(HttpStatusCode httpCode, int? discordCode = null, string reason = null) - { + { string msg; if (discordCode != null && discordCode != 0) { - if (reason != null) - msg = $"The server responded with error {(int)discordCode}: {reason}"; - else - msg = $"The server responded with error {(int)discordCode}: {httpCode}"; + msg = reason != null ? $"The server responded with error {(int)discordCode}: {reason}" : $"The server responded with error {(int)discordCode}: {httpCode}"; } else { - if (reason != null) - msg = $"The server responded with error {(int)httpCode}: {reason}"; - else - msg = $"The server responded with error {(int)httpCode}: {httpCode}"; + msg = reason != null ? $"The server responded with error {(int)httpCode}: {reason}" : $"The server responded with error {(int)httpCode}: {httpCode}"; } + return msg; } } diff --git a/src/Discord.Net.Core/Net/RateLimitedException.cs b/src/Discord.Net.Core/Net/RateLimitedException.cs index 2d34d7bc2..8456e45a5 100644 --- a/src/Discord.Net.Core/Net/RateLimitedException.cs +++ b/src/Discord.Net.Core/Net/RateLimitedException.cs @@ -4,12 +4,12 @@ namespace Discord.Net { public class RateLimitedException : TimeoutException { - public IRequest Request { get; } - public RateLimitedException(IRequest request) : base("You are being rate limited.") { Request = request; } + + public IRequest Request { get; } } } diff --git a/src/Discord.Net.Core/Net/Rest/IRestClient.cs b/src/Discord.Net.Core/Net/Rest/IRestClient.cs index addfa9061..9a65603ee 100644 --- a/src/Discord.Net.Core/Net/Rest/IRestClient.cs +++ b/src/Discord.Net.Core/Net/Rest/IRestClient.cs @@ -9,8 +9,14 @@ namespace Discord.Net.Rest void SetHeader(string key, string value); void SetCancelToken(CancellationToken cancelToken); - Task SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly = false, string reason = null); - Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly = false, string reason = null); - Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly = false, string reason = null); + Task SendAsync(string method, string endpoint, CancellationToken cancelToken, + bool headerOnly = false, string reason = null); + + Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, + bool headerOnly = false, string reason = null); + + Task SendAsync(string method, string endpoint, + IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly = false, + string reason = null); } } diff --git a/src/Discord.Net.Core/Net/RpcException.cs b/src/Discord.Net.Core/Net/RpcException.cs index 195fad73f..119dd1907 100644 --- a/src/Discord.Net.Core/Net/RpcException.cs +++ b/src/Discord.Net.Core/Net/RpcException.cs @@ -4,14 +4,14 @@ namespace Discord { public class RpcException : Exception { - public int ErrorCode { get; } - public string Reason { get; } - public RpcException(int errorCode, string reason = null) : base($"The server sent error {errorCode}{(reason != null ? $": \"{reason}\"" : "")}") { ErrorCode = errorCode; Reason = reason; } + + public int ErrorCode { get; } + public string Reason { get; } } } diff --git a/src/Discord.Net.Core/Net/Udp/IUdpSocket.cs b/src/Discord.Net.Core/Net/Udp/IUdpSocket.cs index 10ac652b3..27c325389 100644 --- a/src/Discord.Net.Core/Net/Udp/IUdpSocket.cs +++ b/src/Discord.Net.Core/Net/Udp/IUdpSocket.cs @@ -6,9 +6,8 @@ namespace Discord.Net.Udp { public interface IUdpSocket { - event Func ReceivedDatagram; - ushort Port { get; } + event Func ReceivedDatagram; void SetCancelToken(CancellationToken cancelToken); void SetDestination(string ip, int port); diff --git a/src/Discord.Net.Core/Net/WebSocketClosedException.cs b/src/Discord.Net.Core/Net/WebSocketClosedException.cs index d647b6c8c..818414f13 100644 --- a/src/Discord.Net.Core/Net/WebSocketClosedException.cs +++ b/src/Discord.Net.Core/Net/WebSocketClosedException.cs @@ -1,16 +1,17 @@ using System; + namespace Discord.Net { public class WebSocketClosedException : Exception { - public int CloseCode { get; } - public string Reason { get; } - public WebSocketClosedException(int closeCode, string reason = null) : base($"The server sent close {closeCode}{(reason != null ? $": \"{reason}\"" : "")}") { CloseCode = closeCode; Reason = reason; } + + public int CloseCode { get; } + public string Reason { get; } } } diff --git a/src/Discord.Net.Core/RequestOptions.cs b/src/Discord.Net.Core/RequestOptions.cs index 5f3a8814b..c753c0e4d 100644 --- a/src/Discord.Net.Core/RequestOptions.cs +++ b/src/Discord.Net.Core/RequestOptions.cs @@ -4,18 +4,26 @@ namespace Discord { public class RequestOptions { + public RequestOptions() + { + Timeout = DiscordConfig.DefaultRequestTimeout; + } + public static RequestOptions Default => new RequestOptions(); - /// - /// The max time, in milliseconds, to wait for this request to complete. If null, a request will not time out. - /// If a rate limit has been triggered for this request's bucket and will not be unpaused in time, this request will fail immediately. + /// + /// The max time, in milliseconds, to wait for this request to complete. If null, a request will not time out. + /// If a rate limit has been triggered for this request's bucket and will not be unpaused in time, this request will + /// fail immediately. /// public int? Timeout { get; set; } + public CancellationToken CancelToken { get; set; } = CancellationToken.None; public RetryMode? RetryMode { get; set; } public bool HeaderOnly { get; internal set; } + /// - /// The reason for this action in the guild's audit log + /// The reason for this action in the guild's audit log /// public string AuditLogReason { get; set; } @@ -24,16 +32,10 @@ namespace Discord internal bool IsClientBucket { get; set; } internal static RequestOptions CreateOrClone(RequestOptions options) - { + { if (options == null) return new RequestOptions(); - else - return options.Clone(); - } - - public RequestOptions() - { - Timeout = DiscordConfig.DefaultRequestTimeout; + return options.Clone(); } public RequestOptions Clone() => MemberwiseClone() as RequestOptions; diff --git a/src/Discord.Net.Core/RetryMode.cs b/src/Discord.Net.Core/RetryMode.cs index 65ae75fc3..e59ca8c37 100644 --- a/src/Discord.Net.Core/RetryMode.cs +++ b/src/Discord.Net.Core/RetryMode.cs @@ -8,15 +8,22 @@ namespace Discord { /// If a request fails, an exception is thrown immediately. AlwaysFail = 0x0, + /// Retry if a request timed out. RetryTimeouts = 0x1, + // /// Retry if a request failed due to a network error. //RetryErrors = 0x2, /// Retry if a request failed due to a ratelimit. RetryRatelimit = 0x4, + /// Retry if a request failed due to an HTTP error 502. Retry502 = 0x8, - /// Continuously retry a request until it times out, its cancel token is triggered, or the server responds with a non-502 error. - AlwaysRetry = RetryTimeouts | /*RetryErrors |*/ RetryRatelimit | Retry502, + + /// + /// Continuously retry a request until it times out, its cancel token is triggered, or the server responds with a + /// non-502 error. + /// + AlwaysRetry = RetryTimeouts | /*RetryErrors |*/ RetryRatelimit | Retry502 } } diff --git a/src/Discord.Net.Core/TokenType.cs b/src/Discord.Net.Core/TokenType.cs index 62181420a..6314c6289 100644 --- a/src/Discord.Net.Core/TokenType.cs +++ b/src/Discord.Net.Core/TokenType.cs @@ -4,7 +4,9 @@ namespace Discord { public enum TokenType { - [Obsolete("User logins are deprecated and may result in a ToS strike against your account - please see https://github.com/RogueException/Discord.Net/issues/827", error: true)] + [Obsolete( + "User logins are deprecated and may result in a ToS strike against your account - please see https://github.com/RogueException/Discord.Net/issues/827", + true)] User, Bearer, Bot, diff --git a/src/Discord.Net.Core/Utils/AsyncEvent.cs b/src/Discord.Net.Core/Utils/AsyncEvent.cs index 731489dea..460961e45 100644 --- a/src/Discord.Net.Core/Utils/AsyncEvent.cs +++ b/src/Discord.Net.Core/Utils/AsyncEvent.cs @@ -11,20 +11,21 @@ namespace Discord private readonly object _subLock = new object(); internal ImmutableArray _subscriptions; - public bool HasSubscribers => _subscriptions.Length != 0; - public IReadOnlyList Subscriptions => _subscriptions; - public AsyncEvent() { _subscriptions = ImmutableArray.Create(); } + public bool HasSubscribers => _subscriptions.Length != 0; + public IReadOnlyList Subscriptions => _subscriptions; + public void Add(T subscriber) { Preconditions.NotNull(subscriber, nameof(subscriber)); lock (_subLock) _subscriptions = _subscriptions.Add(subscriber); } + public void Remove(T subscriber) { Preconditions.NotNull(subscriber, nameof(subscriber)); @@ -34,41 +35,49 @@ namespace Discord } internal static class EventExtensions - { + { public static async Task InvokeAsync(this AsyncEvent> eventHandler) { var subscribers = eventHandler.Subscriptions; - for (int i = 0; i < subscribers.Count; i++) + for (var i = 0; i < subscribers.Count; i++) await subscribers[i].Invoke().ConfigureAwait(false); } + public static async Task InvokeAsync(this AsyncEvent> eventHandler, T arg) { var subscribers = eventHandler.Subscriptions; - for (int i = 0; i < subscribers.Count; i++) + for (var i = 0; i < subscribers.Count; i++) await subscribers[i].Invoke(arg).ConfigureAwait(false); } + public static async Task InvokeAsync(this AsyncEvent> eventHandler, T1 arg1, T2 arg2) { var subscribers = eventHandler.Subscriptions; - for (int i = 0; i < subscribers.Count; i++) + for (var i = 0; i < subscribers.Count; i++) await subscribers[i].Invoke(arg1, arg2).ConfigureAwait(false); } - public static async Task InvokeAsync(this AsyncEvent> eventHandler, T1 arg1, T2 arg2, T3 arg3) + + public static async Task InvokeAsync(this AsyncEvent> eventHandler, T1 arg1, + T2 arg2, T3 arg3) { var subscribers = eventHandler.Subscriptions; - for (int i = 0; i < subscribers.Count; i++) + for (var i = 0; i < subscribers.Count; i++) await subscribers[i].Invoke(arg1, arg2, arg3).ConfigureAwait(false); } - public static async Task InvokeAsync(this AsyncEvent> eventHandler, T1 arg1, T2 arg2, T3 arg3, T4 arg4) + + public static async Task InvokeAsync(this AsyncEvent> eventHandler, + T1 arg1, T2 arg2, T3 arg3, T4 arg4) { var subscribers = eventHandler.Subscriptions; - for (int i = 0; i < subscribers.Count; i++) + for (var i = 0; i < subscribers.Count; i++) await subscribers[i].Invoke(arg1, arg2, arg3, arg4).ConfigureAwait(false); } - public static async Task InvokeAsync(this AsyncEvent> eventHandler, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + + public static async Task InvokeAsync( + this AsyncEvent> eventHandler, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { var subscribers = eventHandler.Subscriptions; - for (int i = 0; i < subscribers.Count; i++) + for (var i = 0; i < subscribers.Count; i++) await subscribers[i].Invoke(arg1, arg2, arg3, arg4, arg5).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Core/Utils/Cacheable.cs b/src/Discord.Net.Core/Utils/Cacheable.cs index f17aa8699..9fed8d3c5 100644 --- a/src/Discord.Net.Core/Utils/Cacheable.cs +++ b/src/Discord.Net.Core/Utils/Cacheable.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; namespace Discord { /// - /// Contains an entity that may be cached. + /// Contains an entity that may be cached. /// /// The type of entity that is cached /// The type of this entity's ID @@ -13,23 +13,26 @@ namespace Discord where TId : IEquatable { /// - /// Is this entity cached? + /// Is this entity cached? /// public bool HasValue { get; } + /// - /// The ID of this entity. + /// The ID of this entity. /// public TId Id { get; } + /// - /// The entity, if it could be pulled from cache. + /// The entity, if it could be pulled from cache. /// /// - /// This value is not guaranteed to be set; in cases where the entity cannot be pulled from cache, it is null. + /// This value is not guaranteed to be set; in cases where the entity cannot be pulled from cache, it is null. /// public TEntity Value { get; } + private Func> DownloadFunc { get; } - internal Cacheable(TEntity value, TId id, bool hasValue , Func> downloadFunc) + internal Cacheable(TEntity value, TId id, bool hasValue, Func> downloadFunc) { Value = value; Id = id; @@ -38,22 +41,19 @@ namespace Discord } /// - /// Downloads this entity to cache. + /// Downloads this entity to cache. /// /// An awaitable Task containing the downloaded entity. /// Thrown when used from a user account. /// Thrown when the message is deleted. - public async Task DownloadAsync() - { - return await DownloadFunc(); - } + public async Task DownloadAsync() => await DownloadFunc(); /// - /// Returns the cached entity if it exists; otherwise downloads it. + /// Returns the cached entity if it exists; otherwise downloads it. /// /// An awaitable Task containing a cached or downloaded entity. /// Thrown when used from a user account. /// Thrown when the message is deleted and is not in cache. public async Task GetOrDownloadAsync() => HasValue ? Value : await DownloadAsync(); } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Utils/Comparers.cs b/src/Discord.Net.Core/Utils/Comparers.cs index d7641e897..9b9dafdc4 100644 --- a/src/Discord.Net.Core/Utils/Comparers.cs +++ b/src/Discord.Net.Core/Utils/Comparers.cs @@ -5,27 +5,37 @@ namespace Discord { public static class DiscordComparers { - // TODO: simplify with '??=' slated for C# 8.0 - public static IEqualityComparer UserComparer => _userComparer ?? (_userComparer = new EntityEqualityComparer()); - public static IEqualityComparer GuildComparer => _guildComparer ?? (_guildComparer = new EntityEqualityComparer()); - public static IEqualityComparer ChannelComparer => _channelComparer ?? (_channelComparer = new EntityEqualityComparer()); - public static IEqualityComparer RoleComparer => _roleComparer ?? (_roleComparer = new EntityEqualityComparer()); - public static IEqualityComparer MessageComparer => _messageComparer ?? (_messageComparer = new EntityEqualityComparer()); - private static IEqualityComparer _userComparer; private static IEqualityComparer _guildComparer; private static IEqualityComparer _channelComparer; private static IEqualityComparer _roleComparer; + private static IEqualityComparer _messageComparer; + // TODO: simplify with '??=' slated for C# 8.0 + public static IEqualityComparer UserComparer => + _userComparer ?? (_userComparer = new EntityEqualityComparer()); + + public static IEqualityComparer GuildComparer => + _guildComparer ?? (_guildComparer = new EntityEqualityComparer()); + + public static IEqualityComparer ChannelComparer => + _channelComparer ?? (_channelComparer = new EntityEqualityComparer()); + + public static IEqualityComparer RoleComparer => + _roleComparer ?? (_roleComparer = new EntityEqualityComparer()); + + public static IEqualityComparer MessageComparer => + _messageComparer ?? (_messageComparer = new EntityEqualityComparer()); + private sealed class EntityEqualityComparer : EqualityComparer where TEntity : IEntity where TId : IEquatable { public override bool Equals(TEntity x, TEntity y) { - bool xNull = x == null; - bool yNull = y == null; + var xNull = x == null; + var yNull = y == null; if (xNull && yNull) return true; @@ -36,10 +46,7 @@ namespace Discord return x.Id.Equals(y.Id); } - public override int GetHashCode(TEntity obj) - { - return obj?.Id.GetHashCode() ?? 0; - } + public override int GetHashCode(TEntity obj) => obj?.Id.GetHashCode() ?? 0; } } } diff --git a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs index 1fc11587e..489e864a5 100644 --- a/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs +++ b/src/Discord.Net.Core/Utils/ConcurrentHashSet.cs @@ -19,12 +19,12 @@ namespace Discord { get { - int now = Environment.TickCount; - if (s_processorCount == 0 || (now - s_lastProcessorCountRefreshTicks) >= PROCESSOR_COUNT_REFRESH_INTERVAL_MS) - { - s_processorCount = Environment.ProcessorCount; - s_lastProcessorCountRefreshTicks = now; - } + var now = Environment.TickCount; + if (s_processorCount != 0 && + now - s_lastProcessorCountRefreshTicks < PROCESSOR_COUNT_REFRESH_INTERVAL_MS) + return s_processorCount; + s_processorCount = Environment.ProcessorCount; + s_lastProcessorCountRefreshTicks = now; return s_processorCount; } @@ -33,109 +33,118 @@ namespace Discord //Based on https://github.com/dotnet/corefx/blob/master/src/System.Collections.Concurrent/src/System/Collections/Concurrent/ConcurrentDictionary.cs //Copyright (c) .NET Foundation and Contributors - [DebuggerDisplay("Count = {Count}")] + [DebuggerDisplay("Count = {" + nameof(Count) + "}")] internal class ConcurrentHashSet : IReadOnlyCollection { - private sealed class Tables - { - internal readonly Node[] _buckets; - internal readonly object[] _locks; - internal volatile int[] _countPerLock; + private const int DefaultCapacity = 31; + private const int MaxLockNumber = 1024; + private readonly IEqualityComparer _comparer; + private readonly bool _growLockArray; + private int _budget; - internal Tables(Node[] buckets, object[] locks, int[] countPerLock) - { - _buckets = buckets; - _locks = locks; - _countPerLock = countPerLock; - } + private volatile Tables _tables; + + public ConcurrentHashSet() + : this(DefaultConcurrencyLevel, DefaultCapacity, true, EqualityComparer.Default) + { } - private sealed class Node + + public ConcurrentHashSet(int concurrencyLevel, int capacity) + : this(concurrencyLevel, capacity, false, EqualityComparer.Default) { - internal readonly T _value; - internal volatile Node _next; - internal readonly int _hashcode; + } - internal Node(T key, int hashcode, Node next) - { - _value = key; - _next = next; - _hashcode = hashcode; - } + public ConcurrentHashSet(IEnumerable collection) + : this(collection, EqualityComparer.Default) + { } - private const int DefaultCapacity = 31; - private const int MaxLockNumber = 1024; + public ConcurrentHashSet(IEqualityComparer comparer) + : this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer) + { + } - private static int GetBucket(int hashcode, int bucketCount) + public ConcurrentHashSet(IEnumerable collection, IEqualityComparer comparer) + : this(comparer) { - int bucketNo = (hashcode & 0x7fffffff) % bucketCount; - return bucketNo; + if (collection == null) throw new ArgumentNullException(nameof(collection)); + InitializeFromCollection(collection); } - private static void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount, int lockCount) + + public ConcurrentHashSet(int concurrencyLevel, IEnumerable collection, IEqualityComparer comparer) + : this(concurrencyLevel, DefaultCapacity, false, comparer) { - bucketNo = (hashcode & 0x7fffffff) % bucketCount; - lockNo = bucketNo % lockCount; + if (collection == null) throw new ArgumentNullException(nameof(collection)); + if (comparer == null) throw new ArgumentNullException(nameof(comparer)); + InitializeFromCollection(collection); } - private static int DefaultConcurrencyLevel => ConcurrentHashSet.DefaultConcurrencyLevel; - private volatile Tables _tables; - private readonly IEqualityComparer _comparer; - private readonly bool _growLockArray; - private int _budget; + public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer comparer) + : this(concurrencyLevel, capacity, false, comparer) + { + } - public int Count + internal ConcurrentHashSet(int concurrencyLevel, int capacity, bool growLockArray, + IEqualityComparer comparer) { - get - { - int count = 0; + if (concurrencyLevel < 1) throw new ArgumentOutOfRangeException(nameof(concurrencyLevel)); + if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity)); + if (comparer == null) throw new ArgumentNullException(nameof(comparer)); - int acquiredLocks = 0; - try - { - AcquireAllLocks(ref acquiredLocks); + if (capacity < concurrencyLevel) + capacity = concurrencyLevel; - for (int i = 0; i < _tables._countPerLock.Length; i++) - count += _tables._countPerLock[i]; - } - finally { ReleaseLocks(0, acquiredLocks); } + var locks = new object[concurrencyLevel]; + for (var i = 0; i < locks.Length; i++) + locks[i] = new object(); - return count; - } + var countPerLock = new int[locks.Length]; + var buckets = new Node[capacity]; + _tables = new Tables(buckets, locks, countPerLock); + + _comparer = comparer; + _growLockArray = growLockArray; + _budget = buckets.Length / locks.Length; } + + private static int DefaultConcurrencyLevel => ConcurrentHashSet.DefaultConcurrencyLevel; + public bool IsEmpty { get { - int acquiredLocks = 0; + var acquiredLocks = 0; try { // Acquire all locks AcquireAllLocks(ref acquiredLocks); - for (int i = 0; i < _tables._countPerLock.Length; i++) - { + for (var i = 0; i < _tables._countPerLock.Length; i++) if (_tables._countPerLock[i] != 0) return false; - } } - finally { ReleaseLocks(0, acquiredLocks); } + finally + { + ReleaseLocks(0, acquiredLocks); + } return true; } } + public ReadOnlyCollection Values { get { - int locksAcquired = 0; + var locksAcquired = 0; try { AcquireAllLocks(ref locksAcquired); - List values = new List(); + var values = new List(); - for (int i = 0; i < _tables._buckets.Length; i++) + for (var i = 0; i < _tables._buckets.Length; i++) { - Node current = _tables._buckets[i]; + var current = _tables._buckets[i]; while (current != null) { values.Add(current._value); @@ -145,54 +154,67 @@ namespace Discord return new ReadOnlyCollection(values); } - finally { ReleaseLocks(0, locksAcquired); } + finally + { + ReleaseLocks(0, locksAcquired); + } } } - public ConcurrentHashSet() - : this(DefaultConcurrencyLevel, DefaultCapacity, true, EqualityComparer.Default) { } - public ConcurrentHashSet(int concurrencyLevel, int capacity) - : this(concurrencyLevel, capacity, false, EqualityComparer.Default) { } - public ConcurrentHashSet(IEnumerable collection) - : this(collection, EqualityComparer.Default) { } - public ConcurrentHashSet(IEqualityComparer comparer) - : this(DefaultConcurrencyLevel, DefaultCapacity, true, comparer) { } - public ConcurrentHashSet(IEnumerable collection, IEqualityComparer comparer) - : this(comparer) + public int Count { - if (collection == null) throw new ArgumentNullException(nameof(collection)); - InitializeFromCollection(collection); + get + { + var count = 0; + + var acquiredLocks = 0; + try + { + AcquireAllLocks(ref acquiredLocks); + + for (var i = 0; i < _tables._countPerLock.Length; i++) + count += _tables._countPerLock[i]; + } + finally + { + ReleaseLocks(0, acquiredLocks); + } + + return count; + } } - public ConcurrentHashSet(int concurrencyLevel, IEnumerable collection, IEqualityComparer comparer) - : this(concurrencyLevel, DefaultCapacity, false, comparer) - { - if (collection == null) throw new ArgumentNullException(nameof(collection)); - if (comparer == null) throw new ArgumentNullException(nameof(comparer)); - InitializeFromCollection(collection); - } - public ConcurrentHashSet(int concurrencyLevel, int capacity, IEqualityComparer comparer) - : this(concurrencyLevel, capacity, false, comparer) { } - internal ConcurrentHashSet(int concurrencyLevel, int capacity, bool growLockArray, IEqualityComparer comparer) + + public IEnumerator GetEnumerator() { - if (concurrencyLevel < 1) throw new ArgumentOutOfRangeException(nameof(concurrencyLevel)); - if (capacity < 0) throw new ArgumentOutOfRangeException(nameof(capacity)); - if (comparer == null) throw new ArgumentNullException(nameof(comparer)); - - if (capacity < concurrencyLevel) - capacity = concurrencyLevel; + var buckets = _tables._buckets; - object[] locks = new object[concurrencyLevel]; - for (int i = 0; i < locks.Length; i++) - locks[i] = new object(); + for (var i = 0; i < buckets.Length; i++) + { + var current = Volatile.Read(ref buckets[i]); - int[] countPerLock = new int[locks.Length]; - Node[] buckets = new Node[capacity]; - _tables = new Tables(buckets, locks, countPerLock); + while (current != null) + { + yield return current._value; + current = current._next; + } + } + } - _comparer = comparer; - _growLockArray = growLockArray; - _budget = buckets.Length / locks.Length; + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + private static int GetBucket(int hashcode, int bucketCount) + { + var bucketNo = (hashcode & 0x7fffffff) % bucketCount; + return bucketNo; + } + + private static void GetBucketAndLockNo(int hashcode, out int bucketNo, out int lockNo, int bucketCount, + int lockCount) + { + bucketNo = (hashcode & 0x7fffffff) % bucketCount; + lockNo = bucketNo % lockCount; } + private void InitializeFromCollection(IEnumerable collection) { foreach (var value in collection) @@ -206,19 +228,20 @@ namespace Discord if (_budget == 0) _budget = _tables._buckets.Length / _tables._locks.Length; } - + public bool ContainsKey(T value) { if (value == null) throw new ArgumentNullException("key"); return ContainsKeyInternal(value, _comparer.GetHashCode(value)); } + private bool ContainsKeyInternal(T value, int hashcode) { - Tables tables = _tables; + var tables = _tables; + + var bucketNo = GetBucket(hashcode, tables._buckets.Length); - int bucketNo = GetBucket(hashcode, tables._buckets.Length); - - Node n = Volatile.Read(ref tables._buckets[bucketNo]); + var n = Volatile.Read(ref tables._buckets[bucketNo]); while (n != null) { @@ -226,24 +249,26 @@ namespace Discord return true; n = n._next; } - + return false; } public bool TryAdd(T value) { - if (value == null) throw new ArgumentNullException("key"); + if (value == null) throw new ArgumentNullException("key"); return TryAddInternal(value, _comparer.GetHashCode(value), true); } + private bool TryAddInternal(T value, int hashcode, bool acquireLock) { while (true) { - Tables tables = _tables; - GetBucketAndLockNo(hashcode, out int bucketNo, out int lockNo, tables._buckets.Length, tables._locks.Length); + var tables = _tables; + GetBucketAndLockNo(hashcode, out var bucketNo, out var lockNo, tables._buckets.Length, + tables._locks.Length); - bool resizeDesired = false; - bool lockTaken = false; + var resizeDesired = false; + var lockTaken = false; try { if (acquireLock) @@ -253,7 +278,7 @@ namespace Discord continue; Node prev = null; - for (Node node = tables._buckets[bucketNo]; node != null; node = node._next) + for (var node = tables._buckets[bucketNo]; node != null; node = node._next) { if (hashcode == node._hashcode && _comparer.Equals(node._value, value)) return false; @@ -261,7 +286,10 @@ namespace Discord } Volatile.Write(ref tables._buckets[bucketNo], new Node(value, hashcode, tables._buckets[bucketNo])); - checked { tables._countPerLock[lockNo]++; } + checked + { + tables._countPerLock[lockNo]++; + } if (tables._countPerLock[lockNo] > _budget) resizeDesired = true; @@ -274,7 +302,7 @@ namespace Discord if (resizeDesired) GrowTable(tables); - + return true; } } @@ -283,14 +311,16 @@ namespace Discord { if (value == null) throw new ArgumentNullException("key"); return TryRemoveInternal(value); - } + } + private bool TryRemoveInternal(T value) { - int hashcode = _comparer.GetHashCode(value); + var hashcode = _comparer.GetHashCode(value); while (true) { - Tables tables = _tables; - GetBucketAndLockNo(hashcode, out int bucketNo, out int lockNo, tables._buckets.Length, tables._locks.Length); + var tables = _tables; + GetBucketAndLockNo(hashcode, out var bucketNo, out var lockNo, tables._buckets.Length, + tables._locks.Length); lock (tables._locks[lockNo]) { @@ -298,7 +328,7 @@ namespace Discord continue; Node prev = null; - for (Node curr = tables._buckets[bucketNo]; curr != null; curr = curr._next) + for (var curr = tables._buckets[bucketNo]; curr != null; curr = curr._next) { if (hashcode == curr._hashcode && _comparer.Equals(curr._value, value)) { @@ -311,6 +341,7 @@ namespace Discord tables._countPerLock[lockNo]--; return true; } + prev = curr; } } @@ -319,15 +350,16 @@ namespace Discord return false; } } - + public void Clear() { - int locksAcquired = 0; + var locksAcquired = 0; try { AcquireAllLocks(ref locksAcquired); - Tables newTables = new Tables(new Node[DefaultCapacity], _tables._locks, new int[_tables._countPerLock.Length]); + var newTables = new Tables(new Node[DefaultCapacity], _tables._locks, + new int[_tables._countPerLock.Length]); _tables = newTables; _budget = Math.Max(1, newTables._buckets.Length / newTables._locks.Length); } @@ -336,28 +368,11 @@ namespace Discord ReleaseLocks(0, locksAcquired); } } - - public IEnumerator GetEnumerator() - { - Node[] buckets = _tables._buckets; - - for (int i = 0; i < buckets.Length; i++) - { - Node current = Volatile.Read(ref buckets[i]); - - while (current != null) - { - yield return current._value; - current = current._next; - } - } - } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private void GrowTable(Tables tables) { const int MaxArrayLength = 0X7FEFFFFF; - int locksAcquired = 0; + var locksAcquired = 0; try { AcquireLocks(0, 1, ref locksAcquired); @@ -365,7 +380,7 @@ namespace Discord return; long approxCount = 0; - for (int i = 0; i < tables._countPerLock.Length; i++) + for (var i = 0; i < tables._countPerLock.Length; i++) approxCount += tables._countPerLock[i]; if (approxCount < tables._buckets.Length / 4) @@ -376,8 +391,8 @@ namespace Discord return; } - int newLength = 0; - bool maximizeTableSize = false; + var newLength = 0; + var maximizeTableSize = false; try { checked @@ -403,30 +418,34 @@ namespace Discord AcquireLocks(1, tables._locks.Length, ref locksAcquired); - object[] newLocks = tables._locks; + var newLocks = tables._locks; if (_growLockArray && tables._locks.Length < MaxLockNumber) { newLocks = new object[tables._locks.Length * 2]; Array.Copy(tables._locks, 0, newLocks, 0, tables._locks.Length); - for (int i = tables._locks.Length; i < newLocks.Length; i++) + for (var i = tables._locks.Length; i < newLocks.Length; i++) newLocks[i] = new object(); } - Node[] newBuckets = new Node[newLength]; - int[] newCountPerLock = new int[newLocks.Length]; + var newBuckets = new Node[newLength]; + var newCountPerLock = new int[newLocks.Length]; - for (int i = 0; i < tables._buckets.Length; i++) + for (var i = 0; i < tables._buckets.Length; i++) { - Node current = tables._buckets[i]; + var current = tables._buckets[i]; while (current != null) { - Node next = current._next; - GetBucketAndLockNo(current._hashcode, out int newBucketNo, out int newLockNo, newBuckets.Length, newLocks.Length); + var next = current._next; + GetBucketAndLockNo(current._hashcode, out var newBucketNo, out var newLockNo, newBuckets.Length, + newLocks.Length); newBuckets[newBucketNo] = new Node(current._value, current._hashcode, newBuckets[newBucketNo]); - checked { newCountPerLock[newLockNo]++; } + checked + { + newCountPerLock[newLockNo]++; + } current = next; } @@ -435,7 +454,10 @@ namespace Discord _budget = Math.Max(1, newBuckets.Length / newLocks.Length); _tables = new Tables(newBuckets, newLocks, newCountPerLock); } - finally { ReleaseLocks(0, locksAcquired); } + finally + { + ReleaseLocks(0, locksAcquired); + } } private void AcquireAllLocks(ref int locksAcquired) @@ -443,13 +465,14 @@ namespace Discord AcquireLocks(0, 1, ref locksAcquired); AcquireLocks(1, _tables._locks.Length, ref locksAcquired); } + private void AcquireLocks(int fromInclusive, int toExclusive, ref int locksAcquired) { - object[] locks = _tables._locks; + var locks = _tables._locks; - for (int i = fromInclusive; i < toExclusive; i++) + for (var i = fromInclusive; i < toExclusive; i++) { - bool lockTaken = false; + var lockTaken = false; try { Monitor.Enter(locks[i], ref lockTaken); @@ -461,10 +484,39 @@ namespace Discord } } } + private void ReleaseLocks(int fromInclusive, int toExclusive) { - for (int i = fromInclusive; i < toExclusive; i++) + for (var i = fromInclusive; i < toExclusive; i++) Monitor.Exit(_tables._locks[i]); - } + } + + private sealed class Tables + { + internal readonly Node[] _buckets; + internal readonly object[] _locks; + internal volatile int[] _countPerLock; + + internal Tables(Node[] buckets, object[] locks, int[] countPerLock) + { + _buckets = buckets; + _locks = locks; + _countPerLock = countPerLock; + } + } + + private sealed class Node + { + internal readonly int _hashcode; + internal readonly T _value; + internal volatile Node _next; + + internal Node(T key, int hashcode, Node next) + { + _value = key; + _next = next; + _hashcode = hashcode; + } + } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Utils/DateTimeUtils.cs b/src/Discord.Net.Core/Utils/DateTimeUtils.cs index e2a8faa75..4a8f73f6e 100644 --- a/src/Discord.Net.Core/Utils/DateTimeUtils.cs +++ b/src/Discord.Net.Core/Utils/DateTimeUtils.cs @@ -7,8 +7,8 @@ namespace Discord { public static DateTimeOffset FromTicks(long ticks) => new DateTimeOffset(ticks, TimeSpan.Zero); + public static DateTimeOffset? FromTicks(long? ticks) => ticks != null ? new DateTimeOffset(ticks.Value, TimeSpan.Zero) : (DateTimeOffset?)null; - } } diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index 6c69827b4..20d027d04 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Linq; using System.Text; namespace Discord @@ -9,20 +10,23 @@ namespace Discord private const char SanitizeChar = '\x200b'; //If the system can't be positive a user doesn't have a nickname, assume useNickname = true (source: Jake) - internal static string MentionUser(string id, bool useNickname = true) => useNickname ? $"<@!{id}>" : $"<@{id}>"; - public static string MentionUser(ulong id) => MentionUser(id.ToString(), true); + internal static string MentionUser(string id, bool useNickname = true) => + useNickname ? $"<@!{id}>" : $"<@{id}>"; + + public static string MentionUser(ulong id) => MentionUser(id.ToString()); internal static string MentionChannel(string id) => $"<#{id}>"; public static string MentionChannel(ulong id) => MentionChannel(id.ToString()); - internal static string MentionRole(string id) => $"<@&{id}>"; + internal static string MentionRole(string id) => $"<@&{id}>"; public static string MentionRole(ulong id) => MentionRole(id.ToString()); /// Parses a provided user mention string. public static ulong ParseUser(string text) { - if (TryParseUser(text, out ulong id)) + if (TryParseUser(text, out var id)) return id; throw new ArgumentException("Invalid mention format", nameof(text)); } + /// Tries to parse a provided user mention string. public static bool TryParseUser(string text, out ulong userId) { @@ -32,10 +36,11 @@ namespace Discord text = text.Substring(3, text.Length - 4); //<@!123> else text = text.Substring(2, text.Length - 3); //<@123> - + if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out userId)) return true; } + userId = 0; return false; } @@ -43,20 +48,22 @@ namespace Discord /// Parses a provided channel mention string. public static ulong ParseChannel(string text) { - if (TryParseChannel(text, out ulong id)) + if (TryParseChannel(text, out var id)) return id; throw new ArgumentException("Invalid mention format", nameof(text)); } + /// Tries to parse a provided channel mention string. public static bool TryParseChannel(string text, out ulong channelId) { if (text.Length >= 3 && text[0] == '<' && text[1] == '#' && text[text.Length - 1] == '>') { text = text.Substring(2, text.Length - 3); //<#123> - + if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out channelId)) return true; } + channelId = 0; return false; } @@ -64,36 +71,40 @@ namespace Discord /// Parses a provided role mention string. public static ulong ParseRole(string text) { - if (TryParseRole(text, out ulong id)) + if (TryParseRole(text, out var id)) return id; throw new ArgumentException("Invalid mention format", nameof(text)); } + /// Tries to parse a provided role mention string. public static bool TryParseRole(string text, out ulong roleId) { if (text.Length >= 4 && text[0] == '<' && text[1] == '@' && text[2] == '&' && text[text.Length - 1] == '>') { text = text.Substring(3, text.Length - 4); //<@&123> - + if (ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out roleId)) return true; } + roleId = 0; return false; } - internal static string Resolve(IMessage msg, int startIndex, TagHandling userHandling, TagHandling channelHandling, TagHandling roleHandling, TagHandling everyoneHandling, TagHandling emojiHandling) + internal static string Resolve(IMessage msg, int startIndex, TagHandling userHandling, + TagHandling channelHandling, TagHandling roleHandling, TagHandling everyoneHandling, + TagHandling emojiHandling) { var text = new StringBuilder(msg.Content.Substring(startIndex)); var tags = msg.Tags; - int indexOffset = -startIndex; + var indexOffset = -startIndex; foreach (var tag in tags) { if (tag.Index < startIndex) continue; - string newText = ""; + var newText = ""; switch (tag.Type) { case TagType.UserMention: @@ -121,159 +132,130 @@ namespace Discord newText = ResolveEmoji(tag, emojiHandling); break; } + text.Remove(tag.Index + indexOffset, tag.Length); text.Insert(tag.Index + indexOffset, newText); indexOffset += newText.Length - tag.Length; } + return text.ToString(); } + internal static string ResolveUserMention(ITag tag, TagHandling mode) { - if (mode != TagHandling.Remove) + if (mode == TagHandling.Remove) return ""; + var user = tag.Value as IUser; + var guildUser = user as IGuildUser; + switch (mode) { - var user = tag.Value as IUser; - var guildUser = user as IGuildUser; - switch (mode) - { - case TagHandling.Name: - if (user != null) - return $"@{guildUser?.Nickname ?? user?.Username}"; - else - return $""; - case TagHandling.NameNoPrefix: - if (user != null) - return $"{guildUser?.Nickname ?? user?.Username}"; - else - return $""; - case TagHandling.FullName: - if (user != null) - return $"@{user.Username}#{user.Discriminator}"; - else - return $""; - case TagHandling.FullNameNoPrefix: - if (user != null) - return $"{user.Username}#{user.Discriminator}"; - else - return $""; - case TagHandling.Sanitize: - if (guildUser != null && guildUser.Nickname == null) - return MentionUser($"{SanitizeChar}{tag.Key}", false); - else - return MentionUser($"{SanitizeChar}{tag.Key}", true); - } + case TagHandling.Name: + return user != null ? $"@{guildUser?.Nickname ?? user.Username}" : ""; + case TagHandling.NameNoPrefix: + return user != null ? $"{guildUser?.Nickname ?? user.Username}" : ""; + case TagHandling.FullName: + return user != null ? $"@{user.Username}#{user.Discriminator}" : ""; + case TagHandling.FullNameNoPrefix: + return user != null ? $"{user.Username}#{user.Discriminator}" : ""; + case TagHandling.Sanitize: + return MentionUser($"{SanitizeChar}{tag.Key}", guildUser == null || guildUser.Nickname != null); } + return ""; } + internal static string ResolveChannelMention(ITag tag, TagHandling mode) { - if (mode != TagHandling.Remove) + if (mode == TagHandling.Remove) return ""; + var channel = tag.Value as IChannel; + switch (mode) { - var channel = tag.Value as IChannel; - switch (mode) - { - case TagHandling.Name: - case TagHandling.FullName: - if (channel != null) - return $"#{channel.Name}"; - else - return $""; - case TagHandling.NameNoPrefix: - case TagHandling.FullNameNoPrefix: - if (channel != null) - return $"{channel.Name}"; - else - return $""; - case TagHandling.Sanitize: - return MentionChannel($"{SanitizeChar}{tag.Key}"); - } + case TagHandling.Name: + case TagHandling.FullName: + return channel != null ? $"#{channel.Name}" : ""; + case TagHandling.NameNoPrefix: + case TagHandling.FullNameNoPrefix: + return channel != null ? $"{channel.Name}" : ""; + case TagHandling.Sanitize: + return MentionChannel($"{SanitizeChar}{tag.Key}"); } + return ""; } + internal static string ResolveRoleMention(ITag tag, TagHandling mode) { - if (mode != TagHandling.Remove) + if (mode == TagHandling.Remove) return ""; + var role = tag.Value as IRole; + switch (mode) { - var role = tag.Value as IRole; - switch (mode) - { - case TagHandling.Name: - case TagHandling.FullName: - if (role != null) - return $"@{role.Name}"; - else - return $""; - case TagHandling.NameNoPrefix: - case TagHandling.FullNameNoPrefix: - if (role != null) - return $"{role.Name}"; - else - return $""; - case TagHandling.Sanitize: - return MentionRole($"{SanitizeChar}{tag.Key}"); - } + case TagHandling.Name: + case TagHandling.FullName: + return role != null ? $"@{role.Name}" : ""; + case TagHandling.NameNoPrefix: + case TagHandling.FullNameNoPrefix: + return role != null ? $"{role.Name}" : ""; + case TagHandling.Sanitize: + return MentionRole($"{SanitizeChar}{tag.Key}"); } + return ""; } + internal static string ResolveEveryoneMention(ITag tag, TagHandling mode) { - if (mode != TagHandling.Remove) + if (mode == TagHandling.Remove) return ""; + switch (mode) { - switch (mode) - { - case TagHandling.Name: - case TagHandling.FullName: - case TagHandling.NameNoPrefix: - case TagHandling.FullNameNoPrefix: - return "everyone"; - case TagHandling.Sanitize: - return $"@{SanitizeChar}everyone"; - } + case TagHandling.Name: + case TagHandling.FullName: + case TagHandling.NameNoPrefix: + case TagHandling.FullNameNoPrefix: + return "everyone"; + case TagHandling.Sanitize: + return $"@{SanitizeChar}everyone"; } return ""; } + internal static string ResolveHereMention(ITag tag, TagHandling mode) { - if (mode != TagHandling.Remove) + if (mode == TagHandling.Remove) return ""; + switch (mode) { - switch (mode) - { - case TagHandling.Name: - case TagHandling.FullName: - case TagHandling.NameNoPrefix: - case TagHandling.FullNameNoPrefix: - return "here"; - case TagHandling.Sanitize: - return $"@{SanitizeChar}here"; - } + case TagHandling.Name: + case TagHandling.FullName: + case TagHandling.NameNoPrefix: + case TagHandling.FullNameNoPrefix: + return "here"; + case TagHandling.Sanitize: + return $"@{SanitizeChar}here"; } return ""; } + internal static string ResolveEmoji(ITag tag, TagHandling mode) { - if (mode != TagHandling.Remove) - { - Emote emoji = (Emote)tag.Value; + if (mode == TagHandling.Remove) return ""; + var emoji = (Emote)tag.Value; - //Remove if its name contains any bad chars (prevents a few tag exploits) - for (int i = 0; i < emoji.Name.Length; i++) - { - char c = emoji.Name[i]; - if (!char.IsLetterOrDigit(c) && c != '_' && c != '-') - return ""; - } + //Remove if its name contains any bad chars (prevents a few tag exploits) + if (emoji.Name.Any(c => !char.IsLetterOrDigit(c) && c != '_' && c != '-')) + { + return ""; + } - switch (mode) - { - case TagHandling.Name: - case TagHandling.FullName: - return $":{emoji.Name}:"; - case TagHandling.NameNoPrefix: - case TagHandling.FullNameNoPrefix: - return $"{emoji.Name}"; - case TagHandling.Sanitize: - return $"<{emoji.Id}{SanitizeChar}:{SanitizeChar}{emoji.Name}>"; - } + switch (mode) + { + case TagHandling.Name: + case TagHandling.FullName: + return $":{emoji.Name}:"; + case TagHandling.NameNoPrefix: + case TagHandling.FullNameNoPrefix: + return $"{emoji.Name}"; + case TagHandling.Sanitize: + return $"<{emoji.Id}{SanitizeChar}:{SanitizeChar}{emoji.Name}>"; } + return ""; } } diff --git a/src/Discord.Net.Core/Utils/Optional.cs b/src/Discord.Net.Core/Utils/Optional.cs index eb3cbdca2..f19c7408f 100644 --- a/src/Discord.Net.Core/Utils/Optional.cs +++ b/src/Discord.Net.Core/Utils/Optional.cs @@ -4,7 +4,7 @@ using System.Diagnostics; namespace Discord { //Based on https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/Nullable.cs - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public struct Optional { public static Optional Unspecified => default(Optional); @@ -20,6 +20,7 @@ namespace Discord return _value; } } + /// Returns true if this value has been specified. public bool IsSpecified { get; } @@ -39,14 +40,16 @@ namespace Discord if (other == null) return false; return _value.Equals(other); } + public override int GetHashCode() => IsSpecified ? _value.GetHashCode() : 0; public override string ToString() => IsSpecified ? _value?.ToString() : null; - private string DebuggerDisplay => IsSpecified ? (_value?.ToString() ?? "") : ""; + private string DebuggerDisplay => IsSpecified ? _value?.ToString() ?? "" : ""; public static implicit operator Optional(T value) => new Optional(value); public static explicit operator T(Optional value) => value.Value; } + public static class Optional { public static Optional Create() => Optional.Unspecified; diff --git a/src/Discord.Net.Core/Utils/Paging/Page.cs b/src/Discord.Net.Core/Utils/Paging/Page.cs index 996d0ac6a..94154658d 100644 --- a/src/Discord.Net.Core/Utils/Paging/Page.cs +++ b/src/Discord.Net.Core/Utils/Paging/Page.cs @@ -7,7 +7,6 @@ namespace Discord internal class Page : IReadOnlyCollection { private readonly IReadOnlyCollection _items; - public int Index { get; } public Page(PageInfo info, IEnumerable source) { @@ -15,6 +14,8 @@ namespace Discord _items = source.ToImmutableArray(); } + public int Index { get; } + int IReadOnlyCollection.Count => _items.Count; IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); diff --git a/src/Discord.Net.Core/Utils/Paging/PageInfo.cs b/src/Discord.Net.Core/Utils/Paging/PageInfo.cs index 3b49225f2..68664c0d2 100644 --- a/src/Discord.Net.Core/Utils/Paging/PageInfo.cs +++ b/src/Discord.Net.Core/Utils/Paging/PageInfo.cs @@ -2,12 +2,6 @@ { internal class PageInfo { - public int Page { get; set; } - public ulong? Position { get; set; } - public int? Count { get; set; } - public int PageSize { get; set; } - public int? Remaining { get; set; } - internal PageInfo(ulong? pos, int? count, int pageSize) { Page = 1; @@ -19,5 +13,11 @@ if (Count != null && Count.Value < PageSize) PageSize = Count.Value; } + + public int Page { get; set; } + public ulong? Position { get; set; } + public int? Count { get; set; } + public int PageSize { get; set; } + public int? Remaining { get; set; } } } diff --git a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs index 96059bb4f..8dedd2f1c 100644 --- a/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs +++ b/src/Discord.Net.Core/Utils/Paging/PagedEnumerator.cs @@ -7,14 +7,15 @@ namespace Discord { internal class PagedAsyncEnumerable : IAsyncEnumerable> { - public int PageSize { get; } - - private readonly ulong? _start; private readonly int? _count; private readonly Func>> _getPage; private readonly Func, bool> _nextPage; - public PagedAsyncEnumerable(int pageSize, Func>> getPage, Func, bool> nextPage = null, + private readonly ulong? _start; + + public PagedAsyncEnumerable(int pageSize, + Func>> getPage, + Func, bool> nextPage = null, ulong? start = null, int? count = null) { PageSize = pageSize; @@ -25,13 +26,14 @@ namespace Discord _nextPage = nextPage; } + public int PageSize { get; } + public IAsyncEnumerator> GetEnumerator() => new Enumerator(this); + internal class Enumerator : IAsyncEnumerator> { - private readonly PagedAsyncEnumerable _source; private readonly PageInfo _info; - - public IReadOnlyCollection Current { get; private set; } + private readonly PagedAsyncEnumerable _source; public Enumerator(PagedAsyncEnumerable source) { @@ -39,6 +41,8 @@ namespace Discord _info = new PageInfo(source._start, source._count, source.PageSize); } + public IReadOnlyCollection Current { get; private set; } + public async Task MoveNext(CancellationToken cancelToken) { if (_info.Remaining == 0) @@ -60,18 +64,19 @@ namespace Discord if (Current.Count == 0) _info.Remaining = 0; } - _info.PageSize = _info.Remaining != null ? (int)Math.Min(_info.Remaining.Value, _source.PageSize) : _source.PageSize; - if (_info.Remaining != 0) - { - if (!_source._nextPage(_info, data)) - _info.Remaining = 0; - } + _info.PageSize = _info.Remaining != null + ? Math.Min(_info.Remaining.Value, _source.PageSize) + : _source.PageSize; + + if (_info.Remaining == 0) return true; + if (!_source._nextPage(_info, data)) + _info.Remaining = 0; return true; } - - public void Dispose() { Current = null; } + + public void Dispose() => Current = null; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Core/Utils/Permissions.cs b/src/Discord.Net.Core/Utils/Permissions.cs index 04e6784c3..0683496ea 100644 --- a/src/Discord.Net.Core/Utils/Permissions.cs +++ b/src/Discord.Net.Core/Utils/Permissions.cs @@ -1,4 +1,5 @@ -using System.Runtime.CompilerServices; +using System.Linq; +using System.Runtime.CompilerServices; namespace Discord { @@ -9,85 +10,97 @@ namespace Discord [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PermValue GetValue(ulong allow, ulong deny, ChannelPermission flag) => GetValue(allow, deny, (ulong)flag); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PermValue GetValue(ulong allow, ulong deny, GuildPermission flag) => GetValue(allow, deny, (ulong)flag); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PermValue GetValue(ulong allow, ulong deny, ulong flag) { if (HasFlag(allow, flag)) return PermValue.Allow; - else if (HasFlag(deny, flag)) + if (HasFlag(deny, flag)) return PermValue.Deny; - else - return PermValue.Inherit; + return PermValue.Inherit; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool GetValue(ulong value, ChannelPermission flag) => GetValue(value, (ulong)flag); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool GetValue(ulong value, GuildPermission flag) => GetValue(value, (ulong)flag); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool GetValue(ulong value, ulong flag) => HasFlag(value, flag); [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetValue(ref ulong rawValue, bool? value, ChannelPermission flag) => SetValue(ref rawValue, value, (ulong)flag); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetValue(ref ulong rawValue, bool? value, GuildPermission flag) => SetValue(ref rawValue, value, (ulong)flag); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetValue(ref ulong rawValue, bool? value, ulong flag) { - if (value.HasValue) + switch (value) { - if (value == true) + case null: + return; + case true: SetFlag(ref rawValue, flag); - else + break; + default: UnsetFlag(ref rawValue, flag); + break; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, ChannelPermission flag) => SetValue(ref allow, ref deny, value, (ulong)flag); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, GuildPermission flag) => SetValue(ref allow, ref deny, value, (ulong)flag); + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetValue(ref ulong allow, ref ulong deny, PermValue? value, ulong flag) { - if (value.HasValue) + if (!value.HasValue) return; + switch (value) { - switch (value) - { - case PermValue.Allow: - SetFlag(ref allow, flag); - UnsetFlag(ref deny, flag); - break; - case PermValue.Deny: - UnsetFlag(ref allow, flag); - SetFlag(ref deny, flag); - break; - default: - UnsetFlag(ref allow, flag); - UnsetFlag(ref deny, flag); - break; - } + case PermValue.Allow: + SetFlag(ref allow, flag); + UnsetFlag(ref deny, flag); + break; + case PermValue.Deny: + UnsetFlag(ref allow, flag); + SetFlag(ref deny, flag); + break; + default: + UnsetFlag(ref allow, flag); + UnsetFlag(ref deny, flag); + break; } } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static bool HasFlag(ulong value, ulong flag) => (value & flag) == flag; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SetFlag(ref ulong value, ulong flag) => value |= flag; + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void UnsetFlag(ref ulong value, ulong flag) => value &= ~flag; public static ChannelPermissions ToChannelPerms(IGuildChannel channel, ulong guildPermissions) => new ChannelPermissions(guildPermissions & ChannelPermissions.All(channel).RawValue); + public static ulong ResolveGuild(IGuild guild, IGuildUser user) { ulong resolvedPermissions = 0; @@ -98,11 +111,11 @@ namespace Discord resolvedPermissions = GuildPermissions.Webhook.RawValue; else { - foreach (var roleId in user.RoleIds) - resolvedPermissions |= guild.GetRole(roleId)?.Permissions.RawValue ?? 0; + resolvedPermissions = user.RoleIds.Aggregate(resolvedPermissions, (current, roleId) => current | (guild.GetRole(roleId)?.Permissions.RawValue ?? 0)); if (GetValue(resolvedPermissions, GuildPermission.Administrator)) resolvedPermissions = GuildPermissions.All.RawValue; //Administrators always have all permissions } + return resolvedPermissions; } @@ -112,20 +125,18 @@ namespace Discord }*/ public static ulong ResolveChannel(IGuild guild, IGuildUser user, IGuildChannel channel, ulong guildPermissions) { - ulong resolvedPermissions = 0; + ulong resolvedPermissions; - ulong mask = ChannelPermissions.All(channel).RawValue; + var mask = ChannelPermissions.All(channel).RawValue; if (GetValue(guildPermissions, GuildPermission.Administrator)) //Includes owner resolvedPermissions = mask; //Owners and administrators always have all permissions else { - OverwritePermissions? perms; - //Start with this user's guild permissions resolvedPermissions = guildPermissions; //Give/Take Everyone permissions - perms = channel.GetPermissionOverwrite(guild.EveryoneRole); + var perms = channel.GetPermissionOverwrite(guild.EveryoneRole); if (perms != null) resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; @@ -134,40 +145,40 @@ namespace Discord foreach (var roleId in user.RoleIds) { IRole role = null; - if (roleId != guild.EveryoneRole.Id && (role = guild.GetRole(roleId)) != null) - { - perms = channel.GetPermissionOverwrite(role); - if (perms != null) - { - allowedPermissions |= perms.Value.AllowValue; - deniedPermissions |= perms.Value.DenyValue; - } - } + if (roleId == guild.EveryoneRole.Id || (role = guild.GetRole(roleId)) == null) continue; + perms = channel.GetPermissionOverwrite(role); + if (perms == null) continue; + allowedPermissions |= perms.Value.AllowValue; + deniedPermissions |= perms.Value.DenyValue; } + resolvedPermissions = (resolvedPermissions & ~deniedPermissions) | allowedPermissions; //Give/Take User permissions perms = channel.GetPermissionOverwrite(user); if (perms != null) - resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; + resolvedPermissions = (resolvedPermissions & ~perms.Value.DenyValue) | perms.Value.AllowValue; - if (channel is ITextChannel textChannel) + switch (channel) { - if (!GetValue(resolvedPermissions, ChannelPermission.ViewChannel)) - { - //No read permission on a text channel removes all other permissions + case ITextChannel _ when !GetValue(resolvedPermissions, ChannelPermission.ViewChannel): resolvedPermissions = 0; - } - else if (!GetValue(resolvedPermissions, ChannelPermission.SendMessages)) - { - //No send permissions on a text channel removes all send-related permissions - resolvedPermissions &= ~(ulong)ChannelPermission.SendTTSMessages; - resolvedPermissions &= ~(ulong)ChannelPermission.MentionEveryone; - resolvedPermissions &= ~(ulong)ChannelPermission.EmbedLinks; - resolvedPermissions &= ~(ulong)ChannelPermission.AttachFiles; - } + break; + case ITextChannel _: + if (!GetValue(resolvedPermissions, ChannelPermission.SendMessages)) + { + //No send permissions on a text channel removes all send-related permissions + resolvedPermissions &= ~(ulong)ChannelPermission.SendTTSMessages; + resolvedPermissions &= ~(ulong)ChannelPermission.MentionEveryone; + resolvedPermissions &= ~(ulong)ChannelPermission.EmbedLinks; + resolvedPermissions &= ~(ulong)ChannelPermission.AttachFiles; + } + + break; } - resolvedPermissions &= mask; //Ensure we didnt get any permissions this channel doesnt support (from guildPerms, for example) + + resolvedPermissions &= + mask; //Ensure we didnt get any permissions this channel doesnt support (from guildPerms, for example) } return resolvedPermissions; diff --git a/src/Discord.Net.Core/Utils/Preconditions.cs b/src/Discord.Net.Core/Utils/Preconditions.cs index 300f584e4..bffbacea4 100644 --- a/src/Discord.Net.Core/Utils/Preconditions.cs +++ b/src/Discord.Net.Core/Utils/Preconditions.cs @@ -5,181 +5,574 @@ namespace Discord internal static class Preconditions { //Objects - public static void NotNull(T obj, string name, string msg = null) where T : class { if (obj == null) throw CreateNotNullException(name, msg); } - public static void NotNull(Optional obj, string name, string msg = null) where T : class { if (obj.IsSpecified && obj.Value == null) throw CreateNotNullException(name, msg); } + public static void NotNull(T obj, string name, string msg = null) where T : class + { + if (obj == null) throw CreateNotNullException(name, msg); + } + + public static void NotNull(Optional obj, string name, string msg = null) where T : class + { + if (obj.IsSpecified && obj.Value == null) throw CreateNotNullException(name, msg); + } private static ArgumentNullException CreateNotNullException(string name, string msg) { if (msg == null) return new ArgumentNullException(name); - else return new ArgumentNullException(name, msg); + return new ArgumentNullException(name, msg); } //Strings - public static void NotEmpty(string obj, string name, string msg = null) { if (obj.Length == 0) throw CreateNotEmptyException(name, msg); } - public static void NotEmpty(Optional obj, string name, string msg = null) { if (obj.IsSpecified && obj.Value.Length == 0) throw CreateNotEmptyException(name, msg); } + public static void NotEmpty(string obj, string name, string msg = null) + { + if (obj.Length == 0) throw CreateNotEmptyException(name, msg); + } + + public static void NotEmpty(Optional obj, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value.Length == 0) throw CreateNotEmptyException(name, msg); + } + public static void NotNullOrEmpty(string obj, string name, string msg = null) { if (obj == null) throw CreateNotNullException(name, msg); if (obj.Length == 0) throw CreateNotEmptyException(name, msg); } + public static void NotNullOrEmpty(Optional obj, string name, string msg = null) { - if (obj.IsSpecified) - { - if (obj.Value == null) throw CreateNotNullException(name, msg); - if (obj.Value.Length == 0) throw CreateNotEmptyException(name, msg); - } + if (!obj.IsSpecified) return; + if (obj.Value == null) throw CreateNotNullException(name, msg); + if (obj.Value.Length == 0) throw CreateNotEmptyException(name, msg); } + public static void NotNullOrWhitespace(string obj, string name, string msg = null) { if (obj == null) throw CreateNotNullException(name, msg); if (obj.Trim().Length == 0) throw CreateNotEmptyException(name, msg); } + public static void NotNullOrWhitespace(Optional obj, string name, string msg = null) { - if (obj.IsSpecified) - { - if (obj.Value == null) throw CreateNotNullException(name, msg); - if (obj.Value.Trim().Length == 0) throw CreateNotEmptyException(name, msg); - } - } + if (!obj.IsSpecified) return; + if (obj.Value == null) throw CreateNotNullException(name, msg); + if (obj.Value.Trim().Length == 0) throw CreateNotEmptyException(name, msg); + } private static ArgumentException CreateNotEmptyException(string name, string msg) { if (msg == null) return new ArgumentException("Argument cannot be blank", name); - else return new ArgumentException(name, msg); + return new ArgumentException(name, msg); } //Numerics - public static void NotEqual(sbyte obj, sbyte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(byte obj, byte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(short obj, short value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(ushort obj, ushort value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(int obj, int value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(uint obj, uint value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(long obj, long value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(ulong obj, ulong value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(sbyte? obj, sbyte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(byte? obj, byte value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(short? obj, short value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(ushort? obj, ushort value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(int? obj, int value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(uint? obj, uint value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(long? obj, long value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(ulong? obj, ulong value, string name, string msg = null) { if (obj == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } - public static void NotEqual(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); } + public static void NotEqual(sbyte obj, sbyte value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(byte obj, byte value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(short obj, short value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(ushort obj, ushort value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(int obj, int value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(uint obj, uint value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(long obj, long value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(ulong obj, ulong value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, sbyte value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, byte value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, short value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, ushort value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, int value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, uint value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, long value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, ulong value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(sbyte? obj, sbyte value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(byte? obj, byte value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(short? obj, short value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(ushort? obj, ushort value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(int? obj, int value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(uint? obj, uint value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(long? obj, long value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(ulong? obj, ulong value, string name, string msg = null) + { + if (obj == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, sbyte value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, byte value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, short value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, ushort value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, int value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, uint value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, long value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } + + public static void NotEqual(Optional obj, ulong value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value == value) throw CreateNotEqualException(name, msg, value); + } private static ArgumentException CreateNotEqualException(string name, string msg, T value) { if (msg == null) return new ArgumentException($"Value may not be equal to {value}", name); - else return new ArgumentException(msg, name); - } - - public static void AtLeast(sbyte obj, sbyte value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(byte obj, byte value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(short obj, short value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(ushort obj, ushort value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(int obj, int value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(uint obj, uint value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(long obj, long value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(ulong obj, ulong value, string name, string msg = null) { if (obj < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(Optional obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(Optional obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(Optional obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(Optional obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(Optional obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(Optional obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(Optional obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } - public static void AtLeast(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); } + return new ArgumentException(msg, name); + } + + public static void AtLeast(sbyte obj, sbyte value, string name, string msg = null) + { + if (obj < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(byte obj, byte value, string name, string msg = null) + { + if (obj < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(short obj, short value, string name, string msg = null) + { + if (obj < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(ushort obj, ushort value, string name, string msg = null) + { + if (obj < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(int obj, int value, string name, string msg = null) + { + if (obj < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(uint obj, uint value, string name, string msg = null) + { + if (obj < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(long obj, long value, string name, string msg = null) + { + if (obj < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(ulong obj, ulong value, string name, string msg = null) + { + if (obj < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(Optional obj, sbyte value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(Optional obj, byte value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(Optional obj, short value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(Optional obj, ushort value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(Optional obj, int value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(Optional obj, uint value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(Optional obj, long value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); + } + + public static void AtLeast(Optional obj, ulong value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value < value) throw CreateAtLeastException(name, msg, value); + } private static ArgumentException CreateAtLeastException(string name, string msg, T value) { if (msg == null) return new ArgumentException($"Value must be at least {value}", name); - else return new ArgumentException(msg, name); - } - - public static void GreaterThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(byte obj, byte value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(short obj, short value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(ushort obj, ushort value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(int obj, int value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(uint obj, uint value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(long obj, long value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(ulong obj, ulong value, string name, string msg = null) { if (obj <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(Optional obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(Optional obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(Optional obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(Optional obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(Optional obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(Optional obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(Optional obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } - public static void GreaterThan(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); } + return new ArgumentException(msg, name); + } + + public static void GreaterThan(sbyte obj, sbyte value, string name, string msg = null) + { + if (obj <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(byte obj, byte value, string name, string msg = null) + { + if (obj <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(short obj, short value, string name, string msg = null) + { + if (obj <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(ushort obj, ushort value, string name, string msg = null) + { + if (obj <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(int obj, int value, string name, string msg = null) + { + if (obj <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(uint obj, uint value, string name, string msg = null) + { + if (obj <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(long obj, long value, string name, string msg = null) + { + if (obj <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(ulong obj, ulong value, string name, string msg = null) + { + if (obj <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(Optional obj, sbyte value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(Optional obj, byte value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(Optional obj, short value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(Optional obj, ushort value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(Optional obj, int value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(Optional obj, uint value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(Optional obj, long value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); + } + + public static void GreaterThan(Optional obj, ulong value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value <= value) throw CreateGreaterThanException(name, msg, value); + } private static ArgumentException CreateGreaterThanException(string name, string msg, T value) { if (msg == null) return new ArgumentException($"Value must be greater than {value}", name); - else return new ArgumentException(msg, name); - } - - public static void AtMost(sbyte obj, sbyte value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(byte obj, byte value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(short obj, short value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(ushort obj, ushort value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(int obj, int value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(uint obj, uint value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(long obj, long value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(ulong obj, ulong value, string name, string msg = null) { if (obj > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(Optional obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(Optional obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(Optional obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(Optional obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(Optional obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(Optional obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(Optional obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } - public static void AtMost(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); } + return new ArgumentException(msg, name); + } + + public static void AtMost(sbyte obj, sbyte value, string name, string msg = null) + { + if (obj > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(byte obj, byte value, string name, string msg = null) + { + if (obj > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(short obj, short value, string name, string msg = null) + { + if (obj > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(ushort obj, ushort value, string name, string msg = null) + { + if (obj > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(int obj, int value, string name, string msg = null) + { + if (obj > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(uint obj, uint value, string name, string msg = null) + { + if (obj > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(long obj, long value, string name, string msg = null) + { + if (obj > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(ulong obj, ulong value, string name, string msg = null) + { + if (obj > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(Optional obj, sbyte value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(Optional obj, byte value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(Optional obj, short value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(Optional obj, ushort value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(Optional obj, int value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(Optional obj, uint value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(Optional obj, long value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); + } + + public static void AtMost(Optional obj, ulong value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value > value) throw CreateAtMostException(name, msg, value); + } private static ArgumentException CreateAtMostException(string name, string msg, T value) { if (msg == null) return new ArgumentException($"Value must be at most {value}", name); - else return new ArgumentException(msg, name); - } - - public static void LessThan(sbyte obj, sbyte value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(byte obj, byte value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(short obj, short value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(ushort obj, ushort value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(int obj, int value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(uint obj, uint value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(long obj, long value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(ulong obj, ulong value, string name, string msg = null) { if (obj >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(Optional obj, sbyte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(Optional obj, byte value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(Optional obj, short value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(Optional obj, ushort value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(Optional obj, int value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(Optional obj, uint value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(Optional obj, long value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } - public static void LessThan(Optional obj, ulong value, string name, string msg = null) { if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); } + return new ArgumentException(msg, name); + } + + public static void LessThan(sbyte obj, sbyte value, string name, string msg = null) + { + if (obj >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(byte obj, byte value, string name, string msg = null) + { + if (obj >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(short obj, short value, string name, string msg = null) + { + if (obj >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(ushort obj, ushort value, string name, string msg = null) + { + if (obj >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(int obj, int value, string name, string msg = null) + { + if (obj >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(uint obj, uint value, string name, string msg = null) + { + if (obj >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(long obj, long value, string name, string msg = null) + { + if (obj >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(ulong obj, ulong value, string name, string msg = null) + { + if (obj >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(Optional obj, sbyte value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(Optional obj, byte value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(Optional obj, short value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(Optional obj, ushort value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(Optional obj, int value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(Optional obj, uint value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(Optional obj, long value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); + } + + public static void LessThan(Optional obj, ulong value, string name, string msg = null) + { + if (obj.IsSpecified && obj.Value >= value) throw CreateLessThanException(name, msg, value); + } private static ArgumentException CreateLessThanException(string name, string msg, T value) { if (msg == null) return new ArgumentException($"Value must be less than {value}", name); - else return new ArgumentException(msg, name); + return new ArgumentException(msg, name); } // Bulk Delete @@ -193,13 +586,12 @@ namespace Discord throw new ArgumentOutOfRangeException(name, "Messages must be younger than two weeks old."); } } + public static void NotEveryoneRole(ulong[] roles, ulong guildId, string name) { for (var i = 0; i < roles.Length; i++) - { if (roles[i] == guildId) - throw new ArgumentException($"The everyone role cannot be assigned to a user", name); - } + throw new ArgumentException("The everyone role cannot be assigned to a user", name); } } } diff --git a/src/Discord.Net.Core/Utils/SnowflakeUtils.cs b/src/Discord.Net.Core/Utils/SnowflakeUtils.cs index eecebfb24..09cde1778 100644 --- a/src/Discord.Net.Core/Utils/SnowflakeUtils.cs +++ b/src/Discord.Net.Core/Utils/SnowflakeUtils.cs @@ -6,6 +6,7 @@ namespace Discord { public static DateTimeOffset FromSnowflake(ulong value) => DateTimeOffset.FromUnixTimeMilliseconds((long)((value >> 22) + 1420070400000UL)); + public static ulong ToSnowflake(DateTimeOffset value) => ((ulong)value.ToUnixTimeMilliseconds() - 1420070400000UL) << 22; } diff --git a/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs b/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs index 1894a8906..6d25b23cf 100644 --- a/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs +++ b/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs @@ -1,10 +1,11 @@ -using Discord.Net.WebSockets; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; +using Discord.Net.WebSockets; +using SuperSocket.ClientEngine; using WebSocket4Net; using WS4NetSocket = WebSocket4Net.WebSocket; @@ -12,17 +13,14 @@ namespace Discord.Net.Providers.WS4Net { internal class WS4NetClient : IWebSocketClient, IDisposable { - public event Func BinaryMessage; - public event Func TextMessage; - public event Func Closed; + private readonly Dictionary _headers; private readonly SemaphoreSlim _lock; - private readonly Dictionary _headers; - private WS4NetSocket _client; - private CancellationTokenSource _cancelTokenSource; private CancellationToken _cancelToken, _parentToken; - private ManualResetEventSlim _waitUntilConnect; + private CancellationTokenSource _cancelTokenSource; + private WS4NetSocket _client; private bool _isDisposed; + private readonly ManualResetEventSlim _waitUntilConnect; public WS4NetClient() { @@ -33,38 +31,79 @@ namespace Discord.Net.Providers.WS4Net _parentToken = CancellationToken.None; _waitUntilConnect = new ManualResetEventSlim(); } - private void Dispose(bool disposing) + + public void Dispose() => Dispose(true); + + public event Func BinaryMessage; + public event Func TextMessage; + public event Func Closed; + + public async Task ConnectAsync(string host) { - if (!_isDisposed) + await _lock.WaitAsync().ConfigureAwait(false); + try { - if (disposing) - DisconnectInternalAsync(true).GetAwaiter().GetResult(); - _isDisposed = true; + await ConnectInternalAsync(host).ConfigureAwait(false); + } + finally + { + _lock.Release(); } } - public void Dispose() + + public async Task DisconnectAsync() { - Dispose(true); + await _lock.WaitAsync().ConfigureAwait(false); + try + { + await DisconnectInternalAsync().ConfigureAwait(false); + } + finally + { + _lock.Release(); + } } - public async Task ConnectAsync(string host) + public void SetHeader(string key, string value) => _headers[key] = value; + + public void SetCancelToken(CancellationToken cancelToken) { - await _lock.WaitAsync().ConfigureAwait(false); + _parentToken = cancelToken; + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token) + .Token; + } + + public async Task SendAsync(byte[] data, int index, int count, bool isText) + { + await _lock.WaitAsync(_cancelToken).ConfigureAwait(false); try { - await ConnectInternalAsync(host).ConfigureAwait(false); + if (isText) + _client.Send(Encoding.UTF8.GetString(data, index, count)); + else + _client.Send(data, index, count); } finally { _lock.Release(); } } + + private void Dispose(bool disposing) + { + if (_isDisposed) return; + if (disposing) + DisconnectInternalAsync(true).GetAwaiter().GetResult(); + _isDisposed = true; + } + private async Task ConnectInternalAsync(string host) { await DisconnectInternalAsync().ConfigureAwait(false); _cancelTokenSource = new CancellationTokenSource(); - _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token) + .Token; _client = new WS4NetSocket(host, "", customHeaderItems: _headers.ToList()) { @@ -82,18 +121,6 @@ namespace Discord.Net.Providers.WS4Net _waitUntilConnect.Wait(_cancelToken); } - public async Task DisconnectAsync() - { - await _lock.WaitAsync().ConfigureAwait(false); - try - { - await DisconnectInternalAsync().ConfigureAwait(false); - } - finally - { - _lock.Release(); - } - } private Task DisconnectInternalAsync(bool isDisposing = false) { _cancelTokenSource.Cancel(); @@ -101,65 +128,44 @@ namespace Discord.Net.Providers.WS4Net return Task.Delay(0); if (_client.State == WebSocketState.Open) - { - try { _client.Close(1000, ""); } - catch { } - } + try + { + _client.Close(1000, ""); + } + catch + { + } _client.MessageReceived -= OnTextMessage; _client.DataReceived -= OnBinaryMessage; _client.Opened -= OnConnected; _client.Closed -= OnClosed; - try { _client.Dispose(); } - catch { } - _client = null; - - _waitUntilConnect.Reset(); - return Task.Delay(0); - } - - public void SetHeader(string key, string value) - { - _headers[key] = value; - } - public void SetCancelToken(CancellationToken cancelToken) - { - _parentToken = cancelToken; - _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; - } - - public async Task SendAsync(byte[] data, int index, int count, bool isText) - { - await _lock.WaitAsync(_cancelToken).ConfigureAwait(false); try { - if (isText) - _client.Send(Encoding.UTF8.GetString(data, index, count)); - else - _client.Send(data, index, count); + _client.Dispose(); } - finally + catch { - _lock.Release(); } + + _client = null; + + _waitUntilConnect.Reset(); + return Task.Delay(0); } - private void OnTextMessage(object sender, MessageReceivedEventArgs e) - { + private void OnTextMessage(object sender, MessageReceivedEventArgs e) => TextMessage(e.Message).GetAwaiter().GetResult(); - } - private void OnBinaryMessage(object sender, DataReceivedEventArgs e) - { - BinaryMessage(e.Data, 0, e.Data.Count()).GetAwaiter().GetResult(); - } - private void OnConnected(object sender, object e) - { - _waitUntilConnect.Set(); - } + + private void OnBinaryMessage(object sender, DataReceivedEventArgs e) => + BinaryMessage(e.Data, 0, e.Data.Length).GetAwaiter().GetResult(); + + private void OnConnected(object sender, object e) => _waitUntilConnect.Set(); + private void OnClosed(object sender, object e) { - var ex = (e as SuperSocket.ClientEngine.ErrorEventArgs)?.Exception ?? new Exception("Unexpected close"); + var ex = (e as ErrorEventArgs)?.Exception ?? new Exception("Unexpected close"); Closed(ex).GetAwaiter().GetResult(); } } diff --git a/src/Discord.Net.Rest/API/Common/Application.cs b/src/Discord.Net.Rest/API/Common/Application.cs index ca4c443f1..656c84769 100644 --- a/src/Discord.Net.Rest/API/Common/Application.cs +++ b/src/Discord.Net.Rest/API/Common/Application.cs @@ -5,20 +5,18 @@ namespace Discord.API { internal class Application { - [JsonProperty("description")] - public string Description { get; set; } - [JsonProperty("rpc_origins")] - public string[] RPCOrigins { get; set; } - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("icon")] - public string Icon { get; set; } + [JsonProperty("description")] public string Description { get; set; } - [JsonProperty("flags"), Int53] - public Optional Flags { get; set; } - [JsonProperty("owner")] - public Optional Owner { get; set; } + [JsonProperty("rpc_origins")] public string[] RPCOrigins { get; set; } + + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("icon")] public string Icon { get; set; } + + [JsonProperty("flags")] [Int53] public Optional Flags { get; set; } + + [JsonProperty("owner")] public Optional Owner { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Attachment.cs b/src/Discord.Net.Rest/API/Common/Attachment.cs index 4a651d9fa..bb058d858 100644 --- a/src/Discord.Net.Rest/API/Common/Attachment.cs +++ b/src/Discord.Net.Rest/API/Common/Attachment.cs @@ -5,19 +5,18 @@ namespace Discord.API { internal class Attachment { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("filename")] - public string Filename { get; set; } - [JsonProperty("size")] - public int Size { get; set; } - [JsonProperty("url")] - public string Url { get; set; } - [JsonProperty("proxy_url")] - public string ProxyUrl { get; set; } - [JsonProperty("height")] - public Optional Height { get; set; } - [JsonProperty("width")] - public Optional Width { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("filename")] public string Filename { get; set; } + + [JsonProperty("size")] public int Size { get; set; } + + [JsonProperty("url")] public string Url { get; set; } + + [JsonProperty("proxy_url")] public string ProxyUrl { get; set; } + + [JsonProperty("height")] public Optional Height { get; set; } + + [JsonProperty("width")] public Optional Width { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/AuditLog.cs b/src/Discord.Net.Rest/API/Common/AuditLog.cs index cd8ad147d..fe588fceb 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLog.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLog.cs @@ -4,13 +4,10 @@ namespace Discord.API { internal class AuditLog { - [JsonProperty("webhooks")] - public Webhook[] Webhooks { get; set; } + [JsonProperty("webhooks")] public Webhook[] Webhooks { get; set; } - [JsonProperty("users")] - public User[] Users { get; set; } + [JsonProperty("users")] public User[] Users { get; set; } - [JsonProperty("audit_log_entries")] - public AuditLogEntry[] Entries { get; set; } + [JsonProperty("audit_log_entries")] public AuditLogEntry[] Entries { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/AuditLogChange.cs b/src/Discord.Net.Rest/API/Common/AuditLogChange.cs index 44e585021..f4b7c6d03 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLogChange.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLogChange.cs @@ -5,13 +5,10 @@ namespace Discord.API { internal class AuditLogChange { - [JsonProperty("key")] - public string ChangedProperty { get; set; } + [JsonProperty("key")] public string ChangedProperty { get; set; } - [JsonProperty("new_value")] - public JToken NewValue { get; set; } + [JsonProperty("new_value")] public JToken NewValue { get; set; } - [JsonProperty("old_value")] - public JToken OldValue { get; set; } + [JsonProperty("old_value")] public JToken OldValue { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs index 80d9a9e97..7eec991cb 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLogEntry.cs @@ -4,23 +4,18 @@ namespace Discord.API { internal class AuditLogEntry { - [JsonProperty("target_id")] - public ulong? TargetId { get; set; } - [JsonProperty("user_id")] - public ulong UserId { get; set; } + [JsonProperty("target_id")] public ulong? TargetId { get; set; } - [JsonProperty("changes")] - public AuditLogChange[] Changes { get; set; } - [JsonProperty("options")] - public AuditLogOptions Options { get; set; } + [JsonProperty("user_id")] public ulong UserId { get; set; } - [JsonProperty("id")] - public ulong Id { get; set; } + [JsonProperty("changes")] public AuditLogChange[] Changes { get; set; } - [JsonProperty("action_type")] - public ActionType Action { get; set; } + [JsonProperty("options")] public AuditLogOptions Options { get; set; } - [JsonProperty("reason")] - public string Reason { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("action_type")] public ActionType Action { get; set; } + + [JsonProperty("reason")] public string Reason { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs index 65b401cce..d040417a7 100644 --- a/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs +++ b/src/Discord.Net.Rest/API/Common/AuditLogOptions.cs @@ -5,23 +5,20 @@ namespace Discord.API internal class AuditLogOptions { //Message delete - [JsonProperty("count")] - public int? MessageDeleteCount { get; set; } - [JsonProperty("channel_id")] - public ulong? MessageDeleteChannelId { get; set; } + [JsonProperty("count")] public int? MessageDeleteCount { get; set; } + + [JsonProperty("channel_id")] public ulong? MessageDeleteChannelId { get; set; } //Prune - [JsonProperty("delete_member_days")] - public int? PruneDeleteMemberDays { get; set; } - [JsonProperty("members_removed")] - public int? PruneMembersRemoved { get; set; } + [JsonProperty("delete_member_days")] public int? PruneDeleteMemberDays { get; set; } + + [JsonProperty("members_removed")] public int? PruneMembersRemoved { get; set; } //Overwrite Update - [JsonProperty("role_name")] - public string OverwriteRoleName { get; set; } - [JsonProperty("type")] - public string OverwriteType { get; set; } - [JsonProperty("id")] - public ulong? OverwriteTargetId { get; set; } + [JsonProperty("role_name")] public string OverwriteRoleName { get; set; } + + [JsonProperty("type")] public string OverwriteType { get; set; } + + [JsonProperty("id")] public ulong? OverwriteTargetId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Ban.cs b/src/Discord.Net.Rest/API/Common/Ban.cs index 202004f53..9bde66cd6 100644 --- a/src/Discord.Net.Rest/API/Common/Ban.cs +++ b/src/Discord.Net.Rest/API/Common/Ban.cs @@ -5,9 +5,8 @@ namespace Discord.API { internal class Ban { - [JsonProperty("user")] - public User User { get; set; } - [JsonProperty("reason")] - public string Reason { get; set; } + [JsonProperty("user")] public User User { get; set; } + + [JsonProperty("reason")] public string Reason { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Channel.cs b/src/Discord.Net.Rest/API/Common/Channel.cs index 97c35a57b..118fa99e6 100644 --- a/src/Discord.Net.Rest/API/Common/Channel.cs +++ b/src/Discord.Net.Rest/API/Common/Channel.cs @@ -1,51 +1,46 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; using System; +using Newtonsoft.Json; namespace Discord.API { internal class Channel { //Shared - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("type")] - public ChannelType Type { get; set; } - [JsonProperty("last_message_id")] - public ulong? LastMessageId { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("type")] public ChannelType Type { get; set; } + + [JsonProperty("last_message_id")] public ulong? LastMessageId { get; set; } //GuildChannel - [JsonProperty("guild_id")] - public Optional GuildId { get; set; } - [JsonProperty("name")] - public Optional Name { get; set; } - [JsonProperty("position")] - public Optional Position { get; set; } + [JsonProperty("guild_id")] public Optional GuildId { get; set; } + + [JsonProperty("name")] public Optional Name { get; set; } + + [JsonProperty("position")] public Optional Position { get; set; } + [JsonProperty("permission_overwrites")] public Optional PermissionOverwrites { get; set; } - [JsonProperty("parent_id")] - public ulong? CategoryId { get; set; } + + [JsonProperty("parent_id")] public ulong? CategoryId { get; set; } //TextChannel - [JsonProperty("topic")] - public Optional Topic { get; set; } - [JsonProperty("last_pin_timestamp")] - public Optional LastPinTimestamp { get; set; } - [JsonProperty("nsfw")] - public Optional Nsfw { get; set; } + [JsonProperty("topic")] public Optional Topic { get; set; } + + [JsonProperty("last_pin_timestamp")] public Optional LastPinTimestamp { get; set; } + + [JsonProperty("nsfw")] public Optional Nsfw { get; set; } //VoiceChannel - [JsonProperty("bitrate")] - public Optional Bitrate { get; set; } - [JsonProperty("user_limit")] - public Optional UserLimit { get; set; } + [JsonProperty("bitrate")] public Optional Bitrate { get; set; } + + [JsonProperty("user_limit")] public Optional UserLimit { get; set; } //PrivateChannel - [JsonProperty("recipients")] - public Optional Recipients { get; set; } + [JsonProperty("recipients")] public Optional Recipients { get; set; } //GroupChannel - [JsonProperty("icon")] - public Optional Icon { get; set; } + [JsonProperty("icon")] public Optional Icon { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Connection.cs b/src/Discord.Net.Rest/API/Common/Connection.cs index ad0a76ac1..3bcbf2b77 100644 --- a/src/Discord.Net.Rest/API/Common/Connection.cs +++ b/src/Discord.Net.Rest/API/Common/Connection.cs @@ -1,21 +1,19 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; using System.Collections.Generic; +using Newtonsoft.Json; namespace Discord.API { internal class Connection { - [JsonProperty("id")] - public string Id { get; set; } - [JsonProperty("type")] - public string Type { get; set; } - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("revoked")] - public bool Revoked { get; set; } + [JsonProperty("id")] public string Id { get; set; } + + [JsonProperty("type")] public string Type { get; set; } + + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("revoked")] public bool Revoked { get; set; } - [JsonProperty("integrations")] - public IReadOnlyCollection Integrations { get; set; } + [JsonProperty("integrations")] public IReadOnlyCollection Integrations { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Embed.cs b/src/Discord.Net.Rest/API/Common/Embed.cs index 1c9fa34e2..db8448763 100644 --- a/src/Discord.Net.Rest/API/Common/Embed.cs +++ b/src/Discord.Net.Rest/API/Common/Embed.cs @@ -7,31 +7,32 @@ namespace Discord.API { internal class Embed { - [JsonProperty("title")] - public string Title { get; set; } - [JsonProperty("description")] - public string Description { get; set; } - [JsonProperty("url")] - public string Url { get; set; } - [JsonProperty("color")] - public uint? Color { get; set; } - [JsonProperty("type"), JsonConverter(typeof(StringEnumConverter))] + [JsonProperty("title")] public string Title { get; set; } + + [JsonProperty("description")] public string Description { get; set; } + + [JsonProperty("url")] public string Url { get; set; } + + [JsonProperty("color")] public uint? Color { get; set; } + + [JsonProperty("type")] + [JsonConverter(typeof(StringEnumConverter))] public EmbedType Type { get; set; } - [JsonProperty("timestamp")] - public DateTimeOffset? Timestamp { get; set; } - [JsonProperty("author")] - public Optional Author { get; set; } - [JsonProperty("footer")] - public Optional Footer { get; set; } - [JsonProperty("video")] - public Optional Video { get; set; } - [JsonProperty("thumbnail")] - public Optional Thumbnail { get; set; } - [JsonProperty("image")] - public Optional Image { get; set; } - [JsonProperty("provider")] - public Optional Provider { get; set; } - [JsonProperty("fields")] - public Optional Fields { get; set; } + + [JsonProperty("timestamp")] public DateTimeOffset? Timestamp { get; set; } + + [JsonProperty("author")] public Optional Author { get; set; } + + [JsonProperty("footer")] public Optional Footer { get; set; } + + [JsonProperty("video")] public Optional Video { get; set; } + + [JsonProperty("thumbnail")] public Optional Thumbnail { get; set; } + + [JsonProperty("image")] public Optional Image { get; set; } + + [JsonProperty("provider")] public Optional Provider { get; set; } + + [JsonProperty("fields")] public Optional Fields { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs b/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs index 4381a9da3..7f1b730b1 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedAuthor.cs @@ -1,17 +1,15 @@ -using System; -using Newtonsoft.Json; +using Newtonsoft.Json; namespace Discord.API { internal class EmbedAuthor { - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("url")] - public string Url { get; set; } - [JsonProperty("icon_url")] - public string IconUrl { get; set; } - [JsonProperty("proxy_icon_url")] - public string ProxyIconUrl { get; set; } + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("url")] public string Url { get; set; } + + [JsonProperty("icon_url")] public string IconUrl { get; set; } + + [JsonProperty("proxy_icon_url")] public string ProxyIconUrl { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedField.cs b/src/Discord.Net.Rest/API/Common/EmbedField.cs index 6ce810f1a..6975ec69c 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedField.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedField.cs @@ -4,11 +4,10 @@ namespace Discord.API { internal class EmbedField { - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("value")] - public string Value { get; set; } - [JsonProperty("inline")] - public bool Inline { get; set; } + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("value")] public string Value { get; set; } + + [JsonProperty("inline")] public bool Inline { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedFooter.cs b/src/Discord.Net.Rest/API/Common/EmbedFooter.cs index 3dd7020d9..bb89a3ec0 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedFooter.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedFooter.cs @@ -1,15 +1,13 @@ -using System; -using Newtonsoft.Json; +using Newtonsoft.Json; namespace Discord.API { internal class EmbedFooter { - [JsonProperty("text")] - public string Text { get; set; } - [JsonProperty("icon_url")] - public string IconUrl { get; set; } - [JsonProperty("proxy_icon_url")] - public string ProxyIconUrl { get; set; } + [JsonProperty("text")] public string Text { get; set; } + + [JsonProperty("icon_url")] public string IconUrl { get; set; } + + [JsonProperty("proxy_icon_url")] public string ProxyIconUrl { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedImage.cs b/src/Discord.Net.Rest/API/Common/EmbedImage.cs index c6b3562a3..ba640a963 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedImage.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedImage.cs @@ -1,18 +1,16 @@ #pragma warning disable CS1591 -using System; using Newtonsoft.Json; namespace Discord.API { internal class EmbedImage { - [JsonProperty("url")] - public string Url { get; set; } - [JsonProperty("proxy_url")] - public string ProxyUrl { get; set; } - [JsonProperty("height")] - public Optional Height { get; set; } - [JsonProperty("width")] - public Optional Width { get; set; } + [JsonProperty("url")] public string Url { get; set; } + + [JsonProperty("proxy_url")] public string ProxyUrl { get; set; } + + [JsonProperty("height")] public Optional Height { get; set; } + + [JsonProperty("width")] public Optional Width { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedProvider.cs b/src/Discord.Net.Rest/API/Common/EmbedProvider.cs index 1658eda1a..86637abd4 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedProvider.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedProvider.cs @@ -1,14 +1,12 @@ #pragma warning disable CS1591 -using System; using Newtonsoft.Json; namespace Discord.API { internal class EmbedProvider { - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("url")] - public string Url { get; set; } + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("url")] public string Url { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs b/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs index 993beb72b..bb51a6492 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedThumbnail.cs @@ -1,18 +1,16 @@ #pragma warning disable CS1591 -using System; using Newtonsoft.Json; namespace Discord.API { internal class EmbedThumbnail { - [JsonProperty("url")] - public string Url { get; set; } - [JsonProperty("proxy_url")] - public string ProxyUrl { get; set; } - [JsonProperty("height")] - public Optional Height { get; set; } - [JsonProperty("width")] - public Optional Width { get; set; } + [JsonProperty("url")] public string Url { get; set; } + + [JsonProperty("proxy_url")] public string ProxyUrl { get; set; } + + [JsonProperty("height")] public Optional Height { get; set; } + + [JsonProperty("width")] public Optional Width { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/EmbedVideo.cs b/src/Discord.Net.Rest/API/Common/EmbedVideo.cs index 610cf58a8..f0659df3f 100644 --- a/src/Discord.Net.Rest/API/Common/EmbedVideo.cs +++ b/src/Discord.Net.Rest/API/Common/EmbedVideo.cs @@ -1,16 +1,14 @@ #pragma warning disable CS1591 -using System; using Newtonsoft.Json; namespace Discord.API { internal class EmbedVideo { - [JsonProperty("url")] - public string Url { get; set; } - [JsonProperty("height")] - public Optional Height { get; set; } - [JsonProperty("width")] - public Optional Width { get; set; } + [JsonProperty("url")] public string Url { get; set; } + + [JsonProperty("height")] public Optional Height { get; set; } + + [JsonProperty("width")] public Optional Width { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Emoji.cs b/src/Discord.Net.Rest/API/Common/Emoji.cs index 2bdfdcc36..23252be32 100644 --- a/src/Discord.Net.Rest/API/Common/Emoji.cs +++ b/src/Discord.Net.Rest/API/Common/Emoji.cs @@ -5,17 +5,16 @@ namespace Discord.API { internal class Emoji { - [JsonProperty("id")] - public ulong? Id { get; set; } - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("animated")] - public bool? Animated { get; set; } - [JsonProperty("roles")] - public ulong[] Roles { get; set; } - [JsonProperty("require_colons")] - public bool RequireColons { get; set; } - [JsonProperty("managed")] - public bool Managed { get; set; } + [JsonProperty("id")] public ulong? Id { get; set; } + + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("animated")] public bool? Animated { get; set; } + + [JsonProperty("roles")] public ulong[] Roles { get; set; } + + [JsonProperty("require_colons")] public bool RequireColons { get; set; } + + [JsonProperty("managed")] public bool Managed { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Game.cs b/src/Discord.Net.Rest/API/Common/Game.cs index 4cde8444a..617c07797 100644 --- a/src/Discord.Net.Rest/API/Common/Game.cs +++ b/src/Discord.Net.Rest/API/Common/Game.cs @@ -1,43 +1,39 @@ #pragma warning disable CS1591 +using System.Runtime.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -using System.Runtime.Serialization; namespace Discord.API { internal class Game { - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("url")] - public Optional StreamUrl { get; set; } - [JsonProperty("type")] - public Optional Type { get; set; } - [JsonProperty("details")] - public Optional Details { get; set; } - [JsonProperty("state")] - public Optional State { get; set; } - [JsonProperty("application_id")] - public Optional ApplicationId { get; set; } - [JsonProperty("assets")] - public Optional Assets { get; set; } - [JsonProperty("party")] - public Optional Party { get; set; } - [JsonProperty("secrets")] - public Optional Secrets { get; set; } - [JsonProperty("timestamps")] - public Optional Timestamps { get; set; } - [JsonProperty("instance")] - public Optional Instance { get; set; } - [JsonProperty("sync_id")] - public Optional SyncId { get; set; } - [JsonProperty("session_id")] - public Optional SessionId { get; set; } + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("url")] public Optional StreamUrl { get; set; } + + [JsonProperty("type")] public Optional Type { get; set; } + + [JsonProperty("details")] public Optional Details { get; set; } + + [JsonProperty("state")] public Optional State { get; set; } + + [JsonProperty("application_id")] public Optional ApplicationId { get; set; } + + [JsonProperty("assets")] public Optional Assets { get; set; } + + [JsonProperty("party")] public Optional Party { get; set; } + + [JsonProperty("secrets")] public Optional Secrets { get; set; } + + [JsonProperty("timestamps")] public Optional Timestamps { get; set; } + + [JsonProperty("instance")] public Optional Instance { get; set; } + + [JsonProperty("sync_id")] public Optional SyncId { get; set; } + + [JsonProperty("session_id")] public Optional SessionId { get; set; } [OnError] - internal void OnError(StreamingContext context, ErrorContext errorContext) - { - errorContext.Handled = true; - } + internal void OnError(StreamingContext context, ErrorContext errorContext) => errorContext.Handled = true; } } diff --git a/src/Discord.Net.Rest/API/Common/GameAssets.cs b/src/Discord.Net.Rest/API/Common/GameAssets.cs index 94a540769..e34073a70 100644 --- a/src/Discord.Net.Rest/API/Common/GameAssets.cs +++ b/src/Discord.Net.Rest/API/Common/GameAssets.cs @@ -4,13 +4,12 @@ namespace Discord.API { internal class GameAssets { - [JsonProperty("small_text")] - public Optional SmallText { get; set; } - [JsonProperty("small_image")] - public Optional SmallImage { get; set; } - [JsonProperty("large_text")] - public Optional LargeText { get; set; } - [JsonProperty("large_image")] - public Optional LargeImage { get; set; } + [JsonProperty("small_text")] public Optional SmallText { get; set; } + + [JsonProperty("small_image")] public Optional SmallImage { get; set; } + + [JsonProperty("large_text")] public Optional LargeText { get; set; } + + [JsonProperty("large_image")] public Optional LargeImage { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/GameParty.cs b/src/Discord.Net.Rest/API/Common/GameParty.cs index 4f8ce2654..3727ad38e 100644 --- a/src/Discord.Net.Rest/API/Common/GameParty.cs +++ b/src/Discord.Net.Rest/API/Common/GameParty.cs @@ -4,9 +4,8 @@ namespace Discord.API { internal class GameParty { - [JsonProperty("id")] - public string Id { get; set; } - [JsonProperty("size")] - public long[] Size { get; set; } + [JsonProperty("id")] public string Id { get; set; } + + [JsonProperty("size")] public long[] Size { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/GameSecrets.cs b/src/Discord.Net.Rest/API/Common/GameSecrets.cs index e70b48ff0..de85b8f4c 100644 --- a/src/Discord.Net.Rest/API/Common/GameSecrets.cs +++ b/src/Discord.Net.Rest/API/Common/GameSecrets.cs @@ -4,11 +4,10 @@ namespace Discord.API { internal class GameSecrets { - [JsonProperty("match")] - public string Match { get; set; } - [JsonProperty("join")] - public string Join { get; set; } - [JsonProperty("spectate")] - public string Spectate { get; set; } + [JsonProperty("match")] public string Match { get; set; } + + [JsonProperty("join")] public string Join { get; set; } + + [JsonProperty("spectate")] public string Spectate { get; set; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Rest/API/Common/GameTimestamps.cs b/src/Discord.Net.Rest/API/Common/GameTimestamps.cs index 5c6f10b86..354bbd811 100644 --- a/src/Discord.Net.Rest/API/Common/GameTimestamps.cs +++ b/src/Discord.Net.Rest/API/Common/GameTimestamps.cs @@ -8,8 +8,7 @@ namespace Discord.API [JsonProperty("start")] [UnixTimestamp] public Optional Start { get; set; } - [JsonProperty("end")] - [UnixTimestamp] - public Optional End { get; set; } + + [JsonProperty("end")] [UnixTimestamp] public Optional End { get; set; } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.Rest/API/Common/Guild.cs b/src/Discord.Net.Rest/API/Common/Guild.cs index 0ca1bc236..a188646c5 100644 --- a/src/Discord.Net.Rest/API/Common/Guild.cs +++ b/src/Discord.Net.Rest/API/Common/Guild.cs @@ -5,40 +5,40 @@ namespace Discord.API { internal class Guild { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("icon")] - public string Icon { get; set; } - [JsonProperty("splash")] - public string Splash { get; set; } - [JsonProperty("owner_id")] - public ulong OwnerId { get; set; } - [JsonProperty("region")] - public string Region { get; set; } - [JsonProperty("afk_channel_id")] - public ulong? AFKChannelId { get; set; } - [JsonProperty("afk_timeout")] - public int AFKTimeout { get; set; } - [JsonProperty("embed_enabled")] - public bool EmbedEnabled { get; set; } - [JsonProperty("embed_channel_id")] - public ulong? EmbedChannelId { get; set; } - [JsonProperty("system_channel_id")] - public ulong? SystemChannelId { get; set; } - [JsonProperty("verification_level")] - public VerificationLevel VerificationLevel { get; set; } - [JsonProperty("voice_states")] - public VoiceState[] VoiceStates { get; set; } - [JsonProperty("roles")] - public Role[] Roles { get; set; } - [JsonProperty("emojis")] - public Emoji[] Emojis { get; set; } - [JsonProperty("features")] - public string[] Features { get; set; } - [JsonProperty("mfa_level")] - public MfaLevel MfaLevel { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("icon")] public string Icon { get; set; } + + [JsonProperty("splash")] public string Splash { get; set; } + + [JsonProperty("owner_id")] public ulong OwnerId { get; set; } + + [JsonProperty("region")] public string Region { get; set; } + + [JsonProperty("afk_channel_id")] public ulong? AFKChannelId { get; set; } + + [JsonProperty("afk_timeout")] public int AFKTimeout { get; set; } + + [JsonProperty("embed_enabled")] public bool EmbedEnabled { get; set; } + + [JsonProperty("embed_channel_id")] public ulong? EmbedChannelId { get; set; } + + [JsonProperty("system_channel_id")] public ulong? SystemChannelId { get; set; } + + [JsonProperty("verification_level")] public VerificationLevel VerificationLevel { get; set; } + + [JsonProperty("voice_states")] public VoiceState[] VoiceStates { get; set; } + + [JsonProperty("roles")] public Role[] Roles { get; set; } + + [JsonProperty("emojis")] public Emoji[] Emojis { get; set; } + + [JsonProperty("features")] public string[] Features { get; set; } + + [JsonProperty("mfa_level")] public MfaLevel MfaLevel { get; set; } + [JsonProperty("default_message_notifications")] public DefaultMessageNotifications DefaultMessageNotifications { get; set; } } diff --git a/src/Discord.Net.Rest/API/Common/GuildEmbed.cs b/src/Discord.Net.Rest/API/Common/GuildEmbed.cs index ff8b8e180..a9a8b4903 100644 --- a/src/Discord.Net.Rest/API/Common/GuildEmbed.cs +++ b/src/Discord.Net.Rest/API/Common/GuildEmbed.cs @@ -5,9 +5,8 @@ namespace Discord.API { internal class GuildEmbed { - [JsonProperty("enabled")] - public bool Enabled { get; set; } - [JsonProperty("channel_id")] - public ulong ChannelId { get; set; } + [JsonProperty("enabled")] public bool Enabled { get; set; } + + [JsonProperty("channel_id")] public ulong ChannelId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/GuildMember.cs b/src/Discord.Net.Rest/API/Common/GuildMember.cs index 24ad17c14..6f7d745b1 100644 --- a/src/Discord.Net.Rest/API/Common/GuildMember.cs +++ b/src/Discord.Net.Rest/API/Common/GuildMember.cs @@ -1,22 +1,21 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; using System; +using Newtonsoft.Json; namespace Discord.API { internal class GuildMember { - [JsonProperty("user")] - public User User { get; set; } - [JsonProperty("nick")] - public Optional Nick { get; set; } - [JsonProperty("roles")] - public Optional Roles { get; set; } - [JsonProperty("joined_at")] - public Optional JoinedAt { get; set; } - [JsonProperty("deaf")] - public Optional Deaf { get; set; } - [JsonProperty("mute")] - public Optional Mute { get; set; } + [JsonProperty("user")] public User User { get; set; } + + [JsonProperty("nick")] public Optional Nick { get; set; } + + [JsonProperty("roles")] public Optional Roles { get; set; } + + [JsonProperty("joined_at")] public Optional JoinedAt { get; set; } + + [JsonProperty("deaf")] public Optional Deaf { get; set; } + + [JsonProperty("mute")] public Optional Mute { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Integration.cs b/src/Discord.Net.Rest/API/Common/Integration.cs index 821359975..f32b4ea5b 100644 --- a/src/Discord.Net.Rest/API/Common/Integration.cs +++ b/src/Discord.Net.Rest/API/Common/Integration.cs @@ -1,32 +1,31 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; using System; +using Newtonsoft.Json; namespace Discord.API { internal class Integration { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("type")] - public string Type { get; set; } - [JsonProperty("enabled")] - public bool Enabled { get; set; } - [JsonProperty("syncing")] - public bool Syncing { get; set; } - [JsonProperty("role_id")] - public ulong RoleId { get; set; } - [JsonProperty("expire_behavior")] - public ulong ExpireBehavior { get; set; } - [JsonProperty("expire_grace_period")] - public ulong ExpireGracePeriod { get; set; } - [JsonProperty("user")] - public User User { get; set; } - [JsonProperty("account")] - public IntegrationAccount Account { get; set; } - [JsonProperty("synced_at")] - public DateTimeOffset SyncedAt { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("type")] public string Type { get; set; } + + [JsonProperty("enabled")] public bool Enabled { get; set; } + + [JsonProperty("syncing")] public bool Syncing { get; set; } + + [JsonProperty("role_id")] public ulong RoleId { get; set; } + + [JsonProperty("expire_behavior")] public ulong ExpireBehavior { get; set; } + + [JsonProperty("expire_grace_period")] public ulong ExpireGracePeriod { get; set; } + + [JsonProperty("user")] public User User { get; set; } + + [JsonProperty("account")] public IntegrationAccount Account { get; set; } + + [JsonProperty("synced_at")] public DateTimeOffset SyncedAt { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs b/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs index 22831e795..cb6a4ebf6 100644 --- a/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs +++ b/src/Discord.Net.Rest/API/Common/IntegrationAccount.cs @@ -5,9 +5,8 @@ namespace Discord.API { internal class IntegrationAccount { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("name")] - public string Name { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("name")] public string Name { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Invite.cs b/src/Discord.Net.Rest/API/Common/Invite.cs index 649bc37ec..ca3b088e1 100644 --- a/src/Discord.Net.Rest/API/Common/Invite.cs +++ b/src/Discord.Net.Rest/API/Common/Invite.cs @@ -5,14 +5,15 @@ namespace Discord.API { internal class Invite { - [JsonProperty("code")] - public string Code { get; set; } - [JsonProperty("guild")] - public Optional Guild { get; set; } - [JsonProperty("channel")] - public InviteChannel Channel { get; set; } + [JsonProperty("code")] public string Code { get; set; } + + [JsonProperty("guild")] public Optional Guild { get; set; } + + [JsonProperty("channel")] public InviteChannel Channel { get; set; } + [JsonProperty("approximate_presence_count")] public Optional PresenceCount { get; set; } + [JsonProperty("approximate_member_count")] public Optional MemberCount { get; set; } } diff --git a/src/Discord.Net.Rest/API/Common/InviteChannel.cs b/src/Discord.Net.Rest/API/Common/InviteChannel.cs index f8f2a34f2..479676f00 100644 --- a/src/Discord.Net.Rest/API/Common/InviteChannel.cs +++ b/src/Discord.Net.Rest/API/Common/InviteChannel.cs @@ -5,11 +5,10 @@ namespace Discord.API { internal class InviteChannel { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("type")] - public int Type { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("type")] public int Type { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/InviteGuild.cs b/src/Discord.Net.Rest/API/Common/InviteGuild.cs index 3d6d7cd74..4ff2d1fb5 100644 --- a/src/Discord.Net.Rest/API/Common/InviteGuild.cs +++ b/src/Discord.Net.Rest/API/Common/InviteGuild.cs @@ -5,11 +5,10 @@ namespace Discord.API { internal class InviteGuild { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("splash_hash")] - public string SplashHash { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("splash_hash")] public string SplashHash { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/InviteMetadata.cs b/src/Discord.Net.Rest/API/Common/InviteMetadata.cs index ca019b79b..a58240676 100644 --- a/src/Discord.Net.Rest/API/Common/InviteMetadata.cs +++ b/src/Discord.Net.Rest/API/Common/InviteMetadata.cs @@ -1,24 +1,23 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; using System; +using Newtonsoft.Json; namespace Discord.API { internal class InviteMetadata : Invite { - [JsonProperty("inviter")] - public User Inviter { get; set; } - [JsonProperty("uses")] - public Optional Uses { get; set; } - [JsonProperty("max_uses")] - public Optional MaxUses { get; set; } - [JsonProperty("max_age")] - public Optional MaxAge { get; set; } - [JsonProperty("temporary")] - public bool Temporary { get; set; } - [JsonProperty("created_at")] - public Optional CreatedAt { get; set; } - [JsonProperty("revoked")] - public bool Revoked { get; set; } + [JsonProperty("inviter")] public User Inviter { get; set; } + + [JsonProperty("uses")] public Optional Uses { get; set; } + + [JsonProperty("max_uses")] public Optional MaxUses { get; set; } + + [JsonProperty("max_age")] public Optional MaxAge { get; set; } + + [JsonProperty("temporary")] public bool Temporary { get; set; } + + [JsonProperty("created_at")] public Optional CreatedAt { get; set; } + + [JsonProperty("revoked")] public bool Revoked { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Message.cs b/src/Discord.Net.Rest/API/Common/Message.cs index 229249ccf..ae0d53784 100644 --- a/src/Discord.Net.Rest/API/Common/Message.cs +++ b/src/Discord.Net.Rest/API/Common/Message.cs @@ -1,48 +1,47 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; using System; +using Newtonsoft.Json; namespace Discord.API { internal class Message { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("type")] - public MessageType Type { get; set; } - [JsonProperty("channel_id")] - public ulong ChannelId { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("type")] public MessageType Type { get; set; } + + [JsonProperty("channel_id")] public ulong ChannelId { get; set; } + // ALWAYS sent on WebSocket messages - [JsonProperty("guild_id")] - public Optional GuildId { get; set; } - [JsonProperty("webhook_id")] - public Optional WebhookId { get; set; } - [JsonProperty("author")] - public Optional Author { get; set; } + [JsonProperty("guild_id")] public Optional GuildId { get; set; } + + [JsonProperty("webhook_id")] public Optional WebhookId { get; set; } + + [JsonProperty("author")] public Optional Author { get; set; } + // ALWAYS sent on WebSocket messages - [JsonProperty("member")] - public Optional Member { get; set; } - [JsonProperty("content")] - public Optional Content { get; set; } - [JsonProperty("timestamp")] - public Optional Timestamp { get; set; } - [JsonProperty("edited_timestamp")] - public Optional EditedTimestamp { get; set; } - [JsonProperty("tts")] - public Optional IsTextToSpeech { get; set; } - [JsonProperty("mention_everyone")] - public Optional MentionEveryone { get; set; } - [JsonProperty("mentions")] - public Optional[]> UserMentions { get; set; } - [JsonProperty("mention_roles")] - public Optional RoleMentions { get; set; } - [JsonProperty("attachments")] - public Optional Attachments { get; set; } - [JsonProperty("embeds")] - public Optional Embeds { get; set; } - [JsonProperty("pinned")] - public Optional Pinned { get; set; } - [JsonProperty("reactions")] - public Optional Reactions { get; set; } + [JsonProperty("member")] public Optional Member { get; set; } + + [JsonProperty("content")] public Optional Content { get; set; } + + [JsonProperty("timestamp")] public Optional Timestamp { get; set; } + + [JsonProperty("edited_timestamp")] public Optional EditedTimestamp { get; set; } + + [JsonProperty("tts")] public Optional IsTextToSpeech { get; set; } + + [JsonProperty("mention_everyone")] public Optional MentionEveryone { get; set; } + + [JsonProperty("mentions")] public Optional[]> UserMentions { get; set; } + + [JsonProperty("mention_roles")] public Optional RoleMentions { get; set; } + + [JsonProperty("attachments")] public Optional Attachments { get; set; } + + [JsonProperty("embeds")] public Optional Embeds { get; set; } + + [JsonProperty("pinned")] public Optional Pinned { get; set; } + + [JsonProperty("reactions")] public Optional Reactions { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Overwrite.cs b/src/Discord.Net.Rest/API/Common/Overwrite.cs index 1ba836127..bb6393c8f 100644 --- a/src/Discord.Net.Rest/API/Common/Overwrite.cs +++ b/src/Discord.Net.Rest/API/Common/Overwrite.cs @@ -5,13 +5,12 @@ namespace Discord.API { internal class Overwrite { - [JsonProperty("id")] - public ulong TargetId { get; set; } - [JsonProperty("type")] - public PermissionTarget TargetType { get; set; } - [JsonProperty("deny"), Int53] - public ulong Deny { get; set; } - [JsonProperty("allow"), Int53] - public ulong Allow { get; set; } + [JsonProperty("id")] public ulong TargetId { get; set; } + + [JsonProperty("type")] public PermissionTarget TargetType { get; set; } + + [JsonProperty("deny")] [Int53] public ulong Deny { get; set; } + + [JsonProperty("allow")] [Int53] public ulong Allow { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Presence.cs b/src/Discord.Net.Rest/API/Common/Presence.cs index 2902b7ce3..e75cc3543 100644 --- a/src/Discord.Net.Rest/API/Common/Presence.cs +++ b/src/Discord.Net.Rest/API/Common/Presence.cs @@ -5,18 +5,16 @@ namespace Discord.API { internal class Presence { - [JsonProperty("user")] - public User User { get; set; } - [JsonProperty("guild_id")] - public Optional GuildId { get; set; } - [JsonProperty("status")] - public UserStatus Status { get; set; } - [JsonProperty("game")] - public Game Game { get; set; } + [JsonProperty("user")] public User User { get; set; } - [JsonProperty("roles")] - public Optional Roles { get; set; } - [JsonProperty("nick")] - public Optional Nick { get; set; } + [JsonProperty("guild_id")] public Optional GuildId { get; set; } + + [JsonProperty("status")] public UserStatus Status { get; set; } + + [JsonProperty("game")] public Game Game { get; set; } + + [JsonProperty("roles")] public Optional Roles { get; set; } + + [JsonProperty("nick")] public Optional Nick { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Reaction.cs b/src/Discord.Net.Rest/API/Common/Reaction.cs index 4d368ab2d..bd6a1794d 100644 --- a/src/Discord.Net.Rest/API/Common/Reaction.cs +++ b/src/Discord.Net.Rest/API/Common/Reaction.cs @@ -4,11 +4,10 @@ namespace Discord.API { internal class Reaction { - [JsonProperty("count")] - public int Count { get; set; } - [JsonProperty("me")] - public bool Me { get; set; } - [JsonProperty("emoji")] - public Emoji Emoji { get; set; } + [JsonProperty("count")] public int Count { get; set; } + + [JsonProperty("me")] public bool Me { get; set; } + + [JsonProperty("emoji")] public Emoji Emoji { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/ReadState.cs b/src/Discord.Net.Rest/API/Common/ReadState.cs index 6ea6e4bd0..1662c550d 100644 --- a/src/Discord.Net.Rest/API/Common/ReadState.cs +++ b/src/Discord.Net.Rest/API/Common/ReadState.cs @@ -5,11 +5,10 @@ namespace Discord.API { internal class ReadState { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("mention_count")] - public int MentionCount { get; set; } - [JsonProperty("last_message_id")] - public Optional LastMessageId { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("mention_count")] public int MentionCount { get; set; } + + [JsonProperty("last_message_id")] public Optional LastMessageId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Relationship.cs b/src/Discord.Net.Rest/API/Common/Relationship.cs index ecbb96f80..f25aac56d 100644 --- a/src/Discord.Net.Rest/API/Common/Relationship.cs +++ b/src/Discord.Net.Rest/API/Common/Relationship.cs @@ -5,11 +5,10 @@ namespace Discord.API { internal class Relationship { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("user")] - public User User { get; set; } - [JsonProperty("type")] - public RelationshipType Type { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("user")] public User User { get; set; } + + [JsonProperty("type")] public RelationshipType Type { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Role.cs b/src/Discord.Net.Rest/API/Common/Role.cs index 856a8695f..9e4d923b5 100644 --- a/src/Discord.Net.Rest/API/Common/Role.cs +++ b/src/Discord.Net.Rest/API/Common/Role.cs @@ -5,21 +5,20 @@ namespace Discord.API { internal class Role { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("color")] - public uint Color { get; set; } - [JsonProperty("hoist")] - public bool Hoist { get; set; } - [JsonProperty("mentionable")] - public bool Mentionable { get; set; } - [JsonProperty("position")] - public int Position { get; set; } - [JsonProperty("permissions"), Int53] - public ulong Permissions { get; set; } - [JsonProperty("managed")] - public bool Managed { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("color")] public uint Color { get; set; } + + [JsonProperty("hoist")] public bool Hoist { get; set; } + + [JsonProperty("mentionable")] public bool Mentionable { get; set; } + + [JsonProperty("position")] public int Position { get; set; } + + [JsonProperty("permissions")] [Int53] public ulong Permissions { get; set; } + + [JsonProperty("managed")] public bool Managed { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/User.cs b/src/Discord.Net.Rest/API/Common/User.cs index d49d24623..03ccd9b29 100644 --- a/src/Discord.Net.Rest/API/Common/User.cs +++ b/src/Discord.Net.Rest/API/Common/User.cs @@ -5,23 +5,21 @@ namespace Discord.API { internal class User { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("username")] - public Optional Username { get; set; } - [JsonProperty("discriminator")] - public Optional Discriminator { get; set; } - [JsonProperty("bot")] - public Optional Bot { get; set; } - [JsonProperty("avatar")] - public Optional Avatar { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("username")] public Optional Username { get; set; } + + [JsonProperty("discriminator")] public Optional Discriminator { get; set; } + + [JsonProperty("bot")] public Optional Bot { get; set; } + + [JsonProperty("avatar")] public Optional Avatar { get; set; } //CurrentUser - [JsonProperty("verified")] - public Optional Verified { get; set; } - [JsonProperty("email")] - public Optional Email { get; set; } - [JsonProperty("mfa_enabled")] - public Optional MfaEnabled { get; set; } + [JsonProperty("verified")] public Optional Verified { get; set; } + + [JsonProperty("email")] public Optional Email { get; set; } + + [JsonProperty("mfa_enabled")] public Optional MfaEnabled { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/UserGuild.cs b/src/Discord.Net.Rest/API/Common/UserGuild.cs index f4f763885..4d39084e1 100644 --- a/src/Discord.Net.Rest/API/Common/UserGuild.cs +++ b/src/Discord.Net.Rest/API/Common/UserGuild.cs @@ -5,15 +5,14 @@ namespace Discord.API { internal class UserGuild { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("icon")] - public string Icon { get; set; } - [JsonProperty("owner")] - public bool Owner { get; set; } - [JsonProperty("permissions"), Int53] - public ulong Permissions { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } + + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("icon")] public string Icon { get; set; } + + [JsonProperty("owner")] public bool Owner { get; set; } + + [JsonProperty("permissions")] [Int53] public ulong Permissions { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/VoiceRegion.cs b/src/Discord.Net.Rest/API/Common/VoiceRegion.cs index 606af07bd..3b22970a0 100644 --- a/src/Discord.Net.Rest/API/Common/VoiceRegion.cs +++ b/src/Discord.Net.Rest/API/Common/VoiceRegion.cs @@ -5,17 +5,16 @@ namespace Discord.API { internal class VoiceRegion { - [JsonProperty("id")] - public string Id { get; set; } - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("vip")] - public bool IsVip { get; set; } - [JsonProperty("optimal")] - public bool IsOptimal { get; set; } - [JsonProperty("deprecated")] - public bool IsDeprecated { get; set; } - [JsonProperty("custom")] - public bool IsCustom { get; set; } + [JsonProperty("id")] public string Id { get; set; } + + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("vip")] public bool IsVip { get; set; } + + [JsonProperty("optimal")] public bool IsOptimal { get; set; } + + [JsonProperty("deprecated")] public bool IsDeprecated { get; set; } + + [JsonProperty("custom")] public bool IsCustom { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/VoiceState.cs b/src/Discord.Net.Rest/API/Common/VoiceState.cs index b1f937b09..aa13e5b55 100644 --- a/src/Discord.Net.Rest/API/Common/VoiceState.cs +++ b/src/Discord.Net.Rest/API/Common/VoiceState.cs @@ -5,26 +5,25 @@ namespace Discord.API { internal class VoiceState { - [JsonProperty("guild_id")] - public ulong? GuildId { get; set; } - [JsonProperty("channel_id")] - public ulong? ChannelId { get; set; } - [JsonProperty("user_id")] - public ulong UserId { get; set; } + [JsonProperty("guild_id")] public ulong? GuildId { get; set; } + + [JsonProperty("channel_id")] public ulong? ChannelId { get; set; } + + [JsonProperty("user_id")] public ulong UserId { get; set; } + // ALWAYS sent over WebSocket, never on REST - [JsonProperty("member")] - public Optional Member { get; set; } - [JsonProperty("session_id")] - public string SessionId { get; set; } - [JsonProperty("deaf")] - public bool Deaf { get; set; } - [JsonProperty("mute")] - public bool Mute { get; set; } - [JsonProperty("self_deaf")] - public bool SelfDeaf { get; set; } - [JsonProperty("self_mute")] - public bool SelfMute { get; set; } - [JsonProperty("suppress")] - public bool Suppress { get; set; } + [JsonProperty("member")] public Optional Member { get; set; } + + [JsonProperty("session_id")] public string SessionId { get; set; } + + [JsonProperty("deaf")] public bool Deaf { get; set; } + + [JsonProperty("mute")] public bool Mute { get; set; } + + [JsonProperty("self_deaf")] public bool SelfDeaf { get; set; } + + [JsonProperty("self_mute")] public bool SelfMute { get; set; } + + [JsonProperty("suppress")] public bool Suppress { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Common/Webhook.cs b/src/Discord.Net.Rest/API/Common/Webhook.cs index cbd5fdad5..05cb57df2 100644 --- a/src/Discord.Net.Rest/API/Common/Webhook.cs +++ b/src/Discord.Net.Rest/API/Common/Webhook.cs @@ -5,21 +5,18 @@ namespace Discord.API { internal class Webhook { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("channel_id")] - public ulong ChannelId { get; set; } - [JsonProperty("token")] - public string Token { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } - [JsonProperty("name")] - public Optional Name { get; set; } - [JsonProperty("avatar")] - public Optional Avatar { get; set; } - [JsonProperty("guild_id")] - public Optional GuildId { get; set; } + [JsonProperty("channel_id")] public ulong ChannelId { get; set; } - [JsonProperty("user")] - public Optional Creator { get; set; } + [JsonProperty("token")] public string Token { get; set; } + + [JsonProperty("name")] public Optional Name { get; set; } + + [JsonProperty("avatar")] public Optional Avatar { get; set; } + + [JsonProperty("guild_id")] public Optional GuildId { get; set; } + + [JsonProperty("user")] public Optional Creator { get; set; } } } diff --git a/src/Discord.Net.Rest/API/EntityOrId.cs b/src/Discord.Net.Rest/API/EntityOrId.cs index 9bcda260a..73c995eec 100644 --- a/src/Discord.Net.Rest/API/EntityOrId.cs +++ b/src/Discord.Net.Rest/API/EntityOrId.cs @@ -10,6 +10,7 @@ Id = id; Object = default(T); } + public EntityOrId(T obj) { Id = 0; diff --git a/src/Discord.Net.Rest/API/Image.cs b/src/Discord.Net.Rest/API/Image.cs index b2357a0a6..e677b02d3 100644 --- a/src/Discord.Net.Rest/API/Image.cs +++ b/src/Discord.Net.Rest/API/Image.cs @@ -12,6 +12,7 @@ namespace Discord.API Stream = stream; Hash = null; } + public Image(string hash) { Stream = null; diff --git a/src/Discord.Net.Rest/API/Int53Attribute.cs b/src/Discord.Net.Rest/API/Int53Attribute.cs index 70ef2f185..25dd8e84c 100644 --- a/src/Discord.Net.Rest/API/Int53Attribute.cs +++ b/src/Discord.Net.Rest/API/Int53Attribute.cs @@ -4,5 +4,7 @@ using System; namespace Discord.API { [AttributeUsage(AttributeTargets.Property)] - internal class Int53Attribute : Attribute { } + internal class Int53Attribute : Attribute + { + } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateChannelInviteParams.cs b/src/Discord.Net.Rest/API/Rest/CreateChannelInviteParams.cs index db79bc314..aa6710955 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateChannelInviteParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateChannelInviteParams.cs @@ -6,13 +6,12 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateChannelInviteParams { - [JsonProperty("max_age")] - public Optional MaxAge { get; set; } - [JsonProperty("max_uses")] - public Optional MaxUses { get; set; } - [JsonProperty("temporary")] - public Optional IsTemporary { get; set; } - [JsonProperty("unique")] - public Optional IsUnique { get; set; } + [JsonProperty("max_age")] public Optional MaxAge { get; set; } + + [JsonProperty("max_uses")] public Optional MaxUses { get; set; } + + [JsonProperty("temporary")] public Optional IsTemporary { get; set; } + + [JsonProperty("unique")] public Optional IsUnique { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateDMChannelParams.cs b/src/Discord.Net.Rest/API/Rest/CreateDMChannelParams.cs index f32796e02..b6d5e8fbd 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateDMChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateDMChannelParams.cs @@ -6,12 +6,11 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateDMChannelParams { - [JsonProperty("recipient_id")] - public ulong RecipientId { get; } - public CreateDMChannelParams(ulong recipientId) { RecipientId = recipientId; } + + [JsonProperty("recipient_id")] public ulong RecipientId { get; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildBanParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildBanParams.cs index f0432e517..9d9dbf581 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildBanParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildBanParams.cs @@ -4,6 +4,6 @@ namespace Discord.API.Rest internal class CreateGuildBanParams { public Optional DeleteMessageDays { get; set; } - public string Reason { get; set; } + public string Reason { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs index 05cdf4b8a..a1fd64572 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildChannelParams.cs @@ -6,29 +6,26 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateGuildChannelParams { - [JsonProperty("name")] - public string Name { get; } - [JsonProperty("type")] - public ChannelType Type { get; } - [JsonProperty("parent_id")] - public Optional CategoryId { get; set; } - - //Text channels - [JsonProperty("topic")] - public Optional Topic { get; set; } - [JsonProperty("nsfw")] - public Optional IsNsfw { get; set; } - - //Voice channels - [JsonProperty("bitrate")] - public Optional Bitrate { get; set; } - [JsonProperty("user_limit")] - public Optional UserLimit { get; set; } - public CreateGuildChannelParams(string name, ChannelType type) { Name = name; Type = type; } + + [JsonProperty("name")] public string Name { get; } + + [JsonProperty("type")] public ChannelType Type { get; } + + [JsonProperty("parent_id")] public Optional CategoryId { get; set; } + + //Text channels + [JsonProperty("topic")] public Optional Topic { get; set; } + + [JsonProperty("nsfw")] public Optional IsNsfw { get; set; } + + //Voice channels + [JsonProperty("bitrate")] public Optional Bitrate { get; set; } + + [JsonProperty("user_limit")] public Optional UserLimit { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildEmoteParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildEmoteParams.cs index 308199820..071c3a5e3 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildEmoteParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildEmoteParams.cs @@ -6,11 +6,10 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateGuildEmoteParams { - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("image")] - public Image Image { get; set; } - [JsonProperty("roles")] - public Optional RoleIds { get; set; } + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("image")] public Image Image { get; set; } + + [JsonProperty("roles")] public Optional RoleIds { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildIntegrationParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildIntegrationParams.cs index 1053a0ed3..3298c55af 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildIntegrationParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildIntegrationParams.cs @@ -6,15 +6,14 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateGuildIntegrationParams { - [JsonProperty("id")] - public ulong Id { get; } - [JsonProperty("type")] - public string Type { get; } - public CreateGuildIntegrationParams(ulong id, string type) { Id = id; Type = type; } + + [JsonProperty("id")] public ulong Id { get; } + + [JsonProperty("type")] public string Type { get; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateGuildParams.cs b/src/Discord.Net.Rest/API/Rest/CreateGuildParams.cs index cda6caedf..6ae861cf6 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateGuildParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateGuildParams.cs @@ -6,18 +6,16 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateGuildParams { - [JsonProperty("name")] - public string Name { get; } - [JsonProperty("region")] - public string RegionId { get; } - - [JsonProperty("icon")] - public Optional Icon { get; set; } - public CreateGuildParams(string name, string regionId) { Name = name; RegionId = regionId; } + + [JsonProperty("name")] public string Name { get; } + + [JsonProperty("region")] public string RegionId { get; } + + [JsonProperty("icon")] public Optional Icon { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs index d77bff8ca..8d80c85ef 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateMessageParams.cs @@ -6,19 +6,17 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateMessageParams { - [JsonProperty("content")] - public string Content { get; } - - [JsonProperty("nonce")] - public Optional Nonce { get; set; } - [JsonProperty("tts")] - public Optional IsTTS { get; set; } - [JsonProperty("embed")] - public Optional Embed { get; set; } - public CreateMessageParams(string content) { Content = content; } + + [JsonProperty("content")] public string Content { get; } + + [JsonProperty("nonce")] public Optional Nonce { get; set; } + + [JsonProperty("tts")] public Optional IsTTS { get; set; } + + [JsonProperty("embed")] public Optional Embed { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs index 970a30201..40b596817 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateWebhookMessageParams.cs @@ -6,23 +6,21 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateWebhookMessageParams { - [JsonProperty("content")] - public string Content { get; } - - [JsonProperty("nonce")] - public Optional Nonce { get; set; } - [JsonProperty("tts")] - public Optional IsTTS { get; set; } - [JsonProperty("embeds")] - public Optional Embeds { get; set; } - [JsonProperty("username")] - public Optional Username { get; set; } - [JsonProperty("avatar_url")] - public Optional AvatarUrl { get; set; } - public CreateWebhookMessageParams(string content) { Content = content; } + + [JsonProperty("content")] public string Content { get; } + + [JsonProperty("nonce")] public Optional Nonce { get; set; } + + [JsonProperty("tts")] public Optional IsTTS { get; set; } + + [JsonProperty("embeds")] public Optional Embeds { get; set; } + + [JsonProperty("username")] public Optional Username { get; set; } + + [JsonProperty("avatar_url")] public Optional AvatarUrl { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/CreateWebhookParams.cs b/src/Discord.Net.Rest/API/Rest/CreateWebhookParams.cs index 0d1059fab..9a6038bbc 100644 --- a/src/Discord.Net.Rest/API/Rest/CreateWebhookParams.cs +++ b/src/Discord.Net.Rest/API/Rest/CreateWebhookParams.cs @@ -6,9 +6,8 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class CreateWebhookParams { - [JsonProperty("name")] - public string Name { get; set; } - [JsonProperty("avatar")] - public Optional Avatar { get; set; } + [JsonProperty("name")] public string Name { get; set; } + + [JsonProperty("avatar")] public Optional Avatar { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/DeleteMessagesParams.cs b/src/Discord.Net.Rest/API/Rest/DeleteMessagesParams.cs index ca9d8c26e..b90a52424 100644 --- a/src/Discord.Net.Rest/API/Rest/DeleteMessagesParams.cs +++ b/src/Discord.Net.Rest/API/Rest/DeleteMessagesParams.cs @@ -6,12 +6,11 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class DeleteMessagesParams { - [JsonProperty("messages")] - public ulong[] MessageIds { get; } - public DeleteMessagesParams(ulong[] messageIds) { MessageIds = messageIds; } + + [JsonProperty("messages")] public ulong[] MessageIds { get; } } } diff --git a/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs b/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs index ceabccbc8..def0fde20 100644 --- a/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs +++ b/src/Discord.Net.Rest/API/Rest/GetAuditLogsParams.cs @@ -1,6 +1,6 @@ namespace Discord.API.Rest { - class GetAuditLogsParams + internal class GetAuditLogsParams { public Optional Limit { get; set; } public Optional BeforeEntryId { get; set; } diff --git a/src/Discord.Net.Rest/API/Rest/GetBotGatewayResponse.cs b/src/Discord.Net.Rest/API/Rest/GetBotGatewayResponse.cs index 111fcf3db..c8d4e8034 100644 --- a/src/Discord.Net.Rest/API/Rest/GetBotGatewayResponse.cs +++ b/src/Discord.Net.Rest/API/Rest/GetBotGatewayResponse.cs @@ -5,9 +5,8 @@ namespace Discord.API.Rest { internal class GetBotGatewayResponse { - [JsonProperty("url")] - public string Url { get; set; } - [JsonProperty("shards")] - public int Shards { get; set; } + [JsonProperty("url")] public string Url { get; set; } + + [JsonProperty("shards")] public int Shards { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/GetGatewayResponse.cs b/src/Discord.Net.Rest/API/Rest/GetGatewayResponse.cs index ce3630170..4284b30ee 100644 --- a/src/Discord.Net.Rest/API/Rest/GetGatewayResponse.cs +++ b/src/Discord.Net.Rest/API/Rest/GetGatewayResponse.cs @@ -5,7 +5,6 @@ namespace Discord.API.Rest { internal class GetGatewayResponse { - [JsonProperty("url")] - public string Url { get; set; } + [JsonProperty("url")] public string Url { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/GetGuildPruneCountResponse.cs b/src/Discord.Net.Rest/API/Rest/GetGuildPruneCountResponse.cs index 4af85acfa..c79d148ed 100644 --- a/src/Discord.Net.Rest/API/Rest/GetGuildPruneCountResponse.cs +++ b/src/Discord.Net.Rest/API/Rest/GetGuildPruneCountResponse.cs @@ -5,7 +5,6 @@ namespace Discord.API.Rest { internal class GetGuildPruneCountResponse { - [JsonProperty("pruned")] - public int Pruned { get; set; } + [JsonProperty("pruned")] public int Pruned { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/GuildPruneParams.cs b/src/Discord.Net.Rest/API/Rest/GuildPruneParams.cs index 6a98d3758..5bcebde28 100644 --- a/src/Discord.Net.Rest/API/Rest/GuildPruneParams.cs +++ b/src/Discord.Net.Rest/API/Rest/GuildPruneParams.cs @@ -6,12 +6,11 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class GuildPruneParams { - [JsonProperty("days")] - public int Days { get; } - public GuildPruneParams(int days) { Days = days; } + + [JsonProperty("days")] public int Days { get; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs index 0fe5f7e5a..cf17d032c 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyChannelPermissionsParams.cs @@ -6,18 +6,17 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyChannelPermissionsParams { - [JsonProperty("type")] - public string Type { get; } - [JsonProperty("allow")] - public ulong Allow { get; } - [JsonProperty("deny")] - public ulong Deny { get; } - public ModifyChannelPermissionsParams(string type, ulong allow, ulong deny) { Type = type; Allow = allow; Deny = deny; } + + [JsonProperty("type")] public string Type { get; } + + [JsonProperty("allow")] public ulong Allow { get; } + + [JsonProperty("deny")] public ulong Deny { get; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs index ba44e34cf..6d394e756 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserNickParams.cs @@ -6,12 +6,11 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyCurrentUserNickParams { - [JsonProperty("nick")] - public string Nickname { get; } - public ModifyCurrentUserNickParams(string nickname) { Nickname = nickname; } + + [JsonProperty("nick")] public string Nickname { get; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserParams.cs index 7ba27c3a5..9eddb8d61 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyCurrentUserParams.cs @@ -6,9 +6,8 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyCurrentUserParams { - [JsonProperty("username")] - public Optional Username { get; set; } - [JsonProperty("avatar")] - public Optional Avatar { get; set; } + [JsonProperty("username")] public Optional Username { get; set; } + + [JsonProperty("avatar")] public Optional Avatar { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs index 120eeb3a8..6698d97df 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelParams.cs @@ -6,11 +6,10 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildChannelParams { - [JsonProperty("name")] - public Optional Name { get; set; } - [JsonProperty("position")] - public Optional Position { get; set; } - [JsonProperty("parent_id")] - public Optional CategoryId { get; set; } + [JsonProperty("name")] public Optional Name { get; set; } + + [JsonProperty("position")] public Optional Position { get; set; } + + [JsonProperty("parent_id")] public Optional CategoryId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelsParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelsParams.cs index f97fbda0b..51e351c7a 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelsParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildChannelsParams.cs @@ -6,15 +6,14 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildChannelsParams { - [JsonProperty("id")] - public ulong Id { get; } - [JsonProperty("position")] - public int Position { get; } - public ModifyGuildChannelsParams(ulong id, int position) { Id = id; Position = position; } + + [JsonProperty("id")] public ulong Id { get; } + + [JsonProperty("position")] public int Position { get; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildEmbedParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildEmbedParams.cs index 487744c65..94262043b 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildEmbedParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildEmbedParams.cs @@ -5,10 +5,9 @@ namespace Discord.API.Rest { [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildEmbedParams - { - [JsonProperty("enabled")] - public Optional Enabled { get; set; } - [JsonProperty("channel")] - public Optional ChannelId { get; set; } + { + [JsonProperty("enabled")] public Optional Enabled { get; set; } + + [JsonProperty("channel")] public Optional ChannelId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildEmoteParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildEmoteParams.cs index a2295dd5d..bdc268ae9 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildEmoteParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildEmoteParams.cs @@ -6,9 +6,8 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildEmoteParams { - [JsonProperty("name")] - public Optional Name { get; set; } - [JsonProperty("roles")] - public Optional RoleIds { get; set; } + [JsonProperty("name")] public Optional Name { get; set; } + + [JsonProperty("roles")] public Optional RoleIds { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildIntegrationParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildIntegrationParams.cs index 0a1b4f9fa..ec1b31ee0 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildIntegrationParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildIntegrationParams.cs @@ -6,11 +6,10 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildIntegrationParams { - [JsonProperty("expire_behavior")] - public Optional ExpireBehavior { get; set; } - [JsonProperty("expire_grace_period")] - public Optional ExpireGracePeriod { get; set; } - [JsonProperty("enable_emoticons")] - public Optional EnableEmoticons { get; set; } + [JsonProperty("expire_behavior")] public Optional ExpireBehavior { get; set; } + + [JsonProperty("expire_grace_period")] public Optional ExpireGracePeriod { get; set; } + + [JsonProperty("enable_emoticons")] public Optional EnableEmoticons { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs index 159670afb..68806b926 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs @@ -6,15 +6,14 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildMemberParams { - [JsonProperty("mute")] - public Optional Mute { get; set; } - [JsonProperty("deaf")] - public Optional Deaf { get; set; } - [JsonProperty("nick")] - public Optional Nickname { get; set; } - [JsonProperty("roles")] - public Optional RoleIds { get; set; } - [JsonProperty("channel_id")] - public Optional ChannelId { get; set; } + [JsonProperty("mute")] public Optional Mute { get; set; } + + [JsonProperty("deaf")] public Optional Deaf { get; set; } + + [JsonProperty("nick")] public Optional Nickname { get; set; } + + [JsonProperty("roles")] public Optional RoleIds { get; set; } + + [JsonProperty("channel_id")] public Optional ChannelId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs index 8de10f534..4b80fccf6 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildParams.cs @@ -6,27 +6,27 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildParams { - [JsonProperty("username")] - public Optional Username { get; set; } - [JsonProperty("name")] - public Optional Name { get; set; } - [JsonProperty("region")] - public Optional RegionId { get; set; } - [JsonProperty("verification_level")] - public Optional VerificationLevel { get; set; } + [JsonProperty("username")] public Optional Username { get; set; } + + [JsonProperty("name")] public Optional Name { get; set; } + + [JsonProperty("region")] public Optional RegionId { get; set; } + + [JsonProperty("verification_level")] public Optional VerificationLevel { get; set; } + [JsonProperty("default_message_notifications")] public Optional DefaultMessageNotifications { get; set; } - [JsonProperty("afk_timeout")] - public Optional AfkTimeout { get; set; } - [JsonProperty("system_channel_id")] - public Optional SystemChannelId { get; set; } - [JsonProperty("icon")] - public Optional Icon { get; set; } - [JsonProperty("splash")] - public Optional Splash { get; set; } - [JsonProperty("afk_channel_id")] - public Optional AfkChannelId { get; set; } - [JsonProperty("owner_id")] - public Optional OwnerId { get; set; } + + [JsonProperty("afk_timeout")] public Optional AfkTimeout { get; set; } + + [JsonProperty("system_channel_id")] public Optional SystemChannelId { get; set; } + + [JsonProperty("icon")] public Optional Icon { get; set; } + + [JsonProperty("splash")] public Optional Splash { get; set; } + + [JsonProperty("afk_channel_id")] public Optional AfkChannelId { get; set; } + + [JsonProperty("owner_id")] public Optional OwnerId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs index 287e1cafe..be0371652 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildRoleParams.cs @@ -6,15 +6,14 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildRoleParams { - [JsonProperty("name")] - public Optional Name { get; set; } - [JsonProperty("permissions")] - public Optional Permissions { get; set; } - [JsonProperty("color")] - public Optional Color { get; set; } - [JsonProperty("hoist")] - public Optional Hoist { get; set; } - [JsonProperty("mentionable")] - public Optional Mentionable { get; set; } + [JsonProperty("name")] public Optional Name { get; set; } + + [JsonProperty("permissions")] public Optional Permissions { get; set; } + + [JsonProperty("color")] public Optional Color { get; set; } + + [JsonProperty("hoist")] public Optional Hoist { get; set; } + + [JsonProperty("mentionable")] public Optional Mentionable { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyGuildRolesParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyGuildRolesParams.cs index 0e816a260..d0b885281 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyGuildRolesParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyGuildRolesParams.cs @@ -6,15 +6,14 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyGuildRolesParams : ModifyGuildRoleParams { - [JsonProperty("id")] - public ulong Id { get; } - [JsonProperty("position")] - public int Position { get; } - public ModifyGuildRolesParams(ulong id, int position) { Id = id; Position = position; } + + [JsonProperty("id")] public ulong Id { get; } + + [JsonProperty("position")] public int Position { get; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs index fdff4de15..25571c92f 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyMessageParams.cs @@ -6,9 +6,8 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyMessageParams { - [JsonProperty("content")] - public Optional Content { get; set; } - [JsonProperty("embed")] - public Optional Embed { get; set; } + [JsonProperty("content")] public Optional Content { get; set; } + + [JsonProperty("embed")] public Optional Embed { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs index 9cabc67c1..d3265b38e 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyTextChannelParams.cs @@ -6,9 +6,8 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyTextChannelParams : ModifyGuildChannelParams { - [JsonProperty("topic")] - public Optional Topic { get; set; } - [JsonProperty("nsfw")] - public Optional IsNsfw { get; set; } + [JsonProperty("topic")] public Optional Topic { get; set; } + + [JsonProperty("nsfw")] public Optional IsNsfw { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyVoiceChannelParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyVoiceChannelParams.cs index ce36eb11f..49c9c2330 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyVoiceChannelParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyVoiceChannelParams.cs @@ -6,9 +6,8 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyVoiceChannelParams : ModifyGuildChannelParams { - [JsonProperty("bitrate")] - public Optional Bitrate { get; set; } - [JsonProperty("user_limit")] - public Optional UserLimit { get; set; } + [JsonProperty("bitrate")] public Optional Bitrate { get; set; } + + [JsonProperty("user_limit")] public Optional UserLimit { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/ModifyWebhookParams.cs b/src/Discord.Net.Rest/API/Rest/ModifyWebhookParams.cs index 0f2d6e33b..72a23ede3 100644 --- a/src/Discord.Net.Rest/API/Rest/ModifyWebhookParams.cs +++ b/src/Discord.Net.Rest/API/Rest/ModifyWebhookParams.cs @@ -6,11 +6,10 @@ namespace Discord.API.Rest [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ModifyWebhookParams { - [JsonProperty("name")] - public Optional Name { get; set; } - [JsonProperty("avatar")] - public Optional Avatar { get; set; } - [JsonProperty("channel_id")] - public Optional ChannelId { get; set; } + [JsonProperty("name")] public Optional Name { get; set; } + + [JsonProperty("avatar")] public Optional Avatar { get; set; } + + [JsonProperty("channel_id")] public Optional ChannelId { get; set; } } } diff --git a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs index 9e909b50c..15ebac662 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadFileParams.cs @@ -1,17 +1,22 @@ #pragma warning disable CS1591 -using Discord.Net.Converters; -using Discord.Net.Rest; -using Newtonsoft.Json; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Text; +using Discord.Net.Converters; +using Discord.Net.Rest; +using Newtonsoft.Json; namespace Discord.API.Rest { internal class UploadFileParams { - private static JsonSerializer _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; + private static readonly JsonSerializer _serializer = new JsonSerializer + {ContractResolver = new DiscordContractResolver()}; + + public UploadFileParams(Stream file) + { + File = file; + } public Stream File { get; } @@ -21,11 +26,6 @@ namespace Discord.API.Rest public Optional IsTTS { get; set; } public Optional Embed { get; set; } - public UploadFileParams(Stream file) - { - File = file; - } - public IReadOnlyDictionary ToDictionary() { var d = new Dictionary(); diff --git a/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs b/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs index 6d6eb29b2..a3907b520 100644 --- a/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs +++ b/src/Discord.Net.Rest/API/Rest/UploadWebhookFileParams.cs @@ -7,6 +7,11 @@ namespace Discord.API.Rest { internal class UploadWebhookFileParams { + public UploadWebhookFileParams(Stream file) + { + File = file; + } + public Stream File { get; } public Optional Filename { get; set; } @@ -17,11 +22,6 @@ namespace Discord.API.Rest public Optional AvatarUrl { get; set; } public Optional Embeds { get; set; } - public UploadWebhookFileParams(Stream file) - { - File = file; - } - public IReadOnlyDictionary ToDictionary() { var d = new Dictionary(); diff --git a/src/Discord.Net.Rest/API/UnixTimestampAttribute.cs b/src/Discord.Net.Rest/API/UnixTimestampAttribute.cs index 3890ffc46..06f28e9f2 100644 --- a/src/Discord.Net.Rest/API/UnixTimestampAttribute.cs +++ b/src/Discord.Net.Rest/API/UnixTimestampAttribute.cs @@ -3,5 +3,7 @@ namespace Discord.API { [AttributeUsage(AttributeTargets.Property)] - internal class UnixTimestampAttribute : Attribute { } -} \ No newline at end of file + internal class UnixTimestampAttribute : Attribute + { + } +} diff --git a/src/Discord.Net.Rest/AssemblyInfo.cs b/src/Discord.Net.Rest/AssemblyInfo.cs index 9c90919af..65d23b3af 100644 --- a/src/Discord.Net.Rest/AssemblyInfo.cs +++ b/src/Discord.Net.Rest/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using Discord; [assembly: InternalsVisibleTo("Discord.Net.Rpc")] [assembly: InternalsVisibleTo("Discord.Net.WebSocket")] @@ -6,17 +7,17 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Discord.Net.Commands")] [assembly: InternalsVisibleTo("Discord.Net.Tests")] -[assembly: TypeForwardedTo(typeof(Discord.Embed))] -[assembly: TypeForwardedTo(typeof(Discord.EmbedBuilder))] -[assembly: TypeForwardedTo(typeof(Discord.EmbedBuilderExtensions))] -[assembly: TypeForwardedTo(typeof(Discord.EmbedAuthor))] -[assembly: TypeForwardedTo(typeof(Discord.EmbedAuthorBuilder))] -[assembly: TypeForwardedTo(typeof(Discord.EmbedField))] -[assembly: TypeForwardedTo(typeof(Discord.EmbedFieldBuilder))] -[assembly: TypeForwardedTo(typeof(Discord.EmbedFooter))] -[assembly: TypeForwardedTo(typeof(Discord.EmbedFooterBuilder))] -[assembly: TypeForwardedTo(typeof(Discord.EmbedImage))] -[assembly: TypeForwardedTo(typeof(Discord.EmbedProvider))] -[assembly: TypeForwardedTo(typeof(Discord.EmbedThumbnail))] -[assembly: TypeForwardedTo(typeof(Discord.EmbedType))] -[assembly: TypeForwardedTo(typeof(Discord.EmbedVideo))] +[assembly: TypeForwardedTo(typeof(Embed))] +[assembly: TypeForwardedTo(typeof(EmbedBuilder))] +[assembly: TypeForwardedTo(typeof(EmbedBuilderExtensions))] +[assembly: TypeForwardedTo(typeof(EmbedAuthor))] +[assembly: TypeForwardedTo(typeof(EmbedAuthorBuilder))] +[assembly: TypeForwardedTo(typeof(EmbedField))] +[assembly: TypeForwardedTo(typeof(EmbedFieldBuilder))] +[assembly: TypeForwardedTo(typeof(EmbedFooter))] +[assembly: TypeForwardedTo(typeof(EmbedFooterBuilder))] +[assembly: TypeForwardedTo(typeof(EmbedImage))] +[assembly: TypeForwardedTo(typeof(EmbedProvider))] +[assembly: TypeForwardedTo(typeof(EmbedThumbnail))] +[assembly: TypeForwardedTo(typeof(EmbedType))] +[assembly: TypeForwardedTo(typeof(EmbedVideo))] diff --git a/src/Discord.Net.Rest/BaseDiscordClient.cs b/src/Discord.Net.Rest/BaseDiscordClient.cs index f8642b96c..c5d79b70f 100644 --- a/src/Discord.Net.Rest/BaseDiscordClient.cs +++ b/src/Discord.Net.Rest/BaseDiscordClient.cs @@ -1,35 +1,26 @@ -using Discord.Logging; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Threading; using System.Threading.Tasks; +using Discord.API; +using Discord.Logging; namespace Discord.Rest { public abstract class BaseDiscordClient : IDiscordClient { - public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } internal readonly AsyncEvent> _logEvent = new AsyncEvent>(); - - public event Func LoggedIn { add { _loggedInEvent.Add(value); } remove { _loggedInEvent.Remove(value); } } private readonly AsyncEvent> _loggedInEvent = new AsyncEvent>(); - public event Func LoggedOut { add { _loggedOutEvent.Add(value); } remove { _loggedOutEvent.Remove(value); } } private readonly AsyncEvent> _loggedOutEvent = new AsyncEvent>(); internal readonly Logger _restLogger; private readonly SemaphoreSlim _stateLock; private bool _isFirstLogin, _isDisposed; - internal API.DiscordRestApiClient ApiClient { get; } - internal LogManager LogManager { get; } - public LoginState LoginState { get; private set; } - public ISelfUser CurrentUser { get; protected set; } - public TokenType TokenType => ApiClient.AuthTokenType; - /// Creates a new REST-only discord client. - internal BaseDiscordClient(DiscordRestConfig config, API.DiscordRestApiClient client) + internal BaseDiscordClient(DiscordRestConfig config, DiscordRestApiClient client) { ApiClient = client; LogManager = new LogManager(config.LogLevel); @@ -42,11 +33,102 @@ namespace Discord.Rest ApiClient.RequestQueue.RateLimitTriggered += async (id, info) => { if (info == null) - await _restLogger.VerboseAsync($"Preemptive Rate limit triggered: {id ?? "null"}").ConfigureAwait(false); + await _restLogger.VerboseAsync($"Preemptive Rate limit triggered: {id ?? "null"}") + .ConfigureAwait(false); else await _restLogger.WarningAsync($"Rate limit triggered: {id ?? "null"}").ConfigureAwait(false); }; - ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); + ApiClient.SentRequest += async (method, endpoint, millis) => + await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); + } + + internal DiscordRestApiClient ApiClient { get; } + internal LogManager LogManager { get; } + public LoginState LoginState { get; private set; } + public ISelfUser CurrentUser { get; protected set; } + public TokenType TokenType => ApiClient.AuthTokenType; + + /// + public void Dispose() => Dispose(true); + + /// + public Task GetRecommendedShardCountAsync(RequestOptions options = null) + => ClientHelper.GetRecommendShardCountAsync(this, options); + + //IDiscordClient + ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; + ISelfUser IDiscordClient.CurrentUser => CurrentUser; + + Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) + => throw new NotSupportedException(); + + Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(null); + + Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, + RequestOptions options) + => Task.FromResult>(ImmutableArray.Create()); + + Task> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) + => Task.FromResult>(ImmutableArray.Create()); + + Task> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, + RequestOptions options) + => Task.FromResult>(ImmutableArray.Create()); + + Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) + => Task.FromResult>(ImmutableArray.Create()); + + Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) + => Task.FromResult(null); + + Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(null); + + Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) + => Task.FromResult>(ImmutableArray.Create()); + + Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, + RequestOptions options) + => throw new NotSupportedException(); + + Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(null); + + Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) + => Task.FromResult(null); + + Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) + => Task.FromResult>(ImmutableArray.Create()); + + Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) + => Task.FromResult(null); + + Task IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) + => Task.FromResult(null); + + Task IDiscordClient.StartAsync() + => Task.Delay(0); + + Task IDiscordClient.StopAsync() + => Task.Delay(0); + + public event Func Log + { + add => _logEvent.Add(value); + remove => _logEvent.Remove(value); + } + + public event Func LoggedIn + { + add => _loggedInEvent.Add(value); + remove => _loggedInEvent.Remove(value); + } + + public event Func LoggedOut + { + add => _loggedOutEvent.Add(value); + remove => _loggedOutEvent.Remove(value); } /// @@ -57,8 +139,12 @@ namespace Discord.Rest { await LoginInternalAsync(tokenType, token).ConfigureAwait(false); } - finally { _stateLock.Release(); } + finally + { + _stateLock.Release(); + } } + private async Task LoginInternalAsync(TokenType tokenType, string token) { if (_isFirstLogin) @@ -85,7 +171,8 @@ namespace Discord.Rest await _loggedInEvent.InvokeAsync().ConfigureAwait(false); } - internal virtual Task OnLoginAsync(TokenType tokenType, string token) + + internal virtual Task OnLoginAsync(TokenType tokenType, string token) => Task.Delay(0); /// @@ -96,8 +183,12 @@ namespace Discord.Rest { await LogoutInternalAsync().ConfigureAwait(false); } - finally { _stateLock.Release(); } + finally + { + _stateLock.Release(); + } } + private async Task LogoutInternalAsync() { if (LoginState == LoginState.LoggedOut) return; @@ -111,7 +202,8 @@ namespace Discord.Rest await _loggedOutEvent.InvokeAsync().ConfigureAwait(false); } - internal virtual Task OnLogoutAsync() + + internal virtual Task OnLogoutAsync() => Task.Delay(0); internal virtual void Dispose(bool disposing) @@ -122,58 +214,5 @@ namespace Discord.Rest _isDisposed = true; } } - /// - public void Dispose() => Dispose(true); - - /// - public Task GetRecommendedShardCountAsync(RequestOptions options = null) - => ClientHelper.GetRecommendShardCountAsync(this, options); - - //IDiscordClient - ConnectionState IDiscordClient.ConnectionState => ConnectionState.Disconnected; - ISelfUser IDiscordClient.CurrentUser => CurrentUser; - - Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) - => throw new NotSupportedException(); - - Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(null); - Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) - => Task.FromResult>(ImmutableArray.Create()); - Task> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) - => Task.FromResult>(ImmutableArray.Create()); - Task> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) - => Task.FromResult>(ImmutableArray.Create()); - - Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) - => Task.FromResult>(ImmutableArray.Create()); - - Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) - => Task.FromResult(null); - - Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(null); - Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) - => Task.FromResult>(ImmutableArray.Create()); - Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) - => throw new NotSupportedException(); - - Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(null); - Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) - => Task.FromResult(null); - - Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) - => Task.FromResult>(ImmutableArray.Create()); - Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) - => Task.FromResult(null); - - Task IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) - => Task.FromResult(null); - - Task IDiscordClient.StartAsync() - => Task.Delay(0); - Task IDiscordClient.StopAsync() - => Task.Delay(0); } } diff --git a/src/Discord.Net.Rest/ClientHelper.cs b/src/Discord.Net.Rest/ClientHelper.cs index d8f481d15..cbdc508cc 100644 --- a/src/Discord.Net.Rest/ClientHelper.cs +++ b/src/Discord.Net.Rest/ClientHelper.cs @@ -1,72 +1,75 @@ -using Discord.API.Rest; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Threading.Tasks; +using Discord.API.Rest; namespace Discord.Rest { internal static class ClientHelper { //Applications - public static async Task GetApplicationInfoAsync(BaseDiscordClient client, RequestOptions options) + public static async Task GetApplicationInfoAsync(BaseDiscordClient client, + RequestOptions options) { var model = await client.ApiClient.GetMyApplicationAsync(options).ConfigureAwait(false); return RestApplication.Create(client, model); } - public static async Task GetChannelAsync(BaseDiscordClient client, + public static async Task GetChannelAsync(BaseDiscordClient client, ulong id, RequestOptions options) { var model = await client.ApiClient.GetChannelAsync(id, options).ConfigureAwait(false); - if (model != null) - return RestChannel.Create(client, model); - return null; + return model != null ? RestChannel.Create(client, model) : null; } - public static async Task> GetPrivateChannelsAsync(BaseDiscordClient client, RequestOptions options) + + public static async Task> GetPrivateChannelsAsync( + BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetMyPrivateChannelsAsync(options).ConfigureAwait(false); return models.Select(x => RestChannel.CreatePrivate(client, x)).ToImmutableArray(); } - public static async Task> GetDMChannelsAsync(BaseDiscordClient client, RequestOptions options) + + public static async Task> GetDMChannelsAsync(BaseDiscordClient client, + RequestOptions options) { var models = await client.ApiClient.GetMyPrivateChannelsAsync(options).ConfigureAwait(false); return models .Where(x => x.Type == ChannelType.DM) .Select(x => RestDMChannel.Create(client, x)).ToImmutableArray(); } - public static async Task> GetGroupChannelsAsync(BaseDiscordClient client, RequestOptions options) + + public static async Task> GetGroupChannelsAsync(BaseDiscordClient client, + RequestOptions options) { var models = await client.ApiClient.GetMyPrivateChannelsAsync(options).ConfigureAwait(false); return models .Where(x => x.Type == ChannelType.Group) .Select(x => RestGroupChannel.Create(client, x)).ToImmutableArray(); } - - public static async Task> GetConnectionsAsync(BaseDiscordClient client, RequestOptions options) + + public static async Task> GetConnectionsAsync(BaseDiscordClient client, + RequestOptions options) { var models = await client.ApiClient.GetMyConnectionsAsync(options).ConfigureAwait(false); return models.Select(x => RestConnection.Create(x)).ToImmutableArray(); } - + public static async Task GetInviteAsync(BaseDiscordClient client, string inviteId, RequestOptions options) { var model = await client.ApiClient.GetInviteAsync(inviteId, options).ConfigureAwait(false); - if (model != null) - return RestInviteMetadata.Create(client, null, null, model); - return null; + return model != null ? RestInviteMetadata.Create(client, null, null, model) : null; } - + public static async Task GetGuildAsync(BaseDiscordClient client, ulong id, RequestOptions options) { var model = await client.ApiClient.GetGuildAsync(id, options).ConfigureAwait(false); - if (model != null) - return RestGuild.Create(client, model); - return null; + return model != null ? RestGuild.Create(client, model) : null; } + public static async Task GetGuildEmbedAsync(BaseDiscordClient client, ulong id, RequestOptions options) { @@ -75,38 +78,40 @@ namespace Discord.Rest return RestGuildEmbed.Create(model); return null; } - public static IAsyncEnumerable> GetGuildSummariesAsync(BaseDiscordClient client, - ulong? fromGuildId, int? limit, RequestOptions options) - { - return new PagedAsyncEnumerable( - DiscordConfig.MaxGuildsPerBatch, - async (info, ct) => - { - var args = new GetGuildSummariesParams - { - Limit = info.PageSize - }; - if (info.Position != null) - args.AfterGuildId = info.Position.Value; - var models = await client.ApiClient.GetMyGuildsAsync(args, options).ConfigureAwait(false); - return models - .Select(x => RestUserGuild.Create(client, x)) - .ToImmutableArray(); - }, - nextPage: (info, lastPage) => + + public static IAsyncEnumerable> GetGuildSummariesAsync( + BaseDiscordClient client, + ulong? fromGuildId, int? limit, RequestOptions options) => new PagedAsyncEnumerable( + DiscordConfig.MaxGuildsPerBatch, + async (info, ct) => + { + var args = new GetGuildSummariesParams { - if (lastPage.Count != DiscordConfig.MaxMessagesPerBatch) - return false; - info.Position = lastPage.Max(x => x.Id); - return true; - }, - start: fromGuildId, - count: limit - ); - } - public static async Task> GetGuildsAsync(BaseDiscordClient client, RequestOptions options) - { - var summaryModels = await GetGuildSummariesAsync(client, null, null, options).FlattenAsync().ConfigureAwait(false); + Limit = info.PageSize + }; + if (info.Position != null) + args.AfterGuildId = info.Position.Value; + var models = await client.ApiClient.GetMyGuildsAsync(args, options).ConfigureAwait(false); + return models + .Select(x => RestUserGuild.Create(client, x)) + .ToImmutableArray(); + }, + (info, lastPage) => + { + if (lastPage.Count != DiscordConfig.MaxMessagesPerBatch) + return false; + info.Position = lastPage.Max(x => x.Id); + return true; + }, + fromGuildId, + limit + ); + + public static async Task> GetGuildsAsync(BaseDiscordClient client, + RequestOptions options) + { + var summaryModels = await GetGuildSummariesAsync(client, null, null, options).FlattenAsync() + .ConfigureAwait(false); var guilds = ImmutableArray.CreateBuilder(); foreach (var summaryModel in summaryModels) { @@ -114,8 +119,10 @@ namespace Discord.Rest if (guildModel != null) guilds.Add(RestGuild.Create(client, guildModel)); } + return guilds.ToImmutable(); } + public static async Task CreateGuildAsync(BaseDiscordClient client, string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) { @@ -126,15 +133,14 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildAsync(args, options).ConfigureAwait(false); return RestGuild.Create(client, model); } - + public static async Task GetUserAsync(BaseDiscordClient client, ulong id, RequestOptions options) { var model = await client.ApiClient.GetUserAsync(id, options).ConfigureAwait(false); - if (model != null) - return RestUser.Create(client, model); - return null; + return model != null ? RestUser.Create(client, model) : null; } + public static async Task GetGuildUserAsync(BaseDiscordClient client, ulong guildId, ulong id, RequestOptions options) { @@ -143,25 +149,23 @@ namespace Discord.Rest return null; var model = await client.ApiClient.GetGuildMemberAsync(guildId, id, options).ConfigureAwait(false); - if (model != null) - return RestGuildUser.Create(client, guild, model); - - return null; + return model != null ? RestGuildUser.Create(client, guild, model) : null; } - public static async Task GetWebhookAsync(BaseDiscordClient client, ulong id, RequestOptions options) + public static async Task GetWebhookAsync(BaseDiscordClient client, ulong id, + RequestOptions options) { var model = await client.ApiClient.GetWebhookAsync(id); - if (model != null) - return RestWebhook.Create(client, (IGuild)null, model); - return null; + return model != null ? RestWebhook.Create(client, (IGuild)null, model) : null; } - public static async Task> GetVoiceRegionsAsync(BaseDiscordClient client, RequestOptions options) + public static async Task> GetVoiceRegionsAsync(BaseDiscordClient client, + RequestOptions options) { var models = await client.ApiClient.GetVoiceRegionsAsync(options).ConfigureAwait(false); return models.Select(x => RestVoiceRegion.Create(client, x)).ToImmutableArray(); } + public static async Task GetVoiceRegionAsync(BaseDiscordClient client, string id, RequestOptions options) { diff --git a/src/Discord.Net.Rest/DiscordRestApiClient.cs b/src/Discord.Net.Rest/DiscordRestApiClient.cs index 2236dbbf8..c906f2097 100644 --- a/src/Discord.Net.Rest/DiscordRestApiClient.cs +++ b/src/Discord.Net.Rest/DiscordRestApiClient.cs @@ -1,10 +1,4 @@ #pragma warning disable CS1591 -using Discord.API.Rest; -using Discord.Net; -using Discord.Net.Converters; -using Discord.Net.Queue; -using Discord.Net.Rest; -using Newtonsoft.Json; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -18,46 +12,63 @@ using System.Runtime.CompilerServices; using System.Text; using System.Threading; using System.Threading.Tasks; +using Discord.API.Rest; +using Discord.Net; +using Discord.Net.Converters; +using Discord.Net.Queue; +using Discord.Net.Rest; +using Newtonsoft.Json; namespace Discord.API { internal class DiscordRestApiClient : IDisposable { - private static readonly ConcurrentDictionary> _bucketIdGenerators = new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> _bucketIdGenerators = + new ConcurrentDictionary>(); + + private readonly RestClientProvider _restClientProvider; - public event Func SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } - private readonly AsyncEvent> _sentRequestEvent = new AsyncEvent>(); + private readonly AsyncEvent> _sentRequestEvent = + new AsyncEvent>(); protected readonly JsonSerializer _serializer; protected readonly SemaphoreSlim _stateLock; - private readonly RestClientProvider _restClientProvider; protected bool _isDisposed; private CancellationTokenSource _loginCancelToken; - public RetryMode DefaultRetryMode { get; } - public string UserAgent { get; } - internal RequestQueue RequestQueue { get; } - - public LoginState LoginState { get; private set; } - public TokenType AuthTokenType { get; private set; } - internal string AuthToken { get; private set; } - internal IRestClient RestClient { get; private set; } - internal ulong? CurrentUserId { get; set;} - - public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, + public DiscordRestApiClient(RestClientProvider restClientProvider, string userAgent, + RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null) { _restClientProvider = restClientProvider; UserAgent = userAgent; DefaultRetryMode = defaultRetryMode; - _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; + _serializer = serializer ?? new JsonSerializer {ContractResolver = new DiscordContractResolver()}; RequestQueue = new RequestQueue(); _stateLock = new SemaphoreSlim(1, 1); SetBaseUrl(DiscordConfig.APIUrl); } + + public RetryMode DefaultRetryMode { get; } + public string UserAgent { get; } + internal RequestQueue RequestQueue { get; } + + public LoginState LoginState { get; private set; } + public TokenType AuthTokenType { get; private set; } + internal string AuthToken { get; private set; } + internal IRestClient RestClient { get; private set; } + internal ulong? CurrentUserId { get; set; } + public void Dispose() => Dispose(true); + + public event Func SentRequest + { + add => _sentRequestEvent.Add(value); + remove => _sentRequestEvent.Remove(value); + } + internal void SetBaseUrl(string baseUrl) { RestClient = _restClientProvider(baseUrl); @@ -65,6 +76,7 @@ namespace Discord.API RestClient.SetHeader("user-agent", UserAgent); RestClient.SetHeader("authorization", GetPrefixedToken(AuthTokenType, AuthToken)); } + internal static string GetPrefixedToken(TokenType tokenType, string token) { switch (tokenType) @@ -79,6 +91,7 @@ namespace Discord.API throw new ArgumentException("Unknown OAuth token type", nameof(tokenType)); } } + internal virtual void Dispose(bool disposing) { if (!_isDisposed) @@ -88,10 +101,10 @@ namespace Discord.API _loginCancelToken?.Dispose(); (RestClient as IDisposable)?.Dispose(); } + _isDisposed = true; } } - public void Dispose() => Dispose(true); public async Task LoginAsync(TokenType tokenType, string token, RequestOptions options = null) { @@ -100,8 +113,12 @@ namespace Discord.API { await LoginInternalAsync(tokenType, token, options).ConfigureAwait(false); } - finally { _stateLock.Release(); } + finally + { + _stateLock.Release(); + } } + private async Task LoginInternalAsync(TokenType tokenType, string token, RequestOptions options = null) { if (LoginState != LoginState.LoggedOut) @@ -137,16 +154,25 @@ namespace Discord.API { await LogoutInternalAsync().ConfigureAwait(false); } - finally { _stateLock.Release(); } + finally + { + _stateLock.Release(); + } } + private async Task LogoutInternalAsync() { //An exception here will lock the client into the unusable LoggingOut state, but that's probably fine since our client is in an undefined state too. if (LoginState == LoginState.LoggedOut) return; LoginState = LoginState.LoggingOut; - try { _loginCancelToken?.Cancel(false); } - catch { } + try + { + _loginCancelToken?.Cancel(false); + } + catch + { + } await DisconnectInternalAsync().ConfigureAwait(false); await RequestQueue.ClearAsync().ConfigureAwait(false); @@ -163,10 +189,14 @@ namespace Discord.API //Core internal Task SendAsync(string method, Expression> endpointExpr, BucketIds ids, - ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) - => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, + [CallerMemberName] string funcName = null) + => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), + clientBucket, options); + public async Task SendAsync(string method, string endpoint, - string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, + RequestOptions options = null) { options = options ?? new RequestOptions(); options.HeaderOnly = true; @@ -177,25 +207,35 @@ namespace Discord.API } internal Task SendJsonAsync(string method, Expression> endpointExpr, object payload, BucketIds ids, - ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) - => SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, + [CallerMemberName] string funcName = null) + => SendJsonAsync(method, GetEndpoint(endpointExpr), payload, + GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); + public async Task SendJsonAsync(string method, string endpoint, object payload, - string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, + RequestOptions options = null) { options = options ?? new RequestOptions(); options.HeaderOnly = true; options.BucketId = bucketId; - string json = payload != null ? SerializeJson(payload) : null; + var json = payload != null ? SerializeJson(payload) : null; var request = new JsonRestRequest(RestClient, method, endpoint, json, options); await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); } - internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, - ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) - => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); - public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, - string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) + internal Task SendMultipartAsync(string method, Expression> endpointExpr, + IReadOnlyDictionary multipartArgs, BucketIds ids, + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, + [CallerMemberName] string funcName = null) + => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, + GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); + + public async Task SendMultipartAsync(string method, string endpoint, + IReadOnlyDictionary multipartArgs, + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, + RequestOptions options = null) { options = options ?? new RequestOptions(); options.HeaderOnly = true; @@ -205,11 +245,16 @@ namespace Discord.API await SendInternalAsync(method, endpoint, request).ConfigureAwait(false); } - internal Task SendAsync(string method, Expression> endpointExpr, BucketIds ids, - ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class - => SendAsync(method, GetEndpoint(endpointExpr), GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); + internal Task SendAsync(string method, Expression> endpointExpr, + BucketIds ids, + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, + [CallerMemberName] string funcName = null) where TResponse : class + => SendAsync(method, GetEndpoint(endpointExpr), + GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); + public async Task SendAsync(string method, string endpoint, - string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, + RequestOptions options = null) where TResponse : class { options = options ?? new RequestOptions(); options.BucketId = bucketId; @@ -218,25 +263,36 @@ namespace Discord.API return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); } - internal Task SendJsonAsync(string method, Expression> endpointExpr, object payload, BucketIds ids, - ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) where TResponse : class - => SendJsonAsync(method, GetEndpoint(endpointExpr), payload, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); + internal Task SendJsonAsync(string method, Expression> endpointExpr, + object payload, BucketIds ids, + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, + [CallerMemberName] string funcName = null) where TResponse : class + => SendJsonAsync(method, GetEndpoint(endpointExpr), payload, + GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); + public async Task SendJsonAsync(string method, string endpoint, object payload, - string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) where TResponse : class + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, + RequestOptions options = null) where TResponse : class { options = options ?? new RequestOptions(); options.BucketId = bucketId; - string json = payload != null ? SerializeJson(payload) : null; + var json = payload != null ? SerializeJson(payload) : null; var request = new JsonRestRequest(RestClient, method, endpoint, json, options); return DeserializeJson(await SendInternalAsync(method, endpoint, request).ConfigureAwait(false)); } - internal Task SendMultipartAsync(string method, Expression> endpointExpr, IReadOnlyDictionary multipartArgs, BucketIds ids, - ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, [CallerMemberName] string funcName = null) - => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); - public async Task SendMultipartAsync(string method, string endpoint, IReadOnlyDictionary multipartArgs, - string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null) + internal Task SendMultipartAsync(string method, Expression> endpointExpr, + IReadOnlyDictionary multipartArgs, BucketIds ids, + ClientBucketType clientBucket = ClientBucketType.Unbucketed, RequestOptions options = null, + [CallerMemberName] string funcName = null) + => SendMultipartAsync(method, GetEndpoint(endpointExpr), multipartArgs, + GetBucketId(ids, endpointExpr, AuthTokenType, funcName), clientBucket, options); + + public async Task SendMultipartAsync(string method, string endpoint, + IReadOnlyDictionary multipartArgs, + string bucketId = null, ClientBucketType clientBucket = ClientBucketType.Unbucketed, + RequestOptions options = null) { options = options ?? new RequestOptions(); options.BucketId = bucketId; @@ -256,7 +312,7 @@ namespace Discord.API var responseStream = await RequestQueue.SendAsync(request).ConfigureAwait(false); stopwatch.Stop(); - double milliseconds = ToMilliseconds(stopwatch); + var milliseconds = ToMilliseconds(stopwatch); await _sentRequestEvent.InvokeAsync(method, endpoint, milliseconds).ConfigureAwait(false); return responseStream; @@ -273,12 +329,15 @@ namespace Discord.API public async Task GetGatewayAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendAsync("GET", () => "gateway", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync("GET", () => "gateway", new BucketIds(), options: options) + .ConfigureAwait(false); } + public async Task GetBotGatewayAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendAsync("GET", () => "gateway/bot", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync("GET", () => "gateway/bot", new BucketIds(), options: options) + .ConfigureAwait(false); } //Channels @@ -290,10 +349,15 @@ namespace Discord.API try { var ids = new BucketIds(channelId: channelId); - return await SendAsync("GET", () => $"channels/{channelId}", ids, options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"channels/{channelId}", ids, options: options) + .ConfigureAwait(false); + } + catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) + { + return null; } - catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } } + public async Task GetChannelAsync(ulong guildId, ulong channelId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); @@ -303,22 +367,31 @@ namespace Discord.API try { var ids = new BucketIds(channelId: channelId); - var model = await SendAsync("GET", () => $"channels/{channelId}", ids, options: options).ConfigureAwait(false); + var model = await SendAsync("GET", () => $"channels/{channelId}", ids, options: options) + .ConfigureAwait(false); if (!model.GuildId.IsSpecified || model.GuildId.Value != guildId) return null; return model; } - catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } + catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) + { + return null; + } } - public async Task> GetGuildChannelsAsync(ulong guildId, RequestOptions options = null) + + public async Task> GetGuildChannelsAsync(ulong guildId, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync>("GET", () => $"guilds/{guildId}/channels", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync>("GET", () => $"guilds/{guildId}/channels", ids, + options: options).ConfigureAwait(false); } - public async Task CreateGuildChannelAsync(ulong guildId, CreateGuildChannelParams args, RequestOptions options = null) + + public async Task CreateGuildChannelAsync(ulong guildId, CreateGuildChannelParams args, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); @@ -326,18 +399,23 @@ namespace Discord.API Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendJsonAsync("POST", () => $"guilds/{guildId}/channels", args, ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendJsonAsync("POST", () => $"guilds/{guildId}/channels", args, ids, options: options) + .ConfigureAwait(false); } + public async Task DeleteChannelAsync(ulong channelId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendAsync("DELETE", () => $"channels/{channelId}", ids, options: options).ConfigureAwait(false); + return await SendAsync("DELETE", () => $"channels/{channelId}", ids, options: options) + .ConfigureAwait(false); } - public async Task ModifyGuildChannelAsync(ulong channelId, Rest.ModifyGuildChannelParams args, RequestOptions options = null) + + public async Task ModifyGuildChannelAsync(ulong channelId, ModifyGuildChannelParams args, + RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); @@ -346,9 +424,12 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options) + .ConfigureAwait(false); } - public async Task ModifyGuildChannelAsync(ulong channelId, Rest.ModifyTextChannelParams args, RequestOptions options = null) + + public async Task ModifyGuildChannelAsync(ulong channelId, ModifyTextChannelParams args, + RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); @@ -357,9 +438,12 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options) + .ConfigureAwait(false); } - public async Task ModifyGuildChannelAsync(ulong channelId, Rest.ModifyVoiceChannelParams args, RequestOptions options = null) + + public async Task ModifyGuildChannelAsync(ulong channelId, ModifyVoiceChannelParams args, + RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); @@ -370,9 +454,12 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => $"channels/{channelId}", args, ids, options: options) + .ConfigureAwait(false); } - public async Task ModifyGuildChannelsAsync(ulong guildId, IEnumerable args, RequestOptions options = null) + + public async Task ModifyGuildChannelsAsync(ulong guildId, IEnumerable args, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); @@ -384,14 +471,17 @@ namespace Discord.API case 0: return; case 1: - await ModifyGuildChannelAsync(channels[0].Id, new Rest.ModifyGuildChannelParams { Position = channels[0].Position }).ConfigureAwait(false); + await ModifyGuildChannelAsync(channels[0].Id, + new ModifyGuildChannelParams {Position = channels[0].Position}).ConfigureAwait(false); break; default: - var ids = new BucketIds(guildId: guildId); - await SendJsonAsync("PATCH", () => $"guilds/{guildId}/channels", channels, ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + await SendJsonAsync("PATCH", () => $"guilds/{guildId}/channels", channels, ids, options: options) + .ConfigureAwait(false); break; } } + public async Task AddRoleAsync(ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); @@ -400,9 +490,10 @@ namespace Discord.API Preconditions.NotEqual(roleId, guildId, nameof(roleId), "The Everyone role cannot be added to a user."); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); + var ids = new BucketIds(guildId); await SendAsync("PUT", () => $"guilds/{guildId}/members/{userId}/roles/{roleId}", ids, options: options); } + public async Task RemoveRoleAsync(ulong guildId, ulong userId, ulong roleId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); @@ -411,12 +502,13 @@ namespace Discord.API Preconditions.NotEqual(roleId, guildId, nameof(roleId), "The Everyone role cannot be removed from a user."); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); + var ids = new BucketIds(guildId); await SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}/roles/{roleId}", ids, options: options); } - + //Channel Messages - public async Task GetChannelMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) + public async Task GetChannelMessageAsync(ulong channelId, ulong messageId, + RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(messageId, 0, nameof(messageId)); @@ -425,11 +517,17 @@ namespace Discord.API try { var ids = new BucketIds(channelId: channelId); - return await SendAsync("GET", () => $"channels/{channelId}/messages/{messageId}", ids, options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"channels/{channelId}/messages/{messageId}", ids, + options: options).ConfigureAwait(false); + } + catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) + { + return null; } - catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } } - public async Task> GetChannelMessagesAsync(ulong channelId, GetChannelMessagesParams args, RequestOptions options = null) + + public async Task> GetChannelMessagesAsync(ulong channelId, + GetChannelMessagesParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); @@ -437,13 +535,12 @@ namespace Discord.API Preconditions.AtMost(args.Limit, DiscordConfig.MaxMessagesPerBatch, nameof(args.Limit)); options = RequestOptions.CreateOrClone(options); - int limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxMessagesPerBatch); - ulong? relativeId = args.RelativeMessageId.IsSpecified ? args.RelativeMessageId.Value : (ulong?)null; + var limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxMessagesPerBatch); + var relativeId = args.RelativeMessageId.IsSpecified ? args.RelativeMessageId.Value : (ulong?)null; string relativeDir; switch (args.RelativeDirection.GetValueOrDefault(Direction.Before)) { - case Direction.Before: default: relativeDir = "before"; break; @@ -461,9 +558,12 @@ namespace Discord.API endpoint = () => $"channels/{channelId}/messages?limit={limit}&{relativeDir}={relativeId}"; else endpoint = () => $"channels/{channelId}/messages?limit={limit}"; - return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); + return await SendAsync>("GET", endpoint, ids, options: options) + .ConfigureAwait(false); } - public async Task CreateMessageAsync(ulong channelId, CreateMessageParams args, RequestOptions options = null) + + public async Task CreateMessageAsync(ulong channelId, CreateMessageParams args, + RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -471,16 +571,22 @@ namespace Discord.API Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); if (args.Content?.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + throw new ArgumentException( + $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", + nameof(args.Content)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendJsonAsync("POST", () => $"channels/{channelId}/messages", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); + return await SendJsonAsync("POST", () => $"channels/{channelId}/messages", args, ids, + ClientBucketType.SendEdit, options).ConfigureAwait(false); } - public async Task CreateWebhookMessageAsync(ulong webhookId, CreateWebhookMessageParams args, RequestOptions options = null) + + public async Task CreateWebhookMessageAsync(ulong webhookId, CreateWebhookMessageParams args, + RequestOptions options = null) { if (AuthTokenType != TokenType.Webhook) - throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token."); + throw new InvalidOperationException( + $"This operation may only be called with a {nameof(TokenType.Webhook)} token."); Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(webhookId, 0, nameof(webhookId)); @@ -488,12 +594,17 @@ namespace Discord.API Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); if (args.Content?.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + throw new ArgumentException( + $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", + nameof(args.Content)); options = RequestOptions.CreateOrClone(options); - - return await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, new BucketIds(), clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); + + return await SendJsonAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args, + new BucketIds(), ClientBucketType.SendEdit, options).ConfigureAwait(false); } - public async Task UploadFileAsync(ulong channelId, UploadFileParams args, RequestOptions options = null) + + public async Task UploadFileAsync(ulong channelId, UploadFileParams args, + RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -502,15 +613,21 @@ namespace Discord.API if (args.Content.GetValueOrDefault(null) == null) args.Content = ""; else if (args.Content.IsSpecified && args.Content.Value?.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + throw new ArgumentOutOfRangeException( + $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", + nameof(args.Content)); var ids = new BucketIds(channelId: channelId); - return await SendMultipartAsync("POST", () => $"channels/{channelId}/messages", args.ToDictionary(), ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); + return await SendMultipartAsync("POST", () => $"channels/{channelId}/messages", + args.ToDictionary(), ids, ClientBucketType.SendEdit, options).ConfigureAwait(false); } - public async Task UploadWebhookFileAsync(ulong webhookId, UploadWebhookFileParams args, RequestOptions options = null) + + public async Task UploadWebhookFileAsync(ulong webhookId, UploadWebhookFileParams args, + RequestOptions options = null) { if (AuthTokenType != TokenType.Webhook) - throw new InvalidOperationException($"This operation may only be called with a {nameof(TokenType.Webhook)} token."); + throw new InvalidOperationException( + $"This operation may only be called with a {nameof(TokenType.Webhook)} token."); Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(webhookId, 0, nameof(webhookId)); @@ -523,11 +640,15 @@ namespace Discord.API if (args.Content.Value == null) args.Content = ""; if (args.Content.Value?.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + throw new ArgumentOutOfRangeException( + $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", + nameof(args.Content)); } - return await SendMultipartAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", args.ToDictionary(), new BucketIds(), clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); + return await SendMultipartAsync("POST", () => $"webhooks/{webhookId}/{AuthToken}?wait=true", + args.ToDictionary(), new BucketIds(), ClientBucketType.SendEdit, options).ConfigureAwait(false); } + public async Task DeleteMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -535,8 +656,10 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}", ids, options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}", ids, options: options) + .ConfigureAwait(false); } + public async Task DeleteMessagesAsync(ulong channelId, DeleteMessagesParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -555,11 +678,14 @@ namespace Discord.API break; default: var ids = new BucketIds(channelId: channelId); - await SendJsonAsync("POST", () => $"channels/{channelId}/messages/bulk-delete", args, ids, options: options).ConfigureAwait(false); + await SendJsonAsync("POST", () => $"channels/{channelId}/messages/bulk-delete", args, ids, + options: options).ConfigureAwait(false); break; } } - public async Task ModifyMessageAsync(ulong channelId, ulong messageId, Rest.ModifyMessageParams args, RequestOptions options = null) + + public async Task ModifyMessageAsync(ulong channelId, ulong messageId, ModifyMessageParams args, + RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(messageId, 0, nameof(messageId)); @@ -569,14 +695,20 @@ namespace Discord.API if (!args.Embed.IsSpecified) Preconditions.NotNullOrEmpty(args.Content, nameof(args.Content)); if (args.Content.Value?.Length > DiscordConfig.MaxMessageSize) - throw new ArgumentOutOfRangeException($"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", nameof(args.Content)); + throw new ArgumentOutOfRangeException( + $"Message content is too long, length must be less or equal to {DiscordConfig.MaxMessageSize}.", + nameof(args.Content)); } + options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendJsonAsync("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, clientBucket: ClientBucketType.SendEdit, options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => $"channels/{channelId}/messages/{messageId}", args, ids, + ClientBucketType.SendEdit, options).ConfigureAwait(false); } - public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, RequestOptions options = null) + + public async Task AddReactionAsync(ulong channelId, ulong messageId, string emoji, + RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(messageId, 0, nameof(messageId)); @@ -586,9 +718,12 @@ namespace Discord.API var ids = new BucketIds(channelId: channelId); - await SendAsync("PUT", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/@me", ids, options: options).ConfigureAwait(false); + await SendAsync("PUT", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/@me", ids, + options: options).ConfigureAwait(false); } - public async Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, string emoji, RequestOptions options = null) + + public async Task RemoveReactionAsync(ulong channelId, ulong messageId, ulong userId, string emoji, + RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(messageId, 0, nameof(messageId)); @@ -598,8 +733,10 @@ namespace Discord.API var ids = new BucketIds(channelId: channelId); - await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/{userId}", ids, options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}/{userId}", + ids, options: options).ConfigureAwait(false); } + public async Task RemoveAllReactionsAsync(ulong channelId, ulong messageId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -609,9 +746,12 @@ namespace Discord.API var ids = new BucketIds(channelId: channelId); - await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions", ids, options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"channels/{channelId}/messages/{messageId}/reactions", ids, + options: options).ConfigureAwait(false); } - public async Task> GetReactionUsersAsync(ulong channelId, ulong messageId, string emoji, GetReactionUsersParams args, RequestOptions options = null) + + public async Task> GetReactionUsersAsync(ulong channelId, ulong messageId, + string emoji, GetReactionUsersParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(messageId, 0, nameof(messageId)); @@ -622,13 +762,16 @@ namespace Discord.API Preconditions.GreaterThan(args.AfterUserId, 0, nameof(args.AfterUserId)); options = RequestOptions.CreateOrClone(options); - int limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxUserReactionsPerBatch); - ulong afterUserId = args.AfterUserId.GetValueOrDefault(0); + var limit = args.Limit.GetValueOrDefault(DiscordConfig.MaxUserReactionsPerBatch); + var afterUserId = args.AfterUserId.GetValueOrDefault(0); var ids = new BucketIds(channelId: channelId); - Expression> endpoint = () => $"channels/{channelId}/messages/{messageId}/reactions/{emoji}?limit={limit}&after={afterUserId}"; - return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); + Expression> endpoint = () => + $"channels/{channelId}/messages/{messageId}/reactions/{emoji}?limit={limit}&after={afterUserId}"; + return await SendAsync>("GET", endpoint, ids, options: options) + .ConfigureAwait(false); } + public async Task AckMessageAsync(ulong channelId, ulong messageId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -636,8 +779,10 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - await SendAsync("POST", () => $"channels/{channelId}/messages/{messageId}/ack", ids, options: options).ConfigureAwait(false); + await SendAsync("POST", () => $"channels/{channelId}/messages/{messageId}/ack", ids, options: options) + .ConfigureAwait(false); } + public async Task TriggerTypingIndicatorAsync(ulong channelId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -648,7 +793,8 @@ namespace Discord.API } //Channel Permissions - public async Task ModifyChannelPermissionsAsync(ulong channelId, ulong targetId, ModifyChannelPermissionsParams args, RequestOptions options = null) + public async Task ModifyChannelPermissionsAsync(ulong channelId, ulong targetId, + ModifyChannelPermissionsParams args, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotEqual(targetId, 0, nameof(targetId)); @@ -656,8 +802,10 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - await SendJsonAsync("PUT", () => $"channels/{channelId}/permissions/{targetId}", args, ids, options: options).ConfigureAwait(false); + await SendJsonAsync("PUT", () => $"channels/{channelId}/permissions/{targetId}", args, ids, + options: options).ConfigureAwait(false); } + public async Task DeleteChannelPermissionAsync(ulong channelId, ulong targetId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -665,7 +813,8 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - await SendAsync("DELETE", () => $"channels/{channelId}/permissions/{targetId}", ids, options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"channels/{channelId}/permissions/{targetId}", ids, options: options) + .ConfigureAwait(false); } //Channel Pins @@ -676,9 +825,10 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - await SendAsync("PUT", () => $"channels/{channelId}/pins/{messageId}", ids, options: options).ConfigureAwait(false); - + await SendAsync("PUT", () => $"channels/{channelId}/pins/{messageId}", ids, options: options) + .ConfigureAwait(false); } + public async Task RemovePinAsync(ulong channelId, ulong messageId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -686,15 +836,18 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - await SendAsync("DELETE", () => $"channels/{channelId}/pins/{messageId}", ids, options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"channels/{channelId}/pins/{messageId}", ids, options: options) + .ConfigureAwait(false); } + public async Task> GetPinsAsync(ulong channelId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendAsync>("GET", () => $"channels/{channelId}/pins", ids, options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => $"channels/{channelId}/pins", ids, + options: options).ConfigureAwait(false); } //Channel Recipients @@ -705,9 +858,10 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - await SendAsync("PUT", () => $"channels/{channelId}/recipients/{userId}", ids, options: options).ConfigureAwait(false); - + await SendAsync("PUT", () => $"channels/{channelId}/recipients/{userId}", ids, options: options) + .ConfigureAwait(false); } + public async Task RemoveGroupRecipientAsync(ulong channelId, ulong userId, RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); @@ -715,7 +869,8 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - await SendAsync("DELETE", () => $"channels/{channelId}/recipients/{userId}", ids, options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"channels/{channelId}/recipients/{userId}", ids, options: options) + .ConfigureAwait(false); } //Guilds @@ -726,37 +881,48 @@ namespace Discord.API try { - var ids = new BucketIds(guildId: guildId); - return await SendAsync("GET", () => $"guilds/{guildId}", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync("GET", () => $"guilds/{guildId}", ids, options: options) + .ConfigureAwait(false); + } + catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) + { + return null; } - catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } } + public async Task CreateGuildAsync(CreateGuildParams args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); Preconditions.NotNullOrWhitespace(args.Name, nameof(args.Name)); Preconditions.NotNullOrWhitespace(args.RegionId, nameof(args.RegionId)); options = RequestOptions.CreateOrClone(options); - - return await SendJsonAsync("POST", () => "guilds", args, new BucketIds(), options: options).ConfigureAwait(false); + + return await SendJsonAsync("POST", () => "guilds", args, new BucketIds(), options: options) + .ConfigureAwait(false); } + public async Task DeleteGuildAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync("DELETE", () => $"guilds/{guildId}", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync("DELETE", () => $"guilds/{guildId}", ids, options: options) + .ConfigureAwait(false); } + public async Task LeaveGuildAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync("DELETE", () => $"users/@me/guilds/{guildId}", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync("DELETE", () => $"users/@me/guilds/{guildId}", ids, options: options) + .ConfigureAwait(false); } - public async Task ModifyGuildAsync(ulong guildId, Rest.ModifyGuildParams args, RequestOptions options = null) + + public async Task ModifyGuildAsync(ulong guildId, ModifyGuildParams args, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); @@ -767,28 +933,35 @@ namespace Discord.API Preconditions.NotNull(args.RegionId, nameof(args.RegionId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendJsonAsync("PATCH", () => $"guilds/{guildId}", args, ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendJsonAsync("PATCH", () => $"guilds/{guildId}", args, ids, options: options) + .ConfigureAwait(false); } - public async Task BeginGuildPruneAsync(ulong guildId, GuildPruneParams args, RequestOptions options = null) + + public async Task BeginGuildPruneAsync(ulong guildId, GuildPruneParams args, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Days, 1, nameof(args.Days)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendJsonAsync("POST", () => $"guilds/{guildId}/prune", args, ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendJsonAsync("POST", () => $"guilds/{guildId}/prune", args, ids, + options: options).ConfigureAwait(false); } - public async Task GetGuildPruneCountAsync(ulong guildId, GuildPruneParams args, RequestOptions options = null) + + public async Task GetGuildPruneCountAsync(ulong guildId, GuildPruneParams args, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); Preconditions.AtLeast(args.Days, 1, nameof(args.Days)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync("GET", () => $"guilds/{guildId}/prune?days={args.Days}", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync("GET", () => $"guilds/{guildId}/prune?days={args.Days}", + ids, options: options).ConfigureAwait(false); } //Guild Bans @@ -797,39 +970,50 @@ namespace Discord.API Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync>("GET", () => $"guilds/{guildId}/bans", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync>("GET", () => $"guilds/{guildId}/bans", ids, + options: options).ConfigureAwait(false); } + public async Task GetGuildBanAsync(ulong guildId, ulong userId, RequestOptions options) { Preconditions.NotEqual(userId, 0, nameof(userId)); Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync("GET", () => $"guilds/{guildId}/bans/{userId}", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync("GET", () => $"guilds/{guildId}/bans/{userId}", ids, options: options) + .ConfigureAwait(false); } - public async Task CreateGuildBanAsync(ulong guildId, ulong userId, CreateGuildBanParams args, RequestOptions options = null) + + public async Task CreateGuildBanAsync(ulong guildId, ulong userId, CreateGuildBanParams args, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(userId, 0, nameof(userId)); Preconditions.NotNull(args, nameof(args)); - Preconditions.AtLeast(args.DeleteMessageDays, 0, nameof(args.DeleteMessageDays), "Prune length must be within [0, 7]"); - Preconditions.AtMost(args.DeleteMessageDays, 7, nameof(args.DeleteMessageDays), "Prune length must be within [0, 7]"); + Preconditions.AtLeast(args.DeleteMessageDays, 0, nameof(args.DeleteMessageDays), + "Prune length must be within [0, 7]"); + Preconditions.AtMost(args.DeleteMessageDays, 7, nameof(args.DeleteMessageDays), + "Prune length must be within [0, 7]"); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - string reason = string.IsNullOrWhiteSpace(args.Reason) ? "" : $"&reason={Uri.EscapeDataString(args.Reason)}"; - await SendAsync("PUT", () => $"guilds/{guildId}/bans/{userId}?delete-message-days={args.DeleteMessageDays}{reason}", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + var reason = string.IsNullOrWhiteSpace(args.Reason) ? "" : $"&reason={Uri.EscapeDataString(args.Reason)}"; + await SendAsync("PUT", + () => $"guilds/{guildId}/bans/{userId}?delete-message-days={args.DeleteMessageDays}{reason}", ids, + options: options).ConfigureAwait(false); } + public async Task RemoveGuildBanAsync(ulong guildId, ulong userId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(userId, 0, nameof(userId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - await SendAsync("DELETE", () => $"guilds/{guildId}/bans/{userId}", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + await SendAsync("DELETE", () => $"guilds/{guildId}/bans/{userId}", ids, options: options) + .ConfigureAwait(false); } //Guild Embeds @@ -840,50 +1024,67 @@ namespace Discord.API try { - var ids = new BucketIds(guildId: guildId); - return await SendAsync("GET", () => $"guilds/{guildId}/embed", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync("GET", () => $"guilds/{guildId}/embed", ids, options: options) + .ConfigureAwait(false); + } + catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) + { + return null; } - catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } } - public async Task ModifyGuildEmbedAsync(ulong guildId, Rest.ModifyGuildEmbedParams args, RequestOptions options = null) + + public async Task ModifyGuildEmbedAsync(ulong guildId, ModifyGuildEmbedParams args, + RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/embed", args, ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/embed", args, ids, + options: options).ConfigureAwait(false); } //Guild Integrations - public async Task> GetGuildIntegrationsAsync(ulong guildId, RequestOptions options = null) + public async Task> GetGuildIntegrationsAsync(ulong guildId, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync>("GET", () => $"guilds/{guildId}/integrations", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync>("GET", () => $"guilds/{guildId}/integrations", ids, + options: options).ConfigureAwait(false); } - public async Task CreateGuildIntegrationAsync(ulong guildId, CreateGuildIntegrationParams args, RequestOptions options = null) + + public async Task CreateGuildIntegrationAsync(ulong guildId, CreateGuildIntegrationParams args, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); Preconditions.NotEqual(args.Id, 0, nameof(args.Id)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync("POST", () => $"guilds/{guildId}/integrations", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync("POST", () => $"guilds/{guildId}/integrations", ids, options: options) + .ConfigureAwait(false); } - public async Task DeleteGuildIntegrationAsync(ulong guildId, ulong integrationId, RequestOptions options = null) + + public async Task DeleteGuildIntegrationAsync(ulong guildId, ulong integrationId, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync("DELETE", () => $"guilds/{guildId}/integrations/{integrationId}", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync("DELETE", () => $"guilds/{guildId}/integrations/{integrationId}", ids, + options: options).ConfigureAwait(false); } - public async Task ModifyGuildIntegrationAsync(ulong guildId, ulong integrationId, Rest.ModifyGuildIntegrationParams args, RequestOptions options = null) + + public async Task ModifyGuildIntegrationAsync(ulong guildId, ulong integrationId, + ModifyGuildIntegrationParams args, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); @@ -892,17 +1093,21 @@ namespace Discord.API Preconditions.AtLeast(args.ExpireGracePeriod, 0, nameof(args.ExpireGracePeriod)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/integrations/{integrationId}", args, ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/integrations/{integrationId}", + args, ids, options: options).ConfigureAwait(false); } - public async Task SyncGuildIntegrationAsync(ulong guildId, ulong integrationId, RequestOptions options = null) + + public async Task SyncGuildIntegrationAsync(ulong guildId, ulong integrationId, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(integrationId, 0, nameof(integrationId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync("POST", () => $"guilds/{guildId}/integrations/{integrationId}/sync", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync("POST", () => $"guilds/{guildId}/integrations/{integrationId}/sync", + ids, options: options).ConfigureAwait(false); } //Guild Invites @@ -915,41 +1120,55 @@ namespace Discord.API if (inviteId[inviteId.Length - 1] == '/') inviteId = inviteId.Substring(0, inviteId.Length - 1); //Remove leading URL - int index = inviteId.LastIndexOf('/'); + var index = inviteId.LastIndexOf('/'); if (index >= 0) inviteId = inviteId.Substring(index + 1); try { - return await SendAsync("GET", () => $"invites/{inviteId}?with_counts=true", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"invites/{inviteId}?with_counts=true", + new BucketIds(), options: options).ConfigureAwait(false); + } + catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) + { + return null; } - catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } } + public async Task GetVanityInviteAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync("GET", () => $"guilds/{guildId}/vanity-url", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync("GET", () => $"guilds/{guildId}/vanity-url", ids, options: options) + .ConfigureAwait(false); } - public async Task> GetGuildInvitesAsync(ulong guildId, RequestOptions options = null) + + public async Task> GetGuildInvitesAsync(ulong guildId, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync>("GET", () => $"guilds/{guildId}/invites", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync>("GET", () => $"guilds/{guildId}/invites", ids, + options: options).ConfigureAwait(false); } - public async Task> GetChannelInvitesAsync(ulong channelId, RequestOptions options = null) + + public async Task> GetChannelInvitesAsync(ulong channelId, + RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendAsync>("GET", () => $"channels/{channelId}/invites", ids, options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => $"channels/{channelId}/invites", + ids, options: options).ConfigureAwait(false); } - public async Task CreateChannelInviteAsync(ulong channelId, CreateChannelInviteParams args, RequestOptions options = null) + + public async Task CreateChannelInviteAsync(ulong channelId, CreateChannelInviteParams args, + RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); @@ -958,14 +1177,17 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendJsonAsync("POST", () => $"channels/{channelId}/invites", args, ids, options: options).ConfigureAwait(false); + return await SendJsonAsync("POST", () => $"channels/{channelId}/invites", args, ids, + options: options).ConfigureAwait(false); } + public async Task DeleteInviteAsync(string inviteId, RequestOptions options = null) { Preconditions.NotNullOrEmpty(inviteId, nameof(inviteId)); options = RequestOptions.CreateOrClone(options); - - return await SendAsync("DELETE", () => $"invites/{inviteId}", new BucketIds(), options: options).ConfigureAwait(false); + + return await SendAsync("DELETE", () => $"invites/{inviteId}", new BucketIds(), options: options) + .ConfigureAwait(false); } //Guild Members @@ -977,12 +1199,18 @@ namespace Discord.API try { - var ids = new BucketIds(guildId: guildId); - return await SendAsync("GET", () => $"guilds/{guildId}/members/{userId}", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync("GET", () => $"guilds/{guildId}/members/{userId}", ids, + options: options).ConfigureAwait(false); + } + catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) + { + return null; } - catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } } - public async Task> GetGuildMembersAsync(ulong guildId, GetGuildMembersParams args, RequestOptions options = null) + + public async Task> GetGuildMembersAsync(ulong guildId, + GetGuildMembersParams args, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); @@ -991,44 +1219,52 @@ namespace Discord.API Preconditions.GreaterThan(args.AfterUserId, 0, nameof(args.AfterUserId)); options = RequestOptions.CreateOrClone(options); - int limit = args.Limit.GetValueOrDefault(int.MaxValue); - ulong afterUserId = args.AfterUserId.GetValueOrDefault(0); + var limit = args.Limit.GetValueOrDefault(int.MaxValue); + var afterUserId = args.AfterUserId.GetValueOrDefault(0); - var ids = new BucketIds(guildId: guildId); + var ids = new BucketIds(guildId); Expression> endpoint = () => $"guilds/{guildId}/members?limit={limit}&after={afterUserId}"; - return await SendAsync>("GET", endpoint, ids, options: options).ConfigureAwait(false); + return await SendAsync>("GET", endpoint, ids, options: options) + .ConfigureAwait(false); } - public async Task RemoveGuildMemberAsync(ulong guildId, ulong userId, string reason, RequestOptions options = null) + + public async Task RemoveGuildMemberAsync(ulong guildId, ulong userId, string reason, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(userId, 0, nameof(userId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); + var ids = new BucketIds(guildId); reason = string.IsNullOrWhiteSpace(reason) ? "" : $"?reason={Uri.EscapeDataString(reason)}"; - await SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}{reason}", ids, options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"guilds/{guildId}/members/{userId}{reason}", ids, options: options) + .ConfigureAwait(false); } - public async Task ModifyGuildMemberAsync(ulong guildId, ulong userId, Rest.ModifyGuildMemberParams args, RequestOptions options = null) + + public async Task ModifyGuildMemberAsync(ulong guildId, ulong userId, ModifyGuildMemberParams args, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(userId, 0, nameof(userId)); Preconditions.NotNull(args, nameof(args)); options = RequestOptions.CreateOrClone(options); - bool isCurrentUser = userId == CurrentUserId; + var isCurrentUser = userId == CurrentUserId; if (args.RoleIds.IsSpecified) Preconditions.NotEveryoneRole(args.RoleIds.Value, guildId, nameof(args.RoleIds)); if (isCurrentUser && args.Nickname.IsSpecified) { - var nickArgs = new Rest.ModifyCurrentUserNickParams(args.Nickname.Value ?? ""); + var nickArgs = new ModifyCurrentUserNickParams(args.Nickname.Value ?? ""); await ModifyMyNickAsync(guildId, nickArgs).ConfigureAwait(false); args.Nickname = Optional.Create(); //Remove } + if (!isCurrentUser || args.Deaf.IsSpecified || args.Mute.IsSpecified || args.RoleIds.IsSpecified) { - var ids = new BucketIds(guildId: guildId); - await SendJsonAsync("PATCH", () => $"guilds/{guildId}/members/{userId}", args, ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + await SendJsonAsync("PATCH", () => $"guilds/{guildId}/members/{userId}", args, ids, options: options) + .ConfigureAwait(false); } } @@ -1038,27 +1274,34 @@ namespace Discord.API Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync>("GET", () => $"guilds/{guildId}/roles", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync>("GET", () => $"guilds/{guildId}/roles", ids, + options: options).ConfigureAwait(false); } + public async Task CreateGuildRoleAsync(ulong guildId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync("POST", () => $"guilds/{guildId}/roles", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync("POST", () => $"guilds/{guildId}/roles", ids, options: options) + .ConfigureAwait(false); } + public async Task DeleteGuildRoleAsync(ulong guildId, ulong roleId, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(roleId, 0, nameof(roleId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - await SendAsync("DELETE", () => $"guilds/{guildId}/roles/{roleId}", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + await SendAsync("DELETE", () => $"guilds/{guildId}/roles/{roleId}", ids, options: options) + .ConfigureAwait(false); } - public async Task ModifyGuildRoleAsync(ulong guildId, ulong roleId, Rest.ModifyGuildRoleParams args, RequestOptions options = null) + + public async Task ModifyGuildRoleAsync(ulong guildId, ulong roleId, ModifyGuildRoleParams args, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(roleId, 0, nameof(roleId)); @@ -1067,17 +1310,21 @@ namespace Discord.API Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/roles/{roleId}", args, ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/roles/{roleId}", args, ids, + options: options).ConfigureAwait(false); } - public async Task> ModifyGuildRolesAsync(ulong guildId, IEnumerable args, RequestOptions options = null) + + public async Task> ModifyGuildRolesAsync(ulong guildId, + IEnumerable args, RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendJsonAsync>("PATCH", () => $"guilds/{guildId}/roles", args, ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendJsonAsync>("PATCH", () => $"guilds/{guildId}/roles", args, ids, + options: options).ConfigureAwait(false); } //Guild emoji @@ -1087,11 +1334,12 @@ namespace Discord.API Preconditions.NotEqual(emoteId, 0, nameof(emoteId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); + var ids = new BucketIds(guildId); return await SendAsync("GET", () => $"guilds/{guildId}/emojis/{emoteId}", ids, options: options); } - public async Task CreateGuildEmoteAsync(ulong guildId, Rest.CreateGuildEmoteParams args, RequestOptions options = null) + public async Task CreateGuildEmoteAsync(ulong guildId, CreateGuildEmoteParams args, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); @@ -1099,19 +1347,21 @@ namespace Discord.API Preconditions.NotNull(args.Image.Stream, nameof(args.Image)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); + var ids = new BucketIds(guildId); return await SendJsonAsync("POST", () => $"guilds/{guildId}/emojis", args, ids, options: options); } - public async Task ModifyGuildEmoteAsync(ulong guildId, ulong emoteId, ModifyGuildEmoteParams args, RequestOptions options = null) + public async Task ModifyGuildEmoteAsync(ulong guildId, ulong emoteId, ModifyGuildEmoteParams args, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotEqual(emoteId, 0, nameof(emoteId)); Preconditions.NotNull(args, nameof(args)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/emojis/{emoteId}", args, ids, options: options); + var ids = new BucketIds(guildId); + return await SendJsonAsync("PATCH", () => $"guilds/{guildId}/emojis/{emoteId}", args, ids, + options: options); } public async Task DeleteGuildEmoteAsync(ulong guildId, ulong emoteId, RequestOptions options = null) @@ -1120,7 +1370,7 @@ namespace Discord.API Preconditions.NotEqual(emoteId, 0, nameof(emoteId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); + var ids = new BucketIds(guildId); await SendAsync("DELETE", () => $"guilds/{guildId}/emojis/{emoteId}", ids, options: options); } @@ -1132,28 +1382,39 @@ namespace Discord.API try { - return await SendAsync("GET", () => $"users/{userId}", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"users/{userId}", new BucketIds(), options: options) + .ConfigureAwait(false); + } + catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) + { + return null; } - catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } } //Current User/DMs public async Task GetMyUserAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendAsync("GET", () => "users/@me", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync("GET", () => "users/@me", new BucketIds(), options: options) + .ConfigureAwait(false); } + public async Task> GetMyConnectionsAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendAsync>("GET", () => "users/@me/connections", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => "users/@me/connections", + new BucketIds(), options: options).ConfigureAwait(false); } + public async Task> GetMyPrivateChannelsAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendAsync>("GET", () => "users/@me/channels", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => "users/@me/channels", new BucketIds(), + options: options).ConfigureAwait(false); } - public async Task> GetMyGuildsAsync(GetGuildSummariesParams args, RequestOptions options = null) + + public async Task> GetMyGuildsAsync(GetGuildSummariesParams args, + RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); Preconditions.GreaterThan(args.Limit, 0, nameof(args.Limit)); @@ -1161,67 +1422,83 @@ namespace Discord.API Preconditions.GreaterThan(args.AfterGuildId, 0, nameof(args.AfterGuildId)); options = RequestOptions.CreateOrClone(options); - int limit = args.Limit.GetValueOrDefault(int.MaxValue); - ulong afterGuildId = args.AfterGuildId.GetValueOrDefault(0); - - return await SendAsync>("GET", () => $"users/@me/guilds?limit={limit}&after={afterGuildId}", new BucketIds(), options: options).ConfigureAwait(false); + var limit = args.Limit.GetValueOrDefault(int.MaxValue); + var afterGuildId = args.AfterGuildId.GetValueOrDefault(0); + + return await SendAsync>("GET", + () => $"users/@me/guilds?limit={limit}&after={afterGuildId}", new BucketIds(), options: options) + .ConfigureAwait(false); } + public async Task GetMyApplicationAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendAsync("GET", () => "oauth2/applications/@me", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync("GET", () => "oauth2/applications/@me", new BucketIds(), + options: options).ConfigureAwait(false); } - public async Task ModifySelfAsync(Rest.ModifyCurrentUserParams args, RequestOptions options = null) + + public async Task ModifySelfAsync(ModifyCurrentUserParams args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); Preconditions.NotNullOrEmpty(args.Username, nameof(args.Username)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("PATCH", () => "users/@me", args, new BucketIds(), options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => "users/@me", args, new BucketIds(), options: options) + .ConfigureAwait(false); } - public async Task ModifyMyNickAsync(ulong guildId, Rest.ModifyCurrentUserNickParams args, RequestOptions options = null) + + public async Task ModifyMyNickAsync(ulong guildId, ModifyCurrentUserNickParams args, + RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); Preconditions.NotNull(args.Nickname, nameof(args.Nickname)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - await SendJsonAsync("PATCH", () => $"guilds/{guildId}/members/@me/nick", args, ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + await SendJsonAsync("PATCH", () => $"guilds/{guildId}/members/@me/nick", args, ids, options: options) + .ConfigureAwait(false); } + public async Task CreateDMChannelAsync(CreateDMChannelParams args, RequestOptions options = null) { Preconditions.NotNull(args, nameof(args)); Preconditions.GreaterThan(args.RecipientId, 0, nameof(args.RecipientId)); options = RequestOptions.CreateOrClone(options); - return await SendJsonAsync("POST", () => "users/@me/channels", args, new BucketIds(), options: options).ConfigureAwait(false); + return await SendJsonAsync("POST", () => "users/@me/channels", args, new BucketIds(), + options: options).ConfigureAwait(false); } //Voice Regions public async Task> GetVoiceRegionsAsync(RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - return await SendAsync>("GET", () => "voice/regions", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => "voice/regions", new BucketIds(), + options: options).ConfigureAwait(false); } - public async Task> GetGuildVoiceRegionsAsync(ulong guildId, RequestOptions options = null) + + public async Task> GetGuildVoiceRegionsAsync(ulong guildId, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync>("GET", () => $"guilds/{guildId}/regions", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync>("GET", () => $"guilds/{guildId}/regions", ids, + options: options).ConfigureAwait(false); } //Audit logs - public async Task GetAuditLogsAsync(ulong guildId, GetAuditLogsParams args, RequestOptions options = null) + public async Task GetAuditLogsAsync(ulong guildId, GetAuditLogsParams args, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); Preconditions.NotNull(args, nameof(args)); options = RequestOptions.CreateOrClone(options); - int limit = args.Limit.GetValueOrDefault(int.MaxValue); + var limit = args.Limit.GetValueOrDefault(int.MaxValue); - var ids = new BucketIds(guildId: guildId); + var ids = new BucketIds(guildId); Expression> endpoint; if (args.BeforeEntryId.IsSpecified) @@ -1233,7 +1510,8 @@ namespace Discord.API } //Webhooks - public async Task CreateWebhookAsync(ulong channelId, CreateWebhookParams args, RequestOptions options = null) + public async Task CreateWebhookAsync(ulong channelId, CreateWebhookParams args, + RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); Preconditions.NotNull(args, nameof(args)); @@ -1241,8 +1519,10 @@ namespace Discord.API options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendJsonAsync("POST", () => $"channels/{channelId}/webhooks", args, ids, options: options); + return await SendJsonAsync("POST", () => $"channels/{channelId}/webhooks", args, ids, + options: options); } + public async Task GetWebhookAsync(ulong webhookId, RequestOptions options = null) { Preconditions.NotEqual(webhookId, 0, nameof(webhookId)); @@ -1251,49 +1531,65 @@ namespace Discord.API try { if (AuthTokenType == TokenType.Webhook) - return await SendAsync("GET", () => $"webhooks/{webhookId}/{AuthToken}", new BucketIds(), options: options).ConfigureAwait(false); - else - return await SendAsync("GET", () => $"webhooks/{webhookId}", new BucketIds(), options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"webhooks/{webhookId}/{AuthToken}", new BucketIds(), + options: options).ConfigureAwait(false); + return await SendAsync("GET", () => $"webhooks/{webhookId}", new BucketIds(), options: options) + .ConfigureAwait(false); + } + catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) + { + return null; } - catch (HttpException ex) when (ex.HttpCode == HttpStatusCode.NotFound) { return null; } } - public async Task ModifyWebhookAsync(ulong webhookId, ModifyWebhookParams args, RequestOptions options = null) + + public async Task ModifyWebhookAsync(ulong webhookId, ModifyWebhookParams args, + RequestOptions options = null) { Preconditions.NotEqual(webhookId, 0, nameof(webhookId)); Preconditions.NotNull(args, nameof(args)); Preconditions.NotNullOrEmpty(args.Name, nameof(args.Name)); options = RequestOptions.CreateOrClone(options); - + if (AuthTokenType == TokenType.Webhook) - return await SendJsonAsync("PATCH", () => $"webhooks/{webhookId}/{AuthToken}", args, new BucketIds(), options: options).ConfigureAwait(false); - else - return await SendJsonAsync("PATCH", () => $"webhooks/{webhookId}", args, new BucketIds(), options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => $"webhooks/{webhookId}/{AuthToken}", args, + new BucketIds(), options: options).ConfigureAwait(false); + return await SendJsonAsync("PATCH", () => $"webhooks/{webhookId}", args, new BucketIds(), + options: options).ConfigureAwait(false); } + public async Task DeleteWebhookAsync(ulong webhookId, RequestOptions options = null) { Preconditions.NotEqual(webhookId, 0, nameof(webhookId)); options = RequestOptions.CreateOrClone(options); if (AuthTokenType == TokenType.Webhook) - await SendAsync("DELETE", () => $"webhooks/{webhookId}/{AuthToken}", new BucketIds(), options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"webhooks/{webhookId}/{AuthToken}", new BucketIds(), options: options) + .ConfigureAwait(false); else - await SendAsync("DELETE", () => $"webhooks/{webhookId}", new BucketIds(), options: options).ConfigureAwait(false); + await SendAsync("DELETE", () => $"webhooks/{webhookId}", new BucketIds(), options: options) + .ConfigureAwait(false); } - public async Task> GetGuildWebhooksAsync(ulong guildId, RequestOptions options = null) + + public async Task> GetGuildWebhooksAsync(ulong guildId, + RequestOptions options = null) { Preconditions.NotEqual(guildId, 0, nameof(guildId)); options = RequestOptions.CreateOrClone(options); - var ids = new BucketIds(guildId: guildId); - return await SendAsync>("GET", () => $"guilds/{guildId}/webhooks", ids, options: options).ConfigureAwait(false); + var ids = new BucketIds(guildId); + return await SendAsync>("GET", () => $"guilds/{guildId}/webhooks", ids, + options: options).ConfigureAwait(false); } - public async Task> GetChannelWebhooksAsync(ulong channelId, RequestOptions options = null) + + public async Task> GetChannelWebhooksAsync(ulong channelId, + RequestOptions options = null) { Preconditions.NotEqual(channelId, 0, nameof(channelId)); options = RequestOptions.CreateOrClone(options); var ids = new BucketIds(channelId: channelId); - return await SendAsync>("GET", () => $"channels/{channelId}/webhooks", ids, options: options).ConfigureAwait(false); + return await SendAsync>("GET", () => $"channels/{channelId}/webhooks", ids, + options: options).ConfigureAwait(false); } //Helpers @@ -1302,7 +1598,10 @@ namespace Discord.API if (LoginState != LoginState.LoggedIn) throw new InvalidOperationException("Client is not logged in."); } - protected static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); + + protected static double ToMilliseconds(Stopwatch stopwatch) => + Math.Round(stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); + protected string SerializeJson(object value) { var sb = new StringBuilder(256); @@ -1311,6 +1610,7 @@ namespace Discord.API _serializer.Serialize(writer, value); return sb.ToString(); } + protected T DeserializeJson(Stream jsonStream) { using (TextReader text = new StreamReader(jsonStream)) @@ -1318,39 +1618,11 @@ namespace Discord.API return _serializer.Deserialize(reader); } - internal class BucketIds - { - public ulong GuildId { get; internal set; } - public ulong ChannelId { get; internal set; } + private static string GetEndpoint(Expression> endpointExpr) => endpointExpr.Compile()(); - internal BucketIds(ulong guildId = 0, ulong channelId = 0) - { - GuildId = guildId; - ChannelId = channelId; - } - internal object[] ToArray() - => new object[] { GuildId, ChannelId }; - - internal static int? GetIndex(string name) - { - switch (name) - { - case "guildId": return 0; - case "channelId": return 1; - default: - return null; - } - } - } - - private static string GetEndpoint(Expression> endpointExpr) - { - return endpointExpr.Compile()(); - } - private static string GetBucketId(BucketIds ids, Expression> endpointExpr, TokenType tokenType, string callingMethod) - { - return _bucketIdGenerators.GetOrAdd(callingMethod, x => CreateBucketId(endpointExpr))(ids); - } + private static string GetBucketId(BucketIds ids, Expression> endpointExpr, TokenType tokenType, + string callingMethod) => + _bucketIdGenerators.GetOrAdd(callingMethod, x => CreateBucketId(endpointExpr))(ids); private static Func CreateBucketId(Expression> endpoint) { @@ -1363,7 +1635,7 @@ namespace Discord.API var builder = new StringBuilder(); var methodCall = endpoint.Body as MethodCallExpression; var methodArgs = methodCall.Arguments.ToArray(); - string format = (methodArgs[0] as ConstantExpression).Value as string; + var format = (methodArgs[0] as ConstantExpression).Value as string; //Unpack the array, if one exists (happens with 4+ parameters) if (methodArgs.Length > 1 && methodArgs[1].NodeType == ExpressionType.NewArrayInit) @@ -1374,28 +1646,29 @@ namespace Discord.API Array.Copy(elements, 0, methodArgs, 1, elements.Length); } - int endIndex = format.IndexOf('?'); //Dont include params + var endIndex = format.IndexOf('?'); //Dont include params if (endIndex == -1) endIndex = format.Length; - int lastIndex = 0; + var lastIndex = 0; while (true) { - int leftIndex = format.IndexOf("{", lastIndex); + var leftIndex = format.IndexOf("{", lastIndex); if (leftIndex == -1 || leftIndex > endIndex) { builder.Append(format, lastIndex, endIndex - lastIndex); break; } + builder.Append(format, lastIndex, leftIndex - lastIndex); - int rightIndex = format.IndexOf("}", leftIndex); - - int argId = int.Parse(format.Substring(leftIndex + 1, rightIndex - leftIndex - 1)); - string fieldName = GetFieldName(methodArgs[argId + 1]); - int? mappedId; - - mappedId = BucketIds.GetIndex(fieldName); - if(!mappedId.HasValue && rightIndex != endIndex && format.Length > rightIndex + 1 && format[rightIndex + 1] == '/') //Ignore the next slash + var rightIndex = format.IndexOf("}", leftIndex); + + var argId = int.Parse(format.Substring(leftIndex + 1, rightIndex - leftIndex - 1)); + var fieldName = GetFieldName(methodArgs[argId + 1]); + + var mappedId = BucketIds.GetIndex(fieldName); + if (!mappedId.HasValue && rightIndex != endIndex && format.Length > rightIndex + 1 && + format[rightIndex + 1] == '/') //Ignore the next slash rightIndex++; if (mappedId.HasValue) @@ -1423,5 +1696,31 @@ namespace Discord.API return (expr as MemberExpression).Member.Name; } + + internal class BucketIds + { + internal BucketIds(ulong guildId = 0, ulong channelId = 0) + { + GuildId = guildId; + ChannelId = channelId; + } + + public ulong GuildId { get; internal set; } + public ulong ChannelId { get; internal set; } + + internal object[] ToArray() + => new object[] {GuildId, ChannelId}; + + internal static int? GetIndex(string name) + { + switch (name) + { + case "guildId": return 0; + case "channelId": return 1; + default: + return null; + } + } + } } } diff --git a/src/Discord.Net.Rest/DiscordRestClient.cs b/src/Discord.Net.Rest/DiscordRestClient.cs index d4dee9f0a..5df2a6f59 100644 --- a/src/Discord.Net.Rest/DiscordRestClient.cs +++ b/src/Discord.Net.Rest/DiscordRestClient.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Threading.Tasks; +using Discord.API; namespace Discord.Rest { @@ -9,13 +10,94 @@ namespace Discord.Rest { private RestApplication _applicationInfo; + public DiscordRestClient() : this(new DiscordRestConfig()) + { + } + + public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config)) + { + } + public new RestSelfUser CurrentUser => base.CurrentUser as RestSelfUser; - public DiscordRestClient() : this(new DiscordRestConfig()) { } - public DiscordRestClient(DiscordRestConfig config) : base(config, CreateApiClient(config)) { } + //IDiscordClient + async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) + => await GetApplicationInfoAsync(options).ConfigureAwait(false); + + async Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetChannelAsync(id, options).ConfigureAwait(false); + return null; + } + + async Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, + RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetPrivateChannelsAsync(options).ConfigureAwait(false); + return ImmutableArray.Create(); + } + + async Task> IDiscordClient.GetDMChannelsAsync(CacheMode mode, + RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetDMChannelsAsync(options).ConfigureAwait(false); + return ImmutableArray.Create(); + } + + async Task> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, + RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetGroupChannelsAsync(options).ConfigureAwait(false); + return ImmutableArray.Create(); + } + + async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) + => await GetConnectionsAsync(options).ConfigureAwait(false); + + async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) + => await GetInviteAsync(inviteId, options).ConfigureAwait(false); + + async Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetGuildAsync(id, options).ConfigureAwait(false); + return null; + } + + async Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetGuildsAsync(options).ConfigureAwait(false); + return ImmutableArray.Create(); + } + + async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, + RequestOptions options) + => await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false); + + async Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetUserAsync(id, options).ConfigureAwait(false); + return null; + } + + async Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) + => await GetVoiceRegionsAsync(options).ConfigureAwait(false); + + async Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) + => await GetVoiceRegionAsync(id, options).ConfigureAwait(false); + + async Task IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) + => await GetWebhookAsync(id, options); + + private static DiscordRestApiClient CreateApiClient(DiscordRestConfig config) + => new DiscordRestApiClient(config.RestClientProvider, DiscordConfig.UserAgent); - private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) - => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent); internal override void Dispose(bool disposing) { if (disposing) @@ -24,10 +106,12 @@ namespace Discord.Rest internal override async Task OnLoginAsync(TokenType tokenType, string token) { - var user = await ApiClient.GetMyUserAsync(new RequestOptions { RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false); + var user = await ApiClient.GetMyUserAsync(new RequestOptions {RetryMode = RetryMode.AlwaysRetry}) + .ConfigureAwait(false); ApiClient.CurrentUserId = user.Id; base.CurrentUser = RestSelfUser.Create(this, user); } + internal override Task OnLogoutAsync() { _applicationInfo = null; @@ -35,19 +119,20 @@ namespace Discord.Rest } /// - public async Task GetApplicationInfoAsync(RequestOptions options = null) - { - return _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options)); - } - + public async Task GetApplicationInfoAsync(RequestOptions options = null) => + _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options)); + /// public Task GetChannelAsync(ulong id, RequestOptions options = null) => ClientHelper.GetChannelAsync(this, id, options); + /// public Task> GetPrivateChannelsAsync(RequestOptions options = null) => ClientHelper.GetPrivateChannelsAsync(this, options); + public Task> GetDMChannelsAsync(RequestOptions options = null) => ClientHelper.GetDMChannelsAsync(this, options); + public Task> GetGroupChannelsAsync(RequestOptions options = null) => ClientHelper.GetGroupChannelsAsync(this, options); @@ -62,25 +147,34 @@ namespace Discord.Rest /// public Task GetGuildAsync(ulong id, RequestOptions options = null) => ClientHelper.GetGuildAsync(this, id, options); + /// public Task GetGuildEmbedAsync(ulong id, RequestOptions options = null) => ClientHelper.GetGuildEmbedAsync(this, id, options); + /// - public IAsyncEnumerable> GetGuildSummariesAsync(RequestOptions options = null) + public IAsyncEnumerable> GetGuildSummariesAsync( + RequestOptions options = null) => ClientHelper.GetGuildSummariesAsync(this, null, null, options); + /// - public IAsyncEnumerable> GetGuildSummariesAsync(ulong fromGuildId, int limit, RequestOptions options = null) + public IAsyncEnumerable> GetGuildSummariesAsync(ulong fromGuildId, int limit, + RequestOptions options = null) => ClientHelper.GetGuildSummariesAsync(this, fromGuildId, limit, options); + /// public Task> GetGuildsAsync(RequestOptions options = null) => ClientHelper.GetGuildsAsync(this, options); + /// - public Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null) + public Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, + RequestOptions options = null) => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options); /// public Task GetUserAsync(ulong id, RequestOptions options = null) => ClientHelper.GetUserAsync(this, id, options); + /// public Task GetGuildUserAsync(ulong guildId, ulong id, RequestOptions options = null) => ClientHelper.GetGuildUserAsync(this, guildId, id, options); @@ -88,83 +182,13 @@ namespace Discord.Rest /// public Task> GetVoiceRegionsAsync(RequestOptions options = null) => ClientHelper.GetVoiceRegionsAsync(this, options); + /// public Task GetVoiceRegionAsync(string id, RequestOptions options = null) => ClientHelper.GetVoiceRegionAsync(this, id, options); + /// public Task GetWebhookAsync(ulong id, RequestOptions options = null) => ClientHelper.GetWebhookAsync(this, id, options); - - //IDiscordClient - async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) - => await GetApplicationInfoAsync(options).ConfigureAwait(false); - - async Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetChannelAsync(id, options).ConfigureAwait(false); - else - return null; - } - async Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetPrivateChannelsAsync(options).ConfigureAwait(false); - else - return ImmutableArray.Create(); - } - async Task> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetDMChannelsAsync(options).ConfigureAwait(false); - else - return ImmutableArray.Create(); - } - async Task> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetGroupChannelsAsync(options).ConfigureAwait(false); - else - return ImmutableArray.Create(); - } - - async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) - => await GetConnectionsAsync(options).ConfigureAwait(false); - - async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) - => await GetInviteAsync(inviteId, options).ConfigureAwait(false); - - async Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetGuildAsync(id, options).ConfigureAwait(false); - else - return null; - } - async Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetGuildsAsync(options).ConfigureAwait(false); - else - return ImmutableArray.Create(); - } - async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) - => await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false); - - async Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetUserAsync(id, options).ConfigureAwait(false); - else - return null; - } - - async Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) - => await GetVoiceRegionsAsync(options).ConfigureAwait(false); - async Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) - => await GetVoiceRegionAsync(id, options).ConfigureAwait(false); - - async Task IDiscordClient.GetWebhookAsync(ulong id, RequestOptions options) - => await GetWebhookAsync(id, options); } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs index 7936343f3..969f9788c 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -8,51 +7,49 @@ namespace Discord.Rest { internal static class AuditLogHelper { - private static readonly Dictionary> CreateMapping - = new Dictionary>() - { - [ActionType.GuildUpdated] = GuildUpdateAuditLogData.Create, - - [ActionType.ChannelCreated] = ChannelCreateAuditLogData.Create, - [ActionType.ChannelUpdated] = ChannelUpdateAuditLogData.Create, - [ActionType.ChannelDeleted] = ChannelDeleteAuditLogData.Create, - - [ActionType.OverwriteCreated] = OverwriteCreateAuditLogData.Create, - [ActionType.OverwriteUpdated] = OverwriteUpdateAuditLogData.Create, - [ActionType.OverwriteDeleted] = OverwriteDeleteAuditLogData.Create, - - [ActionType.Kick] = KickAuditLogData.Create, - [ActionType.Prune] = PruneAuditLogData.Create, - [ActionType.Ban] = BanAuditLogData.Create, - [ActionType.Unban] = UnbanAuditLogData.Create, - [ActionType.MemberUpdated] = MemberUpdateAuditLogData.Create, - [ActionType.MemberRoleUpdated] = MemberRoleAuditLogData.Create, - - [ActionType.RoleCreated] = RoleCreateAuditLogData.Create, - [ActionType.RoleUpdated] = RoleUpdateAuditLogData.Create, - [ActionType.RoleDeleted] = RoleDeleteAuditLogData.Create, - - [ActionType.InviteCreated] = InviteCreateAuditLogData.Create, - [ActionType.InviteUpdated] = InviteUpdateAuditLogData.Create, - [ActionType.InviteDeleted] = InviteDeleteAuditLogData.Create, - - [ActionType.WebhookCreated] = WebhookCreateAuditLogData.Create, - [ActionType.WebhookUpdated] = WebhookUpdateAuditLogData.Create, - [ActionType.WebhookDeleted] = WebhookDeleteAuditLogData.Create, - - [ActionType.EmojiCreated] = EmoteCreateAuditLogData.Create, - [ActionType.EmojiUpdated] = EmoteUpdateAuditLogData.Create, - [ActionType.EmojiDeleted] = EmoteDeleteAuditLogData.Create, - - [ActionType.MessageDeleted] = MessageDeleteAuditLogData.Create, - }; + private static readonly Dictionary> + CreateMapping + = new Dictionary> + { + [ActionType.GuildUpdated] = GuildUpdateAuditLogData.Create, + + [ActionType.ChannelCreated] = ChannelCreateAuditLogData.Create, + [ActionType.ChannelUpdated] = ChannelUpdateAuditLogData.Create, + [ActionType.ChannelDeleted] = ChannelDeleteAuditLogData.Create, + + [ActionType.OverwriteCreated] = OverwriteCreateAuditLogData.Create, + [ActionType.OverwriteUpdated] = OverwriteUpdateAuditLogData.Create, + [ActionType.OverwriteDeleted] = OverwriteDeleteAuditLogData.Create, + + [ActionType.Kick] = KickAuditLogData.Create, + [ActionType.Prune] = PruneAuditLogData.Create, + [ActionType.Ban] = BanAuditLogData.Create, + [ActionType.Unban] = UnbanAuditLogData.Create, + [ActionType.MemberUpdated] = MemberUpdateAuditLogData.Create, + [ActionType.MemberRoleUpdated] = MemberRoleAuditLogData.Create, + + [ActionType.RoleCreated] = RoleCreateAuditLogData.Create, + [ActionType.RoleUpdated] = RoleUpdateAuditLogData.Create, + [ActionType.RoleDeleted] = RoleDeleteAuditLogData.Create, + + [ActionType.InviteCreated] = InviteCreateAuditLogData.Create, + [ActionType.InviteUpdated] = InviteUpdateAuditLogData.Create, + [ActionType.InviteDeleted] = InviteDeleteAuditLogData.Create, + + [ActionType.WebhookCreated] = WebhookCreateAuditLogData.Create, + [ActionType.WebhookUpdated] = WebhookUpdateAuditLogData.Create, + [ActionType.WebhookDeleted] = WebhookDeleteAuditLogData.Create, + + [ActionType.EmojiCreated] = EmoteCreateAuditLogData.Create, + [ActionType.EmojiUpdated] = EmoteUpdateAuditLogData.Create, + [ActionType.EmojiDeleted] = EmoteDeleteAuditLogData.Create, + + [ActionType.MessageDeleted] = MessageDeleteAuditLogData.Create + }; public static IAuditLogData CreateData(BaseDiscordClient discord, Model log, EntryModel entry) { - if (CreateMapping.TryGetValue(entry.Action, out var func)) - return func(discord, log, entry); - - return null; + return CreateMapping.TryGetValue(entry.Action, out var func) ? func(discord, log, entry) : null; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs index 4b9d5875f..2aa94d807 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/BanAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -12,12 +11,12 @@ namespace Discord.Rest Target = user; } + public IUser Target { get; } + internal static BanAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); return new BanAuditLogData(RestUser.Create(discord, userInfo)); } - - public IUser Target { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs index ef4787295..5533f3e0e 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelCreateAuditLogData.cs @@ -1,7 +1,5 @@ -using Newtonsoft.Json.Linq; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -9,7 +7,8 @@ namespace Discord.Rest { public class ChannelCreateAuditLogData : IAuditLogData { - private ChannelCreateAuditLogData(ulong id, string name, ChannelType type, IReadOnlyCollection overwrites) + private ChannelCreateAuditLogData(ulong id, string name, ChannelType type, + IReadOnlyCollection overwrites) { ChannelId = id; ChannelName = name; @@ -17,10 +16,14 @@ namespace Discord.Rest Overwrites = overwrites; } + public ulong ChannelId { get; } + public string ChannelName { get; } + public ChannelType ChannelType { get; } + public IReadOnlyCollection Overwrites { get; } + internal static ChannelCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; - var overwrites = new List(); var overwritesModel = changes.FirstOrDefault(x => x.ChangedProperty == "permission_overwrites"); var typeModel = changes.FirstOrDefault(x => x.ChangedProperty == "type"); @@ -29,24 +32,15 @@ namespace Discord.Rest var type = typeModel.NewValue.ToObject(); var name = nameModel.NewValue.ToObject(); - foreach (var overwrite in overwritesModel.NewValue) - { - var deny = overwrite.Value("deny"); - var _type = overwrite.Value("type"); - var id = overwrite.Value("id"); - var allow = overwrite.Value("allow"); - - PermissionTarget permType = _type == "member" ? PermissionTarget.User : PermissionTarget.Role; - - overwrites.Add(new Overwrite(id, permType, new OverwritePermissions(allow, deny))); - } + var overwrites = (from overwrite in overwritesModel.NewValue + let deny = overwrite.Value("deny") + let _type = overwrite.Value("type") + let id = overwrite.Value("id") + let allow = overwrite.Value("allow") + let permType = _type == "member" ? PermissionTarget.User : PermissionTarget.Role + select new Overwrite(id, permType, new OverwritePermissions(allow, deny))).ToList(); return new ChannelCreateAuditLogData(entry.TargetId.Value, name, type, overwrites.ToReadOnlyCollection()); } - - public ulong ChannelId { get; } - public string ChannelName { get; } - public ChannelType ChannelType { get; } - public IReadOnlyCollection Overwrites { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs index 4816ce770..d6cd6902f 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelDeleteAuditLogData.cs @@ -1,9 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -11,7 +7,8 @@ namespace Discord.Rest { public class ChannelDeleteAuditLogData : IAuditLogData { - private ChannelDeleteAuditLogData(ulong id, string name, ChannelType type, IReadOnlyCollection overwrites) + private ChannelDeleteAuditLogData(ulong id, string name, ChannelType type, + IReadOnlyCollection overwrites) { ChannelId = id; ChannelName = name; @@ -19,6 +16,11 @@ namespace Discord.Rest Overwrites = overwrites; } + public ulong ChannelId { get; } + public string ChannelName { get; } + public ChannelType ChannelType { get; } + public IReadOnlyCollection Overwrites { get; } + internal static ChannelDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -36,10 +38,5 @@ namespace Discord.Rest return new ChannelDeleteAuditLogData(id, name, type, overwrites.ToReadOnlyCollection()); } - - public ulong ChannelId { get; } - public string ChannelName { get; } - public ChannelType ChannelType { get; } - public IReadOnlyCollection Overwrites { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs index 491cb5717..0bc3820dd 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/ChannelUpdateAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -14,6 +13,10 @@ namespace Discord.Rest After = after; } + public ulong ChannelId { get; } + public ChannelInfo Before { get; } + public ChannelInfo After { get; } + internal static ChannelUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -37,9 +40,5 @@ namespace Discord.Rest return new ChannelUpdateAuditLogData(entry.TargetId.Value, before, after); } - - public ulong ChannelId { get; } - public ChannelInfo Before { get; } - public ChannelInfo After { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs index 5d1ef8463..12a26de1e 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteCreateAuditLogData.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -17,6 +12,9 @@ namespace Discord.Rest Name = name; } + public ulong EmoteId { get; } + public string Name { get; } + internal static EmoteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); @@ -24,8 +22,5 @@ namespace Discord.Rest var emoteName = change.NewValue?.ToObject(); return new EmoteCreateAuditLogData(entry.TargetId.Value, emoteName); } - - public ulong EmoteId { get; } - public string Name { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs index d0a11191f..4e89ee3a2 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteDeleteAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -13,6 +12,9 @@ namespace Discord.Rest Name = name; } + public ulong EmoteId { get; } + public string Name { get; } + internal static EmoteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); @@ -21,8 +23,5 @@ namespace Discord.Rest return new EmoteDeleteAuditLogData(entry.TargetId.Value, emoteName); } - - public ulong EmoteId { get; } - public string Name { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs index 60020bcaa..c4f160ee1 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/EmoteUpdateAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -14,6 +13,10 @@ namespace Discord.Rest NewName = newName; } + public ulong EmoteId { get; } + public string NewName { get; } + public string OldName { get; } + internal static EmoteUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var change = entry.Changes.FirstOrDefault(x => x.ChangedProperty == "name"); @@ -23,9 +26,5 @@ namespace Discord.Rest return new EmoteUpdateAuditLogData(entry.TargetId.Value, oldName, newName); } - - public ulong EmoteId { get; } - public string NewName { get; } - public string OldName { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs index 08550ed7a..19b5028e0 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/GuildUpdateAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -13,6 +12,9 @@ namespace Discord.Rest After = after; } + public GuildInfo Before { get; } + public GuildInfo After { get; } + internal static GuildUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -30,8 +32,10 @@ namespace Discord.Rest int? oldAfkTimeout = afkTimeoutModel?.OldValue?.ToObject(), newAfkTimeout = afkTimeoutModel?.NewValue?.ToObject(); - DefaultMessageNotifications? oldDefaultMessageNotifications = defaultMessageNotificationsModel?.OldValue?.ToObject(), - newDefaultMessageNotifications = defaultMessageNotificationsModel?.NewValue?.ToObject(); + DefaultMessageNotifications? oldDefaultMessageNotifications = + defaultMessageNotificationsModel?.OldValue?.ToObject(), + newDefaultMessageNotifications = + defaultMessageNotificationsModel?.NewValue?.ToObject(); ulong? oldAfkChannelId = afkChannelModel?.OldValue?.ToObject(), newAfkChannelId = afkChannelModel?.NewValue?.ToObject(); string oldName = nameModel?.OldValue?.ToObject(), @@ -72,8 +76,5 @@ namespace Discord.Rest return new GuildUpdateAuditLogData(before, after); } - - public GuildInfo Before { get; } - public GuildInfo After { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs index 292715420..de2f5a8e7 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteCreateAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -7,7 +6,8 @@ namespace Discord.Rest { public class InviteCreateAuditLogData : IAuditLogData { - private InviteCreateAuditLogData(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, int uses, int maxUses) + private InviteCreateAuditLogData(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, + int uses, int maxUses) { MaxAge = maxAge; Code = code; @@ -18,6 +18,14 @@ namespace Discord.Rest MaxUses = maxUses; } + public int MaxAge { get; } + public string Code { get; } + public bool Temporary { get; } + public IUser Creator { get; } + public ulong ChannelId { get; } + public int Uses { get; } + public int MaxUses { get; } + internal static InviteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -43,13 +51,5 @@ namespace Discord.Rest return new InviteCreateAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses); } - - public int MaxAge { get; } - public string Code { get; } - public bool Temporary { get; } - public IUser Creator { get; } - public ulong ChannelId { get; } - public int Uses { get; } - public int MaxUses { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs index 1dc6d518b..a33167591 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteDeleteAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -7,7 +6,8 @@ namespace Discord.Rest { public class InviteDeleteAuditLogData : IAuditLogData { - private InviteDeleteAuditLogData(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, int uses, int maxUses) + private InviteDeleteAuditLogData(int maxAge, string code, bool temporary, IUser inviter, ulong channelId, + int uses, int maxUses) { MaxAge = maxAge; Code = code; @@ -18,6 +18,14 @@ namespace Discord.Rest MaxUses = maxUses; } + public int MaxAge { get; } + public string Code { get; } + public bool Temporary { get; } + public IUser Creator { get; } + public ulong ChannelId { get; } + public int Uses { get; } + public int MaxUses { get; } + internal static InviteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -43,13 +51,5 @@ namespace Discord.Rest return new InviteDeleteAuditLogData(maxAge, code, temporary, inviter, channelId, uses, maxUses); } - - public int MaxAge { get; } - public string Code { get; } - public bool Temporary { get; } - public IUser Creator { get; } - public ulong ChannelId { get; } - public int Uses { get; } - public int MaxUses { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs index b932cfbfc..e20a7ca21 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/InviteUpdateAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -13,6 +12,9 @@ namespace Discord.Rest After = after; } + public InviteInfo Before { get; } + public InviteInfo After { get; } + internal static InviteUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -39,8 +41,5 @@ namespace Discord.Rest return new InviteUpdateAuditLogData(before, after); } - - public InviteInfo Before { get; } - public InviteInfo After { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs index 41b5526b8..c17f88529 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/KickAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -12,12 +11,12 @@ namespace Discord.Rest Target = user; } + public IUser Target { get; } + internal static KickAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); return new KickAuditLogData(RestUser.Create(discord, userInfo)); } - - public IUser Target { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs index 3bcbce440..df432a1ea 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberRoleAuditLogData.cs @@ -1,7 +1,6 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; - +using Discord.API; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -15,12 +14,19 @@ namespace Discord.Rest Target = target; } + public IReadOnlyCollection Roles { get; } + public IUser Target { get; } + internal static MemberRoleAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; - var roleInfos = changes.SelectMany(x => x.NewValue.ToObject(), - (model, role) => new { model.ChangedProperty, Role = role }) + var roleInfos = changes.SelectMany(x => x.NewValue.ToObject(), + (model, role) => new + { + model.ChangedProperty, + Role = role + }) .Select(x => new MemberRoleEditInfo(x.Role.Name, x.Role.Id, x.ChangedProperty == "$add")) .ToList(); @@ -29,8 +35,5 @@ namespace Discord.Rest return new MemberRoleAuditLogData(roleInfos.ToReadOnlyCollection(), user); } - - public IReadOnlyCollection Roles { get; } - public IUser Target { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs index 40a3ba681..621872e5e 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MemberUpdateAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; using ChangeModel = Discord.API.AuditLogChange; @@ -15,6 +14,10 @@ namespace Discord.Rest After = after; } + public IUser Target { get; } + public MemberInfo Before { get; } + public MemberInfo After { get; } + internal static MemberUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -41,9 +44,5 @@ namespace Discord.Rest return new MemberUpdateAuditLogData(user, before, after); } - - public IUser Target { get; } - public MemberInfo Before { get; } - public MemberInfo After { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs index 3949cdd68..564aec9c9 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/MessageDeleteAuditLogData.cs @@ -11,12 +11,11 @@ namespace Discord.Rest MessageCount = count; } - internal static MessageDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) - { - return new MessageDeleteAuditLogData(entry.Options.MessageDeleteChannelId.Value, entry.Options.MessageDeleteCount.Value); - } - public int MessageCount { get; } public ulong ChannelId { get; } + + internal static MessageDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) => + new MessageDeleteAuditLogData(entry.Options.MessageDeleteChannelId.Value, + entry.Options.MessageDeleteCount.Value); } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs index d58488136..8c094e275 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteCreateAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -12,6 +11,8 @@ namespace Discord.Rest Overwrite = overwrite; } + public Overwrite Overwrite { get; } + internal static OverwriteCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -27,11 +28,9 @@ namespace Discord.Rest var id = entry.Options.OverwriteTargetId.Value; var type = entry.Options.OverwriteType; - PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; + var target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; return new OverwriteCreateAuditLogData(new Overwrite(id, target, permissions)); } - - public Overwrite Overwrite { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs index 445c2e302..4908ee5e1 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteDeleteAuditLogData.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; using ChangeModel = Discord.API.AuditLogChange; @@ -18,6 +13,8 @@ namespace Discord.Rest Overwrite = deletedOverwrite; } + public Overwrite Overwrite { get; } + internal static OverwriteDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -32,11 +29,9 @@ namespace Discord.Rest var id = idModel.OldValue.ToObject(); var allow = allowModel.OldValue.ToObject(); - PermissionTarget target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; + var target = type == "member" ? PermissionTarget.User : PermissionTarget.Role; return new OverwriteDeleteAuditLogData(new Overwrite(id, target, new OverwritePermissions(allow, deny))); } - - public Overwrite Overwrite { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs index d000146c3..c6921db16 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/OverwriteUpdateAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -7,7 +6,8 @@ namespace Discord.Rest { public class OverwriteUpdateAuditLogData : IAuditLogData { - private OverwriteUpdateAuditLogData(OverwritePermissions before, OverwritePermissions after, ulong targetId, PermissionTarget targetType) + private OverwriteUpdateAuditLogData(OverwritePermissions before, OverwritePermissions after, ulong targetId, + PermissionTarget targetType) { OldPermissions = before; NewPermissions = after; @@ -15,6 +15,12 @@ namespace Discord.Rest OverwriteType = targetType; } + public OverwritePermissions OldPermissions { get; } + public OverwritePermissions NewPermissions { get; } + + public ulong OverwriteTargetId { get; } + public PermissionTarget OverwriteType { get; } + internal static OverwriteUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -30,15 +36,10 @@ namespace Discord.Rest var beforePermissions = new OverwritePermissions(beforeAllow ?? 0, beforeDeny ?? 0); var afterPermissions = new OverwritePermissions(afterAllow ?? 0, afterDeny ?? 0); - PermissionTarget target = entry.Options.OverwriteType == "member" ? PermissionTarget.User : PermissionTarget.Role; + var target = entry.Options.OverwriteType == "member" ? PermissionTarget.User : PermissionTarget.Role; - return new OverwriteUpdateAuditLogData(beforePermissions, afterPermissions, entry.Options.OverwriteTargetId.Value, target); + return new OverwriteUpdateAuditLogData(beforePermissions, afterPermissions, + entry.Options.OverwriteTargetId.Value, target); } - - public OverwritePermissions OldPermissions { get; } - public OverwritePermissions NewPermissions { get; } - - public ulong OverwriteTargetId { get; } - public PermissionTarget OverwriteType { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs index 0005e304d..086a92dd5 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/PruneAuditLogData.cs @@ -11,12 +11,10 @@ namespace Discord.Rest MembersRemoved = membersRemoved; } - internal static PruneAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) - { - return new PruneAuditLogData(entry.Options.PruneDeleteMemberDays.Value, entry.Options.PruneMembersRemoved.Value); - } - public int PruneDays { get; } public int MembersRemoved { get; } + + internal static PruneAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) => + new PruneAuditLogData(entry.Options.PruneDeleteMemberDays.Value, entry.Options.PruneMembersRemoved.Value); } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs index dcc1c6ab6..82dc70cc8 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleCreateAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -13,6 +12,9 @@ namespace Discord.Rest Properties = props; } + public ulong RoleId { get; } + public RoleEditInfo Properties { get; } + internal static RoleCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -23,11 +25,11 @@ namespace Discord.Rest var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); - uint? colorRaw = colorModel?.NewValue?.ToObject(); - bool? mentionable = mentionableModel?.NewValue?.ToObject(); - bool? hoist = hoistModel?.NewValue?.ToObject(); - string name = nameModel?.NewValue?.ToObject(); - ulong? permissionsRaw = permissionsModel?.NewValue?.ToObject(); + var colorRaw = colorModel?.NewValue?.ToObject(); + var mentionable = mentionableModel?.NewValue?.ToObject(); + var hoist = hoistModel?.NewValue?.ToObject(); + var name = nameModel?.NewValue?.ToObject(); + var permissionsRaw = permissionsModel?.NewValue?.ToObject(); Color? color = null; GuildPermissions? permissions = null; @@ -40,8 +42,5 @@ namespace Discord.Rest return new RoleCreateAuditLogData(entry.TargetId.Value, new RoleEditInfo(color, mentionable, hoist, name, permissions)); } - - public ulong RoleId { get; } - public RoleEditInfo Properties { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs index 263909daf..6385a7887 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleDeleteAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -13,6 +12,9 @@ namespace Discord.Rest Properties = props; } + public ulong RoleId { get; } + public RoleEditInfo Properties { get; } + internal static RoleDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -23,11 +25,11 @@ namespace Discord.Rest var nameModel = changes.FirstOrDefault(x => x.ChangedProperty == "name"); var permissionsModel = changes.FirstOrDefault(x => x.ChangedProperty == "permissions"); - uint? colorRaw = colorModel?.OldValue?.ToObject(); - bool? mentionable = mentionableModel?.OldValue?.ToObject(); - bool? hoist = hoistModel?.OldValue?.ToObject(); - string name = nameModel?.OldValue?.ToObject(); - ulong? permissionsRaw = permissionsModel?.OldValue?.ToObject(); + var colorRaw = colorModel?.OldValue?.ToObject(); + var mentionable = mentionableModel?.OldValue?.ToObject(); + var hoist = hoistModel?.OldValue?.ToObject(); + var name = nameModel?.OldValue?.ToObject(); + var permissionsRaw = permissionsModel?.OldValue?.ToObject(); Color? color = null; GuildPermissions? permissions = null; @@ -40,8 +42,5 @@ namespace Discord.Rest return new RoleDeleteAuditLogData(entry.TargetId.Value, new RoleEditInfo(color, mentionable, hoist, name, permissions)); } - - public ulong RoleId { get; } - public RoleEditInfo Properties { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs index b645ef7ae..2c0de7baa 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/RoleUpdateAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -14,6 +13,10 @@ namespace Discord.Rest After = newProps; } + public ulong RoleId { get; } + public RoleEditInfo Before { get; } + public RoleEditInfo After { get; } + internal static RoleUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -54,9 +57,5 @@ namespace Discord.Rest return new RoleUpdateAuditLogData(entry.TargetId.Value, oldProps, newProps); } - - public ulong RoleId { get; } - public RoleEditInfo Before { get; } - public RoleEditInfo After { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs index c94f18271..b04b762ba 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/UnbanAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -12,12 +11,12 @@ namespace Discord.Rest Target = user; } + public IUser Target { get; } + internal static UnbanAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var userInfo = log.Users.FirstOrDefault(x => x.Id == entry.TargetId); return new UnbanAuditLogData(RestUser.Create(discord, userInfo)); } - - public IUser Target { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs index 1ae45fb8c..661fa8e41 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookCreateAuditLogData.cs @@ -1,5 +1,4 @@ using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -15,6 +14,14 @@ namespace Discord.Rest ChannelId = channelId; } + //Corresponds to the *current* data + public IWebhook Webhook { get; } + + //Corresponds to the *audit log* data + public WebhookType Type { get; } + public string Name { get; } + public ulong ChannelId { get; } + internal static WebhookCreateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -32,13 +39,5 @@ namespace Discord.Rest return new WebhookCreateAuditLogData(webhook, type, name, channelId); } - - //Corresponds to the *current* data - public IWebhook Webhook { get; } - - //Corresponds to the *audit log* data - public WebhookType Type { get; } - public string Name { get; } - public ulong ChannelId { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs index 4133d5dff..5f8f49ab0 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookDeleteAuditLogData.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -20,6 +15,12 @@ namespace Discord.Rest Avatar = avatar; } + public ulong WebhookId { get; } + public ulong ChannelId { get; } + public WebhookType Type { get; } + public string Name { get; } + public string Avatar { get; } + internal static WebhookDeleteAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -36,11 +37,5 @@ namespace Discord.Rest return new WebhookDeleteAuditLogData(entry.TargetId.Value, channelId, type, name, avatarHash); } - - public ulong WebhookId { get; } - public ulong ChannelId { get; } - public WebhookType Type { get; } - public string Name { get; } - public string Avatar { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs index 54da42a8b..b7a87526e 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/DataTypes/WebhookUpdateAuditLogData.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - +using System.Linq; using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -18,6 +13,13 @@ namespace Discord.Rest After = after; } + //Again, the *current* data + public IWebhook Webhook { get; } + + //And the *audit log* data + public WebhookInfo Before { get; } + public WebhookInfo After { get; } + internal static WebhookUpdateAuditLogData Create(BaseDiscordClient discord, Model log, EntryModel entry) { var changes = entry.Changes; @@ -41,12 +43,5 @@ namespace Discord.Rest return new WebhookUpdateAuditLogData(webhook, before, after); } - - //Again, the *current* data - public IWebhook Webhook { get; } - - //And the *audit log* data - public WebhookInfo Before { get; } - public WebhookInfo After { get; } } } diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs index d01e964ae..8534431c5 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/RestAuditLogEntry.cs @@ -1,6 +1,5 @@ using System; using System.Linq; - using Model = Discord.API.AuditLog; using EntryModel = Discord.API.AuditLogEntry; @@ -17,6 +16,21 @@ namespace Discord.Rest Reason = model.Reason; } + /// + public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + + /// + public ActionType Action { get; } + + /// + public IAuditLogData Data { get; } + + /// + public IUser User { get; } + + /// + public string Reason { get; } + internal static RestAuditLogEntry Create(BaseDiscordClient discord, Model fullLog, EntryModel model) { var userInfo = fullLog.Users.FirstOrDefault(x => x.Id == model.UserId); @@ -26,16 +40,5 @@ namespace Discord.Rest return new RestAuditLogEntry(discord, fullLog, model, user); } - - /// - public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - /// - public ActionType Action { get; } - /// - public IAuditLogData Data { get; } - /// - public IUser User { get; } - /// - public string Reason { get; } } } diff --git a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs index 50cb352a7..3e461eac0 100644 --- a/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs +++ b/src/Discord.Net.Rest/Entities/Channels/ChannelHelper.cs @@ -1,10 +1,10 @@ -using Discord.API.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Threading.Tasks; +using Discord.API.Rest; using Model = Discord.API.Channel; using UserModel = Discord.API.User; using WebhookModel = Discord.API.Webhook; @@ -15,17 +15,16 @@ namespace Discord.Rest { //General public static async Task DeleteAsync(IChannel channel, BaseDiscordClient client, - RequestOptions options) - { + RequestOptions options) => await client.ApiClient.DeleteChannelAsync(channel.Id, options).ConfigureAwait(false); - } + public static async Task ModifyAsync(IGuildChannel channel, BaseDiscordClient client, Action func, RequestOptions options) { var args = new GuildChannelProperties(); func(args); - var apiArgs = new API.Rest.ModifyGuildChannelParams + var apiArgs = new ModifyGuildChannelParams { Name = args.Name, Position = args.Position, @@ -33,13 +32,14 @@ namespace Discord.Rest }; return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } + public static async Task ModifyAsync(ITextChannel channel, BaseDiscordClient client, Action func, RequestOptions options) { var args = new TextChannelProperties(); func(args); - var apiArgs = new API.Rest.ModifyTextChannelParams + var apiArgs = new ModifyTextChannelParams { Name = args.Name, Position = args.Position, @@ -49,43 +49,45 @@ namespace Discord.Rest }; return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } + public static async Task ModifyAsync(IVoiceChannel channel, BaseDiscordClient client, Action func, RequestOptions options) { var args = new VoiceChannelProperties(); func(args); - var apiArgs = new API.Rest.ModifyVoiceChannelParams + var apiArgs = new ModifyVoiceChannelParams { Bitrate = args.Bitrate, Name = args.Name, Position = args.Position, CategoryId = args.CategoryId, - UserLimit = args.UserLimit.IsSpecified ? (args.UserLimit.Value ?? 0) : Optional.Create() + UserLimit = args.UserLimit.IsSpecified ? args.UserLimit.Value ?? 0 : Optional.Create() }; return await client.ApiClient.ModifyGuildChannelAsync(channel.Id, apiArgs, options).ConfigureAwait(false); } //Invites - public static async Task> GetInvitesAsync(IGuildChannel channel, BaseDiscordClient client, + public static async Task> GetInvitesAsync(IGuildChannel channel, + BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetChannelInvitesAsync(channel.Id, options).ConfigureAwait(false); return models.Select(x => RestInviteMetadata.Create(client, null, channel, x)).ToImmutableArray(); } + public static async Task CreateInviteAsync(IGuildChannel channel, BaseDiscordClient client, int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) { - var args = new CreateChannelInviteParams { IsTemporary = isTemporary, IsUnique = isUnique }; - if (maxAge.HasValue) - args.MaxAge = maxAge.Value; - else - args.MaxAge = 0; - if (maxUses.HasValue) - args.MaxUses = maxUses.Value; - else - args.MaxUses = 0; - var model = await client.ApiClient.CreateChannelInviteAsync(channel.Id, args, options).ConfigureAwait(false); + var args = new CreateChannelInviteParams + { + IsTemporary = isTemporary, + IsUnique = isUnique + }; + args.MaxAge = maxAge ?? 0; + args.MaxUses = maxUses ?? 0; + var model = await client.ApiClient.CreateChannelInviteAsync(channel.Id, args, options) + .ConfigureAwait(false); return RestInviteMetadata.Create(client, null, channel, model); } @@ -94,21 +96,28 @@ namespace Discord.Rest ulong id, RequestOptions options) { var guildId = (channel as IGuildChannel)?.GuildId; - var guild = guildId != null ? await (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).ConfigureAwait(false) : null; + var guild = guildId != null + ? await (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly) + .ConfigureAwait(false) + : null; var model = await client.ApiClient.GetChannelMessageAsync(channel.Id, id, options).ConfigureAwait(false); if (model == null) return null; var author = GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable()); return RestMessage.Create(client, channel, author, model); } - public static IAsyncEnumerable> GetMessagesAsync(IMessageChannel channel, BaseDiscordClient client, + + public static IAsyncEnumerable> GetMessagesAsync(IMessageChannel channel, + BaseDiscordClient client, ulong? fromMessageId, Direction dir, int limit, RequestOptions options) { if (dir == Direction.Around) throw new NotImplementedException(); //TODO: Impl var guildId = (channel as IGuildChannel)?.GuildId; - var guild = guildId != null ? (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result : null; + var guild = guildId != null + ? (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result + : null; return new PagedAsyncEnumerable( DiscordConfig.MaxMessagesPerBatch, @@ -122,34 +131,38 @@ namespace Discord.Rest if (info.Position != null) args.RelativeMessageId = info.Position.Value; - var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args, options).ConfigureAwait(false); + var models = await client.ApiClient.GetChannelMessagesAsync(channel.Id, args, options) + .ConfigureAwait(false); var builder = ImmutableArray.CreateBuilder(); foreach (var model in models) { var author = GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable()); builder.Add(RestMessage.Create(client, channel, author, model)); } + return builder.ToImmutable(); }, - nextPage: (info, lastPage) => + (info, lastPage) => { if (lastPage.Count != DiscordConfig.MaxMessagesPerBatch) return false; - if (dir == Direction.Before) - info.Position = lastPage.Min(x => x.Id); - else - info.Position = lastPage.Max(x => x.Id); + info.Position = dir == Direction.Before ? lastPage.Min(x => x.Id) : lastPage.Max(x => x.Id); return true; }, - start: fromMessageId, - count: limit + fromMessageId, + limit ); } - public static async Task> GetPinnedMessagesAsync(IMessageChannel channel, BaseDiscordClient client, + + public static async Task> GetPinnedMessagesAsync(IMessageChannel channel, + BaseDiscordClient client, RequestOptions options) { var guildId = (channel as IGuildChannel)?.GuildId; - var guild = guildId != null ? await (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).ConfigureAwait(false) : null; + var guild = guildId != null + ? await (client as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly) + .ConfigureAwait(false) + : null; var models = await client.ApiClient.GetPinsAsync(channel.Id, options).ConfigureAwait(false); var builder = ImmutableArray.CreateBuilder(); foreach (var model in models) @@ -157,13 +170,18 @@ namespace Discord.Rest var author = GetAuthor(client, guild, model.Author.Value, model.WebhookId.ToNullable()); builder.Add(RestMessage.Create(client, channel, author, model)); } + return builder.ToImmutable(); } public static async Task SendMessageAsync(IMessageChannel channel, BaseDiscordClient client, string text, bool isTTS, Embed embed, RequestOptions options) { - var args = new CreateMessageParams(text) { IsTTS = isTTS, Embed = embed?.ToModel() }; + var args = new CreateMessageParams(text) + { + IsTTS = isTTS, + Embed = embed?.ToModel() + }; var model = await client.ApiClient.CreateMessageAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, channel, client.CurrentUser, model); } @@ -171,15 +189,22 @@ namespace Discord.Rest public static async Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, string filePath, string text, bool isTTS, Embed embed, RequestOptions options) { - string filename = Path.GetFileName(filePath); + var filename = Path.GetFileName(filePath); using (var file = File.OpenRead(filePath)) - return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, options).ConfigureAwait(false); + return await SendFileAsync(channel, client, file, filename, text, isTTS, embed, options) + .ConfigureAwait(false); } public static async Task SendFileAsync(IMessageChannel channel, BaseDiscordClient client, Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) { - var args = new UploadFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS, Embed = embed != null ? embed.ToModel() : Optional.Unspecified }; + var args = new UploadFileParams(stream) + { + Filename = filename, + Content = text, + IsTTS = isTTS, + Embed = embed != null ? embed.ToModel() : Optional.Unspecified + }; var model = await client.ApiClient.UploadFileAsync(channel.Id, args, options).ConfigureAwait(false); return RestUserMessage.Create(client, channel, client.CurrentUser, model); } @@ -194,22 +219,18 @@ namespace Discord.Rest const int BATCH_SIZE = 100; var msgs = messageIds.ToArray(); - int batches = msgs.Length / BATCH_SIZE; - for (int i = 0; i <= batches; i++) + var batches = msgs.Length / BATCH_SIZE; + for (var i = 0; i <= batches; i++) { ArraySegment batch; if (i < batches) - { batch = new ArraySegment(msgs, i * BATCH_SIZE, BATCH_SIZE); - } else { batch = new ArraySegment(msgs, i * BATCH_SIZE, msgs.Length - batches * BATCH_SIZE); - if (batch.Count == 0) - { - break; - } + if (batch.Count == 0) break; } + var args = new DeleteMessagesParams(batch.ToArray()); await client.ApiClient.DeleteMessagesAsync(channel.Id, args, options).ConfigureAwait(false); } @@ -220,111 +241,113 @@ namespace Discord.Rest IUser user, OverwritePermissions perms, RequestOptions options) { var args = new ModifyChannelPermissionsParams("member", perms.AllowValue, perms.DenyValue); - await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args, options).ConfigureAwait(false); + await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, user.Id, args, options) + .ConfigureAwait(false); } + public static async Task AddPermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, IRole role, OverwritePermissions perms, RequestOptions options) { var args = new ModifyChannelPermissionsParams("role", perms.AllowValue, perms.DenyValue); - await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args, options).ConfigureAwait(false); + await client.ApiClient.ModifyChannelPermissionsAsync(channel.Id, role.Id, args, options) + .ConfigureAwait(false); } + public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, - IUser user, RequestOptions options) - { - await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, user.Id, options).ConfigureAwait(false); - } + IUser user, RequestOptions options) => await client.ApiClient + .DeleteChannelPermissionAsync(channel.Id, user.Id, options).ConfigureAwait(false); + public static async Task RemovePermissionOverwriteAsync(IGuildChannel channel, BaseDiscordClient client, - IRole role, RequestOptions options) - { - await client.ApiClient.DeleteChannelPermissionAsync(channel.Id, role.Id, options).ConfigureAwait(false); - } + IRole role, RequestOptions options) => await client.ApiClient + .DeleteChannelPermissionAsync(channel.Id, role.Id, options).ConfigureAwait(false); //Users - public static async Task GetUserAsync(IGuildChannel channel, IGuild guild, BaseDiscordClient client, + public static async Task GetUserAsync(IGuildChannel channel, IGuild guild, + BaseDiscordClient client, ulong id, RequestOptions options) { var model = await client.ApiClient.GetGuildMemberAsync(channel.GuildId, id, options).ConfigureAwait(false); if (model == null) return null; var user = RestGuildUser.Create(client, guild, model); - if (!user.GetPermissions(channel).ViewChannel) - return null; - - return user; + return !user.GetPermissions(channel).ViewChannel ? null : user; } - public static IAsyncEnumerable> GetUsersAsync(IGuildChannel channel, IGuild guild, BaseDiscordClient client, - ulong? fromUserId, int? limit, RequestOptions options) - { - return new PagedAsyncEnumerable( - DiscordConfig.MaxUsersPerBatch, - async (info, ct) => - { - var args = new GetGuildMembersParams - { - Limit = info.PageSize - }; - if (info.Position != null) - args.AfterUserId = info.Position.Value; - var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args, options).ConfigureAwait(false); - return models - .Select(x => RestGuildUser.Create(client, guild, x)) - .Where(x => x.GetPermissions(channel).ViewChannel) - .ToImmutableArray(); - }, - nextPage: (info, lastPage) => + + public static IAsyncEnumerable> GetUsersAsync(IGuildChannel channel, + IGuild guild, BaseDiscordClient client, + ulong? fromUserId, int? limit, RequestOptions options) => new PagedAsyncEnumerable( + DiscordConfig.MaxUsersPerBatch, + async (info, ct) => + { + var args = new GetGuildMembersParams { - if (lastPage.Count != DiscordConfig.MaxMessagesPerBatch) - return false; - info.Position = lastPage.Max(x => x.Id); - return true; - }, - start: fromUserId, - count: limit - ); - } + Limit = info.PageSize + }; + if (info.Position != null) + args.AfterUserId = info.Position.Value; + var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args, options).ConfigureAwait(false); + return models + .Select(x => RestGuildUser.Create(client, guild, x)) + .Where(x => x.GetPermissions(channel).ViewChannel) + .ToImmutableArray(); + }, + (info, lastPage) => + { + if (lastPage.Count != DiscordConfig.MaxMessagesPerBatch) + return false; + info.Position = lastPage.Max(x => x.Id); + return true; + }, + fromUserId, + limit + ); //Typing public static async Task TriggerTypingAsync(IMessageChannel channel, BaseDiscordClient client, - RequestOptions options = null) - { - await client.ApiClient.TriggerTypingIndicatorAsync(channel.Id, options).ConfigureAwait(false); - } + RequestOptions options = null) => await client.ApiClient.TriggerTypingIndicatorAsync(channel.Id, options) + .ConfigureAwait(false); + public static IDisposable EnterTypingState(IMessageChannel channel, BaseDiscordClient client, RequestOptions options) => new TypingNotifier(client, channel, options); //Webhooks - public static async Task CreateWebhookAsync(ITextChannel channel, BaseDiscordClient client, string name, Stream avatar, RequestOptions options) + public static async Task CreateWebhookAsync(ITextChannel channel, BaseDiscordClient client, + string name, Stream avatar, RequestOptions options) { - var args = new CreateWebhookParams { Name = name }; + var args = new CreateWebhookParams {Name = name}; if (avatar != null) args.Avatar = new API.Image(avatar); var model = await client.ApiClient.CreateWebhookAsync(channel.Id, args, options).ConfigureAwait(false); return RestWebhook.Create(client, channel, model); } - public static async Task GetWebhookAsync(ITextChannel channel, BaseDiscordClient client, ulong id, RequestOptions options) + + public static async Task GetWebhookAsync(ITextChannel channel, BaseDiscordClient client, ulong id, + RequestOptions options) { - var model = await client.ApiClient.GetWebhookAsync(id, options: options).ConfigureAwait(false); - if (model == null) - return null; - return RestWebhook.Create(client, channel, model); + var model = await client.ApiClient.GetWebhookAsync(id, options).ConfigureAwait(false); + return model == null ? null : RestWebhook.Create(client, channel, model); } - public static async Task> GetWebhooksAsync(ITextChannel channel, BaseDiscordClient client, RequestOptions options) + + public static async Task> GetWebhooksAsync(ITextChannel channel, + BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetChannelWebhooksAsync(channel.Id, options).ConfigureAwait(false); return models.Select(x => RestWebhook.Create(client, channel, x)) .ToImmutableArray(); } + // Categories - public static async Task GetCategoryAsync(INestedChannel channel, BaseDiscordClient client, RequestOptions options) + public static async Task GetCategoryAsync(INestedChannel channel, BaseDiscordClient client, + RequestOptions options) { // if no category id specified, return null if (!channel.CategoryId.HasValue) return null; // CategoryId will contain a value here var model = await client.ApiClient.GetChannelAsync(channel.CategoryId.Value, options).ConfigureAwait(false); - return RestCategoryChannel.Create(client, model) as ICategoryChannel; + return RestChannel.Create(client, model) as ICategoryChannel; } //Helpers diff --git a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs index e0095c7b1..f71065e88 100644 --- a/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/IRestMessageChannel.cs @@ -7,21 +7,32 @@ namespace Discord.Rest public interface IRestMessageChannel : IMessageChannel { /// Sends a message to this message channel. - new Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); + new Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, + RequestOptions options = null); + /// Sends a file to this text channel, with an optional caption. - new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); + new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, + Embed embed = null, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. - new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); + new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, + Embed embed = null, RequestOptions options = null); /// Gets a message from this message channel with the given id, or null if not found. Task GetMessageAsync(ulong id, RequestOptions options = null); + /// Gets the last N messages from this message channel. - IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null); + IAsyncEnumerable> GetMessagesAsync( + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null); + /// Gets a collection of messages in this channel. - IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null); + IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null); + /// Gets a collection of messages in this channel. - IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null); + IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null); + /// Gets a collection of pinned messages in this channel. new Task> GetPinnedMessagesAsync(RequestOptions options = null); } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs index 321f1f1d2..3c38b4cec 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestCategoryChannel.cs @@ -1,39 +1,41 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; -using System.Linq; using System.Threading.Tasks; using Model = Discord.API.Channel; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestCategoryChannel : RestGuildChannel, ICategoryChannel { internal RestCategoryChannel(BaseDiscordClient discord, IGuild guild, ulong id) : base(discord, guild, id) { } - internal new static RestCategoryChannel Create(BaseDiscordClient discord, IGuild guild, Model model) - { - var entity = new RestCategoryChannel(discord, guild, model.Id); - entity.Update(model); - return entity; - } private string DebuggerDisplay => $"{Name} ({Id}, Category)"; // IGuildChannel - Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) + Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, + bool isUnique, RequestOptions options) => throw new NotSupportedException(); + Task> IGuildChannel.GetInvitesAsync(RequestOptions options) => throw new NotSupportedException(); //IChannel IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => throw new NotSupportedException(); + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => throw new NotSupportedException(); + + internal new static RestCategoryChannel Create(BaseDiscordClient discord, IGuild guild, Model model) + { + var entity = new RestCategoryChannel(discord, guild, model.Id); + entity.Update(model); + return entity; + } } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs index 5860d8283..0a802cce3 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestChannel.cs @@ -8,12 +8,24 @@ namespace Discord.Rest { public class RestChannel : RestEntity, IChannel, IUpdateable { - public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - internal RestChannel(BaseDiscordClient discord, ulong id) : base(discord, id) { } + + public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + + //IChannel + string IChannel.Name => null; + + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(null); //Overridden + + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + => AsyncEnumerable.Empty>(); //Overridden + + public virtual Task UpdateAsync(RequestOptions options = null) => Task.Delay(0); + internal static RestChannel Create(BaseDiscordClient discord, Model model) { switch (model.Type) @@ -30,6 +42,7 @@ namespace Discord.Rest return new RestChannel(discord, model.Id); } } + internal static IRestPrivateChannel CreatePrivate(BaseDiscordClient discord, Model model) { switch (model.Type) @@ -42,16 +55,9 @@ namespace Discord.Rest throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); } } - internal virtual void Update(Model model) { } - - public virtual Task UpdateAsync(RequestOptions options = null) => Task.Delay(0); - - //IChannel - string IChannel.Name => null; - Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(null); //Overridden - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => AsyncEnumerable.Empty>(); //Overridden + internal virtual void Update(Model model) + { + } } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index 64efcf24b..d5ea56038 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -9,88 +9,37 @@ using Model = Discord.API.Channel; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestDMChannel : RestChannel, IDMChannel, IRestPrivateChannel, IRestMessageChannel, IUpdateable { - public RestUser CurrentUser { get; private set; } - public RestUser Recipient { get; private set; } - - public IReadOnlyCollection Users => ImmutableArray.Create(CurrentUser, Recipient); - internal RestDMChannel(BaseDiscordClient discord, ulong id, ulong recipientId) : base(discord, id) { Recipient = new RestUser(Discord, recipientId); CurrentUser = new RestUser(Discord, discord.CurrentUser.Id); } - internal new static RestDMChannel Create(BaseDiscordClient discord, Model model) - { - var entity = new RestDMChannel(discord, model.Id, model.Recipients.Value[0].Id); - entity.Update(model); - return entity; - } - internal override void Update(Model model) - { - Recipient.Update(model.Recipients.Value[0]); - } - public override async Task UpdateAsync(RequestOptions options = null) - { - var model = await Discord.ApiClient.GetChannelAsync(Id, options).ConfigureAwait(false); - Update(model); - } - public Task CloseAsync(RequestOptions options = null) - => ChannelHelper.DeleteAsync(this, Discord, options); + public RestUser CurrentUser { get; } + public RestUser Recipient { get; } - public RestUser GetUser(ulong id) - { - if (id == Recipient.Id) - return Recipient; - else if (id == Discord.CurrentUser.Id) - return CurrentUser; - else - return null; - } - - public Task GetMessageAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetMessageAsync(this, Discord, id, options); - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); - public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); - - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); + public IReadOnlyCollection Users => ImmutableArray.Create(CurrentUser, Recipient); + private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + public Task CloseAsync(RequestOptions options = null) + => ChannelHelper.DeleteAsync(this, Discord, options); public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); - public IDisposable EnterTypingState(RequestOptions options = null) - => ChannelHelper.EnterTypingState(this, Discord, options); - public override string ToString() => $"@{Recipient}"; - private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; - - //IDMChannel + //IDMChannel IUser IDMChannel.Recipient => Recipient; - //IRestPrivateChannel - IReadOnlyCollection IRestPrivateChannel.Recipients => ImmutableArray.Create(Recipient); - //IPrivateChannel IReadOnlyCollection IPrivateChannel.Recipients => ImmutableArray.Create(Recipient); @@ -99,40 +48,42 @@ namespace Discord.Rest { if (mode == CacheMode.AllowDownload) return await GetMessageAsync(id, options).ConfigureAwait(false); - else - return null; + return null; } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, + RequestOptions options) { - if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(limit, options); - else - return AsyncEnumerable.Empty>(); + return mode == CacheMode.AllowDownload ? GetMessagesAsync(limit, options) : AsyncEnumerable.Empty>(); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, + Direction dir, int limit, CacheMode mode, RequestOptions options) { - if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(fromMessageId, dir, limit, options); - else - return AsyncEnumerable.Empty>(); + return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessageId, dir, limit, options) : AsyncEnumerable.Empty>(); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, + Direction dir, int limit, CacheMode mode, RequestOptions options) { - if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(fromMessage, dir, limit, options); - else - return AsyncEnumerable.Empty>(); + return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessage, dir, limit, options) : AsyncEnumerable.Empty>(); } + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, + RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, + Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) + + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, + RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); @@ -141,7 +92,68 @@ namespace Discord.Rest Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + + public Task GetMessageAsync(ulong id, RequestOptions options = null) + => ChannelHelper.GetMessageAsync(this, Discord, id, options); + + public IAsyncEnumerable> GetMessagesAsync( + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); + + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); + + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); + + public Task> GetPinnedMessagesAsync(RequestOptions options = null) + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, + RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); + + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, + RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); + + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, + Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + + //IRestPrivateChannel + IReadOnlyCollection IRestPrivateChannel.Recipients => ImmutableArray.Create(Recipient); + + public override async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetChannelAsync(Id, options).ConfigureAwait(false); + Update(model); + } + + internal new static RestDMChannel Create(BaseDiscordClient discord, Model model) + { + var entity = new RestDMChannel(discord, model.Id, model.Recipients.Value[0].Id); + entity.Update(model); + return entity; + } + + internal override void Update(Model model) => Recipient.Update(model.Recipients.Value[0]); + + public RestUser GetUser(ulong id) + { + if (id == Recipient.Id) + return Recipient; + return id == Discord.CurrentUser.Id ? CurrentUser : null; + } + + public IDisposable EnterTypingState(RequestOptions options = null) + => ChannelHelper.EnterTypingState(this, Discord, options); + + public override string ToString() => $"@{Recipient}"; } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 5ac0f2c40..4be8d2c75 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -1,4 +1,3 @@ -using Discord.Audio; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -6,100 +5,45 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; +using Discord.API; +using Discord.Audio; using Model = Discord.API.Channel; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class RestGroupChannel : RestChannel, IGroupChannel, IRestPrivateChannel, IRestMessageChannel, IRestAudioChannel, IUpdateable + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] + public class RestGroupChannel : RestChannel, IGroupChannel, IRestPrivateChannel, IRestMessageChannel, + IRestAudioChannel, IUpdateable { private string _iconId; private ImmutableDictionary _users; - public string Name { get; private set; } - - public IReadOnlyCollection Users => _users.ToReadOnlyCollection(); - public IReadOnlyCollection Recipients - => _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id).ToReadOnlyCollection(() => _users.Count - 1); - internal RestGroupChannel(BaseDiscordClient discord, ulong id) : base(discord, id) { } - internal new static RestGroupChannel Create(BaseDiscordClient discord, Model model) - { - var entity = new RestGroupChannel(discord, model.Id); - entity.Update(model); - return entity; - } - internal override void Update(Model model) - { - if (model.Name.IsSpecified) - Name = model.Name.Value; - if (model.Icon.IsSpecified) - _iconId = model.Icon.Value; - if (model.Recipients.IsSpecified) - UpdateUsers(model.Recipients.Value); - } - internal void UpdateUsers(API.User[] models) - { - var users = ImmutableDictionary.CreateBuilder(); - for (int i = 0; i < models.Length; i++) - users[models[i].Id] = RestGroupUser.Create(Discord, models[i]); - _users = users.ToImmutable(); - } + public IReadOnlyCollection Users => _users.ToReadOnlyCollection(); - public override async Task UpdateAsync(RequestOptions options = null) - { - var model = await Discord.ApiClient.GetChannelAsync(Id, options).ConfigureAwait(false); - Update(model); - } - public Task LeaveAsync(RequestOptions options = null) - => ChannelHelper.DeleteAsync(this, Discord, options); + public IReadOnlyCollection Recipients + => _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id) + .ToReadOnlyCollection(() => _users.Count - 1); - public RestUser GetUser(ulong id) - { - if (_users.TryGetValue(id, out RestGroupUser user)) - return user; - return null; - } + private string DebuggerDisplay => $"{Name} ({Id}, Group)"; - public Task GetMessageAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetMessageAsync(this, Discord, id, options); - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); - public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + public string Name { get; private set; } + + public Task LeaveAsync(RequestOptions options = null) + => ChannelHelper.DeleteAsync(this, Discord, options); public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); - - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); - - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); - public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); - public IDisposable EnterTypingState(RequestOptions options = null) - => ChannelHelper.EnterTypingState(this, Discord, options); - - public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id}, Group)"; - - //ISocketPrivateChannel - IReadOnlyCollection IRestPrivateChannel.Recipients => Recipients; //IPrivateChannel IReadOnlyCollection IPrivateChannel.Recipients => Recipients; @@ -109,51 +53,131 @@ namespace Discord.Rest { if (mode == CacheMode.AllowDownload) return await GetMessageAsync(id, options).ConfigureAwait(false); - else - return null; + return null; } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, + RequestOptions options) { - if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(limit, options); - else - return AsyncEnumerable.Empty>(); + return mode == CacheMode.AllowDownload ? GetMessagesAsync(limit, options) : AsyncEnumerable.Empty>(); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, + Direction dir, int limit, CacheMode mode, RequestOptions options) { - if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(fromMessageId, dir, limit, options); - else - return AsyncEnumerable.Empty>(); + return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessageId, dir, limit, options) : AsyncEnumerable.Empty>(); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, + Direction dir, int limit, CacheMode mode, RequestOptions options) { - if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(fromMessage, dir, limit, options); - else - return AsyncEnumerable.Empty>(); + return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessage, dir, limit, options) : AsyncEnumerable.Empty>(); } + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, + RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, + Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) + + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, + RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); //IAudioChannel - Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } - Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } + Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) => + throw new NotSupportedException(); - //IChannel + Task IAudioChannel.DisconnectAsync() => throw new NotSupportedException(); + + //IChannel Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + + public Task GetMessageAsync(ulong id, RequestOptions options = null) + => ChannelHelper.GetMessageAsync(this, Discord, id, options); + + public IAsyncEnumerable> GetMessagesAsync( + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); + + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); + + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); + + public Task> GetPinnedMessagesAsync(RequestOptions options = null) + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, + RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); + + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, + RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); + + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, + Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + + //ISocketPrivateChannel + IReadOnlyCollection IRestPrivateChannel.Recipients => Recipients; + + public override async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetChannelAsync(Id, options).ConfigureAwait(false); + Update(model); + } + + internal new static RestGroupChannel Create(BaseDiscordClient discord, Model model) + { + var entity = new RestGroupChannel(discord, model.Id); + entity.Update(model); + return entity; + } + + internal override void Update(Model model) + { + if (model.Name.IsSpecified) + Name = model.Name.Value; + if (model.Icon.IsSpecified) + _iconId = model.Icon.Value; + + if (model.Recipients.IsSpecified) + UpdateUsers(model.Recipients.Value); + } + + internal void UpdateUsers(User[] models) + { + var users = ImmutableDictionary.CreateBuilder(); + for (var i = 0; i < models.Length; i++) + users[models[i].Id] = RestGroupUser.Create(Discord, models[i]); + _users = users.ToImmutable(); + } + + public RestUser GetUser(ulong id) + { + return _users.TryGetValue(id, out var user) ? user : null; + } + + public IDisposable EnterTypingState(RequestOptions options = null) + => ChannelHelper.EnterTypingState(this, Discord, options); + + public override string ToString() => Name; } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs index 7355e3673..80d328576 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGuildChannel.cs @@ -11,18 +11,87 @@ namespace Discord.Rest { private ImmutableArray _overwrites; - public IReadOnlyCollection PermissionOverwrites => _overwrites; + internal RestGuildChannel(BaseDiscordClient discord, IGuild guild, ulong id) + : base(discord, id) + { + Guild = guild; + } internal IGuild Guild { get; } + + public IReadOnlyCollection PermissionOverwrites => _overwrites; public string Name { get; private set; } public int Position { get; private set; } public ulong GuildId => Guild.Id; - internal RestGuildChannel(BaseDiscordClient discord, IGuild guild, ulong id) - : base(discord, id) + public async Task ModifyAsync(Action func, RequestOptions options = null) { - Guild = guild; + var model = await ChannelHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); + Update(model); + } + + public Task DeleteAsync(RequestOptions options = null) + => ChannelHelper.DeleteAsync(this, Discord, options); + + //IGuildChannel + IGuild IGuildChannel.Guild + { + get + { + if (Guild != null) + return Guild; + throw new InvalidOperationException( + "Unable to return this entity's parent unless it was fetched through that object."); + } + } + + async Task> IGuildChannel.GetInvitesAsync(RequestOptions options) + => await GetInvitesAsync(options).ConfigureAwait(false); + + async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, + bool isUnique, RequestOptions options) + => await CreateInviteAsync(maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); + + OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) + => GetPermissionOverwrite(role); + + OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) + => GetPermissionOverwrite(user); + + async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, + RequestOptions options) + => await AddPermissionOverwriteAsync(role, permissions, options).ConfigureAwait(false); + + async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, + RequestOptions options) + => await AddPermissionOverwriteAsync(user, permissions, options).ConfigureAwait(false); + + async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options) + => await RemovePermissionOverwriteAsync(role, options).ConfigureAwait(false); + + async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) + => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); + + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, + RequestOptions options) + => AsyncEnumerable.Empty>(); //Overridden //Overridden in Text/Voice + + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(null); //Overridden in Text/Voice + + //IChannel + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + => AsyncEnumerable.Empty>(); //Overridden in Text/Voice + + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(null); //Overridden in Text/Voice + + public override async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetChannelAsync(GuildId, Id, options).ConfigureAwait(false); + Update(model); } + internal static RestGuildChannel Create(BaseDiscordClient discord, IGuild guild, Model model) { switch (model.Type) @@ -37,6 +106,7 @@ namespace Discord.Rest return new RestGuildChannel(discord, guild, model.Id); } } + internal override void Update(Model model) { Name = model.Name.Value; @@ -44,124 +114,75 @@ namespace Discord.Rest var overwrites = model.PermissionOverwrites.Value; var newOverwrites = ImmutableArray.CreateBuilder(overwrites.Length); - for (int i = 0; i < overwrites.Length; i++) + for (var i = 0; i < overwrites.Length; i++) newOverwrites.Add(overwrites[i].ToEntity()); _overwrites = newOverwrites.ToImmutable(); } - public override async Task UpdateAsync(RequestOptions options = null) - { - var model = await Discord.ApiClient.GetChannelAsync(GuildId, Id, options).ConfigureAwait(false); - Update(model); - } - public async Task ModifyAsync(Action func, RequestOptions options = null) - { - var model = await ChannelHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); - Update(model); - } - public Task DeleteAsync(RequestOptions options = null) - => ChannelHelper.DeleteAsync(this, Discord, options); - public OverwritePermissions? GetPermissionOverwrite(IUser user) { - for (int i = 0; i < _overwrites.Length; i++) - { + for (var i = 0; i < _overwrites.Length; i++) if (_overwrites[i].TargetId == user.Id) return _overwrites[i].Permissions; - } return null; } + public OverwritePermissions? GetPermissionOverwrite(IRole role) { - for (int i = 0; i < _overwrites.Length; i++) - { + for (var i = 0; i < _overwrites.Length; i++) if (_overwrites[i].TargetId == role.Id) return _overwrites[i].Permissions; - } return null; } - public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms, RequestOptions options = null) + + public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms, + RequestOptions options = null) { await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, perms, options).ConfigureAwait(false); - _overwrites = _overwrites.Add(new Overwrite(user.Id, PermissionTarget.User, new OverwritePermissions(perms.AllowValue, perms.DenyValue))); + _overwrites = _overwrites.Add(new Overwrite(user.Id, PermissionTarget.User, + new OverwritePermissions(perms.AllowValue, perms.DenyValue))); } - public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms, RequestOptions options = null) + + public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms, + RequestOptions options = null) { await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, perms, options).ConfigureAwait(false); - _overwrites = _overwrites.Add(new Overwrite(role.Id, PermissionTarget.Role, new OverwritePermissions(perms.AllowValue, perms.DenyValue))); + _overwrites = _overwrites.Add(new Overwrite(role.Id, PermissionTarget.Role, + new OverwritePermissions(perms.AllowValue, perms.DenyValue))); } + public async Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) { await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user, options).ConfigureAwait(false); - for (int i = 0; i < _overwrites.Length; i++) - { + for (var i = 0; i < _overwrites.Length; i++) if (_overwrites[i].TargetId == user.Id) { _overwrites = _overwrites.RemoveAt(i); return; } - } } + public async Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) { await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role, options).ConfigureAwait(false); - for (int i = 0; i < _overwrites.Length; i++) - { + for (var i = 0; i < _overwrites.Length; i++) if (_overwrites[i].TargetId == role.Id) { _overwrites = _overwrites.RemoveAt(i); return; } - } } public async Task> GetInvitesAsync(RequestOptions options = null) => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); - public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) - => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); - public override string ToString() => Name; + public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, + bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options) + .ConfigureAwait(false); - //IGuildChannel - IGuild IGuildChannel.Guild - { - get - { - if (Guild != null) - return Guild; - throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); - } - } - - async Task> IGuildChannel.GetInvitesAsync(RequestOptions options) - => await GetInvitesAsync(options).ConfigureAwait(false); - async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) - => await CreateInviteAsync(maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); - - OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) - => GetPermissionOverwrite(role); - OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) - => GetPermissionOverwrite(user); - async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options) - => await AddPermissionOverwriteAsync(role, permissions, options).ConfigureAwait(false); - async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options) - => await AddPermissionOverwriteAsync(user, permissions, options).ConfigureAwait(false); - async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options) - => await RemovePermissionOverwriteAsync(role, options).ConfigureAwait(false); - async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) - => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); - - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => AsyncEnumerable.Empty>(); //Overridden //Overridden in Text/Voice - Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(null); //Overridden in Text/Voice - - //IChannel - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => AsyncEnumerable.Empty>(); //Overridden in Text/Voice - Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(null); //Overridden in Text/Voice + public override string ToString() => Name; } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 7f57c96ff..75701a688 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -8,182 +8,199 @@ using Model = Discord.API.Channel; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestTextChannel : RestGuildChannel, IRestMessageChannel, ITextChannel { - public string Topic { get; private set; } - public ulong? CategoryId { get; private set; } - - public string Mention => MentionUtils.MentionChannel(Id); - - private bool _nsfw; - public bool IsNsfw => _nsfw; - internal RestTextChannel(BaseDiscordClient discord, IGuild guild, ulong id) : base(discord, guild, id) { } - internal new static RestTextChannel Create(BaseDiscordClient discord, IGuild guild, Model model) - { - var entity = new RestTextChannel(discord, guild, model.Id); - entity.Update(model); - return entity; - } - internal override void Update(Model model) - { - base.Update(model); - CategoryId = model.CategoryId; - Topic = model.Topic.Value; - _nsfw = model.Nsfw.GetValueOrDefault(); - } - public async Task ModifyAsync(Action func, RequestOptions options = null) - { - var model = await ChannelHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); - Update(model); - } - - public Task GetUserAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetUserAsync(this, Guild, Discord, id, options); - public IAsyncEnumerable> GetUsersAsync(RequestOptions options = null) - => ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options); + private string DebuggerDisplay => $"{Name} ({Id}, Text)"; public Task GetMessageAsync(ulong id, RequestOptions options = null) => ChannelHelper.GetMessageAsync(this, Discord, id, options); - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + + public IAsyncEnumerable> GetMessagesAsync( + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); + public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, + RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, + RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, + Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); - public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); - public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) - => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); - public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); - public IDisposable EnterTypingState(RequestOptions options = null) - => ChannelHelper.EnterTypingState(this, Discord, options); - - public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) - => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options); - public Task GetWebhookAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetWebhookAsync(this, Discord, id, options); - public Task> GetWebhooksAsync(RequestOptions options = null) - => ChannelHelper.GetWebhooksAsync(this, Discord, options); - - public Task GetCategoryAsync(RequestOptions options = null) - => ChannelHelper.GetCategoryAsync(this, Discord, options); - - private string DebuggerDisplay => $"{Name} ({Id}, Text)"; - - //ITextChannel - async Task ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options) - => await CreateWebhookAsync(name, avatar, options).ConfigureAwait(false); - async Task ITextChannel.GetWebhookAsync(ulong id, RequestOptions options) - => await GetWebhookAsync(id, options).ConfigureAwait(false); - async Task> ITextChannel.GetWebhooksAsync(RequestOptions options) - => await GetWebhooksAsync(options).ConfigureAwait(false); //IMessageChannel async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) return await GetMessageAsync(id, options).ConfigureAwait(false); - else - return null; + return null; } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, + RequestOptions options) { - if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(limit, options); - else - return AsyncEnumerable.Empty>(); + return mode == CacheMode.AllowDownload + ? GetMessagesAsync(limit, options) + : AsyncEnumerable.Empty>(); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, + Direction dir, int limit, CacheMode mode, RequestOptions options) { - if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(fromMessageId, dir, limit, options); - else - return AsyncEnumerable.Empty>(); + return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessageId, dir, limit, options) : AsyncEnumerable.Empty>(); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, + Direction dir, int limit, CacheMode mode, RequestOptions options) { - if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(fromMessage, dir, limit, options); - else - return AsyncEnumerable.Empty>(); + return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessage, dir, limit, options) : AsyncEnumerable.Empty>(); } + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, + RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, + Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) + + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, + RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); - //IGuildChannel - async Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + //IChannel + async Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) return await GetUserAsync(id, options).ConfigureAwait(false); - else - return null; + return null; } - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) { - if (mode == CacheMode.AllowDownload) - return GetUsersAsync(options); - else - return AsyncEnumerable.Empty>(); + return mode == CacheMode.AllowDownload ? GetUsersAsync(options) : AsyncEnumerable.Empty>(); } - //IChannel - async Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + public string Topic { get; private set; } + public ulong? CategoryId { get; private set; } + + public string Mention => MentionUtils.MentionChannel(Id); + public bool IsNsfw { get; private set; } + + public async Task ModifyAsync(Action func, RequestOptions options = null) + { + var model = await ChannelHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); + Update(model); + } + + public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); + + public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) + => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); + + //ITextChannel + async Task ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options) + => await CreateWebhookAsync(name, avatar, options).ConfigureAwait(false); + + async Task ITextChannel.GetWebhookAsync(ulong id, RequestOptions options) + => await GetWebhookAsync(id, options).ConfigureAwait(false); + + async Task> ITextChannel.GetWebhooksAsync(RequestOptions options) + => await GetWebhooksAsync(options).ConfigureAwait(false); + + //IGuildChannel + async Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) return await GetUserAsync(id, options).ConfigureAwait(false); - else - return null; + return null; } - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, + RequestOptions options) { - if (mode == CacheMode.AllowDownload) - return GetUsersAsync(options); - else - return AsyncEnumerable.Empty>(); + return mode == CacheMode.AllowDownload ? GetUsersAsync(options) : AsyncEnumerable.Empty>(); } // INestedChannel async Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) { if (CategoryId.HasValue && mode == CacheMode.AllowDownload) - return (await Guild.GetChannelAsync(CategoryId.Value, mode, options).ConfigureAwait(false)) as ICategoryChannel; + return await Guild.GetChannelAsync(CategoryId.Value, mode, options).ConfigureAwait(false) as + ICategoryChannel; return null; } + + internal new static RestTextChannel Create(BaseDiscordClient discord, IGuild guild, Model model) + { + var entity = new RestTextChannel(discord, guild, model.Id); + entity.Update(model); + return entity; + } + + internal override void Update(Model model) + { + base.Update(model); + CategoryId = model.CategoryId; + Topic = model.Topic.Value; + IsNsfw = model.Nsfw.GetValueOrDefault(); + } + + public Task GetUserAsync(ulong id, RequestOptions options = null) + => ChannelHelper.GetUserAsync(this, Guild, Discord, id, options); + + public IAsyncEnumerable> GetUsersAsync(RequestOptions options = null) + => ChannelHelper.GetUsersAsync(this, Guild, Discord, null, null, options); + + public IDisposable EnterTypingState(RequestOptions options = null) + => ChannelHelper.EnterTypingState(this, Discord, options); + + public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) + => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options); + + public Task GetWebhookAsync(ulong id, RequestOptions options = null) + => ChannelHelper.GetWebhookAsync(this, Discord, id, options); + + public Task> GetWebhooksAsync(RequestOptions options = null) + => ChannelHelper.GetWebhooksAsync(this, Discord, options); + + public Task GetCategoryAsync(RequestOptions options = null) + => ChannelHelper.GetCategoryAsync(this, Discord, options); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs index 13f3b5efa..c55901f86 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestVoiceChannel.cs @@ -1,37 +1,25 @@ -using Discord.Audio; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using Discord.Audio; using Model = Discord.API.Channel; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestVoiceChannel : RestGuildChannel, IVoiceChannel, IRestAudioChannel { - public int Bitrate { get; private set; } - public int? UserLimit { get; private set; } - public ulong? CategoryId { get; private set; } - internal RestVoiceChannel(BaseDiscordClient discord, IGuild guild, ulong id) : base(discord, guild, id) { } - internal new static RestVoiceChannel Create(BaseDiscordClient discord, IGuild guild, Model model) - { - var entity = new RestVoiceChannel(discord, guild, model.Id); - entity.Update(model); - return entity; - } - internal override void Update(Model model) - { - base.Update(model); - CategoryId = model.CategoryId; - Bitrate = model.Bitrate.Value; - UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null; - } + + private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; + public int Bitrate { get; private set; } + public int? UserLimit { get; private set; } + public ulong? CategoryId { get; private set; } public async Task ModifyAsync(Action func, RequestOptions options = null) { @@ -39,27 +27,45 @@ namespace Discord.Rest Update(model); } - public Task GetCategoryAsync(RequestOptions options = null) - => ChannelHelper.GetCategoryAsync(this, Discord, options); - - private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; - //IAudioChannel - Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } - Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } + Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) => + throw new NotSupportedException(); + + Task IAudioChannel.DisconnectAsync() => throw new NotSupportedException(); //IGuildChannel Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(null); - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, + RequestOptions options) => AsyncEnumerable.Empty>(); // INestedChannel async Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) { if (CategoryId.HasValue && mode == CacheMode.AllowDownload) - return (await Guild.GetChannelAsync(CategoryId.Value, mode, options).ConfigureAwait(false)) as ICategoryChannel; + return await Guild.GetChannelAsync(CategoryId.Value, mode, options).ConfigureAwait(false) as + ICategoryChannel; return null; } + + internal new static RestVoiceChannel Create(BaseDiscordClient discord, IGuild guild, Model model) + { + var entity = new RestVoiceChannel(discord, guild, model.Id); + entity.Update(model); + return entity; + } + + internal override void Update(Model model) + { + base.Update(model); + CategoryId = model.CategoryId; + Bitrate = model.Bitrate.Value; + UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null; + } + + public Task GetCategoryAsync(RequestOptions options = null) + => ChannelHelper.GetCategoryAsync(this, Discord, options); } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs index e8b939e65..3cc1a8d4f 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs @@ -7,103 +7,115 @@ using System.Threading.Tasks; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] internal class RestVirtualMessageChannel : RestEntity, IMessageChannel { - public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - public string Mention => MentionUtils.MentionChannel(Id); - internal RestVirtualMessageChannel(BaseDiscordClient discord, ulong id) : base(discord, id) { } - internal static RestVirtualMessageChannel Create(BaseDiscordClient discord, ulong id) - { - return new RestVirtualMessageChannel(discord, id); - } - - public Task GetMessageAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetMessageAsync(this, Discord, id, options); - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); - public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public string Mention => MentionUtils.MentionChannel(Id); - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + private string DebuggerDisplay => $"({Id}, Text)"; + public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); - public IDisposable EnterTypingState(RequestOptions options = null) - => ChannelHelper.EnterTypingState(this, Discord, options); - - private string DebuggerDisplay => $"({Id}, Text)"; //IMessageChannel async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) { if (mode == CacheMode.AllowDownload) return await GetMessageAsync(id, options); - else - return null; + return null; } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, + RequestOptions options) { - if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(limit, options); - else - return AsyncEnumerable.Empty>(); + return mode == CacheMode.AllowDownload ? GetMessagesAsync(limit, options) : AsyncEnumerable.Empty>(); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, + Direction dir, int limit, CacheMode mode, RequestOptions options) { - if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(fromMessageId, dir, limit, options); - else - return AsyncEnumerable.Empty>(); + return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessageId, dir, limit, options) : AsyncEnumerable.Empty>(); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, + Direction dir, int limit, CacheMode mode, RequestOptions options) { - if (mode == CacheMode.AllowDownload) - return GetMessagesAsync(fromMessage, dir, limit, options); - else - return AsyncEnumerable.Empty>(); + return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessage, dir, limit, options) : AsyncEnumerable.Empty>(); } + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options); - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, + RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options); - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, + Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) + + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, + RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); //IChannel - string IChannel.Name { get { throw new NotSupportedException(); } } - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - { + string IChannel.Name => throw new NotSupportedException(); + + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => throw new NotSupportedException(); - } - Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - { + + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => throw new NotSupportedException(); - } + + internal static RestVirtualMessageChannel Create(BaseDiscordClient discord, ulong id) => + new RestVirtualMessageChannel(discord, id); + + public Task GetMessageAsync(ulong id, RequestOptions options = null) + => ChannelHelper.GetMessageAsync(this, Discord, id, options); + + public IAsyncEnumerable> GetMessagesAsync( + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, null, Direction.Before, limit, options); + + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessageId, dir, limit, options); + + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => ChannelHelper.GetMessagesAsync(this, Discord, fromMessage.Id, dir, limit, options); + + public Task> GetPinnedMessagesAsync(RequestOptions options = null) + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, + RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); + + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, + RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); + + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, + Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + + public IDisposable EnterTypingState(RequestOptions options = null) + => ChannelHelper.EnterTypingState(this, Discord, options); } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs index 4bd0e9972..7b2c08ce3 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/GuildHelper.cs @@ -1,9 +1,9 @@ -using Discord.API.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; +using Discord.API.Rest; using EmbedModel = Discord.API.GuildEmbed; using Model = Discord.API.Guild; using RoleModel = Discord.API.Role; @@ -22,7 +22,7 @@ namespace Discord.Rest var args = new GuildProperties(); func(args); - var apiArgs = new API.Rest.ModifyGuildParams + var apiArgs = new ModifyGuildParams { AfkChannelId = args.AfkChannelId, AfkTimeout = args.AfkTimeout, @@ -62,6 +62,7 @@ namespace Discord.Rest return await client.ApiClient.ModifyGuildAsync(guild.Id, apiArgs, options).ConfigureAwait(false); } + public static async Task ModifyEmbedAsync(IGuild guild, BaseDiscordClient client, Action func, RequestOptions options) { @@ -69,7 +70,7 @@ namespace Discord.Rest var args = new GuildEmbedProperties(); func(args); - var apiArgs = new API.Rest.ModifyGuildEmbedParams + var apiArgs = new ModifyGuildEmbedParams { Enabled = args.Enabled }; @@ -81,28 +82,27 @@ namespace Discord.Rest return await client.ApiClient.ModifyGuildEmbedAsync(guild.Id, apiArgs, options).ConfigureAwait(false); } + public static async Task ReorderChannelsAsync(IGuild guild, BaseDiscordClient client, IEnumerable args, RequestOptions options) { - var apiArgs = args.Select(x => new API.Rest.ModifyGuildChannelsParams(x.Id, x.Position)); + var apiArgs = args.Select(x => new ModifyGuildChannelsParams(x.Id, x.Position)); await client.ApiClient.ModifyGuildChannelsAsync(guild.Id, apiArgs, options).ConfigureAwait(false); } - public static async Task> ReorderRolesAsync(IGuild guild, BaseDiscordClient client, + + public static async Task> ReorderRolesAsync(IGuild guild, + BaseDiscordClient client, IEnumerable args, RequestOptions options) { - var apiArgs = args.Select(x => new API.Rest.ModifyGuildRolesParams(x.Id, x.Position)); + var apiArgs = args.Select(x => new ModifyGuildRolesParams(x.Id, x.Position)); return await client.ApiClient.ModifyGuildRolesAsync(guild.Id, apiArgs, options).ConfigureAwait(false); } + public static async Task LeaveAsync(IGuild guild, BaseDiscordClient client, - RequestOptions options) - { - await client.ApiClient.LeaveGuildAsync(guild.Id, options).ConfigureAwait(false); - } + RequestOptions options) => await client.ApiClient.LeaveGuildAsync(guild.Id, options).ConfigureAwait(false); + public static async Task DeleteAsync(IGuild guild, BaseDiscordClient client, - RequestOptions options) - { - await client.ApiClient.DeleteGuildAsync(guild.Id, options).ConfigureAwait(false); - } + RequestOptions options) => await client.ApiClient.DeleteGuildAsync(guild.Id, options).ConfigureAwait(false); //Bans public static async Task> GetBansAsync(IGuild guild, BaseDiscordClient client, @@ -111,7 +111,9 @@ namespace Discord.Rest var models = await client.ApiClient.GetGuildBansAsync(guild.Id, options).ConfigureAwait(false); return models.Select(x => RestBan.Create(client, x)).ToImmutableArray(); } - public static async Task GetBanAsync(IGuild guild, BaseDiscordClient client, ulong userId, RequestOptions options) + + public static async Task GetBanAsync(IGuild guild, BaseDiscordClient client, ulong userId, + RequestOptions options) { var model = await client.ApiClient.GetGuildBanAsync(guild.Id, userId, options).ConfigureAwait(false); return RestBan.Create(client, model); @@ -120,30 +122,34 @@ namespace Discord.Rest public static async Task AddBanAsync(IGuild guild, BaseDiscordClient client, ulong userId, int pruneDays, string reason, RequestOptions options) { - var args = new CreateGuildBanParams { DeleteMessageDays = pruneDays, Reason = reason }; + var args = new CreateGuildBanParams + { + DeleteMessageDays = pruneDays, + Reason = reason + }; await client.ApiClient.CreateGuildBanAsync(guild.Id, userId, args, options).ConfigureAwait(false); } + public static async Task RemoveBanAsync(IGuild guild, BaseDiscordClient client, - ulong userId, RequestOptions options) - { + ulong userId, RequestOptions options) => await client.ApiClient.RemoveGuildBanAsync(guild.Id, userId, options).ConfigureAwait(false); - } //Channels public static async Task GetChannelAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) { var model = await client.ApiClient.GetChannelAsync(guild.Id, id, options).ConfigureAwait(false); - if (model != null) - return RestGuildChannel.Create(client, guild, model); - return null; + return model != null ? RestGuildChannel.Create(client, guild, model) : null; } - public static async Task> GetChannelsAsync(IGuild guild, BaseDiscordClient client, + + public static async Task> GetChannelsAsync(IGuild guild, + BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetGuildChannelsAsync(guild.Id, options).ConfigureAwait(false); return models.Select(x => RestGuildChannel.Create(client, guild, x)).ToImmutableArray(); } + public static async Task CreateTextChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options, Action func = null) { @@ -161,6 +167,7 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); return RestTextChannel.Create(client, guild, model); } + public static async Task CreateVoiceChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options, Action func = null) { @@ -178,6 +185,7 @@ namespace Discord.Rest var model = await client.ApiClient.CreateGuildChannelAsync(guild.Id, args, options).ConfigureAwait(false); return RestVoiceChannel.Create(client, guild, model); } + public static async Task CreateCategoryChannelAsync(IGuild guild, BaseDiscordClient client, string name, RequestOptions options) { @@ -189,27 +197,32 @@ namespace Discord.Rest } //Integrations - public static async Task> GetIntegrationsAsync(IGuild guild, BaseDiscordClient client, + public static async Task> GetIntegrationsAsync(IGuild guild, + BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetGuildIntegrationsAsync(guild.Id, options).ConfigureAwait(false); return models.Select(x => RestGuildIntegration.Create(client, guild, x)).ToImmutableArray(); } + public static async Task CreateIntegrationAsync(IGuild guild, BaseDiscordClient client, ulong id, string type, RequestOptions options) { var args = new CreateGuildIntegrationParams(id, type); - var model = await client.ApiClient.CreateGuildIntegrationAsync(guild.Id, args, options).ConfigureAwait(false); + var model = await client.ApiClient.CreateGuildIntegrationAsync(guild.Id, args, options) + .ConfigureAwait(false); return RestGuildIntegration.Create(client, guild, model); } //Invites - public static async Task> GetInvitesAsync(IGuild guild, BaseDiscordClient client, + public static async Task> GetInvitesAsync(IGuild guild, + BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetGuildInvitesAsync(guild.Id, options).ConfigureAwait(false); return models.Select(x => RestInviteMetadata.Create(client, guild, null, x)).ToImmutableArray(); } + public static async Task GetVanityInviteAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) { @@ -229,8 +242,8 @@ namespace Discord.Rest await role.ModifyAsync(x => { x.Name = name; - x.Permissions = (permissions ?? role.Permissions); - x.Color = (color ?? Color.Default); + x.Permissions = permissions ?? role.Permissions; + x.Color = color ?? Color.Default; x.Hoist = isHoisted; }, options).ConfigureAwait(false); @@ -242,42 +255,39 @@ namespace Discord.Rest ulong id, RequestOptions options) { var model = await client.ApiClient.GetGuildMemberAsync(guild.Id, id, options).ConfigureAwait(false); - if (model != null) - return RestGuildUser.Create(client, guild, model); - return null; + return model != null ? RestGuildUser.Create(client, guild, model) : null; } + public static async Task GetCurrentUserAsync(IGuild guild, BaseDiscordClient client, - RequestOptions options) - { - return await GetUserAsync(guild, client, client.CurrentUser.Id, options).ConfigureAwait(false); - } - public static IAsyncEnumerable> GetUsersAsync(IGuild guild, BaseDiscordClient client, - ulong? fromUserId, int? limit, RequestOptions options) - { - return new PagedAsyncEnumerable( - DiscordConfig.MaxMessagesPerBatch, - async (info, ct) => - { - var args = new GetGuildMembersParams - { - Limit = info.PageSize - }; - if (info.Position != null) - args.AfterUserId = info.Position.Value; - var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args, options); - return models.Select(x => RestGuildUser.Create(client, guild, x)).ToImmutableArray(); - }, - nextPage: (info, lastPage) => + RequestOptions options) => + await GetUserAsync(guild, client, client.CurrentUser.Id, options).ConfigureAwait(false); + + public static IAsyncEnumerable> GetUsersAsync(IGuild guild, + BaseDiscordClient client, + ulong? fromUserId, int? limit, RequestOptions options) => new PagedAsyncEnumerable( + DiscordConfig.MaxMessagesPerBatch, + async (info, ct) => + { + var args = new GetGuildMembersParams { - if (lastPage.Count != DiscordConfig.MaxMessagesPerBatch) - return false; - info.Position = lastPage.Max(x => x.Id); - return true; - }, - start: fromUserId, - count: limit - ); - } + Limit = info.PageSize + }; + if (info.Position != null) + args.AfterUserId = info.Position.Value; + var models = await client.ApiClient.GetGuildMembersAsync(guild.Id, args, options); + return models.Select(x => RestGuildUser.Create(client, guild, x)).ToImmutableArray(); + }, + (info, lastPage) => + { + if (lastPage.Count != DiscordConfig.MaxMessagesPerBatch) + return false; + info.Position = lastPage.Max(x => x.Id); + return true; + }, + fromUserId, + limit + ); + public static async Task PruneUsersAsync(IGuild guild, BaseDiscordClient client, int days, bool simulate, RequestOptions options) { @@ -291,55 +301,57 @@ namespace Discord.Rest } // Audit logs - public static IAsyncEnumerable> GetAuditLogsAsync(IGuild guild, BaseDiscordClient client, - ulong? from, int? limit, RequestOptions options) - { - return new PagedAsyncEnumerable( - DiscordConfig.MaxAuditLogEntriesPerBatch, - async (info, ct) => - { - var args = new GetAuditLogsParams - { - Limit = info.PageSize - }; - if (info.Position != null) - args.BeforeEntryId = info.Position.Value; - var model = await client.ApiClient.GetAuditLogsAsync(guild.Id, args, options); - return model.Entries.Select((x) => RestAuditLogEntry.Create(client, model, x)).ToImmutableArray(); - }, - nextPage: (info, lastPage) => + public static IAsyncEnumerable> GetAuditLogsAsync(IGuild guild, + BaseDiscordClient client, + ulong? from, int? limit, RequestOptions options) => new PagedAsyncEnumerable( + DiscordConfig.MaxAuditLogEntriesPerBatch, + async (info, ct) => + { + var args = new GetAuditLogsParams { - if (lastPage.Count != DiscordConfig.MaxAuditLogEntriesPerBatch) - return false; - info.Position = lastPage.Min(x => x.Id); - return true; - }, - start: from, - count: limit - ); - } + Limit = info.PageSize + }; + if (info.Position != null) + args.BeforeEntryId = info.Position.Value; + var model = await client.ApiClient.GetAuditLogsAsync(guild.Id, args, options); + return model.Entries.Select(x => RestAuditLogEntry.Create(client, model, x)).ToImmutableArray(); + }, + (info, lastPage) => + { + if (lastPage.Count != DiscordConfig.MaxAuditLogEntriesPerBatch) + return false; + info.Position = lastPage.Min(x => x.Id); + return true; + }, + from, + limit + ); //Webhooks - public static async Task GetWebhookAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) + public static async Task GetWebhookAsync(IGuild guild, BaseDiscordClient client, ulong id, + RequestOptions options) { - var model = await client.ApiClient.GetWebhookAsync(id, options: options).ConfigureAwait(false); - if (model == null) - return null; - return RestWebhook.Create(client, guild, model); + var model = await client.ApiClient.GetWebhookAsync(id, options).ConfigureAwait(false); + return model == null ? null : RestWebhook.Create(client, guild, model); } - public static async Task> GetWebhooksAsync(IGuild guild, BaseDiscordClient client, RequestOptions options) + + public static async Task> GetWebhooksAsync(IGuild guild, + BaseDiscordClient client, RequestOptions options) { var models = await client.ApiClient.GetGuildWebhooksAsync(guild.Id, options).ConfigureAwait(false); return models.Select(x => RestWebhook.Create(client, guild, x)).ToImmutableArray(); } //Emotes - public static async Task GetEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) + public static async Task GetEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, + RequestOptions options) { var emote = await client.ApiClient.GetGuildEmoteAsync(guild.Id, id, options); return emote.ToEntity(); } - public static async Task CreateEmoteAsync(IGuild guild, BaseDiscordClient client, string name, Image image, Optional> roles, + + public static async Task CreateEmoteAsync(IGuild guild, BaseDiscordClient client, string name, + Image image, Optional> roles, RequestOptions options) { var apiargs = new CreateGuildEmoteParams @@ -348,12 +360,14 @@ namespace Discord.Rest Image = image.ToModel() }; if (roles.IsSpecified) - apiargs.RoleIds = roles.Value?.Select(xr => xr.Id)?.ToArray(); + apiargs.RoleIds = roles.Value?.Select(xr => xr.Id).ToArray(); var emote = await client.ApiClient.CreateGuildEmoteAsync(guild.Id, apiargs, options); return emote.ToEntity(); } - public static async Task ModifyEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, Action func, + + public static async Task ModifyEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, + Action func, RequestOptions options) { if (func == null) throw new ArgumentNullException(nameof(func)); @@ -366,12 +380,13 @@ namespace Discord.Rest Name = props.Name }; if (props.Roles.IsSpecified) - apiargs.RoleIds = props.Roles.Value?.Select(xr => xr.Id)?.ToArray(); + apiargs.RoleIds = props.Roles.Value?.Select(xr => xr.Id).ToArray(); var emote = await client.ApiClient.ModifyGuildEmoteAsync(guild.Id, id, apiargs, options); return emote.ToEntity(); } - public static Task DeleteEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) + + public static Task DeleteEmoteAsync(IGuild guild, BaseDiscordClient client, ulong id, RequestOptions options) => client.ApiClient.DeleteGuildEmoteAsync(guild.Id, id, options); } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs b/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs index 104bec903..1b879c4cb 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestBan.cs @@ -3,26 +3,25 @@ using Model = Discord.API.Ban; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestBan : IBan { - public RestUser User { get; } - public string Reason { get; } - internal RestBan(RestUser user, string reason) { User = user; Reason = reason; } - internal static RestBan Create(BaseDiscordClient client, Model model) - { - return new RestBan(RestUser.Create(client, model.User), model.Reason); - } - public override string ToString() => User.ToString(); + public RestUser User { get; } private string DebuggerDisplay => $"{User}: {Reason}"; + public string Reason { get; } //IBan IUser IBan.User => User; + + internal static RestBan Create(BaseDiscordClient client, Model model) => + new RestBan(RestUser.Create(client, model.User), model.Reason); + + public override string ToString() => User.ToString(); } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index 9bf13fa07..dd2a6425e 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -1,21 +1,33 @@ -using Discord.Audio; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using Discord.API; +using Discord.Audio; using EmbedModel = Discord.API.GuildEmbed; using Model = Discord.API.Guild; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestGuild : RestEntity, IGuild, IUpdateable { - private ImmutableDictionary _roles; private ImmutableArray _emotes; private ImmutableArray _features; + private ImmutableDictionary _roles; + + internal RestGuild(BaseDiscordClient client, ulong id) + : base(client, id) + { + } + + internal bool Available { get; private set; } + + public RestRole EveryoneRole => GetRole(Id); + public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); + private string DebuggerDisplay => $"{Name} ({Id})"; public string Name { get; private set; } public int AFKTimeout { get; private set; } @@ -31,30 +43,267 @@ namespace Discord.Rest public string VoiceRegionId { get; private set; } public string IconId { get; private set; } public string SplashId { get; private set; } - internal bool Available { get; private set; } public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); [Obsolete("DefaultChannelId is deprecated, use GetDefaultChannelAsync")] public ulong DefaultChannelId => Id; + public string IconUrl => CDN.GetGuildIconUrl(Id, IconId); public string SplashUrl => CDN.GetGuildSplashUrl(Id, SplashId); - - public RestRole EveryoneRole => GetRole(Id); - public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); public IReadOnlyCollection Emotes => _emotes; public IReadOnlyCollection Features => _features; - internal RestGuild(BaseDiscordClient client, ulong id) - : base(client, id) + public Task DeleteAsync(RequestOptions options = null) + => GuildHelper.DeleteAsync(this, Discord, options); + + public async Task ModifyAsync(Action func, RequestOptions options = null) + { + var model = await GuildHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); + Update(model); + } + + public async Task ModifyEmbedAsync(Action func, RequestOptions options = null) + { + var model = await GuildHelper.ModifyEmbedAsync(this, Discord, func, options).ConfigureAwait(false); + Update(model); + } + + public async Task ReorderChannelsAsync(IEnumerable args, + RequestOptions options = null) + { + var arr = args.ToArray(); + await GuildHelper.ReorderChannelsAsync(this, Discord, arr, options); + } + + public async Task ReorderRolesAsync(IEnumerable args, RequestOptions options = null) + { + var models = await GuildHelper.ReorderRolesAsync(this, Discord, args, options).ConfigureAwait(false); + foreach (var model in models) + { + var role = GetRole(model.Id); + if (role != null) + role.Update(model); + } + } + + public Task LeaveAsync(RequestOptions options = null) + => GuildHelper.LeaveAsync(this, Discord, options); + + public Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) + => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, reason, options); + + public Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null) + => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, reason, options); + + public Task RemoveBanAsync(IUser user, RequestOptions options = null) + => GuildHelper.RemoveBanAsync(this, Discord, user.Id, options); + + public Task RemoveBanAsync(ulong userId, RequestOptions options = null) + => GuildHelper.RemoveBanAsync(this, Discord, userId, options); + + public Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) + => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); + + //Emotes + public Task GetEmoteAsync(ulong id, RequestOptions options = null) + => GuildHelper.GetEmoteAsync(this, Discord, id, options); + + public Task CreateEmoteAsync(string name, Image image, + Optional> roles = default(Optional>), RequestOptions options = null) + => GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); + + public Task ModifyEmoteAsync(GuildEmote emote, Action func, + RequestOptions options = null) + => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); + + public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null) + => GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options); + + //IGuild + bool IGuild.Available => Available; + IAudioClient IGuild.AudioClient => null; + IRole IGuild.EveryoneRole => EveryoneRole; + IReadOnlyCollection IGuild.Roles => Roles; + + async Task> IGuild.GetBansAsync(RequestOptions options) + => await GetBansAsync(options).ConfigureAwait(false); + + /// + async Task IGuild.GetBanAsync(IUser user, RequestOptions options) + => await GetBanAsync(user, options).ConfigureAwait(false); + + /// + async Task IGuild.GetBanAsync(ulong userId, RequestOptions options) + => await GetBanAsync(userId, options).ConfigureAwait(false); + + async Task> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetChannelsAsync(options).ConfigureAwait(false); + return ImmutableArray.Create(); + } + + async Task IGuild.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetChannelAsync(id, options).ConfigureAwait(false); + return null; + } + + async Task> IGuild.GetTextChannelsAsync(CacheMode mode, + RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetTextChannelsAsync(options).ConfigureAwait(false); + return ImmutableArray.Create(); + } + + async Task IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options) { + if (mode == CacheMode.AllowDownload) + return await GetTextChannelAsync(id, options).ConfigureAwait(false); + return null; } + + async Task> IGuild.GetVoiceChannelsAsync(CacheMode mode, + RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetVoiceChannelsAsync(options).ConfigureAwait(false); + return ImmutableArray.Create(); + } + + async Task> IGuild.GetCategoriesAsync(CacheMode mode, + RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetCategoryChannelsAsync(options).ConfigureAwait(false); + return null; + } + + async Task IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetVoiceChannelAsync(id, options).ConfigureAwait(false); + return null; + } + + async Task IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetAFKChannelAsync(options).ConfigureAwait(false); + return null; + } + + async Task IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetDefaultChannelAsync(options).ConfigureAwait(false); + return null; + } + + async Task IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetEmbedChannelAsync(options).ConfigureAwait(false); + return null; + } + + async Task IGuild.GetSystemChannelAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetSystemChannelAsync(options).ConfigureAwait(false); + return null; + } + + async Task IGuild.CreateTextChannelAsync(string name, Action func, + RequestOptions options) + => await CreateTextChannelAsync(name, func, options).ConfigureAwait(false); + + async Task IGuild.CreateVoiceChannelAsync(string name, Action func, + RequestOptions options) + => await CreateVoiceChannelAsync(name, func, options).ConfigureAwait(false); + + async Task IGuild.CreateCategoryAsync(string name, RequestOptions options) + => await CreateCategoryChannelAsync(name, options).ConfigureAwait(false); + + async Task> IGuild.GetIntegrationsAsync(RequestOptions options) + => await GetIntegrationsAsync(options).ConfigureAwait(false); + + async Task IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options) + => await CreateIntegrationAsync(id, type, options).ConfigureAwait(false); + + async Task> IGuild.GetInvitesAsync(RequestOptions options) + => await GetInvitesAsync(options).ConfigureAwait(false); + + /// + async Task IGuild.GetVanityInviteAsync(RequestOptions options) + => await GetVanityInviteAsync(options).ConfigureAwait(false); + + IRole IGuild.GetRole(ulong id) + => GetRole(id); + + async Task IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, + bool isHoisted, RequestOptions options) + => await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false); + + async Task IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetUserAsync(id, options).ConfigureAwait(false); + return null; + } + + async Task IGuild.GetCurrentUserAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetCurrentUserAsync(options).ConfigureAwait(false); + return null; + } + + async Task IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetOwnerAsync(options).ConfigureAwait(false); + return null; + } + + async Task> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return (await GetUsersAsync(options).FlattenAsync().ConfigureAwait(false)).ToImmutableArray(); + return ImmutableArray.Create(); + } + + Task IGuild.DownloadUsersAsync() => throw new NotSupportedException(); + + async Task> IGuild.GetAuditLogsAsync(int limit, CacheMode cacheMode, + RequestOptions options) + { + if (cacheMode == CacheMode.AllowDownload) + return (await GetAuditLogsAsync(limit, options).FlattenAsync().ConfigureAwait(false)) + .ToImmutableArray(); + return ImmutableArray.Create(); + } + + async Task IGuild.GetWebhookAsync(ulong id, RequestOptions options) + => await GetWebhookAsync(id, options); + + async Task> IGuild.GetWebhooksAsync(RequestOptions options) + => await GetWebhooksAsync(options); + + //General + public async Task UpdateAsync(RequestOptions options = null) + => Update(await Discord.ApiClient.GetGuildAsync(Id, options).ConfigureAwait(false)); + internal static RestGuild Create(BaseDiscordClient discord, Model model) { var entity = new RestGuild(discord, model.Id); entity.Update(model); return entity; } + internal void Update(Model model) { AFKChannelId = model.AFKChannelId; @@ -74,113 +323,74 @@ namespace Discord.Rest if (model.Emojis != null) { var emotes = ImmutableArray.CreateBuilder(model.Emojis.Length); - for (int i = 0; i < model.Emojis.Length; i++) + for (var i = 0; i < model.Emojis.Length; i++) emotes.Add(model.Emojis[i].ToEntity()); _emotes = emotes.ToImmutableArray(); } else _emotes = ImmutableArray.Create(); - if (model.Features != null) - _features = model.Features.ToImmutableArray(); - else - _features = ImmutableArray.Create(); + _features = model.Features?.ToImmutableArray() ?? ImmutableArray.Create(); var roles = ImmutableDictionary.CreateBuilder(); if (model.Roles != null) - { - for (int i = 0; i < model.Roles.Length; i++) - roles[model.Roles[i].Id] = RestRole.Create(Discord, this, model.Roles[i]); - } + foreach (var t in model.Roles) + roles[t.Id] = RestRole.Create(Discord, this, t); + _roles = roles.ToImmutable(); Available = true; } + internal void Update(EmbedModel model) { EmbedChannelId = model.ChannelId; IsEmbeddable = model.Enabled; } - //General - public async Task UpdateAsync(RequestOptions options = null) - => Update(await Discord.ApiClient.GetGuildAsync(Id, options).ConfigureAwait(false)); - public Task DeleteAsync(RequestOptions options = null) - => GuildHelper.DeleteAsync(this, Discord, options); - - public async Task ModifyAsync(Action func, RequestOptions options = null) - { - var model = await GuildHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); - Update(model); - } - public async Task ModifyEmbedAsync(Action func, RequestOptions options = null) - { - var model = await GuildHelper.ModifyEmbedAsync(this, Discord, func, options).ConfigureAwait(false); - Update(model); - } - public async Task ReorderChannelsAsync(IEnumerable args, RequestOptions options = null) - { - var arr = args.ToArray(); - await GuildHelper.ReorderChannelsAsync(this, Discord, arr, options); - } - public async Task ReorderRolesAsync(IEnumerable args, RequestOptions options = null) - { - var models = await GuildHelper.ReorderRolesAsync(this, Discord, args, options).ConfigureAwait(false); - foreach (var model in models) - { - var role = GetRole(model.Id); - if (role != null) - role.Update(model); - } - } - - public Task LeaveAsync(RequestOptions options = null) - => GuildHelper.LeaveAsync(this, Discord, options); - //Bans public Task> GetBansAsync(RequestOptions options = null) => GuildHelper.GetBansAsync(this, Discord, options); + public Task GetBanAsync(IUser user, RequestOptions options = null) => GuildHelper.GetBanAsync(this, Discord, user.Id, options); + public Task GetBanAsync(ulong userId, RequestOptions options = null) => GuildHelper.GetBanAsync(this, Discord, userId, options); - public Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) - => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, reason, options); - public Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null) - => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, reason, options); - - public Task RemoveBanAsync(IUser user, RequestOptions options = null) - => GuildHelper.RemoveBanAsync(this, Discord, user.Id, options); - public Task RemoveBanAsync(ulong userId, RequestOptions options = null) - => GuildHelper.RemoveBanAsync(this, Discord, userId, options); - //Channels public Task> GetChannelsAsync(RequestOptions options = null) => GuildHelper.GetChannelsAsync(this, Discord, options); + public Task GetChannelAsync(ulong id, RequestOptions options = null) => GuildHelper.GetChannelAsync(this, Discord, id, options); + public async Task GetTextChannelAsync(ulong id, RequestOptions options = null) { var channel = await GuildHelper.GetChannelAsync(this, Discord, id, options).ConfigureAwait(false); return channel as RestTextChannel; } + public async Task> GetTextChannelsAsync(RequestOptions options = null) { var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); return channels.Select(x => x as RestTextChannel).Where(x => x != null).ToImmutableArray(); } + public async Task GetVoiceChannelAsync(ulong id, RequestOptions options = null) { var channel = await GuildHelper.GetChannelAsync(this, Discord, id, options).ConfigureAwait(false); return channel as RestVoiceChannel; } + public async Task> GetVoiceChannelsAsync(RequestOptions options = null) { var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); return channels.Select(x => x as RestVoiceChannel).Where(x => x != null).ToImmutableArray(); } - public async Task> GetCategoryChannelsAsync(RequestOptions options = null) + + public async Task> GetCategoryChannelsAsync( + RequestOptions options = null) { var channels = await GuildHelper.GetChannelsAsync(this, Discord, options).ConfigureAwait(false); return channels.Select(x => x as RestCategoryChannel).Where(x => x != null).ToImmutableArray(); @@ -191,11 +401,14 @@ namespace Discord.Rest var afkId = AFKChannelId; if (afkId.HasValue) { - var channel = await GuildHelper.GetChannelAsync(this, Discord, afkId.Value, options).ConfigureAwait(false); + var channel = await GuildHelper.GetChannelAsync(this, Discord, afkId.Value, options) + .ConfigureAwait(false); return channel as RestVoiceChannel; } + return null; } + public async Task GetDefaultChannelAsync(RequestOptions options = null) { var channels = await GetTextChannelsAsync(options).ConfigureAwait(false); @@ -205,6 +418,7 @@ namespace Discord.Rest .OrderBy(c => c.Position) .FirstOrDefault(); } + public async Task GetEmbedChannelAsync(RequestOptions options = null) { var embedId = EmbedChannelId; @@ -212,32 +426,42 @@ namespace Discord.Rest return await GuildHelper.GetChannelAsync(this, Discord, embedId.Value, options).ConfigureAwait(false); return null; } + public async Task GetSystemChannelAsync(RequestOptions options = null) { var systemId = SystemChannelId; if (systemId.HasValue) { - var channel = await GuildHelper.GetChannelAsync(this, Discord, systemId.Value, options).ConfigureAwait(false); + var channel = await GuildHelper.GetChannelAsync(this, Discord, systemId.Value, options) + .ConfigureAwait(false); return channel as RestTextChannel; } + return null; } - public Task CreateTextChannelAsync(string name, Action func = null, RequestOptions options = null) + + public Task CreateTextChannelAsync(string name, Action func = null, + RequestOptions options = null) => GuildHelper.CreateTextChannelAsync(this, Discord, name, options, func); - public Task CreateVoiceChannelAsync(string name, Action func = null, RequestOptions options = null) + + public Task CreateVoiceChannelAsync(string name, Action func = null, + RequestOptions options = null) => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options, func); + public Task CreateCategoryChannelAsync(string name, RequestOptions options = null) => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options); //Integrations public Task> GetIntegrationsAsync(RequestOptions options = null) => GuildHelper.GetIntegrationsAsync(this, Discord, options); + public Task CreateIntegrationAsync(ulong id, string type, RequestOptions options = null) => GuildHelper.CreateIntegrationAsync(this, Discord, id, type, options); //Invites public Task> GetInvitesAsync(RequestOptions options = null) => GuildHelper.GetInvitesAsync(this, Discord, options); + /// /// Gets the vanity invite URL of this guild. /// @@ -251,15 +475,15 @@ namespace Discord.Rest //Roles public RestRole GetRole(ulong id) { - if (_roles.TryGetValue(id, out RestRole value)) - return value; - return null; + return _roles.TryGetValue(id, out var value) ? value : null; } - public async Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), + public async Task CreateRoleAsync(string name, + GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), bool isHoisted = false, RequestOptions options = null) { - var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, options).ConfigureAwait(false); + var role = await GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, options) + .ConfigureAwait(false); _roles = _roles.Add(role.Id, role); return role; } @@ -267,195 +491,28 @@ namespace Discord.Rest //Users public IAsyncEnumerable> GetUsersAsync(RequestOptions options = null) => GuildHelper.GetUsersAsync(this, Discord, null, null, options); + public Task GetUserAsync(ulong id, RequestOptions options = null) => GuildHelper.GetUserAsync(this, Discord, id, options); + public Task GetCurrentUserAsync(RequestOptions options = null) => GuildHelper.GetUserAsync(this, Discord, Discord.CurrentUser.Id, options); + public Task GetOwnerAsync(RequestOptions options = null) => GuildHelper.GetUserAsync(this, Discord, OwnerId, options); - public Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) - => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); - //Audit logs - public IAsyncEnumerable> GetAuditLogsAsync(int limit, RequestOptions options = null) + public IAsyncEnumerable> GetAuditLogsAsync(int limit, + RequestOptions options = null) => GuildHelper.GetAuditLogsAsync(this, Discord, null, limit, options); //Webhooks public Task GetWebhookAsync(ulong id, RequestOptions options = null) => GuildHelper.GetWebhookAsync(this, Discord, id, options); + public Task> GetWebhooksAsync(RequestOptions options = null) => GuildHelper.GetWebhooksAsync(this, Discord, options); public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id})"; - - //Emotes - public Task GetEmoteAsync(ulong id, RequestOptions options = null) - => GuildHelper.GetEmoteAsync(this, Discord, id, options); - public Task CreateEmoteAsync(string name, Image image, Optional> roles = default(Optional>), RequestOptions options = null) - => GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); - public Task ModifyEmoteAsync(GuildEmote emote, Action func, RequestOptions options = null) - => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); - public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null) - => GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options); - - //IGuild - bool IGuild.Available => Available; - IAudioClient IGuild.AudioClient => null; - IRole IGuild.EveryoneRole => EveryoneRole; - IReadOnlyCollection IGuild.Roles => Roles; - - async Task> IGuild.GetBansAsync(RequestOptions options) - => await GetBansAsync(options).ConfigureAwait(false); - /// - async Task IGuild.GetBanAsync(IUser user, RequestOptions options) - => await GetBanAsync(user, options).ConfigureAwait(false); - /// - async Task IGuild.GetBanAsync(ulong userId, RequestOptions options) - => await GetBanAsync(userId, options).ConfigureAwait(false); - - async Task> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetChannelsAsync(options).ConfigureAwait(false); - else - return ImmutableArray.Create(); - } - async Task IGuild.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetChannelAsync(id, options).ConfigureAwait(false); - else - return null; - } - async Task> IGuild.GetTextChannelsAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetTextChannelsAsync(options).ConfigureAwait(false); - else - return ImmutableArray.Create(); - } - async Task IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetTextChannelAsync(id, options).ConfigureAwait(false); - else - return null; - } - async Task> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetVoiceChannelsAsync(options).ConfigureAwait(false); - else - return ImmutableArray.Create(); - } - async Task> IGuild.GetCategoriesAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetCategoryChannelsAsync(options).ConfigureAwait(false); - else - return null; - } - async Task IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetVoiceChannelAsync(id, options).ConfigureAwait(false); - else - return null; - } - async Task IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetAFKChannelAsync(options).ConfigureAwait(false); - else - return null; - } - async Task IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetDefaultChannelAsync(options).ConfigureAwait(false); - else - return null; - } - async Task IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetEmbedChannelAsync(options).ConfigureAwait(false); - else - return null; - } - async Task IGuild.GetSystemChannelAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetSystemChannelAsync(options).ConfigureAwait(false); - else - return null; - } - async Task IGuild.CreateTextChannelAsync(string name, Action func, RequestOptions options) - => await CreateTextChannelAsync(name, func, options).ConfigureAwait(false); - async Task IGuild.CreateVoiceChannelAsync(string name, Action func, RequestOptions options) - => await CreateVoiceChannelAsync(name, func, options).ConfigureAwait(false); - async Task IGuild.CreateCategoryAsync(string name, RequestOptions options) - => await CreateCategoryChannelAsync(name, options).ConfigureAwait(false); - - async Task> IGuild.GetIntegrationsAsync(RequestOptions options) - => await GetIntegrationsAsync(options).ConfigureAwait(false); - async Task IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options) - => await CreateIntegrationAsync(id, type, options).ConfigureAwait(false); - - async Task> IGuild.GetInvitesAsync(RequestOptions options) - => await GetInvitesAsync(options).ConfigureAwait(false); - /// - async Task IGuild.GetVanityInviteAsync(RequestOptions options) - => await GetVanityInviteAsync(options).ConfigureAwait(false); - - IRole IGuild.GetRole(ulong id) - => GetRole(id); - async Task IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) - => await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false); - - async Task IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetUserAsync(id, options).ConfigureAwait(false); - else - return null; - } - async Task IGuild.GetCurrentUserAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetCurrentUserAsync(options).ConfigureAwait(false); - else - return null; - } - async Task IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetOwnerAsync(options).ConfigureAwait(false); - else - return null; - } - async Task> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return (await GetUsersAsync(options).FlattenAsync().ConfigureAwait(false)).ToImmutableArray(); - else - return ImmutableArray.Create(); - } - Task IGuild.DownloadUsersAsync() { throw new NotSupportedException(); } - - async Task> IGuild.GetAuditLogsAsync(int limit, CacheMode cacheMode, RequestOptions options) - { - if (cacheMode == CacheMode.AllowDownload) - return (await GetAuditLogsAsync(limit, options).FlattenAsync().ConfigureAwait(false)).ToImmutableArray(); - else - return ImmutableArray.Create(); - } - - async Task IGuild.GetWebhookAsync(ulong id, RequestOptions options) - => await GetWebhookAsync(id, options); - async Task> IGuild.GetWebhooksAsync(RequestOptions options) - => await GetWebhooksAsync(options); } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs index f26a62d8d..80cfeae5e 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuildEmbed.cs @@ -3,21 +3,19 @@ using Model = Discord.API.GuildEmbed; namespace Discord { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public struct RestGuildEmbed { - public bool IsEnabled { get; private set; } - public ulong? ChannelId { get; private set; } + public bool IsEnabled { get; } + public ulong? ChannelId { get; } internal RestGuildEmbed(bool isEnabled, ulong? channelId) { ChannelId = channelId; IsEnabled = isEnabled; } - internal static RestGuildEmbed Create(Model model) - { - return new RestGuildEmbed(model.Enabled, model.ChannelId); - } + + internal static RestGuildEmbed Create(Model model) => new RestGuildEmbed(model.Enabled, model.ChannelId); public override string ToString() => ChannelId?.ToString(); private string DebuggerDisplay => $"{ChannelId} ({(IsEnabled ? "Enabled" : "Disabled")})"; diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs index eadda53f2..160184beb 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuildIntegration.cs @@ -1,15 +1,26 @@ using System; using System.Diagnostics; using System.Threading.Tasks; +using Discord.API.Rest; using Model = Discord.API.Integration; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestGuildIntegration : RestEntity, IGuildIntegration { private long _syncedAtTicks; + internal RestGuildIntegration(BaseDiscordClient discord, IGuild guild, ulong id) + : base(discord, id) + { + Guild = guild; + } + + public RestUser User { get; private set; } + internal IGuild Guild { get; } + private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})"; + public string Name { get; private set; } public string Type { get; private set; } public bool IsEnabled { get; private set; } @@ -18,17 +29,23 @@ namespace Discord.Rest public ulong ExpireGracePeriod { get; private set; } public ulong GuildId { get; private set; } public ulong RoleId { get; private set; } - public RestUser User { get; private set; } public IntegrationAccount Account { get; private set; } - internal IGuild Guild { get; private set; } public DateTimeOffset SyncedAt => DateTimeUtils.FromTicks(_syncedAtTicks); - internal RestGuildIntegration(BaseDiscordClient discord, IGuild guild, ulong id) - : base(discord, id) + IGuild IGuildIntegration.Guild { - Guild = guild; + get + { + if (Guild != null) + return Guild; + throw new InvalidOperationException( + "Unable to return this entity's parent unless it was fetched through that object."); + } } + + IUser IGuildIntegration.User => User; + internal static RestGuildIntegration Create(BaseDiscordClient discord, IGuild guild, Model model) { var entity = new RestGuildIntegration(discord, guild, model.Id); @@ -49,18 +66,17 @@ namespace Discord.Rest RoleId = model.RoleId; User = RestUser.Create(Discord, model.User); } - - public async Task DeleteAsync() - { + + public async Task DeleteAsync() => await Discord.ApiClient.DeleteGuildIntegrationAsync(GuildId, Id).ConfigureAwait(false); - } + public async Task ModifyAsync(Action func) { if (func == null) throw new NullReferenceException(nameof(func)); var args = new GuildIntegrationProperties(); func(args); - var apiArgs = new API.Rest.ModifyGuildIntegrationParams + var apiArgs = new ModifyGuildIntegrationParams { EnableEmoticons = args.EnableEmoticons, ExpireBehavior = args.ExpireBehavior, @@ -70,23 +86,10 @@ namespace Discord.Rest Update(model); } - public async Task SyncAsync() - { + + public async Task SyncAsync() => await Discord.ApiClient.SyncGuildIntegrationAsync(GuildId, Id).ConfigureAwait(false); - } public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id}{(IsEnabled ? ", Enabled" : "")})"; - - IGuild IGuildIntegration.Guild - { - get - { - if (Guild != null) - return Guild; - throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); - } - } - IUser IGuildIntegration.User => User; } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs index 6bc9cea7a..865150e8e 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestUserGuild.cs @@ -5,22 +5,28 @@ using Model = Discord.API.UserGuild; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestUserGuild : RestEntity, ISnowflakeEntity, IUserGuild { private string _iconId; - - public string Name { get; private set; } - public bool IsOwner { get; private set; } - public GuildPermissions Permissions { get; private set; } - - public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - public string IconUrl => CDN.GetGuildIconUrl(Id, _iconId); internal RestUserGuild(BaseDiscordClient discord, ulong id) : base(discord, id) { } + + private string DebuggerDisplay => $"{Name} ({Id}{(IsOwner ? ", Owned" : "")})"; + + public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + + public string Name { get; private set; } + public bool IsOwner { get; private set; } + public GuildPermissions Permissions { get; private set; } + public string IconUrl => CDN.GetGuildIconUrl(Id, _iconId); + + public async Task DeleteAsync(RequestOptions options = null) => + await Discord.ApiClient.DeleteGuildAsync(Id, options).ConfigureAwait(false); + internal static RestUserGuild Create(BaseDiscordClient discord, Model model) { var entity = new RestUserGuild(discord, model.Id); @@ -35,17 +41,10 @@ namespace Discord.Rest Name = model.Name; Permissions = new GuildPermissions(model.Permissions); } - - public async Task LeaveAsync(RequestOptions options = null) - { + + public async Task LeaveAsync(RequestOptions options = null) => await Discord.ApiClient.LeaveGuildAsync(Id, options).ConfigureAwait(false); - } - public async Task DeleteAsync(RequestOptions options = null) - { - await Discord.ApiClient.DeleteGuildAsync(Id, options).ConfigureAwait(false); - } public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id}{(IsOwner ? ", Owned" : "")})"; } } diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs b/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs index 4e0c3c1ee..0f282486f 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestVoiceRegion.cs @@ -1,33 +1,41 @@ -using Discord.Rest; using System.Diagnostics; +using Discord.Rest; using Model = Discord.API.VoiceRegion; namespace Discord { - [DebuggerDisplay("{DebuggerDisplay,nq}")] + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] public class RestVoiceRegion : RestEntity, IVoiceRegion { + internal RestVoiceRegion(BaseDiscordClient client, string id) + : base(client, id) + { + } + + private string DebuggerDisplay => $"{Name} ({Id}{(IsVip ? ", VIP" : "")}{(IsOptimal ? ", Optimal" : "")})"; + /// public string Name { get; private set; } + /// public bool IsVip { get; private set; } + /// public bool IsOptimal { get; private set; } + /// public bool IsDeprecated { get; private set; } + /// public bool IsCustom { get; private set; } - internal RestVoiceRegion(BaseDiscordClient client, string id) - : base(client, id) - { - } internal static RestVoiceRegion Create(BaseDiscordClient client, Model model) { var entity = new RestVoiceRegion(client, model.Id); entity.Update(model); return entity; } + internal void Update(Model model) { Name = model.Name; @@ -38,6 +46,5 @@ namespace Discord } public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id}{(IsVip ? ", VIP" : "")}{(IsOptimal ? ", Optimal" : "")})"; } } diff --git a/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs b/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs index ebcd93777..4577cc6a2 100644 --- a/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs +++ b/src/Discord.Net.Rest/Entities/Invites/InviteHelper.cs @@ -4,10 +4,8 @@ namespace Discord.Rest { internal static class InviteHelper { - public static async Task DeleteAsync(IInvite invite, BaseDiscordClient client, - RequestOptions options) - { + public static async Task DeleteAsync(IInvite invite, BaseDiscordClient client, + RequestOptions options) => await client.ApiClient.DeleteInviteAsync(invite.Code, options).ConfigureAwait(false); - } } } diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs index 050f117fb..a4864c88a 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInvite.cs @@ -5,9 +5,19 @@ using Model = Discord.API.Invite; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestInvite : RestEntity, IInvite, IUpdateable { + internal RestInvite(BaseDiscordClient discord, IGuild guild, IChannel channel, string id) + : base(discord, id) + { + Guild = guild; + Channel = channel; + } + + internal IChannel Channel { get; } + internal IGuild Guild { get; } + private string DebuggerDisplay => $"{Url} ({GuildName} / {ChannelName})"; public ChannelType ChannelType { get; private set; } public string ChannelName { get; private set; } public string GuildName { get; private set; } @@ -15,46 +25,13 @@ namespace Discord.Rest public int? MemberCount { get; private set; } public ulong ChannelId { get; private set; } public ulong? GuildId { get; private set; } - internal IChannel Channel { get; } - internal IGuild Guild { get; } public string Code => Id; public string Url => $"{DiscordConfig.InviteUrl}{Code}"; - internal RestInvite(BaseDiscordClient discord, IGuild guild, IChannel channel, string id) - : base(discord, id) - { - Guild = guild; - Channel = channel; - } - internal static RestInvite Create(BaseDiscordClient discord, IGuild guild, IChannel channel, Model model) - { - var entity = new RestInvite(discord, guild, channel, model.Code); - entity.Update(model); - return entity; - } - internal void Update(Model model) - { - GuildId = model.Guild.IsSpecified ? model.Guild.Value.Id : default(ulong?); - ChannelId = model.Channel.Id; - GuildName = model.Guild.IsSpecified ? model.Guild.Value.Name : null; - ChannelName = model.Channel.Name; - MemberCount = model.MemberCount.IsSpecified ? model.MemberCount.Value : null; - PresenceCount = model.PresenceCount.IsSpecified ? model.PresenceCount.Value : null; - ChannelType = (ChannelType)model.Channel.Type; - } - - public async Task UpdateAsync(RequestOptions options = null) - { - var model = await Discord.ApiClient.GetInviteAsync(Code, options).ConfigureAwait(false); - Update(model); - } public Task DeleteAsync(RequestOptions options = null) => InviteHelper.DeleteAsync(this, Discord, options); - public override string ToString() => Url; - private string DebuggerDisplay => $"{Url} ({GuildName} / {ChannelName})"; - IGuild IInvite.Guild { get @@ -63,17 +40,46 @@ namespace Discord.Rest return Guild; if (Channel is IGuildChannel guildChannel) return guildChannel.Guild; //If it fails, it'll still return this exception - throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); + throw new InvalidOperationException( + "Unable to return this entity's parent unless it was fetched through that object."); } } + IChannel IInvite.Channel { get { if (Channel != null) return Channel; - throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); + throw new InvalidOperationException( + "Unable to return this entity's parent unless it was fetched through that object."); } } + + public async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetInviteAsync(Code, options).ConfigureAwait(false); + Update(model); + } + + internal static RestInvite Create(BaseDiscordClient discord, IGuild guild, IChannel channel, Model model) + { + var entity = new RestInvite(discord, guild, channel, model.Code); + entity.Update(model); + return entity; + } + + internal void Update(Model model) + { + GuildId = model.Guild.IsSpecified ? model.Guild.Value.Id : default(ulong?); + ChannelId = model.Channel.Id; + GuildName = model.Guild.IsSpecified ? model.Guild.Value.Name : null; + ChannelName = model.Channel.Name; + MemberCount = model.MemberCount.IsSpecified ? model.MemberCount.Value : null; + PresenceCount = model.PresenceCount.IsSpecified ? model.PresenceCount.Value : null; + ChannelType = (ChannelType)model.Channel.Type; + } + + public override string ToString() => Url; } } diff --git a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs index c7236be58..2ff430f3d 100644 --- a/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs +++ b/src/Discord.Net.Rest/Entities/Invites/RestInviteMetadata.cs @@ -7,25 +7,31 @@ namespace Discord.Rest { private long? _createdAtTicks; + internal RestInviteMetadata(BaseDiscordClient discord, IGuild guild, IChannel channel, string id) + : base(discord, guild, channel, id) + { + } + + public RestUser Inviter { get; private set; } + public bool IsRevoked { get; private set; } public bool IsTemporary { get; private set; } public int? MaxAge { get; private set; } public int? MaxUses { get; private set; } public int? Uses { get; private set; } - public RestUser Inviter { get; private set; } public DateTimeOffset? CreatedAt => DateTimeUtils.FromTicks(_createdAtTicks); - internal RestInviteMetadata(BaseDiscordClient discord, IGuild guild, IChannel channel, string id) - : base(discord, guild, channel, id) - { - } - internal static RestInviteMetadata Create(BaseDiscordClient discord, IGuild guild, IChannel channel, Model model) + IUser IInviteMetadata.Inviter => Inviter; + + internal static RestInviteMetadata Create(BaseDiscordClient discord, IGuild guild, IChannel channel, + Model model) { var entity = new RestInviteMetadata(discord, guild, channel, model.Code); entity.Update(model); return entity; } + internal void Update(Model model) { base.Update(model); @@ -37,7 +43,5 @@ namespace Discord.Rest Uses = model.Uses.IsSpecified ? model.Uses.Value : (int?)null; _createdAtTicks = model.CreatedAt.IsSpecified ? model.CreatedAt.Value.UtcTicks : (long?)null; } - - IUser IInviteMetadata.Inviter => Inviter; } } diff --git a/src/Discord.Net.Rest/Entities/Messages/Attachment.cs b/src/Discord.Net.Rest/Entities/Messages/Attachment.cs index e185234ac..7329dc2ed 100644 --- a/src/Discord.Net.Rest/Entities/Messages/Attachment.cs +++ b/src/Discord.Net.Rest/Entities/Messages/Attachment.cs @@ -3,17 +3,9 @@ using Model = Discord.API.Attachment; namespace Discord { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class Attachment : IAttachment { - public ulong Id { get; } - public string Filename { get; } - public string Url { get; } - public string ProxyUrl { get; } - public int Size { get; } - public int? Height { get; } - public int? Width { get; } - internal Attachment(ulong id, string filename, string url, string proxyUrl, int size, int? height, int? width) { Id = id; @@ -24,14 +16,21 @@ namespace Discord Height = height; Width = width; } - internal static Attachment Create(Model model) - { - return new Attachment(model.Id, model.Filename, model.Url, model.ProxyUrl, model.Size, - model.Height.IsSpecified ? model.Height.Value : (int?)null, - model.Width.IsSpecified ? model.Width.Value : (int?)null); - } - public override string ToString() => Filename; private string DebuggerDisplay => $"{Filename} ({Size} bytes)"; + public ulong Id { get; } + public string Filename { get; } + public string Url { get; } + public string ProxyUrl { get; } + public int Size { get; } + public int? Height { get; } + public int? Width { get; } + + internal static Attachment Create(Model model) => new Attachment(model.Id, model.Filename, model.Url, + model.ProxyUrl, model.Size, + model.Height.IsSpecified ? model.Height.Value : (int?)null, + model.Width.IsSpecified ? model.Width.Value : (int?)null); + + public override string ToString() => Filename; } } diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index 3dc3e74e9..db30ddb40 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -1,16 +1,17 @@ -using Discord.API.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; +using Discord.API.Rest; using Model = Discord.API.Message; namespace Discord.Rest { internal static class MessageHelper { - public static async Task ModifyAsync(IMessage msg, BaseDiscordClient client, Action func, + public static async Task ModifyAsync(IMessage msg, BaseDiscordClient client, + Action func, RequestOptions options) { if (msg.Author.Id != client.CurrentUser.Id) @@ -18,41 +19,41 @@ namespace Discord.Rest var args = new MessageProperties(); func(args); - var apiArgs = new API.Rest.ModifyMessageParams + var apiArgs = new ModifyMessageParams { Content = args.Content, Embed = args.Embed.IsSpecified ? args.Embed.Value.ToModel() : Optional.Create() }; - return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options).ConfigureAwait(false); + return await client.ApiClient.ModifyMessageAsync(msg.Channel.Id, msg.Id, apiArgs, options) + .ConfigureAwait(false); } + public static Task DeleteAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) => DeleteAsync(msg.Channel.Id, msg.Id, client, options); + public static async Task DeleteAsync(ulong channelId, ulong msgId, BaseDiscordClient client, - RequestOptions options) - { + RequestOptions options) => await client.ApiClient.DeleteMessageAsync(channelId, msgId, options).ConfigureAwait(false); - } - public static async Task AddReactionAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options) - { - await client.ApiClient.AddReactionAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name, options).ConfigureAwait(false); - } + public static async Task + AddReactionAsync(IMessage msg, IEmote emote, BaseDiscordClient client, RequestOptions options) => + await client.ApiClient + .AddReactionAsync(msg.Channel.Id, msg.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name, options) + .ConfigureAwait(false); - public static async Task RemoveReactionAsync(IMessage msg, IUser user, IEmote emote, BaseDiscordClient client, RequestOptions options) - { - await client.ApiClient.RemoveReactionAsync(msg.Channel.Id, msg.Id, user.Id, emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name, options).ConfigureAwait(false); - } + public static async Task RemoveReactionAsync(IMessage msg, IUser user, IEmote emote, BaseDiscordClient client, + RequestOptions options) => await client.ApiClient.RemoveReactionAsync(msg.Channel.Id, msg.Id, user.Id, + emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name, options).ConfigureAwait(false); - public static async Task RemoveAllReactionsAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) - { + public static async Task + RemoveAllReactionsAsync(IMessage msg, BaseDiscordClient client, RequestOptions options) => await client.ApiClient.RemoveAllReactionsAsync(msg.Channel.Id, msg.Id, options); - } public static IAsyncEnumerable> GetReactionUsersAsync(IMessage msg, IEmote emote, int? limit, BaseDiscordClient client, RequestOptions options) { Preconditions.NotNull(emote, nameof(emote)); - var emoji = (emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name); + var emoji = emote is Emote e ? $"{e.Name}:{e.Id}" : emote.Name; return new PagedAsyncEnumerable( DiscordConfig.MaxUserReactionsPerBatch, @@ -66,10 +67,11 @@ namespace Discord.Rest if (info.Position != null) args.AfterUserId = info.Position.Value; - var models = await client.ApiClient.GetReactionUsersAsync(msg.Channel.Id, msg.Id, emoji, args, options).ConfigureAwait(false); + var models = await client.ApiClient + .GetReactionUsersAsync(msg.Channel.Id, msg.Id, emoji, args, options).ConfigureAwait(false); return models.Select(x => RestUser.Create(client, x)).ToImmutableArray(); }, - nextPage: (info, lastPage) => + (info, lastPage) => { if (lastPage.Count != DiscordConfig.MaxUsersPerBatch) return false; @@ -79,38 +81,34 @@ namespace Discord.Rest }, count: limit ); - } public static async Task PinAsync(IMessage msg, BaseDiscordClient client, - RequestOptions options) - { + RequestOptions options) => await client.ApiClient.AddPinAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); - } + public static async Task UnpinAsync(IMessage msg, BaseDiscordClient client, - RequestOptions options) - { + RequestOptions options) => await client.ApiClient.RemovePinAsync(msg.Channel.Id, msg.Id, options).ConfigureAwait(false); - } - public static ImmutableArray ParseTags(string text, IMessageChannel channel, IGuild guild, IReadOnlyCollection userMentions) + public static ImmutableArray ParseTags(string text, IMessageChannel channel, IGuild guild, + IReadOnlyCollection userMentions) { var tags = ImmutableArray.CreateBuilder(); - - int index = 0; + + var index = 0; while (true) { index = text.IndexOf('<', index); if (index == -1) break; - int endIndex = text.IndexOf('>', index + 1); + var endIndex = text.IndexOf('>', index + 1); if (endIndex == -1) break; - string content = text.Substring(index, endIndex - index + 1); + var content = text.Substring(index, endIndex - index + 1); - if (MentionUtils.TryParseUser(content, out ulong id)) + if (MentionUtils.TryParseUser(content, out var id)) { IUser mentionedUser = null; foreach (var mention in userMentions) - { if (mention.Id == id) { mentionedUser = channel?.GetUserAsync(id, CacheMode.CacheOnly).GetAwaiter().GetResult(); @@ -118,7 +116,7 @@ namespace Discord.Rest mentionedUser = mention; break; } - } + tags.Add(new Tag(TagType.UserMention, index, content.Length, id, mentionedUser)); } else if (MentionUtils.TryParseChannel(content, out id)) @@ -142,6 +140,7 @@ namespace Discord.Rest index = index + 1; continue; } + index = endIndex + 1; } @@ -153,7 +152,8 @@ namespace Discord.Rest var tagIndex = FindIndex(tags, index); if (tagIndex.HasValue) - tags.Insert(tagIndex.Value, new Tag(TagType.EveryoneMention, index, "@everyone".Length, 0, null)); + tags.Insert(tagIndex.Value, + new Tag(TagType.EveryoneMention, index, "@everyone".Length, 0, null)); index++; } @@ -171,44 +171,40 @@ namespace Discord.Rest return tags.ToImmutable(); } + private static int? FindIndex(IReadOnlyList tags, int index) { - int i = 0; + var i = 0; for (; i < tags.Count; i++) { var tag = tags[i]; if (index < tag.Index) break; //Position before this tag } + if (i > 0 && index < tags[i - 1].Index + tags[i - 1].Length) return null; //Overlaps tag before this return i; } - public static ImmutableArray FilterTagsByKey(TagType type, ImmutableArray tags) - { - return tags - .Where(x => x.Type == type) - .Select(x => x.Key) - .ToImmutableArray(); - } - public static ImmutableArray FilterTagsByValue(TagType type, ImmutableArray tags) - { - return tags - .Where(x => x.Type == type) - .Select(x => (T)x.Value) - .Where(x => x != null) - .ToImmutableArray(); - } + + public static ImmutableArray FilterTagsByKey(TagType type, ImmutableArray tags) => tags + .Where(x => x.Type == type) + .Select(x => x.Key) + .ToImmutableArray(); + + public static ImmutableArray FilterTagsByValue(TagType type, ImmutableArray tags) => tags + .Where(x => x.Type == type) + .Select(x => (T)x.Value) + .Where(x => x != null) + .ToImmutableArray(); public static MessageSource GetSource(Model msg) { if (msg.Type != MessageType.Default) return MessageSource.System; - else if (msg.WebhookId.IsSpecified) + if (msg.WebhookId.IsSpecified) return MessageSource.Webhook; - else if (msg.Author.GetValueOrDefault()?.Bot.GetValueOrDefault(false) == true) - return MessageSource.Bot; - return MessageSource.User; + return msg.Author.GetValueOrDefault()?.Bot.GetValueOrDefault(false) == true ? MessageSource.Bot : MessageSource.User; } } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs index 590886886..554aaf0c5 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestMessage.cs @@ -11,8 +11,21 @@ namespace Discord.Rest { private long _timestampTicks; - public IMessageChannel Channel { get; } + internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, + MessageSource source) + : base(discord, id) + { + Channel = channel; + Author = author; + Source = source; + } + public IUser Author { get; } + public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); + public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); + public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); + + public IMessageChannel Channel { get; } public MessageSource Source { get; } public string Content { get; private set; } @@ -21,29 +34,35 @@ namespace Discord.Rest public virtual bool IsTTS => false; public virtual bool IsPinned => false; public virtual DateTimeOffset? EditedTimestamp => null; - public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); - public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedChannelIds => ImmutableArray.Create(); public virtual IReadOnlyCollection MentionedRoleIds => ImmutableArray.Create(); - public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); - internal RestMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) - : base(discord, id) + public Task DeleteAsync(RequestOptions options = null) + => MessageHelper.DeleteAsync(this, Discord, options); + + MessageType IMessage.Type => MessageType.Default; + IUser IMessage.Author => Author; + IReadOnlyCollection IMessage.Attachments => Attachments; + IReadOnlyCollection IMessage.Embeds => Embeds; + IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); + + public async Task UpdateAsync(RequestOptions options = null) { - Channel = channel; - Author = author; - Source = source; + var model = await Discord.ApiClient.GetChannelMessageAsync(Channel.Id, Id, options).ConfigureAwait(false); + Update(model); } - internal static RestMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) + + internal static RestMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, + Model model) { if (model.Type == MessageType.Default) return RestUserMessage.Create(discord, channel, author, model); - else - return RestSystemMessage.Create(discord, channel, author, model); + return RestSystemMessage.Create(discord, channel, author, model); } + internal virtual void Update(Model model) { if (model.Timestamp.IsSpecified) @@ -53,20 +72,6 @@ namespace Discord.Rest Content = model.Content.Value; } - public async Task UpdateAsync(RequestOptions options = null) - { - var model = await Discord.ApiClient.GetChannelMessageAsync(Channel.Id, Id, options).ConfigureAwait(false); - Update(model); - } - public Task DeleteAsync(RequestOptions options = null) - => MessageHelper.DeleteAsync(this, Discord, options); - public override string ToString() => Content; - - MessageType IMessage.Type => MessageType.Default; - IUser IMessage.Author => Author; - IReadOnlyCollection IMessage.Attachments => Attachments; - IReadOnlyCollection IMessage.Embeds => Embeds; - IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs b/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs index 6d3f72419..3a24e463c 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestReaction.cs @@ -4,16 +4,17 @@ namespace Discord.Rest { public class RestReaction : IReaction { - public IEmote Emote { get; } - public int Count { get; } - public bool Me { get; } - internal RestReaction(IEmote emote, int count, bool me) { Emote = emote; Count = count; Me = me; } + + public int Count { get; } + public bool Me { get; } + public IEmote Emote { get; } + internal static RestReaction Create(Model model) { IEmote emote; diff --git a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs index b9dda08ae..89a46b43f 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestSystemMessage.cs @@ -3,28 +3,30 @@ using Model = Discord.API.Message; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestSystemMessage : RestMessage, ISystemMessage { - public MessageType Type { get; private set; } - internal RestSystemMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author) : base(discord, id, channel, author, MessageSource.System) { } - internal new static RestSystemMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) + + private string DebuggerDisplay => $"{Author}: {Content} ({Id}, {Type})"; + public MessageType Type { get; private set; } + + internal new static RestSystemMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, + Model model) { var entity = new RestSystemMessage(discord, model.Id, channel, author); entity.Update(model); return entity; } + internal override void Update(Model model) { base.Update(model); Type = model.Type; } - - private string DebuggerDisplay => $"{Author}: {Content} ({Id}, {Type})"; } } diff --git a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs index 7354cc4af..66e2a1c13 100644 --- a/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs +++ b/src/Discord.Net.Rest/Entities/Messages/RestUserMessage.cs @@ -8,32 +8,85 @@ using Model = Discord.API.Message; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestUserMessage : RestMessage, IUserMessage { - private bool _isMentioningEveryone, _isTTS, _isPinned; - private long? _editedTimestampTicks; private ImmutableArray _attachments; + private long? _editedTimestampTicks; private ImmutableArray _embeds; - private ImmutableArray _tags; + private bool _isMentioningEveryone, _isTTS, _isPinned; private ImmutableArray _reactions; - + private ImmutableArray _tags; + + internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, + MessageSource source) + : base(discord, id, channel, author, source) + { + } + + public override IReadOnlyCollection Attachments => _attachments; + public override IReadOnlyCollection Embeds => _embeds; + + public override IReadOnlyCollection MentionedUsers => + MessageHelper.FilterTagsByValue(TagType.UserMention, _tags); + + private string DebuggerDisplay => + $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; + public override bool IsTTS => _isTTS; public override bool IsPinned => _isPinned; public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); - public override IReadOnlyCollection Attachments => _attachments; - public override IReadOnlyCollection Embeds => _embeds; - public override IReadOnlyCollection MentionedChannelIds => MessageHelper.FilterTagsByKey(TagType.ChannelMention, _tags); - public override IReadOnlyCollection MentionedRoleIds => MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags); - public override IReadOnlyCollection MentionedUsers => MessageHelper.FilterTagsByValue(TagType.UserMention, _tags); + + public override IReadOnlyCollection MentionedChannelIds => + MessageHelper.FilterTagsByKey(TagType.ChannelMention, _tags); + + public override IReadOnlyCollection MentionedRoleIds => + MessageHelper.FilterTagsByKey(TagType.RoleMention, _tags); + public override IReadOnlyCollection Tags => _tags; - public IReadOnlyDictionary Reactions => _reactions.ToDictionary(x => x.Emote, x => new ReactionMetadata { ReactionCount = x.Count, IsMe = x.Me }); - internal RestUserMessage(BaseDiscordClient discord, ulong id, IMessageChannel channel, IUser author, MessageSource source) - : base(discord, id, channel, author, source) + public IReadOnlyDictionary Reactions => _reactions.ToDictionary(x => x.Emote, x => + new ReactionMetadata + { + ReactionCount = x.Count, + IsMe = x.Me + }); + + public async Task ModifyAsync(Action func, RequestOptions options = null) { + var model = await MessageHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); + Update(model); } - internal static new RestUserMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, Model model) + + public Task AddReactionAsync(IEmote emote, RequestOptions options = null) + => MessageHelper.AddReactionAsync(this, emote, Discord, options); + + public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null) + => MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options); + + public Task RemoveAllReactionsAsync(RequestOptions options = null) + => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); + + public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, + RequestOptions options = null) + => MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options); + + + public Task PinAsync(RequestOptions options = null) + => MessageHelper.PinAsync(this, Discord, options); + + public Task UnpinAsync(RequestOptions options = null) + => MessageHelper.UnpinAsync(this, Discord, options); + + public string Resolve(TagHandling userHandling = TagHandling.Name, + TagHandling channelHandling = TagHandling.Name, + TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, + TagHandling emojiHandling = TagHandling.Name) + => MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, + emojiHandling); + + internal new static RestUserMessage Create(BaseDiscordClient discord, IMessageChannel channel, IUser author, + Model model) { var entity = new RestUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); entity.Update(model); @@ -59,7 +112,7 @@ namespace Discord.Rest if (value.Length > 0) { var attachments = ImmutableArray.CreateBuilder(value.Length); - for (int i = 0; i < value.Length; i++) + for (var i = 0; i < value.Length; i++) attachments.Add(Attachment.Create(value[i])); _attachments = attachments.ToImmutable(); } @@ -73,7 +126,7 @@ namespace Discord.Rest if (value.Length > 0) { var embeds = ImmutableArray.CreateBuilder(value.Length); - for (int i = 0; i < value.Length; i++) + for (var i = 0; i < value.Length; i++) embeds.Add(value[i].ToEntity()); _embeds = embeds.ToImmutable(); } @@ -81,19 +134,20 @@ namespace Discord.Rest _embeds = ImmutableArray.Create(); } - ImmutableArray mentions = ImmutableArray.Create(); + var mentions = ImmutableArray.Create(); if (model.UserMentions.IsSpecified) { var value = model.UserMentions.Value; if (value.Length > 0) { var newMentions = ImmutableArray.CreateBuilder(value.Length); - for (int i = 0; i < value.Length; i++) + for (var i = 0; i < value.Length; i++) { var val = value[i]; if (val.Object != null) newMentions.Add(RestUser.Create(Discord, val.Object)); } + mentions = newMentions.ToImmutable(); } } @@ -104,7 +158,7 @@ namespace Discord.Rest if (value.Length > 0) { var reactions = ImmutableArray.CreateBuilder(value.Length); - for (int i = 0; i < value.Length; i++) + for (var i = 0; i < value.Length; i++) reactions.Add(RestReaction.Create(value[i])); _reactions = reactions.ToImmutable(); } @@ -118,40 +172,19 @@ namespace Discord.Rest { var text = model.Content.Value; var guildId = (Channel as IGuildChannel)?.GuildId; - var guild = guildId != null ? (Discord as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result : null; + var guild = guildId != null + ? (Discord as IDiscordClient).GetGuildAsync(guildId.Value, CacheMode.CacheOnly).Result + : null; _tags = MessageHelper.ParseTags(text, null, guild, mentions); model.Content = text; } } - public async Task ModifyAsync(Action func, RequestOptions options = null) - { - var model = await MessageHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); - Update(model); - } - - public Task AddReactionAsync(IEmote emote, RequestOptions options = null) - => MessageHelper.AddReactionAsync(this, emote, Discord, options); - public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null) - => MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options); - public Task RemoveAllReactionsAsync(RequestOptions options = null) - => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); - public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null) - => MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options); - - - public Task PinAsync(RequestOptions options = null) - => MessageHelper.PinAsync(this, Discord, options); - public Task UnpinAsync(RequestOptions options = null) - => MessageHelper.UnpinAsync(this, Discord, options); - - public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, - TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) - => MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); - public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, - TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) - => MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); - - private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; + public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, + TagHandling channelHandling = TagHandling.Name, + TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, + TagHandling emojiHandling = TagHandling.Name) + => MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, + emojiHandling); } } diff --git a/src/Discord.Net.Rest/Entities/RestApplication.cs b/src/Discord.Net.Rest/Entities/RestApplication.cs index 827c33cf7..ebefc1bfa 100644 --- a/src/Discord.Net.Rest/Entities/RestApplication.cs +++ b/src/Discord.Net.Rest/Entities/RestApplication.cs @@ -5,11 +5,18 @@ using Model = Discord.API.Application; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestApplication : RestEntity, IApplication { protected string _iconId; - + + internal RestApplication(BaseDiscordClient discord, ulong id) + : base(discord, id) + { + } + + private string DebuggerDisplay => $"{Name} ({Id})"; + public string Name { get; private set; } public string Description { get; private set; } public string[] RPCOrigins { get; private set; } @@ -20,18 +27,15 @@ namespace Discord.Rest public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public string IconUrl => CDN.GetApplicationIconUrl(Id, _iconId); - internal RestApplication(BaseDiscordClient discord, ulong id) - : base(discord, id) - { - } internal static RestApplication Create(BaseDiscordClient discord, Model model) { var entity = new RestApplication(discord, model.Id); entity.Update(model); return entity; } + internal void Update(Model model) - { + { Description = model.Description; RPCOrigins = model.RPCOrigins; Name = model.Name; @@ -52,6 +56,5 @@ namespace Discord.Rest } public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id})"; } } diff --git a/src/Discord.Net.Rest/Entities/RestEntity.cs b/src/Discord.Net.Rest/Entities/RestEntity.cs index 2b1bb888c..1959516a6 100644 --- a/src/Discord.Net.Rest/Entities/RestEntity.cs +++ b/src/Discord.Net.Rest/Entities/RestEntity.cs @@ -5,13 +5,13 @@ namespace Discord.Rest public abstract class RestEntity : IEntity where T : IEquatable { - internal BaseDiscordClient Discord { get; } - public T Id { get; } - internal RestEntity(BaseDiscordClient discord, T id) { Discord = discord; Id = id; } + + internal BaseDiscordClient Discord { get; } + public T Id { get; } } } diff --git a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs index 486f41b9e..d606a0266 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RestRole.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RestRole.cs @@ -5,10 +5,18 @@ using Model = Discord.API.Role; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestRole : RestEntity, IRole { + internal RestRole(BaseDiscordClient discord, IGuild guild, ulong id) + : base(discord, id) + { + Guild = guild; + } + internal IGuild Guild { get; } + public bool IsEveryone => Id == Guild.Id; + private string DebuggerDisplay => $"{Name} ({Id})"; public Color Color { get; private set; } public bool IsHoisted { get; private set; } public bool IsManaged { get; private set; } @@ -18,20 +26,38 @@ namespace Discord.Rest public int Position { get; private set; } public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - public bool IsEveryone => Id == Guild.Id; public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id); - internal RestRole(BaseDiscordClient discord, IGuild guild, ulong id) - : base(discord, id) + public async Task ModifyAsync(Action func, RequestOptions options = null) { - Guild = guild; + var model = await RoleHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); + Update(model); + } + + public Task DeleteAsync(RequestOptions options = null) + => RoleHelper.DeleteAsync(this, Discord, options); + + public int CompareTo(IRole role) => RoleUtils.Compare(this, role); + + //IRole + IGuild IRole.Guild + { + get + { + if (Guild != null) + return Guild; + throw new InvalidOperationException( + "Unable to return this entity's parent unless it was fetched through that object."); + } } + internal static RestRole Create(BaseDiscordClient discord, IGuild guild, Model model) { var entity = new RestRole(discord, guild, model.Id); entity.Update(model); return entity; } + internal void Update(Model model) { Name = model.Name; @@ -43,28 +69,6 @@ namespace Discord.Rest Permissions = new GuildPermissions(model.Permissions); } - public async Task ModifyAsync(Action func, RequestOptions options = null) - { - var model = await RoleHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); - Update(model); - } - public Task DeleteAsync(RequestOptions options = null) - => RoleHelper.DeleteAsync(this, Discord, options); - - public int CompareTo(IRole role) => RoleUtils.Compare(this, role); - public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id})"; - - //IRole - IGuild IRole.Guild - { - get - { - if (Guild != null) - return Guild; - throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); - } - } } } diff --git a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs index d570f078b..6d71f581d 100644 --- a/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs +++ b/src/Discord.Net.Rest/Entities/Roles/RoleHelper.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Discord.API.Rest; using Model = Discord.API.Role; using BulkParams = Discord.API.Rest.ModifyGuildRolesParams; @@ -9,16 +10,15 @@ namespace Discord.Rest { //General public static async Task DeleteAsync(IRole role, BaseDiscordClient client, - RequestOptions options) - { - await client.ApiClient.DeleteGuildRoleAsync(role.Guild.Id, role.Id, options).ConfigureAwait(false); - } - public static async Task ModifyAsync(IRole role, BaseDiscordClient client, + RequestOptions options) => await client.ApiClient.DeleteGuildRoleAsync(role.Guild.Id, role.Id, options) + .ConfigureAwait(false); + + public static async Task ModifyAsync(IRole role, BaseDiscordClient client, Action func, RequestOptions options) { var args = new RoleProperties(); func(args); - var apiArgs = new API.Rest.ModifyGuildRoleParams + var apiArgs = new ModifyGuildRoleParams { Color = args.Color.IsSpecified ? args.Color.Value.RawValue : Optional.Create(), Hoist = args.Hoist, @@ -26,14 +26,16 @@ namespace Discord.Rest Name = args.Name, Permissions = args.Permissions.IsSpecified ? args.Permissions.Value.RawValue : Optional.Create() }; - var model = await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, apiArgs, options).ConfigureAwait(false); + var model = await client.ApiClient.ModifyGuildRoleAsync(role.Guild.Id, role.Id, apiArgs, options) + .ConfigureAwait(false); if (args.Position.IsSpecified) { - var bulkArgs = new[] { new BulkParams(role.Id, args.Position.Value) }; + var bulkArgs = new[] {new BulkParams(role.Id, args.Position.Value)}; await client.ApiClient.ModifyGuildRolesAsync(role.Guild.Id, bulkArgs, options).ConfigureAwait(false); model.Position = args.Position.Value; } + return model; } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestConnection.cs b/src/Discord.Net.Rest/Entities/Users/RestConnection.cs index b8b83be3e..2204aac17 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestConnection.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestConnection.cs @@ -5,16 +5,11 @@ using Model = Discord.API.Connection; namespace Discord { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestConnection : IConnection { - public string Id { get; } - public string Type { get; } - public string Name { get; } - public bool IsRevoked { get; } - public IReadOnlyCollection IntegrationIds { get; } - - internal RestConnection(string id, string type, string name, bool isRevoked, IReadOnlyCollection integrationIds) + internal RestConnection(string id, string type, string name, bool isRevoked, + IReadOnlyCollection integrationIds) { Id = id; Type = type; @@ -23,12 +18,17 @@ namespace Discord IntegrationIds = integrationIds; } - internal static RestConnection Create(Model model) - { - return new RestConnection(model.Id, model.Type, model.Name, model.Revoked, model.Integrations.ToImmutableArray()); - } - public override string ToString() => Name; private string DebuggerDisplay => $"{Name} ({Id}, {Type}{(IsRevoked ? ", Revoked" : "")})"; + public string Id { get; } + public string Type { get; } + public string Name { get; } + public bool IsRevoked { get; } + public IReadOnlyCollection IntegrationIds { get; } + + internal static RestConnection Create(Model model) => new RestConnection(model.Id, model.Type, model.Name, + model.Revoked, model.Integrations.ToImmutableArray()); + + public override string ToString() => Name; } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs index 951bd2e7c..41b0c37f6 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGroupUser.cs @@ -10,13 +10,7 @@ namespace Discord.Rest : base(discord, id) { } - internal new static RestGroupUser Create(BaseDiscordClient discord, Model model) - { - var entity = new RestGroupUser(discord, model.Id); - entity.Update(model); - return entity; - } - + //IVoiceState bool IVoiceState.IsDeafened => false; bool IVoiceState.IsMuted => false; @@ -25,5 +19,12 @@ namespace Discord.Rest bool IVoiceState.IsSuppressed => false; IVoiceChannel IVoiceState.VoiceChannel => null; string IVoiceState.VoiceSessionId => null; + + internal new static RestGroupUser Create(BaseDiscordClient discord, Model model) + { + var entity = new RestGroupUser(discord, model.Id); + entity.Update(model); + return entity; + } } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs index e571f8f73..ad907c79b 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs @@ -14,64 +14,35 @@ namespace Discord.Rest private long? _joinedAtTicks; private ImmutableArray _roleIds; + internal RestGuildUser(BaseDiscordClient discord, IGuild guild, ulong id) + : base(discord, id) + { + Guild = guild; + } + + internal IGuild Guild { get; } + public string Nickname { get; private set; } - internal IGuild Guild { get; private set; } public bool IsDeafened { get; private set; } public bool IsMuted { get; private set; } public ulong GuildId => Guild.Id; + public GuildPermissions GuildPermissions { get { if (!Guild.Available) - throw new InvalidOperationException("Resolving permissions requires the parent guild to be downloaded."); + throw new InvalidOperationException( + "Resolving permissions requires the parent guild to be downloaded."); return new GuildPermissions(Permissions.ResolveGuild(Guild, this)); } } + public IReadOnlyCollection RoleIds => _roleIds; public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); - internal RestGuildUser(BaseDiscordClient discord, IGuild guild, ulong id) - : base(discord, id) - { - Guild = guild; - } - internal static RestGuildUser Create(BaseDiscordClient discord, IGuild guild, Model model) - { - var entity = new RestGuildUser(discord, guild, model.User.Id); - entity.Update(model); - return entity; - } - internal void Update(Model model) - { - base.Update(model.User); - if (model.JoinedAt.IsSpecified) - _joinedAtTicks = model.JoinedAt.Value.UtcTicks; - if (model.Nick.IsSpecified) - Nickname = model.Nick.Value; - if (model.Deaf.IsSpecified) - IsDeafened = model.Deaf.Value; - if (model.Mute.IsSpecified) - IsMuted = model.Mute.Value; - if (model.Roles.IsSpecified) - UpdateRoles(model.Roles.Value); - } - private void UpdateRoles(ulong[] roleIds) - { - var roles = ImmutableArray.CreateBuilder(roleIds.Length + 1); - roles.Add(Guild.Id); - for (int i = 0; i < roleIds.Length; i++) - roles.Add(roleIds[i]); - _roleIds = roles.ToImmutable(); - } - - public override async Task UpdateAsync(RequestOptions options = null) - { - var model = await Discord.ApiClient.GetGuildMemberAsync(GuildId, Id, options).ConfigureAwait(false); - Update(model); - } public async Task ModifyAsync(Action func, RequestOptions options = null) { var args = await UserHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); @@ -86,17 +57,22 @@ namespace Discord.Rest else if (args.RoleIds.IsSpecified) UpdateRoles(args.RoleIds.Value.ToArray()); } + public Task KickAsync(string reason = null, RequestOptions options = null) => UserHelper.KickAsync(this, Discord, reason, options); + /// public Task AddRoleAsync(IRole role, RequestOptions options = null) - => AddRolesAsync(new[] { role }, options); + => AddRolesAsync(new[] {role}, options); + /// public Task AddRolesAsync(IEnumerable roles, RequestOptions options = null) => UserHelper.AddRolesAsync(this, Discord, roles, options); + /// public Task RemoveRoleAsync(IRole role, RequestOptions options = null) - => RemoveRolesAsync(new[] { role }, options); + => RemoveRolesAsync(new[] {role}, options); + /// public Task RemoveRolesAsync(IEnumerable roles, RequestOptions options = null) => UserHelper.RemoveRolesAsync(this, Discord, roles, options); @@ -114,7 +90,8 @@ namespace Discord.Rest { if (Guild != null) return Guild; - throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); + throw new InvalidOperationException( + "Unable to return this entity's parent unless it was fetched through that object."); } } @@ -124,5 +101,42 @@ namespace Discord.Rest bool IVoiceState.IsSuppressed => false; IVoiceChannel IVoiceState.VoiceChannel => null; string IVoiceState.VoiceSessionId => null; + + public override async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetGuildMemberAsync(GuildId, Id, options).ConfigureAwait(false); + Update(model); + } + + internal static RestGuildUser Create(BaseDiscordClient discord, IGuild guild, Model model) + { + var entity = new RestGuildUser(discord, guild, model.User.Id); + entity.Update(model); + return entity; + } + + internal void Update(Model model) + { + base.Update(model.User); + if (model.JoinedAt.IsSpecified) + _joinedAtTicks = model.JoinedAt.Value.UtcTicks; + if (model.Nick.IsSpecified) + Nickname = model.Nick.Value; + if (model.Deaf.IsSpecified) + IsDeafened = model.Deaf.Value; + if (model.Mute.IsSpecified) + IsMuted = model.Mute.Value; + if (model.Roles.IsSpecified) + UpdateRoles(model.Roles.Value); + } + + private void UpdateRoles(ulong[] roleIds) + { + var roles = ImmutableArray.CreateBuilder(roleIds.Length + 1); + roles.Add(Guild.Id); + for (var i = 0; i < roleIds.Length; i++) + roles.Add(roleIds[i]); + _roleIds = roles.ToImmutable(); + } } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs index ab5ec4a3b..c9e1f59f7 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestSelfUser.cs @@ -8,20 +8,30 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestSelfUser : RestUser, ISelfUser { + internal RestSelfUser(BaseDiscordClient discord, ulong id) + : base(discord, id) + { + } + public string Email { get; private set; } public bool IsVerified { get; private set; } public bool IsMfaEnabled { get; private set; } - internal RestSelfUser(BaseDiscordClient discord, ulong id) - : base(discord, id) + public async Task ModifyAsync(Action func, RequestOptions options = null) { + if (Id != Discord.CurrentUser.Id) + throw new InvalidOperationException("Unable to modify this object using a different token."); + var model = await UserHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); + Update(model); } + internal new static RestSelfUser Create(BaseDiscordClient discord, Model model) { var entity = new RestSelfUser(discord, model.Id); entity.Update(model); return entity; } + internal override void Update(Model model) { base.Update(model); @@ -41,13 +51,5 @@ namespace Discord.Rest throw new InvalidOperationException("Unable to update this object using a different token."); Update(model); } - - public async Task ModifyAsync(Action func, RequestOptions options = null) - { - if (Id != Discord.CurrentUser.Id) - throw new InvalidOperationException("Unable to modify this object using a different token."); - var model = await UserHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); - Update(model); - } } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index c484986b1..e9a2880f1 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -5,9 +5,22 @@ using Model = Discord.API.User; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestUser : RestEntity, IUser, IUpdateable { + internal RestUser(BaseDiscordClient discord, ulong id) + : base(discord, id) + { + } + + private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; + + public virtual async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetUserAsync(Id, options).ConfigureAwait(false); + Update(model); + } + public bool IsBot { get; private set; } public string Username { get; private set; } public ushort DiscriminatorValue { get; private set; } @@ -20,22 +33,27 @@ namespace Discord.Rest public virtual UserStatus Status => UserStatus.Offline; public virtual bool IsWebhook => false; - internal RestUser(BaseDiscordClient discord, ulong id) - : base(discord, id) - { - } + public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) + => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); + + public string GetDefaultAvatarUrl() + => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); + + //IUser + async Task IUser.GetOrCreateDMChannelAsync(RequestOptions options) + => await GetOrCreateDMChannelAsync(options); + internal static RestUser Create(BaseDiscordClient discord, Model model) => Create(discord, null, model, null); + internal static RestUser Create(BaseDiscordClient discord, IGuild guild, Model model, ulong? webhookId) { RestUser entity; - if (webhookId.HasValue) - entity = new RestWebhookUser(discord, guild, model.Id, webhookId.Value); - else - entity = new RestUser(discord, model.Id); + entity = webhookId.HasValue ? new RestWebhookUser(discord, guild, model.Id, webhookId.Value) : new RestUser(discord, model.Id); entity.Update(model); return entity; } + internal virtual void Update(Model model) { if (model.Avatar.IsSpecified) @@ -48,26 +66,9 @@ namespace Discord.Rest Username = model.Username.Value; } - public virtual async Task UpdateAsync(RequestOptions options = null) - { - var model = await Discord.ApiClient.GetUserAsync(Id, options).ConfigureAwait(false); - Update(model); - } - public Task GetOrCreateDMChannelAsync(RequestOptions options = null) => UserHelper.CreateDMChannelAsync(this, Discord, options); - public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) - => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); - - public string GetDefaultAvatarUrl() - => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); - public override string ToString() => $"{Username}#{Discriminator}"; - private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; - - //IUser - async Task IUser.GetOrCreateDMChannelAsync(RequestOptions options) - => await GetOrCreateDMChannelAsync(options); } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs index bb44f2777..5a61b9b7d 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs @@ -10,25 +10,19 @@ namespace Discord.Rest [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class RestWebhookUser : RestUser, IWebhookUser { - public ulong WebhookId { get; } - internal IGuild Guild { get; } - - public override bool IsWebhook => true; - public ulong GuildId => Guild.Id; - internal RestWebhookUser(BaseDiscordClient discord, IGuild guild, ulong id, ulong webhookId) : base(discord, id) { Guild = guild; WebhookId = webhookId; } - internal static RestWebhookUser Create(BaseDiscordClient discord, IGuild guild, Model model, ulong webhookId) - { - var entity = new RestWebhookUser(discord, guild, model.Id, webhookId); - entity.Update(model); - return entity; - } - + + internal IGuild Guild { get; } + public ulong WebhookId { get; } + + public override bool IsWebhook => true; + public ulong GuildId => Guild.Id; + //IGuildUser IGuild IGuildUser.Guild { @@ -36,40 +30,36 @@ namespace Discord.Rest { if (Guild != null) return Guild; - throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); + throw new InvalidOperationException( + "Unable to return this entity's parent unless it was fetched through that object."); } } + IReadOnlyCollection IGuildUser.RoleIds => ImmutableArray.Create(); DateTimeOffset? IGuildUser.JoinedAt => null; string IGuildUser.Nickname => null; GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; - ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); - Task IGuildUser.KickAsync(string reason, RequestOptions options) - { + ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => + Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); + + Task IGuildUser.KickAsync(string reason, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be kicked."); - } - Task IGuildUser.ModifyAsync(Action func, RequestOptions options) - { + + Task IGuildUser.ModifyAsync(Action func, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be modified."); - } - Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) - { + Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } - Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) - { + + Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } - Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) - { + + Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } - Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) - { + + Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } //IVoiceState bool IVoiceState.IsDeafened => false; @@ -79,5 +69,12 @@ namespace Discord.Rest bool IVoiceState.IsSuppressed => false; IVoiceChannel IVoiceState.VoiceChannel => null; string IVoiceState.VoiceSessionId => null; + + internal static RestWebhookUser Create(BaseDiscordClient discord, IGuild guild, Model model, ulong webhookId) + { + var entity = new RestWebhookUser(discord, guild, model.Id, webhookId); + entity.Update(model); + return entity; + } } } diff --git a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs index dfb81ff2c..6cc9b4455 100644 --- a/src/Discord.Net.Rest/Entities/Users/UserHelper.cs +++ b/src/Discord.Net.Rest/Entities/Users/UserHelper.cs @@ -1,21 +1,22 @@ -using Discord.API.Rest; -using System; +using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using Discord.API.Rest; using Model = Discord.API.User; using ImageModel = Discord.API.Image; -using System.Linq; namespace Discord.Rest { internal static class UserHelper { - public static async Task ModifyAsync(ISelfUser user, BaseDiscordClient client, Action func, + public static async Task ModifyAsync(ISelfUser user, BaseDiscordClient client, + Action func, RequestOptions options) { var args = new SelfUserProperties(); func(args); - var apiArgs = new API.Rest.ModifyCurrentUserParams + var apiArgs = new ModifyCurrentUserParams { Avatar = args.Avatar.IsSpecified ? args.Avatar.Value?.ToModel() : Optional.Create(), Username = args.Username @@ -26,12 +27,14 @@ namespace Discord.Rest return await client.ApiClient.ModifySelfAsync(apiArgs, options).ConfigureAwait(false); } - public static async Task ModifyAsync(IGuildUser user, BaseDiscordClient client, Action func, + + public static async Task ModifyAsync(IGuildUser user, BaseDiscordClient client, + Action func, RequestOptions options) { var args = new GuildUserProperties(); func(args); - var apiArgs = new API.Rest.ModifyGuildMemberParams + var apiArgs = new ModifyGuildMemberParams { Deaf = args.Deaf, Mute = args.Mute, @@ -56,30 +59,32 @@ namespace Discord.Rest if (apiArgs.Nickname.IsSpecified && apiArgs.Nickname.Value == null) apiArgs.Nickname = new Optional(string.Empty); - await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, apiArgs, options).ConfigureAwait(false); + await client.ApiClient.ModifyGuildMemberAsync(user.GuildId, user.Id, apiArgs, options) + .ConfigureAwait(false); return args; } public static async Task KickAsync(IGuildUser user, BaseDiscordClient client, - string reason, RequestOptions options) - { - await client.ApiClient.RemoveGuildMemberAsync(user.GuildId, user.Id, reason, options).ConfigureAwait(false); - } + string reason, RequestOptions options) => await client.ApiClient + .RemoveGuildMemberAsync(user.GuildId, user.Id, reason, options).ConfigureAwait(false); public static async Task CreateDMChannelAsync(IUser user, BaseDiscordClient client, RequestOptions options) { var args = new CreateDMChannelParams(user.Id); - return RestDMChannel.Create(client, await client.ApiClient.CreateDMChannelAsync(args, options).ConfigureAwait(false)); + return RestDMChannel.Create(client, + await client.ApiClient.CreateDMChannelAsync(args, options).ConfigureAwait(false)); } - public static async Task AddRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable roles, RequestOptions options) + public static async Task AddRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable roles, + RequestOptions options) { foreach (var role in roles) await client.ApiClient.AddRoleAsync(user.Guild.Id, user.Id, role.Id, options); } - public static async Task RemoveRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable roles, RequestOptions options) + public static async Task RemoveRolesAsync(IGuildUser user, BaseDiscordClient client, IEnumerable roles, + RequestOptions options) { foreach (var role in roles) await client.ApiClient.RemoveRoleAsync(user.Guild.Id, user.Id, role.Id, options); diff --git a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs index 47cc50a9c..5fafe0f41 100644 --- a/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs +++ b/src/Discord.Net.Rest/Entities/Webhooks/RestWebhook.cs @@ -5,22 +5,9 @@ using Model = Discord.API.Webhook; namespace Discord.Rest { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class RestWebhook : RestEntity, IWebhook, IUpdateable { - internal IGuild Guild { get; private set; } - internal ITextChannel Channel { get; private set; } - - public ulong ChannelId { get; } - public string Token { get; } - - public string Name { get; private set; } - public string AvatarId { get; private set; } - public ulong? GuildId { get; private set; } - public IUser Creator { get; private set; } - - public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - internal RestWebhook(BaseDiscordClient discord, IGuild guild, ulong id, string token, ulong channelId) : base(discord, id) { @@ -28,18 +15,58 @@ namespace Discord.Rest Token = token; ChannelId = channelId; } + internal RestWebhook(BaseDiscordClient discord, ITextChannel channel, ulong id, string token, ulong channelId) : this(discord, channel.Guild, id, token, channelId) { Channel = channel; } + internal IGuild Guild { get; } + internal ITextChannel Channel { get; } + private string DebuggerDisplay => $"Webhook: {Name} ({Id})"; + + public async Task UpdateAsync(RequestOptions options = null) + { + var model = await Discord.ApiClient.GetWebhookAsync(Id, options).ConfigureAwait(false); + Update(model); + } + + public ulong ChannelId { get; } + public string Token { get; } + + public string Name { get; private set; } + public string AvatarId { get; private set; } + public ulong? GuildId { get; private set; } + public IUser Creator { get; private set; } + + public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + + public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) + => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); + + public Task DeleteAsync(RequestOptions options = null) + => WebhookHelper.DeleteAsync(this, Discord, options); + + //IWebhook + IGuild IWebhook.Guild + => Guild ?? throw new InvalidOperationException( + "Unable to return this entity's parent unless it was fetched through that object."); + + ITextChannel IWebhook.Channel + => Channel ?? throw new InvalidOperationException( + "Unable to return this entity's parent unless it was fetched through that object."); + + Task IWebhook.ModifyAsync(Action func, RequestOptions options) + => ModifyAsync(func, options); + internal static RestWebhook Create(BaseDiscordClient discord, IGuild guild, Model model) { var entity = new RestWebhook(discord, guild, model.Id, model.Token, model.ChannelId); entity.Update(model); return entity; } + internal static RestWebhook Create(BaseDiscordClient discord, ITextChannel channel, Model model) { var entity = new RestWebhook(discord, channel, model.Id, model.Token, model.ChannelId); @@ -59,33 +86,12 @@ namespace Discord.Rest Name = model.Name.Value; } - public async Task UpdateAsync(RequestOptions options = null) - { - var model = await Discord.ApiClient.GetWebhookAsync(Id, options).ConfigureAwait(false); - Update(model); - } - - public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) - => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); - public async Task ModifyAsync(Action func, RequestOptions options = null) { var model = await WebhookHelper.ModifyAsync(this, Discord, func, options).ConfigureAwait(false); Update(model); } - public Task DeleteAsync(RequestOptions options = null) - => WebhookHelper.DeleteAsync(this, Discord, options); - public override string ToString() => $"Webhook: {Name}:{Id}"; - private string DebuggerDisplay => $"Webhook: {Name} ({Id})"; - - //IWebhook - IGuild IWebhook.Guild - => Guild ?? throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); - ITextChannel IWebhook.Channel - => Channel ?? throw new InvalidOperationException("Unable to return this entity's parent unless it was fetched through that object."); - Task IWebhook.ModifyAsync(Action func, RequestOptions options) - => ModifyAsync(func, options); } } diff --git a/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs b/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs index 50e9cab78..6bc94bff4 100644 --- a/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs +++ b/src/Discord.Net.Rest/Entities/Webhooks/WebhookHelper.cs @@ -29,10 +29,8 @@ namespace Discord.Rest return await client.ApiClient.ModifyWebhookAsync(webhook.Id, apiArgs, options).ConfigureAwait(false); } - public static async Task DeleteAsync(IWebhook webhook, BaseDiscordClient client, RequestOptions options) - { - await client.ApiClient.DeleteWebhookAsync(webhook.Id, options).ConfigureAwait(false); - } + public static async Task DeleteAsync(IWebhook webhook, BaseDiscordClient client, RequestOptions options) => + await client.ApiClient.DeleteWebhookAsync(webhook.Id, options).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs index 74b05dacd..3646b88d4 100644 --- a/src/Discord.Net.Rest/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.Rest/Extensions/EntityExtensions.cs @@ -5,23 +5,22 @@ namespace Discord.Rest { internal static class EntityExtensions { - public static GuildEmote ToEntity(this API.Emoji model) - { - return new GuildEmote(model.Id.Value, model.Name, model.Animated.GetValueOrDefault(), model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles)); - } + public static GuildEmote ToEntity(this API.Emoji model) => new GuildEmote(model.Id.Value, model.Name, + model.Animated.GetValueOrDefault(), model.Managed, model.RequireColons, ImmutableArray.Create(model.Roles)); + + public static Embed ToEntity(this API.Embed model) => new Embed(model.Type, model.Title, model.Description, + model.Url, model.Timestamp, + model.Color.HasValue ? new Color(model.Color.Value) : (Color?)null, + model.Image.IsSpecified ? model.Image.Value.ToEntity() : (EmbedImage?)null, + model.Video.IsSpecified ? model.Video.Value.ToEntity() : (EmbedVideo?)null, + model.Author.IsSpecified ? model.Author.Value.ToEntity() : (EmbedAuthor?)null, + model.Footer.IsSpecified ? model.Footer.Value.ToEntity() : (EmbedFooter?)null, + model.Provider.IsSpecified ? model.Provider.Value.ToEntity() : (EmbedProvider?)null, + model.Thumbnail.IsSpecified ? model.Thumbnail.Value.ToEntity() : (EmbedThumbnail?)null, + model.Fields.IsSpecified + ? model.Fields.Value.Select(x => x.ToEntity()).ToImmutableArray() + : ImmutableArray.Create()); - public static Embed ToEntity(this API.Embed model) - { - return new Embed(model.Type, model.Title, model.Description, model.Url, model.Timestamp, - model.Color.HasValue ? new Color(model.Color.Value) : (Color?)null, - model.Image.IsSpecified ? model.Image.Value.ToEntity() : (EmbedImage?)null, - model.Video.IsSpecified ? model.Video.Value.ToEntity() : (EmbedVideo?)null, - model.Author.IsSpecified ? model.Author.Value.ToEntity() : (EmbedAuthor?)null, - model.Footer.IsSpecified ? model.Footer.Value.ToEntity() : (EmbedFooter?)null, - model.Provider.IsSpecified ? model.Provider.Value.ToEntity() : (EmbedProvider?)null, - model.Thumbnail.IsSpecified ? model.Thumbnail.Value.ToEntity() : (EmbedThumbnail?)null, - model.Fields.IsSpecified ? model.Fields.Value.Select(x => x.ToEntity()).ToImmutableArray() : ImmutableArray.Create()); - } public static API.Embed ToModel(this Embed entity) { if (entity == null) return null; @@ -49,77 +48,67 @@ namespace Discord.Rest model.Video = entity.Video.Value.ToModel(); return model; } - public static EmbedAuthor ToEntity(this API.EmbedAuthor model) - { - return new EmbedAuthor(model.Name, model.Url, model.IconUrl, model.ProxyIconUrl); - } - public static API.EmbedAuthor ToModel(this EmbedAuthor entity) - { - return new API.EmbedAuthor { Name = entity.Name, Url = entity.Url, IconUrl = entity.IconUrl }; - } - public static EmbedField ToEntity(this API.EmbedField model) - { - return new EmbedField(model.Name, model.Value, model.Inline); - } - public static API.EmbedField ToModel(this EmbedField entity) - { - return new API.EmbedField { Name = entity.Name, Value = entity.Value, Inline = entity.Inline }; - } - public static EmbedFooter ToEntity(this API.EmbedFooter model) - { - return new EmbedFooter(model.Text, model.IconUrl, model.ProxyIconUrl); - } - public static API.EmbedFooter ToModel(this EmbedFooter entity) - { - return new API.EmbedFooter { Text = entity.Text, IconUrl = entity.IconUrl }; - } - public static EmbedImage ToEntity(this API.EmbedImage model) - { - return new EmbedImage(model.Url, model.ProxyUrl, - model.Height.IsSpecified ? model.Height.Value : (int?)null, - model.Width.IsSpecified ? model.Width.Value : (int?)null); - } - public static API.EmbedImage ToModel(this EmbedImage entity) - { - return new API.EmbedImage { Url = entity.Url }; - } - public static EmbedProvider ToEntity(this API.EmbedProvider model) - { - return new EmbedProvider(model.Name, model.Url); - } - public static API.EmbedProvider ToModel(this EmbedProvider entity) - { - return new API.EmbedProvider { Name = entity.Name, Url = entity.Url }; - } - public static EmbedThumbnail ToEntity(this API.EmbedThumbnail model) - { - return new EmbedThumbnail(model.Url, model.ProxyUrl, - model.Height.IsSpecified ? model.Height.Value : (int?)null, - model.Width.IsSpecified ? model.Width.Value : (int?)null); - } - public static API.EmbedThumbnail ToModel(this EmbedThumbnail entity) - { - return new API.EmbedThumbnail { Url = entity.Url }; - } - public static EmbedVideo ToEntity(this API.EmbedVideo model) + + public static EmbedAuthor ToEntity(this API.EmbedAuthor model) => + new EmbedAuthor(model.Name, model.Url, model.IconUrl, model.ProxyIconUrl); + + public static API.EmbedAuthor ToModel(this EmbedAuthor entity) => new API.EmbedAuthor { - return new EmbedVideo(model.Url, - model.Height.IsSpecified ? model.Height.Value : (int?)null, - model.Width.IsSpecified ? model.Width.Value : (int?)null); - } - public static API.EmbedVideo ToModel(this EmbedVideo entity) + Name = entity.Name, + Url = entity.Url, + IconUrl = entity.IconUrl + }; + + public static EmbedField ToEntity(this API.EmbedField model) => + new EmbedField(model.Name, model.Value, model.Inline); + + public static API.EmbedField ToModel(this EmbedField entity) => new API.EmbedField { - return new API.EmbedVideo { Url = entity.Url }; - } + Name = entity.Name, + Value = entity.Value, + Inline = entity.Inline + }; + + public static EmbedFooter ToEntity(this API.EmbedFooter model) => + new EmbedFooter(model.Text, model.IconUrl, model.ProxyIconUrl); - public static API.Image ToModel(this Image entity) + public static API.EmbedFooter ToModel(this EmbedFooter entity) => new API.EmbedFooter { - return new API.Image(entity.Stream); - } + Text = entity.Text, + IconUrl = entity.IconUrl + }; + + public static EmbedImage ToEntity(this API.EmbedImage model) => new EmbedImage(model.Url, model.ProxyUrl, + model.Height.IsSpecified ? model.Height.Value : (int?)null, + model.Width.IsSpecified ? model.Width.Value : (int?)null); + + public static API.EmbedImage ToModel(this EmbedImage entity) => new API.EmbedImage {Url = entity.Url}; - public static Overwrite ToEntity(this API.Overwrite model) + public static EmbedProvider ToEntity(this API.EmbedProvider model) => new EmbedProvider(model.Name, model.Url); + + public static API.EmbedProvider ToModel(this EmbedProvider entity) => new API.EmbedProvider { - return new Overwrite(model.TargetId, model.TargetType, new OverwritePermissions(model.Allow, model.Deny)); - } + Name = entity.Name, + Url = entity.Url + }; + + public static EmbedThumbnail ToEntity(this API.EmbedThumbnail model) => new EmbedThumbnail(model.Url, + model.ProxyUrl, + model.Height.IsSpecified ? model.Height.Value : (int?)null, + model.Width.IsSpecified ? model.Width.Value : (int?)null); + + public static API.EmbedThumbnail ToModel(this EmbedThumbnail entity) => + new API.EmbedThumbnail {Url = entity.Url}; + + public static EmbedVideo ToEntity(this API.EmbedVideo model) => new EmbedVideo(model.Url, + model.Height.IsSpecified ? model.Height.Value : (int?)null, + model.Width.IsSpecified ? model.Width.Value : (int?)null); + + public static API.EmbedVideo ToModel(this EmbedVideo entity) => new API.EmbedVideo {Url = entity.Url}; + + public static API.Image ToModel(this Image entity) => new API.Image(entity.Stream); + + public static Overwrite ToEntity(this API.Overwrite model) => new Overwrite(model.TargetId, model.TargetType, + new OverwritePermissions(model.Allow, model.Deny)); } } diff --git a/src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs b/src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs index 3cededb7b..a5c42a3f4 100644 --- a/src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/ArrayConverter.cs @@ -1,6 +1,6 @@ -using Newtonsoft.Json; -using System; +using System; using System.Collections.Generic; +using Newtonsoft.Json; namespace Discord.Net.Converters { @@ -8,16 +8,18 @@ namespace Discord.Net.Converters { private readonly JsonConverter _innerConverter; - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => true; - public override bool CanWrite => true; - public ArrayConverter(JsonConverter innerConverter) { _innerConverter = innerConverter; } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override bool CanRead => true; + public override bool CanWrite => true; + + public override bool CanConvert(Type objectType) => true; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) { var result = new List(); if (reader.TokenType == JsonToken.StartArray) @@ -34,21 +36,21 @@ namespace Discord.Net.Converters reader.Read(); } } + return result.ToArray(); } + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { if (value != null) { writer.WriteStartArray(); var a = (T[])value; - for (int i = 0; i < a.Length; i++) - { + for (var i = 0; i < a.Length; i++) if (_innerConverter != null) _innerConverter.WriteJson(writer, a[i], serializer); else serializer.Serialize(writer, a[i], typeof(T)); - } writer.WriteEndArray(); } diff --git a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs index 8a3c1037b..b043b9ef1 100644 --- a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs +++ b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs @@ -1,18 +1,20 @@ -using Discord.API; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Discord.API; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; namespace Discord.Net.Converters { internal class DiscordContractResolver : DefaultContractResolver { private static readonly TypeInfo _ienumerable = typeof(IEnumerable).GetTypeInfo(); - private static readonly MethodInfo _shouldSerialize = typeof(DiscordContractResolver).GetTypeInfo().GetDeclaredMethod("ShouldSerialize"); - + + private static readonly MethodInfo _shouldSerialize = + typeof(DiscordContractResolver).GetTypeInfo().GetDeclaredMethod("ShouldSerialize"); + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); @@ -22,13 +24,12 @@ namespace Discord.Net.Converters if (member is PropertyInfo propInfo) { var converter = GetConverter(property, propInfo, propInfo.PropertyType, 0); - if (converter != null) - { - property.Converter = converter; - } + if (converter != null) property.Converter = converter; } else - throw new InvalidOperationException($"{member.DeclaringType.FullName}.{member.Name} is not a property."); + throw new InvalidOperationException( + $"{member.DeclaringType.FullName}.{member.Name} is not a property."); + return property; } @@ -38,7 +39,7 @@ namespace Discord.Net.Converters return MakeGenericConverter(property, propInfo, typeof(ArrayConverter<>), type.GetElementType(), depth); if (type.IsConstructedGenericType) { - Type genericType = type.GetGenericTypeDefinition(); + var genericType = type.GetGenericTypeDefinition(); if (depth == 0 && genericType == typeof(Optional<>)) { var typeInput = propInfo.DeclaringType; @@ -47,30 +48,32 @@ namespace Discord.Net.Converters var getter = typeof(Func<,>).MakeGenericType(typeInput, type); var getterDelegate = propInfo.GetMethod.CreateDelegate(getter); var shouldSerialize = _shouldSerialize.MakeGenericMethod(typeInput, innerTypeOutput); - var shouldSerializeDelegate = (Func)shouldSerialize.CreateDelegate(typeof(Func)); + var shouldSerializeDelegate = + (Func)shouldSerialize.CreateDelegate( + typeof(Func)); property.ShouldSerialize = x => shouldSerializeDelegate(x, getterDelegate); - return MakeGenericConverter(property, propInfo, typeof(OptionalConverter<>), innerTypeOutput, depth); + return MakeGenericConverter(property, propInfo, typeof(OptionalConverter<>), innerTypeOutput, + depth); } - else if (genericType == typeof(Nullable<>)) - return MakeGenericConverter(property, propInfo, typeof(NullableConverter<>), type.GenericTypeArguments[0], depth); - else if (genericType == typeof(EntityOrId<>)) - return MakeGenericConverter(property, propInfo, typeof(UInt64EntityOrIdConverter<>), type.GenericTypeArguments[0], depth); + + if (genericType == typeof(Nullable<>)) + return MakeGenericConverter(property, propInfo, typeof(NullableConverter<>), + type.GenericTypeArguments[0], depth); + if (genericType == typeof(EntityOrId<>)) + return MakeGenericConverter(property, propInfo, typeof(UInt64EntityOrIdConverter<>), + type.GenericTypeArguments[0], depth); } //Primitives - bool hasInt53 = propInfo.GetCustomAttribute() != null; + var hasInt53 = propInfo.GetCustomAttribute() != null; if (!hasInt53) - { if (type == typeof(ulong)) return UInt64Converter.Instance; - } - bool hasUnixStamp = propInfo.GetCustomAttribute() != null; + var hasUnixStamp = propInfo.GetCustomAttribute() != null; if (hasUnixStamp) - { if (type == typeof(DateTimeOffset)) return UnixTimestampConverter.Instance; - } //Enums if (type == typeof(PermissionTarget)) @@ -86,22 +89,18 @@ namespace Discord.Net.Converters var typeInfo = type.GetTypeInfo(); if (typeInfo.ImplementedInterfaces.Any(x => x == typeof(IEntity))) return UInt64EntityConverter.Instance; - if (typeInfo.ImplementedInterfaces.Any(x => x == typeof(IEntity))) - return StringEntityConverter.Instance; - - return null; + return typeInfo.ImplementedInterfaces.Any(x => x == typeof(IEntity)) ? StringEntityConverter.Instance : null; } - private static bool ShouldSerialize(object owner, Delegate getter) - { - return (getter as Func>)((TOwner)owner).IsSpecified; - } + private static bool ShouldSerialize(object owner, Delegate getter) => + (getter as Func>)((TOwner)owner).IsSpecified; - private static JsonConverter MakeGenericConverter(JsonProperty property, PropertyInfo propInfo, Type converterType, Type innerType, int depth) + private static JsonConverter MakeGenericConverter(JsonProperty property, PropertyInfo propInfo, + Type converterType, Type innerType, int depth) { var genericType = converterType.MakeGenericType(innerType).GetTypeInfo(); var innerConverter = GetConverter(property, propInfo, innerType, depth + 1); - return genericType.DeclaredConstructors.First().Invoke(new object[] { innerConverter }) as JsonConverter; + return genericType.DeclaredConstructors.First().Invoke(new object[] {innerConverter}) as JsonConverter; } } } diff --git a/src/Discord.Net.Rest/Net/Converters/ImageConverter.cs b/src/Discord.Net.Rest/Net/Converters/ImageConverter.cs index a5a440d8b..d4cd19fff 100644 --- a/src/Discord.Net.Rest/Net/Converters/ImageConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/ImageConverter.cs @@ -8,15 +8,13 @@ namespace Discord.Net.Converters internal class ImageConverter : JsonConverter { public static readonly ImageConverter Instance = new ImageConverter(); - - public override bool CanConvert(Type objectType) => true; public override bool CanRead => true; public override bool CanWrite => true; - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - throw new InvalidOperationException(); - } + public override bool CanConvert(Type objectType) => true; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) => throw new InvalidOperationException(); public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { @@ -41,7 +39,7 @@ namespace Discord.Net.Converters length = (int)cloneStream.Length; } - string base64 = Convert.ToBase64String(bytes, 0, length); + var base64 = Convert.ToBase64String(bytes, 0, length); writer.WriteValue($"data:image/jpeg;base64,{base64}"); } else if (image.Hash != null) diff --git a/src/Discord.Net.Rest/Net/Converters/NullableConverter.cs b/src/Discord.Net.Rest/Net/Converters/NullableConverter.cs index 0b149e725..28a67e781 100644 --- a/src/Discord.Net.Rest/Net/Converters/NullableConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/NullableConverter.cs @@ -1,5 +1,5 @@ -using Newtonsoft.Json; -using System; +using System; +using Newtonsoft.Json; namespace Discord.Net.Converters { @@ -8,29 +8,28 @@ namespace Discord.Net.Converters { private readonly JsonConverter _innerConverter; - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => true; - public override bool CanWrite => true; - public NullableConverter(JsonConverter innerConverter) { _innerConverter = innerConverter; } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override bool CanRead => true; + public override bool CanWrite => true; + + public override bool CanConvert(Type objectType) => true; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) { - object value = reader.Value; + var value = reader.Value; if (value == null) return null; + T obj; + if (_innerConverter != null) + obj = (T)_innerConverter.ReadJson(reader, typeof(T), null, serializer); else - { - T obj; - if (_innerConverter != null) - obj = (T)_innerConverter.ReadJson(reader, typeof(T), null, serializer); - else - obj = serializer.Deserialize(reader); - return obj; - } + obj = serializer.Deserialize(reader); + return obj; } public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) diff --git a/src/Discord.Net.Rest/Net/Converters/OptionalConverter.cs b/src/Discord.Net.Rest/Net/Converters/OptionalConverter.cs index d3d6191c0..6da96b021 100644 --- a/src/Discord.Net.Rest/Net/Converters/OptionalConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/OptionalConverter.cs @@ -1,5 +1,5 @@ -using Newtonsoft.Json; using System; +using Newtonsoft.Json; namespace Discord.Net.Converters { @@ -7,22 +7,24 @@ namespace Discord.Net.Converters { private readonly JsonConverter _innerConverter; - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => true; - public override bool CanWrite => true; - public OptionalConverter(JsonConverter innerConverter) { _innerConverter = innerConverter; } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override bool CanRead => true; + public override bool CanWrite => true; + + public override bool CanConvert(Type objectType) => true; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) { T obj; // custom converters need to be able to safely fail; move this check in here to prevent wasteful casting when parsing primitives if (_innerConverter != null) { - object o = _innerConverter.ReadJson(reader, typeof(T), null, serializer); + var o = _innerConverter.ReadJson(reader, typeof(T), null, serializer); if (o is Optional) return o; diff --git a/src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs b/src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs index 0ed566a84..fd8d66140 100644 --- a/src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/PermissionTargetConverter.cs @@ -1,17 +1,18 @@ -using Newtonsoft.Json; -using System; +using System; +using Newtonsoft.Json; namespace Discord.Net.Converters { internal class PermissionTargetConverter : JsonConverter { public static readonly PermissionTargetConverter Instance = new PermissionTargetConverter(); - - public override bool CanConvert(Type objectType) => true; public override bool CanRead => true; public override bool CanWrite => true; - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override bool CanConvert(Type objectType) => true; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) { switch ((string)reader.Value) { diff --git a/src/Discord.Net.Rest/Net/Converters/StringEntityConverter.cs b/src/Discord.Net.Rest/Net/Converters/StringEntityConverter.cs index d7dd58d71..92569cd3c 100644 --- a/src/Discord.Net.Rest/Net/Converters/StringEntityConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/StringEntityConverter.cs @@ -1,20 +1,18 @@ -using Newtonsoft.Json; -using System; +using System; +using Newtonsoft.Json; namespace Discord.Net.Converters { internal class StringEntityConverter : JsonConverter { public static readonly StringEntityConverter Instance = new StringEntityConverter(); - - public override bool CanConvert(Type objectType) => true; public override bool CanRead => false; public override bool CanWrite => true; - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - throw new InvalidOperationException(); - } + public override bool CanConvert(Type objectType) => true; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) => throw new InvalidOperationException(); public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { diff --git a/src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs b/src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs index 27cbe9290..015dc89ad 100644 --- a/src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs +++ b/src/Discord.Net.Rest/Net/Converters/UInt64Converter.cs @@ -1,25 +1,22 @@ -using Newtonsoft.Json; -using System; +using System; using System.Globalization; +using Newtonsoft.Json; namespace Discord.Net.Converters { internal class UInt64Converter : JsonConverter { public static readonly UInt64Converter Instance = new UInt64Converter(); - - public override bool CanConvert(Type objectType) => true; public override bool CanRead => true; public override bool CanWrite => true; - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - return ulong.Parse((string)reader.Value, NumberStyles.None, CultureInfo.InvariantCulture); - } + public override bool CanConvert(Type objectType) => true; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) => + ulong.Parse((string)reader.Value, NumberStyles.None, CultureInfo.InvariantCulture); - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => writer.WriteValue(((ulong)value).ToString(CultureInfo.InvariantCulture)); - } } } diff --git a/src/Discord.Net.Rest/Net/Converters/UInt64EntityConverter.cs b/src/Discord.Net.Rest/Net/Converters/UInt64EntityConverter.cs index b8d8f1057..555466faa 100644 --- a/src/Discord.Net.Rest/Net/Converters/UInt64EntityConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/UInt64EntityConverter.cs @@ -1,21 +1,19 @@ -using Newtonsoft.Json; -using System; +using System; using System.Globalization; +using Newtonsoft.Json; namespace Discord.Net.Converters { internal class UInt64EntityConverter : JsonConverter { public static readonly UInt64EntityConverter Instance = new UInt64EntityConverter(); - - public override bool CanConvert(Type objectType) => true; public override bool CanRead => false; public override bool CanWrite => true; - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - throw new InvalidOperationException(); - } + public override bool CanConvert(Type objectType) => true; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) => throw new InvalidOperationException(); public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { diff --git a/src/Discord.Net.Rest/Net/Converters/UInt64EntityOrIdConverter.cs b/src/Discord.Net.Rest/Net/Converters/UInt64EntityOrIdConverter.cs index ae8cf2cb2..4f6876857 100644 --- a/src/Discord.Net.Rest/Net/Converters/UInt64EntityOrIdConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/UInt64EntityOrIdConverter.cs @@ -1,6 +1,6 @@ -using Discord.API; +using System; +using Discord.API; using Newtonsoft.Json; -using System; namespace Discord.Net.Converters { @@ -8,16 +8,18 @@ namespace Discord.Net.Converters { private readonly JsonConverter _innerConverter; - public override bool CanConvert(Type objectType) => true; - public override bool CanRead => true; - public override bool CanWrite => false; - public UInt64EntityOrIdConverter(JsonConverter innerConverter) { _innerConverter = innerConverter; } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override bool CanRead => true; + public override bool CanWrite => false; + + public override bool CanConvert(Type objectType) => true; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) { switch (reader.TokenType) { @@ -34,9 +36,7 @@ namespace Discord.Net.Converters } } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new InvalidOperationException(); - } } } diff --git a/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs b/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs index 0b50cb166..69f53bf71 100644 --- a/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/UnixTimestampConverter.cs @@ -5,29 +5,31 @@ namespace Discord.Net.Converters { public class UnixTimestampConverter : JsonConverter { + // 1e13 unix ms = year 2286 + // necessary to prevent discord.js from sending values in the e15 and overflowing a DTO + private const long MaxSaneMs = 1_000_000_000_000_0; public static readonly UnixTimestampConverter Instance = new UnixTimestampConverter(); - - public override bool CanConvert(Type objectType) => true; public override bool CanRead => true; public override bool CanWrite => true; - // 1e13 unix ms = year 2286 - // necessary to prevent discord.js from sending values in the e15 and overflowing a DTO - private const long MaxSaneMs = 1_000_000_000_000_0; + public override bool CanConvert(Type objectType) => true; - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) { - // Discord doesn't validate if timestamps contain decimals or not, and they also don't validate if timestamps are reasonably sized - if (reader.Value is double d && d < MaxSaneMs) - return new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero).AddMilliseconds(d); - else if (reader.Value is long l && l < MaxSaneMs) - return new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero).AddMilliseconds(l); + switch (reader.Value) + { + // Discord doesn't validate if timestamps contain decimals or not, and they also don't validate if timestamps are reasonably sized + case double d when d < MaxSaneMs: + return new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero).AddMilliseconds(d); + case long l when l < MaxSaneMs: + return new DateTimeOffset(1970, 1, 1, 0, 0, 0, 0, TimeSpan.Zero).AddMilliseconds(l); + } + return Optional.Unspecified; } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) => throw new NotImplementedException(); - } } } diff --git a/src/Discord.Net.Rest/Net/Converters/UserStatusConverter.cs b/src/Discord.Net.Rest/Net/Converters/UserStatusConverter.cs index c0a287c16..19a551845 100644 --- a/src/Discord.Net.Rest/Net/Converters/UserStatusConverter.cs +++ b/src/Discord.Net.Rest/Net/Converters/UserStatusConverter.cs @@ -1,17 +1,18 @@ -using Newtonsoft.Json; -using System; +using System; +using Newtonsoft.Json; namespace Discord.Net.Converters { internal class UserStatusConverter : JsonConverter { public static readonly UserStatusConverter Instance = new UserStatusConverter(); - - public override bool CanConvert(Type objectType) => true; public override bool CanRead => true; public override bool CanWrite => true; - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override bool CanConvert(Type objectType) => true; + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, + JsonSerializer serializer) { switch ((string)reader.Value) { diff --git a/src/Discord.Net.Rest/Net/DefaultRestClient.cs b/src/Discord.Net.Rest/Net/DefaultRestClient.cs index ec789be59..5139e046a 100644 --- a/src/Discord.Net.Rest/Net/DefaultRestClient.cs +++ b/src/Discord.Net.Rest/Net/DefaultRestClient.cs @@ -1,5 +1,3 @@ -using Discord.Net.Converters; -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Globalization; @@ -10,6 +8,7 @@ using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; +using Newtonsoft.Json; namespace Discord.Net.Rest { @@ -17,8 +16,10 @@ namespace Discord.Net.Rest { private const int HR_SECURECHANNELFAILED = -2146233079; - private readonly HttpClient _client; + private static readonly HttpMethod _patch = new HttpMethod("PATCH"); private readonly string _baseUrl; + + private readonly HttpClient _client; private readonly JsonSerializer _errorDeserializer; private CancellationToken _cancelToken; private bool _isDisposed; @@ -31,26 +32,15 @@ namespace Discord.Net.Rest { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, UseCookies = false, - UseProxy = useProxy, + UseProxy = useProxy }); SetHeader("accept-encoding", "gzip, deflate"); _cancelToken = CancellationToken.None; _errorDeserializer = new JsonSerializer(); } - private void Dispose(bool disposing) - { - if (!_isDisposed) - { - if (disposing) - _client.Dispose(); - _isDisposed = true; - } - } - public void Dispose() - { - Dispose(true); - } + + public void Dispose() => Dispose(true); public void SetHeader(string key, string value) { @@ -58,23 +48,24 @@ namespace Discord.Net.Rest if (value != null) _client.DefaultRequestHeaders.Add(key, value); } - public void SetCancelToken(CancellationToken cancelToken) - { - _cancelToken = cancelToken; - } - public async Task SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly, string reason = null) + public void SetCancelToken(CancellationToken cancelToken) => _cancelToken = cancelToken; + + public async Task SendAsync(string method, string endpoint, CancellationToken cancelToken, + bool headerOnly, string reason = null) { - string uri = Path.Combine(_baseUrl, endpoint); + var uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) { if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason)); return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false); } } - public async Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly, string reason = null) + + public async Task SendAsync(string method, string endpoint, string json, + CancellationToken cancelToken, bool headerOnly, string reason = null) { - string uri = Path.Combine(_baseUrl, endpoint); + var uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) { if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason)); @@ -82,22 +73,36 @@ namespace Discord.Net.Rest return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false); } } - public async Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly, string reason = null) + + public async Task SendAsync(string method, string endpoint, + IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly, + string reason = null) { - string uri = Path.Combine(_baseUrl, endpoint); + var uri = Path.Combine(_baseUrl, endpoint); using (var restRequest = new HttpRequestMessage(GetMethod(method), uri)) { if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason)); - var content = new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)); + var content = + new MultipartFormDataContent("Upload----" + DateTime.Now.ToString(CultureInfo.InvariantCulture)); if (multipartParams != null) - { foreach (var p in multipartParams) - { switch (p.Value) { - case string stringValue: { content.Add(new StringContent(stringValue), p.Key); continue; } - case byte[] byteArrayValue: { content.Add(new ByteArrayContent(byteArrayValue), p.Key); continue; } - case Stream streamValue: { content.Add(new StreamContent(streamValue), p.Key); continue; } + case string stringValue: + { + content.Add(new StringContent(stringValue), p.Key); + continue; + } + case byte[] byteArrayValue: + { + content.Add(new ByteArrayContent(byteArrayValue), p.Key); + continue; + } + case Stream streamValue: + { + content.Add(new StreamContent(streamValue), p.Key); + continue; + } case MultipartFile fileValue: { var stream = fileValue.Stream; @@ -108,30 +113,42 @@ namespace Discord.Net.Rest memoryStream.Position = 0; stream = memoryStream; } + content.Add(new StreamContent(stream), p.Key, fileValue.Filename); continue; } - default: throw new InvalidOperationException($"Unsupported param type \"{p.Value.GetType().Name}\""); + default: + throw new InvalidOperationException( + $"Unsupported param type \"{p.Value.GetType().Name}\""); } - } - } restRequest.Content = content; return await SendInternalAsync(restRequest, cancelToken, headerOnly).ConfigureAwait(false); } } - private async Task SendInternalAsync(HttpRequestMessage request, CancellationToken cancelToken, bool headerOnly) + private void Dispose(bool disposing) + { + if (!_isDisposed) + { + if (disposing) + _client.Dispose(); + _isDisposed = true; + } + } + + private async Task SendInternalAsync(HttpRequestMessage request, CancellationToken cancelToken, + bool headerOnly) { cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelToken, cancelToken).Token; - HttpResponseMessage response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); - - var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault(), StringComparer.OrdinalIgnoreCase); + var response = await _client.SendAsync(request, cancelToken).ConfigureAwait(false); + + var headers = response.Headers.ToDictionary(x => x.Key, x => x.Value.FirstOrDefault(), + StringComparer.OrdinalIgnoreCase); var stream = !headerOnly ? await response.Content.ReadAsStreamAsync().ConfigureAwait(false) : null; return new RestResponse(response.StatusCode, headers, stream); } - private static readonly HttpMethod _patch = new HttpMethod("PATCH"); private HttpMethod GetMethod(string method) { switch (method) diff --git a/src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs b/src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs index e0e776549..ecc90eda3 100644 --- a/src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs +++ b/src/Discord.Net.Rest/Net/DefaultRestClientProvider.cs @@ -6,19 +6,17 @@ namespace Discord.Net.Rest { public static readonly RestClientProvider Instance = Create(); - public static RestClientProvider Create(bool useProxy = false) + public static RestClientProvider Create(bool useProxy = false) => url => { - return url => + try { - try - { - return new DefaultRestClient(url, useProxy); - } - catch (PlatformNotSupportedException ex) - { - throw new PlatformNotSupportedException("The default RestClientProvider is not supported on this platform.", ex); - } - }; - } + return new DefaultRestClient(url, useProxy); + } + catch (PlatformNotSupportedException ex) + { + throw new PlatformNotSupportedException( + "The default RestClientProvider is not supported on this platform.", ex); + } + }; } } diff --git a/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs b/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs index f32df1bcf..3fa49e98c 100644 --- a/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/ClientBucket.cs @@ -7,6 +7,7 @@ namespace Discord.Net.Queue Unbucketed = 0, SendEdit = 1 } + internal struct ClientBucket { private static readonly ImmutableDictionary _defsByType; @@ -33,7 +34,7 @@ namespace Discord.Net.Queue public static ClientBucket Get(ClientBucketType type) => _defsByType[type]; public static ClientBucket Get(string id) => _defsById[id]; - + public ClientBucketType Type { get; } public string Id { get; } public int WindowCount { get; } diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs index 943b76359..bbdfbcbfe 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs @@ -1,29 +1,28 @@ using System; using System.Collections.Concurrent; -#if DEBUG_LIMITS -using System.Diagnostics; -#endif using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +#if DEBUG_LIMITS +using System.Diagnostics; +#endif + namespace Discord.Net.Queue { internal class RequestQueue : IDisposable { - public event Func RateLimitTriggered; - private readonly ConcurrentDictionary _buckets; private readonly SemaphoreSlim _tokenLock; + private readonly CancellationTokenSource _cancelToken; //Dispose token + + private Task _cleanupTask; private CancellationTokenSource _clearToken; private CancellationToken _parentToken; private CancellationToken _requestCancelToken; //Parent token + Clear token - private CancellationTokenSource _cancelToken; //Dispose token private DateTimeOffset _waitUntil; - private Task _cleanupTask; - public RequestQueue() { _tokenLock = new SemaphoreSlim(1, 1); @@ -32,22 +31,31 @@ namespace Discord.Net.Queue _cancelToken = new CancellationTokenSource(); _requestCancelToken = CancellationToken.None; _parentToken = CancellationToken.None; - + _buckets = new ConcurrentDictionary(); _cleanupTask = RunCleanup(); } + public void Dispose() => _cancelToken.Dispose(); + + public event Func RateLimitTriggered; + public async Task SetCancelTokenAsync(CancellationToken cancelToken) { await _tokenLock.WaitAsync().ConfigureAwait(false); try { _parentToken = cancelToken; - _requestCancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _clearToken.Token).Token; + _requestCancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _clearToken.Token) + .Token; + } + finally + { + _tokenLock.Release(); } - finally { _tokenLock.Release(); } } + public async Task ClearAsync() { await _tokenLock.WaitAsync().ConfigureAwait(false); @@ -55,24 +63,27 @@ namespace Discord.Net.Queue { _clearToken?.Cancel(); _clearToken = new CancellationTokenSource(); - if (_parentToken != null) - _requestCancelToken = CancellationTokenSource.CreateLinkedTokenSource(_clearToken.Token, _parentToken).Token; - else - _requestCancelToken = _clearToken.Token; + _requestCancelToken = CancellationTokenSource + .CreateLinkedTokenSource(_clearToken.Token, _parentToken).Token; + } + finally + { + _tokenLock.Release(); } - finally { _tokenLock.Release(); } } public async Task SendAsync(RestRequest request) { if (request.Options.CancelToken.CanBeCanceled) - request.Options.CancelToken = CancellationTokenSource.CreateLinkedTokenSource(_requestCancelToken, request.Options.CancelToken).Token; + request.Options.CancelToken = CancellationTokenSource + .CreateLinkedTokenSource(_requestCancelToken, request.Options.CancelToken).Token; else request.Options.CancelToken = _requestCancelToken; var bucket = GetOrCreateBucket(request.Options.BucketId, request); return await bucket.SendAsync(request).ConfigureAwait(false); } + public async Task SendAsync(WebSocketRequest request) { //TODO: Re-impl websocket buckets @@ -82,7 +93,7 @@ namespace Discord.Net.Queue internal async Task EnterGlobalAsync(int id, RestRequest request) { - int millis = (int)Math.Ceiling((_waitUntil - DateTimeOffset.UtcNow).TotalMilliseconds); + var millis = (int)Math.Ceiling((_waitUntil - DateTimeOffset.UtcNow).TotalMilliseconds); if (millis > 0) { #if DEBUG_LIMITS @@ -91,19 +102,15 @@ namespace Discord.Net.Queue await Task.Delay(millis).ConfigureAwait(false); } } - internal void PauseGlobal(RateLimitInfo info) - { - _waitUntil = DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value + (info.Lag?.TotalMilliseconds ?? 0.0)); - } - private RequestBucket GetOrCreateBucket(string id, RestRequest request) - { - return _buckets.GetOrAdd(id, x => new RequestBucket(this, request, x)); - } - internal async Task RaiseRateLimitTriggered(string bucketId, RateLimitInfo? info) - { + internal void PauseGlobal(RateLimitInfo info) => _waitUntil = + DateTimeOffset.UtcNow.AddMilliseconds(info.RetryAfter.Value + (info.Lag?.TotalMilliseconds ?? 0.0)); + + private RequestBucket GetOrCreateBucket(string id, RestRequest request) => + _buckets.GetOrAdd(id, x => new RequestBucket(this, request, x)); + + internal async Task RaiseRateLimitTriggered(string bucketId, RateLimitInfo? info) => await RateLimitTriggered(bucketId, info).ConfigureAwait(false); - } private async Task RunCleanup() { @@ -113,20 +120,17 @@ namespace Discord.Net.Queue { var now = DateTimeOffset.UtcNow; foreach (var bucket in _buckets.Select(x => x.Value)) - { if ((now - bucket.LastAttemptAt).TotalMinutes > 1.0) - _buckets.TryRemove(bucket.Id, out RequestBucket ignored); - } + _buckets.TryRemove(bucket.Id, out var ignored); await Task.Delay(60000, _cancelToken.Token); //Runs each minute } } - catch (OperationCanceledException) { } - catch (ObjectDisposedException) { } - } - - public void Dispose() - { - _cancelToken.Dispose(); + catch (OperationCanceledException) + { + } + catch (ObjectDisposedException) + { + } } } } diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs index c4f5996c5..1ade4d295 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueueBucket.cs @@ -1,26 +1,24 @@ -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using System; -#if DEBUG_LIMITS -using System.Diagnostics; -#endif using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +#if DEBUG_LIMITS +using System.Diagnostics; +#endif namespace Discord.Net.Queue { internal class RequestBucket { + private static int nextId; private readonly object _lock; private readonly RequestQueue _queue; - private int _semaphore; private DateTimeOffset? _resetTick; - - public string Id { get; private set; } - public int WindowCount { get; private set; } - public DateTimeOffset LastAttemptAt { get; private set; } + private int _semaphore; public RequestBucket(RequestQueue queue, RestRequest request, string id) { @@ -29,19 +27,19 @@ namespace Discord.Net.Queue _lock = new object(); - if (request.Options.IsClientBucket) - WindowCount = ClientBucket.Get(request.Options.BucketId).WindowCount; - else - WindowCount = 1; //Only allow one request until we get a header back + WindowCount = request.Options.IsClientBucket ? ClientBucket.Get(request.Options.BucketId).WindowCount : 1; _semaphore = WindowCount; _resetTick = null; LastAttemptAt = DateTimeOffset.UtcNow; } - - static int nextId = 0; + + public string Id { get; } + public int WindowCount { get; private set; } + public DateTimeOffset LastAttemptAt { get; private set; } + public async Task SendAsync(RestRequest request) { - int id = Interlocked.Increment(ref nextId); + var id = Interlocked.Increment(ref nextId); #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Start"); #endif @@ -54,14 +52,13 @@ namespace Discord.Net.Queue #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Sending..."); #endif - RateLimitInfo info = default(RateLimitInfo); + var info = default(RateLimitInfo); try { var response = await request.SendAsync().ConfigureAwait(false); info = new RateLimitInfo(response.Headers); if (response.StatusCode < (HttpStatusCode)200 || response.StatusCode >= (HttpStatusCode)300) - { switch (response.StatusCode) { case (HttpStatusCode)429: @@ -79,6 +76,7 @@ namespace Discord.Net.Queue #endif UpdateRateLimit(id, request, info, true); } + await _queue.RaiseRateLimitTriggered(Id, info).ConfigureAwait(false); continue; //Retry case HttpStatusCode.BadGateway: //502 @@ -86,29 +84,45 @@ namespace Discord.Net.Queue Debug.WriteLine($"[{id}] (!) 502"); #endif if ((request.Options.RetryMode & RetryMode.Retry502) == 0) - throw new HttpException(HttpStatusCode.BadGateway, request, null); + throw new HttpException(HttpStatusCode.BadGateway, request); continue; //Retry default: int? code = null; string reason = null; if (response.Stream != null) - { try { using (var reader = new StreamReader(response.Stream)) using (var jsonReader = new JsonTextReader(reader)) { var json = JToken.Load(jsonReader); - try { code = json.Value("code"); } catch { }; - try { reason = json.Value("message"); } catch { }; + try + { + code = json.Value("code"); + } + catch + { + } + + ; + try + { + reason = json.Value("message"); + } + catch + { + } + + ; } } - catch { } - } + catch + { + } + throw new HttpException(response.StatusCode, request, code, reason); } - } else { #if DEBUG_LIMITS @@ -152,9 +166,7 @@ namespace Discord.Net.Queue private async Task EnterAsync(int id, RestRequest request) { - int windowCount; - DateTimeOffset? resetAt; - bool isRateLimited = false; + var isRateLimited = false; while (true) { @@ -162,17 +174,18 @@ namespace Discord.Net.Queue { if (!isRateLimited) throw new TimeoutException(); - else - ThrowRetryLimit(request); + ThrowRetryLimit(request); } + int windowCount; + DateTimeOffset? resetAt; lock (_lock) { windowCount = WindowCount; resetAt = _resetTick; } - DateTimeOffset? timeoutAt = request.TimeoutAt; + var timeoutAt = request.TimeoutAt; if (windowCount > 0 && Interlocked.Decrement(ref _semaphore) < 0) { if (!isRateLimited) @@ -187,7 +200,7 @@ namespace Discord.Net.Queue { if (resetAt > timeoutAt) ThrowRetryLimit(request); - int millis = (int)Math.Ceiling((resetAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds); + var millis = (int)Math.Ceiling((resetAt.Value - DateTimeOffset.UtcNow).TotalMilliseconds); #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Sleeping {millis} ms (Pre-emptive)"); #endif @@ -203,6 +216,7 @@ namespace Discord.Net.Queue #endif await Task.Delay(500, request.Options.CancelToken).ConfigureAwait(false); } + continue; } #if DEBUG_LIMITS @@ -220,7 +234,7 @@ namespace Discord.Net.Queue lock (_lock) { - bool hasQueuedReset = _resetTick != null; + var hasQueuedReset = _resetTick != null; if (info.Limit.HasValue && WindowCount != info.Limit.Value) { WindowCount = info.Limit.Value; @@ -250,14 +264,15 @@ namespace Discord.Net.Queue else if (info.Reset.HasValue) { resetTick = info.Reset.Value.AddSeconds(info.Lag?.TotalSeconds ?? 1.0); - int diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds; + var diff = (int)(resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds; #if DEBUG_LIMITS Debug.WriteLine($"[{id}] X-RateLimit-Reset: {info.Reset.Value.ToUnixTimeSeconds()} ({diff} ms, {info.Lag?.TotalMilliseconds} ms lag)"); #endif } else if (request.Options.IsClientBucket && request.Options.BucketId != null) { - resetTick = DateTimeOffset.UtcNow.AddSeconds(ClientBucket.Get(request.Options.BucketId).WindowSeconds); + resetTick = DateTimeOffset.UtcNow.AddSeconds(ClientBucket.Get(request.Options.BucketId) + .WindowSeconds); #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Client Bucket ({ClientBucket.Get(request.Options.BucketId).WindowSeconds * 1000} ms)"); #endif @@ -265,7 +280,8 @@ namespace Discord.Net.Queue if (resetTick == null) { - WindowCount = 0; //No rate limit info, disable limits on this bucket (should only ever happen with a user token) + WindowCount = + 0; //No rate limit info, disable limits on this bucket (should only ever happen with a user token) #if DEBUG_LIMITS Debug.WriteLine($"[{id}] Disabled Semaphore"); #endif @@ -282,11 +298,13 @@ namespace Discord.Net.Queue if (!hasQueuedReset) { - var _ = QueueReset(id, (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds)); + var _ = QueueReset(id, + (int)Math.Ceiling((_resetTick.Value - DateTimeOffset.UtcNow).TotalMilliseconds)); } } } } + private async Task QueueReset(int id, int millis) { while (true) diff --git a/src/Discord.Net.Rest/Net/Queue/Requests/JsonRestRequest.cs b/src/Discord.Net.Rest/Net/Queue/Requests/JsonRestRequest.cs index 2949bab3c..4e3ce11df 100644 --- a/src/Discord.Net.Rest/Net/Queue/Requests/JsonRestRequest.cs +++ b/src/Discord.Net.Rest/Net/Queue/Requests/JsonRestRequest.cs @@ -1,21 +1,20 @@ -using Discord.Net.Rest; -using System.Threading.Tasks; +using System.Threading.Tasks; +using Discord.Net.Rest; namespace Discord.Net.Queue { public class JsonRestRequest : RestRequest { - public string Json { get; } - public JsonRestRequest(IRestClient client, string method, string endpoint, string json, RequestOptions options) : base(client, method, endpoint, options) { Json = json; } - public override async Task SendAsync() - { - return await Client.SendAsync(Method, Endpoint, Json, Options.CancelToken, Options.HeaderOnly, Options.AuditLogReason).ConfigureAwait(false); - } + public string Json { get; } + + public override async Task SendAsync() => await Client + .SendAsync(Method, Endpoint, Json, Options.CancelToken, Options.HeaderOnly, Options.AuditLogReason) + .ConfigureAwait(false); } } diff --git a/src/Discord.Net.Rest/Net/Queue/Requests/MultipartRestRequest.cs b/src/Discord.Net.Rest/Net/Queue/Requests/MultipartRestRequest.cs index c8d97bbdf..8c7db4684 100644 --- a/src/Discord.Net.Rest/Net/Queue/Requests/MultipartRestRequest.cs +++ b/src/Discord.Net.Rest/Net/Queue/Requests/MultipartRestRequest.cs @@ -1,22 +1,21 @@ -using Discord.Net.Rest; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading.Tasks; +using Discord.Net.Rest; namespace Discord.Net.Queue { public class MultipartRestRequest : RestRequest { - public IReadOnlyDictionary MultipartParams { get; } - - public MultipartRestRequest(IRestClient client, string method, string endpoint, IReadOnlyDictionary multipartParams, RequestOptions options) + public MultipartRestRequest(IRestClient client, string method, string endpoint, + IReadOnlyDictionary multipartParams, RequestOptions options) : base(client, method, endpoint, options) { MultipartParams = multipartParams; } - public override async Task SendAsync() - { - return await Client.SendAsync(Method, Endpoint, MultipartParams, Options.CancelToken, Options.HeaderOnly, Options.AuditLogReason).ConfigureAwait(false); - } + public IReadOnlyDictionary MultipartParams { get; } + + public override async Task SendAsync() => await Client.SendAsync(Method, Endpoint, + MultipartParams, Options.CancelToken, Options.HeaderOnly, Options.AuditLogReason).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Rest/Net/Queue/Requests/RestRequest.cs b/src/Discord.Net.Rest/Net/Queue/Requests/RestRequest.cs index bb5840ce2..ac2de16c9 100644 --- a/src/Discord.Net.Rest/Net/Queue/Requests/RestRequest.cs +++ b/src/Discord.Net.Rest/Net/Queue/Requests/RestRequest.cs @@ -1,19 +1,12 @@ -using Discord.Net.Rest; using System; using System.IO; using System.Threading.Tasks; +using Discord.Net.Rest; namespace Discord.Net.Queue { public class RestRequest : IRequest { - public IRestClient Client { get; } - public string Method { get; } - public string Endpoint { get; } - public DateTimeOffset? TimeoutAt { get; } - public TaskCompletionSource Promise { get; } - public RequestOptions Options { get; } - public RestRequest(IRestClient client, string method, string endpoint, RequestOptions options) { Preconditions.NotNull(options, nameof(options)); @@ -22,13 +15,21 @@ namespace Discord.Net.Queue Method = method; Endpoint = endpoint; Options = options; - TimeoutAt = options.Timeout.HasValue ? DateTimeOffset.UtcNow.AddMilliseconds(options.Timeout.Value) : (DateTimeOffset?)null; + TimeoutAt = options.Timeout.HasValue + ? DateTimeOffset.UtcNow.AddMilliseconds(options.Timeout.Value) + : (DateTimeOffset?)null; Promise = new TaskCompletionSource(); } - public virtual async Task SendAsync() - { - return await Client.SendAsync(Method, Endpoint, Options.CancelToken, Options.HeaderOnly, Options.AuditLogReason).ConfigureAwait(false); - } + public IRestClient Client { get; } + public string Method { get; } + public string Endpoint { get; } + public TaskCompletionSource Promise { get; } + public DateTimeOffset? TimeoutAt { get; } + public RequestOptions Options { get; } + + public virtual async Task SendAsync() => await Client + .SendAsync(Method, Endpoint, Options.CancelToken, Options.HeaderOnly, Options.AuditLogReason) + .ConfigureAwait(false); } } diff --git a/src/Discord.Net.Rest/Net/Queue/Requests/WebSocketRequest.cs b/src/Discord.Net.Rest/Net/Queue/Requests/WebSocketRequest.cs index 81eb40b31..af9f0ee8e 100644 --- a/src/Discord.Net.Rest/Net/Queue/Requests/WebSocketRequest.cs +++ b/src/Discord.Net.Rest/Net/Queue/Requests/WebSocketRequest.cs @@ -1,23 +1,15 @@ -using Discord.Net.WebSockets; using System; using System.IO; using System.Threading; using System.Threading.Tasks; +using Discord.Net.WebSockets; namespace Discord.Net.Queue { public class WebSocketRequest : IRequest { - public IWebSocketClient Client { get; } - public string BucketId { get; } - public byte[] Data { get; } - public bool IsText { get; } - public DateTimeOffset? TimeoutAt { get; } - public TaskCompletionSource Promise { get; } - public RequestOptions Options { get; } - public CancellationToken CancelToken { get; internal set; } - - public WebSocketRequest(IWebSocketClient client, string bucketId, byte[] data, bool isText, RequestOptions options) + public WebSocketRequest(IWebSocketClient client, string bucketId, byte[] data, bool isText, + RequestOptions options) { Preconditions.NotNull(options, nameof(options)); @@ -26,13 +18,21 @@ namespace Discord.Net.Queue Data = data; IsText = isText; Options = options; - TimeoutAt = options.Timeout.HasValue ? DateTimeOffset.UtcNow.AddMilliseconds(options.Timeout.Value) : (DateTimeOffset?)null; + TimeoutAt = options.Timeout.HasValue + ? DateTimeOffset.UtcNow.AddMilliseconds(options.Timeout.Value) + : (DateTimeOffset?)null; Promise = new TaskCompletionSource(); } - public async Task SendAsync() - { - await Client.SendAsync(Data, 0, Data.Length, IsText).ConfigureAwait(false); - } + public IWebSocketClient Client { get; } + public string BucketId { get; } + public byte[] Data { get; } + public bool IsText { get; } + public TaskCompletionSource Promise { get; } + public CancellationToken CancelToken { get; internal set; } + public DateTimeOffset? TimeoutAt { get; } + public RequestOptions Options { get; } + + public async Task SendAsync() => await Client.SendAsync(Data, 0, Data.Length, IsText).ConfigureAwait(false); } } diff --git a/src/Discord.Net.Rest/Net/RateLimitInfo.cs b/src/Discord.Net.Rest/Net/RateLimitInfo.cs index a517f290c..0ac247c1c 100644 --- a/src/Discord.Net.Rest/Net/RateLimitInfo.cs +++ b/src/Discord.Net.Rest/Net/RateLimitInfo.cs @@ -14,18 +14,28 @@ namespace Discord.Net internal RateLimitInfo(Dictionary headers) { - IsGlobal = headers.TryGetValue("X-RateLimit-Global", out string temp) && - bool.TryParse(temp, out var isGlobal) ? isGlobal : false; - Limit = headers.TryGetValue("X-RateLimit-Limit", out temp) && - int.TryParse(temp, out var limit) ? limit : (int?)null; - Remaining = headers.TryGetValue("X-RateLimit-Remaining", out temp) && - int.TryParse(temp, out var remaining) ? remaining : (int?)null; - Reset = headers.TryGetValue("X-RateLimit-Reset", out temp) && - int.TryParse(temp, out var reset) ? DateTimeOffset.FromUnixTimeSeconds(reset) : (DateTimeOffset?)null; + IsGlobal = headers.TryGetValue("X-RateLimit-Global", out var temp) && + bool.TryParse(temp, out var isGlobal) && isGlobal; + Limit = headers.TryGetValue("X-RateLimit-Limit", out temp) && + int.TryParse(temp, out var limit) + ? limit + : (int?)null; + Remaining = headers.TryGetValue("X-RateLimit-Remaining", out temp) && + int.TryParse(temp, out var remaining) + ? remaining + : (int?)null; + Reset = headers.TryGetValue("X-RateLimit-Reset", out temp) && + int.TryParse(temp, out var reset) + ? DateTimeOffset.FromUnixTimeSeconds(reset) + : (DateTimeOffset?)null; RetryAfter = headers.TryGetValue("Retry-After", out temp) && - int.TryParse(temp, out var retryAfter) ? retryAfter : (int?)null; + int.TryParse(temp, out var retryAfter) + ? retryAfter + : (int?)null; Lag = headers.TryGetValue("Date", out temp) && - DateTimeOffset.TryParse(temp, out var date) ? DateTimeOffset.UtcNow - date : (TimeSpan?)null; + DateTimeOffset.TryParse(temp, out var date) + ? DateTimeOffset.UtcNow - date + : (TimeSpan?)null; } } } diff --git a/src/Discord.Net.Rest/Utils/TypingNotifier.cs b/src/Discord.Net.Rest/Utils/TypingNotifier.cs index b4bd2f44b..47b1d1a92 100644 --- a/src/Discord.Net.Rest/Utils/TypingNotifier.cs +++ b/src/Discord.Net.Rest/Utils/TypingNotifier.cs @@ -6,9 +6,9 @@ namespace Discord.Rest { internal class TypingNotifier : IDisposable { - private readonly BaseDiscordClient _client; private readonly CancellationTokenSource _cancelToken; private readonly IMessageChannel _channel; + private readonly BaseDiscordClient _client; private readonly RequestOptions _options; public TypingNotifier(BaseDiscordClient discord, IMessageChannel channel, RequestOptions options) @@ -20,6 +20,8 @@ namespace Discord.Rest var _ = Run(); } + public void Dispose() => _cancelToken.Cancel(); + private async Task Run() { try @@ -31,16 +33,16 @@ namespace Discord.Rest { await _channel.TriggerTypingAsync(_options).ConfigureAwait(false); } - catch { } + catch + { + } + await Task.Delay(9500, token).ConfigureAwait(false); } } - catch (OperationCanceledException) { } - } - - public void Dispose() - { - _cancelToken.Cancel(); + catch (OperationCanceledException) + { + } } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs b/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs index 910f6d909..e628f15f6 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ExtendedGuild.cs @@ -1,25 +1,23 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; using System; +using Newtonsoft.Json; namespace Discord.API.Gateway { internal class ExtendedGuild : Guild { - [JsonProperty("unavailable")] - public bool? Unavailable { get; set; } - [JsonProperty("member_count")] - public int MemberCount { get; set; } - [JsonProperty("large")] - public bool Large { get; set; } + [JsonProperty("unavailable")] public bool? Unavailable { get; set; } + + [JsonProperty("member_count")] public int MemberCount { get; set; } + + [JsonProperty("large")] public bool Large { get; set; } + + [JsonProperty("presences")] public Presence[] Presences { get; set; } + + [JsonProperty("members")] public GuildMember[] Members { get; set; } + + [JsonProperty("channels")] public Channel[] Channels { get; set; } - [JsonProperty("presences")] - public Presence[] Presences { get; set; } - [JsonProperty("members")] - public GuildMember[] Members { get; set; } - [JsonProperty("channels")] - public Channel[] Channels { get; set; } - [JsonProperty("joined_at")] - public DateTimeOffset JoinedAt { get; set; } + [JsonProperty("joined_at")] public DateTimeOffset JoinedAt { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs b/src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs index 13a2bb462..6ae2fbbad 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GatewayOpCode.cs @@ -5,28 +5,40 @@ namespace Discord.API.Gateway { /// C←S - Used to send most events. Dispatch = 0, + /// C↔S - Used to keep the connection alive and measure latency. Heartbeat = 1, + /// C→S - Used to associate a connection with a token and specify configuration. Identify = 2, + /// C→S - Used to update client's status and current game id. StatusUpdate = 3, + /// C→S - Used to join a particular voice channel. VoiceStateUpdate = 4, + /// C→S - Used to ensure the guild's voice server is alive. VoiceServerPing = 5, + /// C→S - Used to resume a connection after a redirect occurs. Resume = 6, + /// C←S - Used to notify a client that they must reconnect to another gateway. Reconnect = 7, + /// C→S - Used to request members that were withheld by large_threshold RequestGuildMembers = 8, + /// C←S - Used to notify the client that their session has expired and cannot be resumed. InvalidSession = 9, + /// C←S - Used to provide information to the client immediately on connection. Hello = 10, + /// C←S - Used to reply to a client's heartbeat. HeartbeatAck = 11, + /// C→S - Used to request presence updates from particular guilds. GuildSync = 12 } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildBanEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildBanEvent.cs index 59a3304dd..b4628ca99 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildBanEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildBanEvent.cs @@ -5,9 +5,8 @@ namespace Discord.API.Gateway { internal class GuildBanEvent { - [JsonProperty("guild_id")] - public ulong GuildId { get; set; } - [JsonProperty("user")] - public User User { get; set; } + [JsonProperty("guild_id")] public ulong GuildId { get; set; } + + [JsonProperty("user")] public User User { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildEmojiUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildEmojiUpdateEvent.cs index 715341dc5..cfea742e4 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildEmojiUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildEmojiUpdateEvent.cs @@ -5,9 +5,8 @@ namespace Discord.API.Gateway { internal class GuildEmojiUpdateEvent { - [JsonProperty("guild_id")] - public ulong GuildId { get; set; } - [JsonProperty("emojis")] - public Emoji[] Emojis { get; set; } + [JsonProperty("guild_id")] public ulong GuildId { get; set; } + + [JsonProperty("emojis")] public Emoji[] Emojis { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberAddEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberAddEvent.cs index 350652faf..758838c09 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberAddEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberAddEvent.cs @@ -5,7 +5,6 @@ namespace Discord.API.Gateway { internal class GuildMemberAddEvent : GuildMember { - [JsonProperty("guild_id")] - public ulong GuildId { get; set; } + [JsonProperty("guild_id")] public ulong GuildId { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberRemoveEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberRemoveEvent.cs index 501408a7f..7751aeb3b 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberRemoveEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberRemoveEvent.cs @@ -5,9 +5,8 @@ namespace Discord.API.Gateway { internal class GuildMemberRemoveEvent { - [JsonProperty("guild_id")] - public ulong GuildId { get; set; } - [JsonProperty("user")] - public User User { get; set; } + [JsonProperty("guild_id")] public ulong GuildId { get; set; } + + [JsonProperty("user")] public User User { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs index a234d6da5..677158691 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildMemberUpdateEvent.cs @@ -5,7 +5,6 @@ namespace Discord.API.Gateway { internal class GuildMemberUpdateEvent : GuildMember { - [JsonProperty("guild_id")] - public ulong GuildId { get; set; } + [JsonProperty("guild_id")] public ulong GuildId { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildMembersChunkEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildMembersChunkEvent.cs index e401d7fa1..11ec60d3e 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildMembersChunkEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildMembersChunkEvent.cs @@ -5,9 +5,8 @@ namespace Discord.API.Gateway { internal class GuildMembersChunkEvent { - [JsonProperty("guild_id")] - public ulong GuildId { get; set; } - [JsonProperty("members")] - public GuildMember[] Members { get; set; } + [JsonProperty("guild_id")] public ulong GuildId { get; set; } + + [JsonProperty("members")] public GuildMember[] Members { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleCreateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleCreateEvent.cs index 3409b1c91..eeb905f76 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleCreateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleCreateEvent.cs @@ -5,9 +5,8 @@ namespace Discord.API.Gateway { internal class GuildRoleCreateEvent { - [JsonProperty("guild_id")] - public ulong GuildId { get; set; } - [JsonProperty("role")] - public Role Role { get; set; } + [JsonProperty("guild_id")] public ulong GuildId { get; set; } + + [JsonProperty("role")] public Role Role { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleDeleteEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleDeleteEvent.cs index dbdaeff67..7de3ad536 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleDeleteEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleDeleteEvent.cs @@ -5,9 +5,8 @@ namespace Discord.API.Gateway { internal class GuildRoleDeleteEvent { - [JsonProperty("guild_id")] - public ulong GuildId { get; set; } - [JsonProperty("role_id")] - public ulong RoleId { get; set; } + [JsonProperty("guild_id")] public ulong GuildId { get; set; } + + [JsonProperty("role_id")] public ulong RoleId { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleUpdateEvent.cs index b04ecb182..67d0c618f 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildRoleUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildRoleUpdateEvent.cs @@ -5,9 +5,8 @@ namespace Discord.API.Gateway { internal class GuildRoleUpdateEvent { - [JsonProperty("guild_id")] - public ulong GuildId { get; set; } - [JsonProperty("role")] - public Role Role { get; set; } + [JsonProperty("guild_id")] public ulong GuildId { get; set; } + + [JsonProperty("role")] public Role Role { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/GuildSyncEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/GuildSyncEvent.cs index 6b2e6c02f..d1de01c84 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/GuildSyncEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/GuildSyncEvent.cs @@ -5,14 +5,12 @@ namespace Discord.API.Gateway { internal class GuildSyncEvent { - [JsonProperty("id")] - public ulong Id { get; set; } - [JsonProperty("large")] - public bool Large { get; set; } + [JsonProperty("id")] public ulong Id { get; set; } - [JsonProperty("presences")] - public Presence[] Presences { get; set; } - [JsonProperty("members")] - public GuildMember[] Members { get; set; } + [JsonProperty("large")] public bool Large { get; set; } + + [JsonProperty("presences")] public Presence[] Presences { get; set; } + + [JsonProperty("members")] public GuildMember[] Members { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/HelloEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/HelloEvent.cs index e1ed9463c..18f5bee12 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/HelloEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/HelloEvent.cs @@ -5,7 +5,6 @@ namespace Discord.API.Gateway { internal class HelloEvent { - [JsonProperty("heartbeat_interval")] - public int HeartbeatInterval { get; set; } + [JsonProperty("heartbeat_interval")] public int HeartbeatInterval { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs index af16f22f5..fe13c8e8f 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/IdentifyParams.cs @@ -1,19 +1,18 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; using System.Collections.Generic; +using Newtonsoft.Json; namespace Discord.API.Gateway { [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class IdentifyParams { - [JsonProperty("token")] - public string Token { get; set; } - [JsonProperty("properties")] - public IDictionary Properties { get; set; } - [JsonProperty("large_threshold")] - public int LargeThreshold { get; set; } - [JsonProperty("shard")] - public Optional ShardingParams { get; set; } + [JsonProperty("token")] public string Token { get; set; } + + [JsonProperty("properties")] public IDictionary Properties { get; set; } + + [JsonProperty("large_threshold")] public int LargeThreshold { get; set; } + + [JsonProperty("shard")] public Optional ShardingParams { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs index aba4a2f1c..7f3c660e3 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/MessageDeleteBulkEvent.cs @@ -1,14 +1,13 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; using System.Collections.Generic; +using Newtonsoft.Json; namespace Discord.API.Gateway { internal class MessageDeleteBulkEvent { - [JsonProperty("channel_id")] - public ulong ChannelId { get; set; } - [JsonProperty("ids")] - public IEnumerable Ids { get; set; } + [JsonProperty("channel_id")] public ulong ChannelId { get; set; } + + [JsonProperty("ids")] public IEnumerable Ids { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs b/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs index 62de456e2..bab4646da 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/Reaction.cs @@ -4,13 +4,12 @@ namespace Discord.API.Gateway { internal class Reaction { - [JsonProperty("user_id")] - public ulong UserId { get; set; } - [JsonProperty("message_id")] - public ulong MessageId { get; set; } - [JsonProperty("channel_id")] - public ulong ChannelId { get; set; } - [JsonProperty("emoji")] - public Emoji Emoji { get; set; } + [JsonProperty("user_id")] public ulong UserId { get; set; } + + [JsonProperty("message_id")] public ulong MessageId { get; set; } + + [JsonProperty("channel_id")] public ulong ChannelId { get; set; } + + [JsonProperty("emoji")] public Emoji Emoji { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/ReadyEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/ReadyEvent.cs index ab92d8c36..f74298269 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ReadyEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ReadyEvent.cs @@ -5,30 +5,28 @@ namespace Discord.API.Gateway { internal class ReadyEvent { + [JsonProperty("v")] public int Version { get; set; } + + [JsonProperty("user")] public User User { get; set; } + + [JsonProperty("session_id")] public string SessionId { get; set; } + + [JsonProperty("read_state")] public ReadState[] ReadStates { get; set; } + + [JsonProperty("guilds")] public ExtendedGuild[] Guilds { get; set; } + + [JsonProperty("private_channels")] public Channel[] PrivateChannels { get; set; } + + [JsonProperty("relationships")] public Relationship[] Relationships { get; set; } + public class ReadState { - [JsonProperty("id")] - public string ChannelId { get; set; } - [JsonProperty("mention_count")] - public int MentionCount { get; set; } - [JsonProperty("last_message_id")] - public string LastMessageId { get; set; } - } + [JsonProperty("id")] public string ChannelId { get; set; } - [JsonProperty("v")] - public int Version { get; set; } - [JsonProperty("user")] - public User User { get; set; } - [JsonProperty("session_id")] - public string SessionId { get; set; } - [JsonProperty("read_state")] - public ReadState[] ReadStates { get; set; } - [JsonProperty("guilds")] - public ExtendedGuild[] Guilds { get; set; } - [JsonProperty("private_channels")] - public Channel[] PrivateChannels { get; set; } - [JsonProperty("relationships")] - public Relationship[] Relationships { get; set; } + [JsonProperty("mention_count")] public int MentionCount { get; set; } + + [JsonProperty("last_message_id")] public string LastMessageId { get; set; } + } //Ignored /*[JsonProperty("user_settings")] diff --git a/src/Discord.Net.WebSocket/API/Gateway/RecipientEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/RecipientEvent.cs index 336ffd029..e1fe04fb0 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/RecipientEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/RecipientEvent.cs @@ -5,9 +5,8 @@ namespace Discord.API.Gateway { internal class RecipientEvent { - [JsonProperty("user")] - public User User { get; set; } - [JsonProperty("channel_id")] - public ulong ChannelId { get; set; } + [JsonProperty("user")] public User User { get; set; } + + [JsonProperty("channel_id")] public ulong ChannelId { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsEvent.cs index 4833c5123..57731f762 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/RemoveAllReactionsEvent.cs @@ -4,9 +4,8 @@ namespace Discord.API.Gateway { internal class RemoveAllReactionsEvent { - [JsonProperty("channel_id")] - public ulong ChannelId { get; set; } - [JsonProperty("message_id")] - public ulong MessageId { get; set; } + [JsonProperty("channel_id")] public ulong ChannelId { get; set; } + + [JsonProperty("message_id")] public ulong MessageId { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs b/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs index 6a8d283ed..6eedff321 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/RequestMembersParams.cs @@ -1,18 +1,16 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; using System.Collections.Generic; +using Newtonsoft.Json; namespace Discord.API.Gateway { [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class RequestMembersParams { - [JsonProperty("query")] - public string Query { get; set; } - [JsonProperty("limit")] - public int Limit { get; set; } + [JsonProperty("query")] public string Query { get; set; } + + [JsonProperty("limit")] public int Limit { get; set; } - [JsonProperty("guild_id")] - public IEnumerable GuildIds { get; set; } + [JsonProperty("guild_id")] public IEnumerable GuildIds { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/ResumeParams.cs b/src/Discord.Net.WebSocket/API/Gateway/ResumeParams.cs index ffb46327b..b3fa74f82 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ResumeParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ResumeParams.cs @@ -6,11 +6,10 @@ namespace Discord.API.Gateway [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class ResumeParams { - [JsonProperty("token")] - public string Token { get; set; } - [JsonProperty("session_id")] - public string SessionId { get; set; } - [JsonProperty("seq")] - public int Sequence { get; set; } + [JsonProperty("token")] public string Token { get; set; } + + [JsonProperty("session_id")] public string SessionId { get; set; } + + [JsonProperty("seq")] public int Sequence { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/ResumedEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/ResumedEvent.cs index d1347beae..7d92abb68 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/ResumedEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/ResumedEvent.cs @@ -3,9 +3,8 @@ using Newtonsoft.Json; namespace Discord.API.Gateway { - internal class ResumedEvent - { - [JsonProperty("heartbeat_interval")] - public int HeartbeatInterval { get; set; } + internal class ResumedEvent + { + [JsonProperty("heartbeat_interval")] public int HeartbeatInterval { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs b/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs index fc0964c17..9e14bc413 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/StatusUpdateParams.cs @@ -6,13 +6,12 @@ namespace Discord.API.Gateway [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class StatusUpdateParams { - [JsonProperty("status")] - public UserStatus Status { get; set; } - [JsonProperty("since"), Int53] - public long? IdleSince { get; set; } - [JsonProperty("afk")] - public bool IsAFK { get; set; } - [JsonProperty("game")] - public Game Game { get; set; } + [JsonProperty("status")] public UserStatus Status { get; set; } + + [JsonProperty("since")] [Int53] public long? IdleSince { get; set; } + + [JsonProperty("afk")] public bool IsAFK { get; set; } + + [JsonProperty("game")] public Game Game { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs index 5ceae4b7a..addcf992d 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/TypingStartEvent.cs @@ -5,15 +5,14 @@ namespace Discord.API.Gateway { internal class TypingStartEvent { - [JsonProperty("user_id")] - public ulong UserId { get; set; } - [JsonProperty("channel_id")] - public ulong ChannelId { get; set; } - [JsonProperty("guild_id")] - public ulong GuildId { get; set; } - [JsonProperty("member")] - public GuildMember Member { get; set; } - [JsonProperty("timestamp")] - public int Timestamp { get; set; } + [JsonProperty("user_id")] public ulong UserId { get; set; } + + [JsonProperty("channel_id")] public ulong ChannelId { get; set; } + + [JsonProperty("guild_id")] public ulong GuildId { get; set; } + + [JsonProperty("member")] public GuildMember Member { get; set; } + + [JsonProperty("timestamp")] public int Timestamp { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs index 29167c1cc..808377ff3 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/VoiceServerUpdateEvent.cs @@ -4,12 +4,11 @@ using Newtonsoft.Json; namespace Discord.API.Gateway { internal class VoiceServerUpdateEvent - { - [JsonProperty("guild_id")] - public ulong GuildId { get; set; } - [JsonProperty("endpoint")] - public string Endpoint { get; set; } - [JsonProperty("token")] - public string Token { get; set; } + { + [JsonProperty("guild_id")] public ulong GuildId { get; set; } + + [JsonProperty("endpoint")] public string Endpoint { get; set; } + + [JsonProperty("token")] public string Token { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/VoiceStateUpdateParams.cs b/src/Discord.Net.WebSocket/API/Gateway/VoiceStateUpdateParams.cs index 521160126..59de44953 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/VoiceStateUpdateParams.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/VoiceStateUpdateParams.cs @@ -6,14 +6,12 @@ namespace Discord.API.Gateway [JsonObject(MemberSerialization = MemberSerialization.OptIn)] internal class VoiceStateUpdateParams { - [JsonProperty("self_mute")] - public bool SelfMute { get; set; } - [JsonProperty("self_deaf")] - public bool SelfDeaf { get; set; } + [JsonProperty("self_mute")] public bool SelfMute { get; set; } - [JsonProperty("guild_id")] - public ulong? GuildId { get; set; } - [JsonProperty("channel_id")] - public ulong? ChannelId { get; set; } + [JsonProperty("self_deaf")] public bool SelfDeaf { get; set; } + + [JsonProperty("guild_id")] public ulong? GuildId { get; set; } + + [JsonProperty("channel_id")] public ulong? ChannelId { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Gateway/WebhookUpdateEvent.cs b/src/Discord.Net.WebSocket/API/Gateway/WebhookUpdateEvent.cs index e5c7afe41..93fb730bd 100644 --- a/src/Discord.Net.WebSocket/API/Gateway/WebhookUpdateEvent.cs +++ b/src/Discord.Net.WebSocket/API/Gateway/WebhookUpdateEvent.cs @@ -5,9 +5,8 @@ namespace Discord.API.Gateway { internal class WebhookUpdateEvent { - [JsonProperty("guild_id")] - public ulong GuildId { get; set; } - [JsonProperty("channel_id")] - public ulong ChannelId { get; set; } + [JsonProperty("guild_id")] public ulong GuildId { get; set; } + + [JsonProperty("channel_id")] public ulong ChannelId { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/SocketFrame.cs b/src/Discord.Net.WebSocket/API/SocketFrame.cs index fae741432..1a7b5e73a 100644 --- a/src/Discord.Net.WebSocket/API/SocketFrame.cs +++ b/src/Discord.Net.WebSocket/API/SocketFrame.cs @@ -5,13 +5,14 @@ namespace Discord.API { internal class SocketFrame { - [JsonProperty("op")] - public int Operation { get; set; } + [JsonProperty("op")] public int Operation { get; set; } + [JsonProperty("t", NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; } + [JsonProperty("s", NullValueHandling = NullValueHandling.Ignore)] public int? Sequence { get; set; } - [JsonProperty("d")] - public object Payload { get; set; } + + [JsonProperty("d")] public object Payload { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/HelloEvent.cs b/src/Discord.Net.WebSocket/API/Voice/HelloEvent.cs index 8fdb0808f..24b17c13c 100644 --- a/src/Discord.Net.WebSocket/API/Voice/HelloEvent.cs +++ b/src/Discord.Net.WebSocket/API/Voice/HelloEvent.cs @@ -4,7 +4,6 @@ namespace Discord.API.Voice { internal class HelloEvent { - [JsonProperty("heartbeat_interval")] - public int HeartbeatInterval { get; set; } + [JsonProperty("heartbeat_interval")] public int HeartbeatInterval { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/IdentifyParams.cs b/src/Discord.Net.WebSocket/API/Voice/IdentifyParams.cs index d446867e1..e2da13318 100644 --- a/src/Discord.Net.WebSocket/API/Voice/IdentifyParams.cs +++ b/src/Discord.Net.WebSocket/API/Voice/IdentifyParams.cs @@ -5,13 +5,12 @@ namespace Discord.API.Voice { internal class IdentifyParams { - [JsonProperty("server_id")] - public ulong GuildId { get; set; } - [JsonProperty("user_id")] - public ulong UserId { get; set; } - [JsonProperty("session_id")] - public string SessionId { get; set; } - [JsonProperty("token")] - public string Token { get; set; } + [JsonProperty("server_id")] public ulong GuildId { get; set; } + + [JsonProperty("user_id")] public ulong UserId { get; set; } + + [JsonProperty("session_id")] public string SessionId { get; set; } + + [JsonProperty("token")] public string Token { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs b/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs index 7188cd8f7..2c1cff131 100644 --- a/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs +++ b/src/Discord.Net.WebSocket/API/Voice/ReadyEvent.cs @@ -1,19 +1,19 @@ #pragma warning disable CS1591 -using Newtonsoft.Json; using System; +using Newtonsoft.Json; namespace Discord.API.Voice { internal class ReadyEvent { - [JsonProperty("ssrc")] - public uint SSRC { get; set; } - [JsonProperty("ip")] - public string Ip { get; set; } - [JsonProperty("port")] - public ushort Port { get; set; } - [JsonProperty("modes")] - public string[] Modes { get; set; } + [JsonProperty("ssrc")] public uint SSRC { get; set; } + + [JsonProperty("ip")] public string Ip { get; set; } + + [JsonProperty("port")] public ushort Port { get; set; } + + [JsonProperty("modes")] public string[] Modes { get; set; } + [JsonProperty("heartbeat_interval")] [Obsolete("This field is errorneous and should not be used", true)] public int HeartbeatInterval { get; set; } diff --git a/src/Discord.Net.WebSocket/API/Voice/SelectProtocolParams.cs b/src/Discord.Net.WebSocket/API/Voice/SelectProtocolParams.cs index 8c577e5b5..8de3c1633 100644 --- a/src/Discord.Net.WebSocket/API/Voice/SelectProtocolParams.cs +++ b/src/Discord.Net.WebSocket/API/Voice/SelectProtocolParams.cs @@ -5,9 +5,8 @@ namespace Discord.API.Voice { internal class SelectProtocolParams { - [JsonProperty("protocol")] - public string Protocol { get; set; } - [JsonProperty("data")] - public UdpProtocolInfo Data { get; set; } + [JsonProperty("protocol")] public string Protocol { get; set; } + + [JsonProperty("data")] public UdpProtocolInfo Data { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/SessionDescriptionEvent.cs b/src/Discord.Net.WebSocket/API/Voice/SessionDescriptionEvent.cs index 45befadcf..0bfd33c66 100644 --- a/src/Discord.Net.WebSocket/API/Voice/SessionDescriptionEvent.cs +++ b/src/Discord.Net.WebSocket/API/Voice/SessionDescriptionEvent.cs @@ -5,9 +5,8 @@ namespace Discord.API.Voice { internal class SessionDescriptionEvent { - [JsonProperty("secret_key")] - public byte[] SecretKey { get; set; } - [JsonProperty("mode")] - public string Mode { get; set; } + [JsonProperty("secret_key")] public byte[] SecretKey { get; set; } + + [JsonProperty("mode")] public string Mode { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/SpeakingEvent.cs b/src/Discord.Net.WebSocket/API/Voice/SpeakingEvent.cs index 0272a8f53..8024041f1 100644 --- a/src/Discord.Net.WebSocket/API/Voice/SpeakingEvent.cs +++ b/src/Discord.Net.WebSocket/API/Voice/SpeakingEvent.cs @@ -5,11 +5,10 @@ namespace Discord.API.Voice { internal class SpeakingEvent { - [JsonProperty("user_id")] - public ulong UserId { get; set; } - [JsonProperty("ssrc")] - public uint Ssrc { get; set; } - [JsonProperty("speaking")] - public bool Speaking { get; set; } + [JsonProperty("user_id")] public ulong UserId { get; set; } + + [JsonProperty("ssrc")] public uint Ssrc { get; set; } + + [JsonProperty("speaking")] public bool Speaking { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs b/src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs index abdf90667..bfcdd871b 100644 --- a/src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs +++ b/src/Discord.Net.WebSocket/API/Voice/SpeakingParams.cs @@ -5,9 +5,8 @@ namespace Discord.API.Voice { internal class SpeakingParams { - [JsonProperty("speaking")] - public bool IsSpeaking { get; set; } - [JsonProperty("delay")] - public int Delay { get; set; } + [JsonProperty("speaking")] public bool IsSpeaking { get; set; } + + [JsonProperty("delay")] public int Delay { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/UdpProtocolInfo.cs b/src/Discord.Net.WebSocket/API/Voice/UdpProtocolInfo.cs index 6f4719e7e..b4f9db792 100644 --- a/src/Discord.Net.WebSocket/API/Voice/UdpProtocolInfo.cs +++ b/src/Discord.Net.WebSocket/API/Voice/UdpProtocolInfo.cs @@ -5,11 +5,10 @@ namespace Discord.API.Voice { internal class UdpProtocolInfo { - [JsonProperty("address")] - public string Address { get; set; } - [JsonProperty("port")] - public int Port { get; set; } - [JsonProperty("mode")] - public string Mode { get; set; } + [JsonProperty("address")] public string Address { get; set; } + + [JsonProperty("port")] public int Port { get; set; } + + [JsonProperty("mode")] public string Mode { get; set; } } } diff --git a/src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs b/src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs index 67afe6173..455284df1 100644 --- a/src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs +++ b/src/Discord.Net.WebSocket/API/Voice/VoiceOpCode.cs @@ -5,25 +5,35 @@ namespace Discord.API.Voice { /// C→S - Used to associate a connection with a token. Identify = 0, + /// C→S - Used to specify configuration. SelectProtocol = 1, + /// C←S - Used to notify that the voice connection was successful and informs the client of available protocols. Ready = 2, + /// C→S - Used to keep the connection alive and measure latency. Heartbeat = 3, + /// C←S - Used to provide an encryption key to the client. SessionDescription = 4, + /// C↔S - Used to inform that a certain user is speaking. Speaking = 5, + /// C←S - Used to reply to a client's heartbeat. HeartbeatAck = 6, + /// C→S - Used to resume a connection. Resume = 7, + /// C←S - Used to inform the client the heartbeat interval. Hello = 8, + /// C←S - Used to acknowledge a resumed connection. Resumed = 9, + /// C←S - Used to notify that a client has disconnected. - ClientDisconnect = 13, + ClientDisconnect = 13 } } diff --git a/src/Discord.Net.WebSocket/AssemblyInfo.cs b/src/Discord.Net.WebSocket/AssemblyInfo.cs index ca3e05e2f..d850937db 100644 --- a/src/Discord.Net.WebSocket/AssemblyInfo.cs +++ b/src/Discord.Net.WebSocket/AssemblyInfo.cs @@ -1,4 +1,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Discord.Net.Relay")] -[assembly: InternalsVisibleTo("Discord.Net.Tests")] \ No newline at end of file +[assembly: InternalsVisibleTo("Discord.Net.Tests")] diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.Events.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.Events.cs index b3e438a01..98fb5f5eb 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.Events.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.Events.cs @@ -5,47 +5,61 @@ namespace Discord.Audio { internal partial class AudioClient { + private readonly AsyncEvent> _connectedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _latencyUpdatedEvent = new AsyncEvent>(); + + private readonly AsyncEvent> _speakingUpdatedEvent = + new AsyncEvent>(); + + private readonly AsyncEvent> _streamCreatedEvent = + new AsyncEvent>(); + + private readonly AsyncEvent> _streamDestroyedEvent = new AsyncEvent>(); + + private readonly AsyncEvent> _udpLatencyUpdatedEvent = + new AsyncEvent>(); + public event Func Connected { - add { _connectedEvent.Add(value); } - remove { _connectedEvent.Remove(value); } + add => _connectedEvent.Add(value); + remove => _connectedEvent.Remove(value); } - private readonly AsyncEvent> _connectedEvent = new AsyncEvent>(); + public event Func Disconnected { - add { _disconnectedEvent.Add(value); } - remove { _disconnectedEvent.Remove(value); } + add => _disconnectedEvent.Add(value); + remove => _disconnectedEvent.Remove(value); } - private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); + public event Func LatencyUpdated { - add { _latencyUpdatedEvent.Add(value); } - remove { _latencyUpdatedEvent.Remove(value); } + add => _latencyUpdatedEvent.Add(value); + remove => _latencyUpdatedEvent.Remove(value); } - private readonly AsyncEvent> _latencyUpdatedEvent = new AsyncEvent>(); + public event Func UdpLatencyUpdated { - add { _udpLatencyUpdatedEvent.Add(value); } - remove { _udpLatencyUpdatedEvent.Remove(value); } + add => _udpLatencyUpdatedEvent.Add(value); + remove => _udpLatencyUpdatedEvent.Remove(value); } - private readonly AsyncEvent> _udpLatencyUpdatedEvent = new AsyncEvent>(); + public event Func StreamCreated { - add { _streamCreatedEvent.Add(value); } - remove { _streamCreatedEvent.Remove(value); } + add => _streamCreatedEvent.Add(value); + remove => _streamCreatedEvent.Remove(value); } - private readonly AsyncEvent> _streamCreatedEvent = new AsyncEvent>(); + public event Func StreamDestroyed { - add { _streamDestroyedEvent.Add(value); } - remove { _streamDestroyedEvent.Remove(value); } + add => _streamDestroyedEvent.Add(value); + remove => _streamDestroyedEvent.Remove(value); } - private readonly AsyncEvent> _streamDestroyedEvent = new AsyncEvent>(); + public event Func SpeakingUpdated { - add { _speakingUpdatedEvent.Add(value); } - remove { _speakingUpdatedEvent.Remove(value); } + add => _speakingUpdatedEvent.Add(value); + remove => _speakingUpdatedEvent.Remove(value); } - private readonly AsyncEvent> _speakingUpdatedEvent = new AsyncEvent>(); } } diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index bbb65a298..b3f913201 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -1,3 +1,10 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; using Discord.API.Voice; using Discord.Audio.Streams; using Discord.Logging; @@ -5,56 +12,27 @@ using Discord.Net.Converters; using Discord.WebSocket; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System; -using System.Collections.Concurrent; -using System.Linq; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using System.Collections.Generic; namespace Discord.Audio { //TODO: Add audio reconnecting internal partial class AudioClient : IAudioClient, IDisposable { - internal struct StreamPair - { - public AudioInStream Reader; - public AudioOutStream Writer; - - public StreamPair(AudioInStream reader, AudioOutStream writer) - { - Reader = reader; - Writer = writer; - } - } - private readonly Logger _audioLogger; - private readonly JsonSerializer _serializer; private readonly ConnectionManager _connection; - private readonly SemaphoreSlim _stateLock; private readonly ConcurrentQueue _heartbeatTimes; private readonly ConcurrentQueue> _keepaliveTimes; + private readonly JsonSerializer _serializer; private readonly ConcurrentDictionary _ssrcMap; + private readonly SemaphoreSlim _stateLock; private readonly ConcurrentDictionary _streams; private Task _heartbeatTask, _keepaliveTask; + private bool _isSpeaking; private long _lastMessageTime; + private uint _ssrc; private string _url, _sessionId, _token; private ulong _userId; - private uint _ssrc; - private bool _isSpeaking; - - public SocketGuild Guild { get; } - public DiscordVoiceAPIClient ApiClient { get; private set; } - public int Latency { get; private set; } - public int UdpLatency { get; private set; } - public ulong ChannelId { get; internal set; } - internal byte[] SecretKey { get; private set; } - - private DiscordSocketClient Discord => Guild.Discord; - public ConnectionState ConnectionState => _connection.State; /// Creates a new REST/WebSocket discord client. internal AudioClient(SocketGuild guild, int clientId, ulong channelId) @@ -64,14 +42,16 @@ namespace Discord.Audio _audioLogger = Discord.LogManager.CreateLogger($"Audio #{clientId}"); ApiClient = new DiscordVoiceAPIClient(guild.Id, Discord.WebSocketProvider, Discord.UdpSocketProvider); - ApiClient.SentGatewayMessage += async opCode => await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); - ApiClient.SentDiscovery += async () => await _audioLogger.DebugAsync($"Sent Discovery").ConfigureAwait(false); + ApiClient.SentGatewayMessage += async opCode => + await _audioLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); + ApiClient.SentDiscovery += + async () => await _audioLogger.DebugAsync("Sent Discovery").ConfigureAwait(false); //ApiClient.SentData += async bytes => await _audioLogger.DebugAsync($"Sent {bytes} Bytes").ConfigureAwait(false); ApiClient.ReceivedEvent += ProcessMessageAsync; ApiClient.ReceivedPacket += ProcessPacketAsync; _stateLock = new SemaphoreSlim(1, 1); - _connection = new ConnectionManager(_stateLock, _audioLogger, 30000, + _connection = new ConnectionManager(_stateLock, _audioLogger, 30000, OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); _connection.Connected += () => _connectedEvent.InvokeAsync(); _connection.Disconnected += (ex, recon) => _disconnectedEvent.InvokeAsync(ex); @@ -79,19 +59,82 @@ namespace Discord.Audio _keepaliveTimes = new ConcurrentQueue>(); _ssrcMap = new ConcurrentDictionary(); _streams = new ConcurrentDictionary(); - - _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; + + _serializer = new JsonSerializer {ContractResolver = new DiscordContractResolver()}; _serializer.Error += (s, e) => { _audioLogger.WarningAsync(e.ErrorContext.Error).GetAwaiter().GetResult(); e.ErrorContext.Handled = true; }; - LatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"Latency = {val} ms").ConfigureAwait(false); - UdpLatencyUpdated += async (old, val) => await _audioLogger.DebugAsync($"UDP Latency = {val} ms").ConfigureAwait(false); + LatencyUpdated += async (old, val) => + await _audioLogger.DebugAsync($"Latency = {val} ms").ConfigureAwait(false); + UdpLatencyUpdated += async (old, val) => + await _audioLogger.DebugAsync($"UDP Latency = {val} ms").ConfigureAwait(false); + } + + public SocketGuild Guild { get; } + public DiscordVoiceAPIClient ApiClient { get; } + public ulong ChannelId { get; internal set; } + internal byte[] SecretKey { get; private set; } + + private DiscordSocketClient Discord => Guild.Discord; + public int Latency { get; private set; } + public int UdpLatency { get; private set; } + public ConnectionState ConnectionState => _connection.State; + + public async Task StopAsync() => await _connection.StopAsync().ConfigureAwait(false); + + public AudioOutStream CreateOpusStream(int bufferMillis) + { + var outputStream = new OutputStream(ApiClient); //Ignores header + var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header + var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header, passes + return new BufferedWriteStream(rtpWriter, this, bufferMillis, _connection.CancelToken, + _audioLogger); //Generates header + } + + public AudioOutStream CreateDirectOpusStream() + { + var outputStream = new OutputStream(ApiClient); //Ignores header + var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header + return new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header (external input), passes + } + + public AudioOutStream CreatePCMStream(AudioApplication application, int? bitrate, int bufferMillis, + int packetLoss) + { + var outputStream = new OutputStream(ApiClient); //Ignores header + var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header + var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header, passes + var bufferedStream = + new BufferedWriteStream(rtpWriter, this, bufferMillis, _connection.CancelToken, + _audioLogger); //Ignores header, generates header + return new OpusEncodeStream(bufferedStream, bitrate ?? 96 * 1024, application, + packetLoss); //Generates header + } + + public AudioOutStream CreateDirectPCMStream(AudioApplication application, int? bitrate, int packetLoss) + { + var outputStream = new OutputStream(ApiClient); //Ignores header + var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header + var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header, passes + return new OpusEncodeStream(rtpWriter, bitrate ?? 96 * 1024, application, packetLoss); //Generates header } - internal async Task StartAsync(string url, ulong userId, string sessionId, string token) + public async Task SetSpeakingAsync(bool value) + { + if (_isSpeaking != value) + { + _isSpeaking = value; + await ApiClient.SendSetSpeaking(value).ConfigureAwait(false); + } + } + + /// + public void Dispose() => Dispose(true); + + internal async Task StartAsync(string url, ulong userId, string sessionId, string token) { _url = url; _userId = userId; @@ -99,10 +142,6 @@ namespace Discord.Audio _token = token; await _connection.StartAsync().ConfigureAwait(false); } - public async Task StopAsync() - { - await _connection.StopAsync().ConfigureAwait(false); - } private async Task OnConnectingAsync() { @@ -115,6 +154,7 @@ namespace Discord.Audio //Wait for READY await _connection.WaitAsync().ConfigureAwait(false); } + private async Task OnDisconnectingAsync(Exception ex) { await _audioLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false); @@ -131,7 +171,10 @@ namespace Discord.Audio await keepaliveTask.ConfigureAwait(false); _keepaliveTask = null; - while (_heartbeatTimes.TryDequeue(out long time)) { } + while (_heartbeatTimes.TryDequeue(out var time)) + { + } + _lastMessageTime = 0; await ClearInputStreamsAsync().ConfigureAwait(false); @@ -140,35 +183,6 @@ namespace Discord.Audio await Discord.ApiClient.SendVoiceStateUpdateAsync(Guild.Id, null, false, false).ConfigureAwait(false); } - public AudioOutStream CreateOpusStream(int bufferMillis) - { - var outputStream = new OutputStream(ApiClient); //Ignores header - var sodiumEncrypter = new SodiumEncryptStream( outputStream, this); //Passes header - var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header, passes - return new BufferedWriteStream(rtpWriter, this, bufferMillis, _connection.CancelToken, _audioLogger); //Generates header - } - public AudioOutStream CreateDirectOpusStream() - { - var outputStream = new OutputStream(ApiClient); //Ignores header - var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header - return new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header (external input), passes - } - public AudioOutStream CreatePCMStream(AudioApplication application, int? bitrate, int bufferMillis, int packetLoss) - { - var outputStream = new OutputStream(ApiClient); //Ignores header - var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header - var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header, passes - var bufferedStream = new BufferedWriteStream(rtpWriter, this, bufferMillis, _connection.CancelToken, _audioLogger); //Ignores header, generates header - return new OpusEncodeStream(bufferedStream, bitrate ?? (96 * 1024), application, packetLoss); //Generates header - } - public AudioOutStream CreateDirectPCMStream(AudioApplication application, int? bitrate, int packetLoss) - { - var outputStream = new OutputStream(ApiClient); //Ignores header - var sodiumEncrypter = new SodiumEncryptStream(outputStream, this); //Passes header - var rtpWriter = new RTPWriteStream(sodiumEncrypter, _ssrc); //Consumes header, passes - return new OpusEncodeStream(rtpWriter, bitrate ?? (96 * 1024), application, packetLoss); //Generates header - } - internal async Task CreateInputStreamAsync(ulong userId) { //Assume Thread-safe @@ -183,12 +197,12 @@ namespace Discord.Audio await _streamCreatedEvent.InvokeAsync(userId, readerStream); } } + internal AudioInStream GetInputStream(ulong id) { - if (_streams.TryGetValue(id, out StreamPair streamPair)) - return streamPair.Reader; - return null; + return _streams.TryGetValue(id, out var streamPair) ? streamPair.Reader : null; } + internal async Task RemoveInputStreamAsync(ulong userId) { if (_streams.TryRemove(userId, out var pair)) @@ -197,6 +211,7 @@ namespace Discord.Audio pair.Reader.Dispose(); } } + internal async Task ClearInputStreamsAsync() { foreach (var pair in _streams) @@ -204,6 +219,7 @@ namespace Discord.Audio await _streamDestroyedEvent.InvokeAsync(pair.Key).ConfigureAwait(false); pair.Value.Reader.Dispose(); } + _ssrcMap.Clear(); _streams.Clear(); } @@ -217,61 +233,62 @@ namespace Discord.Audio switch (opCode) { case VoiceOpCode.Ready: - { - await _audioLogger.DebugAsync("Received Ready").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + { + await _audioLogger.DebugAsync("Received Ready").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); - _ssrc = data.SSRC; + _ssrc = data.SSRC; - if (!data.Modes.Contains(DiscordVoiceAPIClient.Mode)) - throw new InvalidOperationException($"Discord does not support {DiscordVoiceAPIClient.Mode}"); - - ApiClient.SetUdpEndpoint(data.Ip, data.Port); - await ApiClient.SendDiscoveryAsync(_ssrc).ConfigureAwait(false); + if (!data.Modes.Contains(DiscordVoiceAPIClient.Mode)) + throw new InvalidOperationException( + $"Discord does not support {DiscordVoiceAPIClient.Mode}"); - - _heartbeatTask = RunHeartbeatAsync(41250, _connection.CancelToken); - } + ApiClient.SetUdpEndpoint(data.Ip, data.Port); + await ApiClient.SendDiscoveryAsync(_ssrc).ConfigureAwait(false); + + + _heartbeatTask = RunHeartbeatAsync(41250, _connection.CancelToken); + } break; case VoiceOpCode.SessionDescription: - { - await _audioLogger.DebugAsync("Received SessionDescription").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + { + await _audioLogger.DebugAsync("Received SessionDescription").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); - if (data.Mode != DiscordVoiceAPIClient.Mode) - throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}"); + if (data.Mode != DiscordVoiceAPIClient.Mode) + throw new InvalidOperationException($"Discord selected an unexpected mode: {data.Mode}"); - SecretKey = data.SecretKey; - _isSpeaking = false; - await ApiClient.SendSetSpeaking(false).ConfigureAwait(false); - _keepaliveTask = RunKeepaliveAsync(5000, _connection.CancelToken); + SecretKey = data.SecretKey; + _isSpeaking = false; + await ApiClient.SendSetSpeaking(false).ConfigureAwait(false); + _keepaliveTask = RunKeepaliveAsync(5000, _connection.CancelToken); - var _ = _connection.CompleteAsync(); - } + var _ = _connection.CompleteAsync(); + } break; case VoiceOpCode.HeartbeatAck: - { - await _audioLogger.DebugAsync("Received HeartbeatAck").ConfigureAwait(false); + { + await _audioLogger.DebugAsync("Received HeartbeatAck").ConfigureAwait(false); - if (_heartbeatTimes.TryDequeue(out long time)) - { - int latency = (int)(Environment.TickCount - time); - int before = Latency; - Latency = latency; + if (_heartbeatTimes.TryDequeue(out var time)) + { + var latency = (int)(Environment.TickCount - time); + var before = Latency; + Latency = latency; - await _latencyUpdatedEvent.InvokeAsync(before, latency).ConfigureAwait(false); - } + await _latencyUpdatedEvent.InvokeAsync(before, latency).ConfigureAwait(false); } + } break; case VoiceOpCode.Speaking: - { - await _audioLogger.DebugAsync("Received Speaking").ConfigureAwait(false); + { + await _audioLogger.DebugAsync("Received Speaking").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - _ssrcMap[data.Ssrc] = data.UserId; //TODO: Memory Leak: SSRCs are never cleaned up + var data = (payload as JToken).ToObject(_serializer); + _ssrcMap[data.Ssrc] = data.UserId; //TODO: Memory Leak: SSRCs are never cleaned up - await _speakingUpdatedEvent.InvokeAsync(data.UserId, data.Speaking); - } + await _speakingUpdatedEvent.InvokeAsync(data.UserId, data.Speaking); + } break; default: await _audioLogger.WarningAsync($"Unknown OpCode ({opCode})").ConfigureAwait(false); @@ -281,43 +298,39 @@ namespace Discord.Audio catch (Exception ex) { await _audioLogger.ErrorAsync($"Error handling {opCode}", ex).ConfigureAwait(false); - return; } } + private async Task ProcessPacketAsync(byte[] packet) { try { - if (_connection.State == ConnectionState.Connecting) + switch (_connection.State) { - if (packet.Length != 70) - { - await _audioLogger.DebugAsync($"Malformed Packet").ConfigureAwait(false); + case ConnectionState.Connecting when packet.Length != 70: + await _audioLogger.DebugAsync("Malformed Packet").ConfigureAwait(false); return; - } - string ip; - int port; - try - { - ip = Encoding.UTF8.GetString(packet, 4, 70 - 6).TrimEnd('\0'); - port = (packet[69] << 8) | packet[68]; - } - catch (Exception ex) - { - await _audioLogger.DebugAsync($"Malformed Packet", ex).ConfigureAwait(false); - return; - } - - await _audioLogger.DebugAsync("Received Discovery").ConfigureAwait(false); - await ApiClient.SendSelectProtocol(ip, port).ConfigureAwait(false); - } - else if (_connection.State == ConnectionState.Connected) - { - if (packet.Length == 8) - { + case ConnectionState.Connecting: + string ip; + int port; + try + { + ip = Encoding.UTF8.GetString(packet, 4, 70 - 6).TrimEnd('\0'); + port = (packet[69] << 8) | packet[68]; + } + catch (Exception ex) + { + await _audioLogger.DebugAsync("Malformed Packet", ex).ConfigureAwait(false); + return; + } + + await _audioLogger.DebugAsync("Received Discovery").ConfigureAwait(false); + await ApiClient.SendSelectProtocol(ip, port).ConfigureAwait(false); + break; + case ConnectionState.Connected when packet.Length == 8: await _audioLogger.DebugAsync("Received Keepalive").ConfigureAwait(false); - ulong value = + var value = ((ulong)packet[0] >> 0) | ((ulong)packet[1] >> 8) | ((ulong)packet[2] >> 16) | @@ -327,53 +340,53 @@ namespace Discord.Audio ((ulong)packet[6] >> 48) | ((ulong)packet[7] >> 56); - while (_keepaliveTimes.TryDequeue(out var pair)) - { - if (pair.Key == value) + while (_keepaliveTimes.TryDequeue(out var keyValuePair)) + if (keyValuePair.Key == value) { - int latency = (int)(Environment.TickCount - pair.Value); - int before = UdpLatency; + var latency = Environment.TickCount - keyValuePair.Value; + var before = UdpLatency; UdpLatency = latency; await _udpLatencyUpdatedEvent.InvokeAsync(before, latency).ConfigureAwait(false); break; } - } - } - else - { + + break; + case ConnectionState.Connected: if (!RTPReadStream.TryReadSsrc(packet, 0, out var ssrc)) { - await _audioLogger.DebugAsync($"Malformed Frame").ConfigureAwait(false); + await _audioLogger.DebugAsync("Malformed Frame").ConfigureAwait(false); return; } + if (!_ssrcMap.TryGetValue(ssrc, out var userId)) { await _audioLogger.DebugAsync($"Unknown SSRC {ssrc}").ConfigureAwait(false); return; } + if (!_streams.TryGetValue(userId, out var pair)) { await _audioLogger.DebugAsync($"Unknown User {userId}").ConfigureAwait(false); return; } + try { await pair.Writer.WriteAsync(packet, 0, packet.Length).ConfigureAwait(false); } catch (Exception ex) { - await _audioLogger.DebugAsync($"Malformed Frame", ex).ConfigureAwait(false); - return; + await _audioLogger.DebugAsync("Malformed Frame", ex).ConfigureAwait(false); } + //await _audioLogger.DebugAsync($"Received {packet.Length} bytes from user {userId}").ConfigureAwait(false); - } + break; } } catch (Exception ex) { - await _audioLogger.WarningAsync($"Failed to process UDP packet", ex).ConfigureAwait(false); - return; + await _audioLogger.WarningAsync("Failed to process UDP packet", ex).ConfigureAwait(false); } } @@ -388,7 +401,7 @@ namespace Discord.Audio var now = Environment.TickCount; //Did server respond to our last heartbeat? - if (_heartbeatTimes.Count != 0 && (now - _lastMessageTime) > intervalMillis && + if (_heartbeatTimes.Count != 0 && now - _lastMessageTime > intervalMillis && ConnectionState == ConnectionState.Connected) { _connection.Error(new Exception("Server missed last heartbeat")); @@ -407,6 +420,7 @@ namespace Discord.Audio await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false); } + await _audioLogger.DebugAsync("Heartbeat Stopped").ConfigureAwait(false); } catch (OperationCanceledException) @@ -418,6 +432,7 @@ namespace Discord.Audio await _audioLogger.ErrorAsync("Heartbeat Errored", ex).ConfigureAwait(false); } } + private async Task RunKeepaliveAsync(int intervalMillis, CancellationToken cancelToken) { var packet = new byte[8]; @@ -430,7 +445,7 @@ namespace Discord.Audio try { - ulong value = await ApiClient.SendKeepaliveAsync().ConfigureAwait(false); + var value = await ApiClient.SendKeepaliveAsync().ConfigureAwait(false); if (_keepaliveTimes.Count < 12) //No reply for 60 Seconds _keepaliveTimes.Enqueue(new KeyValuePair(value, now)); } @@ -438,9 +453,10 @@ namespace Discord.Audio { await _audioLogger.WarningAsync("Failed to send keepalive", ex).ConfigureAwait(false); } - + await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false); } + await _audioLogger.DebugAsync("Keepalive Stopped").ConfigureAwait(false); } catch (OperationCanceledException) @@ -453,24 +469,23 @@ namespace Discord.Audio } } - public async Task SetSpeakingAsync(bool value) + internal void Dispose(bool disposing) { - if (_isSpeaking != value) - { - _isSpeaking = value; - await ApiClient.SendSetSpeaking(value).ConfigureAwait(false); - } + if (!disposing) return; + StopAsync().GetAwaiter().GetResult(); + ApiClient.Dispose(); } - internal void Dispose(bool disposing) + internal struct StreamPair { - if (disposing) + public AudioInStream Reader; + public AudioOutStream Writer; + + public StreamPair(AudioInStream reader, AudioOutStream writer) { - StopAsync().GetAwaiter().GetResult(); - ApiClient.Dispose(); + Reader = reader; + Writer = writer; } } - /// - public void Dispose() => Dispose(true); } } diff --git a/src/Discord.Net.WebSocket/Audio/Opus/OpusApplication.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusApplication.cs index e288bb626..3b55f5bcd 100644 --- a/src/Discord.Net.WebSocket/Audio/Opus/OpusApplication.cs +++ b/src/Discord.Net.WebSocket/Audio/Opus/OpusApplication.cs @@ -1,6 +1,6 @@ namespace Discord.Audio { - internal enum OpusApplication : int + internal enum OpusApplication { Voice = 2048, MusicOrMixed = 2049, diff --git a/src/Discord.Net.WebSocket/Audio/Opus/OpusConverter.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusConverter.cs index 4179ce9c9..abd8e2bb8 100644 --- a/src/Discord.Net.WebSocket/Audio/Opus/OpusConverter.cs +++ b/src/Discord.Net.WebSocket/Audio/Opus/OpusConverter.cs @@ -4,8 +4,6 @@ namespace Discord.Audio { internal abstract class OpusConverter : IDisposable { - protected IntPtr _ptr; - public const int SamplingRate = 48000; public const int Channels = 2; public const int FrameMillis = 20; @@ -15,29 +13,33 @@ namespace Discord.Audio public const int FrameSamplesPerChannel = SamplingRate / 1000 * FrameMillis; public const int FrameSamples = FrameSamplesPerChannel * Channels; public const int FrameBytes = FrameSamplesPerChannel * SampleBytes; - - protected bool _isDisposed = false; + + protected bool _isDisposed; + protected IntPtr _ptr; + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } protected virtual void Dispose(bool disposing) { if (!_isDisposed) _isDisposed = true; } + ~OpusConverter() { Dispose(false); } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - + protected static void CheckError(int result) { if (result < 0) throw new Exception($"Opus Error: {(OpusError)result}"); } + protected static void CheckError(OpusError error) { if ((int)error < 0) diff --git a/src/Discord.Net.WebSocket/Audio/Opus/OpusCtl.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusCtl.cs index 0b6a4e37f..3d6fde36b 100644 --- a/src/Discord.Net.WebSocket/Audio/Opus/OpusCtl.cs +++ b/src/Discord.Net.WebSocket/Audio/Opus/OpusCtl.cs @@ -1,7 +1,7 @@ namespace Discord.Audio { //https://github.com/gcp/opus/blob/master/include/opus_defines.h - internal enum OpusCtl : int + internal enum OpusCtl { SetBitrate = 4002, SetBandwidth = 4008, diff --git a/src/Discord.Net.WebSocket/Audio/Opus/OpusDecoder.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusDecoder.cs index 41c48e1ac..d28103ae2 100644 --- a/src/Discord.Net.WebSocket/Audio/Opus/OpusDecoder.cs +++ b/src/Discord.Net.WebSocket/Audio/Opus/OpusDecoder.cs @@ -5,39 +5,42 @@ namespace Discord.Audio { internal unsafe class OpusDecoder : OpusConverter { + public OpusDecoder() + { + _ptr = CreateDecoder(SamplingRate, Channels, out var error); + CheckError(error); + } + [DllImport("opus", EntryPoint = "opus_decoder_create", CallingConvention = CallingConvention.Cdecl)] private static extern IntPtr CreateDecoder(int Fs, int channels, out OpusError error); + [DllImport("opus", EntryPoint = "opus_decoder_destroy", CallingConvention = CallingConvention.Cdecl)] private static extern void DestroyDecoder(IntPtr decoder); + [DllImport("opus", EntryPoint = "opus_decode", CallingConvention = CallingConvention.Cdecl)] private static extern int Decode(IntPtr st, byte* data, int len, byte* pcm, int max_frame_size, int decode_fec); + [DllImport("opus", EntryPoint = "opus_decoder_ctl", CallingConvention = CallingConvention.Cdecl)] private static extern int DecoderCtl(IntPtr st, OpusCtl request, int value); - public OpusDecoder() - { - _ptr = CreateDecoder(SamplingRate, Channels, out var error); - CheckError(error); - } - - public unsafe int DecodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output, int outputOffset, bool decodeFEC) + public int DecodeFrame(byte[] input, int inputOffset, int inputCount, byte[] output, int outputOffset, + bool decodeFEC) { - int result = 0; + int result; fixed (byte* inPtr = input) fixed (byte* outPtr = output) - result = Decode(_ptr, inPtr + inputOffset, inputCount, outPtr + outputOffset, FrameSamplesPerChannel, decodeFEC ? 1 : 0); + result = Decode(_ptr, inPtr + inputOffset, inputCount, outPtr + outputOffset, FrameSamplesPerChannel, + decodeFEC ? 1 : 0); CheckError(result); return result * SampleBytes; } protected override void Dispose(bool disposing) { - if (!_isDisposed) - { - if (_ptr != IntPtr.Zero) - DestroyDecoder(_ptr); - base.Dispose(disposing); - } + if (_isDisposed) return; + if (_ptr != IntPtr.Zero) + DestroyDecoder(_ptr); + base.Dispose(disposing); } } } diff --git a/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs index 1ff5a5d9a..8f425ca2c 100644 --- a/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs +++ b/src/Discord.Net.WebSocket/Audio/Opus/OpusEncoder.cs @@ -5,18 +5,6 @@ namespace Discord.Audio { internal unsafe class OpusEncoder : OpusConverter { - [DllImport("opus", EntryPoint = "opus_encoder_create", CallingConvention = CallingConvention.Cdecl)] - private static extern IntPtr CreateEncoder(int Fs, int channels, int application, out OpusError error); - [DllImport("opus", EntryPoint = "opus_encoder_destroy", CallingConvention = CallingConvention.Cdecl)] - private static extern void DestroyEncoder(IntPtr encoder); - [DllImport("opus", EntryPoint = "opus_encode", CallingConvention = CallingConvention.Cdecl)] - private static extern int Encode(IntPtr st, byte* pcm, int frame_size, byte* data, int max_data_bytes); - [DllImport("opus", EntryPoint = "opus_encoder_ctl", CallingConvention = CallingConvention.Cdecl)] - private static extern OpusError EncoderCtl(IntPtr st, OpusCtl request, int value); - - public AudioApplication Application { get; } - public int BitRate { get;} - public OpusEncoder(int bitrate, AudioApplication application, int packetLoss) { if (bitrate < 1 || bitrate > DiscordVoiceAPIClient.MaxBitrate) @@ -53,24 +41,38 @@ namespace Discord.Audio CheckError(EncoderCtl(_ptr, OpusCtl.SetBitrate, bitrate)); } - public unsafe int EncodeFrame(byte[] input, int inputOffset, byte[] output, int outputOffset) + public AudioApplication Application { get; } + public int BitRate { get; } + + [DllImport("opus", EntryPoint = "opus_encoder_create", CallingConvention = CallingConvention.Cdecl)] + private static extern IntPtr CreateEncoder(int Fs, int channels, int application, out OpusError error); + + [DllImport("opus", EntryPoint = "opus_encoder_destroy", CallingConvention = CallingConvention.Cdecl)] + private static extern void DestroyEncoder(IntPtr encoder); + + [DllImport("opus", EntryPoint = "opus_encode", CallingConvention = CallingConvention.Cdecl)] + private static extern int Encode(IntPtr st, byte* pcm, int frame_size, byte* data, int max_data_bytes); + + [DllImport("opus", EntryPoint = "opus_encoder_ctl", CallingConvention = CallingConvention.Cdecl)] + private static extern OpusError EncoderCtl(IntPtr st, OpusCtl request, int value); + + public int EncodeFrame(byte[] input, int inputOffset, byte[] output, int outputOffset) { - int result = 0; + int result; fixed (byte* inPtr = input) fixed (byte* outPtr = output) - result = Encode(_ptr, inPtr + inputOffset, FrameSamplesPerChannel, outPtr + outputOffset, output.Length - outputOffset); + result = Encode(_ptr, inPtr + inputOffset, FrameSamplesPerChannel, outPtr + outputOffset, + output.Length - outputOffset); CheckError(result); return result; } protected override void Dispose(bool disposing) { - if (!_isDisposed) - { - if (_ptr != IntPtr.Zero) - DestroyEncoder(_ptr); - base.Dispose(disposing); - } + if (_isDisposed) return; + if (_ptr != IntPtr.Zero) + DestroyEncoder(_ptr); + base.Dispose(disposing); } } } diff --git a/src/Discord.Net.WebSocket/Audio/Opus/OpusError.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusError.cs index d29d8b9dd..baddfbab9 100644 --- a/src/Discord.Net.WebSocket/Audio/Opus/OpusError.cs +++ b/src/Discord.Net.WebSocket/Audio/Opus/OpusError.cs @@ -1,6 +1,6 @@ namespace Discord.Audio { - internal enum OpusError : int + internal enum OpusError { OK = 0, BadArg = -1, diff --git a/src/Discord.Net.WebSocket/Audio/Opus/OpusSignal.cs b/src/Discord.Net.WebSocket/Audio/Opus/OpusSignal.cs index 3f95183f4..0e75c99d6 100644 --- a/src/Discord.Net.WebSocket/Audio/Opus/OpusSignal.cs +++ b/src/Discord.Net.WebSocket/Audio/Opus/OpusSignal.cs @@ -1,9 +1,9 @@ namespace Discord.Audio { - internal enum OpusSignal : int + internal enum OpusSignal { Auto = -1000, Voice = 3001, - Music = 3002, + Music = 3002 } } diff --git a/src/Discord.Net.WebSocket/Audio/Sodium/SecretBox.cs b/src/Discord.Net.WebSocket/Audio/Sodium/SecretBox.cs index 4187c9f08..627a4039b 100644 --- a/src/Discord.Net.WebSocket/Audio/Sodium/SecretBox.cs +++ b/src/Discord.Net.WebSocket/Audio/Sodium/SecretBox.cs @@ -3,30 +3,36 @@ using System.Runtime.InteropServices; namespace Discord.Audio { - public unsafe static class SecretBox + public static unsafe class SecretBox { [DllImport("libsodium", EntryPoint = "crypto_secretbox_easy", CallingConvention = CallingConvention.Cdecl)] - private static extern int SecretBoxEasy(byte* output, byte* input, long inputLength, byte[] nonce, byte[] secret); + private static extern int SecretBoxEasy(byte* output, byte* input, long inputLength, byte[] nonce, + byte[] secret); + [DllImport("libsodium", EntryPoint = "crypto_secretbox_open_easy", CallingConvention = CallingConvention.Cdecl)] - private static extern int SecretBoxOpenEasy(byte* output, byte* input, long inputLength, byte[] nonce, byte[] secret); + private static extern int SecretBoxOpenEasy(byte* output, byte* input, long inputLength, byte[] nonce, + byte[] secret); - public static int Encrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret) + public static int Encrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, + byte[] nonce, byte[] secret) { fixed (byte* inPtr = input) fixed (byte* outPtr = output) { - int error = SecretBoxEasy(outPtr + outputOffset, inPtr + inputOffset, inputLength, nonce, secret); + var error = SecretBoxEasy(outPtr + outputOffset, inPtr + inputOffset, inputLength, nonce, secret); if (error != 0) throw new Exception($"Sodium Error: {error}"); return inputLength + 16; } } - public static int Decrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, byte[] nonce, byte[] secret) + + public static int Decrypt(byte[] input, int inputOffset, int inputLength, byte[] output, int outputOffset, + byte[] nonce, byte[] secret) { fixed (byte* inPtr = input) fixed (byte* outPtr = output) { - int error = SecretBoxOpenEasy(outPtr + outputOffset, inPtr + inputOffset, inputLength, nonce, secret); + var error = SecretBoxOpenEasy(outPtr + outputOffset, inPtr + inputOffset, inputLength, nonce, secret); if (error != 0) throw new Exception($"Sodium Error: {error}"); return inputLength - 16; diff --git a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs index fb302f132..7211c3c19 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs @@ -1,8 +1,8 @@ -using Discord.Logging; using System; using System.Collections.Concurrent; using System.Threading; using System.Threading.Tasks; +using Discord.Logging; namespace Discord.Audio.Streams { @@ -11,41 +11,34 @@ namespace Discord.Audio.Streams { private const int MaxSilenceFrames = 10; - private struct Frame - { - public Frame(byte[] buffer, int bytes) - { - Buffer = buffer; - Bytes = bytes; - } - - public readonly byte[] Buffer; - public readonly int Bytes; - } - private static readonly byte[] _silenceFrame = new byte[0]; + private readonly ConcurrentQueue _bufferPool; + private readonly CancellationToken _cancelToken; + private readonly CancellationTokenSource _cancelTokenSource; private readonly AudioClient _client; + private readonly Logger _logger; private readonly AudioStream _next; - private readonly CancellationTokenSource _cancelTokenSource; - private readonly CancellationToken _cancelToken; - private readonly Task _task; private readonly ConcurrentQueue _queuedFrames; - private readonly ConcurrentQueue _bufferPool; private readonly SemaphoreSlim _queueLock; - private readonly Logger _logger; + private readonly Task _task; private readonly int _ticksPerFrame, _queueLength; private bool _isPreloaded; private int _silenceFrames; - public BufferedWriteStream(AudioStream next, IAudioClient client, int bufferMillis, CancellationToken cancelToken, int maxFrameSize = 1500) - : this(next, client as AudioClient, bufferMillis, cancelToken, null, maxFrameSize) { } - internal BufferedWriteStream(AudioStream next, AudioClient client, int bufferMillis, CancellationToken cancelToken, Logger logger, int maxFrameSize = 1500) + public BufferedWriteStream(AudioStream next, IAudioClient client, int bufferMillis, + CancellationToken cancelToken, int maxFrameSize = 1500) + : this(next, client as AudioClient, bufferMillis, cancelToken, null, maxFrameSize) + { + } + + internal BufferedWriteStream(AudioStream next, AudioClient client, int bufferMillis, + CancellationToken cancelToken, Logger logger, int maxFrameSize = 1500) { //maxFrameSize = 1275 was too limiting at 128kbps,2ch,60ms _next = next; _client = client; - _ticksPerFrame = OpusEncoder.FrameMillis; + _ticksPerFrame = OpusConverter.FrameMillis; _logger = logger; _queueLength = (bufferMillis + (_ticksPerFrame - 1)) / _ticksPerFrame; //Round up @@ -53,13 +46,14 @@ namespace Discord.Audio.Streams _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_cancelTokenSource.Token, cancelToken).Token; _queuedFrames = new ConcurrentQueue(); _bufferPool = new ConcurrentQueue(); - for (int i = 0; i < _queueLength; i++) - _bufferPool.Enqueue(new byte[maxFrameSize]); + for (var i = 0; i < _queueLength; i++) + _bufferPool.Enqueue(new byte[maxFrameSize]); _queueLock = new SemaphoreSlim(_queueLength, _queueLength); _silenceFrames = MaxSilenceFrames; _task = Run(); } + protected override void Dispose(bool disposing) { if (disposing) @@ -82,10 +76,10 @@ namespace Discord.Audio.Streams while (!_cancelToken.IsCancellationRequested) { long tick = Environment.TickCount; - long dist = nextTick - tick; + var dist = nextTick - tick; if (dist <= 0) { - if (_queuedFrames.TryDequeue(out Frame frame)) + if (_queuedFrames.TryDequeue(out var frame)) { await _client.SetSpeakingAsync(true).ConfigureAwait(false); _next.WriteHeader(seq, timestamp, false); @@ -94,62 +88,68 @@ namespace Discord.Audio.Streams _queueLock.Release(); nextTick += _ticksPerFrame; seq++; - timestamp += OpusEncoder.FrameSamplesPerChannel; + timestamp += OpusConverter.FrameSamplesPerChannel; _silenceFrames = 0; #if DEBUG - var _ = _logger?.DebugAsync($"Sent {frame.Bytes} bytes ({_queuedFrames.Count} frames buffered)"); + var _ = _logger?.DebugAsync( + $"Sent {frame.Bytes} bytes ({_queuedFrames.Count} frames buffered)"); #endif } else { - while ((nextTick - tick) <= 0) + while (nextTick - tick <= 0) { if (_silenceFrames++ < MaxSilenceFrames) { _next.WriteHeader(seq, timestamp, false); - await _next.WriteAsync(_silenceFrame, 0, _silenceFrame.Length).ConfigureAwait(false); + await _next.WriteAsync(_silenceFrame, 0, _silenceFrame.Length) + .ConfigureAwait(false); } else await _client.SetSpeakingAsync(false).ConfigureAwait(false); + nextTick += _ticksPerFrame; seq++; - timestamp += OpusEncoder.FrameSamplesPerChannel; + timestamp += OpusConverter.FrameSamplesPerChannel; } #if DEBUG - var _ = _logger?.DebugAsync($"Buffer underrun"); + var _ = _logger?.DebugAsync("Buffer underrun"); #endif } } else - await Task.Delay((int)(dist)/*, _cancelToken*/).ConfigureAwait(false); + await Task.Delay((int)dist /*, _cancelToken*/).ConfigureAwait(false); } } - catch (OperationCanceledException) { } + catch (OperationCanceledException) + { + } }); } - public override void WriteHeader(ushort seq, uint timestamp, bool missed) { } //Ignore, we use our own timing + public override void WriteHeader(ushort seq, uint timestamp, bool missed) + { + } //Ignore, we use our own timing + public override async Task WriteAsync(byte[] data, int offset, int count, CancellationToken cancelToken) { - if (cancelToken.CanBeCanceled) - cancelToken = CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _cancelToken).Token; - else - cancelToken = _cancelToken; + cancelToken = cancelToken.CanBeCanceled ? CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _cancelToken).Token : _cancelToken; await _queueLock.WaitAsync(-1, cancelToken).ConfigureAwait(false); - if (!_bufferPool.TryDequeue(out byte[] buffer)) + if (!_bufferPool.TryDequeue(out var buffer)) { #if DEBUG - var _ = _logger?.DebugAsync($"Buffer overflow"); //Should never happen because of the queueLock + var _ = _logger?.DebugAsync("Buffer overflow"); //Should never happen because of the queueLock #endif return; } + Buffer.BlockCopy(data, offset, buffer, 0, count); _queuedFrames.Enqueue(new Frame(buffer, count)); if (!_isPreloaded && _queuedFrames.Count == _queueLength) { #if DEBUG - var _ = _logger?.DebugAsync($"Preloaded"); + var _ = _logger?.DebugAsync("Preloaded"); #endif _isPreloaded = true; } @@ -165,12 +165,25 @@ namespace Discord.Audio.Streams await Task.Delay(250, cancelToken).ConfigureAwait(false); } } + public override Task ClearAsync(CancellationToken cancelToken) { do cancelToken.ThrowIfCancellationRequested(); - while (_queuedFrames.TryDequeue(out Frame ignored)); + while (_queuedFrames.TryDequeue(out var ignored)); return Task.Delay(0); } + + private struct Frame + { + public Frame(byte[] buffer, int bytes) + { + Buffer = buffer; + Bytes = bytes; + } + + public readonly byte[] Buffer; + public readonly int Bytes; + } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.WebSocket/Audio/Streams/InputStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/InputStream.cs index b9d6157ea..d8449d422 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/InputStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/InputStream.cs @@ -10,18 +10,13 @@ namespace Discord.Audio.Streams { private const int MaxFrames = 100; //1-2 Seconds - private ConcurrentQueue _frames; - private SemaphoreSlim _signal; - private ushort _nextSeq; - private uint _nextTimestamp; - private bool _nextMissed; + private readonly ConcurrentQueue _frames; private bool _hasHeader; private bool _isDisposed; - - public override bool CanRead => !_isDisposed; - public override bool CanSeek => false; - public override bool CanWrite => false; - public override int AvailableFrames => _signal.CurrentCount; + private bool _nextMissed; + private ushort _nextSeq; + private uint _nextTimestamp; + private readonly SemaphoreSlim _signal; public InputStream() { @@ -29,6 +24,11 @@ namespace Discord.Audio.Streams _signal = new SemaphoreSlim(0, MaxFrames); } + public override bool CanRead => !_isDisposed; + public override bool CanSeek => false; + public override bool CanWrite => false; + public override int AvailableFrames => _signal.CurrentCount; + public override bool TryReadFrame(CancellationToken cancelToken, out RTPFrame frame) { cancelToken.ThrowIfCancellationRequested(); @@ -38,9 +38,11 @@ namespace Discord.Audio.Streams _frames.TryDequeue(out frame); return true; } + frame = default(RTPFrame); return false; } + public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { cancelToken.ThrowIfCancellationRequested(); @@ -51,12 +53,13 @@ namespace Discord.Audio.Streams Buffer.BlockCopy(frame.Payload, 0, buffer, offset, frame.Payload.Length); return frame.Payload.Length; } + public override async Task ReadFrameAsync(CancellationToken cancelToken) { cancelToken.ThrowIfCancellationRequested(); await _signal.WaitAsync(cancelToken).ConfigureAwait(false); - _frames.TryDequeue(out RTPFrame frame); + _frames.TryDequeue(out var frame); return frame; } @@ -69,6 +72,7 @@ namespace Discord.Audio.Streams _nextTimestamp = timestamp; _nextMissed = missed; } + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { cancelToken.ThrowIfCancellationRequested(); @@ -78,15 +82,16 @@ namespace Discord.Audio.Streams _hasHeader = false; return Task.Delay(0); //Buffer overloaded } + if (!_hasHeader) throw new InvalidOperationException("Received payload without an RTP header"); _hasHeader = false; - byte[] payload = new byte[count]; + var payload = new byte[count]; Buffer.BlockCopy(buffer, offset, payload, 0, count); _frames.Enqueue(new RTPFrame( - sequence: _nextSeq, - timestamp: _nextTimestamp, + _nextSeq, + _nextTimestamp, missed: _nextMissed, payload: payload )); @@ -94,9 +99,6 @@ namespace Discord.Audio.Streams return Task.Delay(0); } - protected override void Dispose(bool isDisposing) - { - _isDisposed = true; - } + protected override void Dispose(bool isDisposing) => _isDisposed = true; } } diff --git a/src/Discord.Net.WebSocket/Audio/Streams/JitterBuffer.cs b/src/Discord.Net.WebSocket/Audio/Streams/JitterBuffer.cs index 10f842a9d..be54ae6fe 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/JitterBuffer.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/JitterBuffer.cs @@ -243,4 +243,6 @@ namespace Discord.Audio.Streams return Task.Delay(0); } } -}*/ \ No newline at end of file +}*/ + + diff --git a/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs index 58c4f4c70..6962f08fd 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs @@ -8,12 +8,12 @@ namespace Discord.Audio.Streams public class OpusDecodeStream : AudioOutStream { public const int SampleRate = OpusEncodeStream.SampleRate; + private readonly byte[] _buffer; + private readonly OpusDecoder _decoder; private readonly AudioStream _next; - private readonly OpusDecoder _decoder; - private readonly byte[] _buffer; - private bool _nextMissed; private bool _hasHeader; + private bool _nextMissed; public OpusDecodeStream(AudioStream next) { @@ -25,12 +25,13 @@ namespace Discord.Audio.Streams public override void WriteHeader(ushort seq, uint timestamp, bool missed) { if (_hasHeader) - throw new InvalidOperationException("Header received with no payload"); + throw new InvalidOperationException("Header received with no payload"); _hasHeader = true; _nextMissed = missed; _next.WriteHeader(seq, timestamp, missed); } + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { if (!_hasHeader) @@ -54,14 +55,11 @@ namespace Discord.Audio.Streams } } - public override async Task FlushAsync(CancellationToken cancelToken) - { + public override async Task FlushAsync(CancellationToken cancelToken) => await _next.FlushAsync(cancelToken).ConfigureAwait(false); - } - public override async Task ClearAsync(CancellationToken cancelToken) - { + + public override async Task ClearAsync(CancellationToken cancelToken) => await _next.ClearAsync(cancelToken).ConfigureAwait(false); - } protected override void Dispose(bool disposing) { diff --git a/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs index f5883ad4b..6dbc8c998 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/OpusEncodeStream.cs @@ -8,14 +8,14 @@ namespace Discord.Audio.Streams public class OpusEncodeStream : AudioOutStream { public const int SampleRate = 48000; + private readonly byte[] _buffer; + private readonly OpusEncoder _encoder; private readonly AudioStream _next; - private readonly OpusEncoder _encoder; - private readonly byte[] _buffer; private int _partialFramePos; private ushort _seq; private uint _timestamp; - + public OpusEncodeStream(AudioStream next, int bitrate, AudioApplication application, int packetLoss) { _next = next; @@ -27,11 +27,10 @@ namespace Discord.Audio.Streams { //Assume threadsafe while (count > 0) - { if (_partialFramePos == 0 && count >= OpusConverter.FrameBytes) { //We have enough data and no partial frames. Pass the buffer directly to the encoder - int encFrameSize = _encoder.EncodeFrame(buffer, offset, _buffer, 0); + var encFrameSize = _encoder.EncodeFrame(buffer, offset, _buffer, 0); _next.WriteHeader(_seq, _timestamp, false); await _next.WriteAsync(_buffer, 0, encFrameSize, cancelToken).ConfigureAwait(false); @@ -43,9 +42,9 @@ namespace Discord.Audio.Streams else if (_partialFramePos + count >= OpusConverter.FrameBytes) { //We have enough data to complete a previous partial frame. - int partialSize = OpusConverter.FrameBytes - _partialFramePos; + var partialSize = OpusConverter.FrameBytes - _partialFramePos; Buffer.BlockCopy(buffer, offset, _buffer, _partialFramePos, partialSize); - int encFrameSize = _encoder.EncodeFrame(_buffer, 0, _buffer, 0); + var encFrameSize = _encoder.EncodeFrame(_buffer, 0, _buffer, 0); _next.WriteHeader(_seq, _timestamp, false); await _next.WriteAsync(_buffer, 0, encFrameSize, cancelToken).ConfigureAwait(false); @@ -62,7 +61,6 @@ namespace Discord.Audio.Streams _partialFramePos += count; break; } - } } /* //Opus throws memory errors on bad frames @@ -78,14 +76,11 @@ namespace Discord.Audio.Streams await base.FlushAsync(cancelToken).ConfigureAwait(false); }*/ - public override async Task FlushAsync(CancellationToken cancelToken) - { + public override async Task FlushAsync(CancellationToken cancelToken) => await _next.FlushAsync(cancelToken).ConfigureAwait(false); - } - public override async Task ClearAsync(CancellationToken cancelToken) - { + + public override async Task ClearAsync(CancellationToken cancelToken) => await _next.ClearAsync(cancelToken).ConfigureAwait(false); - } protected override void Dispose(bool disposing) { diff --git a/src/Discord.Net.WebSocket/Audio/Streams/OutputStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/OutputStream.cs index cba4e3cb6..d089d059a 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/OutputStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/OutputStream.cs @@ -7,18 +7,25 @@ namespace Discord.Audio.Streams public class OutputStream : AudioOutStream { private readonly DiscordVoiceAPIClient _client; + public OutputStream(IAudioClient client) - : this((client as AudioClient).ApiClient) { } + : this((client as AudioClient).ApiClient) + { + } + internal OutputStream(DiscordVoiceAPIClient client) { _client = client; } - - public override void WriteHeader(ushort seq, uint timestamp, bool missed) { } //Ignore + + public override void WriteHeader(ushort seq, uint timestamp, bool missed) + { + } //Ignore + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { cancelToken.ThrowIfCancellationRequested(); await _client.SendAsync(buffer, offset, count).ConfigureAwait(false); } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs index 2cedea114..da785f98d 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/RTPReadStream.cs @@ -6,12 +6,8 @@ namespace Discord.Audio.Streams /// Reads the payload from an RTP frame public class RTPReadStream : AudioOutStream { - private readonly AudioStream _next; private readonly byte[] _buffer, _nonce; - - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => true; + private readonly AudioStream _next; public RTPReadStream(AudioStream next, int bufferSize = 4000) { @@ -20,19 +16,23 @@ namespace Discord.Audio.Streams _nonce = new byte[24]; } + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => true; + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { cancelToken.ThrowIfCancellationRequested(); - int headerSize = GetHeaderSize(buffer, offset); + var headerSize = GetHeaderSize(buffer, offset); - ushort seq = (ushort)((buffer[offset + 2] << 8) | - (buffer[offset + 3] << 0)); + var seq = (ushort)((buffer[offset + 2] << 8) | + (buffer[offset + 3] << 0)); - uint timestamp = (uint)((buffer[offset + 4] << 24) | - (buffer[offset + 5] << 16) | - (buffer[offset + 6] << 8) | - (buffer[offset + 7] << 0)); + var timestamp = (uint)((buffer[offset + 4] << 24) | + (buffer[offset + 5] << 16) | + (buffer[offset + 6] << 8) | + (buffer[offset + 7] << 0)); _next.WriteHeader(seq, timestamp, false); await _next.WriteAsync(buffer, offset + headerSize, count - headerSize, cancelToken).ConfigureAwait(false); @@ -43,35 +43,35 @@ namespace Discord.Audio.Streams ssrc = 0; if (buffer.Length - offset < 12) return false; - - int version = (buffer[offset + 0] & 0b1100_0000) >> 6; + + var version = (buffer[offset + 0] & 0b1100_0000) >> 6; if (version != 2) return false; - int type = (buffer[offset + 1] & 0b01111_1111); + var type = buffer[offset + 1] & 0b01111_1111; if (type != 120) //Dynamic Discord type return false; ssrc = (uint)((buffer[offset + 8] << 24) | - (buffer[offset + 9] << 16) | - (buffer[offset + 10] << 8) | - (buffer[offset + 11] << 0)); + (buffer[offset + 9] << 16) | + (buffer[offset + 10] << 8) | + (buffer[offset + 11] << 0)); return true; } public static int GetHeaderSize(byte[] buffer, int offset) { - byte headerByte = buffer[offset]; - bool extension = (headerByte & 0b0001_0000) != 0; - int csics = (headerByte & 0b0000_1111) >> 4; + var headerByte = buffer[offset]; + var extension = (headerByte & 0b0001_0000) != 0; + var csics = (headerByte & 0b0000_1111) >> 4; if (!extension) return 12 + csics * 4; - int extensionOffset = offset + 12 + (csics * 4); - int extensionLength = - (buffer[extensionOffset + 2] << 8) | - (buffer[extensionOffset + 3]); - return extensionOffset + 4 + (extensionLength * 4); + var extensionOffset = offset + 12 + csics * 4; + var extensionLength = + (buffer[extensionOffset + 2] << 8) | + buffer[extensionOffset + 3]; + return extensionOffset + 4 + extensionLength * 4; } } } diff --git a/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs index ce407eada..34133116d 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs @@ -7,13 +7,13 @@ namespace Discord.Audio.Streams /// Wraps data in an RTP frame public class RTPWriteStream : AudioOutStream { - private readonly AudioStream _next; - private readonly byte[] _header; protected readonly byte[] _buffer; - private uint _ssrc; + private readonly byte[] _header; + private readonly AudioStream _next; + private bool _hasHeader; private ushort _nextSeq; private uint _nextTimestamp; - private bool _hasHeader; + private readonly uint _ssrc; public RTPWriteStream(AudioStream next, uint ssrc, int bufferSize = 4000) { @@ -33,11 +33,12 @@ namespace Discord.Audio.Streams { if (_hasHeader) throw new InvalidOperationException("Header received with no payload"); - + _hasHeader = true; _nextSeq = seq; _nextTimestamp = timestamp; } + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { cancelToken.ThrowIfCancellationRequested(); @@ -54,6 +55,7 @@ namespace Discord.Audio.Streams _header[6] = (byte)(_nextTimestamp >> 8); _header[7] = (byte)(_nextTimestamp >> 0); } + Buffer.BlockCopy(_header, 0, _buffer, 0, 12); //Copy RTP header from to the buffer Buffer.BlockCopy(buffer, offset, _buffer, 12, count); @@ -61,13 +63,10 @@ namespace Discord.Audio.Streams await _next.WriteAsync(_buffer, 0, count + 12).ConfigureAwait(false); } - public override async Task FlushAsync(CancellationToken cancelToken) - { + public override async Task FlushAsync(CancellationToken cancelToken) => await _next.FlushAsync(cancelToken).ConfigureAwait(false); - } - public override async Task ClearAsync(CancellationToken cancelToken) - { + + public override async Task ClearAsync(CancellationToken cancelToken) => await _next.ClearAsync(cancelToken).ConfigureAwait(false); - } } } diff --git a/src/Discord.Net.WebSocket/Audio/Streams/SodiumDecryptStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/SodiumDecryptStream.cs index 9ed849a5e..d79d5533d 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/SodiumDecryptStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/SodiumDecryptStream.cs @@ -11,10 +11,6 @@ namespace Discord.Audio.Streams private readonly AudioStream _next; private readonly byte[] _nonce; - public override bool CanRead => true; - public override bool CanSeek => false; - public override bool CanWrite => true; - public SodiumDecryptStream(AudioStream next, IAudioClient client) { _next = next; @@ -22,6 +18,10 @@ namespace Discord.Audio.Streams _nonce = new byte[24]; } + public override bool CanRead => true; + public override bool CanSeek => false; + public override bool CanWrite => true; + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { cancelToken.ThrowIfCancellationRequested(); @@ -34,13 +34,10 @@ namespace Discord.Audio.Streams await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false); } - public override async Task FlushAsync(CancellationToken cancelToken) - { + public override async Task FlushAsync(CancellationToken cancelToken) => await _next.FlushAsync(cancelToken).ConfigureAwait(false); - } - public override async Task ClearAsync(CancellationToken cancelToken) - { + + public override async Task ClearAsync(CancellationToken cancelToken) => await _next.ClearAsync(cancelToken).ConfigureAwait(false); - } } } diff --git a/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs index bacc9be47..00e929105 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/SodiumEncryptStream.cs @@ -20,7 +20,7 @@ namespace Discord.Audio.Streams _client = (AudioClient)client; _nonce = new byte[24]; } - + public override void WriteHeader(ushort seq, uint timestamp, bool missed) { if (_hasHeader) @@ -30,6 +30,7 @@ namespace Discord.Audio.Streams _nextTimestamp = timestamp; _hasHeader = true; } + public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancelToken) { cancelToken.ThrowIfCancellationRequested(); @@ -39,20 +40,17 @@ namespace Discord.Audio.Streams if (_client.SecretKey == null) return; - + Buffer.BlockCopy(buffer, offset, _nonce, 0, 12); //Copy nonce from RTP header count = SecretBox.Encrypt(buffer, offset + 12, count - 12, buffer, 12, _nonce, _client.SecretKey); _next.WriteHeader(_nextSeq, _nextTimestamp, false); await _next.WriteAsync(buffer, 0, count + 12, cancelToken).ConfigureAwait(false); } - public override async Task FlushAsync(CancellationToken cancelToken) - { + public override async Task FlushAsync(CancellationToken cancelToken) => await _next.FlushAsync(cancelToken).ConfigureAwait(false); - } - public override async Task ClearAsync(CancellationToken cancelToken) - { + + public override async Task ClearAsync(CancellationToken cancelToken) => await _next.ClearAsync(cancelToken).ConfigureAwait(false); - } } } diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs index c236b1045..f50b6a182 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.Events.cs @@ -5,196 +5,313 @@ namespace Discord.WebSocket { public partial class BaseSocketClient { + internal readonly AsyncEvent> _channelCreatedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _channelDestroyedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _channelUpdatedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _guildAvailableEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _guildMembersDownloadedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _guildMemberUpdatedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _guildUnavailableEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _guildUpdatedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _joinedGuildEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _leftGuildEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent, ISocketMessageChannel, Task>> _messageDeletedEvent + = new AsyncEvent, ISocketMessageChannel, Task>>(); + + internal readonly AsyncEvent> _messageReceivedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent, SocketMessage, ISocketMessageChannel, Task>> + _messageUpdatedEvent = + new AsyncEvent, SocketMessage, ISocketMessageChannel, Task>>(); + + internal readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> + _reactionAddedEvent = + new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + + internal readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> + _reactionRemovedEvent = + new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + + internal readonly AsyncEvent, ISocketMessageChannel, Task>> + _reactionsClearedEvent = + new AsyncEvent, ISocketMessageChannel, Task>>(); + + internal readonly AsyncEvent> _recipientAddedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _recipientRemovedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _roleCreatedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _roleDeletedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _roleUpdatedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _selfUpdatedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _userBannedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _userIsTypingEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _userJoinedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _userLeftEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _userUnbannedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> _userUpdatedEvent = + new AsyncEvent>(); + + internal readonly AsyncEvent> + _userVoiceStateUpdatedEvent = new AsyncEvent>(); + + internal readonly AsyncEvent> _voiceServerUpdatedEvent = + new AsyncEvent>(); + //Channels /// Fired when a channel is created. - public event Func ChannelCreated + public event Func ChannelCreated { - add { _channelCreatedEvent.Add(value); } - remove { _channelCreatedEvent.Remove(value); } + add => _channelCreatedEvent.Add(value); + remove => _channelCreatedEvent.Remove(value); } - internal readonly AsyncEvent> _channelCreatedEvent = new AsyncEvent>(); + /// Fired when a channel is destroyed. - public event Func ChannelDestroyed { - add { _channelDestroyedEvent.Add(value); } - remove { _channelDestroyedEvent.Remove(value); } + public event Func ChannelDestroyed + { + add => _channelDestroyedEvent.Add(value); + remove => _channelDestroyedEvent.Remove(value); } - internal readonly AsyncEvent> _channelDestroyedEvent = new AsyncEvent>(); + /// Fired when a channel is updated. - public event Func ChannelUpdated { - add { _channelUpdatedEvent.Add(value); } - remove { _channelUpdatedEvent.Remove(value); } - } - internal readonly AsyncEvent> _channelUpdatedEvent = new AsyncEvent>(); + public event Func ChannelUpdated + { + add => _channelUpdatedEvent.Add(value); + remove => _channelUpdatedEvent.Remove(value); + } //Messages /// Fired when a message is received. - public event Func MessageReceived { - add { _messageReceivedEvent.Add(value); } - remove { _messageReceivedEvent.Remove(value); } + public event Func MessageReceived + { + add => _messageReceivedEvent.Add(value); + remove => _messageReceivedEvent.Remove(value); } - internal readonly AsyncEvent> _messageReceivedEvent = new AsyncEvent>(); + /// Fired when a message is deleted. - public event Func, ISocketMessageChannel, Task> MessageDeleted { - add { _messageDeletedEvent.Add(value); } - remove { _messageDeletedEvent.Remove(value); } + public event Func, ISocketMessageChannel, Task> MessageDeleted + { + add => _messageDeletedEvent.Add(value); + remove => _messageDeletedEvent.Remove(value); } - internal readonly AsyncEvent, ISocketMessageChannel, Task>> _messageDeletedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); + /// Fired when a message is updated. - public event Func, SocketMessage, ISocketMessageChannel, Task> MessageUpdated { - add { _messageUpdatedEvent.Add(value); } - remove { _messageUpdatedEvent.Remove(value); } + public event Func, SocketMessage, ISocketMessageChannel, Task> MessageUpdated + { + add => _messageUpdatedEvent.Add(value); + remove => _messageUpdatedEvent.Remove(value); } - internal readonly AsyncEvent, SocketMessage, ISocketMessageChannel, Task>> _messageUpdatedEvent = new AsyncEvent, SocketMessage, ISocketMessageChannel, Task>>(); + /// Fired when a reaction is added to a message. - public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionAdded { - add { _reactionAddedEvent.Add(value); } - remove { _reactionAddedEvent.Remove(value); } + public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionAdded + { + add => _reactionAddedEvent.Add(value); + remove => _reactionAddedEvent.Remove(value); } - internal readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionAddedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + /// Fired when a reaction is removed from a message. - public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved { - add { _reactionRemovedEvent.Add(value); } - remove { _reactionRemovedEvent.Remove(value); } + public event Func, ISocketMessageChannel, SocketReaction, Task> ReactionRemoved + { + add => _reactionRemovedEvent.Add(value); + remove => _reactionRemovedEvent.Remove(value); } - internal readonly AsyncEvent, ISocketMessageChannel, SocketReaction, Task>> _reactionRemovedEvent = new AsyncEvent, ISocketMessageChannel, SocketReaction, Task>>(); + /// Fired when all reactions to a message are cleared. - public event Func, ISocketMessageChannel, Task> ReactionsCleared { - add { _reactionsClearedEvent.Add(value); } - remove { _reactionsClearedEvent.Remove(value); } + public event Func, ISocketMessageChannel, Task> ReactionsCleared + { + add => _reactionsClearedEvent.Add(value); + remove => _reactionsClearedEvent.Remove(value); } - internal readonly AsyncEvent, ISocketMessageChannel, Task>> _reactionsClearedEvent = new AsyncEvent, ISocketMessageChannel, Task>>(); //Roles /// Fired when a role is created. - public event Func RoleCreated { - add { _roleCreatedEvent.Add(value); } - remove { _roleCreatedEvent.Remove(value); } + public event Func RoleCreated + { + add => _roleCreatedEvent.Add(value); + remove => _roleCreatedEvent.Remove(value); } - internal readonly AsyncEvent> _roleCreatedEvent = new AsyncEvent>(); + /// Fired when a role is deleted. - public event Func RoleDeleted { - add { _roleDeletedEvent.Add(value); } - remove { _roleDeletedEvent.Remove(value); } + public event Func RoleDeleted + { + add => _roleDeletedEvent.Add(value); + remove => _roleDeletedEvent.Remove(value); } - internal readonly AsyncEvent> _roleDeletedEvent = new AsyncEvent>(); + /// Fired when a role is updated. - public event Func RoleUpdated { - add { _roleUpdatedEvent.Add(value); } - remove { _roleUpdatedEvent.Remove(value); } + public event Func RoleUpdated + { + add => _roleUpdatedEvent.Add(value); + remove => _roleUpdatedEvent.Remove(value); } - internal readonly AsyncEvent> _roleUpdatedEvent = new AsyncEvent>(); //Guilds /// Fired when the connected account joins a guild. - public event Func JoinedGuild { - add { _joinedGuildEvent.Add(value); } - remove { _joinedGuildEvent.Remove(value); } + public event Func JoinedGuild + { + add => _joinedGuildEvent.Add(value); + remove => _joinedGuildEvent.Remove(value); } - internal readonly AsyncEvent> _joinedGuildEvent = new AsyncEvent>(); + /// Fired when the connected account leaves a guild. - public event Func LeftGuild { - add { _leftGuildEvent.Add(value); } - remove { _leftGuildEvent.Remove(value); } + public event Func LeftGuild + { + add => _leftGuildEvent.Add(value); + remove => _leftGuildEvent.Remove(value); } - internal readonly AsyncEvent> _leftGuildEvent = new AsyncEvent>(); + /// Fired when a guild becomes available. - public event Func GuildAvailable { - add { _guildAvailableEvent.Add(value); } - remove { _guildAvailableEvent.Remove(value); } + public event Func GuildAvailable + { + add => _guildAvailableEvent.Add(value); + remove => _guildAvailableEvent.Remove(value); } - internal readonly AsyncEvent> _guildAvailableEvent = new AsyncEvent>(); + /// Fired when a guild becomes unavailable. - public event Func GuildUnavailable { - add { _guildUnavailableEvent.Add(value); } - remove { _guildUnavailableEvent.Remove(value); } + public event Func GuildUnavailable + { + add => _guildUnavailableEvent.Add(value); + remove => _guildUnavailableEvent.Remove(value); } - internal readonly AsyncEvent> _guildUnavailableEvent = new AsyncEvent>(); + /// Fired when offline guild members are downloaded. - public event Func GuildMembersDownloaded { - add { _guildMembersDownloadedEvent.Add(value); } - remove { _guildMembersDownloadedEvent.Remove(value); } + public event Func GuildMembersDownloaded + { + add => _guildMembersDownloadedEvent.Add(value); + remove => _guildMembersDownloadedEvent.Remove(value); } - internal readonly AsyncEvent> _guildMembersDownloadedEvent = new AsyncEvent>(); + /// Fired when a guild is updated. - public event Func GuildUpdated { - add { _guildUpdatedEvent.Add(value); } - remove { _guildUpdatedEvent.Remove(value); } + public event Func GuildUpdated + { + add => _guildUpdatedEvent.Add(value); + remove => _guildUpdatedEvent.Remove(value); } - internal readonly AsyncEvent> _guildUpdatedEvent = new AsyncEvent>(); //Users /// Fired when a user joins a guild. - public event Func UserJoined { - add { _userJoinedEvent.Add(value); } - remove { _userJoinedEvent.Remove(value); } + public event Func UserJoined + { + add => _userJoinedEvent.Add(value); + remove => _userJoinedEvent.Remove(value); } - internal readonly AsyncEvent> _userJoinedEvent = new AsyncEvent>(); + /// Fired when a user leaves a guild. - public event Func UserLeft { - add { _userLeftEvent.Add(value); } - remove { _userLeftEvent.Remove(value); } + public event Func UserLeft + { + add => _userLeftEvent.Add(value); + remove => _userLeftEvent.Remove(value); } - internal readonly AsyncEvent> _userLeftEvent = new AsyncEvent>(); + /// Fired when a user is banned from a guild. - public event Func UserBanned { - add { _userBannedEvent.Add(value); } - remove { _userBannedEvent.Remove(value); } + public event Func UserBanned + { + add => _userBannedEvent.Add(value); + remove => _userBannedEvent.Remove(value); } - internal readonly AsyncEvent> _userBannedEvent = new AsyncEvent>(); + /// Fired when a user is unbanned from a guild. - public event Func UserUnbanned { - add { _userUnbannedEvent.Add(value); } - remove { _userUnbannedEvent.Remove(value); } + public event Func UserUnbanned + { + add => _userUnbannedEvent.Add(value); + remove => _userUnbannedEvent.Remove(value); } - internal readonly AsyncEvent> _userUnbannedEvent = new AsyncEvent>(); + /// Fired when a user is updated. - public event Func UserUpdated { - add { _userUpdatedEvent.Add(value); } - remove { _userUpdatedEvent.Remove(value); } + public event Func UserUpdated + { + add => _userUpdatedEvent.Add(value); + remove => _userUpdatedEvent.Remove(value); } - internal readonly AsyncEvent> _userUpdatedEvent = new AsyncEvent>(); + /// Fired when a guild member is updated, or a member presence is updated. - public event Func GuildMemberUpdated { - add { _guildMemberUpdatedEvent.Add(value); } - remove { _guildMemberUpdatedEvent.Remove(value); } + public event Func GuildMemberUpdated + { + add => _guildMemberUpdatedEvent.Add(value); + remove => _guildMemberUpdatedEvent.Remove(value); } - internal readonly AsyncEvent> _guildMemberUpdatedEvent = new AsyncEvent>(); + /// Fired when a user joins, leaves, or moves voice channels. - public event Func UserVoiceStateUpdated { - add { _userVoiceStateUpdatedEvent.Add(value); } - remove { _userVoiceStateUpdatedEvent.Remove(value); } + public event Func UserVoiceStateUpdated + { + add => _userVoiceStateUpdatedEvent.Add(value); + remove => _userVoiceStateUpdatedEvent.Remove(value); } - internal readonly AsyncEvent> _userVoiceStateUpdatedEvent = new AsyncEvent>(); + /// Fired when the bot connects to a Discord voice server. public event Func VoiceServerUpdated { - add { _voiceServerUpdatedEvent.Add(value); } - remove { _voiceServerUpdatedEvent.Remove(value); } + add => _voiceServerUpdatedEvent.Add(value); + remove => _voiceServerUpdatedEvent.Remove(value); } - internal readonly AsyncEvent> _voiceServerUpdatedEvent = new AsyncEvent>(); + /// Fired when the connected account is updated. - public event Func CurrentUserUpdated { - add { _selfUpdatedEvent.Add(value); } - remove { _selfUpdatedEvent.Remove(value); } + public event Func CurrentUserUpdated + { + add => _selfUpdatedEvent.Add(value); + remove => _selfUpdatedEvent.Remove(value); } - internal readonly AsyncEvent> _selfUpdatedEvent = new AsyncEvent>(); + /// Fired when a user starts typing. - public event Func UserIsTyping { - add { _userIsTypingEvent.Add(value); } - remove { _userIsTypingEvent.Remove(value); } + public event Func UserIsTyping + { + add => _userIsTypingEvent.Add(value); + remove => _userIsTypingEvent.Remove(value); } - internal readonly AsyncEvent> _userIsTypingEvent = new AsyncEvent>(); + /// Fired when a user joins a group channel. - public event Func RecipientAdded { - add { _recipientAddedEvent.Add(value); } - remove { _recipientAddedEvent.Remove(value); } + public event Func RecipientAdded + { + add => _recipientAddedEvent.Add(value); + remove => _recipientAddedEvent.Remove(value); } - internal readonly AsyncEvent> _recipientAddedEvent = new AsyncEvent>(); + /// Fired when a user is removed from a group channel. - public event Func RecipientRemoved { - add { _recipientRemovedEvent.Add(value); } - remove { _recipientRemovedEvent.Remove(value); } + public event Func RecipientRemoved + { + add => _recipientRemovedEvent.Add(value); + remove => _recipientRemovedEvent.Remove(value); } - internal readonly AsyncEvent> _recipientRemovedEvent = new AsyncEvent>(); } } diff --git a/src/Discord.Net.WebSocket/BaseSocketClient.cs b/src/Discord.Net.WebSocket/BaseSocketClient.cs index a7d590b42..8b121f34c 100644 --- a/src/Discord.Net.WebSocket/BaseSocketClient.cs +++ b/src/Discord.Net.WebSocket/BaseSocketClient.cs @@ -10,61 +10,45 @@ namespace Discord.WebSocket { protected readonly DiscordSocketConfig _baseconfig; + internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) + : base(config, client) + { + _baseconfig = config; + } + /// Gets the estimated round-trip latency, in milliseconds, to the gateway server. public abstract int Latency { get; protected set; } - public abstract UserStatus Status { get; protected set; } + + public abstract UserStatus Status { get; protected set; } public abstract IActivity Activity { get; protected set; } internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; - public new SocketSelfUser CurrentUser { get => base.CurrentUser as SocketSelfUser; protected set => base.CurrentUser = value; } + public new SocketSelfUser CurrentUser + { + get => base.CurrentUser as SocketSelfUser; + protected set => base.CurrentUser = value; + } + public abstract IReadOnlyCollection Guilds { get; } public abstract IReadOnlyCollection PrivateChannels { get; } public abstract IReadOnlyCollection VoiceRegions { get; } - internal BaseSocketClient(DiscordSocketConfig config, DiscordRestApiClient client) - : base(config, client) => _baseconfig = config; - private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) - => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); - - /// - public abstract Task GetApplicationInfoAsync(RequestOptions options = null); - /// - public abstract SocketUser GetUser(ulong id); - /// - public abstract SocketUser GetUser(string username, string discriminator); - /// - public abstract SocketChannel GetChannel(ulong id); - /// - public abstract SocketGuild GetGuild(ulong id); - /// - public abstract RestVoiceRegion GetVoiceRegion(string id); /// public abstract Task StartAsync(); + /// public abstract Task StopAsync(); - public abstract Task SetStatusAsync(UserStatus status); - public abstract Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing); - public abstract Task SetActivityAsync(IActivity activity); - public abstract Task DownloadUsersAsync(IEnumerable guilds); - /// - public Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, RequestOptions options = null) - => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options ?? RequestOptions.Default); - /// - public Task> GetConnectionsAsync(RequestOptions options = null) - => ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); - /// - public Task GetInviteAsync(string inviteId, RequestOptions options = null) - => ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); - // IDiscordClient async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) => await GetApplicationInfoAsync(options).ConfigureAwait(false); Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetChannel(id)); - Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) + + Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, + RequestOptions options) => Task.FromResult>(PrivateChannels); async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) @@ -75,20 +59,66 @@ namespace Discord.WebSocket Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetGuild(id)); + Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) => Task.FromResult>(Guilds); - async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) + async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, + RequestOptions options) => await CreateGuildAsync(name, region, jpegIcon, options).ConfigureAwait(false); Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) => Task.FromResult(GetUser(username, discriminator)); Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) => Task.FromResult(GetVoiceRegion(id)); + Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) => Task.FromResult>(VoiceRegions); + + private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) + => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordConfig.UserAgent); + + /// + public abstract Task GetApplicationInfoAsync(RequestOptions options = null); + + /// + public abstract SocketUser GetUser(ulong id); + + /// + public abstract SocketUser GetUser(string username, string discriminator); + + /// + public abstract SocketChannel GetChannel(ulong id); + + /// + public abstract SocketGuild GetGuild(ulong id); + + /// + public abstract RestVoiceRegion GetVoiceRegion(string id); + + public abstract Task SetStatusAsync(UserStatus status); + + public abstract Task SetGameAsync(string name, string streamUrl = null, + ActivityType type = ActivityType.Playing); + + public abstract Task SetActivityAsync(IActivity activity); + public abstract Task DownloadUsersAsync(IEnumerable guilds); + + /// + public Task CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon = null, + RequestOptions options = null) + => ClientHelper.CreateGuildAsync(this, name, region, jpegIcon, options ?? RequestOptions.Default); + + /// + public Task> GetConnectionsAsync(RequestOptions options = null) + => ClientHelper.GetConnectionsAsync(this, options ?? RequestOptions.Default); + + /// + public Task GetInviteAsync(string inviteId, RequestOptions options = null) + => ClientHelper.GetInviteAsync(this, inviteId, options ?? RequestOptions.Default); } } diff --git a/src/Discord.Net.WebSocket/ClientState.cs b/src/Discord.Net.WebSocket/ClientState.cs index f07976a0a..525a8f173 100644 --- a/src/Discord.Net.WebSocket/ClientState.cs +++ b/src/Discord.Net.WebSocket/ClientState.cs @@ -13,44 +13,50 @@ namespace Discord.WebSocket private readonly ConcurrentDictionary _channels; private readonly ConcurrentDictionary _dmChannels; + private readonly ConcurrentHashSet _groupChannels; private readonly ConcurrentDictionary _guilds; private readonly ConcurrentDictionary _users; - private readonly ConcurrentHashSet _groupChannels; + + public ClientState(int guildCount, int dmChannelCount) + { + var estimatedChannelCount = guildCount * AverageChannelsPerGuild + dmChannelCount; + var estimatedUsersCount = guildCount * AverageUsersPerGuild; + _channels = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, + (int)(estimatedChannelCount * CollectionMultiplier)); + _dmChannels = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, + (int)(dmChannelCount * CollectionMultiplier)); + _guilds = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, + (int)(guildCount * CollectionMultiplier)); + _users = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, + (int)(estimatedUsersCount * CollectionMultiplier)); + _groupChannels = new ConcurrentHashSet(ConcurrentHashSet.DefaultConcurrencyLevel, + (int)(10 * CollectionMultiplier)); + } internal IReadOnlyCollection Channels => _channels.ToReadOnlyCollection(); internal IReadOnlyCollection DMChannels => _dmChannels.ToReadOnlyCollection(); - internal IReadOnlyCollection GroupChannels => _groupChannels.Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels); + + internal IReadOnlyCollection GroupChannels => _groupChannels + .Select(x => GetChannel(x) as SocketGroupChannel).ToReadOnlyCollection(_groupChannels); + internal IReadOnlyCollection Guilds => _guilds.ToReadOnlyCollection(); internal IReadOnlyCollection Users => _users.ToReadOnlyCollection(); internal IReadOnlyCollection PrivateChannels => _dmChannels.Select(x => x.Value as ISocketPrivateChannel).Concat( - _groupChannels.Select(x => GetChannel(x) as ISocketPrivateChannel)) - .ToReadOnlyCollection(() => _dmChannels.Count + _groupChannels.Count); - - public ClientState(int guildCount, int dmChannelCount) - { - double estimatedChannelCount = guildCount * AverageChannelsPerGuild + dmChannelCount; - double estimatedUsersCount = guildCount * AverageUsersPerGuild; - _channels = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(estimatedChannelCount * CollectionMultiplier)); - _dmChannels = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(dmChannelCount * CollectionMultiplier)); - _guilds = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(guildCount * CollectionMultiplier)); - _users = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(estimatedUsersCount * CollectionMultiplier)); - _groupChannels = new ConcurrentHashSet(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(10 * CollectionMultiplier)); - } + _groupChannels.Select(x => GetChannel(x) as ISocketPrivateChannel)) + .ToReadOnlyCollection(() => _dmChannels.Count + _groupChannels.Count); internal SocketChannel GetChannel(ulong id) { - if (_channels.TryGetValue(id, out SocketChannel channel)) - return channel; - return null; + return _channels.TryGetValue(id, out var channel) ? channel : null; } + internal SocketDMChannel GetDMChannel(ulong userId) { - if (_dmChannels.TryGetValue(userId, out SocketDMChannel channel)) - return channel; - return null; + return _dmChannels.TryGetValue(userId, out var channel) ? channel : null; } + internal void AddChannel(SocketChannel channel) { _channels[channel.Id] = channel; @@ -65,56 +71,47 @@ namespace Discord.WebSocket break; } } + internal SocketChannel RemoveChannel(ulong id) { - if (_channels.TryRemove(id, out SocketChannel channel)) + if (!_channels.TryRemove(id, out var channel)) return null; + switch (channel) { - switch (channel) - { - case SocketDMChannel dmChannel: - _dmChannels.TryRemove(dmChannel.Recipient.Id, out var ignored); - break; - case SocketGroupChannel groupChannel: - _groupChannels.TryRemove(id); - break; - } - return channel; + case SocketDMChannel dmChannel: + _dmChannels.TryRemove(dmChannel.Recipient.Id, out var ignored); + break; + case SocketGroupChannel groupChannel: + _groupChannels.TryRemove(id); + break; } - return null; + + return channel; + } internal SocketGuild GetGuild(ulong id) { - if (_guilds.TryGetValue(id, out SocketGuild guild)) - return guild; - return null; - } - internal void AddGuild(SocketGuild guild) - { - _guilds[guild.Id] = guild; + return _guilds.TryGetValue(id, out var guild) ? guild : null; } + + internal void AddGuild(SocketGuild guild) => _guilds[guild.Id] = guild; + internal SocketGuild RemoveGuild(ulong id) { - if (_guilds.TryRemove(id, out SocketGuild guild)) - return guild; - return null; + return _guilds.TryRemove(id, out var guild) ? guild : null; } internal SocketGlobalUser GetUser(ulong id) { - if (_users.TryGetValue(id, out SocketGlobalUser user)) - return user; - return null; - } - internal SocketGlobalUser GetOrAddUser(ulong id, Func userFactory) - { - return _users.GetOrAdd(id, userFactory); + return _users.TryGetValue(id, out var user) ? user : null; } + + internal SocketGlobalUser GetOrAddUser(ulong id, Func userFactory) => + _users.GetOrAdd(id, userFactory); + internal SocketGlobalUser RemoveUser(ulong id) { - if (_users.TryRemove(id, out SocketGlobalUser user)) - return user; - return null; + return _users.TryRemove(id, out var user) ? user : null; } } } diff --git a/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs b/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs index a29c9bb70..244137ed5 100644 --- a/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs +++ b/src/Discord.Net.WebSocket/Commands/ShardedCommandContext.cs @@ -4,18 +4,18 @@ namespace Discord.Commands { public class ShardedCommandContext : SocketCommandContext, ICommandContext { - public new DiscordShardedClient Client { get; } - public ShardedCommandContext(DiscordShardedClient client, SocketUserMessage msg) : base(client.GetShard(GetShardId(client, (msg.Channel as SocketGuildChannel)?.Guild)), msg) { Client = client; } - private static int GetShardId(DiscordShardedClient client, IGuild guild) - => guild == null ? 0 : client.GetShardIdFor(guild); + public new DiscordShardedClient Client { get; } //ICommandContext IDiscordClient ICommandContext.Client => Client; + + private static int GetShardId(DiscordShardedClient client, IGuild guild) + => guild == null ? 0 : client.GetShardIdFor(guild); } } diff --git a/src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs b/src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs index c8b0747e7..4e96aff47 100644 --- a/src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs +++ b/src/Discord.Net.WebSocket/Commands/SocketCommandContext.cs @@ -4,14 +4,6 @@ namespace Discord.Commands { public class SocketCommandContext : ICommandContext { - public DiscordSocketClient Client { get; } - public SocketGuild Guild { get; } - public ISocketMessageChannel Channel { get; } - public SocketUser User { get; } - public SocketUserMessage Message { get; } - - public bool IsPrivate => Channel is IPrivateChannel; - public SocketCommandContext(DiscordSocketClient client, SocketUserMessage msg) { Client = client; @@ -21,6 +13,14 @@ namespace Discord.Commands Message = msg; } + public DiscordSocketClient Client { get; } + public SocketGuild Guild { get; } + public ISocketMessageChannel Channel { get; } + public SocketUser User { get; } + public SocketUserMessage Message { get; } + + public bool IsPrivate => Channel is IPrivateChannel; + //ICommandContext IDiscordClient ICommandContext.Client => Client; IGuild ICommandContext.Guild => Guild; diff --git a/src/Discord.Net.WebSocket/ConnectionManager.cs b/src/Discord.Net.WebSocket/ConnectionManager.cs index decae4163..79bc97506 100644 --- a/src/Discord.Net.WebSocket/ConnectionManager.cs +++ b/src/Discord.Net.WebSocket/ConnectionManager.cs @@ -1,33 +1,32 @@ -using Discord.Logging; using System; using System.Threading; using System.Threading.Tasks; +using Discord.Logging; using Discord.Net; namespace Discord { internal class ConnectionManager { - public event Func Connected { add { _connectedEvent.Add(value); } remove { _connectedEvent.Remove(value); } } private readonly AsyncEvent> _connectedEvent = new AsyncEvent>(); - public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } - private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); + private readonly int _connectionTimeout; + + private readonly AsyncEvent> _disconnectedEvent = + new AsyncEvent>(); - private readonly SemaphoreSlim _stateLock; private readonly Logger _logger; - private readonly int _connectionTimeout; private readonly Func _onConnecting; private readonly Func _onDisconnecting; - private TaskCompletionSource _connectionPromise, _readyPromise; + private readonly SemaphoreSlim _stateLock; private CancellationTokenSource _combinedCancelToken, _reconnectCancelToken, _connectionCancelToken; - private Task _task; - public ConnectionState State { get; private set; } - public CancellationToken CancelToken { get; private set; } + private TaskCompletionSource _connectionPromise, _readyPromise; + private Task _task; - internal ConnectionManager(SemaphoreSlim stateLock, Logger logger, int connectionTimeout, - Func onConnecting, Func onDisconnecting, Action> clientDisconnectHandler) + internal ConnectionManager(SemaphoreSlim stateLock, Logger logger, int connectionTimeout, + Func onConnecting, Func onDisconnecting, + Action> clientDisconnectHandler) { _stateLock = stateLock; _logger = logger; @@ -47,10 +46,26 @@ namespace Discord } else Error(new Exception("WebSocket connection was closed")); + return Task.Delay(0); }); } + public ConnectionState State { get; private set; } + public CancellationToken CancelToken { get; private set; } + + public event Func Connected + { + add => _connectedEvent.Add(value); + remove => _connectedEvent.Remove(value); + } + + public event Func Disconnected + { + add => _disconnectedEvent.Add(value); + remove => _disconnectedEvent.Remove(value); + } + public virtual async Task StartAsync() { await AcquireConnectionLock().ConfigureAwait(false); @@ -60,23 +75,24 @@ namespace Discord { try { - Random jitter = new Random(); - int nextReconnectDelay = 1000; + var jitter = new Random(); + var nextReconnectDelay = 1000; while (!reconnectCancelToken.IsCancellationRequested) { try { await ConnectAsync(reconnectCancelToken).ConfigureAwait(false); - nextReconnectDelay = 1000; //Reset delay + nextReconnectDelay = 1000; //Reset delay await _connectionPromise.Task.ConfigureAwait(false); } - catch (OperationCanceledException ex) - { + catch (OperationCanceledException ex) + { Cancel(); //In case this exception didn't come from another Error call - await DisconnectAsync(ex, !reconnectCancelToken.IsCancellationRequested).ConfigureAwait(false); + await DisconnectAsync(ex, !reconnectCancelToken.IsCancellationRequested) + .ConfigureAwait(false); } - catch (Exception ex) - { + catch (Exception ex) + { Error(ex); //In case this exception didn't come from another Error call if (!reconnectCancelToken.IsCancellationRequested) { @@ -90,19 +106,21 @@ namespace Discord } } - if (!reconnectCancelToken.IsCancellationRequested) - { - //Wait before reconnecting - await Task.Delay(nextReconnectDelay, reconnectCancelToken.Token).ConfigureAwait(false); - nextReconnectDelay = (nextReconnectDelay * 2) + jitter.Next(-250, 250); - if (nextReconnectDelay > 60000) - nextReconnectDelay = 60000; - } + if (reconnectCancelToken.IsCancellationRequested) continue; + //Wait before reconnecting + await Task.Delay(nextReconnectDelay, reconnectCancelToken.Token).ConfigureAwait(false); + nextReconnectDelay = nextReconnectDelay * 2 + jitter.Next(-250, 250); + if (nextReconnectDelay > 60000) + nextReconnectDelay = 60000; } } - finally { _stateLock.Release(); } + finally + { + _stateLock.Release(); + } }); } + public virtual async Task StopAsync() { Cancel(); @@ -114,13 +132,15 @@ namespace Discord private async Task ConnectAsync(CancellationTokenSource reconnectCancelToken) { _connectionCancelToken = new CancellationTokenSource(); - _combinedCancelToken = CancellationTokenSource.CreateLinkedTokenSource(_connectionCancelToken.Token, reconnectCancelToken.Token); + _combinedCancelToken = + CancellationTokenSource.CreateLinkedTokenSource(_connectionCancelToken.Token, + reconnectCancelToken.Token); CancelToken = _combinedCancelToken.Token; _connectionPromise = new TaskCompletionSource(); State = ConnectionState.Connecting; await _logger.InfoAsync("Connecting").ConfigureAwait(false); - + try { var readyPromise = new TaskCompletionSource(); @@ -135,7 +155,9 @@ namespace Discord await Task.Delay(_connectionTimeout, cancelToken).ConfigureAwait(false); readyPromise.TrySetException(new TimeoutException()); } - catch (OperationCanceledException) { } + catch (OperationCanceledException) + { + } }); await _onConnecting().ConfigureAwait(false); @@ -151,6 +173,7 @@ namespace Discord throw; } } + private async Task DisconnectAsync(Exception ex, bool isReconnecting) { if (State == ConnectionState.Disconnected) return; @@ -164,14 +187,9 @@ namespace Discord await _disconnectedEvent.InvokeAsync(ex, isReconnecting).ConfigureAwait(false); } - public async Task CompleteAsync() - { - await _readyPromise.TrySetResultAsync(true).ConfigureAwait(false); - } - public async Task WaitAsync() - { - await _readyPromise.Task.ConfigureAwait(false); - } + public async Task CompleteAsync() => await _readyPromise.TrySetResultAsync(true).ConfigureAwait(false); + + public async Task WaitAsync() => await _readyPromise.Task.ConfigureAwait(false); public void Cancel() { @@ -180,23 +198,27 @@ namespace Discord _reconnectCancelToken?.Cancel(); _connectionCancelToken?.Cancel(); } + public void Error(Exception ex) { _readyPromise.TrySetException(ex); _connectionPromise.TrySetException(ex); _connectionCancelToken?.Cancel(); } + public void CriticalError(Exception ex) { _reconnectCancelToken?.Cancel(); Error(ex); } + public void Reconnect() { _readyPromise.TrySetCanceled(); _connectionPromise.TrySetCanceled(); _connectionCancelToken?.Cancel(); } + private async Task AcquireConnectionLock() { while (true) @@ -207,4 +229,4 @@ namespace Discord } } } -} \ No newline at end of file +} diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs index c9e679669..a99b12d14 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.Events.cs @@ -5,34 +5,45 @@ namespace Discord.WebSocket { public partial class DiscordShardedClient { + private readonly AsyncEvent> _shardConnectedEvent = + new AsyncEvent>(); + + private readonly AsyncEvent> _shardDisconnectedEvent = + new AsyncEvent>(); + + private readonly AsyncEvent> _shardLatencyUpdatedEvent = + new AsyncEvent>(); + + private readonly AsyncEvent> _shardReadyEvent = + new AsyncEvent>(); + //General /// Fired when a shard is connected to the Discord gateway. - public event Func ShardConnected + public event Func ShardConnected { - add { _shardConnectedEvent.Add(value); } - remove { _shardConnectedEvent.Remove(value); } + add => _shardConnectedEvent.Add(value); + remove => _shardConnectedEvent.Remove(value); } - private readonly AsyncEvent> _shardConnectedEvent = new AsyncEvent>(); + /// Fired when a shard is disconnected from the Discord gateway. - public event Func ShardDisconnected + public event Func ShardDisconnected { - add { _shardDisconnectedEvent.Add(value); } - remove { _shardDisconnectedEvent.Remove(value); } + add => _shardDisconnectedEvent.Add(value); + remove => _shardDisconnectedEvent.Remove(value); } - private readonly AsyncEvent> _shardDisconnectedEvent = new AsyncEvent>(); + /// Fired when a guild data for a shard has finished downloading. - public event Func ShardReady + public event Func ShardReady { - add { _shardReadyEvent.Add(value); } - remove { _shardReadyEvent.Remove(value); } + add => _shardReadyEvent.Add(value); + remove => _shardReadyEvent.Remove(value); } - private readonly AsyncEvent> _shardReadyEvent = new AsyncEvent>(); + /// Fired when a shard receives a heartbeat from the Discord gateway. - public event Func ShardLatencyUpdated + public event Func ShardLatencyUpdated { - add { _shardLatencyUpdatedEvent.Add(value); } - remove { _shardLatencyUpdatedEvent.Remove(value); } + add => _shardLatencyUpdatedEvent.Add(value); + remove => _shardLatencyUpdatedEvent.Remove(value); } - private readonly AsyncEvent> _shardLatencyUpdatedEvent = new AsyncEvent>(); } -} \ No newline at end of file +} diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index e29cc4057..ff2525c64 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -1,11 +1,11 @@ -using Discord.API; -using Discord.Rest; using System; using System.Collections.Generic; using System.IO; using System.Linq; -using System.Threading.Tasks; using System.Threading; +using System.Threading.Tasks; +using Discord.API; +using Discord.Rest; namespace Discord.WebSocket { @@ -13,38 +13,40 @@ namespace Discord.WebSocket { private readonly DiscordSocketConfig _baseConfig; private readonly SemaphoreSlim _connectionGroupLock; + private readonly bool _automaticShards; private int[] _shardIds; - private Dictionary _shardIdsToIndex; + private readonly Dictionary _shardIdsToIndex; private DiscordSocketClient[] _shards; private int _totalShards; - private bool _automaticShards; - - /// Gets the estimated round-trip latency, in milliseconds, to the gateway server. - public override int Latency { get => GetLatency(); protected set { } } - public override UserStatus Status { get => _shards[0].Status; protected set { } } - public override IActivity Activity { get => _shards[0].Activity; protected set { } } - - internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; - public override IReadOnlyCollection Guilds => GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); - public override IReadOnlyCollection PrivateChannels => GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); - public IReadOnlyCollection Shards => _shards; - public override IReadOnlyCollection VoiceRegions => _shards[0].VoiceRegions; /// Creates a new REST/WebSocket discord client. - public DiscordShardedClient() : this(null, new DiscordSocketConfig()) { } + public DiscordShardedClient() : this(null, new DiscordSocketConfig()) + { + } + /// Creates a new REST/WebSocket discord client. - public DiscordShardedClient(DiscordSocketConfig config) : this(null, config, CreateApiClient(config)) { } + public DiscordShardedClient(DiscordSocketConfig config) : this(null, config, CreateApiClient(config)) + { + } + /// Creates a new REST/WebSocket discord client. - public DiscordShardedClient(int[] ids) : this(ids, new DiscordSocketConfig()) { } + public DiscordShardedClient(int[] ids) : this(ids, new DiscordSocketConfig()) + { + } + /// Creates a new REST/WebSocket discord client. - public DiscordShardedClient(int[] ids, DiscordSocketConfig config) : this(ids, config, CreateApiClient(config)) { } - private DiscordShardedClient(int[] ids, DiscordSocketConfig config, API.DiscordSocketApiClient client) + public DiscordShardedClient(int[] ids, DiscordSocketConfig config) : this(ids, config, CreateApiClient(config)) + { + } + + private DiscordShardedClient(int[] ids, DiscordSocketConfig config, DiscordSocketApiClient client) : base(config, client) { if (config.ShardId != null) throw new ArgumentException($"{nameof(config.ShardId)} must not be set."); if (ids != null && config.TotalShards == null) - throw new ArgumentException($"Custom ids are not supported when {nameof(config.TotalShards)} is not specified."); + throw new ArgumentException( + $"Custom ids are not supported when {nameof(config.TotalShards)} is not specified."); _shardIdsToIndex = new Dictionary(); config.DisplayInitialLog = false; @@ -58,7 +60,7 @@ namespace Discord.WebSocket _totalShards = config.TotalShards.Value; _shardIds = ids ?? Enumerable.Range(0, _totalShards).ToArray(); _shards = new DiscordSocketClient[_shardIds.Length]; - for (int i = 0; i < _shardIds.Length; i++) + for (var i = 0; i < _shardIds.Length; i++) { _shardIdsToIndex.Add(_shardIds[i], i); var newConfig = config.Clone(); @@ -68,8 +70,86 @@ namespace Discord.WebSocket } } } - private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) - => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent); + + /// Gets the estimated round-trip latency, in milliseconds, to the gateway server. + public override int Latency + { + get => GetLatency(); + protected set { } + } + + public override UserStatus Status + { + get => _shards[0].Status; + protected set { } + } + + public override IActivity Activity + { + get => _shards[0].Activity; + protected set { } + } + + internal new DiscordSocketApiClient ApiClient => base.ApiClient; + + public override IReadOnlyCollection Guilds => + GetGuilds().ToReadOnlyCollection(() => GetGuildCount()); + + public override IReadOnlyCollection PrivateChannels => + GetPrivateChannels().ToReadOnlyCollection(() => GetPrivateChannelCount()); + + public IReadOnlyCollection Shards => _shards; + public override IReadOnlyCollection VoiceRegions => _shards[0].VoiceRegions; + + /// + public override async Task StartAsync() + => await Task.WhenAll(_shards.Select(x => x.StartAsync())).ConfigureAwait(false); + + /// + public override async Task StopAsync() + => await Task.WhenAll(_shards.Select(x => x.StopAsync())).ConfigureAwait(false); + + //IDiscordClient + async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) + => await GetApplicationInfoAsync().ConfigureAwait(false); + + Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetChannel(id)); + + Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, + RequestOptions options) + => Task.FromResult>(PrivateChannels); + + async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) + => await GetConnectionsAsync().ConfigureAwait(false); + + async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) + => await GetInviteAsync(inviteId, options).ConfigureAwait(false); + + Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetGuild(id)); + + Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) + => Task.FromResult>(Guilds); + + async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, + RequestOptions options) + => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); + + Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetUser(id)); + + Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) + => Task.FromResult(GetUser(username, discriminator)); + + Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) + => Task.FromResult>(VoiceRegions); + + Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) + => Task.FromResult(GetVoiceRegion(id)); + + private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) + => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordConfig.UserAgent); internal override async Task OnLoginAsync(TokenType tokenType, string token) { @@ -79,7 +159,7 @@ namespace Discord.WebSocket _shardIds = Enumerable.Range(0, shardCount).ToArray(); _totalShards = _shardIds.Length; _shards = new DiscordSocketClient[_shardIds.Length]; - for (int i = 0; i < _shardIds.Length; i++) + for (var i = 0; i < _shardIds.Length; i++) { _shardIdsToIndex.Add(_shardIds[i], i); var newConfig = _baseConfig.Clone(); @@ -91,17 +171,16 @@ namespace Discord.WebSocket } //Assume threadsafe: already in a connection lock - for (int i = 0; i < _shards.Length; i++) - await _shards[i].LoginAsync(tokenType, token, false); + foreach (var t in _shards) + await t.LoginAsync(tokenType, token, false); } + internal override async Task OnLogoutAsync() { //Assume threadsafe: already in a connection lock if (_shards != null) - { - for (int i = 0; i < _shards.Length; i++) - await _shards[i].LogoutAsync(); - } + foreach (var t in _shards) + await t.LogoutAsync(); CurrentUser = null; if (_automaticShards) @@ -113,25 +192,20 @@ namespace Discord.WebSocket } } - /// - public override async Task StartAsync() - => await Task.WhenAll(_shards.Select(x => x.StartAsync())).ConfigureAwait(false); - /// - public override async Task StopAsync() - => await Task.WhenAll(_shards.Select(x => x.StopAsync())).ConfigureAwait(false); - public DiscordSocketClient GetShard(int id) { - if (_shardIdsToIndex.TryGetValue(id, out id)) - return _shards[id]; - return null; + return _shardIdsToIndex.TryGetValue(id, out id) ? _shards[id] : null; } + private int GetShardIdFor(ulong guildId) => (int)((guildId >> 22) % (uint)_totalShards); + public int GetShardIdFor(IGuild guild) => GetShardIdFor(guild?.Id ?? 0); + private DiscordSocketClient GetShardFor(ulong guildId) => GetShard(GetShardIdFor(guildId)); + public DiscordSocketClient GetShardFor(IGuild guild) => GetShardFor(guild?.Id ?? 0); @@ -140,73 +214,45 @@ namespace Discord.WebSocket => await _shards[0].GetApplicationInfoAsync(options).ConfigureAwait(false); /// - public override SocketGuild GetGuild(ulong id) + public override SocketGuild GetGuild(ulong id) => GetShardFor(id).GetGuild(id); /// public override SocketChannel GetChannel(ulong id) { - for (int i = 0; i < _shards.Length; i++) - { - var channel = _shards[i].GetChannel(id); - if (channel != null) - return channel; - } - return null; + return _shards.Select(t => t.GetChannel(id)).FirstOrDefault(channel => channel != null); } + private IEnumerable GetPrivateChannels() { - for (int i = 0; i < _shards.Length; i++) - { - foreach (var channel in _shards[i].PrivateChannels) - yield return channel; - } + return _shards.SelectMany(t => t.PrivateChannels); } + private int GetPrivateChannelCount() { - int result = 0; - for (int i = 0; i < _shards.Length; i++) - result += _shards[i].PrivateChannels.Count; - return result; - } + return _shards.Sum(t => t.PrivateChannels.Count); + } private IEnumerable GetGuilds() { - for (int i = 0; i < _shards.Length; i++) - { - foreach (var guild in _shards[i].Guilds) - yield return guild; - } + return _shards.SelectMany(t => t.Guilds); } + private int GetGuildCount() { - int result = 0; - for (int i = 0; i < _shards.Length; i++) - result += _shards[i].Guilds.Count; - return result; - } + return _shards.Sum(t => t.Guilds.Count); + } /// public override SocketUser GetUser(ulong id) { - for (int i = 0; i < _shards.Length; i++) - { - var user = _shards[i].GetUser(id); - if (user != null) - return user; - } - return null; + return _shards.Select(t => t.GetUser(id)).FirstOrDefault(user => user != null); } + /// public override SocketUser GetUser(string username, string discriminator) { - for (int i = 0; i < _shards.Length; i++) - { - var user = _shards[i].GetUser(username, discriminator); - if (user != null) - return user; - } - return null; + return _shards.Select(t => t.GetUser(username, discriminator)).FirstOrDefault(user => user != null); } /// @@ -216,9 +262,9 @@ namespace Discord.WebSocket /// Downloads the users list for the provided guilds, if they don't have a complete list. public override async Task DownloadUsersAsync(IEnumerable guilds) { - for (int i = 0; i < _shards.Length; i++) + for (var i = 0; i < _shards.Length; i++) { - int id = _shardIds[i]; + var id = _shardIds[i]; var arr = guilds.Where(x => GetShardIdFor(x) == id).ToArray(); if (arr.Length > 0) await _shards[i].DownloadUsersAsync(arr).ConfigureAwait(false); @@ -227,18 +273,18 @@ namespace Discord.WebSocket private int GetLatency() { - int total = 0; - for (int i = 0; i < _shards.Length; i++) - total += _shards[i].Latency; + var total = _shards.Sum(t => t.Latency); return (int)Math.Round(total / (double)_shards.Length); } public override async Task SetStatusAsync(UserStatus status) { - for (int i = 0; i < _shards.Length; i++) - await _shards[i].SetStatusAsync(status).ConfigureAwait(false); + foreach (var t in _shards) + await t.SetStatusAsync(status).ConfigureAwait(false); } - public override async Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing) + + public override async Task SetGameAsync(string name, string streamUrl = null, + ActivityType type = ActivityType.Playing) { IActivity activity = null; if (!string.IsNullOrEmpty(streamUrl)) @@ -247,15 +293,16 @@ namespace Discord.WebSocket activity = new Game(name, type); await SetActivityAsync(activity).ConfigureAwait(false); } + public override async Task SetActivityAsync(IActivity activity) { - for (int i = 0; i < _shards.Length; i++) - await _shards[i].SetActivityAsync(activity).ConfigureAwait(false); + foreach (var t in _shards) + await t.SetActivityAsync(activity).ConfigureAwait(false); } private void RegisterEvents(DiscordSocketClient client, bool isPrimary) { - client.Log += (msg) => _logEvent.InvokeAsync(msg); + client.Log += msg => _logEvent.InvokeAsync(msg); client.LoggedOut += () => { var state = LoginState; @@ -264,88 +311,61 @@ namespace Discord.WebSocket //Should only happen if token is changed var _ = LogoutAsync(); //Signal the logout, fire and forget } + return Task.Delay(0); }; if (isPrimary) - { client.Ready += () => { CurrentUser = client.CurrentUser; return Task.Delay(0); }; - } client.Connected += () => _shardConnectedEvent.InvokeAsync(client); - client.Disconnected += (exception) => _shardDisconnectedEvent.InvokeAsync(exception, client); + client.Disconnected += exception => _shardDisconnectedEvent.InvokeAsync(exception, client); client.Ready += () => _shardReadyEvent.InvokeAsync(client); - client.LatencyUpdated += (oldLatency, newLatency) => _shardLatencyUpdatedEvent.InvokeAsync(oldLatency, newLatency, client); + client.LatencyUpdated += (oldLatency, newLatency) => + _shardLatencyUpdatedEvent.InvokeAsync(oldLatency, newLatency, client); - client.ChannelCreated += (channel) => _channelCreatedEvent.InvokeAsync(channel); - client.ChannelDestroyed += (channel) => _channelDestroyedEvent.InvokeAsync(channel); - client.ChannelUpdated += (oldChannel, newChannel) => _channelUpdatedEvent.InvokeAsync(oldChannel, newChannel); + client.ChannelCreated += channel => _channelCreatedEvent.InvokeAsync(channel); + client.ChannelDestroyed += channel => _channelDestroyedEvent.InvokeAsync(channel); + client.ChannelUpdated += + (oldChannel, newChannel) => _channelUpdatedEvent.InvokeAsync(oldChannel, newChannel); - client.MessageReceived += (msg) => _messageReceivedEvent.InvokeAsync(msg); + client.MessageReceived += msg => _messageReceivedEvent.InvokeAsync(msg); client.MessageDeleted += (cache, channel) => _messageDeletedEvent.InvokeAsync(cache, channel); - client.MessageUpdated += (oldMsg, newMsg, channel) => _messageUpdatedEvent.InvokeAsync(oldMsg, newMsg, channel); - client.ReactionAdded += (cache, channel, reaction) => _reactionAddedEvent.InvokeAsync(cache, channel, reaction); - client.ReactionRemoved += (cache, channel, reaction) => _reactionRemovedEvent.InvokeAsync(cache, channel, reaction); + client.MessageUpdated += (oldMsg, newMsg, channel) => + _messageUpdatedEvent.InvokeAsync(oldMsg, newMsg, channel); + client.ReactionAdded += (cache, channel, reaction) => + _reactionAddedEvent.InvokeAsync(cache, channel, reaction); + client.ReactionRemoved += (cache, channel, reaction) => + _reactionRemovedEvent.InvokeAsync(cache, channel, reaction); client.ReactionsCleared += (cache, channel) => _reactionsClearedEvent.InvokeAsync(cache, channel); - client.RoleCreated += (role) => _roleCreatedEvent.InvokeAsync(role); - client.RoleDeleted += (role) => _roleDeletedEvent.InvokeAsync(role); + client.RoleCreated += role => _roleCreatedEvent.InvokeAsync(role); + client.RoleDeleted += role => _roleDeletedEvent.InvokeAsync(role); client.RoleUpdated += (oldRole, newRole) => _roleUpdatedEvent.InvokeAsync(oldRole, newRole); - client.JoinedGuild += (guild) => _joinedGuildEvent.InvokeAsync(guild); - client.LeftGuild += (guild) => _leftGuildEvent.InvokeAsync(guild); - client.GuildAvailable += (guild) => _guildAvailableEvent.InvokeAsync(guild); - client.GuildUnavailable += (guild) => _guildUnavailableEvent.InvokeAsync(guild); - client.GuildMembersDownloaded += (guild) => _guildMembersDownloadedEvent.InvokeAsync(guild); + client.JoinedGuild += guild => _joinedGuildEvent.InvokeAsync(guild); + client.LeftGuild += guild => _leftGuildEvent.InvokeAsync(guild); + client.GuildAvailable += guild => _guildAvailableEvent.InvokeAsync(guild); + client.GuildUnavailable += guild => _guildUnavailableEvent.InvokeAsync(guild); + client.GuildMembersDownloaded += guild => _guildMembersDownloadedEvent.InvokeAsync(guild); client.GuildUpdated += (oldGuild, newGuild) => _guildUpdatedEvent.InvokeAsync(oldGuild, newGuild); - client.UserJoined += (user) => _userJoinedEvent.InvokeAsync(user); - client.UserLeft += (user) => _userLeftEvent.InvokeAsync(user); + client.UserJoined += user => _userJoinedEvent.InvokeAsync(user); + client.UserLeft += user => _userLeftEvent.InvokeAsync(user); client.UserBanned += (user, guild) => _userBannedEvent.InvokeAsync(user, guild); client.UserUnbanned += (user, guild) => _userUnbannedEvent.InvokeAsync(user, guild); client.UserUpdated += (oldUser, newUser) => _userUpdatedEvent.InvokeAsync(oldUser, newUser); client.GuildMemberUpdated += (oldUser, newUser) => _guildMemberUpdatedEvent.InvokeAsync(oldUser, newUser); - client.UserVoiceStateUpdated += (user, oldVoiceState, newVoiceState) => _userVoiceStateUpdatedEvent.InvokeAsync(user, oldVoiceState, newVoiceState); - client.VoiceServerUpdated += (server) => _voiceServerUpdatedEvent.InvokeAsync(server); + client.UserVoiceStateUpdated += (user, oldVoiceState, newVoiceState) => + _userVoiceStateUpdatedEvent.InvokeAsync(user, oldVoiceState, newVoiceState); + client.VoiceServerUpdated += server => _voiceServerUpdatedEvent.InvokeAsync(server); client.CurrentUserUpdated += (oldUser, newUser) => _selfUpdatedEvent.InvokeAsync(oldUser, newUser); client.UserIsTyping += (oldUser, newUser) => _userIsTypingEvent.InvokeAsync(oldUser, newUser); - client.RecipientAdded += (user) => _recipientAddedEvent.InvokeAsync(user); - client.RecipientRemoved += (user) => _recipientRemovedEvent.InvokeAsync(user); + client.RecipientAdded += user => _recipientAddedEvent.InvokeAsync(user); + client.RecipientRemoved += user => _recipientRemovedEvent.InvokeAsync(user); } - - //IDiscordClient - async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) - => await GetApplicationInfoAsync().ConfigureAwait(false); - - Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetChannel(id)); - Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) - => Task.FromResult>(PrivateChannels); - - async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) - => await GetConnectionsAsync().ConfigureAwait(false); - - async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) - => await GetInviteAsync(inviteId, options).ConfigureAwait(false); - - Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetGuild(id)); - Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) - => Task.FromResult>(Guilds); - async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) - => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); - - Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetUser(id)); - Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) - => Task.FromResult(GetUser(username, discriminator)); - - Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) - => Task.FromResult>(VoiceRegions); - Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) - => Task.FromResult(GetVoiceRegion(id)); } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index 8ae41cc59..f1cd10c02 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -1,11 +1,4 @@ #pragma warning disable CS1591 -using Discord.API.Gateway; -using Discord.API.Rest; -using Discord.Net.Queue; -using Discord.Net.Rest; -using Discord.Net.WebSockets; -using Discord.WebSocket; -using Newtonsoft.Json; using System; using System.Collections.Generic; using System.IO; @@ -13,32 +6,35 @@ using System.IO.Compression; using System.Text; using System.Threading; using System.Threading.Tasks; +using Discord.API.Gateway; +using Discord.Net.Queue; +using Discord.Net.Rest; +using Discord.Net.WebSockets; +using Discord.WebSocket; +using Newtonsoft.Json; namespace Discord.API { internal class DiscordSocketApiClient : DiscordRestApiClient { - public event Func SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } - private readonly AsyncEvent> _sentGatewayMessageEvent = new AsyncEvent>(); - public event Func ReceivedGatewayEvent { add { _receivedGatewayEvent.Add(value); } remove { _receivedGatewayEvent.Remove(value); } } - private readonly AsyncEvent> _receivedGatewayEvent = new AsyncEvent>(); - - public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); - private CancellationTokenSource _connectCancelToken; - private string _gatewayUrl; - private bool _isExplicitUrl; + private readonly AsyncEvent> _receivedGatewayEvent = + new AsyncEvent>(); + + private readonly AsyncEvent> _sentGatewayMessageEvent = + new AsyncEvent>(); //Store our decompression streams for zlib shared state private MemoryStream _compressed; - private DeflateStream _decompressor; - internal IWebSocketClient WebSocketClient { get; } - - public ConnectionState ConnectionState { get; private set; } + private CancellationTokenSource _connectCancelToken; + private DeflateStream _decompressor; + private string _gatewayUrl; + private readonly bool _isExplicitUrl; - public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, + public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, + string userAgent, string url = null, RetryMode defaultRetryMode = RetryMode.AlwaysRetry, JsonSerializer serializer = null) : base(restClientProvider, userAgent, defaultRetryMode, serializer) { @@ -75,7 +71,9 @@ namespace Discord.API { var msg = _serializer.Deserialize(jsonReader); if (msg != null) - await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); + await _receivedGatewayEvent + .InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload) + .ConfigureAwait(false); } } }; @@ -86,7 +84,9 @@ namespace Discord.API { var msg = _serializer.Deserialize(jsonReader); if (msg != null) - await _receivedGatewayEvent.InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload).ConfigureAwait(false); + await _receivedGatewayEvent + .InvokeAsync((GatewayOpCode)msg.Operation, msg.Sequence, msg.Type, msg.Payload) + .ConfigureAwait(false); } }; WebSocketClient.Closed += async ex => @@ -96,19 +96,40 @@ namespace Discord.API }; } + internal IWebSocketClient WebSocketClient { get; } + + public ConnectionState ConnectionState { get; private set; } + + public event Func SentGatewayMessage + { + add => _sentGatewayMessageEvent.Add(value); + remove => _sentGatewayMessageEvent.Remove(value); + } + + public event Func ReceivedGatewayEvent + { + add => _receivedGatewayEvent.Add(value); + remove => _receivedGatewayEvent.Remove(value); + } + + public event Func Disconnected + { + add => _disconnectedEvent.Add(value); + remove => _disconnectedEvent.Remove(value); + } + internal override void Dispose(bool disposing) { - if (!_isDisposed) + if (_isDisposed) return; + if (disposing) { - if (disposing) - { - _connectCancelToken?.Dispose(); - (WebSocketClient as IDisposable)?.Dispose(); - _decompressor?.Dispose(); - _compressed?.Dispose(); - } - _isDisposed = true; + _connectCancelToken?.Dispose(); + (WebSocketClient as IDisposable)?.Dispose(); + _decompressor?.Dispose(); + _compressed?.Dispose(); } + + _isDisposed = true; } public async Task ConnectAsync() @@ -118,8 +139,12 @@ namespace Discord.API { await ConnectInternalAsync().ConfigureAwait(false); } - finally { _stateLock.Release(); } + finally + { + _stateLock.Release(); + } } + internal override async Task ConnectInternalAsync() { if (LoginState != LoginState.LoggedIn) @@ -143,8 +168,10 @@ namespace Discord.API if (!_isExplicitUrl) { var gatewayResponse = await GetGatewayAsync().ConfigureAwait(false); - _gatewayUrl = $"{gatewayResponse.Url}?v={DiscordConfig.APIVersion}&encoding={DiscordSocketConfig.GatewayEncoding}&compress=zlib-stream"; + _gatewayUrl = + $"{gatewayResponse.Url}?v={DiscordConfig.APIVersion}&encoding={DiscordSocketConfig.GatewayEncoding}&compress=zlib-stream"; } + await WebSocketClient.ConnectAsync(_gatewayUrl).ConfigureAwait(false); ConnectionState = ConnectionState.Connected; @@ -165,8 +192,12 @@ namespace Discord.API { await DisconnectInternalAsync().ConfigureAwait(false); } - finally { _stateLock.Release(); } + finally + { + _stateLock.Release(); + } } + public async Task DisconnectAsync(Exception ex) { await _stateLock.WaitAsync().ConfigureAwait(false); @@ -174,8 +205,12 @@ namespace Discord.API { await DisconnectInternalAsync().ConfigureAwait(false); } - finally { _stateLock.Release(); } + finally + { + _stateLock.Release(); + } } + internal override async Task DisconnectInternalAsync() { if (WebSocketClient == null) @@ -184,8 +219,13 @@ namespace Discord.API if (ConnectionState == ConnectionState.Disconnected) return; ConnectionState = ConnectionState.Disconnecting; - try { _connectCancelToken?.Cancel(false); } - catch { } + try + { + _connectCancelToken?.Cancel(false); + } + catch + { + } await WebSocketClient.DisconnectAsync().ConfigureAwait(false); @@ -195,54 +235,64 @@ namespace Discord.API //Core public Task SendGatewayAsync(GatewayOpCode opCode, object payload, RequestOptions options = null) => SendGatewayInternalAsync(opCode, payload, options); + private async Task SendGatewayInternalAsync(GatewayOpCode opCode, object payload, RequestOptions options) { CheckState(); //TODO: Add ETF byte[] bytes = null; - payload = new SocketFrame { Operation = (int)opCode, Payload = payload }; - if (payload != null) - bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); - await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, null, bytes, true, options)).ConfigureAwait(false); + payload = new SocketFrame + { + Operation = (int)opCode, + Payload = payload + }; + bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); + await RequestQueue.SendAsync(new WebSocketRequest(WebSocketClient, null, bytes, true, options)) + .ConfigureAwait(false); await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); } - - public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, RequestOptions options = null) + + public async Task SendIdentifyAsync(int largeThreshold = 100, int shardID = 0, int totalShards = 1, + RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); var props = new Dictionary { ["$device"] = "Discord.Net" }; - var msg = new IdentifyParams() + var msg = new IdentifyParams { Token = AuthToken, Properties = props, LargeThreshold = largeThreshold }; if (totalShards > 1) - msg.ShardingParams = new int[] { shardID, totalShards }; + msg.ShardingParams = new[] {shardID, totalShards}; - await SendGatewayAsync(GatewayOpCode.Identify, msg, options: options).ConfigureAwait(false); + await SendGatewayAsync(GatewayOpCode.Identify, msg, options).ConfigureAwait(false); } + public async Task SendResumeAsync(string sessionId, int lastSeq, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - var msg = new ResumeParams() + var msg = new ResumeParams { Token = AuthToken, SessionId = sessionId, Sequence = lastSeq }; - await SendGatewayAsync(GatewayOpCode.Resume, msg, options: options).ConfigureAwait(false); + await SendGatewayAsync(GatewayOpCode.Resume, msg, options).ConfigureAwait(false); } + public async Task SendHeartbeatAsync(int lastSeq, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - await SendGatewayAsync(GatewayOpCode.Heartbeat, lastSeq, options: options).ConfigureAwait(false); + await SendGatewayAsync(GatewayOpCode.Heartbeat, lastSeq, options).ConfigureAwait(false); } - public async Task SendStatusUpdateAsync(UserStatus status, bool isAFK, long? since, Game game, RequestOptions options = null) + + public async Task SendStatusUpdateAsync(UserStatus status, bool isAFK, long? since, Game game, + RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); var args = new StatusUpdateParams @@ -252,14 +302,22 @@ namespace Discord.API IsAFK = isAFK, Game = game }; - await SendGatewayAsync(GatewayOpCode.StatusUpdate, args, options: options).ConfigureAwait(false); + await SendGatewayAsync(GatewayOpCode.StatusUpdate, args, options).ConfigureAwait(false); } + public async Task SendRequestMembersAsync(IEnumerable guildIds, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - await SendGatewayAsync(GatewayOpCode.RequestGuildMembers, new RequestMembersParams { GuildIds = guildIds, Query = "", Limit = 0 }, options: options).ConfigureAwait(false); + await SendGatewayAsync(GatewayOpCode.RequestGuildMembers, new RequestMembersParams + { + GuildIds = guildIds, + Query = "", + Limit = 0 + }, options).ConfigureAwait(false); } - public async Task SendVoiceStateUpdateAsync(ulong guildId, ulong? channelId, bool selfDeaf, bool selfMute, RequestOptions options = null) + + public async Task SendVoiceStateUpdateAsync(ulong guildId, ulong? channelId, bool selfDeaf, bool selfMute, + RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); var payload = new VoiceStateUpdateParams @@ -269,12 +327,13 @@ namespace Discord.API SelfDeaf = selfDeaf, SelfMute = selfMute }; - await SendGatewayAsync(GatewayOpCode.VoiceStateUpdate, payload, options: options).ConfigureAwait(false); + await SendGatewayAsync(GatewayOpCode.VoiceStateUpdate, payload, options).ConfigureAwait(false); } + public async Task SendGuildSyncAsync(IEnumerable guildIds, RequestOptions options = null) { options = RequestOptions.CreateOrClone(options); - await SendGatewayAsync(GatewayOpCode.GuildSync, guildIds, options: options).ConfigureAwait(false); + await SendGatewayAsync(GatewayOpCode.GuildSync, guildIds, options).ConfigureAwait(false); } } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs index 1222b270e..5fa344e30 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.Events.cs @@ -2,37 +2,42 @@ using System.Threading.Tasks; namespace Discord.WebSocket -{ +{ public partial class DiscordSocketClient { + private readonly AsyncEvent> _connectedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); + private readonly AsyncEvent> _latencyUpdatedEvent = new AsyncEvent>(); + + private readonly AsyncEvent> _readyEvent = new AsyncEvent>(); + //General /// Fired when connected to the Discord gateway. public event Func Connected { - add { _connectedEvent.Add(value); } - remove { _connectedEvent.Remove(value); } + add => _connectedEvent.Add(value); + remove => _connectedEvent.Remove(value); } - private readonly AsyncEvent> _connectedEvent = new AsyncEvent>(); + /// Fired when disconnected to the Discord gateway. public event Func Disconnected { - add { _disconnectedEvent.Add(value); } - remove { _disconnectedEvent.Remove(value); } + add => _disconnectedEvent.Add(value); + remove => _disconnectedEvent.Remove(value); } - private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); + /// Fired when guild data has finished downloading. public event Func Ready { - add { _readyEvent.Add(value); } - remove { _readyEvent.Remove(value); } + add => _readyEvent.Add(value); + remove => _readyEvent.Remove(value); } - private readonly AsyncEvent> _readyEvent = new AsyncEvent>(); + /// Fired when a heartbeat is received from the Discord gateway. public event Func LatencyUpdated { - add { _latencyUpdatedEvent.Add(value); } - remove { _latencyUpdatedEvent.Remove(value); } + add => _latencyUpdatedEvent.Add(value); + remove => _latencyUpdatedEvent.Remove(value); } - private readonly AsyncEvent> _latencyUpdatedEvent = new AsyncEvent>(); } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index 9efc7d3fa..ea41a2064 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -1,3 +1,11 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; using Discord.API; using Discord.API.Gateway; using Discord.Logging; @@ -7,73 +15,49 @@ using Discord.Net.WebSockets; using Discord.Rest; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using GameModel = Discord.API.Game; +using Reaction = Discord.API.Gateway.Reaction; namespace Discord.WebSocket { public partial class DiscordSocketClient : BaseSocketClient, IDiscordClient { - private readonly ConcurrentQueue _largeGuilds; - private readonly JsonSerializer _serializer; - private readonly SemaphoreSlim _connectionGroupLock; - private readonly DiscordSocketClient _parentClient; - private readonly ConcurrentQueue _heartbeatTimes; private readonly ConnectionManager _connection; + private readonly SemaphoreSlim _connectionGroupLock; private readonly Logger _gatewayLogger; + private readonly ConcurrentQueue _heartbeatTimes; + private readonly ConcurrentQueue _largeGuilds; + private readonly DiscordSocketClient _parentClient; + private readonly JsonSerializer _serializer; private readonly SemaphoreSlim _stateLock; - - private string _sessionId; - private int _lastSeq; - private ImmutableDictionary _voiceRegions; + private RestApplication _applicationInfo; private Task _heartbeatTask, _guildDownloadTask; - private int _unavailableGuildCount; private long _lastGuildAvailableTime, _lastMessageTime; + private int _lastSeq; private int _nextAudioId; + + private string _sessionId; private DateTimeOffset? _statusSince; - private RestApplication _applicationInfo; + private int _unavailableGuildCount; + private ImmutableDictionary _voiceRegions; - /// Gets the shard of of this client. - public int ShardId { get; } - /// Gets the current connection state of this client. - public ConnectionState ConnectionState => _connection.State; - /// - public override int Latency { get; protected set; } - public override UserStatus Status { get; protected set; } = UserStatus.Online; - public override IActivity Activity { get; protected set; } + /// Creates a new REST/WebSocket discord client. + public DiscordSocketClient() : this(new DiscordSocketConfig()) + { + } - //From DiscordSocketConfig - internal int TotalShards { get; private set; } - internal int MessageCacheSize { get; private set; } - internal int LargeThreshold { get; private set; } - internal ClientState State { get; private set; } - internal UdpSocketProvider UdpSocketProvider { get; private set; } - internal WebSocketProvider WebSocketProvider { get; private set; } - internal bool AlwaysDownloadUsers { get; private set; } - internal int? HandlerTimeout { get; private set; } + /// Creates a new REST/WebSocket discord client. + public DiscordSocketClient(DiscordSocketConfig config) : this(config, CreateApiClient(config), null, null) + { + } - internal new DiscordSocketApiClient ApiClient => base.ApiClient as DiscordSocketApiClient; - public override IReadOnlyCollection Guilds => State.Guilds; - public override IReadOnlyCollection PrivateChannels => State.PrivateChannels; - public IReadOnlyCollection DMChannels - => State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); - public IReadOnlyCollection GroupChannels - => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); - public override IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection(); + internal DiscordSocketClient(DiscordSocketConfig config, SemaphoreSlim groupLock, + DiscordSocketClient parentClient) : this(config, CreateApiClient(config), groupLock, parentClient) + { + } - /// Creates a new REST/WebSocket discord client. - public DiscordSocketClient() : this(new DiscordSocketConfig()) { } - /// Creates a new REST/WebSocket discord client. - public DiscordSocketClient(DiscordSocketConfig config) : this(config, CreateApiClient(config), null, null) { } - internal DiscordSocketClient(DiscordSocketConfig config, SemaphoreSlim groupLock, DiscordSocketClient parentClient) : this(config, CreateApiClient(config), groupLock, parentClient) { } - private DiscordSocketClient(DiscordSocketConfig config, API.DiscordSocketApiClient client, SemaphoreSlim groupLock, DiscordSocketClient parentClient) + private DiscordSocketClient(DiscordSocketConfig config, DiscordSocketApiClient client, SemaphoreSlim groupLock, + DiscordSocketClient parentClient) : base(config, client) { ShardId = config.ShardId ?? 0; @@ -88,7 +72,8 @@ namespace Discord.WebSocket _heartbeatTimes = new ConcurrentQueue(); _stateLock = new SemaphoreSlim(1, 1); - _gatewayLogger = LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); + _gatewayLogger = + LogManager.CreateLogger(ShardId == 0 && TotalShards == 1 ? "Gateway" : $"Shard #{ShardId}"); _connection = new ConnectionManager(_stateLock, _gatewayLogger, config.ConnectionTimeout, OnConnectingAsync, OnDisconnectingAsync, x => ApiClient.Disconnected += x); _connection.Connected += () => TimedInvokeAsync(_connectedEvent, nameof(Connected)); @@ -98,21 +83,25 @@ namespace Discord.WebSocket _connectionGroupLock = groupLock; _parentClient = parentClient; - _serializer = new JsonSerializer { ContractResolver = new DiscordContractResolver() }; + _serializer = new JsonSerializer {ContractResolver = new DiscordContractResolver()}; _serializer.Error += (s, e) => { _gatewayLogger.WarningAsync("Serializer Error", e.ErrorContext.Error).GetAwaiter().GetResult(); e.ErrorContext.Handled = true; }; - ApiClient.SentGatewayMessage += async opCode => await _gatewayLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); + ApiClient.SentGatewayMessage += async opCode => + await _gatewayLogger.DebugAsync($"Sent {opCode}").ConfigureAwait(false); ApiClient.ReceivedGatewayEvent += ProcessMessageAsync; LeftGuild += async g => await _gatewayLogger.InfoAsync($"Left {g.Name}").ConfigureAwait(false); JoinedGuild += async g => await _gatewayLogger.InfoAsync($"Joined {g.Name}").ConfigureAwait(false); - GuildAvailable += async g => await _gatewayLogger.VerboseAsync($"Connected to {g.Name}").ConfigureAwait(false); - GuildUnavailable += async g => await _gatewayLogger.VerboseAsync($"Disconnected from {g.Name}").ConfigureAwait(false); - LatencyUpdated += async (old, val) => await _gatewayLogger.DebugAsync($"Latency = {val} ms").ConfigureAwait(false); + GuildAvailable += async g => + await _gatewayLogger.VerboseAsync($"Connected to {g.Name}").ConfigureAwait(false); + GuildUnavailable += async g => + await _gatewayLogger.VerboseAsync($"Disconnected from {g.Name}").ConfigureAwait(false); + LatencyUpdated += async (old, val) => + await _gatewayLogger.DebugAsync($"Latency = {val} ms").ConfigureAwait(false); GuildAvailable += g => { @@ -120,33 +109,127 @@ namespace Discord.WebSocket { var _ = g.DownloadUsersAsync(); } + return Task.Delay(0); }; _voiceRegions = ImmutableDictionary.Create(); _largeGuilds = new ConcurrentQueue(); } - private static API.DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) - => new API.DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordRestConfig.UserAgent, config.GatewayHost); + + /// Gets the shard of of this client. + public int ShardId { get; } + + /// + public override int Latency { get; protected set; } + + public override UserStatus Status { get; protected set; } = UserStatus.Online; + public override IActivity Activity { get; protected set; } + + //From DiscordSocketConfig + internal int TotalShards { get; } + internal int MessageCacheSize { get; } + internal int LargeThreshold { get; } + internal ClientState State { get; private set; } + internal UdpSocketProvider UdpSocketProvider { get; } + internal WebSocketProvider WebSocketProvider { get; } + internal bool AlwaysDownloadUsers { get; } + internal int? HandlerTimeout { get; } + + internal new DiscordSocketApiClient ApiClient => base.ApiClient; + public override IReadOnlyCollection Guilds => State.Guilds; + public override IReadOnlyCollection PrivateChannels => State.PrivateChannels; + + public IReadOnlyCollection DMChannels + => State.PrivateChannels.Select(x => x as SocketDMChannel).Where(x => x != null).ToImmutableArray(); + + public IReadOnlyCollection GroupChannels + => State.PrivateChannels.Select(x => x as SocketGroupChannel).Where(x => x != null).ToImmutableArray(); + + public override IReadOnlyCollection VoiceRegions => _voiceRegions.ToReadOnlyCollection(); + + /// Gets the current connection state of this client. + public ConnectionState ConnectionState => _connection.State; + + //IDiscordClient + async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) + => await GetApplicationInfoAsync().ConfigureAwait(false); + + Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetChannel(id)); + + Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, + RequestOptions options) + => Task.FromResult>(PrivateChannels); + + Task> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) + => Task.FromResult>(DMChannels); + + Task> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, + RequestOptions options) + => Task.FromResult>(GroupChannels); + + async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) + => await GetConnectionsAsync().ConfigureAwait(false); + + async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) + => await GetInviteAsync(inviteId, options).ConfigureAwait(false); + + Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetGuild(id)); + + Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) + => Task.FromResult>(Guilds); + + async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, + RequestOptions options) + => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); + + Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetUser(id)); + + Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) + => Task.FromResult(GetUser(username, discriminator)); + + Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) + => Task.FromResult>(VoiceRegions); + + Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) + => Task.FromResult(GetVoiceRegion(id)); + + async Task IDiscordClient.StartAsync() + => await StartAsync().ConfigureAwait(false); + + async Task IDiscordClient.StopAsync() + => await StopAsync().ConfigureAwait(false); + + private static DiscordSocketApiClient CreateApiClient(DiscordSocketConfig config) + => new DiscordSocketApiClient(config.RestClientProvider, config.WebSocketProvider, DiscordConfig.UserAgent, + config.GatewayHost); + internal override void Dispose(bool disposing) { - if (disposing) - { - StopAsync().GetAwaiter().GetResult(); - ApiClient.Dispose(); - } + if (!disposing) return; + StopAsync().GetAwaiter().GetResult(); + ApiClient.Dispose(); } internal override async Task OnLoginAsync(TokenType tokenType, string token) { if (_parentClient == null) { - var voiceRegions = await ApiClient.GetVoiceRegionsAsync(new RequestOptions { IgnoreState = true, RetryMode = RetryMode.AlwaysRetry }).ConfigureAwait(false); - _voiceRegions = voiceRegions.Select(x => RestVoiceRegion.Create(this, x)).ToImmutableDictionary(x => x.Id); + var voiceRegions = await ApiClient.GetVoiceRegionsAsync(new RequestOptions + { + IgnoreState = true, + RetryMode = RetryMode.AlwaysRetry + }).ConfigureAwait(false); + _voiceRegions = voiceRegions.Select(x => RestVoiceRegion.Create(this, x)) + .ToImmutableDictionary(x => x.Id); } else _voiceRegions = _parentClient._voiceRegions; } + internal override async Task OnLogoutAsync() { await StopAsync().ConfigureAwait(false); @@ -156,6 +239,7 @@ namespace Discord.WebSocket public override async Task StartAsync() => await _connection.StartAsync().ConfigureAwait(false); + public override async Task StopAsync() => await _connection.StopAsync().ConfigureAwait(false); @@ -194,9 +278,9 @@ namespace Discord.WebSocket } } } + private async Task OnDisconnectingAsync(Exception ex) { - await _gatewayLogger.DebugAsync("Disconnecting ApiClient").ConfigureAwait(false); await ApiClient.DisconnectAsync().ConfigureAwait(false); @@ -207,7 +291,10 @@ namespace Discord.WebSocket await heartbeatTask.ConfigureAwait(false); _heartbeatTask = null; - while (_heartbeatTimes.TryDequeue(out long time)) { } + while (_heartbeatTimes.TryDequeue(out var time)) + { + } + _lastMessageTime = 0; await _gatewayLogger.DebugAsync("Waiting for guild downloader").ConfigureAwait(false); @@ -218,20 +305,21 @@ namespace Discord.WebSocket //Clear large guild queue await _gatewayLogger.DebugAsync("Clearing large guild queue").ConfigureAwait(false); - while (_largeGuilds.TryDequeue(out ulong guildId)) { } + while (_largeGuilds.TryDequeue(out var guildId)) + { + } //Raise virtual GUILD_UNAVAILABLEs await _gatewayLogger.DebugAsync("Raising virtual GuildUnavailables").ConfigureAwait(false); foreach (var guild in State.Guilds) - { if (guild.IsAvailable) await GuildUnavailableAsync(guild).ConfigureAwait(false); - } } /// public override async Task GetApplicationInfoAsync(RequestOptions options = null) - => _applicationInfo ?? (_applicationInfo = await ClientHelper.GetApplicationInfoAsync(this, options ?? RequestOptions.Default).ConfigureAwait(false)); + => _applicationInfo ?? (_applicationInfo = await ClientHelper + .GetApplicationInfoAsync(this, options ?? RequestOptions.Default).ConfigureAwait(false)); /// public override SocketGuild GetGuild(ulong id) @@ -244,63 +332,59 @@ namespace Discord.WebSocket /// public override SocketUser GetUser(ulong id) => State.GetUser(id); + /// public override SocketUser GetUser(string username, string discriminator) => State.Users.FirstOrDefault(x => x.Discriminator == discriminator && x.Username == username); - internal SocketGlobalUser GetOrCreateUser(ClientState state, Discord.API.User model) - { - return state.GetOrAddUser(model.Id, x => - { - var user = SocketGlobalUser.Create(this, state, model); - user.GlobalUser.AddRef(); - return user; - }); - } - internal SocketGlobalUser GetOrCreateSelfUser(ClientState state, Discord.API.User model) + + internal SocketGlobalUser GetOrCreateUser(ClientState state, User model) => state.GetOrAddUser(model.Id, x => { - return state.GetOrAddUser(model.Id, x => + var user = SocketGlobalUser.Create(this, state, model); + user.GlobalUser.AddRef(); + return user; + }); + + internal SocketGlobalUser GetOrCreateSelfUser(ClientState state, User model) => state.GetOrAddUser(model.Id, + x => { var user = SocketGlobalUser.Create(this, state, model); user.GlobalUser.AddRef(); user.Presence = new SocketPresence(UserStatus.Online, null); return user; }); - } + internal void RemoveUser(ulong id) => State.RemoveUser(id); /// public override RestVoiceRegion GetVoiceRegion(string id) { - if (_voiceRegions.TryGetValue(id, out RestVoiceRegion region)) - return region; - return null; + return _voiceRegions.TryGetValue(id, out var region) ? region : null; } /// Downloads the users list for the provided guilds, if they don't have a complete list. public override async Task DownloadUsersAsync(IEnumerable guilds) { if (ConnectionState == ConnectionState.Connected) - { - //Race condition leads to guilds being requested twice, probably okay - await ProcessUserDownloadsAsync(guilds.Select(x => GetGuild(x.Id)).Where(x => x != null)).ConfigureAwait(false); - } + await ProcessUserDownloadsAsync(guilds.Select(x => GetGuild(x.Id)).Where(x => x != null)) + .ConfigureAwait(false); } + private async Task ProcessUserDownloadsAsync(IEnumerable guilds) { var cachedGuilds = guilds.ToImmutableArray(); const short batchSize = 50; - ulong[] batchIds = new ulong[Math.Min(batchSize, cachedGuilds.Length)]; - Task[] batchTasks = new Task[batchIds.Length]; - int batchCount = (cachedGuilds.Length + (batchSize - 1)) / batchSize; + var batchIds = new ulong[Math.Min(batchSize, cachedGuilds.Length)]; + var batchTasks = new Task[batchIds.Length]; + var batchCount = (cachedGuilds.Length + (batchSize - 1)) / batchSize; for (int i = 0, k = 0; i < batchCount; i++) { - bool isLast = i == batchCount - 1; - int count = isLast ? (batchIds.Length - (batchCount - 1) * batchSize) : batchSize; + var isLast = i == batchCount - 1; + var count = isLast ? batchIds.Length - (batchCount - 1) * batchSize : batchSize; - for (int j = 0; j < count; j++, k++) + for (var j = 0; j < count; j++, k++) { var guild = cachedGuilds[k]; batchIds[j] = guild.Id; @@ -325,7 +409,9 @@ namespace Discord.WebSocket _statusSince = null; await SendStatusAsync().ConfigureAwait(false); } - public override async Task SetGameAsync(string name, string streamUrl = null, ActivityType type = ActivityType.Playing) + + public override async Task SetGameAsync(string name, string streamUrl = null, + ActivityType type = ActivityType.Playing) { if (!string.IsNullOrEmpty(streamUrl)) Activity = new StreamingGame(name, streamUrl); @@ -335,6 +421,7 @@ namespace Discord.WebSocket Activity = null; await SendStatusAsync().ConfigureAwait(false); } + public override async Task SetActivityAsync(IActivity activity) { Activity = activity; @@ -380,1146 +467,1141 @@ namespace Discord.WebSocket switch (opCode) { case GatewayOpCode.Hello: - { - await _gatewayLogger.DebugAsync("Received Hello").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + { + await _gatewayLogger.DebugAsync("Received Hello").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); - _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); - } + _heartbeatTask = RunHeartbeatAsync(data.HeartbeatInterval, _connection.CancelToken); + } break; case GatewayOpCode.Heartbeat: - { - await _gatewayLogger.DebugAsync("Received Heartbeat").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Heartbeat").ConfigureAwait(false); - await ApiClient.SendHeartbeatAsync(_lastSeq).ConfigureAwait(false); - } + await ApiClient.SendHeartbeatAsync(_lastSeq).ConfigureAwait(false); + } break; case GatewayOpCode.HeartbeatAck: - { - await _gatewayLogger.DebugAsync("Received HeartbeatAck").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received HeartbeatAck").ConfigureAwait(false); - if (_heartbeatTimes.TryDequeue(out long time)) - { - int latency = (int)(Environment.TickCount - time); - int before = Latency; - Latency = latency; + if (_heartbeatTimes.TryDequeue(out var time)) + { + var latency = (int)(Environment.TickCount - time); + var before = Latency; + Latency = latency; - await TimedInvokeAsync(_latencyUpdatedEvent, nameof(LatencyUpdated), before, latency).ConfigureAwait(false); - } + await TimedInvokeAsync(_latencyUpdatedEvent, nameof(LatencyUpdated), before, latency) + .ConfigureAwait(false); } + } break; case GatewayOpCode.InvalidSession: - { - await _gatewayLogger.DebugAsync("Received InvalidSession").ConfigureAwait(false); - await _gatewayLogger.WarningAsync("Failed to resume previous session").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received InvalidSession").ConfigureAwait(false); + await _gatewayLogger.WarningAsync("Failed to resume previous session").ConfigureAwait(false); - _sessionId = null; - _lastSeq = 0; + _sessionId = null; + _lastSeq = 0; - await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards).ConfigureAwait(false); - } + await ApiClient.SendIdentifyAsync(shardID: ShardId, totalShards: TotalShards) + .ConfigureAwait(false); + } break; case GatewayOpCode.Reconnect: - { - await _gatewayLogger.DebugAsync("Received Reconnect").ConfigureAwait(false); - _connection.Error(new Exception("Server requested a reconnect")); - } + { + await _gatewayLogger.DebugAsync("Received Reconnect").ConfigureAwait(false); + _connection.Error(new Exception("Server requested a reconnect")); + } break; case GatewayOpCode.Dispatch: switch (type) { //Connection case "READY": + { + try { - try + await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); + + var currentUser = SocketSelfUser.Create(this, state, data.User); + ApiClient.CurrentUserId = currentUser.Id; + var unavailableGuilds = 0; + foreach (var model in data.Guilds) { - await _gatewayLogger.DebugAsync("Received Dispatch (READY)").ConfigureAwait(false); + var guild = AddGuild(model, state); + if (!guild.IsAvailable) + unavailableGuilds++; + else + await GuildAvailableAsync(guild).ConfigureAwait(false); + } - var data = (payload as JToken).ToObject(_serializer); - var state = new ClientState(data.Guilds.Length, data.PrivateChannels.Length); + foreach (var t in data.PrivateChannels) + AddPrivateChannel(t, state); - var currentUser = SocketSelfUser.Create(this, state, data.User); - ApiClient.CurrentUserId = currentUser.Id; - int unavailableGuilds = 0; - for (int i = 0; i < data.Guilds.Length; i++) + _sessionId = data.SessionId; + _unavailableGuildCount = unavailableGuilds; + CurrentUser = currentUser; + State = state; + } + catch (Exception ex) + { + _connection.CriticalError(new Exception("Processing READY failed", ex)); + return; + } + + _lastGuildAvailableTime = Environment.TickCount; + _guildDownloadTask = WaitForGuildsAsync(_connection.CancelToken, _gatewayLogger) + .ContinueWith(async x => + { + if (x.IsFaulted) { - var model = data.Guilds[i]; - var guild = AddGuild(model, state); - if (!guild.IsAvailable) - unavailableGuilds++; - else - await GuildAvailableAsync(guild).ConfigureAwait(false); + _connection.Error(x.Exception); + return; } - for (int i = 0; i < data.PrivateChannels.Length; i++) - AddPrivateChannel(data.PrivateChannels[i], state); - _sessionId = data.SessionId; - _unavailableGuildCount = unavailableGuilds; - CurrentUser = currentUser; - State = state; - } - catch (Exception ex) - { - _connection.CriticalError(new Exception("Processing READY failed", ex)); - return; - } + if (_connection.CancelToken.IsCancellationRequested) + return; - _lastGuildAvailableTime = Environment.TickCount; - _guildDownloadTask = WaitForGuildsAsync(_connection.CancelToken, _gatewayLogger) - .ContinueWith(async x => - { - if (x.IsFaulted) - { - _connection.Error(x.Exception); - return; - } - else if (_connection.CancelToken.IsCancellationRequested) - return; - - await TimedInvokeAsync(_readyEvent, nameof(Ready)).ConfigureAwait(false); - await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); - }); - _ = _connection.CompleteAsync(); - } + await TimedInvokeAsync(_readyEvent, nameof(Ready)).ConfigureAwait(false); + await _gatewayLogger.InfoAsync("Ready").ConfigureAwait(false); + }); + _ = _connection.CompleteAsync(); + } break; case "RESUMED": - { - await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (RESUMED)").ConfigureAwait(false); - _ = _connection.CompleteAsync(); + _ = _connection.CompleteAsync(); - //Notify the client that these guilds are available again - foreach (var guild in State.Guilds) - { - if (guild.IsAvailable) - await GuildAvailableAsync(guild).ConfigureAwait(false); - } + //Notify the client that these guilds are available again + foreach (var guild in State.Guilds) + if (guild.IsAvailable) + await GuildAvailableAsync(guild).ConfigureAwait(false); - await _gatewayLogger.InfoAsync("Resumed previous session").ConfigureAwait(false); - } + await _gatewayLogger.InfoAsync("Resumed previous session").ConfigureAwait(false); + } break; //Guilds case "GUILD_CREATE": + { + var data = (payload as JToken).ToObject(_serializer); + + if (data.Unavailable == false) { - var data = (payload as JToken).ToObject(_serializer); + type = "GUILD_AVAILABLE"; + _lastGuildAvailableTime = Environment.TickCount; + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_AVAILABLE)") + .ConfigureAwait(false); - if (data.Unavailable == false) + var guild = State.GetGuild(data.Id); + if (guild != null) { - type = "GUILD_AVAILABLE"; - _lastGuildAvailableTime = Environment.TickCount; - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_AVAILABLE)").ConfigureAwait(false); - - var guild = State.GetGuild(data.Id); - if (guild != null) - { - guild.Update(State, data); + guild.Update(State, data); - if (_unavailableGuildCount != 0) - _unavailableGuildCount--; - await GuildAvailableAsync(guild).ConfigureAwait(false); + if (_unavailableGuildCount != 0) + _unavailableGuildCount--; + await GuildAvailableAsync(guild).ConfigureAwait(false); - if (guild.DownloadedMemberCount >= guild.MemberCount && !guild.DownloaderPromise.IsCompleted) - { - guild.CompleteDownloadUsers(); - await TimedInvokeAsync(_guildMembersDownloadedEvent, nameof(GuildMembersDownloaded), guild).ConfigureAwait(false); - } - } - else + if (guild.DownloadedMemberCount >= guild.MemberCount && + !guild.DownloaderPromise.IsCompleted) { - await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); - return; + guild.CompleteDownloadUsers(); + await TimedInvokeAsync(_guildMembersDownloadedEvent, + nameof(GuildMembersDownloaded), guild).ConfigureAwait(false); } } else - { - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_CREATE)").ConfigureAwait(false); - - var guild = AddGuild(data, State); - if (guild != null) - { - await TimedInvokeAsync(_joinedGuildEvent, nameof(JoinedGuild), guild).ConfigureAwait(false); - } - else - { - await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); - return; - } - } + await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); } - break; - case "GUILD_UPDATE": + else { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_CREATE)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.Id); + var guild = AddGuild(data, State); if (guild != null) - { - var before = guild.Clone(); - guild.Update(State, data); - await TimedInvokeAsync(_guildUpdatedEvent, nameof(GuildUpdated), before, guild).ConfigureAwait(false); - } + await TimedInvokeAsync(_joinedGuildEvent, nameof(JoinedGuild), guild) + .ConfigureAwait(false); else - { await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); - return; - } } + } break; - case "GUILD_EMOJIS_UPDATE": + case "GUILD_UPDATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UPDATE)") + .ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.Id); + if (guild != null) { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)").ConfigureAwait(false); + var before = guild.Clone(); + guild.Update(State, data); + await TimedInvokeAsync(_guildUpdatedEvent, nameof(GuildUpdated), before, guild) + .ConfigureAwait(false); + } + else + await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); + } + break; + case "GUILD_EMOJIS_UPDATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_EMOJIS_UPDATE)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) - { - var before = guild.Clone(); - guild.Update(State, data); - await TimedInvokeAsync(_guildUpdatedEvent, nameof(GuildUpdated), before, guild).ConfigureAwait(false); - } - else - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) + { + var before = guild.Clone(); + guild.Update(State, data); + await TimedInvokeAsync(_guildUpdatedEvent, nameof(GuildUpdated), before, guild) + .ConfigureAwait(false); } + else + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + } break; case "GUILD_SYNC": + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.Id); + if (guild != null) + { + var before = guild.Clone(); + guild.Update(State, data); + //This is treated as an extension of GUILD_AVAILABLE + _unavailableGuildCount--; + _lastGuildAvailableTime = Environment.TickCount; + await GuildAvailableAsync(guild).ConfigureAwait(false); + await TimedInvokeAsync(_guildUpdatedEvent, nameof(GuildUpdated), before, guild) + .ConfigureAwait(false); + } + else + await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); + } + break; + case "GUILD_DELETE": + { + var data = (payload as JToken).ToObject(_serializer); + if (data.Unavailable == true) { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_SYNC)").ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + type = "GUILD_UNAVAILABLE"; + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_UNAVAILABLE)") + .ConfigureAwait(false); + var guild = State.GetGuild(data.Id); if (guild != null) { - var before = guild.Clone(); - guild.Update(State, data); - //This is treated as an extension of GUILD_AVAILABLE - _unavailableGuildCount--; - _lastGuildAvailableTime = Environment.TickCount; - await GuildAvailableAsync(guild).ConfigureAwait(false); - await TimedInvokeAsync(_guildUpdatedEvent, nameof(GuildUpdated), before, guild).ConfigureAwait(false); + await GuildUnavailableAsync(guild).ConfigureAwait(false); + _unavailableGuildCount++; } else - { await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); - return; - } } - break; - case "GUILD_DELETE": + else { - var data = (payload as JToken).ToObject(_serializer); - if (data.Unavailable == true) - { - type = "GUILD_UNAVAILABLE"; - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_UNAVAILABLE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_DELETE)") + .ConfigureAwait(false); - var guild = State.GetGuild(data.Id); - if (guild != null) - { - await GuildUnavailableAsync(guild).ConfigureAwait(false); - _unavailableGuildCount++; - } - else - { - await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); - return; - } - } - else + var guild = RemoveGuild(data.Id); + if (guild != null) { - await _gatewayLogger.DebugAsync($"Received Dispatch (GUILD_DELETE)").ConfigureAwait(false); - - var guild = RemoveGuild(data.Id); - if (guild != null) - { - await GuildUnavailableAsync(guild).ConfigureAwait(false); - await TimedInvokeAsync(_leftGuildEvent, nameof(LeftGuild), guild).ConfigureAwait(false); - } - else - { - await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); - return; - } + await GuildUnavailableAsync(guild).ConfigureAwait(false); + await TimedInvokeAsync(_leftGuildEvent, nameof(LeftGuild), guild) + .ConfigureAwait(false); } + else + await UnknownGuildAsync(type, data.Id).ConfigureAwait(false); } + } break; //Channels case "CHANNEL_CREATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_CREATE)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - SocketChannel channel = null; - if (data.GuildId.IsSpecified) + var data = (payload as JToken).ToObject(_serializer); + SocketChannel channel; + if (data.GuildId.IsSpecified) + { + var guild = State.GetGuild(data.GuildId.Value); + if (guild != null) { - var guild = State.GetGuild(data.GuildId.Value); - if (guild != null) - { - channel = guild.AddChannel(State, data); + channel = guild.AddChannel(State, data); - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - } - else + if (!guild.IsSynced) { - await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } } else { - channel = State.GetChannel(data.Id); - if (channel != null) - return; //Discord may send duplicate CHANNEL_CREATEs for DMs - channel = AddPrivateChannel(data, State) as SocketChannel; + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; } - + } + else + { + channel = State.GetChannel(data.Id); if (channel != null) - await TimedInvokeAsync(_channelCreatedEvent, nameof(ChannelCreated), channel).ConfigureAwait(false); + return; //Discord may send duplicate CHANNEL_CREATEs for DMs + channel = AddPrivateChannel(data, State) as SocketChannel; } + + if (channel != null) + await TimedInvokeAsync(_channelCreatedEvent, nameof(ChannelCreated), channel) + .ConfigureAwait(false); + } break; case "CHANNEL_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var channel = State.GetChannel(data.Id); - if (channel != null) - { - var before = channel.Clone(); - channel.Update(State, data); + { + await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_UPDATE)") + .ConfigureAwait(false); - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + var data = (payload as JToken).ToObject(_serializer); + var channel = State.GetChannel(data.Id); + if (channel != null) + { + var before = channel.Clone(); + channel.Update(State, data); - await TimedInvokeAsync(_channelUpdatedEvent, nameof(ChannelUpdated), before, channel).ConfigureAwait(false); - } - else + var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) { - await UnknownChannelAsync(type, data.Id).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } + + await TimedInvokeAsync(_channelUpdatedEvent, nameof(ChannelUpdated), before, + channel).ConfigureAwait(false); } + else + await UnknownChannelAsync(type, data.Id).ConfigureAwait(false); + } break; case "CHANNEL_DELETE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_DELETE)") + .ConfigureAwait(false); - SocketChannel channel = null; - var data = (payload as JToken).ToObject(_serializer); - if (data.GuildId.IsSpecified) + SocketChannel channel; + var data = (payload as JToken).ToObject(_serializer); + if (data.GuildId.IsSpecified) + { + var guild = State.GetGuild(data.GuildId.Value); + if (guild != null) { - var guild = State.GetGuild(data.GuildId.Value); - if (guild != null) - { - channel = guild.RemoveChannel(State, data.Id); + channel = guild.RemoveChannel(State, data.Id); - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - } - else + if (!guild.IsSynced) { - await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } } - else - channel = RemovePrivateChannel(data.Id) as SocketChannel; - - if (channel != null) - await TimedInvokeAsync(_channelDestroyedEvent, nameof(ChannelDestroyed), channel).ConfigureAwait(false); else { - await UnknownChannelAsync(type, data.Id, data.GuildId.GetValueOrDefault(0)).ConfigureAwait(false); + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); return; } } + else + channel = RemovePrivateChannel(data.Id) as SocketChannel; + + if (channel != null) + await TimedInvokeAsync(_channelDestroyedEvent, nameof(ChannelDestroyed), channel) + .ConfigureAwait(false); + else + await UnknownChannelAsync(type, data.Id, data.GuildId.GetValueOrDefault(0)) + .ConfigureAwait(false); + } break; //Members case "GUILD_MEMBER_ADD": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) - { - var user = guild.AddOrUpdateUser(data); - guild.MemberCount++; + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_ADD)") + .ConfigureAwait(false); - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) + { + var user = guild.AddOrUpdateUser(data); + guild.MemberCount++; - await TimedInvokeAsync(_userJoinedEvent, nameof(UserJoined), user).ConfigureAwait(false); - } - else + if (!guild.IsSynced) { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } + + await TimedInvokeAsync(_userJoinedEvent, nameof(UserJoined), user) + .ConfigureAwait(false); } + else + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + } break; case "GUILD_MEMBER_UPDATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)") + .ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_UPDATE)").ConfigureAwait(false); + var user = guild.GetUser(data.User.Id); - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) + if (!guild.IsSynced) { - var user = guild.GetUser(data.User.Id); - - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - if (user != null) - { - var before = user.Clone(); - user.Update(State, data); - await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), before, user).ConfigureAwait(false); - } - else - { - if (!guild.HasAllMembers) - await IncompleteGuildUserAsync(type, data.User.Id, data.GuildId).ConfigureAwait(false); - else - await UnknownGuildUserAsync(type, data.User.Id, data.GuildId).ConfigureAwait(false); - return; - } + if (user != null) + { + var before = user.Clone(); + user.Update(State, data); + await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), + before, user).ConfigureAwait(false); } else { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; + if (!guild.HasAllMembers) + await IncompleteGuildUserAsync(type, data.User.Id, data.GuildId) + .ConfigureAwait(false); + else + await UnknownGuildUserAsync(type, data.User.Id, data.GuildId) + .ConfigureAwait(false); } } + else + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + } break; case "GUILD_MEMBER_REMOVE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)") + .ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBER_REMOVE)").ConfigureAwait(false); + var user = guild.RemoveUser(data.User.Id); + guild.MemberCount--; - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) + if (!guild.IsSynced) { - var user = guild.RemoveUser(data.User.Id); - guild.MemberCount--; - - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - - if (user != null) - await TimedInvokeAsync(_userLeftEvent, nameof(UserLeft), user).ConfigureAwait(false); - else - { - if (!guild.HasAllMembers) - await IncompleteGuildUserAsync(type, data.User.Id, data.GuildId).ConfigureAwait(false); - else - await UnknownGuildUserAsync(type, data.User.Id, data.GuildId).ConfigureAwait(false); - return; - } + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; } + + if (user != null) + await TimedInvokeAsync(_userLeftEvent, nameof(UserLeft), user) + .ConfigureAwait(false); else { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; + if (!guild.HasAllMembers) + await IncompleteGuildUserAsync(type, data.User.Id, data.GuildId) + .ConfigureAwait(false); + else + await UnknownGuildUserAsync(type, data.User.Id, data.GuildId) + .ConfigureAwait(false); } } + else + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + } break; case "GUILD_MEMBERS_CHUNK": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_MEMBERS_CHUNK)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) - { - foreach (var memberModel in data.Members) - guild.AddOrUpdateUser(memberModel); + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) + { + foreach (var memberModel in data.Members) + guild.AddOrUpdateUser(memberModel); - if (guild.DownloadedMemberCount >= guild.MemberCount && !guild.DownloaderPromise.IsCompleted) - { - guild.CompleteDownloadUsers(); - await TimedInvokeAsync(_guildMembersDownloadedEvent, nameof(GuildMembersDownloaded), guild).ConfigureAwait(false); - } - } - else + if (guild.DownloadedMemberCount >= guild.MemberCount && + !guild.DownloaderPromise.IsCompleted) { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; + guild.CompleteDownloadUsers(); + await TimedInvokeAsync(_guildMembersDownloadedEvent, + nameof(GuildMembersDownloaded), guild).ConfigureAwait(false); } } + else + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + } break; case "CHANNEL_RECIPIENT_ADD": - { - await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_ADD)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) - { - var user = channel.GetOrAddUser(data.User); - await TimedInvokeAsync(_recipientAddedEvent, nameof(RecipientAdded), user).ConfigureAwait(false); - } - else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) + { + var user = channel.GetOrAddUser(data.User); + await TimedInvokeAsync(_recipientAddedEvent, nameof(RecipientAdded), user) + .ConfigureAwait(false); } + else + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + } break; case "CHANNEL_RECIPIENT_REMOVE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (CHANNEL_RECIPIENT_REMOVE)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) - { - var user = channel.RemoveUser(data.User.Id); - if (user != null) - await TimedInvokeAsync(_recipientRemovedEvent, nameof(RecipientRemoved), user).ConfigureAwait(false); - else - { - await UnknownChannelUserAsync(type, data.User.Id, data.ChannelId).ConfigureAwait(false); - return; - } - } + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is SocketGroupChannel channel) + { + var user = channel.RemoveUser(data.User.Id); + if (user != null) + await TimedInvokeAsync(_recipientRemovedEvent, nameof(RecipientRemoved), user) + .ConfigureAwait(false); else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } + await UnknownChannelUserAsync(type, data.User.Id, data.ChannelId) + .ConfigureAwait(false); } + else + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + } break; //Roles case "GUILD_ROLE_CREATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_CREATE)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) - { - var role = guild.AddRole(data.Role); + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) + { + var role = guild.AddRole(data.Role); - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - await TimedInvokeAsync(_roleCreatedEvent, nameof(RoleCreated), role).ConfigureAwait(false); - } - else + if (!guild.IsSynced) { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } + + await TimedInvokeAsync(_roleCreatedEvent, nameof(RoleCreated), role) + .ConfigureAwait(false); } + else + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + } break; case "GUILD_ROLE_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_UPDATE)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) + { + var role = guild.GetRole(data.Role.Id); + if (role != null) { - var role = guild.GetRole(data.Role.Id); - if (role != null) - { - var before = role.Clone(); - role.Update(State, data.Role); - - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + var before = role.Clone(); + role.Update(State, data.Role); - await TimedInvokeAsync(_roleUpdatedEvent, nameof(RoleUpdated), before, role).ConfigureAwait(false); - } - else + if (!guild.IsSynced) { - await UnknownRoleAsync(type, data.Role.Id, guild.Id).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } + + await TimedInvokeAsync(_roleUpdatedEvent, nameof(RoleUpdated), before, role) + .ConfigureAwait(false); } else - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } + await UnknownRoleAsync(type, data.Role.Id, guild.Id).ConfigureAwait(false); } + else + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + } break; case "GUILD_ROLE_DELETE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_ROLE_DELETE)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) + { + var role = guild.RemoveRole(data.RoleId); + if (role != null) { - var role = guild.RemoveRole(data.RoleId); - if (role != null) - { - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - - await TimedInvokeAsync(_roleDeletedEvent, nameof(RoleDeleted), role).ConfigureAwait(false); - } - else + if (!guild.IsSynced) { - await UnknownRoleAsync(type, data.RoleId, guild.Id).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } + + await TimedInvokeAsync(_roleDeletedEvent, nameof(RoleDeleted), role) + .ConfigureAwait(false); } else - { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); - return; - } + await UnknownRoleAsync(type, data.RoleId, guild.Id).ConfigureAwait(false); } + else + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + } break; //Bans case "GUILD_BAN_ADD": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) - { - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_ADD)") + .ConfigureAwait(false); - SocketUser user = guild.GetUser(data.User.Id); - if (user == null) - user = SocketUnknownUser.Create(this, State, data.User); - await TimedInvokeAsync(_userBannedEvent, nameof(UserBanned), user, guild).ConfigureAwait(false); - } - else + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) + { + if (!guild.IsSynced) { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } + + SocketUser user = guild.GetUser(data.User.Id); + if (user == null) + user = SocketUnknownUser.Create(this, State, data.User); + await TimedInvokeAsync(_userBannedEvent, nameof(UserBanned), user, guild) + .ConfigureAwait(false); } + else + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + } break; case "GUILD_BAN_REMOVE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - if (guild != null) - { - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + { + await _gatewayLogger.DebugAsync("Received Dispatch (GUILD_BAN_REMOVE)") + .ConfigureAwait(false); - SocketUser user = State.GetUser(data.User.Id); - if (user == null) - user = SocketUnknownUser.Create(this, State, data.User); - await TimedInvokeAsync(_userUnbannedEvent, nameof(UserUnbanned), user, guild).ConfigureAwait(false); - } - else + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + if (guild != null) + { + if (!guild.IsSynced) { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } + + SocketUser user = State.GetUser(data.User.Id); + if (user == null) + user = SocketUnknownUser.Create(this, State, data.User); + await TimedInvokeAsync(_userUnbannedEvent, nameof(UserUnbanned), user, guild) + .ConfigureAwait(false); } + else + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + } break; //Messages case "MESSAGE_CREATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)") + .ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_CREATE)").ConfigureAwait(false); + var guild = (channel as SocketGuildChannel)?.Guild; + if (guild != null && !guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + SocketUser author; + if (guild != null) { - var guild = (channel as SocketGuildChannel)?.Guild; - if (guild != null && !guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + if (data.WebhookId.IsSpecified) + author = SocketWebhookUser.Create(guild, State, data.Author.Value, + data.WebhookId.Value); + else + author = guild.GetUser(data.Author.Value.Id); + } + else + author = ((SocketChannel) channel).GetUser(data.Author.Value.Id); - SocketUser author; + if (author == null) + { if (guild != null) - { - if (data.WebhookId.IsSpecified) - author = SocketWebhookUser.Create(guild, State, data.Author.Value, data.WebhookId.Value); - else - author = guild.GetUser(data.Author.Value.Id); - } + author = guild.AddOrUpdateUser(data.Member + .Value); //per g250k, we can create an entire member now + else if (channel is SocketGroupChannel groupChannel) + author = groupChannel.GetOrAddUser(data.Author.Value); else - author = (channel as SocketChannel).GetUser(data.Author.Value.Id); - - if (author == null) { - if (guild != null) - author = guild.AddOrUpdateUser(data.Member.Value); //per g250k, we can create an entire member now - else if (channel is SocketGroupChannel) - author = (channel as SocketGroupChannel).GetOrAddUser(data.Author.Value); - else - { - await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id).ConfigureAwait(false); - return; - } + await UnknownChannelUserAsync(type, data.Author.Value.Id, channel.Id) + .ConfigureAwait(false); + return; } - - var msg = SocketMessage.Create(this, State, author, channel, data); - SocketChannelHelper.AddMessage(channel, this, msg); - await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg).ConfigureAwait(false); - } - else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; } + + var msg = SocketMessage.Create(this, State, author, channel, data); + SocketChannelHelper.AddMessage(channel, this, msg); + await TimedInvokeAsync(_messageReceivedEvent, nameof(MessageReceived), msg) + .ConfigureAwait(false); } + else + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + } break; case "MESSAGE_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_UPDATE)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + { + var guild = (channel as SocketGuildChannel)?.Guild; + if (guild != null && !guild.IsSynced) { - var guild = (channel as SocketGuildChannel)?.Guild; - if (guild != null && !guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - - SocketMessage before = null, after = null; - SocketMessage cachedMsg = channel.GetCachedMessage(data.Id); - bool isCached = cachedMsg != null; - if (isCached) - { - before = cachedMsg.Clone(); - cachedMsg.Update(State, data); - after = cachedMsg; - } - else if (data.Author.IsSpecified) - { - //Edited message isnt in cache, create a detached one - SocketUser author; - if (guild != null) - author = guild.GetUser(data.Author.Value.Id); - else - author = (channel as SocketChannel).GetUser(data.Author.Value.Id); - if (author == null) - author = SocketUnknownUser.Create(this, State, data.Author.Value); - - after = SocketMessage.Create(this, State, author, channel, data); - } - var cacheableBefore = new Cacheable(before, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id)); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - await TimedInvokeAsync(_messageUpdatedEvent, nameof(MessageUpdated), cacheableBefore, after, channel).ConfigureAwait(false); + SocketMessage before = null, after = null; + var cachedMsg = channel.GetCachedMessage(data.Id); + var isCached = cachedMsg != null; + if (isCached) + { + before = cachedMsg.Clone(); + cachedMsg.Update(State, data); + after = cachedMsg; } - else + else if (data.Author.IsSpecified) { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; + //Edited message isnt in cache, create a detached one + var author = (guild != null ? guild.GetUser(data.Author.Value.Id) : ((SocketChannel) channel).GetUser(data.Author.Value.Id)) ?? + SocketUnknownUser.Create(this, State, data.Author.Value); + + after = SocketMessage.Create(this, State, author, channel, data); } + + var cacheableBefore = new Cacheable(before, data.Id, isCached, + async () => await channel.GetMessageAsync(data.Id)); + + await TimedInvokeAsync(_messageUpdatedEvent, nameof(MessageUpdated), + cacheableBefore, after, channel).ConfigureAwait(false); } + else + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + } break; case "MESSAGE_DELETE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) - { - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - - var msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); - bool isCached = msg != null; - var cacheable = new Cacheable(msg, data.Id, isCached, async () => await channel.GetMessageAsync(data.Id)); + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE)") + .ConfigureAwait(false); - await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); - } - else + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + { + var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); return; } + + var msg = SocketChannelHelper.RemoveMessage(channel, this, data.Id); + var isCached = msg != null; + var cacheable = new Cacheable(msg, data.Id, isCached, + async () => await channel.GetMessageAsync(data.Id)); + + await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, + channel).ConfigureAwait(false); } + else + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + } break; case "MESSAGE_REACTION_ADD": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) - { - var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; - bool isCached = cachedMsg != null; - var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly); - var reaction = SocketReaction.Create(data, channel, cachedMsg, Optional.Create(user)); - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); - - cachedMsg?.AddReaction(reaction); + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_ADD)") + .ConfigureAwait(false); - await TimedInvokeAsync(_reactionAddedEvent, nameof(ReactionAdded), cacheable, channel, reaction).ConfigureAwait(false); - } - else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + { + var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; + var isCached = cachedMsg != null; + var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly); + var reaction = SocketReaction.Create(data, channel, cachedMsg, + Optional.Create(user)); + var cacheable = new Cacheable(cachedMsg, data.MessageId, + isCached, + async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); + + cachedMsg?.AddReaction(reaction); + + await TimedInvokeAsync(_reactionAddedEvent, nameof(ReactionAdded), cacheable, + channel, reaction).ConfigureAwait(false); } + else + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + } break; case "MESSAGE_REACTION_REMOVE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) - { - var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; - bool isCached = cachedMsg != null; - var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly); - var reaction = SocketReaction.Create(data, channel, cachedMsg, Optional.Create(user)); - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); - - cachedMsg?.RemoveReaction(reaction); + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE)") + .ConfigureAwait(false); - await TimedInvokeAsync(_reactionRemovedEvent, nameof(ReactionRemoved), cacheable, channel, reaction).ConfigureAwait(false); - } - else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + { + var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; + var isCached = cachedMsg != null; + var user = await channel.GetUserAsync(data.UserId, CacheMode.CacheOnly); + var reaction = SocketReaction.Create(data, channel, cachedMsg, + Optional.Create(user)); + var cacheable = new Cacheable(cachedMsg, data.MessageId, + isCached, + async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); + + cachedMsg?.RemoveReaction(reaction); + + await TimedInvokeAsync(_reactionRemovedEvent, nameof(ReactionRemoved), cacheable, + channel, reaction).ConfigureAwait(false); } + else + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + } break; case "MESSAGE_REACTION_REMOVE_ALL": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_REACTION_REMOVE_ALL)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) - { - var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; - bool isCached = cachedMsg != null; - var cacheable = new Cacheable(cachedMsg, data.MessageId, isCached, async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + { + var cachedMsg = channel.GetCachedMessage(data.MessageId) as SocketUserMessage; + var isCached = cachedMsg != null; + var cacheable = new Cacheable(cachedMsg, data.MessageId, + isCached, + async () => await channel.GetMessageAsync(data.MessageId) as IUserMessage); - cachedMsg?.ClearReactions(); + cachedMsg?.ClearReactions(); - await TimedInvokeAsync(_reactionsClearedEvent, nameof(ReactionsCleared), cacheable, channel).ConfigureAwait(false); - } - else - { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; - } + await TimedInvokeAsync(_reactionsClearedEvent, nameof(ReactionsCleared), cacheable, + channel).ConfigureAwait(false); } + else + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + } break; case "MESSAGE_DELETE_BULK": - { - await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (MESSAGE_DELETE_BULK)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + { + var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) { - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - - foreach (ulong id in data.Ids) - { - var msg = SocketChannelHelper.RemoveMessage(channel, this, id); - bool isCached = msg != null; - var cacheable = new Cacheable(msg, id, isCached, async () => await channel.GetMessageAsync(id)); - await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, channel).ConfigureAwait(false); - } + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; } - else + + foreach (var id in data.Ids) { - await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); - return; + var msg = SocketChannelHelper.RemoveMessage(channel, this, id); + var isCached = msg != null; + var cacheable = new Cacheable(msg, id, isCached, + async () => await channel.GetMessageAsync(id)); + await TimedInvokeAsync(_messageDeletedEvent, nameof(MessageDeleted), cacheable, + channel).ConfigureAwait(false); } } + else + await UnknownChannelAsync(type, data.ChannelId).ConfigureAwait(false); + } break; //Statuses case "PRESENCE_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (PRESENCE_UPDATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (PRESENCE_UPDATE)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); + var data = (payload as JToken).ToObject(_serializer); - if (data.GuildId.IsSpecified) + if (data.GuildId.IsSpecified) + { + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) { - var guild = State.GetGuild(data.GuildId.Value); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); - return; - } - if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } - var user = guild.GetUser(data.User.Id); - if (user == null) - { - if (data.Status == UserStatus.Offline) - { - return; - } - user = guild.AddOrUpdateUser(data); - } - else - { - var globalBefore = user.GlobalUser.Clone(); - if (user.GlobalUser.Update(State, data.User)) - { - //Global data was updated, trigger UserUpdated - await TimedInvokeAsync(_userUpdatedEvent, nameof(UserUpdated), globalBefore, user).ConfigureAwait(false); - } - } + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - var before = user.Clone(); - user.Update(State, data, true); - await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), before, user).ConfigureAwait(false); + var user = guild.GetUser(data.User.Id); + if (user == null) + { + if (data.Status == UserStatus.Offline) return; + user = guild.AddOrUpdateUser(data); } else { - var globalUser = State.GetUser(data.User.Id); - if (globalUser == null) - { - await UnknownGlobalUserAsync(type, data.User.Id).ConfigureAwait(false); - return; - } + var globalBefore = user.GlobalUser.Clone(); + if (user.GlobalUser.Update(State, data.User)) + await TimedInvokeAsync(_userUpdatedEvent, nameof(UserUpdated), globalBefore, + user).ConfigureAwait(false); + } - var before = globalUser.Clone(); - globalUser.Update(State, data.User); - globalUser.Update(State, data); - await TimedInvokeAsync(_userUpdatedEvent, nameof(UserUpdated), before, globalUser).ConfigureAwait(false); + var before = user.Clone(); + user.Update(State, data, true); + await TimedInvokeAsync(_guildMemberUpdatedEvent, nameof(GuildMemberUpdated), before, + user).ConfigureAwait(false); + } + else + { + var globalUser = State.GetUser(data.User.Id); + if (globalUser == null) + { + await UnknownGlobalUserAsync(type, data.User.Id).ConfigureAwait(false); + return; } + + var before = globalUser.Clone(); + globalUser.Update(State, data.User); + globalUser.Update(State, data); + await TimedInvokeAsync(_userUpdatedEvent, nameof(UserUpdated), before, globalUser) + .ConfigureAwait(false); } + } break; case "TYPING_START": - { - await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (TYPING_START)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + var data = (payload as JToken).ToObject(_serializer); + if (State.GetChannel(data.ChannelId) is ISocketMessageChannel channel) + { + var guild = (channel as SocketGuildChannel)?.Guild; + if (!(guild?.IsSynced ?? true)) { - var guild = (channel as SocketGuildChannel)?.Guild; - if (!(guild?.IsSynced ?? true)) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } - - var user = (channel as SocketChannel).GetUser(data.UserId); - if (user == null) - { - if (guild != null) - user = guild.AddOrUpdateUser(data.Member); - } - if (user != null) - await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), user, channel).ConfigureAwait(false); + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; } + + var user = (channel as SocketChannel).GetUser(data.UserId); + if (user == null) + if (guild != null) + user = guild.AddOrUpdateUser(data.Member); + if (user != null) + await TimedInvokeAsync(_userIsTypingEvent, nameof(UserIsTyping), user, channel) + .ConfigureAwait(false); } + } break; //Users case "USER_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (USER_UPDATE)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - if (data.Id == CurrentUser.Id) - { - var before = CurrentUser.Clone(); - CurrentUser.Update(State, data); - await TimedInvokeAsync(_selfUpdatedEvent, nameof(CurrentUserUpdated), before, CurrentUser).ConfigureAwait(false); - } - else - { - await _gatewayLogger.WarningAsync("Received USER_UPDATE for wrong user.").ConfigureAwait(false); - return; - } + var data = (payload as JToken).ToObject(_serializer); + if (data.Id == CurrentUser.Id) + { + var before = CurrentUser.Clone(); + CurrentUser.Update(State, data); + await TimedInvokeAsync(_selfUpdatedEvent, nameof(CurrentUserUpdated), before, + CurrentUser).ConfigureAwait(false); } + else + await _gatewayLogger.WarningAsync("Received USER_UPDATE for wrong user.") + .ConfigureAwait(false); + } break; //Voice case "VOICE_STATE_UPDATE": - { - await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)").ConfigureAwait(false); + { + await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_STATE_UPDATE)") + .ConfigureAwait(false); - var data = (payload as JToken).ToObject(_serializer); - SocketUser user; - SocketVoiceState before, after; - if (data.GuildId != null) + var data = (payload as JToken).ToObject(_serializer); + SocketUser user; + SocketVoiceState before, after; + if (data.GuildId != null) + { + var guild = State.GetGuild(data.GuildId.Value); + if (guild == null) { - var guild = State.GetGuild(data.GuildId.Value); - if (guild == null) - { - await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); - return; - } - else if (!guild.IsSynced) - { - await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); - return; - } + await UnknownGuildAsync(type, data.GuildId.Value).ConfigureAwait(false); + return; + } - if (data.ChannelId != null) - { - before = guild.GetVoiceState(data.UserId)?.Clone() ?? SocketVoiceState.Default; - after = await guild.AddOrUpdateVoiceStateAsync(State, data).ConfigureAwait(false); - /*if (data.UserId == CurrentUser.Id) - { - var _ = guild.FinishJoinAudioChannel().ConfigureAwait(false); - }*/ - } - else - { - before = await guild.RemoveVoiceStateAsync(data.UserId).ConfigureAwait(false) ?? SocketVoiceState.Default; - after = SocketVoiceState.Create(null, data); - } + if (!guild.IsSynced) + { + await UnsyncedGuildAsync(type, guild.Id).ConfigureAwait(false); + return; + } - // per g250k, this should always be sent, but apparently not always - user = guild.GetUser(data.UserId) - ?? (data.Member.IsSpecified ? guild.AddOrUpdateUser(data.Member.Value) : null); - if (user == null) + if (data.ChannelId != null) + { + before = guild.GetVoiceState(data.UserId)?.Clone() ?? SocketVoiceState.Default; + after = await guild.AddOrUpdateVoiceStateAsync(State, data) + .ConfigureAwait(false); + /*if (data.UserId == CurrentUser.Id) { - await UnknownGuildUserAsync(type, data.UserId, guild.Id).ConfigureAwait(false); - return; - } + var _ = guild.FinishJoinAudioChannel().ConfigureAwait(false); + }*/ } else { - var groupChannel = State.GetChannel(data.ChannelId.Value) as SocketGroupChannel; - if (groupChannel == null) - { - await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); - return; - } - if (data.ChannelId != null) - { - before = groupChannel.GetVoiceState(data.UserId)?.Clone() ?? SocketVoiceState.Default; - after = groupChannel.AddOrUpdateVoiceState(State, data); - } - else - { - before = groupChannel.RemoveVoiceState(data.UserId) ?? SocketVoiceState.Default; - after = SocketVoiceState.Create(null, data); - } - user = groupChannel.GetUser(data.UserId); - if (user == null) - { - await UnknownChannelUserAsync(type, data.UserId, groupChannel.Id).ConfigureAwait(false); - return; - } + before = await guild.RemoveVoiceStateAsync(data.UserId).ConfigureAwait(false) ?? + SocketVoiceState.Default; + after = SocketVoiceState.Create(null, data); } - await TimedInvokeAsync(_userVoiceStateUpdatedEvent, nameof(UserVoiceStateUpdated), user, before, after).ConfigureAwait(false); + // per g250k, this should always be sent, but apparently not always + user = guild.GetUser(data.UserId) + ?? (data.Member.IsSpecified + ? guild.AddOrUpdateUser(data.Member.Value) + : null); + if (user == null) + { + await UnknownGuildUserAsync(type, data.UserId, guild.Id).ConfigureAwait(false); + return; + } } - break; - case "VOICE_SERVER_UPDATE": + else { - await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_SERVER_UPDATE)").ConfigureAwait(false); - - var data = (payload as JToken).ToObject(_serializer); - var guild = State.GetGuild(data.GuildId); - var isCached = guild != null; - var cachedGuild = new Cacheable(guild, data.GuildId, isCached, - () => Task.FromResult(State.GetGuild(data.GuildId) as IGuild)); - - var voiceServer = new SocketVoiceServer(cachedGuild, data.Endpoint, data.Token); - await TimedInvokeAsync(_voiceServerUpdatedEvent, nameof(UserVoiceStateUpdated), voiceServer).ConfigureAwait(false); - - if (isCached) + var groupChannel = State.GetChannel(data.ChannelId.Value) as SocketGroupChannel; + if (groupChannel == null) { - var endpoint = data.Endpoint; - - //Only strip out the port if the endpoint contains it - var portBegin = endpoint.LastIndexOf(':'); - if (portBegin > 0) - endpoint = endpoint.Substring(0, portBegin); + await UnknownChannelAsync(type, data.ChannelId.Value).ConfigureAwait(false); + return; + } - var _ = guild.FinishConnectAudio(endpoint, data.Token).ConfigureAwait(false); + if (data.ChannelId != null) + { + before = groupChannel.GetVoiceState(data.UserId)?.Clone() ?? + SocketVoiceState.Default; + after = groupChannel.AddOrUpdateVoiceState(State, data); } else { - await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + before = groupChannel.RemoveVoiceState(data.UserId) ?? SocketVoiceState.Default; + after = SocketVoiceState.Create(null, data); + } + + user = groupChannel.GetUser(data.UserId); + if (user == null) + { + await UnknownChannelUserAsync(type, data.UserId, groupChannel.Id) + .ConfigureAwait(false); + return; } + } + + await TimedInvokeAsync(_userVoiceStateUpdatedEvent, nameof(UserVoiceStateUpdated), user, + before, after).ConfigureAwait(false); + } + break; + case "VOICE_SERVER_UPDATE": + { + await _gatewayLogger.DebugAsync("Received Dispatch (VOICE_SERVER_UPDATE)") + .ConfigureAwait(false); + + var data = (payload as JToken).ToObject(_serializer); + var guild = State.GetGuild(data.GuildId); + var isCached = guild != null; + var cachedGuild = new Cacheable(guild, data.GuildId, isCached, + () => Task.FromResult(State.GetGuild(data.GuildId) as IGuild)); + + var voiceServer = new SocketVoiceServer(cachedGuild, data.Endpoint, data.Token); + await TimedInvokeAsync(_voiceServerUpdatedEvent, nameof(UserVoiceStateUpdated), + voiceServer).ConfigureAwait(false); + if (isCached) + { + var endpoint = data.Endpoint; + + //Only strip out the port if the endpoint contains it + var portBegin = endpoint.LastIndexOf(':'); + if (portBegin > 0) + endpoint = endpoint.Substring(0, portBegin); + + var _ = guild.FinishConnectAudio(endpoint, data.Token).ConfigureAwait(false); } + else + await UnknownGuildAsync(type, data.GuildId).ConfigureAwait(false); + } break; //Ignored (User only) case "CHANNEL_PINS_ACK": - await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_ACK)") + .ConfigureAwait(false); break; case "CHANNEL_PINS_UPDATE": - await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_UPDATE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Ignored Dispatch (CHANNEL_PINS_UPDATE)") + .ConfigureAwait(false); break; case "GUILD_INTEGRATIONS_UPDATE": - await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_INTEGRATIONS_UPDATE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Ignored Dispatch (GUILD_INTEGRATIONS_UPDATE)") + .ConfigureAwait(false); break; case "MESSAGE_ACK": await _gatewayLogger.DebugAsync("Ignored Dispatch (MESSAGE_ACK)").ConfigureAwait(false); break; case "PRESENCES_REPLACE": - await _gatewayLogger.DebugAsync("Ignored Dispatch (PRESENCES_REPLACE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Ignored Dispatch (PRESENCES_REPLACE)") + .ConfigureAwait(false); break; case "USER_SETTINGS_UPDATE": - await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Ignored Dispatch (USER_SETTINGS_UPDATE)") + .ConfigureAwait(false); break; case "WEBHOOKS_UPDATE": - await _gatewayLogger.DebugAsync("Ignored Dispatch (WEBHOOKS_UPDATE)").ConfigureAwait(false); + await _gatewayLogger.DebugAsync("Ignored Dispatch (WEBHOOKS_UPDATE)") + .ConfigureAwait(false); break; //Others @@ -1527,6 +1609,7 @@ namespace Discord.WebSocket await _gatewayLogger.WarningAsync($"Unknown Dispatch ({type})").ConfigureAwait(false); break; } + break; default: await _gatewayLogger.WarningAsync($"Unknown OpCode ({opCode})").ConfigureAwait(false); @@ -1535,7 +1618,8 @@ namespace Discord.WebSocket } catch (Exception ex) { - await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex).ConfigureAwait(false); + await _gatewayLogger.ErrorAsync($"Error handling {opCode}{(type != null ? $" ({type})" : "")}", ex) + .ConfigureAwait(false); } } @@ -1546,17 +1630,15 @@ namespace Discord.WebSocket await _gatewayLogger.DebugAsync("Heartbeat Started").ConfigureAwait(false); while (!cancelToken.IsCancellationRequested) { - int now = Environment.TickCount; + var now = Environment.TickCount; //Did server respond to our last heartbeat, or are we still receiving messages (long load?) - if (_heartbeatTimes.Count != 0 && (now - _lastMessageTime) > intervalMillis) - { + if (_heartbeatTimes.Count != 0 && now - _lastMessageTime > intervalMillis) if (ConnectionState == ConnectionState.Connected && (_guildDownloadTask?.IsCompleted ?? true)) { _connection.Error(new Exception("Server missed last heartbeat")); return; } - } _heartbeatTimes.Enqueue(now); try @@ -1570,6 +1652,7 @@ namespace Discord.WebSocket await Task.Delay(intervalMillis, cancelToken).ConfigureAwait(false); } + await _gatewayLogger.DebugAsync("Heartbeat Stopped").ConfigureAwait(false); } catch (OperationCanceledException) @@ -1581,6 +1664,7 @@ namespace Discord.WebSocket await _gatewayLogger.ErrorAsync("Heartbeat Errored", ex).ConfigureAwait(false); } } + /*public async Task WaitForGuildsAsync() { var downloadTask = _guildDownloadTask; @@ -1593,7 +1677,7 @@ namespace Discord.WebSocket try { await logger.DebugAsync("GuildDownloader Started").ConfigureAwait(false); - while ((_unavailableGuildCount != 0) && (Environment.TickCount - _lastGuildAvailableTime < 2000)) + while (_unavailableGuildCount != 0 && Environment.TickCount - _lastGuildAvailableTime < 2000) await Task.Delay(500, cancelToken).ConfigureAwait(false); await logger.DebugAsync("GuildDownloader Stopped").ConfigureAwait(false); } @@ -1606,6 +1690,7 @@ namespace Discord.WebSocket await logger.ErrorAsync("GuildDownloader Errored", ex).ConfigureAwait(false); } } + private async Task SyncGuildsAsync() { var guildIds = Guilds.Where(x => !x.IsSynced).Select(x => x.Id).ToImmutableArray(); @@ -1621,20 +1706,20 @@ namespace Discord.WebSocket _largeGuilds.Enqueue(model.Id); return guild; } + internal SocketGuild RemoveGuild(ulong id) { var guild = State.RemoveGuild(id); - if (guild != null) - { - foreach (var channel in guild.Channels) - State.RemoveChannel(id); - foreach (var user in guild.Users) - user.GlobalUser.RemoveRef(this); - } + if (guild == null) return null; + foreach (var channel in guild.Channels) + State.RemoveChannel(id); + foreach (var user in guild.Users) + user.GlobalUser.RemoveRef(this); + return guild; } - internal ISocketPrivateChannel AddPrivateChannel(API.Channel model, ClientState state) + internal ISocketPrivateChannel AddPrivateChannel(Channel model, ClientState state) { var channel = SocketChannel.CreatePrivate(this, state, model); state.AddChannel(channel as SocketChannel); @@ -1643,17 +1728,22 @@ namespace Discord.WebSocket return channel; } + internal ISocketPrivateChannel RemovePrivateChannel(ulong id) { var channel = State.RemoveChannel(id) as ISocketPrivateChannel; - if (channel != null) + switch (channel) { - if (channel is SocketDMChannel dmChannel) + case null: + return null; + case SocketDMChannel dmChannel: dmChannel.Recipient.GlobalUser.DMChannel = null; - - foreach (var recipient in channel.Recipients) - recipient.GlobalUser.RemoveRef(this); + break; } + + foreach (var recipient in channel.Recipients) + recipient.GlobalUser.RemoveRef(this); + return channel; } @@ -1665,6 +1755,7 @@ namespace Discord.WebSocket await TimedInvokeAsync(_guildAvailableEvent, nameof(GuildAvailable), guild).ConfigureAwait(false); } } + private async Task GuildUnavailableAsync(SocketGuild guild) { if (guild.IsConnected) @@ -1684,6 +1775,7 @@ namespace Discord.WebSocket await eventHandler.InvokeAsync().ConfigureAwait(false); } } + private async Task TimedInvokeAsync(AsyncEvent> eventHandler, string name, T arg) { if (eventHandler.HasSubscribers) @@ -1694,7 +1786,9 @@ namespace Discord.WebSocket await eventHandler.InvokeAsync(arg).ConfigureAwait(false); } } - private async Task TimedInvokeAsync(AsyncEvent> eventHandler, string name, T1 arg1, T2 arg2) + + private async Task TimedInvokeAsync(AsyncEvent> eventHandler, string name, T1 arg1, + T2 arg2) { if (eventHandler.HasSubscribers) { @@ -1704,7 +1798,9 @@ namespace Discord.WebSocket await eventHandler.InvokeAsync(arg1, arg2).ConfigureAwait(false); } } - private async Task TimedInvokeAsync(AsyncEvent> eventHandler, string name, T1 arg1, T2 arg2, T3 arg3) + + private async Task TimedInvokeAsync(AsyncEvent> eventHandler, string name, + T1 arg1, T2 arg2, T3 arg3) { if (eventHandler.HasSubscribers) { @@ -1714,26 +1810,33 @@ namespace Discord.WebSocket await eventHandler.InvokeAsync(arg1, arg2, arg3).ConfigureAwait(false); } } - private async Task TimedInvokeAsync(AsyncEvent> eventHandler, string name, T1 arg1, T2 arg2, T3 arg3, T4 arg4) + + private async Task TimedInvokeAsync(AsyncEvent> eventHandler, + string name, T1 arg1, T2 arg2, T3 arg3, T4 arg4) { if (eventHandler.HasSubscribers) { if (HandlerTimeout.HasValue) - await TimeoutWrap(name, () => eventHandler.InvokeAsync(arg1, arg2, arg3, arg4)).ConfigureAwait(false); + await TimeoutWrap(name, () => eventHandler.InvokeAsync(arg1, arg2, arg3, arg4)) + .ConfigureAwait(false); else await eventHandler.InvokeAsync(arg1, arg2, arg3, arg4).ConfigureAwait(false); } } - private async Task TimedInvokeAsync(AsyncEvent> eventHandler, string name, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + + private async Task TimedInvokeAsync(AsyncEvent> eventHandler, + string name, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) { if (eventHandler.HasSubscribers) { if (HandlerTimeout.HasValue) - await TimeoutWrap(name, () => eventHandler.InvokeAsync(arg1, arg2, arg3, arg4, arg5)).ConfigureAwait(false); + await TimeoutWrap(name, () => eventHandler.InvokeAsync(arg1, arg2, arg3, arg4, arg5)) + .ConfigureAwait(false); else await eventHandler.InvokeAsync(arg1, arg2, arg3, arg4, arg5).ConfigureAwait(false); } } + private async Task TimeoutWrap(string name, Func action) { try @@ -1742,41 +1845,48 @@ namespace Discord.WebSocket var handlersTask = action(); if (await Task.WhenAny(timeoutTask, handlersTask).ConfigureAwait(false) == timeoutTask) { - await _gatewayLogger.WarningAsync($"A {name} handler is blocking the gateway task.").ConfigureAwait(false); + await _gatewayLogger.WarningAsync($"A {name} handler is blocking the gateway task.") + .ConfigureAwait(false); await handlersTask.ConfigureAwait(false); //Ensure the handler completes } } catch (Exception ex) { - await _gatewayLogger.WarningAsync($"A {name} handler has thrown an unhandled exception.", ex).ConfigureAwait(false); + await _gatewayLogger.WarningAsync($"A {name} handler has thrown an unhandled exception.", ex) + .ConfigureAwait(false); } } private async Task UnknownGlobalUserAsync(string evnt, ulong userId) { - string details = $"{evnt} User={userId}"; + var details = $"{evnt} User={userId}"; await _gatewayLogger.WarningAsync($"Unknown User ({details}).").ConfigureAwait(false); } + private async Task UnknownChannelUserAsync(string evnt, ulong userId, ulong channelId) { - string details = $"{evnt} User={userId} Channel={channelId}"; + var details = $"{evnt} User={userId} Channel={channelId}"; await _gatewayLogger.WarningAsync($"Unknown User ({details}).").ConfigureAwait(false); } + private async Task UnknownGuildUserAsync(string evnt, ulong userId, ulong guildId) { - string details = $"{evnt} User={userId} Guild={guildId}"; + var details = $"{evnt} User={userId} Guild={guildId}"; await _gatewayLogger.WarningAsync($"Unknown User ({details}).").ConfigureAwait(false); } + private async Task IncompleteGuildUserAsync(string evnt, ulong userId, ulong guildId) { - string details = $"{evnt} User={userId} Guild={guildId}"; + var details = $"{evnt} User={userId} Guild={guildId}"; await _gatewayLogger.DebugAsync($"User has not been downloaded ({details}).").ConfigureAwait(false); } + private async Task UnknownChannelAsync(string evnt, ulong channelId) { - string details = $"{evnt} Channel={channelId}"; + var details = $"{evnt} Channel={channelId}"; await _gatewayLogger.WarningAsync($"Unknown Channel ({details}).").ConfigureAwait(false); } + private async Task UnknownChannelAsync(string evnt, ulong channelId, ulong guildId) { if (guildId == 0) @@ -1784,66 +1894,29 @@ namespace Discord.WebSocket await UnknownChannelAsync(evnt, channelId).ConfigureAwait(false); return; } - string details = $"{evnt} Channel={channelId} Guild={guildId}"; + + var details = $"{evnt} Channel={channelId} Guild={guildId}"; await _gatewayLogger.WarningAsync($"Unknown Channel ({details}).").ConfigureAwait(false); } + private async Task UnknownRoleAsync(string evnt, ulong roleId, ulong guildId) { - string details = $"{evnt} Role={roleId} Guild={guildId}"; + var details = $"{evnt} Role={roleId} Guild={guildId}"; await _gatewayLogger.WarningAsync($"Unknown Role ({details}).").ConfigureAwait(false); } + private async Task UnknownGuildAsync(string evnt, ulong guildId) { - string details = $"{evnt} Guild={guildId}"; + var details = $"{evnt} Guild={guildId}"; await _gatewayLogger.WarningAsync($"Unknown Guild ({details}).").ConfigureAwait(false); } + private async Task UnsyncedGuildAsync(string evnt, ulong guildId) { - string details = $"{evnt} Guild={guildId}"; + var details = $"{evnt} Guild={guildId}"; await _gatewayLogger.DebugAsync($"Unsynced Guild ({details}).").ConfigureAwait(false); } internal int GetAudioId() => _nextAudioId++; - - //IDiscordClient - async Task IDiscordClient.GetApplicationInfoAsync(RequestOptions options) - => await GetApplicationInfoAsync().ConfigureAwait(false); - - Task IDiscordClient.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetChannel(id)); - Task> IDiscordClient.GetPrivateChannelsAsync(CacheMode mode, RequestOptions options) - => Task.FromResult>(PrivateChannels); - Task> IDiscordClient.GetDMChannelsAsync(CacheMode mode, RequestOptions options) - => Task.FromResult>(DMChannels); - Task> IDiscordClient.GetGroupChannelsAsync(CacheMode mode, RequestOptions options) - => Task.FromResult>(GroupChannels); - - async Task> IDiscordClient.GetConnectionsAsync(RequestOptions options) - => await GetConnectionsAsync().ConfigureAwait(false); - - async Task IDiscordClient.GetInviteAsync(string inviteId, RequestOptions options) - => await GetInviteAsync(inviteId, options).ConfigureAwait(false); - - Task IDiscordClient.GetGuildAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetGuild(id)); - Task> IDiscordClient.GetGuildsAsync(CacheMode mode, RequestOptions options) - => Task.FromResult>(Guilds); - async Task IDiscordClient.CreateGuildAsync(string name, IVoiceRegion region, Stream jpegIcon, RequestOptions options) - => await CreateGuildAsync(name, region, jpegIcon).ConfigureAwait(false); - - Task IDiscordClient.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetUser(id)); - Task IDiscordClient.GetUserAsync(string username, string discriminator, RequestOptions options) - => Task.FromResult(GetUser(username, discriminator)); - - Task> IDiscordClient.GetVoiceRegionsAsync(RequestOptions options) - => Task.FromResult>(VoiceRegions); - Task IDiscordClient.GetVoiceRegionAsync(string id, RequestOptions options) - => Task.FromResult(GetVoiceRegion(id)); - - async Task IDiscordClient.StartAsync() - => await StartAsync().ConfigureAwait(false); - async Task IDiscordClient.StopAsync() - => await StopAsync().ConfigureAwait(false); } } diff --git a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs index 3f9c18863..8e51877b5 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketConfig.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketConfig.cs @@ -8,6 +8,12 @@ namespace Discord.WebSocket { public const string GatewayEncoding = "json"; + public DiscordSocketConfig() + { + WebSocketProvider = DefaultWebSocketProvider.Instance; + UdpSocketProvider = DefaultUdpSocketProvider.Instance; + } + /// Gets or sets the websocket host to connect to. If null, the client will use the /gateway endpoint. public string GatewayHost { get; set; } = null; @@ -16,31 +22,36 @@ namespace Discord.WebSocket /// Gets or sets the id for this shard. Must be less than TotalShards. public int? ShardId { get; set; } = null; + /// Gets or sets the total number of shards for this application. public int? TotalShards { get; set; } = null; - /// Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero disables the message cache entirely. + /// + /// Gets or sets the number of messages per channel that should be kept in cache. Setting this to zero disables + /// the message cache entirely. + /// public int MessageCacheSize { get; set; } = 0; - /// - /// Gets or sets the max number of users a guild may have for offline users to be included in the READY packet. Max is 250. + + /// + /// Gets or sets the max number of users a guild may have for offline users to be included in the READY packet. Max is + /// 250. /// public int LargeThreshold { get; set; } = 250; /// Gets or sets the provider used to generate new websocket connections. public WebSocketProvider WebSocketProvider { get; set; } + /// Gets or sets the provider used to generate new udp sockets. public UdpSocketProvider UdpSocketProvider { get; set; } /// Gets or sets whether or not all users should be downloaded as guilds come available. public bool AlwaysDownloadUsers { get; set; } = false; - /// Gets or sets the timeout for event handlers, in milliseconds, after which a warning will be logged. Null disables this check. - public int? HandlerTimeout { get; set; } = 3000; - public DiscordSocketConfig() - { - WebSocketProvider = DefaultWebSocketProvider.Instance; - UdpSocketProvider = DefaultUdpSocketProvider.Instance; - } + /// + /// Gets or sets the timeout for event handlers, in milliseconds, after which a warning will be logged. Null + /// disables this check. + /// + public int? HandlerTimeout { get; set; } = 3000; internal DiscordSocketConfig Clone() => MemberwiseClone() as DiscordSocketConfig; } diff --git a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs index 0eb92caed..df54def6e 100644 --- a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs @@ -1,10 +1,4 @@ #pragma warning disable CS1591 -using Discord.API; -using Discord.API.Voice; -using Discord.Net.Converters; -using Discord.Net.Udp; -using Discord.Net.WebSockets; -using Newtonsoft.Json; using System; using System.Diagnostics; using System.Globalization; @@ -13,6 +7,12 @@ using System.IO.Compression; using System.Text; using System.Threading; using System.Threading.Tasks; +using Discord.API; +using Discord.API.Voice; +using Discord.Net.Converters; +using Discord.Net.Udp; +using Discord.Net.WebSockets; +using Newtonsoft.Json; namespace Discord.Audio { @@ -20,37 +20,30 @@ namespace Discord.Audio { public const int MaxBitrate = 128 * 1024; public const string Mode = "xsalsa20_poly1305"; + private readonly SemaphoreSlim _connectionLock; + private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); - public event Func SentRequest { add { _sentRequestEvent.Add(value); } remove { _sentRequestEvent.Remove(value); } } - private readonly AsyncEvent> _sentRequestEvent = new AsyncEvent>(); - public event Func SentGatewayMessage { add { _sentGatewayMessageEvent.Add(value); } remove { _sentGatewayMessageEvent.Remove(value); } } - private readonly AsyncEvent> _sentGatewayMessageEvent = new AsyncEvent>(); - public event Func SentDiscovery { add { _sentDiscoveryEvent.Add(value); } remove { _sentDiscoveryEvent.Remove(value); } } - private readonly AsyncEvent> _sentDiscoveryEvent = new AsyncEvent>(); - public event Func SentData { add { _sentDataEvent.Add(value); } remove { _sentDataEvent.Remove(value); } } - private readonly AsyncEvent> _sentDataEvent = new AsyncEvent>(); + private readonly AsyncEvent> _receivedEvent = + new AsyncEvent>(); - public event Func ReceivedEvent { add { _receivedEvent.Add(value); } remove { _receivedEvent.Remove(value); } } - private readonly AsyncEvent> _receivedEvent = new AsyncEvent>(); - public event Func ReceivedPacket { add { _receivedPacketEvent.Add(value); } remove { _receivedPacketEvent.Remove(value); } } private readonly AsyncEvent> _receivedPacketEvent = new AsyncEvent>(); - public event Func Disconnected { add { _disconnectedEvent.Add(value); } remove { _disconnectedEvent.Remove(value); } } - private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); - + private readonly AsyncEvent> _sentDataEvent = new AsyncEvent>(); + private readonly AsyncEvent> _sentDiscoveryEvent = new AsyncEvent>(); + + private readonly AsyncEvent> _sentGatewayMessageEvent = + new AsyncEvent>(); + + private readonly AsyncEvent> _sentRequestEvent = + new AsyncEvent>(); + private readonly JsonSerializer _serializer; - private readonly SemaphoreSlim _connectionLock; private CancellationTokenSource _connectCancelToken; - private IUdpSocket _udp; private bool _isDisposed; private ulong _nextKeepalive; + private readonly IUdpSocket _udp; - public ulong GuildId { get; } - internal IWebSocketClient WebSocketClient { get; } - public ConnectionState ConnectionState { get; private set; } - - public ushort UdpPort => _udp.Port; - - internal DiscordVoiceAPIClient(ulong guildId, WebSocketProvider webSocketProvider, UdpSocketProvider udpSocketProvider, JsonSerializer serializer = null) + internal DiscordVoiceAPIClient(ulong guildId, WebSocketProvider webSocketProvider, + UdpSocketProvider udpSocketProvider, JsonSerializer serializer = null) { GuildId = guildId; _connectionLock = new SemaphoreSlim(1, 1); @@ -63,6 +56,7 @@ namespace Discord.Audio Buffer.BlockCopy(data, index, newData, 0, count); data = newData; } + await _receivedPacketEvent.InvokeAsync(data).ConfigureAwait(false); }; @@ -94,56 +88,107 @@ namespace Discord.Audio await _disconnectedEvent.InvokeAsync(ex).ConfigureAwait(false); }; - _serializer = serializer ?? new JsonSerializer { ContractResolver = new DiscordContractResolver() }; + _serializer = serializer ?? new JsonSerializer {ContractResolver = new DiscordContractResolver()}; + } + + public ulong GuildId { get; } + internal IWebSocketClient WebSocketClient { get; } + public ConnectionState ConnectionState { get; private set; } + + public ushort UdpPort => _udp.Port; + + public event Func SentRequest + { + add => _sentRequestEvent.Add(value); + remove => _sentRequestEvent.Remove(value); + } + + public event Func SentGatewayMessage + { + add => _sentGatewayMessageEvent.Add(value); + remove => _sentGatewayMessageEvent.Remove(value); + } + + public event Func SentDiscovery + { + add => _sentDiscoveryEvent.Add(value); + remove => _sentDiscoveryEvent.Remove(value); + } + + public event Func SentData + { + add => _sentDataEvent.Add(value); + remove => _sentDataEvent.Remove(value); + } + + public event Func ReceivedEvent + { + add => _receivedEvent.Add(value); + remove => _receivedEvent.Remove(value); + } + + public event Func ReceivedPacket + { + add => _receivedPacketEvent.Add(value); + remove => _receivedPacketEvent.Remove(value); + } + + public event Func Disconnected + { + add => _disconnectedEvent.Add(value); + remove => _disconnectedEvent.Remove(value); } + private void Dispose(bool disposing) { - if (!_isDisposed) + if (_isDisposed) return; + if (disposing) { - if (disposing) - { - _connectCancelToken?.Dispose(); - (_udp as IDisposable)?.Dispose(); - (WebSocketClient as IDisposable)?.Dispose(); - } - _isDisposed = true; + _connectCancelToken?.Dispose(); + (_udp as IDisposable)?.Dispose(); + (WebSocketClient as IDisposable)?.Dispose(); } + + _isDisposed = true; } + public void Dispose() => Dispose(true); public async Task SendAsync(VoiceOpCode opCode, object payload, RequestOptions options = null) { byte[] bytes = null; - payload = new SocketFrame { Operation = (int)opCode, Payload = payload }; - if (payload != null) - bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); + payload = new SocketFrame + { + Operation = (int)opCode, + Payload = payload + }; + bytes = Encoding.UTF8.GetBytes(SerializeJson(payload)); await WebSocketClient.SendAsync(bytes, 0, bytes.Length, true).ConfigureAwait(false); await _sentGatewayMessageEvent.InvokeAsync(opCode).ConfigureAwait(false); } + public async Task SendAsync(byte[] data, int offset, int bytes) { - await _udp.SendAsync(data, offset, bytes).ConfigureAwait(false); + await _udp.SendAsync(data, offset, bytes).ConfigureAwait(false); await _sentDataEvent.InvokeAsync(bytes).ConfigureAwait(false); } //WebSocket - public async Task SendHeartbeatAsync(RequestOptions options = null) - { - await SendAsync(VoiceOpCode.Heartbeat, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), options: options).ConfigureAwait(false); - } - public async Task SendIdentityAsync(ulong userId, string sessionId, string token) - { - await SendAsync(VoiceOpCode.Identify, new IdentifyParams + public async Task SendHeartbeatAsync(RequestOptions options = null) => + await SendAsync(VoiceOpCode.Heartbeat, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), options) + .ConfigureAwait(false); + + public async Task SendIdentityAsync(ulong userId, string sessionId, string token) => await SendAsync( + VoiceOpCode.Identify, new IdentifyParams { GuildId = GuildId, UserId = userId, SessionId = sessionId, Token = token }).ConfigureAwait(false); - } - public async Task SendSelectProtocol(string externalIp, int externalPort) - { - await SendAsync(VoiceOpCode.SelectProtocol, new SelectProtocolParams + + public async Task SendSelectProtocol(string externalIp, int externalPort) => await SendAsync( + VoiceOpCode.SelectProtocol, new SelectProtocolParams { Protocol = "udp", Data = new UdpProtocolInfo @@ -153,15 +198,12 @@ namespace Discord.Audio Mode = Mode } }).ConfigureAwait(false); - } - public async Task SendSetSpeaking(bool value) + + public async Task SendSetSpeaking(bool value) => await SendAsync(VoiceOpCode.Speaking, new SpeakingParams { - await SendAsync(VoiceOpCode.Speaking, new SpeakingParams - { - IsSpeaking = value, - Delay = 0 - }).ConfigureAwait(false); - } + IsSpeaking = value, + Delay = 0 + }).ConfigureAwait(false); public async Task ConnectAsync(string url) { @@ -170,8 +212,12 @@ namespace Discord.Audio { await ConnectInternalAsync(url).ConfigureAwait(false); } - finally { _connectionLock.Release(); } + finally + { + _connectionLock.Release(); + } } + private async Task ConnectInternalAsync(string url) { ConnectionState = ConnectionState.Connecting; @@ -202,15 +248,24 @@ namespace Discord.Audio { await DisconnectInternalAsync().ConfigureAwait(false); } - finally { _connectionLock.Release(); } + finally + { + _connectionLock.Release(); + } } + private async Task DisconnectInternalAsync() { if (ConnectionState == ConnectionState.Disconnected) return; ConnectionState = ConnectionState.Disconnecting; - - try { _connectCancelToken?.Cancel(false); } - catch { } + + try + { + _connectCancelToken?.Cancel(false); + } + catch + { + } //Wait for tasks to complete await _udp.StopAsync().ConfigureAwait(false); @@ -230,6 +285,7 @@ namespace Discord.Audio await SendAsync(packet, 0, 70).ConfigureAwait(false); await _sentDiscoveryEvent.InvokeAsync().ConfigureAwait(false); } + public async Task SendKeepaliveAsync() { var value = _nextKeepalive++; @@ -246,13 +302,12 @@ namespace Discord.Audio return value; } - public void SetUdpEndpoint(string ip, int port) - { - _udp.SetDestination(ip, port); - } + public void SetUdpEndpoint(string ip, int port) => _udp.SetDestination(ip, port); //Helpers - private static double ToMilliseconds(Stopwatch stopwatch) => Math.Round((double)stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); + private static double ToMilliseconds(Stopwatch stopwatch) => + Math.Round(stopwatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000.0, 2); + private string SerializeJson(object value) { var sb = new StringBuilder(256); @@ -261,6 +316,7 @@ namespace Discord.Audio _serializer.Serialize(writer, value); return sb.ToString(); } + private T DeserializeJson(Stream jsonStream) { using (TextReader text = new StreamReader(jsonStream)) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs index 5fef7e4cd..d5a7b4228 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/ISocketMessageChannel.cs @@ -1,7 +1,7 @@ -using Discord.Rest; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; +using Discord.Rest; namespace Discord.WebSocket { @@ -11,20 +11,30 @@ namespace Discord.WebSocket IReadOnlyCollection CachedMessages { get; } /// Sends a message to this message channel. - new Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); + new Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, + RequestOptions options = null); + /// Sends a file to this text channel, with an optional caption. - new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); + new Task SendFileAsync(string filePath, string text = null, bool isTTS = false, + Embed embed = null, RequestOptions options = null); /// Sends a file to this text channel, with an optional caption. - new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null); + new Task SendFileAsync(Stream stream, string filename, string text = null, bool isTTS = false, + Embed embed = null, RequestOptions options = null); SocketMessage GetCachedMessage(ulong id); + /// Gets the last N messages from this message channel. IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch); + /// Gets a collection of messages in this channel. - IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); + IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch); + /// Gets a collection of messages in this channel. - IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch); + IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch); + /// Gets a collection of pinned messages in this channel. new Task> GetPinnedMessagesAsync(RequestOptions options = null); } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs index 74ca02dba..51901d048 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketCategoryChannel.cs @@ -3,67 +3,69 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; -using System.Text; using System.Threading.Tasks; -using Discord.Audio; -using Discord.Rest; using Model = Discord.API.Channel; namespace Discord.WebSocket { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class SocketCategoryChannel : SocketGuildChannel, ICategoryChannel { - public override IReadOnlyCollection Users - => Guild.Users.Where(x => Permissions.GetValue( - Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)), - ChannelPermission.ViewChannel)).ToImmutableArray(); - - public IReadOnlyCollection Channels - => Guild.Channels.Where(x => x is INestedChannel nestedChannel && nestedChannel.CategoryId == Id).ToImmutableArray(); - internal SocketCategoryChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) : base(discord, id, guild) { } - internal new static SocketCategoryChannel Create(SocketGuild guild, ClientState state, Model model) - { - var entity = new SocketCategoryChannel(guild.Discord, model.Id, guild); - entity.Update(state, model); - return entity; - } - //Users - public override SocketGuildUser GetUser(ulong id) - { - var user = Guild.GetUser(id); - if (user != null) - { - var guildPerms = Permissions.ResolveGuild(Guild, user); - var channelPerms = Permissions.ResolveChannel(Guild, user, this, guildPerms); - if (Permissions.GetValue(channelPerms, ChannelPermission.ViewChannel)) - return user; - } - return null; - } + public override IReadOnlyCollection Users + => Guild.Users.Where(x => Permissions.GetValue( + Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)), + ChannelPermission.ViewChannel)).ToImmutableArray(); + + public IReadOnlyCollection Channels + => Guild.Channels.Where(x => x is INestedChannel nestedChannel && nestedChannel.CategoryId == Id) + .ToImmutableArray(); private string DebuggerDisplay => $"{Name} ({Id}, Category)"; - internal new SocketCategoryChannel Clone() => MemberwiseClone() as SocketCategoryChannel; // IGuildChannel - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, + RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); - Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) + + Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, + bool isUnique, RequestOptions options) => throw new NotSupportedException(); + Task> IGuildChannel.GetInvitesAsync(RequestOptions options) => throw new NotSupportedException(); //IChannel IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); + + internal new static SocketCategoryChannel Create(SocketGuild guild, ClientState state, Model model) + { + var entity = new SocketCategoryChannel(guild.Discord, model.Id, guild); + entity.Update(state, model); + return entity; + } + + //Users + public override SocketGuildUser GetUser(ulong id) + { + var user = Guild.GetUser(id); + if (user == null) return null; + var guildPerms = Permissions.ResolveGuild(Guild, user); + var channelPerms = Permissions.ResolveChannel(Guild, user, this, guildPerms); + return Permissions.GetValue(channelPerms, ChannelPermission.ViewChannel) ? user : null; + } + + internal new SocketCategoryChannel Clone() => MemberwiseClone() as SocketCategoryChannel; } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs index 502e61d15..6204bac52 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannel.cs @@ -1,5 +1,4 @@ -using Discord.Rest; -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -11,13 +10,23 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public abstract class SocketChannel : SocketEntity, IChannel { - public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - public IReadOnlyCollection Users => GetUsersInternal(); - internal SocketChannel(DiscordSocketClient discord, ulong id) : base(discord, id) { } + + public IReadOnlyCollection Users => GetUsersInternal(); + public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + + //IChannel + string IChannel.Name => null; + + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(null); //Overridden + + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + => AsyncEnumerable.Empty>(); //Overridden + internal static ISocketPrivateChannel CreatePrivate(DiscordSocketClient discord, ClientState state, Model model) { switch (model.Type) @@ -30,6 +39,7 @@ namespace Discord.WebSocket throw new InvalidOperationException($"Unexpected channel type: {model.Type}"); } } + internal abstract void Update(ClientState state, Model model); //User @@ -38,13 +48,5 @@ namespace Discord.WebSocket internal abstract IReadOnlyCollection GetUsersInternal(); internal SocketChannel Clone() => MemberwiseClone() as SocketChannel; - - //IChannel - string IChannel.Name => null; - - Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(null); //Overridden - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => AsyncEnumerable.Empty>(); //Overridden } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs index ca53315aa..55a980ccb 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs @@ -1,14 +1,15 @@ -using Discord.Rest; -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using Discord.Rest; namespace Discord.WebSocket { internal static class SocketChannelHelper { - public static IAsyncEnumerable> GetMessagesAsync(ISocketMessageChannel channel, DiscordSocketClient discord, MessageCache messages, + public static IAsyncEnumerable> GetMessagesAsync(ISocketMessageChannel channel, + DiscordSocketClient discord, MessageCache messages, ulong? fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) { if (dir == Direction.Around) @@ -16,46 +17,37 @@ namespace Discord.WebSocket IReadOnlyCollection cachedMessages = null; IAsyncEnumerable> result = null; - + if (dir == Direction.After && fromMessageId == null) return AsyncEnumerable.Empty>(); if (dir == Direction.Before || mode == CacheMode.CacheOnly) { - if (messages != null) //Cache enabled - cachedMessages = messages.GetMany(fromMessageId, dir, limit); - else - cachedMessages = ImmutableArray.Create(); + cachedMessages = messages != null ? messages.GetMany(fromMessageId, dir, limit) : ImmutableArray.Create(); result = ImmutableArray.Create(cachedMessages).ToAsyncEnumerable>(); } - if (dir == Direction.Before) - { - limit -= cachedMessages.Count; - if (mode == CacheMode.CacheOnly || limit <= 0) - return result; + if (dir != Direction.Before) + return mode == CacheMode.CacheOnly + ? result + : ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit, options); + limit -= cachedMessages.Count; + if (mode == CacheMode.CacheOnly || limit <= 0) + return result; - //Download remaining messages - ulong? minId = cachedMessages.Count > 0 ? cachedMessages.Min(x => x.Id) : fromMessageId; - var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, minId, dir, limit, options); - return result.Concat(downloadedMessages); - } - else - { - if (mode == CacheMode.CacheOnly) - return result; + //Download remaining messages + var minId = cachedMessages.Count > 0 ? cachedMessages.Min(x => x.Id) : fromMessageId; + var downloadedMessages = ChannelHelper.GetMessagesAsync(channel, discord, minId, dir, limit, options); + return result.Concat(downloadedMessages); - //Dont use cache in this case - return ChannelHelper.GetMessagesAsync(channel, discord, fromMessageId, dir, limit, options); - } + //Dont use cache in this case } - public static IReadOnlyCollection GetCachedMessages(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, + + public static IReadOnlyCollection GetCachedMessages(SocketChannel channel, + DiscordSocketClient discord, MessageCache messages, ulong? fromMessageId, Direction dir, int limit) { - if (messages != null) //Cache enabled - return messages.GetMany(fromMessageId, dir, limit); - else - return ImmutableArray.Create(); + return messages != null ? messages.GetMany(fromMessageId, dir, limit) : ImmutableArray.Create(); } public static void AddMessage(ISocketMessageChannel channel, DiscordSocketClient discord, @@ -63,12 +55,19 @@ namespace Discord.WebSocket { switch (channel) { - case SocketDMChannel dmChannel: dmChannel.AddMessage(msg); break; - case SocketGroupChannel groupChannel: groupChannel.AddMessage(msg); break; - case SocketTextChannel textChannel: textChannel.AddMessage(msg); break; + case SocketDMChannel dmChannel: + dmChannel.AddMessage(msg); + break; + case SocketGroupChannel groupChannel: + groupChannel.AddMessage(msg); + break; + case SocketTextChannel textChannel: + textChannel.AddMessage(msg); + break; default: throw new NotSupportedException("Unexpected ISocketMessageChannel type"); } } + public static SocketMessage RemoveMessage(ISocketMessageChannel channel, DiscordSocketClient discord, ulong id) { diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs index 11b7ce25a..6a63f0688 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketDMChannel.cs @@ -1,4 +1,3 @@ -using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -6,20 +5,16 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; +using Discord.Rest; using Model = Discord.API.Channel; namespace Discord.WebSocket { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class SocketDMChannel : SocketChannel, IDMChannel, ISocketPrivateChannel, ISocketMessageChannel { private readonly MessageCache _messages; - public SocketUser Recipient { get; private set; } - - public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create(); - public new IReadOnlyCollection Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); - internal SocketDMChannel(DiscordSocketClient discord, ulong id, SocketGlobalUser recipient) : base(discord, id) { @@ -28,66 +23,154 @@ namespace Discord.WebSocket if (Discord.MessageCacheSize > 0) _messages = new MessageCache(Discord, this); } - internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, Model model) - { - var entity = new SocketDMChannel(discord, model.Id, discord.GetOrCreateUser(state, model.Recipients.Value[0])); - entity.Update(state, model); - return entity; - } - internal override void Update(ClientState state, Model model) - { - Recipient.Update(state, model.Recipients.Value[0]); - } + + public SocketUser Recipient { get; } + public new IReadOnlyCollection Users => ImmutableArray.Create(Discord.CurrentUser, Recipient); + private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; public Task CloseAsync(RequestOptions options = null) => ChannelHelper.DeleteAsync(this, Discord, options); + public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + + public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + + public Task TriggerTypingAsync(RequestOptions options = null) + => ChannelHelper.TriggerTypingAsync(this, Discord, options); + + //IDMChannel + IUser IDMChannel.Recipient => Recipient; + + //IPrivateChannel + IReadOnlyCollection IPrivateChannel.Recipients => ImmutableArray.Create(Recipient); + + //IMessageChannel + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetMessageAsync(id, options).ConfigureAwait(false); + return GetCachedMessage(id); + } + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, + RequestOptions options) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, + options); + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, + Direction dir, int limit, CacheMode mode, RequestOptions options) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, + Direction dir, int limit, CacheMode mode, RequestOptions options) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, + options); + + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) + => await GetPinnedMessagesAsync(options).ConfigureAwait(false); + + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, + RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); + + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, + Embed embed, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); + + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, + RequestOptions options) + => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); + + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) + => EnterTypingState(options); + + //IChannel + string IChannel.Name => $"@{Recipient}"; + + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetUser(id)); + + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + + public IReadOnlyCollection CachedMessages => + _messages?.Messages ?? ImmutableArray.Create(); + //Messages public SocketMessage GetCachedMessage(ulong id) => _messages?.Get(id); - public async Task GetMessageAsync(ulong id, RequestOptions options = null) - { - IMessage msg = _messages?.Get(id); - if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); - return msg; - } - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); + public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); - public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + + public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); - public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + + public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); + public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, + RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, + RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, + Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); - public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) - => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); - public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) - => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + //ISocketPrivateChannel + IReadOnlyCollection ISocketPrivateChannel.Recipients => ImmutableArray.Create(Recipient); + + internal static SocketDMChannel Create(DiscordSocketClient discord, ClientState state, Model model) + { + var entity = new SocketDMChannel(discord, model.Id, + discord.GetOrCreateUser(state, model.Recipients.Value[0])); + entity.Update(state, model); + return entity; + } + + internal override void Update(ClientState state, Model model) => + Recipient.Update(state, model.Recipients.Value[0]); + + public async Task GetMessageAsync(ulong id, RequestOptions options = null) + { + IMessage msg = _messages?.Get(id); + if (msg == null) + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); + return msg; + } + + public IAsyncEnumerable> GetMessagesAsync( + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, + CacheMode.AllowDownload, options); + + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, + CacheMode.AllowDownload, options); + + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, + CacheMode.AllowDownload, options); - public Task TriggerTypingAsync(RequestOptions options = null) - => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) => ChannelHelper.EnterTypingState(this, Discord, options); internal void AddMessage(SocketMessage msg) => _messages?.Add(msg); + internal SocketMessage RemoveMessage(ulong id) => _messages?.Remove(id); @@ -96,60 +179,14 @@ namespace Discord.WebSocket { if (id == Recipient.Id) return Recipient; - else if (id == Discord.CurrentUser.Id) - return Discord.CurrentUser as SocketSelfUser; - else - return null; + return id == Discord.CurrentUser.Id ? Discord.CurrentUser : null; } public override string ToString() => $"@{Recipient}"; - private string DebuggerDisplay => $"@{Recipient} ({Id}, DM)"; internal new SocketDMChannel Clone() => MemberwiseClone() as SocketDMChannel; //SocketChannel internal override IReadOnlyCollection GetUsersInternal() => Users; internal override SocketUser GetUserInternal(ulong id) => GetUser(id); - - //IDMChannel - IUser IDMChannel.Recipient => Recipient; - - //ISocketPrivateChannel - IReadOnlyCollection ISocketPrivateChannel.Recipients => ImmutableArray.Create(Recipient); - - //IPrivateChannel - IReadOnlyCollection IPrivateChannel.Recipients => ImmutableArray.Create(Recipient); - - //IMessageChannel - async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id, options).ConfigureAwait(false); - else - return GetCachedMessage(id); - } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); - async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) - => await GetPinnedMessagesAsync(options).ConfigureAwait(false); - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) - => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); - IDisposable IMessageChannel.EnterTypingState(RequestOptions options) - => EnterTypingState(options); - - //IChannel - string IChannel.Name => $"@{Recipient}"; - - Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetUser(id)); - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => ImmutableArray.Create>(Users).ToAsyncEnumerable(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 125625456..835889532 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -1,5 +1,3 @@ -using Discord.Audio; -using Discord.Rest; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -8,42 +6,155 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; +using Discord.Audio; +using Discord.Rest; using Model = Discord.API.Channel; using UserModel = Discord.API.User; using VoiceStateModel = Discord.API.VoiceState; namespace Discord.WebSocket { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] - public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, ISocketAudioChannel + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] + public class SocketGroupChannel : SocketChannel, IGroupChannel, ISocketPrivateChannel, ISocketMessageChannel, + ISocketAudioChannel { private readonly MessageCache _messages; private string _iconId; private ConcurrentDictionary _users; - private ConcurrentDictionary _voiceStates; - - public string Name { get; private set; } - - public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create(); - public new IReadOnlyCollection Users => _users.ToReadOnlyCollection(); - public IReadOnlyCollection Recipients - => _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id).ToReadOnlyCollection(() => _users.Count - 1); + private readonly ConcurrentDictionary _voiceStates; internal SocketGroupChannel(DiscordSocketClient discord, ulong id) : base(discord, id) { if (Discord.MessageCacheSize > 0) _messages = new MessageCache(Discord, this); - _voiceStates = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, 5); + _voiceStates = + new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, 5); _users = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, 5); } + + public new IReadOnlyCollection Users => _users.ToReadOnlyCollection(); + + public IReadOnlyCollection Recipients + => _users.Select(x => x.Value).Where(x => x.Id != Discord.CurrentUser.Id) + .ToReadOnlyCollection(() => _users.Count - 1); + + private string DebuggerDisplay => $"{Name} ({Id}, Group)"; + + public string Name { get; private set; } + + public Task LeaveAsync(RequestOptions options = null) + => ChannelHelper.DeleteAsync(this, Discord, options); + + public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + + public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) + => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); + + public Task TriggerTypingAsync(RequestOptions options = null) + => ChannelHelper.TriggerTypingAsync(this, Discord, options); + + //IPrivateChannel + IReadOnlyCollection IPrivateChannel.Recipients => Recipients; + + //IMessageChannel + async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) + { + if (mode == CacheMode.AllowDownload) + return await GetMessageAsync(id, options).ConfigureAwait(false); + return GetCachedMessage(id); + } + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, + RequestOptions options) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, + options); + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, + Direction dir, int limit, CacheMode mode, RequestOptions options) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, + Direction dir, int limit, CacheMode mode, RequestOptions options) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, + options); + + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) + => await GetPinnedMessagesAsync(options).ConfigureAwait(false); + + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, + RequestOptions options) + => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); + + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, + Embed embed, RequestOptions options) + => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); + + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, + RequestOptions options) + => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); + + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) + => EnterTypingState(options); + + //IAudioChannel + Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) => + throw new NotSupportedException(); + + Task IAudioChannel.DisconnectAsync() => throw new NotSupportedException(); + + //IChannel + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetUser(id)); + + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + + public IReadOnlyCollection CachedMessages => + _messages?.Messages ?? ImmutableArray.Create(); + + //Messages + public SocketMessage GetCachedMessage(ulong id) + => _messages?.Get(id); + + public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); + + public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); + + public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch) + => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); + + public Task> GetPinnedMessagesAsync(RequestOptions options = null) + => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); + + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, + RequestOptions options = null) + => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); + + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, + RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); + + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, + Embed embed = null, RequestOptions options = null) + => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + + //ISocketPrivateChannel + IReadOnlyCollection ISocketPrivateChannel.Recipients => Recipients; + internal static SocketGroupChannel Create(DiscordSocketClient discord, ClientState state, Model model) { var entity = new SocketGroupChannel(discord, model.Id); entity.Update(state, model); return entity; } + internal override void Update(ClientState state, Model model) { if (model.Name.IsSpecified) @@ -54,25 +165,19 @@ namespace Discord.WebSocket if (model.Recipients.IsSpecified) UpdateUsers(state, model.Recipients.Value); } + private void UpdateUsers(ClientState state, UserModel[] models) { - var users = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(models.Length * 1.05)); - for (int i = 0; i < models.Length; i++) + var users = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, + (int)(models.Length * 1.05)); + for (var i = 0; i < models.Length; i++) users[models[i].Id] = SocketGroupUser.Create(this, state, models[i]); _users = users; } - public Task LeaveAsync(RequestOptions options = null) - => ChannelHelper.DeleteAsync(this, Discord, options); - - public Task ConnectAsync() - { + public Task ConnectAsync() => throw new NotSupportedException("Voice is not yet supported for group channels."); - } - //Messages - public SocketMessage GetCachedMessage(ulong id) - => _messages?.Get(id); public async Task GetMessageAsync(ulong id, RequestOptions options = null) { IMessage msg = _messages?.Get(id); @@ -80,72 +185,53 @@ namespace Discord.WebSocket msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); return msg; } - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); - public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); - public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); - public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) - => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); - public Task> GetPinnedMessagesAsync(RequestOptions options = null) - => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) - => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); + public IAsyncEnumerable> GetMessagesAsync( + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, + CacheMode.AllowDownload, options); - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, + CacheMode.AllowDownload, options); - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) - => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, + CacheMode.AllowDownload, options); - public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) - => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); - public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) - => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); - - public Task TriggerTypingAsync(RequestOptions options = null) - => ChannelHelper.TriggerTypingAsync(this, Discord, options); public IDisposable EnterTypingState(RequestOptions options = null) => ChannelHelper.EnterTypingState(this, Discord, options); internal void AddMessage(SocketMessage msg) => _messages?.Add(msg); + internal SocketMessage RemoveMessage(ulong id) => _messages?.Remove(id); //Users public new SocketGroupUser GetUser(ulong id) { - if (_users.TryGetValue(id, out SocketGroupUser user)) - return user; - return null; + return _users.TryGetValue(id, out var user) ? user : null; } + internal SocketGroupUser GetOrAddUser(UserModel model) { - if (_users.TryGetValue(model.Id, out SocketGroupUser user)) - return user as SocketGroupUser; - else - { - var privateUser = SocketGroupUser.Create(this, Discord.State, model); - privateUser.GlobalUser.AddRef(); - _users[privateUser.Id] = privateUser; - return privateUser; - } + if (_users.TryGetValue(model.Id, out var user)) + return user; + var privateUser = SocketGroupUser.Create(this, Discord.State, model); + privateUser.GlobalUser.AddRef(); + _users[privateUser.Id] = privateUser; + return privateUser; } + internal SocketGroupUser RemoveUser(ulong id) { - if (_users.TryRemove(id, out SocketGroupUser user)) - { - user.GlobalUser.RemoveRef(Discord); - return user as SocketGroupUser; - } - return null; + if (!_users.TryRemove(id, out var user)) return null; + user.GlobalUser.RemoveRef(Discord); + return user; + } //Voice States @@ -156,68 +242,26 @@ namespace Discord.WebSocket _voiceStates[model.UserId] = voiceState; return voiceState; } + internal SocketVoiceState? GetVoiceState(ulong id) { - if (_voiceStates.TryGetValue(id, out SocketVoiceState voiceState)) + if (_voiceStates.TryGetValue(id, out var voiceState)) return voiceState; return null; } + internal SocketVoiceState? RemoveVoiceState(ulong id) { - if (_voiceStates.TryRemove(id, out SocketVoiceState voiceState)) + if (_voiceStates.TryRemove(id, out var voiceState)) return voiceState; return null; } public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id}, Group)"; internal new SocketGroupChannel Clone() => MemberwiseClone() as SocketGroupChannel; //SocketChannel internal override IReadOnlyCollection GetUsersInternal() => Users; internal override SocketUser GetUserInternal(ulong id) => GetUser(id); - - //ISocketPrivateChannel - IReadOnlyCollection ISocketPrivateChannel.Recipients => Recipients; - - //IPrivateChannel - IReadOnlyCollection IPrivateChannel.Recipients => Recipients; - - //IMessageChannel - async Task IMessageChannel.GetMessageAsync(ulong id, CacheMode mode, RequestOptions options) - { - if (mode == CacheMode.AllowDownload) - return await GetMessageAsync(id, options).ConfigureAwait(false); - else - return GetCachedMessage(id); - } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); - async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) - => await GetPinnedMessagesAsync(options).ConfigureAwait(false); - - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) - => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); - - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) - => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) - => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); - IDisposable IMessageChannel.EnterTypingState(RequestOptions options) - => EnterTypingState(options); - - //IAudioChannel - Task IAudioChannel.ConnectAsync(bool selfDeaf, bool selfMute, bool external) { throw new NotSupportedException(); } - Task IAudioChannel.DisconnectAsync() { throw new NotSupportedException(); } - - //IChannel - Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetUser(id)); - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => ImmutableArray.Create>(Users).ToAsyncEnumerable(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs index bfcffa35f..ef7e7fa10 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGuildChannel.cs @@ -1,10 +1,10 @@ -using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using Discord.Rest; using Model = Discord.API.Channel; namespace Discord.WebSocket @@ -14,18 +14,70 @@ namespace Discord.WebSocket { private ImmutableArray _overwrites; - public SocketGuild Guild { get; } - public string Name { get; private set; } - public int Position { get; private set; } - - public IReadOnlyCollection PermissionOverwrites => _overwrites; - public new virtual IReadOnlyCollection Users => ImmutableArray.Create(); - internal SocketGuildChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) : base(discord, id) { Guild = guild; } + + public SocketGuild Guild { get; } + public new virtual IReadOnlyCollection Users => ImmutableArray.Create(); + public string Name { get; private set; } + public int Position { get; private set; } + + public IReadOnlyCollection PermissionOverwrites => _overwrites; + + public Task ModifyAsync(Action func, RequestOptions options = null) + => ChannelHelper.ModifyAsync(this, Discord, func, options); + + public Task DeleteAsync(RequestOptions options = null) + => ChannelHelper.DeleteAsync(this, Discord, options); + + //IGuildChannel + IGuild IGuildChannel.Guild => Guild; + ulong IGuildChannel.GuildId => Guild.Id; + + async Task> IGuildChannel.GetInvitesAsync(RequestOptions options) + => await GetInvitesAsync(options).ConfigureAwait(false); + + async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, + bool isUnique, RequestOptions options) + => await CreateInviteAsync(maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); + + OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) + => GetPermissionOverwrite(role); + + OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) + => GetPermissionOverwrite(user); + + async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, + RequestOptions options) + => await AddPermissionOverwriteAsync(role, permissions, options).ConfigureAwait(false); + + async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, + RequestOptions options) + => await AddPermissionOverwriteAsync(user, permissions, options).ConfigureAwait(false); + + async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options) + => await RemovePermissionOverwriteAsync(role, options).ConfigureAwait(false); + + async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) + => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); + + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, + RequestOptions options) + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetUser(id)); + + //IChannel + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); //Overridden in Text/Voice + + Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetUser(id)); //Overridden in Text/Voice + internal static SocketGuildChannel Create(SocketGuild guild, ClientState state, Model model) { switch (model.Type) @@ -41,82 +93,83 @@ namespace Discord.WebSocket return new SocketGuildChannel(guild.Discord, model.Id, guild); } } + internal override void Update(ClientState state, Model model) { Name = model.Name.Value; Position = model.Position.Value; - + var overwrites = model.PermissionOverwrites.Value; var newOverwrites = ImmutableArray.CreateBuilder(overwrites.Length); - for (int i = 0; i < overwrites.Length; i++) - newOverwrites.Add(overwrites[i].ToEntity()); + foreach (var t in overwrites) + newOverwrites.Add(t.ToEntity()); + _overwrites = newOverwrites.ToImmutable(); } - public Task ModifyAsync(Action func, RequestOptions options = null) - => ChannelHelper.ModifyAsync(this, Discord, func, options); - public Task DeleteAsync(RequestOptions options = null) - => ChannelHelper.DeleteAsync(this, Discord, options); - public OverwritePermissions? GetPermissionOverwrite(IUser user) { - for (int i = 0; i < _overwrites.Length; i++) - { + for (var i = 0; i < _overwrites.Length; i++) if (_overwrites[i].TargetId == user.Id) return _overwrites[i].Permissions; - } return null; } + public OverwritePermissions? GetPermissionOverwrite(IRole role) { - for (int i = 0; i < _overwrites.Length; i++) - { + for (var i = 0; i < _overwrites.Length; i++) if (_overwrites[i].TargetId == role.Id) return _overwrites[i].Permissions; - } return null; } - public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms, RequestOptions options = null) + + public async Task AddPermissionOverwriteAsync(IUser user, OverwritePermissions perms, + RequestOptions options = null) { await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, user, perms, options).ConfigureAwait(false); - _overwrites = _overwrites.Add(new Overwrite(user.Id, PermissionTarget.User, new OverwritePermissions(perms.AllowValue, perms.DenyValue))); + _overwrites = _overwrites.Add(new Overwrite(user.Id, PermissionTarget.User, + new OverwritePermissions(perms.AllowValue, perms.DenyValue))); } - public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms, RequestOptions options = null) + + public async Task AddPermissionOverwriteAsync(IRole role, OverwritePermissions perms, + RequestOptions options = null) { await ChannelHelper.AddPermissionOverwriteAsync(this, Discord, role, perms, options).ConfigureAwait(false); - _overwrites = _overwrites.Add(new Overwrite(role.Id, PermissionTarget.Role, new OverwritePermissions(perms.AllowValue, perms.DenyValue))); + _overwrites = _overwrites.Add(new Overwrite(role.Id, PermissionTarget.Role, + new OverwritePermissions(perms.AllowValue, perms.DenyValue))); } + public async Task RemovePermissionOverwriteAsync(IUser user, RequestOptions options = null) { await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, user, options).ConfigureAwait(false); - for (int i = 0; i < _overwrites.Length; i++) - { + for (var i = 0; i < _overwrites.Length; i++) if (_overwrites[i].TargetId == user.Id) { _overwrites = _overwrites.RemoveAt(i); return; } - } } + public async Task RemovePermissionOverwriteAsync(IRole role, RequestOptions options = null) { await ChannelHelper.RemovePermissionOverwriteAsync(this, Discord, role, options).ConfigureAwait(false); - for (int i = 0; i < _overwrites.Length; i++) - { + for (var i = 0; i < _overwrites.Length; i++) if (_overwrites[i].TargetId == role.Id) { _overwrites = _overwrites.RemoveAt(i); return; } - } } public async Task> GetInvitesAsync(RequestOptions options = null) => await ChannelHelper.GetInvitesAsync(this, Discord, options).ConfigureAwait(false); - public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, bool isTemporary = false, bool isUnique = false, RequestOptions options = null) - => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); + + public async Task CreateInviteAsync(int? maxAge = 86400, int? maxUses = null, + bool isTemporary = false, bool isUnique = false, RequestOptions options = null) + => await ChannelHelper.CreateInviteAsync(this, Discord, maxAge, maxUses, isTemporary, isUnique, options) + .ConfigureAwait(false); public new virtual SocketGuildUser GetUser(ulong id) => null; @@ -126,38 +179,5 @@ namespace Discord.WebSocket //SocketChannel internal override IReadOnlyCollection GetUsersInternal() => Users; internal override SocketUser GetUserInternal(ulong id) => GetUser(id); - - //IGuildChannel - IGuild IGuildChannel.Guild => Guild; - ulong IGuildChannel.GuildId => Guild.Id; - - async Task> IGuildChannel.GetInvitesAsync(RequestOptions options) - => await GetInvitesAsync(options).ConfigureAwait(false); - async Task IGuildChannel.CreateInviteAsync(int? maxAge, int? maxUses, bool isTemporary, bool isUnique, RequestOptions options) - => await CreateInviteAsync(maxAge, maxUses, isTemporary, isUnique, options).ConfigureAwait(false); - - OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IRole role) - => GetPermissionOverwrite(role); - OverwritePermissions? IGuildChannel.GetPermissionOverwrite(IUser user) - => GetPermissionOverwrite(user); - async Task IGuildChannel.AddPermissionOverwriteAsync(IRole role, OverwritePermissions permissions, RequestOptions options) - => await AddPermissionOverwriteAsync(role, permissions, options).ConfigureAwait(false); - async Task IGuildChannel.AddPermissionOverwriteAsync(IUser user, OverwritePermissions permissions, RequestOptions options) - => await AddPermissionOverwriteAsync(user, permissions, options).ConfigureAwait(false); - async Task IGuildChannel.RemovePermissionOverwriteAsync(IRole role, RequestOptions options) - => await RemovePermissionOverwriteAsync(role, options).ConfigureAwait(false); - async Task IGuildChannel.RemovePermissionOverwriteAsync(IUser user, RequestOptions options) - => await RemovePermissionOverwriteAsync(user, options).ConfigureAwait(false); - - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => ImmutableArray.Create>(Users).ToAsyncEnumerable(); - Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetUser(id)); - - //IChannel - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => ImmutableArray.Create>(Users).ToAsyncEnumerable(); //Overridden in Text/Voice - Task IChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetUser(id)); //Overridden in Text/Voice } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs index e41c2ba94..fa9632fd5 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketTextChannel.cs @@ -1,4 +1,3 @@ -using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; @@ -6,144 +5,106 @@ using System.Diagnostics; using System.IO; using System.Linq; using System.Threading.Tasks; +using Discord.Rest; using Model = Discord.API.Channel; namespace Discord.WebSocket { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class SocketTextChannel : SocketGuildChannel, ITextChannel, ISocketMessageChannel { private readonly MessageCache _messages; - public string Topic { get; private set; } - public ulong? CategoryId { get; private set; } + internal SocketTextChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) + : base(discord, id, guild) + { + if (Discord.MessageCacheSize > 0) + _messages = new MessageCache(Discord, this); + } + public ICategoryChannel Category => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; - private bool _nsfw; - public bool IsNsfw => _nsfw; - - public string Mention => MentionUtils.MentionChannel(Id); - public IReadOnlyCollection CachedMessages => _messages?.Messages ?? ImmutableArray.Create(); public override IReadOnlyCollection Users => Guild.Users.Where(x => Permissions.GetValue( Permissions.ResolveChannel(Guild, x, this, Permissions.ResolveGuild(Guild, x)), ChannelPermission.ViewChannel)).ToImmutableArray(); - internal SocketTextChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) - : base(discord, id, guild) - { - if (Discord.MessageCacheSize > 0) - _messages = new MessageCache(Discord, this); - } - internal new static SocketTextChannel Create(SocketGuild guild, ClientState state, Model model) - { - var entity = new SocketTextChannel(guild.Discord, model.Id, guild); - entity.Update(state, model); - return entity; - } - internal override void Update(ClientState state, Model model) - { - base.Update(state, model); - CategoryId = model.CategoryId; - Topic = model.Topic.Value; - _nsfw = model.Nsfw.GetValueOrDefault(); - } + private string DebuggerDisplay => $"{Name} ({Id}, Text)"; - public Task ModifyAsync(Action func, RequestOptions options = null) - => ChannelHelper.ModifyAsync(this, Discord, func, options); + public IReadOnlyCollection CachedMessages => + _messages?.Messages ?? ImmutableArray.Create(); //Messages public SocketMessage GetCachedMessage(ulong id) => _messages?.Get(id); - public async Task GetMessageAsync(ulong id, RequestOptions options = null) - { - IMessage msg = _messages?.Get(id); - if (msg == null) - msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); - return msg; - } - public IAsyncEnumerable> GetMessagesAsync(int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, CacheMode.AllowDownload, options); - public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, CacheMode.AllowDownload, options); - public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, CacheMode.AllowDownload, options); + public IReadOnlyCollection GetCachedMessages(int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, null, Direction.Before, limit); - public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + + public IReadOnlyCollection GetCachedMessages(ulong fromMessageId, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessageId, dir, limit); - public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + + public IReadOnlyCollection GetCachedMessages(IMessage fromMessage, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch) => SocketChannelHelper.GetCachedMessages(this, Discord, _messages, fromMessage.Id, dir, limit); + public Task> GetPinnedMessagesAsync(RequestOptions options = null) => ChannelHelper.GetPinnedMessagesAsync(this, Discord, options); - public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendMessageAsync(string text = null, bool isTTS = false, Embed embed = null, + RequestOptions options = null) => ChannelHelper.SendMessageAsync(this, Discord, text, isTTS, embed, options); - public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendFileAsync(string filePath, string text, bool isTTS = false, Embed embed = null, + RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, filePath, text, isTTS, embed, options); - public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, Embed embed = null, RequestOptions options = null) + public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, + Embed embed = null, RequestOptions options = null) => ChannelHelper.SendFileAsync(this, Discord, stream, filename, text, isTTS, embed, options); + public string Topic { get; private set; } + public ulong? CategoryId { get; private set; } + public bool IsNsfw { get; private set; } + + public string Mention => MentionUtils.MentionChannel(Id); + + public Task ModifyAsync(Action func, RequestOptions options = null) + => ChannelHelper.ModifyAsync(this, Discord, func, options); + public Task DeleteMessagesAsync(IEnumerable messages, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messages.Select(x => x.Id), options); + public Task DeleteMessagesAsync(IEnumerable messageIds, RequestOptions options = null) => ChannelHelper.DeleteMessagesAsync(this, Discord, messageIds, options); public Task DeleteMessageAsync(ulong messageId, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, messageId, Discord, options); + public Task DeleteMessageAsync(IMessage message, RequestOptions options = null) => ChannelHelper.DeleteMessageAsync(this, message.Id, Discord, options); public Task TriggerTypingAsync(RequestOptions options = null) => ChannelHelper.TriggerTypingAsync(this, Discord, options); - public IDisposable EnterTypingState(RequestOptions options = null) - => ChannelHelper.EnterTypingState(this, Discord, options); - - internal void AddMessage(SocketMessage msg) - => _messages?.Add(msg); - internal SocketMessage RemoveMessage(ulong id) - => _messages?.Remove(id); - - //Users - public override SocketGuildUser GetUser(ulong id) - { - var user = Guild.GetUser(id); - if (user != null) - { - var guildPerms = Permissions.ResolveGuild(Guild, user); - var channelPerms = Permissions.ResolveChannel(Guild, user, this, guildPerms); - if (Permissions.GetValue(channelPerms, ChannelPermission.ViewChannel)) - return user; - } - return null; - } - - //Webhooks - public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) - => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options); - public Task GetWebhookAsync(ulong id, RequestOptions options = null) - => ChannelHelper.GetWebhookAsync(this, Discord, id, options); - public Task> GetWebhooksAsync(RequestOptions options = null) - => ChannelHelper.GetWebhooksAsync(this, Discord, options); - - private string DebuggerDisplay => $"{Name} ({Id}, Text)"; - internal new SocketTextChannel Clone() => MemberwiseClone() as SocketTextChannel; //ITextChannel async Task ITextChannel.CreateWebhookAsync(string name, Stream avatar, RequestOptions options) => await CreateWebhookAsync(name, avatar, options); + async Task ITextChannel.GetWebhookAsync(ulong id, RequestOptions options) => await GetWebhookAsync(id, options); + async Task> ITextChannel.GetWebhooksAsync(RequestOptions options) => await GetWebhooksAsync(options); //IGuildChannel Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) => Task.FromResult(GetUser(id)); - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, + RequestOptions options) => ImmutableArray.Create>(Users).ToAsyncEnumerable(); //IMessageChannel @@ -151,30 +112,115 @@ namespace Discord.WebSocket { if (mode == CacheMode.AllowDownload) return await GetMessageAsync(id, options).ConfigureAwait(false); - else - return GetCachedMessage(id); + return GetCachedMessage(id); } - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, options); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, Direction dir, int limit, CacheMode mode, RequestOptions options) + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, + RequestOptions options) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, mode, + options); + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, + Direction dir, int limit, CacheMode mode, RequestOptions options) => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, mode, options); - IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, Direction dir, int limit, CacheMode mode, RequestOptions options) - => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, options); + + IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, + Direction dir, int limit, CacheMode mode, RequestOptions options) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, mode, + options); + async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); - async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, RequestOptions options) + async Task IMessageChannel.SendFileAsync(string filePath, string text, bool isTTS, Embed embed, + RequestOptions options) => await SendFileAsync(filePath, text, isTTS, embed, options).ConfigureAwait(false); - async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, Embed embed, RequestOptions options) + async Task IMessageChannel.SendFileAsync(Stream stream, string filename, string text, bool isTTS, + Embed embed, RequestOptions options) => await SendFileAsync(stream, filename, text, isTTS, embed, options).ConfigureAwait(false); - async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, RequestOptions options) + + async Task IMessageChannel.SendMessageAsync(string text, bool isTTS, Embed embed, + RequestOptions options) => await SendMessageAsync(text, isTTS, embed, options).ConfigureAwait(false); + IDisposable IMessageChannel.EnterTypingState(RequestOptions options) => EnterTypingState(options); // INestedChannel Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) => Task.FromResult(Category); + + internal new static SocketTextChannel Create(SocketGuild guild, ClientState state, Model model) + { + var entity = new SocketTextChannel(guild.Discord, model.Id, guild); + entity.Update(state, model); + return entity; + } + + internal override void Update(ClientState state, Model model) + { + base.Update(state, model); + CategoryId = model.CategoryId; + Topic = model.Topic.Value; + IsNsfw = model.Nsfw.GetValueOrDefault(); + } + + public async Task GetMessageAsync(ulong id, RequestOptions options = null) + { + IMessage msg = _messages?.Get(id); + if (msg == null) + msg = await ChannelHelper.GetMessageAsync(this, Discord, id, options).ConfigureAwait(false); + return msg; + } + + public IAsyncEnumerable> GetMessagesAsync( + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, null, Direction.Before, limit, + CacheMode.AllowDownload, options); + + public IAsyncEnumerable> GetMessagesAsync(ulong fromMessageId, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessageId, dir, limit, + CacheMode.AllowDownload, options); + + public IAsyncEnumerable> GetMessagesAsync(IMessage fromMessage, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch, RequestOptions options = null) + => SocketChannelHelper.GetMessagesAsync(this, Discord, _messages, fromMessage.Id, dir, limit, + CacheMode.AllowDownload, options); + + public IDisposable EnterTypingState(RequestOptions options = null) + => ChannelHelper.EnterTypingState(this, Discord, options); + + internal void AddMessage(SocketMessage msg) + => _messages?.Add(msg); + + internal SocketMessage RemoveMessage(ulong id) + => _messages?.Remove(id); + + //Users + public override SocketGuildUser GetUser(ulong id) + { + var user = Guild.GetUser(id); + if (user == null) return null; + var guildPerms = Permissions.ResolveGuild(Guild, user); + var channelPerms = Permissions.ResolveChannel(Guild, user, this, guildPerms); + if (Permissions.GetValue(channelPerms, ChannelPermission.ViewChannel)) + return user; + + return null; + } + + //Webhooks + public Task CreateWebhookAsync(string name, Stream avatar = null, RequestOptions options = null) + => ChannelHelper.CreateWebhookAsync(this, Discord, name, avatar, options); + + public Task GetWebhookAsync(ulong id, RequestOptions options = null) + => ChannelHelper.GetWebhookAsync(this, Discord, id, options); + + public Task> GetWebhooksAsync(RequestOptions options = null) + => ChannelHelper.GetWebhooksAsync(this, Discord, options); + + internal new SocketTextChannel Clone() => MemberwiseClone() as SocketTextChannel; } } diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs index f967e6e6a..a8747695e 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketVoiceChannel.cs @@ -1,37 +1,62 @@ -using Discord.Audio; -using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using Discord.Audio; +using Discord.Rest; using Model = Discord.API.Channel; namespace Discord.WebSocket { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class SocketVoiceChannel : SocketGuildChannel, IVoiceChannel, ISocketAudioChannel { - public int Bitrate { get; private set; } - public int? UserLimit { get; private set; } - public ulong? CategoryId { get; private set; } + internal SocketVoiceChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) + : base(discord, id, guild) + { + } + public ICategoryChannel Category => CategoryId.HasValue ? Guild.GetChannel(CategoryId.Value) as ICategoryChannel : null; public override IReadOnlyCollection Users => Guild.Users.Where(x => x.VoiceChannel?.Id == Id).ToImmutableArray(); - internal SocketVoiceChannel(DiscordSocketClient discord, ulong id, SocketGuild guild) - : base(discord, id, guild) - { - } + private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; + public int Bitrate { get; private set; } + public int? UserLimit { get; private set; } + public ulong? CategoryId { get; private set; } + + public Task ModifyAsync(Action func, RequestOptions options = null) + => ChannelHelper.ModifyAsync(this, Discord, func, options); + + public async Task ConnectAsync(bool selfDeaf, bool selfMute, bool external) => + await Guild.ConnectAudioAsync(Id, selfDeaf, selfMute, external).ConfigureAwait(false); + + public async Task DisconnectAsync() + => await Guild.DisconnectAudioAsync(); + + //IGuildChannel + Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetUser(id)); + + IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, + RequestOptions options) + => ImmutableArray.Create>(Users).ToAsyncEnumerable(); + + // INestedChannel + Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) + => Task.FromResult(Category); + internal new static SocketVoiceChannel Create(SocketGuild guild, ClientState state, Model model) { var entity = new SocketVoiceChannel(guild.Discord, model.Id, guild); entity.Update(state, model); return entity; } + internal override void Update(ClientState state, Model model) { base.Update(state, model); @@ -40,36 +65,12 @@ namespace Discord.WebSocket UserLimit = model.UserLimit.Value != 0 ? model.UserLimit.Value : (int?)null; } - public Task ModifyAsync(Action func, RequestOptions options = null) - => ChannelHelper.ModifyAsync(this, Discord, func, options); - - public async Task ConnectAsync(bool selfDeaf, bool selfMute, bool external) - { - return await Guild.ConnectAudioAsync(Id, selfDeaf, selfMute, external).ConfigureAwait(false); - } - - public async Task DisconnectAsync() - => await Guild.DisconnectAudioAsync(); - public override SocketGuildUser GetUser(ulong id) { var user = Guild.GetUser(id); - if (user?.VoiceChannel?.Id == Id) - return user; - return null; + return user?.VoiceChannel?.Id == Id ? user : null; } - private string DebuggerDisplay => $"{Name} ({Id}, Voice)"; internal new SocketVoiceChannel Clone() => MemberwiseClone() as SocketVoiceChannel; - - //IGuildChannel - Task IGuildChannel.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetUser(id)); - IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - => ImmutableArray.Create>(Users).ToAsyncEnumerable(); - - // INestedChannel - Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) - => Task.FromResult(Category); } } diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index bf33ef569..8c3fb3944 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -1,13 +1,12 @@ -using Discord.Audio; -using Discord.Rest; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Discord.Audio; +using Discord.Rest; using ChannelModel = Discord.API.Channel; using EmojiUpdateModel = Discord.API.Gateway.GuildEmojiUpdateEvent; using ExtendedModel = Discord.API.Gateway.ExtendedGuild; @@ -24,22 +23,24 @@ namespace Discord.WebSocket public class SocketGuild : SocketEntity, IGuild { private readonly SemaphoreSlim _audioLock; - private TaskCompletionSource _syncPromise, _downloaderPromise; + private AudioClient _audioClient; private TaskCompletionSource _audioConnectPromise; private ConcurrentHashSet _channels; + private ImmutableArray _emotes; + private ImmutableArray _features; private ConcurrentDictionary _members; private ConcurrentDictionary _roles; + private TaskCompletionSource _syncPromise, _downloaderPromise; private ConcurrentDictionary _voiceStates; - private ImmutableArray _emotes; - private ImmutableArray _features; - private AudioClient _audioClient; - public string Name { get; private set; } - public int AFKTimeout { get; private set; } - public bool IsEmbeddable { get; private set; } - public VerificationLevel VerificationLevel { get; private set; } - public MfaLevel MfaLevel { get; private set; } - public DefaultMessageNotifications DefaultMessageNotifications { get; private set; } + internal SocketGuild(DiscordSocketClient client, ulong id) + : base(client, id) + { + _audioLock = new SemaphoreSlim(1, 1); + _emotes = ImmutableArray.Create(); + _features = ImmutableArray.Create(); + } + public int MemberCount { get; internal set; } public int DownloadedMemberCount { get; private set; } internal bool IsAvailable { get; private set; } @@ -48,24 +49,18 @@ namespace Discord.WebSocket internal ulong? AFKChannelId { get; private set; } internal ulong? EmbedChannelId { get; private set; } internal ulong? SystemChannelId { get; private set; } - public ulong OwnerId { get; private set; } public SocketGuildUser Owner => GetUser(OwnerId); - public string VoiceRegionId { get; private set; } - public string IconId { get; private set; } - public string SplashId { get; private set; } - - public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - public string IconUrl => CDN.GetGuildIconUrl(Id, IconId); - public string SplashUrl => CDN.GetGuildSplashUrl(Id, SplashId); - public bool HasAllMembers => MemberCount == DownloadedMemberCount;// _downloaderPromise.Task.IsCompleted; + public bool HasAllMembers => MemberCount == DownloadedMemberCount; // _downloaderPromise.Task.IsCompleted; public bool IsSynced => _syncPromise.Task.IsCompleted; public Task SyncPromise => _syncPromise.Task; public Task DownloaderPromise => _downloaderPromise.Task; public IAudioClient AudioClient => _audioClient; + public SocketTextChannel DefaultChannel => TextChannels .Where(c => CurrentUser.GetPermissions(c).ViewChannel) .OrderBy(c => c.Position) .FirstOrDefault(); + public SocketVoiceChannel AFKChannel { get @@ -74,6 +69,7 @@ namespace Discord.WebSocket return id.HasValue ? GetVoiceChannel(id.Value) : null; } } + public SocketGuildChannel EmbedChannel { get @@ -82,6 +78,7 @@ namespace Discord.WebSocket return id.HasValue ? GetChannel(id.Value) : null; } } + public SocketTextChannel SystemChannel { get @@ -90,41 +87,223 @@ namespace Discord.WebSocket return id.HasValue ? GetTextChannel(id.Value) : null; } } + public IReadOnlyCollection TextChannels => Channels.Select(x => x as SocketTextChannel).Where(x => x != null).ToImmutableArray(); + public IReadOnlyCollection VoiceChannels => Channels.Select(x => x as SocketVoiceChannel).Where(x => x != null).ToImmutableArray(); + public IReadOnlyCollection CategoryChannels => Channels.Select(x => x as SocketCategoryChannel).Where(x => x != null).ToImmutableArray(); - public SocketGuildUser CurrentUser => _members.TryGetValue(Discord.CurrentUser.Id, out SocketGuildUser member) ? member : null; + + public SocketGuildUser CurrentUser => + _members.TryGetValue(Discord.CurrentUser.Id, out var member) ? member : null; + public SocketRole EveryoneRole => GetRole(Id); + public IReadOnlyCollection Channels { get { var channels = _channels; var state = Discord.State; - return channels.Select(x => state.GetChannel(x) as SocketGuildChannel).Where(x => x != null).ToReadOnlyCollection(channels); + return channels.Select(x => state.GetChannel(x) as SocketGuildChannel).Where(x => x != null) + .ToReadOnlyCollection(channels); } } - public IReadOnlyCollection Emotes => _emotes; - public IReadOnlyCollection Features => _features; + public IReadOnlyCollection Users => _members.ToReadOnlyCollection(); public IReadOnlyCollection Roles => _roles.ToReadOnlyCollection(); + private string DebuggerDisplay => $"{Name} ({Id})"; - internal SocketGuild(DiscordSocketClient client, ulong id) - : base(client, id) + public string Name { get; private set; } + public int AFKTimeout { get; private set; } + public bool IsEmbeddable { get; private set; } + public VerificationLevel VerificationLevel { get; private set; } + public MfaLevel MfaLevel { get; private set; } + public DefaultMessageNotifications DefaultMessageNotifications { get; private set; } + public ulong OwnerId { get; private set; } + public string VoiceRegionId { get; private set; } + public string IconId { get; private set; } + public string SplashId { get; private set; } + + public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); + public string IconUrl => CDN.GetGuildIconUrl(Id, IconId); + public string SplashUrl => CDN.GetGuildSplashUrl(Id, SplashId); + public IReadOnlyCollection Emotes => _emotes; + public IReadOnlyCollection Features => _features; + + //General + public Task DeleteAsync(RequestOptions options = null) + => GuildHelper.DeleteAsync(this, Discord, options); + + public Task ModifyAsync(Action func, RequestOptions options = null) + => GuildHelper.ModifyAsync(this, Discord, func, options); + + public Task ModifyEmbedAsync(Action func, RequestOptions options = null) + => GuildHelper.ModifyEmbedAsync(this, Discord, func, options); + + public Task ReorderChannelsAsync(IEnumerable args, RequestOptions options = null) + => GuildHelper.ReorderChannelsAsync(this, Discord, args, options); + + public Task ReorderRolesAsync(IEnumerable args, RequestOptions options = null) + => GuildHelper.ReorderRolesAsync(this, Discord, args, options); + + public Task LeaveAsync(RequestOptions options = null) + => GuildHelper.LeaveAsync(this, Discord, options); + + public Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) + => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, reason, options); + + public Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null) + => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, reason, options); + + public Task RemoveBanAsync(IUser user, RequestOptions options = null) + => GuildHelper.RemoveBanAsync(this, Discord, user.Id, options); + + public Task RemoveBanAsync(ulong userId, RequestOptions options = null) + => GuildHelper.RemoveBanAsync(this, Discord, userId, options); + + public Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) + => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); + + public async Task DownloadUsersAsync() => await Discord.DownloadUsersAsync(new[] {this}).ConfigureAwait(false); + + //Emotes + public Task GetEmoteAsync(ulong id, RequestOptions options = null) + => GuildHelper.GetEmoteAsync(this, Discord, id, options); + + public Task CreateEmoteAsync(string name, Image image, + Optional> roles = default(Optional>), RequestOptions options = null) + => GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); + + public Task ModifyEmoteAsync(GuildEmote emote, Action func, + RequestOptions options = null) + => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); + + public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null) + => GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options); + + //IGuild + ulong? IGuild.AFKChannelId => AFKChannelId; + IAudioClient IGuild.AudioClient => null; + bool IGuild.Available => true; + ulong IGuild.DefaultChannelId => DefaultChannel?.Id ?? 0; + ulong? IGuild.EmbedChannelId => EmbedChannelId; + ulong? IGuild.SystemChannelId => SystemChannelId; + IRole IGuild.EveryoneRole => EveryoneRole; + IReadOnlyCollection IGuild.Roles => Roles; + + async Task> IGuild.GetBansAsync(RequestOptions options) + => await GetBansAsync(options).ConfigureAwait(false); + + /// + async Task IGuild.GetBanAsync(IUser user, RequestOptions options) + => await GetBanAsync(user, options).ConfigureAwait(false); + + /// + async Task IGuild.GetBanAsync(ulong userId, RequestOptions options) + => await GetBanAsync(userId, options).ConfigureAwait(false); + + Task> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) + => Task.FromResult>(Channels); + + Task IGuild.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetChannel(id)); + + Task> IGuild.GetTextChannelsAsync(CacheMode mode, RequestOptions options) + => Task.FromResult>(TextChannels); + + Task IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetTextChannel(id)); + + Task> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options) + => Task.FromResult>(VoiceChannels); + + Task> IGuild.GetCategoriesAsync(CacheMode mode, RequestOptions options) + => Task.FromResult>(CategoryChannels); + + Task IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetVoiceChannel(id)); + + Task IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options) + => Task.FromResult(AFKChannel); + + Task IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options) + => Task.FromResult(DefaultChannel); + + Task IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) + => Task.FromResult(EmbedChannel); + + Task IGuild.GetSystemChannelAsync(CacheMode mode, RequestOptions options) + => Task.FromResult(SystemChannel); + + async Task IGuild.CreateTextChannelAsync(string name, Action func, + RequestOptions options) + => await CreateTextChannelAsync(name, func, options).ConfigureAwait(false); + + async Task IGuild.CreateVoiceChannelAsync(string name, Action func, + RequestOptions options) + => await CreateVoiceChannelAsync(name, func, options).ConfigureAwait(false); + + async Task IGuild.CreateCategoryAsync(string name, RequestOptions options) + => await CreateCategoryChannelAsync(name, options).ConfigureAwait(false); + + async Task> IGuild.GetIntegrationsAsync(RequestOptions options) + => await GetIntegrationsAsync(options).ConfigureAwait(false); + + async Task IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options) + => await CreateIntegrationAsync(id, type, options).ConfigureAwait(false); + + async Task> IGuild.GetInvitesAsync(RequestOptions options) + => await GetInvitesAsync(options).ConfigureAwait(false); + + /// + async Task IGuild.GetVanityInviteAsync(RequestOptions options) + => await GetVanityInviteAsync(options).ConfigureAwait(false); + + IRole IGuild.GetRole(ulong id) + => GetRole(id); + + async Task IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, + bool isHoisted, RequestOptions options) + => await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false); + + Task> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) + => Task.FromResult>(Users); + + Task IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) + => Task.FromResult(GetUser(id)); + + Task IGuild.GetCurrentUserAsync(CacheMode mode, RequestOptions options) + => Task.FromResult(CurrentUser); + + Task IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) + => Task.FromResult(Owner); + + async Task> IGuild.GetAuditLogsAsync(int limit, CacheMode cacheMode, + RequestOptions options) { - _audioLock = new SemaphoreSlim(1, 1); - _emotes = ImmutableArray.Create(); - _features = ImmutableArray.Create(); + if (cacheMode == CacheMode.AllowDownload) + return (await GetAuditLogsAsync(limit, options).FlattenAsync().ConfigureAwait(false)) + .ToImmutableArray(); + return ImmutableArray.Create(); } + + async Task IGuild.GetWebhookAsync(ulong id, RequestOptions options) + => await GetWebhookAsync(id, options); + + async Task> IGuild.GetWebhooksAsync(RequestOptions options) + => await GetWebhooksAsync(options); + internal static SocketGuild Create(DiscordSocketClient discord, ClientState state, ExtendedModel model) { var entity = new SocketGuild(discord, model.Id); entity.Update(state, model); return entity; } + internal void Update(ClientState state, ExtendedModel model) { IsAvailable = !(model.Unavailable ?? false); @@ -147,44 +326,47 @@ namespace Discord.WebSocket Update(state, model as Model); - var channels = new ConcurrentHashSet(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Channels.Length * 1.05)); + var channels = new ConcurrentHashSet(ConcurrentHashSet.DefaultConcurrencyLevel, + (int)(model.Channels.Length * 1.05)); { - for (int i = 0; i < model.Channels.Length; i++) + foreach (var t in model.Channels) { - var channel = SocketGuildChannel.Create(this, state, model.Channels[i]); + var channel = SocketGuildChannel.Create(this, state, t); state.AddChannel(channel); channels.TryAdd(channel.Id); } } _channels = channels; - var members = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05)); + var members = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, + (int)(model.Members.Length * 1.05)); { - for (int i = 0; i < model.Members.Length; i++) + foreach (var t in model.Members) { - var member = SocketGuildUser.Create(this, state, model.Members[i]); + var member = SocketGuildUser.Create(this, state, t); members.TryAdd(member.Id, member); } + DownloadedMemberCount = members.Count; - for (int i = 0; i < model.Presences.Length; i++) - { - if (members.TryGetValue(model.Presences[i].User.Id, out SocketGuildUser member)) - member.Update(state, model.Presences[i], true); - } + foreach (var t in model.Presences) + if (members.TryGetValue(t.User.Id, out var member)) + member.Update(state, t, true); } _members = members; MemberCount = model.MemberCount; - var voiceStates = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.VoiceStates.Length * 1.05)); + var voiceStates = + new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, + (int)(model.VoiceStates.Length * 1.05)); { - for (int i = 0; i < model.VoiceStates.Length; i++) + foreach (var t in model.VoiceStates) { SocketVoiceChannel channel = null; - if (model.VoiceStates[i].ChannelId.HasValue) - channel = state.GetChannel(model.VoiceStates[i].ChannelId.Value) as SocketVoiceChannel; - var voiceState = SocketVoiceState.Create(channel, model.VoiceStates[i]); - voiceStates.TryAdd(model.VoiceStates[i].UserId, voiceState); + if (t.ChannelId.HasValue) + channel = state.GetChannel(t.ChannelId.Value) as SocketVoiceChannel; + var voiceState = SocketVoiceState.Create(channel, t); + voiceStates.TryAdd(t.UserId, voiceState); } } _voiceStates = voiceStates; @@ -195,6 +377,7 @@ namespace Discord.WebSocket /*if (!model.Large) _ = _downloaderPromise.TrySetResultAsync(true);*/ } + internal void Update(ClientState state, Model model) { AFKChannelId = model.AFKChannelId; @@ -214,45 +397,44 @@ namespace Discord.WebSocket if (model.Emojis != null) { var emojis = ImmutableArray.CreateBuilder(model.Emojis.Length); - for (int i = 0; i < model.Emojis.Length; i++) - emojis.Add(model.Emojis[i].ToEntity()); + foreach (var t in model.Emojis) + emojis.Add(t.ToEntity()); + _emotes = emojis.ToImmutable(); } else _emotes = ImmutableArray.Create(); - if (model.Features != null) - _features = model.Features.ToImmutableArray(); - else - _features = ImmutableArray.Create(); + _features = model.Features?.ToImmutableArray() ?? ImmutableArray.Create(); - var roles = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Roles.Length * 1.05)); + var roles = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, + (int)(model.Roles.Length * 1.05)); if (model.Roles != null) - { - for (int i = 0; i < model.Roles.Length; i++) + foreach (var t in model.Roles) { - var role = SocketRole.Create(this, state, model.Roles[i]); + var role = SocketRole.Create(this, state, t); roles.TryAdd(role.Id, role); } - } + _roles = roles; } + internal void Update(ClientState state, GuildSyncModel model) { - var members = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(model.Members.Length * 1.05)); + var members = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, + (int)(model.Members.Length * 1.05)); { - for (int i = 0; i < model.Members.Length; i++) + foreach (var t in model.Members) { - var member = SocketGuildUser.Create(this, state, model.Members[i]); + var member = SocketGuildUser.Create(this, state, t); members.TryAdd(member.Id, member); } + DownloadedMemberCount = members.Count; - for (int i = 0; i < model.Presences.Length; i++) - { - if (members.TryGetValue(model.Presences[i].User.Id, out SocketGuildUser member)) - member.Update(state, model.Presences[i], true); - } + foreach (var t in model.Presences) + if (members.TryGetValue(t.User.Id, out var member)) + member.Update(state, t, true); } _members = members; @@ -264,61 +446,43 @@ namespace Discord.WebSocket internal void Update(ClientState state, EmojiUpdateModel model) { var emotes = ImmutableArray.CreateBuilder(model.Emojis.Length); - for (int i = 0; i < model.Emojis.Length; i++) - emotes.Add(model.Emojis[i].ToEntity()); + foreach (var t in model.Emojis) + emotes.Add(t.ToEntity()); + _emotes = emotes.ToImmutable(); } - //General - public Task DeleteAsync(RequestOptions options = null) - => GuildHelper.DeleteAsync(this, Discord, options); - - public Task ModifyAsync(Action func, RequestOptions options = null) - => GuildHelper.ModifyAsync(this, Discord, func, options); - public Task ModifyEmbedAsync(Action func, RequestOptions options = null) - => GuildHelper.ModifyEmbedAsync(this, Discord, func, options); - public Task ReorderChannelsAsync(IEnumerable args, RequestOptions options = null) - => GuildHelper.ReorderChannelsAsync(this, Discord, args, options); - public Task ReorderRolesAsync(IEnumerable args, RequestOptions options = null) - => GuildHelper.ReorderRolesAsync(this, Discord, args, options); - - public Task LeaveAsync(RequestOptions options = null) - => GuildHelper.LeaveAsync(this, Discord, options); - //Bans public Task> GetBansAsync(RequestOptions options = null) => GuildHelper.GetBansAsync(this, Discord, options); + public Task GetBanAsync(IUser user, RequestOptions options = null) => GuildHelper.GetBanAsync(this, Discord, user.Id, options); + public Task GetBanAsync(ulong userId, RequestOptions options = null) => GuildHelper.GetBanAsync(this, Discord, userId, options); - public Task AddBanAsync(IUser user, int pruneDays = 0, string reason = null, RequestOptions options = null) - => GuildHelper.AddBanAsync(this, Discord, user.Id, pruneDays, reason, options); - public Task AddBanAsync(ulong userId, int pruneDays = 0, string reason = null, RequestOptions options = null) - => GuildHelper.AddBanAsync(this, Discord, userId, pruneDays, reason, options); - - public Task RemoveBanAsync(IUser user, RequestOptions options = null) - => GuildHelper.RemoveBanAsync(this, Discord, user.Id, options); - public Task RemoveBanAsync(ulong userId, RequestOptions options = null) - => GuildHelper.RemoveBanAsync(this, Discord, userId, options); - //Channels public SocketGuildChannel GetChannel(ulong id) { var channel = Discord.State.GetChannel(id) as SocketGuildChannel; - if (channel?.Guild.Id == Id) - return channel; - return null; + return channel?.Guild.Id == Id ? channel : null; } + public SocketTextChannel GetTextChannel(ulong id) => GetChannel(id) as SocketTextChannel; + public SocketVoiceChannel GetVoiceChannel(ulong id) => GetChannel(id) as SocketVoiceChannel; - public Task CreateTextChannelAsync(string name, Action func = null, RequestOptions options = null) + + public Task CreateTextChannelAsync(string name, Action func = null, + RequestOptions options = null) => GuildHelper.CreateTextChannelAsync(this, Discord, name, options, func); - public Task CreateVoiceChannelAsync(string name, Action func = null, RequestOptions options = null) + + public Task CreateVoiceChannelAsync(string name, Action func = null, + RequestOptions options = null) => GuildHelper.CreateVoiceChannelAsync(this, Discord, name, options, func); + public Task CreateCategoryChannelAsync(string name, RequestOptions options = null) => GuildHelper.CreateCategoryChannelAsync(this, Discord, name, options); @@ -329,6 +493,7 @@ namespace Discord.WebSocket state.AddChannel(channel); return channel; } + internal SocketGuildChannel RemoveChannel(ClientState state, ulong id) { if (_channels.TryRemove(id)) @@ -339,12 +504,14 @@ namespace Discord.WebSocket //Integrations public Task> GetIntegrationsAsync(RequestOptions options = null) => GuildHelper.GetIntegrationsAsync(this, Discord, options); + public Task CreateIntegrationAsync(ulong id, string type, RequestOptions options = null) => GuildHelper.CreateIntegrationAsync(this, Discord, id, type, options); //Invites public Task> GetInvitesAsync(RequestOptions options = null) => GuildHelper.GetInvitesAsync(this, Discord, options); + /// /// Gets the vanity invite URL of this guild. /// @@ -358,39 +525,35 @@ namespace Discord.WebSocket //Roles public SocketRole GetRole(ulong id) { - if (_roles.TryGetValue(id, out SocketRole value)) - return value; - return null; + return _roles.TryGetValue(id, out var value) ? value : null; } - public Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), + + public Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), + Color? color = default(Color?), bool isHoisted = false, RequestOptions options = null) => GuildHelper.CreateRoleAsync(this, Discord, name, permissions, color, isHoisted, options); + internal SocketRole AddRole(RoleModel model) { var role = SocketRole.Create(this, Discord.State, model); _roles[model.Id] = role; return role; } + internal SocketRole RemoveRole(ulong id) { - if (_roles.TryRemove(id, out SocketRole role)) - return role; - return null; + return _roles.TryRemove(id, out var role) ? role : null; } //Users public SocketGuildUser GetUser(ulong id) { - if (_members.TryGetValue(id, out SocketGuildUser member)) - return member; - return null; + return _members.TryGetValue(id, out var member) ? member : null; } - public Task PruneUsersAsync(int days = 30, bool simulate = false, RequestOptions options = null) - => GuildHelper.PruneUsersAsync(this, Discord, days, simulate, options); internal SocketGuildUser AddOrUpdateUser(UserModel model) { - if (_members.TryGetValue(model.Id, out SocketGuildUser member)) + if (_members.TryGetValue(model.Id, out var member)) member.GlobalUser?.Update(Discord.State, model); else { @@ -399,11 +562,13 @@ namespace Discord.WebSocket _members[member.Id] = member; DownloadedMemberCount++; } + return member; } + internal SocketGuildUser AddOrUpdateUser(MemberModel model) { - if (_members.TryGetValue(model.User.Id, out SocketGuildUser member)) + if (_members.TryGetValue(model.User.Id, out var member)) member.Update(Discord.State, model); else { @@ -412,11 +577,13 @@ namespace Discord.WebSocket _members[member.Id] = member; DownloadedMemberCount++; } + return member; } + internal SocketGuildUser AddOrUpdateUser(PresenceModel model) { - if (_members.TryGetValue(model.User.Id, out SocketGuildUser member)) + if (_members.TryGetValue(model.User.Id, out var member)) member.Update(Discord.State, model, false); else { @@ -425,48 +592,33 @@ namespace Discord.WebSocket _members[member.Id] = member; DownloadedMemberCount++; } + return member; } + internal SocketGuildUser RemoveUser(ulong id) { - if (_members.TryRemove(id, out SocketGuildUser member)) - { - DownloadedMemberCount--; - member.GlobalUser.RemoveRef(Discord); - return member; - } - return null; - } + if (!_members.TryRemove(id, out var member)) return null; + DownloadedMemberCount--; + member.GlobalUser.RemoveRef(Discord); + return member; - public async Task DownloadUsersAsync() - { - await Discord.DownloadUsersAsync(new[] { this }).ConfigureAwait(false); - } - internal void CompleteDownloadUsers() - { - _downloaderPromise.TrySetResultAsync(true); } + internal void CompleteDownloadUsers() => _downloaderPromise.TrySetResultAsync(true); + //Audit logs - public IAsyncEnumerable> GetAuditLogsAsync(int limit, RequestOptions options = null) + public IAsyncEnumerable> GetAuditLogsAsync(int limit, + RequestOptions options = null) => GuildHelper.GetAuditLogsAsync(this, Discord, null, limit, options); //Webhooks public Task GetWebhookAsync(ulong id, RequestOptions options = null) => GuildHelper.GetWebhookAsync(this, Discord, id, options); + public Task> GetWebhooksAsync(RequestOptions options = null) => GuildHelper.GetWebhooksAsync(this, Discord, options); - //Emotes - public Task GetEmoteAsync(ulong id, RequestOptions options = null) - => GuildHelper.GetEmoteAsync(this, Discord, id, options); - public Task CreateEmoteAsync(string name, Image image, Optional> roles = default(Optional>), RequestOptions options = null) - => GuildHelper.CreateEmoteAsync(this, Discord, name, image, roles, options); - public Task ModifyEmoteAsync(GuildEmote emote, Action func, RequestOptions options = null) - => GuildHelper.ModifyEmoteAsync(this, Discord, emote.Id, func, options); - public Task DeleteEmoteAsync(GuildEmote emote, RequestOptions options = null) - => GuildHelper.DeleteEmoteAsync(this, Discord, emote.Id, options); - //Voice States internal async Task AddOrUpdateVoiceStateAsync(ClientState state, VoiceStateModel model) { @@ -475,49 +627,45 @@ namespace Discord.WebSocket var after = SocketVoiceState.Create(voiceChannel, model); _voiceStates[model.UserId] = after; - if (_audioClient != null && before.VoiceChannel?.Id != after.VoiceChannel?.Id) + if (_audioClient == null || before.VoiceChannel?.Id == after.VoiceChannel?.Id) return after; + if (model.UserId == CurrentUser.Id) { - if (model.UserId == CurrentUser.Id) - { - if (after.VoiceChannel != null && _audioClient.ChannelId != after.VoiceChannel?.Id) - { - _audioClient.ChannelId = after.VoiceChannel.Id; - await RepopulateAudioStreamsAsync().ConfigureAwait(false); - } - } - else - { - await _audioClient.RemoveInputStreamAsync(model.UserId).ConfigureAwait(false); //User changed channels, end their stream - if (CurrentUser.VoiceChannel != null && after.VoiceChannel?.Id == CurrentUser.VoiceChannel?.Id) - await _audioClient.CreateInputStreamAsync(model.UserId).ConfigureAwait(false); - } + if (after.VoiceChannel == null || _audioClient.ChannelId == after.VoiceChannel?.Id) return after; + _audioClient.ChannelId = after.VoiceChannel.Id; + await RepopulateAudioStreamsAsync().ConfigureAwait(false); + } + else + { + await _audioClient.RemoveInputStreamAsync(model.UserId) + .ConfigureAwait(false); //User changed channels, end their stream + if (CurrentUser.VoiceChannel != null && after.VoiceChannel?.Id == CurrentUser.VoiceChannel?.Id) + await _audioClient.CreateInputStreamAsync(model.UserId).ConfigureAwait(false); } return after; } + internal SocketVoiceState? GetVoiceState(ulong id) { - if (_voiceStates.TryGetValue(id, out SocketVoiceState voiceState)) + if (_voiceStates.TryGetValue(id, out var voiceState)) return voiceState; return null; } + internal async Task RemoveVoiceStateAsync(ulong id) { - if (_voiceStates.TryRemove(id, out SocketVoiceState voiceState)) - { - if (_audioClient != null) - await _audioClient.RemoveInputStreamAsync(id).ConfigureAwait(false); //User changed channels, end their stream - return voiceState; - } - return null; + if (!_voiceStates.TryRemove(id, out var voiceState)) return null; + if (_audioClient != null) + await _audioClient.RemoveInputStreamAsync(id) + .ConfigureAwait(false); //User changed channels, end their stream + return voiceState; } //Audio - internal AudioInStream GetAudioStream(ulong userId) - { - return _audioClient?.GetInputStream(userId); - } - internal async Task ConnectAudioAsync(ulong channelId, bool selfDeaf, bool selfMute, bool external) + internal AudioInStream GetAudioStream(ulong userId) => _audioClient?.GetInputStream(userId); + + internal async Task ConnectAudioAsync(ulong channelId, bool selfDeaf, bool selfMute, + bool external) { TaskCompletionSource promise; @@ -531,7 +679,8 @@ namespace Discord.WebSocket if (external) { var _ = promise.TrySetResultAsync(null); - await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); + await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute) + .ConfigureAwait(false); return null; } @@ -543,14 +692,18 @@ namespace Discord.WebSocket if (!promise.Task.IsCompleted) { try - { audioClient.Dispose(); } - catch { } + { + audioClient.Dispose(); + } + catch + { + } + _audioClient = null; if (ex != null) await promise.TrySetExceptionAsync(ex); else await promise.TrySetCanceledAsync(); - return; } }; audioClient.Connected += () => @@ -561,7 +714,8 @@ namespace Discord.WebSocket _audioClient = audioClient; } - await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute).ConfigureAwait(false); + await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, channelId, selfDeaf, selfMute) + .ConfigureAwait(false); } catch (Exception) { @@ -599,6 +753,7 @@ namespace Discord.WebSocket _audioLock.Release(); } } + private async Task DisconnectAudioInternalAsync() { _audioConnectPromise?.TrySetCanceledAsync(); //Cancel any previous audio connection @@ -608,6 +763,7 @@ namespace Discord.WebSocket await Discord.ApiClient.SendVoiceStateUpdateAsync(Id, null, false, false).ConfigureAwait(false); _audioClient = null; } + internal async Task FinishConnectAudio(string url, string token) { //TODO: Mem Leak: Disconnected/Connected handlers arent cleaned up @@ -619,7 +775,8 @@ namespace Discord.WebSocket if (_audioClient != null) { await RepopulateAudioStreamsAsync().ConfigureAwait(false); - await _audioClient.StartAsync(url, Discord.CurrentUser.Id, voiceState.VoiceSessionId, token).ConfigureAwait(false); + await _audioClient.StartAsync(url, Discord.CurrentUser.Id, voiceState.VoiceSessionId, token) + .ConfigureAwait(false); } } catch (OperationCanceledException) @@ -639,105 +796,15 @@ namespace Discord.WebSocket internal async Task RepopulateAudioStreamsAsync() { - await _audioClient.ClearInputStreamsAsync().ConfigureAwait(false); //We changed channels, end all current streams + await _audioClient.ClearInputStreamsAsync() + .ConfigureAwait(false); //We changed channels, end all current streams if (CurrentUser.VoiceChannel != null) - { foreach (var pair in _voiceStates) - { if (pair.Value.VoiceChannel?.Id == CurrentUser.VoiceChannel?.Id && pair.Key != CurrentUser.Id) await _audioClient.CreateInputStreamAsync(pair.Key).ConfigureAwait(false); - } - } } public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id})"; internal SocketGuild Clone() => MemberwiseClone() as SocketGuild; - - //IGuild - ulong? IGuild.AFKChannelId => AFKChannelId; - IAudioClient IGuild.AudioClient => null; - bool IGuild.Available => true; - ulong IGuild.DefaultChannelId => DefaultChannel?.Id ?? 0; - ulong? IGuild.EmbedChannelId => EmbedChannelId; - ulong? IGuild.SystemChannelId => SystemChannelId; - IRole IGuild.EveryoneRole => EveryoneRole; - IReadOnlyCollection IGuild.Roles => Roles; - - async Task> IGuild.GetBansAsync(RequestOptions options) - => await GetBansAsync(options).ConfigureAwait(false); - /// - async Task IGuild.GetBanAsync(IUser user, RequestOptions options) - => await GetBanAsync(user, options).ConfigureAwait(false); - /// - async Task IGuild.GetBanAsync(ulong userId, RequestOptions options) - => await GetBanAsync(userId, options).ConfigureAwait(false); - - Task> IGuild.GetChannelsAsync(CacheMode mode, RequestOptions options) - => Task.FromResult>(Channels); - Task IGuild.GetChannelAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetChannel(id)); - Task> IGuild.GetTextChannelsAsync(CacheMode mode, RequestOptions options) - => Task.FromResult>(TextChannels); - Task IGuild.GetTextChannelAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetTextChannel(id)); - Task> IGuild.GetVoiceChannelsAsync(CacheMode mode, RequestOptions options) - => Task.FromResult>(VoiceChannels); - Task> IGuild.GetCategoriesAsync(CacheMode mode , RequestOptions options) - => Task.FromResult>(CategoryChannels); - Task IGuild.GetVoiceChannelAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetVoiceChannel(id)); - Task IGuild.GetAFKChannelAsync(CacheMode mode, RequestOptions options) - => Task.FromResult(AFKChannel); - Task IGuild.GetDefaultChannelAsync(CacheMode mode, RequestOptions options) - => Task.FromResult(DefaultChannel); - Task IGuild.GetEmbedChannelAsync(CacheMode mode, RequestOptions options) - => Task.FromResult(EmbedChannel); - Task IGuild.GetSystemChannelAsync(CacheMode mode, RequestOptions options) - => Task.FromResult(SystemChannel); - async Task IGuild.CreateTextChannelAsync(string name, Action func, RequestOptions options) - => await CreateTextChannelAsync(name, func, options).ConfigureAwait(false); - async Task IGuild.CreateVoiceChannelAsync(string name, Action func, RequestOptions options) - => await CreateVoiceChannelAsync(name, func, options).ConfigureAwait(false); - async Task IGuild.CreateCategoryAsync(string name, RequestOptions options) - => await CreateCategoryChannelAsync(name, options).ConfigureAwait(false); - - async Task> IGuild.GetIntegrationsAsync(RequestOptions options) - => await GetIntegrationsAsync(options).ConfigureAwait(false); - async Task IGuild.CreateIntegrationAsync(ulong id, string type, RequestOptions options) - => await CreateIntegrationAsync(id, type, options).ConfigureAwait(false); - - async Task> IGuild.GetInvitesAsync(RequestOptions options) - => await GetInvitesAsync(options).ConfigureAwait(false); - /// - async Task IGuild.GetVanityInviteAsync(RequestOptions options) - => await GetVanityInviteAsync(options).ConfigureAwait(false); - - IRole IGuild.GetRole(ulong id) - => GetRole(id); - async Task IGuild.CreateRoleAsync(string name, GuildPermissions? permissions, Color? color, bool isHoisted, RequestOptions options) - => await CreateRoleAsync(name, permissions, color, isHoisted, options).ConfigureAwait(false); - - Task> IGuild.GetUsersAsync(CacheMode mode, RequestOptions options) - => Task.FromResult>(Users); - Task IGuild.GetUserAsync(ulong id, CacheMode mode, RequestOptions options) - => Task.FromResult(GetUser(id)); - Task IGuild.GetCurrentUserAsync(CacheMode mode, RequestOptions options) - => Task.FromResult(CurrentUser); - Task IGuild.GetOwnerAsync(CacheMode mode, RequestOptions options) - => Task.FromResult(Owner); - - async Task> IGuild.GetAuditLogsAsync(int limit, CacheMode cacheMode, RequestOptions options) - { - if (cacheMode == CacheMode.AllowDownload) - return (await GetAuditLogsAsync(limit, options).FlattenAsync().ConfigureAwait(false)).ToImmutableArray(); - else - return ImmutableArray.Create(); - } - - async Task IGuild.GetWebhookAsync(ulong id, RequestOptions options) - => await GetWebhookAsync(id, options); - async Task> IGuild.GetWebhooksAsync(RequestOptions options) - => await GetWebhooksAsync(options); } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs index c2cad4d86..613d910b6 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs @@ -12,39 +12,38 @@ namespace Discord.WebSocket private readonly ConcurrentQueue _orderedMessages; private readonly int _size; - public IReadOnlyCollection Messages => _messages.ToReadOnlyCollection(); - public MessageCache(DiscordSocketClient discord, IChannel channel) { _size = discord.MessageCacheSize; - _messages = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, (int)(_size * 1.05)); + _messages = new ConcurrentDictionary(ConcurrentHashSet.DefaultConcurrencyLevel, + (int)(_size * 1.05)); _orderedMessages = new ConcurrentQueue(); } + public IReadOnlyCollection Messages => _messages.ToReadOnlyCollection(); + public void Add(SocketMessage message) { - if (_messages.TryAdd(message.Id, message)) - { - _orderedMessages.Enqueue(message.Id); + if (!_messages.TryAdd(message.Id, message)) return; + _orderedMessages.Enqueue(message.Id); - while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out ulong msgId)) - _messages.TryRemove(msgId, out SocketMessage msg); - } + while (_orderedMessages.Count > _size && _orderedMessages.TryDequeue(out var msgId)) + _messages.TryRemove(msgId, out _); } public SocketMessage Remove(ulong id) { - _messages.TryRemove(id, out SocketMessage msg); + _messages.TryRemove(id, out var msg); return msg; } public SocketMessage Get(ulong id) { - if (_messages.TryGetValue(id, out SocketMessage result)) - return result; - return null; + return _messages.TryGetValue(id, out var result) ? result : null; } - public IReadOnlyCollection GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) + + public IReadOnlyCollection GetMany(ulong? fromMessageId, Direction dir, + int limit = DiscordConfig.MaxMessagesPerBatch) { if (limit < 0) throw new ArgumentOutOfRangeException(nameof(limit)); if (limit == 0) return ImmutableArray.Empty; @@ -63,9 +62,7 @@ namespace Discord.WebSocket return cachedMessageIds .Select(x => { - if (_messages.TryGetValue(x, out SocketMessage msg)) - return msg; - return null; + return _messages.TryGetValue(x, out var msg) ? msg : null; }) .Where(x => x != null) .Take(limit) diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs index 5442c888a..0eea22bce 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketMessage.cs @@ -1,9 +1,9 @@ -using Discord.Rest; -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; +using Discord.Rest; using Model = Discord.API.Message; namespace Discord.WebSocket @@ -11,9 +11,26 @@ namespace Discord.WebSocket public abstract class SocketMessage : SocketEntity, IMessage { private long _timestampTicks; - + + internal SocketMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, + MessageSource source) + : base(discord, id) + { + Channel = channel; + Author = author; + Source = source; + } + public SocketUser Author { get; } public ISocketMessageChannel Channel { get; } + public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); + public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); + + public virtual IReadOnlyCollection MentionedChannels => + ImmutableArray.Create(); + + public virtual IReadOnlyCollection MentionedRoles => ImmutableArray.Create(); + public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); public MessageSource Source { get; } public string Content { get; private set; } @@ -22,29 +39,34 @@ namespace Discord.WebSocket public virtual bool IsTTS => false; public virtual bool IsPinned => false; public virtual DateTimeOffset? EditedTimestamp => null; - public virtual IReadOnlyCollection Attachments => ImmutableArray.Create(); - public virtual IReadOnlyCollection Embeds => ImmutableArray.Create(); - public virtual IReadOnlyCollection MentionedChannels => ImmutableArray.Create(); - public virtual IReadOnlyCollection MentionedRoles => ImmutableArray.Create(); - public virtual IReadOnlyCollection MentionedUsers => ImmutableArray.Create(); public virtual IReadOnlyCollection Tags => ImmutableArray.Create(); public DateTimeOffset Timestamp => DateTimeUtils.FromTicks(_timestampTicks); - internal SocketMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) - : base(discord, id) - { - Channel = channel; - Author = author; - Source = source; - } - internal static SocketMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) + public Task DeleteAsync(RequestOptions options = null) + => MessageHelper.DeleteAsync(this, Discord, options); + + //IMessage + IUser IMessage.Author => Author; + IMessageChannel IMessage.Channel => Channel; + MessageType IMessage.Type => MessageType.Default; + IReadOnlyCollection IMessage.Attachments => Attachments; + IReadOnlyCollection IMessage.Embeds => Embeds; + + IReadOnlyCollection IMessage.MentionedChannelIds => + MentionedChannels.Select(x => x.Id).ToImmutableArray(); + + IReadOnlyCollection IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); + IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); + + internal static SocketMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, + ISocketMessageChannel channel, Model model) { if (model.Type == MessageType.Default) return SocketUserMessage.Create(discord, state, author, channel, model); - else - return SocketSystemMessage.Create(discord, state, author, channel, model); + return SocketSystemMessage.Create(discord, state, author, channel, model); } + internal virtual void Update(ClientState state, Model model) { if (model.Timestamp.IsSpecified) @@ -54,20 +76,7 @@ namespace Discord.WebSocket Content = model.Content.Value; } - public Task DeleteAsync(RequestOptions options = null) - => MessageHelper.DeleteAsync(this, Discord, options); - public override string ToString() => Content; internal SocketMessage Clone() => MemberwiseClone() as SocketMessage; - - //IMessage - IUser IMessage.Author => Author; - IMessageChannel IMessage.Channel => Channel; - MessageType IMessage.Type => MessageType.Default; - IReadOnlyCollection IMessage.Attachments => Attachments; - IReadOnlyCollection IMessage.Embeds => Embeds; - IReadOnlyCollection IMessage.MentionedChannelIds => MentionedChannels.Select(x => x.Id).ToImmutableArray(); - IReadOnlyCollection IMessage.MentionedRoleIds => MentionedRoles.Select(x => x.Id).ToImmutableArray(); - IReadOnlyCollection IMessage.MentionedUserIds => MentionedUsers.Select(x => x.Id).ToImmutableArray(); } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs index e8fa17a35..626ce88e7 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketReaction.cs @@ -4,14 +4,8 @@ namespace Discord.WebSocket { public class SocketReaction : IReaction { - public ulong UserId { get; } - public Optional User { get; } - public ulong MessageId { get; } - public Optional Message { get; } - public ISocketMessageChannel Channel { get; } - public IEmote Emote { get; } - - internal SocketReaction(ISocketMessageChannel channel, ulong messageId, Optional message, ulong userId, Optional user, IEmote emoji) + internal SocketReaction(ISocketMessageChannel channel, ulong messageId, Optional message, + ulong userId, Optional user, IEmote emoji) { Channel = channel; MessageId = messageId; @@ -20,7 +14,16 @@ namespace Discord.WebSocket User = user; Emote = emoji; } - internal static SocketReaction Create(Model model, ISocketMessageChannel channel, Optional message, Optional user) + + public ulong UserId { get; } + public Optional User { get; } + public ulong MessageId { get; } + public Optional Message { get; } + public ISocketMessageChannel Channel { get; } + public IEmote Emote { get; } + + internal static SocketReaction Create(Model model, ISocketMessageChannel channel, + Optional message, Optional user) { IEmote emote; if (model.Emoji.Id.HasValue) @@ -38,7 +41,8 @@ namespace Discord.WebSocket var otherReaction = other as SocketReaction; if (otherReaction == null) return false; - return UserId == otherReaction.UserId && MessageId == otherReaction.MessageId && Emote.Equals(otherReaction.Emote); + return UserId == otherReaction.UserId && MessageId == otherReaction.MessageId && + Emote.Equals(otherReaction.Emote); } public override int GetHashCode() diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs index e6c67159f..b6c0d9fdf 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketSystemMessage.cs @@ -3,29 +3,33 @@ using Model = Discord.API.Message; namespace Discord.WebSocket { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class SocketSystemMessage : SocketMessage, ISystemMessage { - public MessageType Type { get; private set; } - - internal SocketSystemMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author) + internal SocketSystemMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, + SocketUser author) : base(discord, id, channel, author, MessageSource.System) { } - internal new static SocketSystemMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) + + private string DebuggerDisplay => $"{Author}: {Content} ({Id}, {Type})"; + public MessageType Type { get; private set; } + + internal new static SocketSystemMessage Create(DiscordSocketClient discord, ClientState state, + SocketUser author, ISocketMessageChannel channel, Model model) { var entity = new SocketSystemMessage(discord, model.Id, channel, author); entity.Update(state, model); return entity; } + internal override void Update(ClientState state, Model model) { base.Update(state, model); Type = model.Type; } - - private string DebuggerDisplay => $"{Author}: {Content} ({Id}, {Type})"; + internal new SocketSystemMessage Clone() => MemberwiseClone() as SocketSystemMessage; } } diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index f8c15a986..f5ff3e17f 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -1,40 +1,88 @@ -using Discord.Rest; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using Discord.Rest; using Model = Discord.API.Message; namespace Discord.WebSocket { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class SocketUserMessage : SocketMessage, IUserMessage { - private bool _isMentioningEveryone, _isTTS, _isPinned; - private long? _editedTimestampTicks; private ImmutableArray _attachments; + private long? _editedTimestampTicks; private ImmutableArray _embeds; + private bool _isMentioningEveryone, _isTTS, _isPinned; + private readonly List _reactions = new List(); private ImmutableArray _tags; - private List _reactions = new List(); - + + internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, + SocketUser author, MessageSource source) + : base(discord, id, channel, author, source) + { + } + + public override IReadOnlyCollection Attachments => _attachments; + public override IReadOnlyCollection Embeds => _embeds; + + public override IReadOnlyCollection MentionedChannels => + MessageHelper.FilterTagsByValue(TagType.ChannelMention, _tags); + + public override IReadOnlyCollection MentionedRoles => + MessageHelper.FilterTagsByValue(TagType.RoleMention, _tags); + + public override IReadOnlyCollection MentionedUsers => + MessageHelper.FilterTagsByValue(TagType.UserMention, _tags); + + private string DebuggerDisplay => + $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; + public override bool IsTTS => _isTTS; public override bool IsPinned => _isPinned; public override DateTimeOffset? EditedTimestamp => DateTimeUtils.FromTicks(_editedTimestampTicks); - public override IReadOnlyCollection Attachments => _attachments; - public override IReadOnlyCollection Embeds => _embeds; public override IReadOnlyCollection Tags => _tags; - public override IReadOnlyCollection MentionedChannels => MessageHelper.FilterTagsByValue(TagType.ChannelMention, _tags); - public override IReadOnlyCollection MentionedRoles => MessageHelper.FilterTagsByValue(TagType.RoleMention, _tags); - public override IReadOnlyCollection MentionedUsers => MessageHelper.FilterTagsByValue(TagType.UserMention, _tags); - public IReadOnlyDictionary Reactions => _reactions.GroupBy(r => r.Emote).ToDictionary(x => x.Key, x => new ReactionMetadata { ReactionCount = x.Count(), IsMe = x.Any(y => y.UserId == Discord.CurrentUser.Id) }); - internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, SocketUser author, MessageSource source) - : base(discord, id, channel, author, source) - { - } - internal static new SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, ISocketMessageChannel channel, Model model) + public IReadOnlyDictionary Reactions => _reactions.GroupBy(r => r.Emote).ToDictionary( + x => x.Key, x => new ReactionMetadata + { + ReactionCount = x.Count(), + IsMe = x.Any(y => y.UserId == Discord.CurrentUser.Id) + }); + + public Task ModifyAsync(Action func, RequestOptions options = null) + => MessageHelper.ModifyAsync(this, Discord, func, options); + + public Task AddReactionAsync(IEmote emote, RequestOptions options = null) + => MessageHelper.AddReactionAsync(this, emote, Discord, options); + + public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null) + => MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options); + + public Task RemoveAllReactionsAsync(RequestOptions options = null) + => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); + + public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, + RequestOptions options = null) + => MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options); + + public Task PinAsync(RequestOptions options = null) + => MessageHelper.PinAsync(this, Discord, options); + + public Task UnpinAsync(RequestOptions options = null) + => MessageHelper.UnpinAsync(this, Discord, options); + + public string Resolve(TagHandling userHandling = TagHandling.Name, + TagHandling channelHandling = TagHandling.Name, + TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, + TagHandling emojiHandling = TagHandling.Name) + => MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, + emojiHandling); + + internal new static SocketUserMessage Create(DiscordSocketClient discord, ClientState state, SocketUser author, + ISocketMessageChannel channel, Model model) { var entity = new SocketUserMessage(discord, model.Id, channel, author, MessageHelper.GetSource(model)); entity.Update(state, model); @@ -60,8 +108,9 @@ namespace Discord.WebSocket if (value.Length > 0) { var attachments = ImmutableArray.CreateBuilder(value.Length); - for (int i = 0; i < value.Length; i++) - attachments.Add(Attachment.Create(value[i])); + foreach (var t in value) + attachments.Add(Attachment.Create(t)); + _attachments = attachments.ToImmutable(); } else @@ -74,78 +123,58 @@ namespace Discord.WebSocket if (value.Length > 0) { var embeds = ImmutableArray.CreateBuilder(value.Length); - for (int i = 0; i < value.Length; i++) - embeds.Add(value[i].ToEntity()); + foreach (var t in value) + embeds.Add(t.ToEntity()); + _embeds = embeds.ToImmutable(); } else _embeds = ImmutableArray.Create(); } - IReadOnlyCollection mentions = ImmutableArray.Create(); //Is passed to ParseTags to get real mention collection + IReadOnlyCollection + mentions = ImmutableArray + .Create(); //Is passed to ParseTags to get real mention collection if (model.UserMentions.IsSpecified) { var value = model.UserMentions.Value; if (value.Length > 0) { var newMentions = ImmutableArray.CreateBuilder(value.Length); - for (int i = 0; i < value.Length; i++) + foreach (var val in value) { - var val = value[i]; if (val.Object != null) newMentions.Add(SocketUnknownUser.Create(Discord, state, val.Object)); } + mentions = newMentions.ToImmutable(); } } - if (model.Content.IsSpecified) - { - var text = model.Content.Value; - var guild = (Channel as SocketGuildChannel)?.Guild; - _tags = MessageHelper.ParseTags(text, Channel, guild, mentions); - model.Content = text; - } - } - internal void AddReaction(SocketReaction reaction) - { - _reactions.Add(reaction); + if (!model.Content.IsSpecified) return; + var text = model.Content.Value; + var guild = (Channel as SocketGuildChannel)?.Guild; + _tags = MessageHelper.ParseTags(text, Channel, guild, mentions); + model.Content = text; } + + internal void AddReaction(SocketReaction reaction) => _reactions.Add(reaction); + internal void RemoveReaction(SocketReaction reaction) { if (_reactions.Contains(reaction)) _reactions.Remove(reaction); } - internal void ClearReactions() - { - _reactions.Clear(); - } - - public Task ModifyAsync(Action func, RequestOptions options = null) - => MessageHelper.ModifyAsync(this, Discord, func, options); - - public Task AddReactionAsync(IEmote emote, RequestOptions options = null) - => MessageHelper.AddReactionAsync(this, emote, Discord, options); - public Task RemoveReactionAsync(IEmote emote, IUser user, RequestOptions options = null) - => MessageHelper.RemoveReactionAsync(this, user, emote, Discord, options); - public Task RemoveAllReactionsAsync(RequestOptions options = null) - => MessageHelper.RemoveAllReactionsAsync(this, Discord, options); - public IAsyncEnumerable> GetReactionUsersAsync(IEmote emote, int limit, RequestOptions options = null) - => MessageHelper.GetReactionUsersAsync(this, emote, limit, Discord, options); - public Task PinAsync(RequestOptions options = null) - => MessageHelper.PinAsync(this, Discord, options); - public Task UnpinAsync(RequestOptions options = null) - => MessageHelper.UnpinAsync(this, Discord, options); + internal void ClearReactions() => _reactions.Clear(); - public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, - TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) - => MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); - public string Resolve(TagHandling userHandling = TagHandling.Name, TagHandling channelHandling = TagHandling.Name, - TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, TagHandling emojiHandling = TagHandling.Name) - => MentionUtils.Resolve(this, 0, userHandling, channelHandling, roleHandling, everyoneHandling, emojiHandling); + public string Resolve(int startIndex, TagHandling userHandling = TagHandling.Name, + TagHandling channelHandling = TagHandling.Name, + TagHandling roleHandling = TagHandling.Name, TagHandling everyoneHandling = TagHandling.Ignore, + TagHandling emojiHandling = TagHandling.Name) + => MentionUtils.Resolve(this, startIndex, userHandling, channelHandling, roleHandling, everyoneHandling, + emojiHandling); - private string DebuggerDisplay => $"{Author}: {Content} ({Id}{(Attachments.Count > 0 ? $", {Attachments.Count} Attachments" : "")})"; internal new SocketUserMessage Clone() => MemberwiseClone() as SocketUserMessage; } } diff --git a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs index c366258cc..86f419dea 100644 --- a/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs +++ b/src/Discord.Net.WebSocket/Entities/Roles/SocketRole.cs @@ -1,17 +1,29 @@ -using Discord.Rest; -using System; -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using Discord.Rest; using Model = Discord.API.Role; namespace Discord.WebSocket { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class SocketRole : SocketEntity, IRole { + internal SocketRole(SocketGuild guild, ulong id) + : base(guild.Discord, id) + { + Guild = guild; + } + public SocketGuild Guild { get; } + public bool IsEveryone => Id == Guild.Id; + + public IEnumerable Members + => Guild.Users.Where(x => x.Roles.Any(r => r.Id == Id)); + + private string DebuggerDisplay => $"{Name} ({Id})"; public Color Color { get; private set; } public bool IsHoisted { get; private set; } @@ -22,22 +34,26 @@ namespace Discord.WebSocket public int Position { get; private set; } public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - public bool IsEveryone => Id == Guild.Id; public string Mention => IsEveryone ? "@everyone" : MentionUtils.MentionRole(Id); - public IEnumerable Members - => Guild.Users.Where(x => x.Roles.Any(r => r.Id == Id)); - internal SocketRole(SocketGuild guild, ulong id) - : base(guild.Discord, id) - { - Guild = guild; - } + public Task ModifyAsync(Action func, RequestOptions options = null) + => RoleHelper.ModifyAsync(this, Discord, func, options); + + public Task DeleteAsync(RequestOptions options = null) + => RoleHelper.DeleteAsync(this, Discord, options); + + public int CompareTo(IRole role) => RoleUtils.Compare(this, role); + + //IRole + IGuild IRole.Guild => Guild; + internal static SocketRole Create(SocketGuild guild, ClientState state, Model model) { var entity = new SocketRole(guild, model.Id); entity.Update(state, model); return entity; } + internal void Update(ClientState state, Model model) { Name = model.Name; @@ -49,18 +65,7 @@ namespace Discord.WebSocket Permissions = new GuildPermissions(model.Permissions); } - public Task ModifyAsync(Action func, RequestOptions options = null) - => RoleHelper.ModifyAsync(this, Discord, func, options); - public Task DeleteAsync(RequestOptions options = null) - => RoleHelper.DeleteAsync(this, Discord, options); - public override string ToString() => Name; - private string DebuggerDisplay => $"{Name} ({Id})"; internal SocketRole Clone() => MemberwiseClone() as SocketRole; - - public int CompareTo(IRole role) => RoleUtils.Compare(this, role); - - //IRole - IGuild IRole.Guild => Guild; } } diff --git a/src/Discord.Net.WebSocket/Entities/SocketEntity.cs b/src/Discord.Net.WebSocket/Entities/SocketEntity.cs index c8e14fb6c..5ab552580 100644 --- a/src/Discord.Net.WebSocket/Entities/SocketEntity.cs +++ b/src/Discord.Net.WebSocket/Entities/SocketEntity.cs @@ -5,13 +5,13 @@ namespace Discord.WebSocket public abstract class SocketEntity : IEntity where T : IEquatable { - internal DiscordSocketClient Discord { get; } - public T Id { get; } - internal SocketEntity(DiscordSocketClient discord, T id) { Discord = discord; Id = id; } + + internal DiscordSocketClient Discord { get; } + public T Id { get; } } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs index 3117eb14c..28c0afec7 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGlobalUser.cs @@ -8,6 +8,14 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] internal class SocketGlobalUser : SocketUser { + private readonly object _lockObj = new object(); + private ushort _references; + + private SocketGlobalUser(DiscordSocketClient discord, ulong id) + : base(discord, id) + { + } + public override bool IsBot { get; internal set; } public override string Username { get; internal set; } public override ushort DiscriminatorValue { get; internal set; } @@ -18,13 +26,6 @@ namespace Discord.WebSocket public override bool IsWebhook => false; internal override SocketGlobalUser GlobalUser => this; - private readonly object _lockObj = new object(); - private ushort _references; - - private SocketGlobalUser(DiscordSocketClient discord, ulong id) - : base(discord, id) - { - } internal static SocketGlobalUser Create(DiscordSocketClient discord, ClientState state, Model model) { var entity = new SocketGlobalUser(discord, model.Id); @@ -40,21 +41,20 @@ namespace Discord.WebSocket _references++; } } + internal void RemoveRef(DiscordSocketClient discord) { lock (_lockObj) - { if (--_references <= 0) discord.RemoveUser(Id); - } } - + internal void Update(ClientState state, PresenceModel model) { Presence = SocketPresence.Create(model); DMChannel = state.DMChannels.FirstOrDefault(x => x.Recipient.Id == Id); } - + internal new SocketGlobalUser Clone() => MemberwiseClone() as SocketGlobalUser; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs index 8d1b360e3..2a3faf1b0 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGroupUser.cs @@ -6,31 +6,47 @@ namespace Discord.WebSocket [DebuggerDisplay("{DebuggerDisplay,nq}")] public class SocketGroupUser : SocketUser, IGroupUser { + internal SocketGroupUser(SocketGroupChannel channel, SocketGlobalUser globalUser) + : base(channel.Discord, globalUser.Id) + { + Channel = channel; + GlobalUser = globalUser; + } + public SocketGroupChannel Channel { get; } internal override SocketGlobalUser GlobalUser { get; } - public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } } - public override string Username { get { return GlobalUser.Username; } internal set { GlobalUser.Username = value; } } - public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } - public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } - internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } + internal override SocketPresence Presence + { + get => GlobalUser.Presence; + set => GlobalUser.Presence = value; + } - public override bool IsWebhook => false; + public override bool IsBot + { + get => GlobalUser.IsBot; + internal set => GlobalUser.IsBot = value; + } - internal SocketGroupUser(SocketGroupChannel channel, SocketGlobalUser globalUser) - : base(channel.Discord, globalUser.Id) + public override string Username { - Channel = channel; - GlobalUser = globalUser; + get => GlobalUser.Username; + internal set => GlobalUser.Username = value; } - internal static SocketGroupUser Create(SocketGroupChannel channel, ClientState state, Model model) + + public override ushort DiscriminatorValue { - var entity = new SocketGroupUser(channel, channel.Discord.GetOrCreateUser(state, model)); - entity.Update(state, model); - return entity; + get => GlobalUser.DiscriminatorValue; + internal set => GlobalUser.DiscriminatorValue = value; } - internal new SocketGroupUser Clone() => MemberwiseClone() as SocketGroupUser; + public override string AvatarId + { + get => GlobalUser.AvatarId; + internal set => GlobalUser.AvatarId = value; + } + + public override bool IsWebhook => false; //IVoiceState bool IVoiceState.IsDeafened => false; @@ -40,5 +56,14 @@ namespace Discord.WebSocket bool IVoiceState.IsSuppressed => false; IVoiceChannel IVoiceState.VoiceChannel => null; string IVoiceState.VoiceSessionId => null; + + internal static SocketGroupUser Create(SocketGroupChannel channel, ClientState state, Model model) + { + var entity = new SocketGroupUser(channel, channel.Discord.GetOrCreateUser(state, model)); + entity.Update(state, model); + return entity; + } + + internal new SocketGroupUser Clone() => MemberwiseClone() as SocketGroupUser; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs index 66af20bb6..e59bedcf0 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs @@ -1,11 +1,11 @@ -using Discord.Audio; -using Discord.Rest; -using System; +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using Discord.Audio; +using Discord.Rest; using UserModel = Discord.API.User; using MemberModel = Discord.API.GuildMember; using PresenceModel = Discord.API.Presence; @@ -18,34 +18,30 @@ namespace Discord.WebSocket private long? _joinedAtTicks; private ImmutableArray _roleIds; + internal SocketGuildUser(SocketGuild guild, SocketGlobalUser globalUser) + : base(guild.Discord, globalUser.Id) + { + Guild = guild; + GlobalUser = globalUser; + } + internal override SocketGlobalUser GlobalUser { get; } public SocketGuild Guild { get; } - public string Nickname { get; private set; } - - public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } } - public override string Username { get { return GlobalUser.Username; } internal set { GlobalUser.Username = value; } } - public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } - public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } - public GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this)); internal override SocketPresence Presence { get; set; } - public override bool IsWebhook => false; - public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; - public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; - public bool IsSuppressed => VoiceState?.IsSuppressed ?? false; - public bool IsDeafened => VoiceState?.IsDeafened ?? false; - public bool IsMuted => VoiceState?.IsMuted ?? false; - public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); - public IReadOnlyCollection Roles - => _roleIds.Select(id => Guild.GetRole(id)).Where(x => x != null).ToReadOnlyCollection(() => _roleIds.Length); + public IReadOnlyCollection Roles + => _roleIds.Select(id => Guild.GetRole(id)).Where(x => x != null) + .ToReadOnlyCollection(() => _roleIds.Length); + public SocketVoiceChannel VoiceChannel => VoiceState?.VoiceChannel; - public string VoiceSessionId => VoiceState?.VoiceSessionId ?? ""; public SocketVoiceState? VoiceState => Guild.GetVoiceState(Id); public AudioInStream AudioStream => Guild.GetAudioStream(Id); /// The position of the user within the role hierarchy. - /// The returned value equal to the position of the highest role the user has, - /// or int.MaxValue if user is the server owner. + /// + /// The returned value equal to the position of the highest role the user has, + /// or int.MaxValue if user is the server owner. + /// public int Hierarchy { get @@ -53,23 +49,88 @@ namespace Discord.WebSocket if (Guild.OwnerId == Id) return int.MaxValue; - int maxPos = 0; - for (int i = 0; i < _roleIds.Length; i++) + var maxPos = 0; + foreach (var t in _roleIds) { - var role = Guild.GetRole(_roleIds[i]); + var role = Guild.GetRole(t); if (role != null && role.Position > maxPos) maxPos = role.Position; } + return maxPos; } } - internal SocketGuildUser(SocketGuild guild, SocketGlobalUser globalUser) - : base(guild.Discord, globalUser.Id) + public string Nickname { get; private set; } + + public override bool IsBot { - Guild = guild; - GlobalUser = globalUser; + get => GlobalUser.IsBot; + internal set => GlobalUser.IsBot = value; } + + public override string Username + { + get => GlobalUser.Username; + internal set => GlobalUser.Username = value; + } + + public override ushort DiscriminatorValue + { + get => GlobalUser.DiscriminatorValue; + internal set => GlobalUser.DiscriminatorValue = value; + } + + public override string AvatarId + { + get => GlobalUser.AvatarId; + internal set => GlobalUser.AvatarId = value; + } + + public GuildPermissions GuildPermissions => new GuildPermissions(Permissions.ResolveGuild(Guild, this)); + + public override bool IsWebhook => false; + public bool IsSelfDeafened => VoiceState?.IsSelfDeafened ?? false; + public bool IsSelfMuted => VoiceState?.IsSelfMuted ?? false; + public bool IsSuppressed => VoiceState?.IsSuppressed ?? false; + public bool IsDeafened => VoiceState?.IsDeafened ?? false; + public bool IsMuted => VoiceState?.IsMuted ?? false; + public DateTimeOffset? JoinedAt => DateTimeUtils.FromTicks(_joinedAtTicks); + public string VoiceSessionId => VoiceState?.VoiceSessionId ?? ""; + + public Task ModifyAsync(Action func, RequestOptions options = null) + => UserHelper.ModifyAsync(this, Discord, func, options); + + public Task KickAsync(string reason = null, RequestOptions options = null) + => UserHelper.KickAsync(this, Discord, reason, options); + + /// + public Task AddRoleAsync(IRole role, RequestOptions options = null) + => AddRolesAsync(new[] {role}, options); + + /// + public Task AddRolesAsync(IEnumerable roles, RequestOptions options = null) + => UserHelper.AddRolesAsync(this, Discord, roles, options); + + /// + public Task RemoveRoleAsync(IRole role, RequestOptions options = null) + => RemoveRolesAsync(new[] {role}, options); + + /// + public Task RemoveRolesAsync(IEnumerable roles, RequestOptions options = null) + => UserHelper.RemoveRolesAsync(this, Discord, roles, options); + + public ChannelPermissions GetPermissions(IGuildChannel channel) + => new ChannelPermissions(Permissions.ResolveChannel(Guild, this, channel, GuildPermissions.RawValue)); + + //IGuildUser + IGuild IGuildUser.Guild => Guild; + ulong IGuildUser.GuildId => Guild.Id; + IReadOnlyCollection IGuildUser.RoleIds => _roleIds; + + //IVoiceState + IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; + internal static SocketGuildUser Create(SocketGuild guild, ClientState state, UserModel model) { var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model)); @@ -77,18 +138,21 @@ namespace Discord.WebSocket entity.UpdateRoles(new ulong[0]); return entity; } + internal static SocketGuildUser Create(SocketGuild guild, ClientState state, MemberModel model) { var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); entity.Update(state, model); return entity; } + internal static SocketGuildUser Create(SocketGuild guild, ClientState state, PresenceModel model) { var entity = new SocketGuildUser(guild, guild.Discord.GetOrCreateUser(state, model.User)); entity.Update(state, model, false); return entity; } + internal void Update(ClientState state, MemberModel model) { base.Update(state, model.User); @@ -99,6 +163,7 @@ namespace Discord.WebSocket if (model.Roles.IsSpecified) UpdateRoles(model.Roles.Value); } + internal void Update(ClientState state, PresenceModel model, bool updatePresence) { if (updatePresence) @@ -106,48 +171,23 @@ namespace Discord.WebSocket Presence = SocketPresence.Create(model); GlobalUser.Update(state, model); } + if (model.Nick.IsSpecified) Nickname = model.Nick.Value; if (model.Roles.IsSpecified) UpdateRoles(model.Roles.Value); } + private void UpdateRoles(ulong[] roleIds) { var roles = ImmutableArray.CreateBuilder(roleIds.Length + 1); roles.Add(Guild.Id); - for (int i = 0; i < roleIds.Length; i++) - roles.Add(roleIds[i]); + foreach (var t in roleIds) + roles.Add(t); + _roleIds = roles.ToImmutable(); } - - public Task ModifyAsync(Action func, RequestOptions options = null) - => UserHelper.ModifyAsync(this, Discord, func, options); - public Task KickAsync(string reason = null, RequestOptions options = null) - => UserHelper.KickAsync(this, Discord, reason, options); - /// - public Task AddRoleAsync(IRole role, RequestOptions options = null) - => AddRolesAsync(new[] { role }, options); - /// - public Task AddRolesAsync(IEnumerable roles, RequestOptions options = null) - => UserHelper.AddRolesAsync(this, Discord, roles, options); - /// - public Task RemoveRoleAsync(IRole role, RequestOptions options = null) - => RemoveRolesAsync(new[] { role }, options); - /// - public Task RemoveRolesAsync(IEnumerable roles, RequestOptions options = null) - => UserHelper.RemoveRolesAsync(this, Discord, roles, options); - - public ChannelPermissions GetPermissions(IGuildChannel channel) - => new ChannelPermissions(Permissions.ResolveChannel(Guild, this, channel, GuildPermissions.RawValue)); internal new SocketGuildUser Clone() => MemberwiseClone() as SocketGuildUser; - - //IGuildUser - IGuild IGuildUser.Guild => Guild; - ulong IGuildUser.GuildId => Guild.Id; - IReadOnlyCollection IGuildUser.RoleIds => _roleIds; - - //IVoiceState - IVoiceChannel IVoiceState.VoiceChannel => VoiceChannel; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs index 7d7ba16ce..168cd8972 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketPresence.cs @@ -4,7 +4,7 @@ using Model = Discord.API.Presence; namespace Discord.WebSocket { //TODO: C#7 Candidate for record type - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public struct SocketPresence : IPresence { public UserStatus Status { get; } @@ -13,15 +13,13 @@ namespace Discord.WebSocket internal SocketPresence(UserStatus status, IActivity activity) { Status = status; - Activity= activity; - } - internal static SocketPresence Create(Model model) - { - return new SocketPresence(model.Status, model.Game?.ToEntity()); + Activity = activity; } + internal static SocketPresence Create(Model model) => new SocketPresence(model.Status, model.Game?.ToEntity()); + public override string ToString() => Status.ToString(); - private string DebuggerDisplay => $"{Status}{(Activity != null ? $", {Activity.Name}": "")}"; + private string DebuggerDisplay => $"{Status}{(Activity != null ? $", {Activity.Name}" : "")}"; internal SocketPresence Clone() => this; } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs index b7c02c2db..289e14e30 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketSelfUser.cs @@ -1,7 +1,7 @@ -using Discord.Rest; -using System; +using System; using System.Diagnostics; using System.Threading.Tasks; +using Discord.Rest; using Model = Discord.API.User; namespace Discord.WebSocket @@ -9,53 +9,80 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketSelfUser : SocketUser, ISelfUser { + internal SocketSelfUser(DiscordSocketClient discord, SocketGlobalUser globalUser) + : base(discord, globalUser.Id) + { + GlobalUser = globalUser; + } + + internal override SocketGlobalUser GlobalUser { get; } + + internal override SocketPresence Presence + { + get => GlobalUser.Presence; + set => GlobalUser.Presence = value; + } + public string Email { get; private set; } public bool IsVerified { get; private set; } public bool IsMfaEnabled { get; private set; } - internal override SocketGlobalUser GlobalUser { get; } - public override bool IsBot { get { return GlobalUser.IsBot; } internal set { GlobalUser.IsBot = value; } } - public override string Username { get { return GlobalUser.Username; } internal set { GlobalUser.Username = value; } } - public override ushort DiscriminatorValue { get { return GlobalUser.DiscriminatorValue; } internal set { GlobalUser.DiscriminatorValue = value; } } - public override string AvatarId { get { return GlobalUser.AvatarId; } internal set { GlobalUser.AvatarId = value; } } - internal override SocketPresence Presence { get { return GlobalUser.Presence; } set { GlobalUser.Presence = value; } } + public override bool IsBot + { + get => GlobalUser.IsBot; + internal set => GlobalUser.IsBot = value; + } - public override bool IsWebhook => false; + public override string Username + { + get => GlobalUser.Username; + internal set => GlobalUser.Username = value; + } - internal SocketSelfUser(DiscordSocketClient discord, SocketGlobalUser globalUser) - : base(discord, globalUser.Id) + public override ushort DiscriminatorValue { - GlobalUser = globalUser; + get => GlobalUser.DiscriminatorValue; + internal set => GlobalUser.DiscriminatorValue = value; } + + public override string AvatarId + { + get => GlobalUser.AvatarId; + internal set => GlobalUser.AvatarId = value; + } + + public override bool IsWebhook => false; + + public Task ModifyAsync(Action func, RequestOptions options = null) + => UserHelper.ModifyAsync(this, Discord, func, options); + internal static SocketSelfUser Create(DiscordSocketClient discord, ClientState state, Model model) { var entity = new SocketSelfUser(discord, discord.GetOrCreateSelfUser(state, model)); entity.Update(state, model); return entity; } + internal override bool Update(ClientState state, Model model) { - bool hasGlobalChanges = base.Update(state, model); + var hasGlobalChanges = base.Update(state, model); if (model.Email.IsSpecified) { Email = model.Email.Value; hasGlobalChanges = true; } + if (model.Verified.IsSpecified) { IsVerified = model.Verified.Value; hasGlobalChanges = true; } - if (model.MfaEnabled.IsSpecified) - { - IsMfaEnabled = model.MfaEnabled.Value; - hasGlobalChanges = true; - } - return hasGlobalChanges; + + if (!model.MfaEnabled.IsSpecified) return hasGlobalChanges; + IsMfaEnabled = model.MfaEnabled.Value; + + return true; } - - public Task ModifyAsync(Action func, RequestOptions options = null) - => UserHelper.ModifyAsync(this, Discord, func, options); internal new SocketSelfUser Clone() => MemberwiseClone() as SocketSelfUser; } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs index c7f6cb846..f405c2988 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUnknownUser.cs @@ -7,6 +7,11 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketUnknownUser : SocketUser { + internal SocketUnknownUser(DiscordSocketClient discord, ulong id) + : base(discord, id) + { + } + public override string Username { get; internal set; } public override ushort DiscriminatorValue { get; internal set; } public override string AvatarId { get; internal set; } @@ -14,13 +19,14 @@ namespace Discord.WebSocket public override bool IsWebhook => false; - internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null); } set { } } - internal override SocketGlobalUser GlobalUser { get { throw new NotSupportedException(); } } - - internal SocketUnknownUser(DiscordSocketClient discord, ulong id) - : base(discord, id) + internal override SocketPresence Presence { + get => new SocketPresence(UserStatus.Offline, null); + set { } } + + internal override SocketGlobalUser GlobalUser => throw new NotSupportedException(); + internal static SocketUnknownUser Create(DiscordSocketClient discord, ClientState state, Model model) { var entity = new SocketUnknownUser(discord, model.Id); diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs index 00899d47e..5baf0c0a9 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketUser.cs @@ -1,19 +1,25 @@ -using Discord.Rest; using System; using System.Threading.Tasks; +using Discord.Rest; using Model = Discord.API.User; namespace Discord.WebSocket { public abstract class SocketUser : SocketEntity, IUser { + internal SocketUser(DiscordSocketClient discord, ulong id) + : base(discord, id) + { + } + + internal abstract SocketGlobalUser GlobalUser { get; } + internal abstract SocketPresence Presence { get; set; } + private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; public abstract bool IsBot { get; internal set; } public abstract string Username { get; internal set; } public abstract ushort DiscriminatorValue { get; internal set; } public abstract string AvatarId { get; internal set; } public abstract bool IsWebhook { get; } - internal abstract SocketGlobalUser GlobalUser { get; } - internal abstract SocketPresence Presence { get; set; } public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); public string Discriminator => DiscriminatorValue.ToString("D4"); @@ -21,18 +27,24 @@ namespace Discord.WebSocket public IActivity Activity => Presence.Activity; public UserStatus Status => Presence.Status; - internal SocketUser(DiscordSocketClient discord, ulong id) - : base(discord, id) - { - } + public async Task GetOrCreateDMChannelAsync(RequestOptions options = null) + => GlobalUser.DMChannel ?? await UserHelper.CreateDMChannelAsync(this, Discord, options) as IDMChannel; + + public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) + => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); + + public string GetDefaultAvatarUrl() + => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); + internal virtual bool Update(ClientState state, Model model) { - bool hasChanges = false; + var hasChanges = false; if (model.Avatar.IsSpecified && model.Avatar.Value != AvatarId) { AvatarId = model.Avatar.Value; hasChanges = true; } + if (model.Discriminator.IsSpecified) { var newVal = ushort.Parse(model.Discriminator.Value); @@ -42,30 +54,20 @@ namespace Discord.WebSocket hasChanges = true; } } + if (model.Bot.IsSpecified && model.Bot.Value != IsBot) { IsBot = model.Bot.Value; hasChanges = true; } - if (model.Username.IsSpecified && model.Username.Value != Username) - { - Username = model.Username.Value; - hasChanges = true; - } - return hasChanges; - } - public async Task GetOrCreateDMChannelAsync(RequestOptions options = null) - => GlobalUser.DMChannel ?? await UserHelper.CreateDMChannelAsync(this, Discord, options) as IDMChannel; - - public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) - => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); + if (!model.Username.IsSpecified || model.Username.Value == Username) return hasChanges; + Username = model.Username.Value; - public string GetDefaultAvatarUrl() - => CDN.GetDefaultUserAvatarUrl(DiscriminatorValue); + return true; + } public override string ToString() => $"{Username}#{Discriminator}"; - private string DebuggerDisplay => $"{Username}#{Discriminator} ({Id}{(IsBot ? ", Bot" : "")})"; internal SocketUser Clone() => MemberwiseClone() as SocketUser; } } diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs index 480103326..e82e10a48 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketVoiceState.cs @@ -5,10 +5,11 @@ using Model = Discord.API.VoiceState; namespace Discord.WebSocket { //TODO: C#7 Candidate for record type - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public struct SocketVoiceState : IVoiceState { - public static readonly SocketVoiceState Default = new SocketVoiceState(null, null, false, false, false, false, false); + public static readonly SocketVoiceState Default = + new SocketVoiceState(null, null, false, false, false, false, false); [Flags] private enum Flags : byte @@ -18,11 +19,11 @@ namespace Discord.WebSocket Muted = 0x02, Deafened = 0x04, SelfMuted = 0x08, - SelfDeafened = 0x10, + SelfDeafened = 0x10 } private readonly Flags _voiceStates; - + public SocketVoiceChannel VoiceChannel { get; } public string VoiceSessionId { get; } @@ -32,12 +33,13 @@ namespace Discord.WebSocket public bool IsSelfMuted => (_voiceStates & Flags.SelfMuted) != 0; public bool IsSelfDeafened => (_voiceStates & Flags.SelfDeafened) != 0; - internal SocketVoiceState(SocketVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed) + internal SocketVoiceState(SocketVoiceChannel voiceChannel, string sessionId, bool isSelfMuted, + bool isSelfDeafened, bool isMuted, bool isDeafened, bool isSuppressed) { VoiceChannel = voiceChannel; VoiceSessionId = sessionId; - Flags voiceStates = Flags.Normal; + var voiceStates = Flags.Normal; if (isSelfMuted) voiceStates |= Flags.SelfMuted; if (isSelfDeafened) @@ -50,10 +52,9 @@ namespace Discord.WebSocket voiceStates |= Flags.Suppressed; _voiceStates = voiceStates; } - internal static SocketVoiceState Create(SocketVoiceChannel voiceChannel, Model model) - { - return new SocketVoiceState(voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress); - } + + internal static SocketVoiceState Create(SocketVoiceChannel voiceChannel, Model model) => new SocketVoiceState( + voiceChannel, model.SessionId, model.SelfMute, model.SelfDeaf, model.Mute, model.Deaf, model.Suppress); public override string ToString() => VoiceChannel?.Name ?? "Unknown"; private string DebuggerDisplay => $"{VoiceChannel?.Name ?? "Unknown"} ({_voiceStates})"; diff --git a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs index dd80648d2..89ccf916f 100644 --- a/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs +++ b/src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs @@ -10,33 +10,30 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{DebuggerDisplay,nq}")] public class SocketWebhookUser : SocketUser, IWebhookUser { - public SocketGuild Guild { get; } - public ulong WebhookId { get; } - - public override string Username { get; internal set; } - public override ushort DiscriminatorValue { get; internal set; } - public override string AvatarId { get; internal set; } - public override bool IsBot { get; internal set; } - - public override bool IsWebhook => true; - - internal override SocketPresence Presence { get { return new SocketPresence(UserStatus.Offline, null); } set { } } - internal override SocketGlobalUser GlobalUser { get { throw new NotSupportedException(); } } - internal SocketWebhookUser(SocketGuild guild, ulong id, ulong webhookId) : base(guild.Discord, id) { Guild = guild; WebhookId = webhookId; } - internal static SocketWebhookUser Create(SocketGuild guild, ClientState state, Model model, ulong webhookId) + + public SocketGuild Guild { get; } + + internal override SocketPresence Presence { - var entity = new SocketWebhookUser(guild, model.Id, webhookId); - entity.Update(state, model); - return entity; + get => new SocketPresence(UserStatus.Offline, null); + set { } } - internal new SocketWebhookUser Clone() => MemberwiseClone() as SocketWebhookUser; + internal override SocketGlobalUser GlobalUser => throw new NotSupportedException(); + public ulong WebhookId { get; } + + public override string Username { get; internal set; } + public override ushort DiscriminatorValue { get; internal set; } + public override string AvatarId { get; internal set; } + public override bool IsBot { get; internal set; } + + public override bool IsWebhook => true; //IGuildUser @@ -47,32 +44,26 @@ namespace Discord.WebSocket string IGuildUser.Nickname => null; GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook; - ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); - Task IGuildUser.KickAsync(string reason, RequestOptions options) - { + ChannelPermissions IGuildUser.GetPermissions(IGuildChannel channel) => + Permissions.ToChannelPerms(channel, GuildPermissions.Webhook.RawValue); + + Task IGuildUser.KickAsync(string reason, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be kicked."); - } - Task IGuildUser.ModifyAsync(Action func, RequestOptions options) - { + + Task IGuildUser.ModifyAsync(Action func, RequestOptions options) => throw new NotSupportedException("Webhook users cannot be modified."); - } - Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) - { + Task IGuildUser.AddRoleAsync(IRole role, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } - Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) - { + + Task IGuildUser.AddRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } - Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) - { + + Task IGuildUser.RemoveRoleAsync(IRole role, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } - Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) - { + + Task IGuildUser.RemoveRolesAsync(IEnumerable roles, RequestOptions options) => throw new NotSupportedException("Roles are not supported on webhook users."); - } //IVoiceState bool IVoiceState.IsDeafened => false; @@ -82,5 +73,14 @@ namespace Discord.WebSocket bool IVoiceState.IsSuppressed => false; IVoiceChannel IVoiceState.VoiceChannel => null; string IVoiceState.VoiceSessionId => null; + + internal static SocketWebhookUser Create(SocketGuild guild, ClientState state, Model model, ulong webhookId) + { + var entity = new SocketWebhookUser(guild, model.Id, webhookId); + entity.Update(state, model); + return entity; + } + + internal new SocketWebhookUser Clone() => MemberwiseClone() as SocketWebhookUser; } } diff --git a/src/Discord.Net.WebSocket/Entities/Voice/SocketVoiceServer.cs b/src/Discord.Net.WebSocket/Entities/Voice/SocketVoiceServer.cs index 57abf1d03..4ee46b948 100644 --- a/src/Discord.Net.WebSocket/Entities/Voice/SocketVoiceServer.cs +++ b/src/Discord.Net.WebSocket/Entities/Voice/SocketVoiceServer.cs @@ -2,13 +2,9 @@ using System.Diagnostics; namespace Discord.WebSocket { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class SocketVoiceServer { - public Cacheable Guild { get; private set; } - public string Endpoint { get; private set; } - public string Token { get; private set; } - internal SocketVoiceServer(Cacheable guild, string endpoint, string token) { Guild = guild; @@ -16,6 +12,10 @@ namespace Discord.WebSocket Token = token; } + public Cacheable Guild { get; } + public string Endpoint { get; } + public string Token { get; } + private string DebuggerDisplay => $"SocketVoiceServer ({Guild.Id})"; } } diff --git a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs index e8dc4b5f0..1affa8a7b 100644 --- a/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs +++ b/src/Discord.Net.WebSocket/Extensions/EntityExtensions.cs @@ -1,5 +1,6 @@ using System.Collections.Immutable; using System.Linq; +using Discord.API; namespace Discord.WebSocket { @@ -11,8 +12,8 @@ namespace Discord.WebSocket if (model.SyncId.IsSpecified) { var assets = model.Assets.GetValueOrDefault()?.ToEntity(); - string albumText = assets?[1]?.Text; - string albumArtId = assets?[1]?.ImageId?.Replace("spotify:",""); + var albumText = assets?[1]?.Text; + var albumArtId = assets?[1]?.ImageId?.Replace("spotify:", ""); var timestamps = model.Timestamps.IsSpecified ? model.Timestamps.Value.ToEntity() : null; return new SpotifyGame { @@ -22,7 +23,7 @@ namespace Discord.WebSocket TrackUrl = CDN.GetSpotifyDirectUrl(model.SyncId.Value), AlbumTitle = albumText, TrackTitle = model.Details.GetValueOrDefault(), - Artists = model.State.GetValueOrDefault()?.Split(';').Select(x=>x?.Trim()).ToImmutableArray(), + Artists = model.State.GetValueOrDefault()?.Split(';').Select(x => x?.Trim()).ToImmutableArray(), Duration = timestamps?.End - timestamps?.Start, AlbumArtUrl = albumArtId != null ? CDN.GetSpotifyAlbumArtUrl(albumArtId) : null, Type = ActivityType.Listening @@ -32,7 +33,7 @@ namespace Discord.WebSocket // Rich Game if (model.ApplicationId.IsSpecified) { - ulong appId = model.ApplicationId.Value; + var appId = model.ApplicationId.Value; var assets = model.Assets.GetValueOrDefault()?.ToEntity(appId); return new RichGame { @@ -47,62 +48,63 @@ namespace Discord.WebSocket Timestamps = model.Timestamps.IsSpecified ? model.Timestamps.Value.ToEntity() : null }; } + // Stream Game if (model.StreamUrl.IsSpecified) - { return new StreamingGame( - model.Name, + model.Name, model.StreamUrl.Value); - } // Normal Game return new Game(model.Name, model.Type.GetValueOrDefault() ?? ActivityType.Playing); } // (Small, Large) - public static GameAsset[] ToEntity(this API.GameAssets model, ulong? appId = null) + public static GameAsset[] ToEntity(this GameAssets model, ulong? appId = null) => new[] { - return new GameAsset[] - { - model.SmallImage.IsSpecified ? new GameAsset + model.SmallImage.IsSpecified + ? new GameAsset { ApplicationId = appId, ImageId = model.SmallImage.GetValueOrDefault(), Text = model.SmallText.GetValueOrDefault() - } : null, - model.LargeImage.IsSpecified ? new GameAsset + } + : null, + model.LargeImage.IsSpecified + ? new GameAsset { ApplicationId = appId, ImageId = model.LargeImage.GetValueOrDefault(), Text = model.LargeText.GetValueOrDefault() - } : null, - }; - } + } + : null + }; public static GameParty ToEntity(this API.GameParty model) { // Discord will probably send bad data since they don't validate anything long current = 0, cap = 0; - if (model.Size?.Length == 2) - { - current = model.Size[0]; - cap = model.Size[1]; - } + if (model.Size?.Length != 2) + return new GameParty + { + Id = model.Id, + Members = current, + Capacity = cap + }; + current = model.Size[0]; + cap = model.Size[1]; + return new GameParty { Id = model.Id, Members = current, - Capacity = cap, + Capacity = cap }; } - public static GameSecrets ToEntity(this API.GameSecrets model) - { - return new GameSecrets(model.Match, model.Join, model.Spectate); - } + public static GameSecrets ToEntity(this API.GameSecrets model) => + new GameSecrets(model.Match, model.Join, model.Spectate); - public static GameTimestamps ToEntity(this API.GameTimestamps model) - { - return new GameTimestamps(model.Start.ToNullable(), model.End.ToNullable()); - } + public static GameTimestamps ToEntity(this API.GameTimestamps model) => + new GameTimestamps(model.Start.ToNullable(), model.End.ToNullable()); } } diff --git a/src/Discord.Net.WebSocket/Net/DefaultUdpSocket.cs b/src/Discord.Net.WebSocket/Net/DefaultUdpSocket.cs index 251a761d4..421e30771 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultUdpSocket.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultUdpSocket.cs @@ -8,36 +8,25 @@ namespace Discord.Net.Udp { internal class DefaultUdpSocket : IUdpSocket, IDisposable { - public event Func ReceivedDatagram; - private readonly SemaphoreSlim _lock; - private UdpClient _udp; - private IPEndPoint _destination; - private CancellationTokenSource _cancelTokenSource; private CancellationToken _cancelToken, _parentToken; - private Task _task; + private CancellationTokenSource _cancelTokenSource; + private IPEndPoint _destination; private bool _isDisposed; - - public ushort Port => (ushort)((_udp?.Client.LocalEndPoint as IPEndPoint)?.Port ?? 0); + private Task _task; + private UdpClient _udp; public DefaultUdpSocket() { _lock = new SemaphoreSlim(1, 1); _cancelTokenSource = new CancellationTokenSource(); } - private void Dispose(bool disposing) - { - if (!_isDisposed) - { - if (disposing) - StopInternalAsync(true).GetAwaiter().GetResult(); - _isDisposed = true; - } - } - public void Dispose() - { - Dispose(true); - } + + public void Dispose() => Dispose(true); + + public event Func ReceivedDatagram; + + public ushort Port => (ushort)((_udp?.Client.LocalEndPoint as IPEndPoint)?.Port ?? 0); public async Task StartAsync() @@ -52,17 +41,7 @@ namespace Discord.Net.Udp _lock.Release(); } } - public async Task StartInternalAsync(CancellationToken cancelToken) - { - await StopInternalAsync().ConfigureAwait(false); - - _cancelTokenSource = new CancellationTokenSource(); - _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; - - _udp = new UdpClient(0); - _task = RunAsync(_cancelToken); - } public async Task StopAsync() { await _lock.WaitAsync().ConfigureAwait(false); @@ -75,29 +54,14 @@ namespace Discord.Net.Udp _lock.Release(); } } - public async Task StopInternalAsync(bool isDisposing = false) - { - try { _cancelTokenSource.Cancel(false); } catch { } - if (!isDisposing) - await (_task ?? Task.Delay(0)).ConfigureAwait(false); - - if (_udp != null) - { - try { _udp.Dispose(); } - catch { } - _udp = null; - } - } + public void SetDestination(string ip, int port) => _destination = new IPEndPoint(IPAddress.Parse(ip), port); - public void SetDestination(string ip, int port) - { - _destination = new IPEndPoint(IPAddress.Parse(ip), port); - } public void SetCancelToken(CancellationToken cancelToken) { _parentToken = cancelToken; - _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token) + .Token; } public async Task SendAsync(byte[] data, int index, int count) @@ -108,9 +72,58 @@ namespace Discord.Net.Udp Buffer.BlockCopy(data, index, newData, 0, count); data = newData; } + await _udp.SendAsync(data, count, _destination).ConfigureAwait(false); } + private void Dispose(bool disposing) + { + if (_isDisposed) return; + if (disposing) + StopInternalAsync(true).GetAwaiter().GetResult(); + _isDisposed = true; + } + + public async Task StartInternalAsync(CancellationToken cancelToken) + { + await StopInternalAsync().ConfigureAwait(false); + + _cancelTokenSource = new CancellationTokenSource(); + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token) + .Token; + + _udp = new UdpClient(0); + + _task = RunAsync(_cancelToken); + } + + public async Task StopInternalAsync(bool isDisposing = false) + { + try + { + _cancelTokenSource.Cancel(false); + } + catch + { + } + + if (!isDisposing) + await (_task ?? Task.Delay(0)).ConfigureAwait(false); + + if (_udp != null) + { + try + { + _udp.Dispose(); + } + catch + { + } + + _udp = null; + } + } + private async Task RunAsync(CancellationToken cancelToken) { var closeTask = Task.Delay(-1, cancelToken); diff --git a/src/Discord.Net.WebSocket/Net/DefaultUdpSocketProvider.cs b/src/Discord.Net.WebSocket/Net/DefaultUdpSocketProvider.cs index d701fa79a..59a64e8d7 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultUdpSocketProvider.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultUdpSocketProvider.cs @@ -4,7 +4,7 @@ namespace Discord.Net.Udp { public static class DefaultUdpSocketProvider { - public static readonly UdpSocketProvider Instance = () => + public static readonly UdpSocketProvider Instance = () => { try { @@ -12,7 +12,8 @@ namespace Discord.Net.Udp } catch (PlatformNotSupportedException ex) { - throw new PlatformNotSupportedException("The default UdpSocketProvider is not supported on this platform.", ex); + throw new PlatformNotSupportedException( + "The default UdpSocketProvider is not supported on this platform.", ex); } }; } diff --git a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs index c60368da0..1aeffc73a 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs @@ -15,19 +15,15 @@ namespace Discord.Net.WebSockets public const int ReceiveChunkSize = 16 * 1024; //16KB public const int SendChunkSize = 4 * 1024; //4KB private const int HR_TIMEOUT = -2147012894; - - public event Func BinaryMessage; - public event Func TextMessage; - public event Func Closed; + private readonly Dictionary _headers; private readonly SemaphoreSlim _lock; - private readonly Dictionary _headers; - private ClientWebSocket _client; - private IWebProxy _proxy; - private Task _task; - private CancellationTokenSource _cancelTokenSource; private CancellationToken _cancelToken, _parentToken; + private CancellationTokenSource _cancelTokenSource; + private ClientWebSocket _client; private bool _isDisposed, _isDisconnecting; + private readonly IWebProxy _proxy; + private Task _task; public DefaultWebSocketClient(IWebProxy proxy = null) { @@ -38,67 +34,114 @@ namespace Discord.Net.WebSockets _headers = new Dictionary(); _proxy = proxy; } - private void Dispose(bool disposing) + + public void Dispose() => Dispose(true); + + public event Func BinaryMessage; + public event Func TextMessage; + public event Func Closed; + + public async Task ConnectAsync(string host) + { + await _lock.WaitAsync().ConfigureAwait(false); + try + { + await ConnectInternalAsync(host).ConfigureAwait(false); + } + finally + { + _lock.Release(); + } + } + + public async Task DisconnectAsync() { - if (!_isDisposed) + await _lock.WaitAsync().ConfigureAwait(false); + try + { + await DisconnectInternalAsync().ConfigureAwait(false); + } + finally { - if (disposing) - DisconnectInternalAsync(true).GetAwaiter().GetResult(); - _isDisposed = true; + _lock.Release(); } } - public void Dispose() + + public void SetHeader(string key, string value) => _headers[key] = value; + + public void SetCancelToken(CancellationToken cancelToken) { - Dispose(true); + _parentToken = cancelToken; + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token) + .Token; } - public async Task ConnectAsync(string host) + public async Task SendAsync(byte[] data, int index, int count, bool isText) { await _lock.WaitAsync().ConfigureAwait(false); try { - await ConnectInternalAsync(host).ConfigureAwait(false); + if (_client == null) return; + + var frameCount = (int)Math.Ceiling((double)count / SendChunkSize); + + for (var i = 0; i < frameCount; i++, index += SendChunkSize) + { + var isLast = i == frameCount - 1; + + int frameSize; + if (isLast) + frameSize = count - i * SendChunkSize; + else + frameSize = SendChunkSize; + + var type = isText ? WebSocketMessageType.Text : WebSocketMessageType.Binary; + await _client.SendAsync(new ArraySegment(data, index, count), type, isLast, _cancelToken) + .ConfigureAwait(false); + } } finally { _lock.Release(); } } + + private void Dispose(bool disposing) + { + if (_isDisposed) return; + if (disposing) + DisconnectInternalAsync(true).GetAwaiter().GetResult(); + _isDisposed = true; + } + private async Task ConnectInternalAsync(string host) { await DisconnectInternalAsync().ConfigureAwait(false); _cancelTokenSource = new CancellationTokenSource(); - _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token) + .Token; _client = new ClientWebSocket(); _client.Options.Proxy = _proxy; _client.Options.KeepAliveInterval = TimeSpan.Zero; foreach (var header in _headers) - { if (header.Value != null) _client.Options.SetRequestHeader(header.Key, header.Value); - } await _client.ConnectAsync(new Uri(host), _cancelToken).ConfigureAwait(false); _task = RunAsync(_cancelToken); } - public async Task DisconnectAsync() + private async Task DisconnectInternalAsync(bool isDisposing = false) { - await _lock.WaitAsync().ConfigureAwait(false); try { - await DisconnectInternalAsync().ConfigureAwait(false); + _cancelTokenSource.Cancel(false); } - finally + catch { - _lock.Release(); } - } - private async Task DisconnectInternalAsync(bool isDisposing = false) - { - try { _cancelTokenSource.Cancel(false); } catch { } _isDisconnecting = true; try @@ -106,21 +149,34 @@ namespace Discord.Net.WebSockets await (_task ?? Task.Delay(0)).ConfigureAwait(false); _task = null; } - finally { _isDisconnecting = false; } + finally + { + _isDisconnecting = false; + } if (_client != null) { if (!isDisposing) + try + { + await _client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", new CancellationToken()); + } + catch + { + } + + try + { + _client.Dispose(); + } + catch { - try { await _client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", new CancellationToken()); } - catch { } } - try { _client.Dispose(); } - catch { } - + _client = null; } } + private async Task OnClosed(Exception ex) { if (_isDisconnecting) @@ -129,54 +185,16 @@ namespace Discord.Net.WebSockets await _lock.WaitAsync().ConfigureAwait(false); try { - await DisconnectInternalAsync(false); + await DisconnectInternalAsync(); } finally { _lock.Release(); } - await Closed(ex); - } - public void SetHeader(string key, string value) - { - _headers[key] = value; - } - public void SetCancelToken(CancellationToken cancelToken) - { - _parentToken = cancelToken; - _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; + await Closed(ex); } - public async Task SendAsync(byte[] data, int index, int count, bool isText) - { - await _lock.WaitAsync().ConfigureAwait(false); - try - { - if (_client == null) return; - - int frameCount = (int)Math.Ceiling((double)count / SendChunkSize); - - for (int i = 0; i < frameCount; i++, index += SendChunkSize) - { - bool isLast = i == (frameCount - 1); - - int frameSize; - if (isLast) - frameSize = count - (i * SendChunkSize); - else - frameSize = SendChunkSize; - - var type = isText ? WebSocketMessageType.Text : WebSocketMessageType.Binary; - await _client.SendAsync(new ArraySegment(data, index, count), type, isLast, _cancelToken).ConfigureAwait(false); - } - } - finally - { - _lock.Release(); - } - } - private async Task RunAsync(CancellationToken cancelToken) { var buffer = new ArraySegment(new byte[ReceiveChunkSize]); @@ -185,16 +203,15 @@ namespace Discord.Net.WebSockets { while (!cancelToken.IsCancellationRequested) { - WebSocketReceiveResult socketResult = await _client.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false); + var socketResult = await _client.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false); byte[] result; int resultCount; - + if (socketResult.MessageType == WebSocketMessageType.Close) - throw new WebSocketClosedException((int)socketResult.CloseStatus, socketResult.CloseStatusDescription); + throw new WebSocketClosedException((int)socketResult.CloseStatus, + socketResult.CloseStatusDescription); if (!socketResult.EndOfMessage) - { - //This is a large message (likely just READY), lets create a temporary expandable stream using (var stream = new MemoryStream()) { stream.Write(buffer.Array, 0, socketResult.Count); @@ -203,26 +220,23 @@ namespace Discord.Net.WebSockets if (cancelToken.IsCancellationRequested) return; socketResult = await _client.ReceiveAsync(buffer, cancelToken).ConfigureAwait(false); stream.Write(buffer.Array, 0, socketResult.Count); - } - while (socketResult == null || !socketResult.EndOfMessage); + } while (!socketResult.EndOfMessage); //Use the internal buffer if we can get it resultCount = (int)stream.Length; result = stream.TryGetBuffer(out var streamBuffer) ? streamBuffer.Array : stream.ToArray(); - } - } else { //Small message resultCount = socketResult.Count; result = buffer.Array; } - + if (socketResult.MessageType == WebSocketMessageType.Text) { - string text = Encoding.UTF8.GetString(result, 0, resultCount); + var text = Encoding.UTF8.GetString(result, 0, resultCount); await TextMessage(text).ConfigureAwait(false); } else @@ -233,7 +247,9 @@ namespace Discord.Net.WebSockets { var _ = OnClosed(new Exception("Connection timed out.", ex)); } - catch (OperationCanceledException) { } + catch (OperationCanceledException) + { + } catch (Exception ex) { //This cannot be awaited otherwise we'll deadlock when DiscordApiClient waits for this task to complete. diff --git a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs index 2d66d5900..453dd0e8d 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClientProvider.cs @@ -7,19 +7,17 @@ namespace Discord.Net.WebSockets { public static readonly WebSocketProvider Instance = Create(); - public static WebSocketProvider Create(IWebProxy proxy = null) + public static WebSocketProvider Create(IWebProxy proxy = null) => () => { - return () => + try { - try - { - return new DefaultWebSocketClient(proxy); - } - catch (PlatformNotSupportedException ex) - { - throw new PlatformNotSupportedException("The default WebSocketProvider is not supported on this platform.", ex); - } - }; - } + return new DefaultWebSocketClient(proxy); + } + catch (PlatformNotSupportedException ex) + { + throw new PlatformNotSupportedException( + "The default WebSocketProvider is not supported on this platform.", ex); + } + }; } } diff --git a/src/Discord.Net.Webhook/AssemblyInfo.cs b/src/Discord.Net.Webhook/AssemblyInfo.cs index c6b5997b4..a18f8049b 100644 --- a/src/Discord.Net.Webhook/AssemblyInfo.cs +++ b/src/Discord.Net.Webhook/AssemblyInfo.cs @@ -1,3 +1,3 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Discord.Net.Tests")] \ No newline at end of file +[assembly: InternalsVisibleTo("Discord.Net.Tests")] diff --git a/src/Discord.Net.Webhook/DiscordWebhookClient.cs b/src/Discord.Net.Webhook/DiscordWebhookClient.cs index 67a5462be..d21ee12cb 100644 --- a/src/Discord.Net.Webhook/DiscordWebhookClient.cs +++ b/src/Discord.Net.Webhook/DiscordWebhookClient.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Threading.Tasks; +using Discord.API; using Discord.Logging; using Discord.Rest; @@ -9,22 +10,23 @@ namespace Discord.Webhook { public class DiscordWebhookClient : IDisposable { - public event Func Log { add { _logEvent.Add(value); } remove { _logEvent.Remove(value); } } - internal readonly AsyncEvent> _logEvent = new AsyncEvent>(); + internal readonly AsyncEvent> LogEvent = new AsyncEvent>(); + internal readonly Logger RestLogger; private readonly ulong _webhookId; internal IWebhook Webhook; - internal readonly Logger _restLogger; - - internal API.DiscordRestApiClient ApiClient { get; } - internal LogManager LogManager { get; } /// Creates a new Webhook discord client. public DiscordWebhookClient(IWebhook webhook) - : this(webhook.Id, webhook.Token, new DiscordRestConfig()) { } + : this(webhook.Id, webhook.Token, new DiscordRestConfig()) + { + } + /// Creates a new Webhook discord client. public DiscordWebhookClient(ulong webhookId, string webhookToken) - : this(webhookId, webhookToken, new DiscordRestConfig()) { } + : this(webhookId, webhookToken, new DiscordRestConfig()) + { + } /// Creates a new Webhook discord client. public DiscordWebhookClient(ulong webhookId, string webhookToken, DiscordRestConfig config) @@ -34,6 +36,7 @@ namespace Discord.Webhook ApiClient.LoginAsync(TokenType.Webhook, webhookToken).GetAwaiter().GetResult(); Webhook = WebhookClientHelper.GetWebhookAsync(this, webhookId).GetAwaiter().GetResult(); } + /// Creates a new Webhook discord client. public DiscordWebhookClient(IWebhook webhook, DiscordRestConfig config) : this(config) @@ -46,22 +49,36 @@ namespace Discord.Webhook { ApiClient = CreateApiClient(config); LogManager = new LogManager(config.LogLevel); - LogManager.Message += async msg => await _logEvent.InvokeAsync(msg).ConfigureAwait(false); + LogManager.Message += async msg => await LogEvent.InvokeAsync(msg).ConfigureAwait(false); - _restLogger = LogManager.CreateLogger("Rest"); + RestLogger = LogManager.CreateLogger("Rest"); ApiClient.RequestQueue.RateLimitTriggered += async (id, info) => { if (info == null) - await _restLogger.VerboseAsync($"Preemptive Rate limit triggered: {id ?? "null"}").ConfigureAwait(false); + await RestLogger.VerboseAsync($"Preemptive Rate limit triggered: {id ?? "null"}") + .ConfigureAwait(false); else - await _restLogger.WarningAsync($"Rate limit triggered: {id ?? "null"}").ConfigureAwait(false); + await RestLogger.WarningAsync($"Rate limit triggered: {id ?? "null"}").ConfigureAwait(false); }; - ApiClient.SentRequest += async (method, endpoint, millis) => await _restLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); + ApiClient.SentRequest += async (method, endpoint, millis) => + await RestLogger.VerboseAsync($"{method} {endpoint}: {millis} ms").ConfigureAwait(false); + } + + internal DiscordRestApiClient ApiClient { get; } + internal LogManager LogManager { get; } + + public void Dispose() => ApiClient?.Dispose(); + + public event Func Log + { + add => LogEvent.Add(value); + remove => LogEvent.Remove(value); } - private static API.DiscordRestApiClient CreateApiClient(DiscordRestConfig config) - => new API.DiscordRestApiClient(config.RestClientProvider, DiscordRestConfig.UserAgent); - + + private static DiscordRestApiClient CreateApiClient(DiscordRestConfig config) + => new DiscordRestApiClient(config.RestClientProvider, DiscordConfig.UserAgent); + /// Sends a message using to the channel for this webhook. Returns the ID of the created message. public Task SendMessageAsync(string text = null, bool isTTS = false, IEnumerable embeds = null, string username = null, string avatarUrl = null, RequestOptions options = null) @@ -69,13 +86,16 @@ namespace Discord.Webhook /// Send a message to the channel for this webhook with an attachment. Returns the ID of the created message. public Task SendFileAsync(string filePath, string text, bool isTTS = false, - IEnumerable embeds = null, string username = null, string avatarUrl = null, RequestOptions options = null) + IEnumerable embeds = null, string username = null, string avatarUrl = null, + RequestOptions options = null) => WebhookClientHelper.SendFileAsync(this, filePath, text, isTTS, embeds, username, avatarUrl, options); /// Send a message to the channel for this webhook with an attachment. Returns the ID of the created message. public Task SendFileAsync(Stream stream, string filename, string text, bool isTTS = false, - IEnumerable embeds = null, string username = null, string avatarUrl = null, RequestOptions options = null) - => WebhookClientHelper.SendFileAsync(this, stream, filename, text, isTTS, embeds, username, avatarUrl, options); + IEnumerable embeds = null, string username = null, string avatarUrl = null, + RequestOptions options = null) + => WebhookClientHelper.SendFileAsync(this, stream, filename, text, isTTS, embeds, username, avatarUrl, + options); /// Modifies the properties of this webhook. public Task ModifyWebhookAsync(Action func, RequestOptions options = null) @@ -87,10 +107,5 @@ namespace Discord.Webhook await Webhook.DeleteAsync(options).ConfigureAwait(false); Dispose(); } - - public void Dispose() - { - ApiClient?.Dispose(); - } } } diff --git a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs index cd35d731c..13b4e1df9 100644 --- a/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs +++ b/src/Discord.Net.Webhook/Entities/Webhooks/RestInternalWebhook.cs @@ -5,10 +5,20 @@ using Model = Discord.API.Webhook; namespace Discord.Webhook { - [DebuggerDisplay(@"{DebuggerDisplay,nq}")] + [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] internal class RestInternalWebhook : IWebhook { - private DiscordWebhookClient _client; + private readonly DiscordWebhookClient _client; + + internal RestInternalWebhook(DiscordWebhookClient apiClient, Model model) + { + _client = apiClient; + Id = model.Id; + ChannelId = model.Id; + Token = model.Token; + } + + private string DebuggerDisplay => $"Webhook: {Name} ({Id})"; public ulong Id { get; } public ulong ChannelId { get; } @@ -20,13 +30,22 @@ namespace Discord.Webhook public DateTimeOffset CreatedAt => SnowflakeUtils.FromSnowflake(Id); - internal RestInternalWebhook(DiscordWebhookClient apiClient, Model model) + public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) + => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); + + public async Task ModifyAsync(Action func, RequestOptions options = null) { - _client = apiClient; - Id = model.Id; - ChannelId = model.Id; - Token = model.Token; + var model = await WebhookClientHelper.ModifyAsync(_client, func, options); + Update(model); } + + public Task DeleteAsync(RequestOptions options = null) + => WebhookClientHelper.DeleteAsync(_client, options); + + IUser IWebhook.Creator => null; + ITextChannel IWebhook.Channel => null; + IGuild IWebhook.Guild => null; + internal static RestInternalWebhook Create(DiscordWebhookClient client, Model model) { var entity = new RestInternalWebhook(client, model); @@ -44,23 +63,6 @@ namespace Discord.Webhook Name = model.Name.Value; } - public string GetAvatarUrl(ImageFormat format = ImageFormat.Auto, ushort size = 128) - => CDN.GetUserAvatarUrl(Id, AvatarId, size, format); - - public async Task ModifyAsync(Action func, RequestOptions options = null) - { - var model = await WebhookClientHelper.ModifyAsync(_client, func, options); - Update(model); - } - - public Task DeleteAsync(RequestOptions options = null) - => WebhookClientHelper.DeleteAsync(_client, options); - public override string ToString() => $"Webhook: {Name}:{Id}"; - private string DebuggerDisplay => $"Webhook: {Name} ({Id})"; - - IUser IWebhook.Creator => null; - ITextChannel IWebhook.Channel => null; - IGuild IWebhook.Guild => null; } } diff --git a/src/Discord.Net.Webhook/WebhookClientHelper.cs b/src/Discord.Net.Webhook/WebhookClientHelper.cs index d3cac9703..ce88170b6 100644 --- a/src/Discord.Net.Webhook/WebhookClientHelper.cs +++ b/src/Discord.Net.Webhook/WebhookClientHelper.cs @@ -19,10 +19,12 @@ namespace Discord.Webhook throw new InvalidOperationException("Could not find a webhook for the supplied credentials."); return RestInternalWebhook.Create(client, model); } - public static async Task SendMessageAsync(DiscordWebhookClient client, - string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, RequestOptions options) + + public static async Task SendMessageAsync(DiscordWebhookClient client, + string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, + RequestOptions options) { - var args = new CreateWebhookMessageParams(text) { IsTTS = isTTS }; + var args = new CreateWebhookMessageParams(text) {IsTTS = isTTS}; if (embeds != null) args.Embeds = embeds.Select(x => x.ToModel()).ToArray(); if (username != null) @@ -30,27 +32,39 @@ namespace Discord.Webhook if (avatarUrl != null) args.AvatarUrl = avatarUrl; - var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options: options).ConfigureAwait(false); + var model = await client.ApiClient.CreateWebhookMessageAsync(client.Webhook.Id, args, options) + .ConfigureAwait(false); return model.Id; } - public static async Task SendFileAsync(DiscordWebhookClient client, string filePath, string text, bool isTTS, + + public static async Task SendFileAsync(DiscordWebhookClient client, string filePath, string text, + bool isTTS, IEnumerable embeds, string username, string avatarUrl, RequestOptions options) { - string filename = Path.GetFileName(filePath); + var filename = Path.GetFileName(filePath); using (var file = File.OpenRead(filePath)) - return await SendFileAsync(client, file, filename, text, isTTS, embeds, username, avatarUrl, options).ConfigureAwait(false); + return await SendFileAsync(client, file, filename, text, isTTS, embeds, username, avatarUrl, options) + .ConfigureAwait(false); } - public static async Task SendFileAsync(DiscordWebhookClient client, Stream stream, string filename, string text, bool isTTS, + + public static async Task SendFileAsync(DiscordWebhookClient client, Stream stream, string filename, + string text, bool isTTS, IEnumerable embeds, string username, string avatarUrl, RequestOptions options) { - var args = new UploadWebhookFileParams(stream) { Filename = filename, Content = text, IsTTS = isTTS }; + var args = new UploadWebhookFileParams(stream) + { + Filename = filename, + Content = text, + IsTTS = isTTS + }; if (username != null) args.Username = username; if (avatarUrl != null) args.AvatarUrl = avatarUrl; if (embeds != null) args.Embeds = embeds.Select(x => x.ToModel()).ToArray(); - var msg = await client.ApiClient.UploadWebhookFileAsync(client.Webhook.Id, args, options).ConfigureAwait(false); + var msg = await client.ApiClient.UploadWebhookFileAsync(client.Webhook.Id, args, options) + .ConfigureAwait(false); return msg.Id; } @@ -71,9 +85,7 @@ namespace Discord.Webhook return await client.ApiClient.ModifyWebhookAsync(client.Webhook.Id, apiArgs, options).ConfigureAwait(false); } - public static async Task DeleteAsync(DiscordWebhookClient client, RequestOptions options) - { + public static async Task DeleteAsync(DiscordWebhookClient client, RequestOptions options) => await client.ApiClient.DeleteWebhookAsync(client.Webhook.Id, options).ConfigureAwait(false); - } } } diff --git a/test/Discord.Net.Tests/AnalyzerTests/Extensions/AppDomainPolyfill.cs b/test/Discord.Net.Tests/AnalyzerTests/Extensions/AppDomainPolyfill.cs index 729bc385c..c482c75ae 100644 --- a/test/Discord.Net.Tests/AnalyzerTests/Extensions/AppDomainPolyfill.cs +++ b/test/Discord.Net.Tests/AnalyzerTests/Extensions/AppDomainPolyfill.cs @@ -8,17 +8,17 @@ namespace System /// Polyfill of the AppDomain class from full framework. internal class AppDomain { - public static AppDomain CurrentDomain { get; private set; } - - private AppDomain() + static AppDomain() { + CurrentDomain = new AppDomain(); } - static AppDomain() + private AppDomain() { - CurrentDomain = new AppDomain(); } + public static AppDomain CurrentDomain { get; } + public Assembly[] GetAssemblies() { var rid = RuntimeEnvironment.GetRuntimeIdentifier(); @@ -27,4 +27,4 @@ namespace System return ass.Select(xan => Assembly.Load(xan)).ToArray(); } } -} \ No newline at end of file +} diff --git a/test/Discord.Net.Tests/AnalyzerTests/GuildAccessTests.cs b/test/Discord.Net.Tests/AnalyzerTests/GuildAccessTests.cs index 073cc1de7..c73c96b91 100644 --- a/test/Discord.Net.Tests/AnalyzerTests/GuildAccessTests.cs +++ b/test/Discord.Net.Tests/AnalyzerTests/GuildAccessTests.cs @@ -1,24 +1,23 @@ using System; -using System.Collections.Generic; -using System.Text; -using System.Linq; -using System.Threading.Tasks; +using Discord.Analyzers; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -using Discord.Analyzers; using TestHelper; using Xunit; namespace Discord { - public partial class AnalyserTests + public class AnalyserTests { public class GuildAccessTests : DiagnosticVerifier { + protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() + => new GuildAccessAnalyzer(); + [Fact] public void VerifyDiagnosticWhenLackingRequireContext() { - string source = @"using System; + var source = @"using System; using System.Threading.Tasks; using Discord.Commands; @@ -30,11 +29,12 @@ namespace Test public Task TestCmd() => ReplyAsync(Context.Guild.Name); } }"; - var expected = new DiagnosticResult() + var expected = new DiagnosticResult { Id = "DNET0001", - Locations = new[] { new DiagnosticResultLocation("Test0.cs", line: 10, column: 45) }, - Message = "Command method 'TestCmd' is accessing 'Context.Guild' but is not restricted to Guild contexts.", + Locations = new[] {new DiagnosticResultLocation("Test0.cs", 10, 45)}, + Message = + "Command method 'TestCmd' is accessing 'Context.Guild' but is not restricted to Guild contexts.", Severity = DiagnosticSeverity.Warning }; VerifyCSharpDiagnostic(source, expected); @@ -43,7 +43,7 @@ namespace Test [Fact] public void VerifyDiagnosticWhenWrongRequireContext() { - string source = @"using System; + var source = @"using System; using System.Threading.Tasks; using Discord.Commands; @@ -55,28 +55,30 @@ namespace Test public Task TestCmd() => ReplyAsync(Context.Guild.Name); } }"; - var expected = new DiagnosticResult() + var expected = new DiagnosticResult { Id = "DNET0001", - Locations = new[] { new DiagnosticResultLocation("Test0.cs", line: 10, column: 45) }, - Message = "Command method 'TestCmd' is accessing 'Context.Guild' but is not restricted to Guild contexts.", + Locations = new[] {new DiagnosticResultLocation("Test0.cs", 10, 45)}, + Message = + "Command method 'TestCmd' is accessing 'Context.Guild' but is not restricted to Guild contexts.", Severity = DiagnosticSeverity.Warning }; VerifyCSharpDiagnostic(source, expected); } [Fact] - public void VerifyNoDiagnosticWhenRequireContextOnMethod() + public void VerifyNoDiagnosticWhenRequireContextOnClass() { - string source = @"using System; + var source = @"using System; using System.Threading.Tasks; using Discord.Commands; namespace Test { + [RequireContext(ContextType.Guild)] public class TestModule : ModuleBase { - [Command(""test""), RequireContext(ContextType.Guild)] + [Command(""test"")] public Task TestCmd() => ReplyAsync(Context.Guild.Name); } }"; @@ -85,27 +87,23 @@ namespace Test } [Fact] - public void VerifyNoDiagnosticWhenRequireContextOnClass() + public void VerifyNoDiagnosticWhenRequireContextOnMethod() { - string source = @"using System; + var source = @"using System; using System.Threading.Tasks; using Discord.Commands; namespace Test { - [RequireContext(ContextType.Guild)] public class TestModule : ModuleBase { - [Command(""test"")] + [Command(""test""), RequireContext(ContextType.Guild)] public Task TestCmd() => ReplyAsync(Context.Guild.Name); } }"; VerifyCSharpDiagnostic(source, Array.Empty()); } - - protected override DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() - => new GuildAccessAnalyzer(); } } } diff --git a/test/Discord.Net.Tests/AnalyzerTests/Helpers/CodeFixVerifier.Helper.cs b/test/Discord.Net.Tests/AnalyzerTests/Helpers/CodeFixVerifier.Helper.cs index 0f73d0643..91a1a9f46 100644 --- a/test/Discord.Net.Tests/AnalyzerTests/Helpers/CodeFixVerifier.Helper.cs +++ b/test/Discord.Net.Tests/AnalyzerTests/Helpers/CodeFixVerifier.Helper.cs @@ -1,22 +1,22 @@ -using Microsoft.CodeAnalysis; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.Simplification; -using System.Collections.Generic; -using System.Linq; -using System.Threading; namespace TestHelper { /// - /// Diagnostic Producer class with extra methods dealing with applying codefixes - /// All methods are static + /// Diagnostic Producer class with extra methods dealing with applying codefixes + /// All methods are static /// public abstract partial class CodeFixVerifier : DiagnosticVerifier { /// - /// Apply the inputted CodeAction to the inputted document. - /// Meant to be used to apply codefixes. + /// Apply the inputted CodeAction to the inputted document. + /// Meant to be used to apply codefixes. /// /// The Document to apply the fix on /// A CodeAction that will be applied to the Document. @@ -29,47 +29,44 @@ namespace TestHelper } /// - /// Compare two collections of Diagnostics,and return a list of any new diagnostics that appear only in the second collection. - /// Note: Considers Diagnostics to be the same if they have the same Ids. In the case of multiple diagnostics with the same Id in a row, - /// this method may not necessarily return the new one. + /// Compare two collections of Diagnostics,and return a list of any new diagnostics that appear only in the second + /// collection. + /// Note: Considers Diagnostics to be the same if they have the same Ids. In the case of multiple diagnostics with the + /// same Id in a row, + /// this method may not necessarily return the new one. /// /// The Diagnostics that existed in the code before the CodeFix was applied /// The Diagnostics that exist in the code after the CodeFix was applied /// A list of Diagnostics that only surfaced in the code after the CodeFix was applied - private static IEnumerable GetNewDiagnostics(IEnumerable diagnostics, IEnumerable newDiagnostics) + private static IEnumerable GetNewDiagnostics(IEnumerable diagnostics, + IEnumerable newDiagnostics) { var oldArray = diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); var newArray = newDiagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - int oldIndex = 0; - int newIndex = 0; + var oldIndex = 0; + var newIndex = 0; while (newIndex < newArray.Length) - { if (oldIndex < oldArray.Length && oldArray[oldIndex].Id == newArray[newIndex].Id) { ++oldIndex; ++newIndex; } else - { yield return newArray[newIndex++]; - } - } } /// - /// Get the existing compiler diagnostics on the inputted document. + /// Get the existing compiler diagnostics on the inputted document. /// /// The Document to run the compiler diagnostic analyzers on /// The compiler diagnostics that were found in the code - private static IEnumerable GetCompilerDiagnostics(Document document) - { - return document.GetSemanticModelAsync().Result.GetDiagnostics(); - } + private static IEnumerable GetCompilerDiagnostics(Document document) => + document.GetSemanticModelAsync().Result.GetDiagnostics(); /// - /// Given a document, turn it into a string based on the syntax root + /// Given a document, turn it into a string based on the syntax root /// /// The Document to be converted to a string /// A string containing the syntax of the Document after formatting @@ -82,4 +79,3 @@ namespace TestHelper } } } - diff --git a/test/Discord.Net.Tests/AnalyzerTests/Helpers/DiagnosticResult.cs b/test/Discord.Net.Tests/AnalyzerTests/Helpers/DiagnosticResult.cs index 5ae6f528e..7356461cc 100644 --- a/test/Discord.Net.Tests/AnalyzerTests/Helpers/DiagnosticResult.cs +++ b/test/Discord.Net.Tests/AnalyzerTests/Helpers/DiagnosticResult.cs @@ -1,28 +1,22 @@ -using Microsoft.CodeAnalysis; -using System; +using System; +using Microsoft.CodeAnalysis; namespace TestHelper { /// - /// Location where the diagnostic appears, as determined by path, line number, and column number. + /// Location where the diagnostic appears, as determined by path, line number, and column number. /// public struct DiagnosticResultLocation { public DiagnosticResultLocation(string path, int line, int column) { - if (line < -1) - { - throw new ArgumentOutOfRangeException(nameof(line), "line must be >= -1"); - } + if (line < -1) throw new ArgumentOutOfRangeException(nameof(line), "line must be >= -1"); - if (column < -1) - { - throw new ArgumentOutOfRangeException(nameof(column), "column must be >= -1"); - } + if (column < -1) throw new ArgumentOutOfRangeException(nameof(column), "column must be >= -1"); - this.Path = path; - this.Line = line; - this.Column = column; + Path = path; + Line = line; + Column = column; } public string Path { get; } @@ -31,7 +25,7 @@ namespace TestHelper } /// - /// Struct that stores information about a Diagnostic appearing in a source + /// Struct that stores information about a Diagnostic appearing in a source /// public struct DiagnosticResult { @@ -41,17 +35,11 @@ namespace TestHelper { get { - if (this.locations == null) - { - this.locations = new DiagnosticResultLocation[] { }; - } - return this.locations; + if (locations == null) locations = new DiagnosticResultLocation[] { }; + return locations; } - set - { - this.locations = value; - } + set => locations = value; } public DiagnosticSeverity Severity { get; set; } @@ -60,28 +48,10 @@ namespace TestHelper public string Message { get; set; } - public string Path - { - get - { - return this.Locations.Length > 0 ? this.Locations[0].Path : ""; - } - } + public string Path => Locations.Length > 0 ? Locations[0].Path : ""; - public int Line - { - get - { - return this.Locations.Length > 0 ? this.Locations[0].Line : -1; - } - } + public int Line => Locations.Length > 0 ? Locations[0].Line : -1; - public int Column - { - get - { - return this.Locations.Length > 0 ? this.Locations[0].Column : -1; - } - } + public int Column => Locations.Length > 0 ? Locations[0].Column : -1; } -} \ No newline at end of file +} diff --git a/test/Discord.Net.Tests/AnalyzerTests/Helpers/DiagnosticVerifier.Helper.cs b/test/Discord.Net.Tests/AnalyzerTests/Helpers/DiagnosticVerifier.Helper.cs index 7a8eb2e9c..4de366aa8 100644 --- a/test/Discord.Net.Tests/AnalyzerTests/Helpers/DiagnosticVerifier.Helper.cs +++ b/test/Discord.Net.Tests/AnalyzerTests/Helpers/DiagnosticVerifier.Helper.cs @@ -3,25 +3,32 @@ using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Reflection; +using Discord.Commands; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; -using Discord; -using Discord.Commands; namespace TestHelper { /// - /// Class for turning strings into documents and getting the diagnostics on them - /// All methods are static + /// Class for turning strings into documents and getting the diagnostics on them + /// All methods are static /// public abstract partial class DiagnosticVerifier { - private static readonly MetadataReference CorlibReference = MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location); - private static readonly MetadataReference SystemCoreReference = MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location); - private static readonly MetadataReference CSharpSymbolsReference = MetadataReference.CreateFromFile(typeof(CSharpCompilation).GetTypeInfo().Assembly.Location); - private static readonly MetadataReference CodeAnalysisReference = MetadataReference.CreateFromFile(typeof(Compilation).GetTypeInfo().Assembly.Location); + private static readonly MetadataReference CorlibReference = + MetadataReference.CreateFromFile(typeof(object).GetTypeInfo().Assembly.Location); + + private static readonly MetadataReference SystemCoreReference = + MetadataReference.CreateFromFile(typeof(Enumerable).GetTypeInfo().Assembly.Location); + + private static readonly MetadataReference CSharpSymbolsReference = + MetadataReference.CreateFromFile(typeof(CSharpCompilation).GetTypeInfo().Assembly.Location); + + private static readonly MetadataReference CodeAnalysisReference = + MetadataReference.CreateFromFile(typeof(Compilation).GetTypeInfo().Assembly.Location); + //private static readonly MetadataReference DiscordNetReference = MetadataReference.CreateFromFile(typeof(IDiscordClient).GetTypeInfo().Assembly.Location); //private static readonly MetadataReference DiscordCommandsReference = MetadataReference.CreateFromFile(typeof(CommandAttribute).GetTypeInfo().Assembly.Location); private static readonly Assembly DiscordCommandsAssembly = typeof(CommandAttribute).GetTypeInfo().Assembly; @@ -31,59 +38,76 @@ namespace TestHelper internal static string VisualBasicDefaultExt = "vb"; internal static string TestProjectName = "TestProject"; + /// + /// Get the for and all assemblies referenced by + /// + /// + /// The assembly. + /// s. + private static IEnumerable Transitive(Assembly assembly) + { + foreach (var a in RecursiveReferencedAssemblies(assembly)) + yield return MetadataReference.CreateFromFile(a.Location); + } + + private static HashSet RecursiveReferencedAssemblies(Assembly a, HashSet assemblies = null) + { + assemblies = assemblies ?? new HashSet(); + if (!assemblies.Add(a)) return assemblies; + foreach (var referencedAssemblyName in a.GetReferencedAssemblies()) + { + var referencedAssembly = AppDomain.CurrentDomain.GetAssemblies() + .SingleOrDefault(x => x.GetName() == referencedAssemblyName) ?? + Assembly.Load(referencedAssemblyName); + RecursiveReferencedAssemblies(referencedAssembly, assemblies); + } + + return assemblies; + } + #region Get Diagnostics /// - /// Given classes in the form of strings, their language, and an IDiagnosticAnlayzer to apply to it, return the diagnostics found in the string after converting it to a document. + /// Given classes in the form of strings, their language, and an IDiagnosticAnlayzer to apply to it, return the + /// diagnostics found in the string after converting it to a document. /// /// Classes in the form of strings /// The language the source classes are in /// The analyzer to be run on the sources /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location - private static Diagnostic[] GetSortedDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer) - { - return GetSortedDiagnosticsFromDocuments(analyzer, GetDocuments(sources, language)); - } + private static Diagnostic[] + GetSortedDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer) => + GetSortedDiagnosticsFromDocuments(analyzer, GetDocuments(sources, language)); /// - /// Given an analyzer and a document to apply it to, run the analyzer and gather an array of diagnostics found in it. - /// The returned diagnostics are then ordered by location in the source document. + /// Given an analyzer and a document to apply it to, run the analyzer and gather an array of diagnostics found in it. + /// The returned diagnostics are then ordered by location in the source document. /// /// The analyzer to run on the documents /// The Documents that the analyzer will be run on /// An IEnumerable of Diagnostics that surfaced in the source code, sorted by Location - protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, Document[] documents) + protected static Diagnostic[] GetSortedDiagnosticsFromDocuments(DiagnosticAnalyzer analyzer, + Document[] documents) { var projects = new HashSet(); - foreach (var document in documents) - { - projects.Add(document.Project); - } + foreach (var document in documents) projects.Add(document.Project); var diagnostics = new List(); foreach (var project in projects) { - var compilationWithAnalyzers = project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer)); + var compilationWithAnalyzers = + project.GetCompilationAsync().Result.WithAnalyzers(ImmutableArray.Create(analyzer)); var diags = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().Result; foreach (var diag in diags) - { if (diag.Location == Location.None || diag.Location.IsInMetadata) - { diagnostics.Add(diag); - } else - { - for (int i = 0; i < documents.Length; i++) + for (var i = 0; i < documents.Length; i++) { var document = documents[i]; var tree = document.GetSyntaxTreeAsync().Result; - if (tree == diag.Location.SourceTree) - { - diagnostics.Add(diag); - } + if (tree == diag.Location.SourceTree) diagnostics.Add(diag); } - } - } } var results = SortDiagnostics(diagnostics); @@ -92,20 +116,20 @@ namespace TestHelper } /// - /// Sort diagnostics by location in source document + /// Sort diagnostics by location in source document /// /// The list of Diagnostics to be sorted /// An IEnumerable containing the Diagnostics in order of Location - private static Diagnostic[] SortDiagnostics(IEnumerable diagnostics) - { - return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - } + private static Diagnostic[] SortDiagnostics(IEnumerable diagnostics) => + diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); #endregion #region Set up compilation and documents + /// - /// Given an array of strings as sources and a language, turn them into a project and return the documents and spans of it. + /// Given an array of strings as sources and a language, turn them into a project and return the documents and spans of + /// it. /// /// Classes in the form of strings /// The language the source code is in @@ -113,44 +137,38 @@ namespace TestHelper private static Document[] GetDocuments(string[] sources, string language) { if (language != LanguageNames.CSharp && language != LanguageNames.VisualBasic) - { throw new ArgumentException("Unsupported Language"); - } var project = CreateProject(sources, language); var documents = project.Documents.ToArray(); if (sources.Length != documents.Length) - { throw new Exception("Amount of sources did not match amount of Documents created"); - } return documents; } /// - /// Create a Document from a string through creating a project that contains it. + /// Create a Document from a string through creating a project that contains it. /// /// Classes in the form of a string /// The language the source code is in /// A Document created from the source string - protected static Document CreateDocument(string source, string language = LanguageNames.CSharp) - { - return CreateProject(new[] { source }, language).Documents.First(); - } + protected static Document CreateDocument(string source, string language = LanguageNames.CSharp) => + CreateProject(new[] {source}, language).Documents.First(); /// - /// Create a project using the inputted strings as sources. + /// Create a project using the inputted strings as sources. /// /// Classes in the form of strings /// The language the source code is in /// A Project created out of the Documents created from the source strings private static Project CreateProject(string[] sources, string language = LanguageNames.CSharp) { - string fileNamePrefix = DefaultFilePathPrefix; - string fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt; + var fileNamePrefix = DefaultFilePathPrefix; + var fileExt = language == LanguageNames.CSharp ? CSharpDefaultFileExt : VisualBasicDefaultExt; - var projectId = ProjectId.CreateNewId(debugName: TestProjectName); + var projectId = ProjectId.CreateNewId(TestProjectName); var solution = new AdhocWorkspace() .CurrentSolution @@ -161,47 +179,18 @@ namespace TestHelper .AddMetadataReference(projectId, CodeAnalysisReference) .AddMetadataReferences(projectId, Transitive(DiscordCommandsAssembly)); - int count = 0; + var count = 0; foreach (var source in sources) { var newFileName = fileNamePrefix + count + "." + fileExt; - var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); + var documentId = DocumentId.CreateNewId(projectId, newFileName); solution = solution.AddDocument(documentId, newFileName, SourceText.From(source)); count++; } - return solution.GetProject(projectId); - } - #endregion - /// - /// Get the for and all assemblies referenced by - /// - /// The assembly. - /// s. - private static IEnumerable Transitive(Assembly assembly) - { - foreach (var a in RecursiveReferencedAssemblies(assembly)) - { - yield return MetadataReference.CreateFromFile(a.Location); - } + return solution.GetProject(projectId); } - private static HashSet RecursiveReferencedAssemblies(Assembly a, HashSet assemblies = null) - { - assemblies = assemblies ?? new HashSet(); - if (assemblies.Add(a)) - { - foreach (var referencedAssemblyName in a.GetReferencedAssemblies()) - { - var referencedAssembly = AppDomain.CurrentDomain.GetAssemblies() - .SingleOrDefault(x => x.GetName() == referencedAssemblyName) ?? - Assembly.Load(referencedAssemblyName); - RecursiveReferencedAssemblies(referencedAssembly, assemblies); - } - } - - return assemblies; - } + #endregion } } - diff --git a/test/Discord.Net.Tests/AnalyzerTests/Verifiers/CodeFixVerifier.cs b/test/Discord.Net.Tests/AnalyzerTests/Verifiers/CodeFixVerifier.cs index 5d057b610..426d67a65 100644 --- a/test/Discord.Net.Tests/AnalyzerTests/Verifiers/CodeFixVerifier.cs +++ b/test/Discord.Net.Tests/AnalyzerTests/Verifiers/CodeFixVerifier.cs @@ -1,69 +1,70 @@ -using Microsoft.CodeAnalysis; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Formatting; -//using Microsoft.VisualStudio.TestTools.UnitTesting; -using System.Collections.Generic; -using System.Linq; -using System.Threading; using Xunit; +//using Microsoft.VisualStudio.TestTools.UnitTesting; + namespace TestHelper { /// - /// Superclass of all Unit tests made for diagnostics with codefixes. - /// Contains methods used to verify correctness of codefixes + /// Superclass of all Unit tests made for diagnostics with codefixes. + /// Contains methods used to verify correctness of codefixes /// public abstract partial class CodeFixVerifier : DiagnosticVerifier { /// - /// Returns the codefix being tested (C#) - to be implemented in non-abstract class + /// Returns the codefix being tested (C#) - to be implemented in non-abstract class /// /// The CodeFixProvider to be used for CSharp code - protected virtual CodeFixProvider GetCSharpCodeFixProvider() - { - return null; - } + protected virtual CodeFixProvider GetCSharpCodeFixProvider() => null; /// - /// Returns the codefix being tested (VB) - to be implemented in non-abstract class + /// Returns the codefix being tested (VB) - to be implemented in non-abstract class /// /// The CodeFixProvider to be used for VisualBasic code - protected virtual CodeFixProvider GetBasicCodeFixProvider() - { - return null; - } + protected virtual CodeFixProvider GetBasicCodeFixProvider() => null; /// - /// Called to test a C# codefix when applied on the inputted string as a source + /// Called to test a C# codefix when applied on the inputted string as a source /// /// A class in the form of a string before the CodeFix was applied to it /// A class in the form of a string after the CodeFix was applied to it /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied - protected void VerifyCSharpFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) - { - VerifyFix(LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), GetCSharpCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); - } + /// + /// A bool controlling whether or not the test will fail if the CodeFix + /// introduces other warnings after being applied + /// + protected void VerifyCSharpFix(string oldSource, string newSource, int? codeFixIndex = null, + bool allowNewCompilerDiagnostics = false) => VerifyFix(LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), + GetCSharpCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); /// - /// Called to test a VB codefix when applied on the inputted string as a source + /// Called to test a VB codefix when applied on the inputted string as a source /// /// A class in the form of a string before the CodeFix was applied to it /// A class in the form of a string after the CodeFix was applied to it /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied - protected void VerifyBasicFix(string oldSource, string newSource, int? codeFixIndex = null, bool allowNewCompilerDiagnostics = false) - { - VerifyFix(LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), GetBasicCodeFixProvider(), oldSource, newSource, codeFixIndex, allowNewCompilerDiagnostics); - } + /// + /// A bool controlling whether or not the test will fail if the CodeFix + /// introduces other warnings after being applied + /// + protected void VerifyBasicFix(string oldSource, string newSource, int? codeFixIndex = null, + bool allowNewCompilerDiagnostics = false) => VerifyFix(LanguageNames.VisualBasic, + GetBasicDiagnosticAnalyzer(), GetBasicCodeFixProvider(), oldSource, newSource, codeFixIndex, + allowNewCompilerDiagnostics); /// - /// General verifier for codefixes. - /// Creates a Document from the source string, then gets diagnostics on it and applies the relevant codefixes. - /// Then gets the string after the codefix is applied and compares it with the expected result. - /// Note: If any codefix causes new diagnostics to show up, the test fails unless allowNewCompilerDiagnostics is set to true. + /// General verifier for codefixes. + /// Creates a Document from the source string, then gets diagnostics on it and applies the relevant codefixes. + /// Then gets the string after the codefix is applied and compares it with the expected result. + /// Note: If any codefix causes new diagnostics to show up, the test fails unless allowNewCompilerDiagnostics is set to + /// true. /// /// The language the source code is in /// The analyzer to be applied to the source code @@ -71,24 +72,26 @@ namespace TestHelper /// A class in the form of a string before the CodeFix was applied to it /// A class in the form of a string after the CodeFix was applied to it /// Index determining which codefix to apply if there are multiple - /// A bool controlling whether or not the test will fail if the CodeFix introduces other warnings after being applied - private void VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, string oldSource, string newSource, int? codeFixIndex, bool allowNewCompilerDiagnostics) + /// + /// A bool controlling whether or not the test will fail if the CodeFix + /// introduces other warnings after being applied + /// + private void VerifyFix(string language, DiagnosticAnalyzer analyzer, CodeFixProvider codeFixProvider, + string oldSource, string newSource, int? codeFixIndex, bool allowNewCompilerDiagnostics) { var document = CreateDocument(oldSource, language); - var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); + var analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] {document}); var compilerDiagnostics = GetCompilerDiagnostics(document); var attempts = analyzerDiagnostics.Length; - for (int i = 0; i < attempts; ++i) + for (var i = 0; i < attempts; ++i) { var actions = new List(); - var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), CancellationToken.None); + var context = new CodeFixContext(document, analyzerDiagnostics[0], (a, d) => actions.Add(a), + CancellationToken.None); codeFixProvider.RegisterCodeFixesAsync(context).Wait(); - if (!actions.Any()) - { - break; - } + if (!actions.Any()) break; if (codeFixIndex != null) { @@ -97,7 +100,7 @@ namespace TestHelper } document = ApplyFix(document, actions.ElementAt(0)); - analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] { document }); + analyzerDiagnostics = GetSortedDiagnosticsFromDocuments(analyzer, new[] {document}); var newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); @@ -105,7 +108,8 @@ namespace TestHelper if (!allowNewCompilerDiagnostics && newCompilerDiagnostics.Any()) { // Format and get the compiler diagnostics again so that the locations make sense in the output - document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync().Result, Formatter.Annotation, document.Project.Solution.Workspace)); + document = document.WithSyntaxRoot(Formatter.Format(document.GetSyntaxRootAsync().Result, + Formatter.Annotation, document.Project.Solution.Workspace)); newCompilerDiagnostics = GetNewDiagnostics(compilerDiagnostics, GetCompilerDiagnostics(document)); Assert.True(false, @@ -115,10 +119,7 @@ namespace TestHelper } //check if there are analyzer diagnostics left after the code fix - if (!analyzerDiagnostics.Any()) - { - break; - } + if (!analyzerDiagnostics.Any()) break; } //after applying all of the code fixes, compare the resulting string to the inputted one @@ -126,4 +127,4 @@ namespace TestHelper Assert.Equal(newSource, actual); } } -} \ No newline at end of file +} diff --git a/test/Discord.Net.Tests/AnalyzerTests/Verifiers/DiagnosticVerifier.cs b/test/Discord.Net.Tests/AnalyzerTests/Verifiers/DiagnosticVerifier.cs index 498e5ef27..2da3670a2 100644 --- a/test/Discord.Net.Tests/AnalyzerTests/Verifiers/DiagnosticVerifier.cs +++ b/test/Discord.Net.Tests/AnalyzerTests/Verifiers/DiagnosticVerifier.cs @@ -2,91 +2,134 @@ using System.Linq; using System.Text; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; -//using Microsoft.VisualStudio.TestTools.UnitTesting; using Xunit; +//using Microsoft.VisualStudio.TestTools.UnitTesting; + namespace TestHelper { /// - /// Superclass of all Unit Tests for DiagnosticAnalyzers + /// Superclass of all Unit Tests for DiagnosticAnalyzers /// public abstract partial class DiagnosticVerifier { - #region To be implemented by Test classes + #region Formatting Diagnostics + /// - /// Get the CSharp analyzer being tested - to be implemented in non-abstract class + /// Helper method to format a Diagnostic into an easily readable string /// - protected virtual DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() + /// The analyzer that this verifier tests + /// The Diagnostics to be formatted + /// The Diagnostics formatted as a string + private static string FormatDiagnostics(DiagnosticAnalyzer analyzer, params Diagnostic[] diagnostics) { - return null; + var builder = new StringBuilder(); + for (var i = 0; i < diagnostics.Length; ++i) + { + builder.AppendLine("// " + diagnostics[i]); + + var analyzerType = analyzer.GetType(); + var rules = analyzer.SupportedDiagnostics; + + foreach (var rule in rules) + if (rule != null && rule.Id == diagnostics[i].Id) + { + var location = diagnostics[i].Location; + if (location == Location.None) + builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, rule.Id); + else + { + Assert.True(location.IsInSource, + $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics[i]}\r\n"); + + var resultMethodName = diagnostics[i].Location.SourceTree.FilePath.EndsWith(".cs") + ? "GetCSharpResultAt" + : "GetBasicResultAt"; + var linePosition = diagnostics[i].Location.GetLineSpan().StartLinePosition; + + builder.AppendFormat("{0}({1}, {2}, {3}.{4})", + resultMethodName, + linePosition.Line + 1, + linePosition.Character + 1, + analyzerType.Name, + rule.Id); + } + + if (i != diagnostics.Length - 1) builder.Append(','); + + builder.AppendLine(); + break; + } + } + + return builder.ToString(); } + #endregion + + #region To be implemented by Test classes + /// - /// Get the Visual Basic analyzer being tested (C#) - to be implemented in non-abstract class + /// Get the CSharp analyzer being tested - to be implemented in non-abstract class /// - protected virtual DiagnosticAnalyzer GetBasicDiagnosticAnalyzer() - { - return null; - } + protected virtual DiagnosticAnalyzer GetCSharpDiagnosticAnalyzer() => null; + + /// + /// Get the Visual Basic analyzer being tested (C#) - to be implemented in non-abstract class + /// + protected virtual DiagnosticAnalyzer GetBasicDiagnosticAnalyzer() => null; + #endregion #region Verifier wrappers /// - /// Called to test a C# DiagnosticAnalyzer when applied on the single inputted string as a source - /// Note: input a DiagnosticResult for each Diagnostic expected + /// Called to test a C# DiagnosticAnalyzer when applied on the single inputted string as a source + /// Note: input a DiagnosticResult for each Diagnostic expected /// /// A class in the form of a string to run the analyzer on /// DiagnosticResults that should appear after the analyzer is run on the source - protected void VerifyCSharpDiagnostic(string source, params DiagnosticResult[] expected) - { - VerifyDiagnostics(new[] { source }, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); - } + protected void VerifyCSharpDiagnostic(string source, params DiagnosticResult[] expected) => + VerifyDiagnostics(new[] {source}, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); /// - /// Called to test a VB DiagnosticAnalyzer when applied on the single inputted string as a source - /// Note: input a DiagnosticResult for each Diagnostic expected + /// Called to test a VB DiagnosticAnalyzer when applied on the single inputted string as a source + /// Note: input a DiagnosticResult for each Diagnostic expected /// /// A class in the form of a string to run the analyzer on /// DiagnosticResults that should appear after the analyzer is run on the source - protected void VerifyBasicDiagnostic(string source, params DiagnosticResult[] expected) - { - VerifyDiagnostics(new[] { source }, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); - } + protected void VerifyBasicDiagnostic(string source, params DiagnosticResult[] expected) => + VerifyDiagnostics(new[] {source}, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); /// - /// Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as a source - /// Note: input a DiagnosticResult for each Diagnostic expected + /// Called to test a C# DiagnosticAnalyzer when applied on the inputted strings as a source + /// Note: input a DiagnosticResult for each Diagnostic expected /// /// An array of strings to create source documents from to run the analyzers on /// DiagnosticResults that should appear after the analyzer is run on the sources - protected void VerifyCSharpDiagnostic(string[] sources, params DiagnosticResult[] expected) - { + protected void VerifyCSharpDiagnostic(string[] sources, params DiagnosticResult[] expected) => VerifyDiagnostics(sources, LanguageNames.CSharp, GetCSharpDiagnosticAnalyzer(), expected); - } /// - /// Called to test a VB DiagnosticAnalyzer when applied on the inputted strings as a source - /// Note: input a DiagnosticResult for each Diagnostic expected + /// Called to test a VB DiagnosticAnalyzer when applied on the inputted strings as a source + /// Note: input a DiagnosticResult for each Diagnostic expected /// /// An array of strings to create source documents from to run the analyzers on /// DiagnosticResults that should appear after the analyzer is run on the sources - protected void VerifyBasicDiagnostic(string[] sources, params DiagnosticResult[] expected) - { + protected void VerifyBasicDiagnostic(string[] sources, params DiagnosticResult[] expected) => VerifyDiagnostics(sources, LanguageNames.VisualBasic, GetBasicDiagnosticAnalyzer(), expected); - } /// - /// General method that gets a collection of actual diagnostics found in the source after the analyzer is run, - /// then verifies each of them. + /// General method that gets a collection of actual diagnostics found in the source after the analyzer is run, + /// then verifies each of them. /// /// An array of strings to create source documents from to run the analyzers on /// The language of the classes represented by the source strings /// The analyzer to be run on the source code /// DiagnosticResults that should appear after the analyzer is run on the sources - private void VerifyDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expected) + private void VerifyDiagnostics(string[] sources, string language, DiagnosticAnalyzer analyzer, + params DiagnosticResult[] expected) { var diagnostics = GetSortedDiagnostics(sources, language, analyzer); VerifyDiagnosticResults(diagnostics, analyzer, expected); @@ -95,27 +138,35 @@ namespace TestHelper #endregion #region Actual comparisons and verifications + /// - /// Checks each of the actual Diagnostics found and compares them with the corresponding DiagnosticResult in the array of expected results. - /// Diagnostics are considered equal only if the DiagnosticResultLocation, Id, Severity, and Message of the DiagnosticResult match the actual diagnostic. + /// Checks each of the actual Diagnostics found and compares them with the corresponding DiagnosticResult in the array + /// of expected results. + /// Diagnostics are considered equal only if the DiagnosticResultLocation, Id, Severity, and Message of the + /// DiagnosticResult match the actual diagnostic. /// /// The Diagnostics found by the compiler after running the analyzer on the source code /// The analyzer that was being run on the sources /// Diagnostic Results that should have appeared in the code - private static void VerifyDiagnosticResults(IEnumerable actualResults, DiagnosticAnalyzer analyzer, params DiagnosticResult[] expectedResults) + private static void VerifyDiagnosticResults(IEnumerable actualResults, DiagnosticAnalyzer analyzer, + params DiagnosticResult[] expectedResults) { - int expectedCount = expectedResults.Count(); - int actualCount = actualResults.Count(); + var expectedCount = expectedResults.Length; + var actualCount = actualResults.Count(); if (expectedCount != actualCount) { - string diagnosticsOutput = actualResults.Any() ? FormatDiagnostics(analyzer, actualResults.ToArray()) : " NONE."; + var diagnosticsOutput = actualResults.Any() + ? FormatDiagnostics(analyzer, actualResults.ToArray()) + : " NONE."; Assert.True(false, - string.Format("Mismatch between number of diagnostics returned, expected \"{0}\" actual \"{1}\"\r\n\r\nDiagnostics:\r\n{2}\r\n", expectedCount, actualCount, diagnosticsOutput)); + string.Format( + "Mismatch between number of diagnostics returned, expected \"{0}\" actual \"{1}\"\r\n\r\nDiagnostics:\r\n{2}\r\n", + expectedCount, actualCount, diagnosticsOutput)); } - for (int i = 0; i < expectedResults.Length; i++) + for (var i = 0; i < expectedResults.Length; i++) { var actual = actualResults.ElementAt(i); var expected = expectedResults[i]; @@ -123,11 +174,9 @@ namespace TestHelper if (expected.Line == -1 && expected.Column == -1) { if (actual.Location != Location.None) - { Assert.True(false, string.Format("Expected:\nA project diagnostic with No location\nActual:\n{0}", - FormatDiagnostics(analyzer, actual))); - } + FormatDiagnostics(analyzer, actual))); } else { @@ -135,137 +184,76 @@ namespace TestHelper var additionalLocations = actual.AdditionalLocations.ToArray(); if (additionalLocations.Length != expected.Locations.Length - 1) - { Assert.True(false, - string.Format("Expected {0} additional locations but got {1} for Diagnostic:\r\n {2}\r\n", + string.Format( + "Expected {0} additional locations but got {1} for Diagnostic:\r\n {2}\r\n", expected.Locations.Length - 1, additionalLocations.Length, FormatDiagnostics(analyzer, actual))); - } - for (int j = 0; j < additionalLocations.Length; ++j) - { + for (var j = 0; j < additionalLocations.Length; ++j) VerifyDiagnosticLocation(analyzer, actual, additionalLocations[j], expected.Locations[j + 1]); - } } if (actual.Id != expected.Id) - { Assert.True(false, - string.Format("Expected diagnostic id to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", + string.Format( + "Expected diagnostic id to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", expected.Id, actual.Id, FormatDiagnostics(analyzer, actual))); - } if (actual.Severity != expected.Severity) - { Assert.True(false, - string.Format("Expected diagnostic severity to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", + string.Format( + "Expected diagnostic severity to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", expected.Severity, actual.Severity, FormatDiagnostics(analyzer, actual))); - } if (actual.GetMessage() != expected.Message) - { Assert.True(false, - string.Format("Expected diagnostic message to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", + string.Format( + "Expected diagnostic message to be \"{0}\" was \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", expected.Message, actual.GetMessage(), FormatDiagnostics(analyzer, actual))); - } } } /// - /// Helper method to VerifyDiagnosticResult that checks the location of a diagnostic and compares it with the location in the expected DiagnosticResult. + /// Helper method to VerifyDiagnosticResult that checks the location of a diagnostic and compares it with the location + /// in the expected DiagnosticResult. /// /// The analyzer that was being run on the sources /// The diagnostic that was found in the code /// The Location of the Diagnostic found in the code /// The DiagnosticResultLocation that should have been found - private static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, Location actual, DiagnosticResultLocation expected) + private static void VerifyDiagnosticLocation(DiagnosticAnalyzer analyzer, Diagnostic diagnostic, + Location actual, DiagnosticResultLocation expected) { var actualSpan = actual.GetLineSpan(); - Assert.True(actualSpan.Path == expected.Path || (actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && expected.Path.Contains("Test.")), - string.Format("Expected diagnostic to be in file \"{0}\" was actually in file \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", + Assert.True( + actualSpan.Path == expected.Path || actualSpan.Path != null && actualSpan.Path.Contains("Test0.") && + expected.Path.Contains("Test."), + string.Format( + "Expected diagnostic to be in file \"{0}\" was actually in file \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", expected.Path, actualSpan.Path, FormatDiagnostics(analyzer, diagnostic))); var actualLinePosition = actualSpan.StartLinePosition; // Only check line position if there is an actual line in the real diagnostic if (actualLinePosition.Line > 0) - { if (actualLinePosition.Line + 1 != expected.Line) - { Assert.True(false, - string.Format("Expected diagnostic to be on line \"{0}\" was actually on line \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", + string.Format( + "Expected diagnostic to be on line \"{0}\" was actually on line \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", expected.Line, actualLinePosition.Line + 1, FormatDiagnostics(analyzer, diagnostic))); - } - } // Only check column position if there is an actual column position in the real diagnostic - if (actualLinePosition.Character > 0) - { - if (actualLinePosition.Character + 1 != expected.Column) - { - Assert.True(false, - string.Format("Expected diagnostic to start at column \"{0}\" was actually at column \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", - expected.Column, actualLinePosition.Character + 1, FormatDiagnostics(analyzer, diagnostic))); - } - } + if (actualLinePosition.Character <= 0) return; + if (actualLinePosition.Character + 1 != expected.Column) + Assert.True(false, + string.Format( + "Expected diagnostic to start at column \"{0}\" was actually at column \"{1}\"\r\n\r\nDiagnostic:\r\n {2}\r\n", + expected.Column, actualLinePosition.Character + 1, + FormatDiagnostics(analyzer, diagnostic))); } - #endregion - #region Formatting Diagnostics - /// - /// Helper method to format a Diagnostic into an easily readable string - /// - /// The analyzer that this verifier tests - /// The Diagnostics to be formatted - /// The Diagnostics formatted as a string - private static string FormatDiagnostics(DiagnosticAnalyzer analyzer, params Diagnostic[] diagnostics) - { - var builder = new StringBuilder(); - for (int i = 0; i < diagnostics.Length; ++i) - { - builder.AppendLine("// " + diagnostics[i].ToString()); - - var analyzerType = analyzer.GetType(); - var rules = analyzer.SupportedDiagnostics; - - foreach (var rule in rules) - { - if (rule != null && rule.Id == diagnostics[i].Id) - { - var location = diagnostics[i].Location; - if (location == Location.None) - { - builder.AppendFormat("GetGlobalResult({0}.{1})", analyzerType.Name, rule.Id); - } - else - { - Assert.True(location.IsInSource, - $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostics[i]}\r\n"); - - string resultMethodName = diagnostics[i].Location.SourceTree.FilePath.EndsWith(".cs") ? "GetCSharpResultAt" : "GetBasicResultAt"; - var linePosition = diagnostics[i].Location.GetLineSpan().StartLinePosition; - - builder.AppendFormat("{0}({1}, {2}, {3}.{4})", - resultMethodName, - linePosition.Line + 1, - linePosition.Character + 1, - analyzerType.Name, - rule.Id); - } - - if (i != diagnostics.Length - 1) - { - builder.Append(','); - } - - builder.AppendLine(); - break; - } - } - } - return builder.ToString(); - } #endregion } } diff --git a/test/Discord.Net.Tests/Net/CacheInfo.cs b/test/Discord.Net.Tests/Net/CacheInfo.cs index ed2820b8e..0027fd2d6 100644 --- a/test/Discord.Net.Tests/Net/CacheInfo.cs +++ b/test/Discord.Net.Tests/Net/CacheInfo.cs @@ -4,9 +4,8 @@ namespace Discord.Net { internal class CacheInfo { - [JsonProperty("guild_id")] - public ulong? GuildId { get; set; } - [JsonProperty("version")] - public uint Version { get; set; } + [JsonProperty("guild_id")] public ulong? GuildId { get; set; } + + [JsonProperty("version")] public uint Version { get; set; } } -} \ No newline at end of file +} diff --git a/test/Discord.Net.Tests/Net/CachedRestClient.cs b/test/Discord.Net.Tests/Net/CachedRestClient.cs index 4bc8a386a..e82f04419 100644 --- a/test/Discord.Net.Tests/Net/CachedRestClient.cs +++ b/test/Discord.Net.Tests/Net/CachedRestClient.cs @@ -1,6 +1,3 @@ -using Akavache; -using Akavache.Sqlite3; -using Discord.Net.Rest; using System; using System.Collections.Generic; using System.IO; @@ -9,6 +6,9 @@ using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Threading; using System.Threading.Tasks; +using Akavache; +using Akavache.Sqlite3; +using Discord.Net.Rest; using Splat; namespace Discord.Net @@ -16,14 +16,12 @@ namespace Discord.Net internal class CachedRestClient : IRestClient { private readonly Dictionary _headers; - private IBlobCache _blobCache; private string _baseUrl; - private CancellationTokenSource _cancelTokenSource; + private readonly IBlobCache _blobCache; private CancellationToken _cancelToken, _parentToken; + private readonly CancellationTokenSource _cancelTokenSource; private bool _isDisposed; - public CacheInfo Info { get; private set; } - public CachedRestClient() { _headers = new Dictionary(); @@ -33,68 +31,63 @@ namespace Discord.Net _parentToken = CancellationToken.None; Locator.CurrentMutable.Register(() => Scheduler.Default, typeof(IScheduler), "Taskpool"); - Locator.CurrentMutable.Register(() => new FilesystemProvider(), typeof(IFilesystemProvider), null); - Locator.CurrentMutable.Register(() => new HttpMixin(), typeof(IAkavacheHttpMixin), null); + Locator.CurrentMutable.Register(() => new FilesystemProvider(), typeof(IFilesystemProvider)); + Locator.CurrentMutable.Register(() => new HttpMixin(), typeof(IAkavacheHttpMixin)); //new Akavache.Sqlite3.Registrations().Register(Locator.CurrentMutable); _blobCache = new SQLitePersistentBlobCache("cache.db"); } - private void Dispose(bool disposing) - { - if (!_isDisposed) - { - if (disposing) - _blobCache.Dispose(); - _isDisposed = true; - } - } - public void Dispose() - { - Dispose(true); - } - public void SetUrl(string url) - { - _baseUrl = url; - } - public void SetHeader(string key, string value) - { - _headers[key] = value; - } + public CacheInfo Info { get; private set; } + + public void SetHeader(string key, string value) => _headers[key] = value; + public void SetCancelToken(CancellationToken cancelToken) { _parentToken = cancelToken; - _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token).Token; + _cancelToken = CancellationTokenSource.CreateLinkedTokenSource(_parentToken, _cancelTokenSource.Token) + .Token; } - public async Task SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly, string reason = null) + public async Task SendAsync(string method, string endpoint, CancellationToken cancelToken, + bool headerOnly, string reason = null) { if (method != "GET") throw new InvalidOperationException("This RestClient only supports GET requests."); - - string uri = Path.Combine(_baseUrl, endpoint); + + var uri = Path.Combine(_baseUrl, endpoint); var bytes = await _blobCache.DownloadUrl(uri, _headers); return new RestResponse(HttpStatusCode.OK, _headers, new MemoryStream(bytes)); } - public Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly, string reason = null) - { + + public Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, + bool headerOnly, string reason = null) => throw new InvalidOperationException("This RestClient does not support payloads."); - } - public Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly, string reason = null) - { + + public Task SendAsync(string method, string endpoint, + IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly, + string reason = null) => throw new InvalidOperationException("This RestClient does not support multipart requests."); - } - public async Task ClearAsync() + private void Dispose(bool disposing) { - await _blobCache.InvalidateAll(); + if (_isDisposed) return; + if (disposing) + _blobCache.Dispose(); + _isDisposed = true; } + public void Dispose() => Dispose(true); + + public void SetUrl(string url) => _baseUrl = url; + + public async Task ClearAsync() => await _blobCache.InvalidateAll(); + public async Task LoadInfoAsync(ulong guildId) { if (Info != null) return; - - bool needsReset = false; + + var needsReset = false; try { Info = await _blobCache.GetObject("info"); @@ -105,16 +98,22 @@ namespace Discord.Net { needsReset = true; } + if (needsReset) { - Info = new CacheInfo() { GuildId = guildId, Version = 0 }; + Info = new CacheInfo + { + GuildId = guildId, + Version = 0 + }; await SaveInfoAsync().ConfigureAwait(false); } } + public async Task SaveInfoAsync() { await ClearAsync().ConfigureAwait(false); //Version changed, invalidate cache - await _blobCache.InsertObject("info", Info); + await _blobCache.InsertObject("info", Info); } } -} \ No newline at end of file +} diff --git a/test/Discord.Net.Tests/Net/FilesystemProvider.cs b/test/Discord.Net.Tests/Net/FilesystemProvider.cs index ae1b9a301..0ff7b073d 100644 --- a/test/Discord.Net.Tests/Net/FilesystemProvider.cs +++ b/test/Discord.Net.Tests/Net/FilesystemProvider.cs @@ -2,7 +2,6 @@ //Copyright (c) 2012 GitHub //TODO: Remove once netstandard support is added -using Akavache; using System; using System.Collections.Generic; using System.Diagnostics; @@ -13,20 +12,17 @@ using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Subjects; using System.Reflection; +using Akavache; namespace Discord { public class FilesystemProvider : IFilesystemProvider { - public IObservable OpenFileForReadAsync(string path, IScheduler scheduler) - { - return SafeOpenFileAsync(path, FileMode.Open, FileAccess.Read, FileShare.Read, scheduler); - } + public IObservable OpenFileForReadAsync(string path, IScheduler scheduler) => + SafeOpenFileAsync(path, FileMode.Open, FileAccess.Read, FileShare.Read, scheduler); - public IObservable OpenFileForWriteAsync(string path, IScheduler scheduler) - { - return SafeOpenFileAsync(path, FileMode.Create, FileAccess.Write, FileShare.None, scheduler); - } + public IObservable OpenFileForWriteAsync(string path, IScheduler scheduler) => + SafeOpenFileAsync(path, FileMode.Create, FileAccess.Write, FileShare.None, scheduler); public IObservable CreateRecursive(string path) { @@ -34,25 +30,13 @@ namespace Discord return Observable.Return(Unit.Default); } - public IObservable Delete(string path) - { - return Observable.Start(() => File.Delete(path), Scheduler.Default); - } - - public string GetDefaultRoamingCacheDirectory() - { - throw new NotSupportedException(); - } + public IObservable Delete(string path) => Observable.Start(() => File.Delete(path), Scheduler.Default); - public string GetDefaultSecretCacheDirectory() - { - throw new NotSupportedException(); - } + public string GetDefaultRoamingCacheDirectory() => throw new NotSupportedException(); - public string GetDefaultLocalMachineCacheDirectory() - { - throw new NotSupportedException(); - } + public string GetDefaultSecretCacheDirectory() => throw new NotSupportedException(); + + public string GetDefaultLocalMachineCacheDirectory() => throw new NotSupportedException(); protected static string GetAssemblyDirectoryName() { @@ -61,7 +45,8 @@ namespace Discord return assemblyDirectoryName; } - private static IObservable SafeOpenFileAsync(string path, FileMode mode, FileAccess access, FileShare share, IScheduler scheduler = null) + private static IObservable SafeOpenFileAsync(string path, FileMode mode, FileAccess access, + FileShare share, IScheduler scheduler = null) { scheduler = scheduler ?? Scheduler.Default; var ret = new AsyncSubject(); @@ -74,7 +59,7 @@ namespace Discord { FileMode.Create, FileMode.CreateNew, - FileMode.OpenOrCreate, + FileMode.OpenOrCreate }; @@ -88,7 +73,8 @@ namespace Discord return; } - Observable.Start(() => new FileStream(path, mode, access, share, 4096, false), scheduler).Cast().Subscribe(ret); + Observable.Start(() => new FileStream(path, mode, access, share, 4096, false), scheduler) + .Cast().Subscribe(ret); } catch (Exception ex) { @@ -98,16 +84,14 @@ namespace Discord return ret; } - private static void CreateRecursive(DirectoryInfo info) + + private static void CreateRecursive(DirectoryInfo info) => SplitFullPath(info).Aggregate((parent, dir) => { - SplitFullPath(info).Aggregate((parent, dir) => - { - var path = Path.Combine(parent, dir); - if (!Directory.Exists(path)) - Directory.CreateDirectory(path); - return path; - }); - } + var path = Path.Combine(parent, dir); + if (!Directory.Exists(path)) + Directory.CreateDirectory(path); + return path; + }); private static IEnumerable SplitFullPath(DirectoryInfo info) { @@ -116,13 +100,14 @@ namespace Discord for (var path = info.FullName; path != root && path != null; path = Path.GetDirectoryName(path)) { var filename = Path.GetFileName(path); - if (String.IsNullOrEmpty(filename)) + if (string.IsNullOrEmpty(filename)) continue; components.Add(filename); } + components.Add(root); components.Reverse(); return components; } } -} \ No newline at end of file +} diff --git a/test/Discord.Net.Tests/Net/HttpMixin.cs b/test/Discord.Net.Tests/Net/HttpMixin.cs index c4a78ce0b..f76417ca1 100644 --- a/test/Discord.Net.Tests/Net/HttpMixin.cs +++ b/test/Discord.Net.Tests/Net/HttpMixin.cs @@ -4,79 +4,73 @@ #pragma warning disable CS0618 -using Akavache; using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; -using System.Linq; using System.Net; +using System.Reactive; using System.Reactive.Linq; using System.Reactive.Subjects; -using System.Text; -using System.Reactive; using System.Reactive.Threading.Tasks; +using System.Text; +using Akavache; namespace Discord.Net { public class HttpMixin : IAkavacheHttpMixin { /// - /// Download data from an HTTP URL and insert the result into the - /// cache. If the data is already in the cache, this returns - /// a cached value. The URL itself is used as the key. + /// Download data from an HTTP URL and insert the result into the + /// cache. If the data is already in the cache, this returns + /// a cached value. The URL itself is used as the key. /// /// The URL to download. - /// An optional Dictionary containing the HTTP - /// request headers. + /// + /// An optional Dictionary containing the HTTP + /// request headers. + /// /// Force a web request to always be issued, skipping the cache. /// An optional expiration date. /// The data downloaded from the URL. - public IObservable DownloadUrl(IBlobCache This, string url, IDictionary headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) - { - return This.DownloadUrl(url, url, headers, fetchAlways, absoluteExpiration); - } + public IObservable DownloadUrl(IBlobCache This, string url, IDictionary headers = null, + bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) => + This.DownloadUrl(url, url, headers, fetchAlways, absoluteExpiration); /// - /// Download data from an HTTP URL and insert the result into the - /// cache. If the data is already in the cache, this returns - /// a cached value. An explicit key is provided rather than the URL itself. + /// Download data from an HTTP URL and insert the result into the + /// cache. If the data is already in the cache, this returns + /// a cached value. An explicit key is provided rather than the URL itself. /// /// The key to store with. /// The URL to download. - /// An optional Dictionary containing the HTTP - /// request headers. + /// + /// An optional Dictionary containing the HTTP + /// request headers. + /// /// Force a web request to always be issued, skipping the cache. /// An optional expiration date. /// The data downloaded from the URL. - public IObservable DownloadUrl(IBlobCache This, string key, string url, IDictionary headers = null, bool fetchAlways = false, DateTimeOffset? absoluteExpiration = null) + public IObservable DownloadUrl(IBlobCache This, string key, string url, + IDictionary headers = null, bool fetchAlways = false, + DateTimeOffset? absoluteExpiration = null) { - var doFetch = MakeWebRequest(new Uri(url), headers).SelectMany(x => ProcessWebResponse(x, url, absoluteExpiration)); + var doFetch = MakeWebRequest(new Uri(url), headers) + .SelectMany(x => ProcessWebResponse(x, url, absoluteExpiration)); var fetchAndCache = doFetch.SelectMany(x => This.Insert(key, x, absoluteExpiration).Select(_ => x)); - var ret = default(IObservable); - if (!fetchAlways) - { - ret = This.Get(key).Catch(fetchAndCache); - } - else - { - ret = fetchAndCache; - } + var ret = !fetchAlways ? This.Get(key).Catch(fetchAndCache) : fetchAndCache; var conn = ret.PublishLast(); conn.Connect(); return conn; } - IObservable ProcessWebResponse(WebResponse wr, string url, DateTimeOffset? absoluteExpiration) + private IObservable ProcessWebResponse(WebResponse wr, string url, DateTimeOffset? absoluteExpiration) { var hwr = (HttpWebResponse)wr; Debug.Assert(hwr != null, "The Web Response is somehow null but shouldn't be."); - if ((int)hwr.StatusCode >= 400) - { - return Observable.Throw(new WebException(hwr.StatusDescription)); - } + if ((int)hwr.StatusCode >= 400) return Observable.Throw(new WebException(hwr.StatusDescription)); var ms = new MemoryStream(); using (var responseStream = hwr.GetResponseStream()) @@ -89,32 +83,30 @@ namespace Discord.Net return Observable.Return(ret); } - static IObservable MakeWebRequest( + private static IObservable MakeWebRequest( Uri uri, IDictionary headers = null, string content = null, int retries = 3, TimeSpan? timeout = null) { - IObservable request; - - request = Observable.Defer(() => + var request = Observable.Defer(() => { var hwr = CreateWebRequest(uri, headers); if (content == null) - return Observable.FromAsyncPattern(hwr.BeginGetResponse, hwr.EndGetResponse)(); + return Observable.FromAsyncPattern(hwr.BeginGetResponse, hwr.EndGetResponse)(); var buf = Encoding.UTF8.GetBytes(content); - // NB: You'd think that BeginGetResponse would never block, + // NB: You'd think that BeginGetResponse would never block, // seeing as how it's asynchronous. You'd be wrong :-/ var ret = new AsyncSubject(); Observable.Start(() => { - Observable.FromAsyncPattern(hwr.BeginGetRequestStream, hwr.EndGetRequestStream)() + Observable.FromAsyncPattern(hwr.BeginGetRequestStream, hwr.EndGetRequestStream)() .SelectMany(x => WriteAsyncRx(x, buf, 0, buf.Length)) - .SelectMany(_ => Observable.FromAsyncPattern(hwr.BeginGetResponse, hwr.EndGetResponse)()) + .SelectMany(_ => Observable.FromAsyncPattern(hwr.BeginGetResponse, hwr.EndGetResponse)()) .Multicast(ret).Connect(); }, BlobCache.TaskpoolScheduler); @@ -127,19 +119,13 @@ namespace Discord.Net private static WebRequest CreateWebRequest(Uri uri, IDictionary headers) { var hwr = WebRequest.Create(uri); - if (headers != null) - { - foreach (var x in headers) - { - hwr.Headers[x.Key] = x.Value; - } - } + if (headers == null) return hwr; + foreach (var x in headers) + hwr.Headers[x.Key] = x.Value; return hwr; } - private static IObservable WriteAsyncRx(Stream stream, byte[] data, int start, int length) - { - return stream.WriteAsync(data, start, length).ToObservable(); - } + private static IObservable WriteAsyncRx(Stream stream, byte[] data, int start, int length) => + stream.WriteAsync(data, start, length).ToObservable(); } -} \ No newline at end of file +} diff --git a/test/Discord.Net.Tests/TestConfig.cs b/test/Discord.Net.Tests/TestConfig.cs index bdab13ea7..0a52a45f2 100644 --- a/test/Discord.Net.Tests/TestConfig.cs +++ b/test/Discord.Net.Tests/TestConfig.cs @@ -1,33 +1,27 @@ -using Newtonsoft.Json; -using System.IO; using System; +using System.IO; +using Newtonsoft.Json; namespace Discord { internal class TestConfig { - [JsonProperty("token")] - public string Token { get; private set; } - [JsonProperty("guild_id")] - public ulong GuildId { get; private set; } - + [JsonProperty("token")] public string Token { get; private set; } + + [JsonProperty("guild_id")] public ulong GuildId { get; private set; } + public static TestConfig LoadFile(string path) { - if (File.Exists(path)) - { - using (var stream = new FileStream(path, FileMode.Open)) - using (var reader = new StreamReader(stream)) - using (var jsonReader = new JsonTextReader(reader)) - return new JsonSerializer().Deserialize(jsonReader); - } - else - { - return new TestConfig() + if (!File.Exists(path)) + return new TestConfig { Token = Environment.GetEnvironmentVariable("DNET_TEST_TOKEN"), GuildId = ulong.Parse(Environment.GetEnvironmentVariable("DNET_TEST_GUILDID")) }; - } + using (var stream = new FileStream(path, FileMode.Open)) + using (var reader = new StreamReader(stream)) + using (var jsonReader = new JsonTextReader(reader)) + return new JsonSerializer().Deserialize(jsonReader); } } -} \ No newline at end of file +} diff --git a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs index dd87c2e24..94518ffce 100644 --- a/test/Discord.Net.Tests/Tests.ChannelPermissions.cs +++ b/test/Discord.Net.Tests/Tests.ChannelPermissions.cs @@ -47,25 +47,25 @@ namespace Discord Assert.Equal((ulong)0, ChannelPermissions.None.RawValue); // for text channels - ulong textChannel = (ulong)( ChannelPermission.CreateInstantInvite - | ChannelPermission.ManageChannels - | ChannelPermission.AddReactions - | ChannelPermission.ViewChannel - | ChannelPermission.SendMessages - | ChannelPermission.SendTTSMessages - | ChannelPermission.ManageMessages - | ChannelPermission.EmbedLinks - | ChannelPermission.AttachFiles - | ChannelPermission.ReadMessageHistory - | ChannelPermission.MentionEveryone - | ChannelPermission.UseExternalEmojis - | ChannelPermission.ManageRoles - | ChannelPermission.ManageWebhooks); + var textChannel = (ulong)(ChannelPermission.CreateInstantInvite + | ChannelPermission.ManageChannels + | ChannelPermission.AddReactions + | ChannelPermission.ViewChannel + | ChannelPermission.SendMessages + | ChannelPermission.SendTTSMessages + | ChannelPermission.ManageMessages + | ChannelPermission.EmbedLinks + | ChannelPermission.AttachFiles + | ChannelPermission.ReadMessageHistory + | ChannelPermission.MentionEveryone + | ChannelPermission.UseExternalEmojis + | ChannelPermission.ManageRoles + | ChannelPermission.ManageWebhooks); Assert.Equal(textChannel, ChannelPermissions.Text.RawValue); // voice channels - ulong voiceChannel = (ulong)( + var voiceChannel = (ulong)( ChannelPermission.CreateInstantInvite | ChannelPermission.ManageChannels | ChannelPermission.ViewChannel @@ -80,7 +80,7 @@ namespace Discord Assert.Equal(voiceChannel, ChannelPermissions.Voice.RawValue); // DM Channels - ulong dmChannel = (ulong)( + var dmChannel = (ulong)( ChannelPermission.ViewChannel | ChannelPermission.SendMessages | ChannelPermission.EmbedLinks @@ -90,11 +90,11 @@ namespace Discord | ChannelPermission.Connect | ChannelPermission.Speak | ChannelPermission.UseVAD - ); + ); Assert.Equal(dmChannel, ChannelPermissions.DM.RawValue); // group channel - ulong groupChannel = (ulong)( + var groupChannel = (ulong)( ChannelPermission.SendMessages | ChannelPermission.EmbedLinks | ChannelPermission.AttachFiles @@ -102,10 +102,11 @@ namespace Discord | ChannelPermission.Connect | ChannelPermission.Speak | ChannelPermission.UseVAD - ); + ); Assert.Equal(groupChannel, ChannelPermissions.Group.RawValue); return Task.CompletedTask; } + [Fact] public Task TestChannelPermissionModify() { @@ -117,12 +118,12 @@ namespace Discord Assert.False(perm.CreateInstantInvite); // ensure that when modified it works - perm = perm.Modify(createInstantInvite: true); + perm = perm.Modify(true); Assert.True(perm.CreateInstantInvite); Assert.Equal((ulong)ChannelPermission.CreateInstantInvite, perm.RawValue); // set false again, move on to next permission - perm = perm.Modify(createInstantInvite: false); + perm = perm.Modify(false); Assert.False(perm.CreateInstantInvite); Assert.Equal(ChannelPermissions.None.RawValue, perm.RawValue); @@ -340,9 +341,8 @@ namespace Discord [Fact] public Task TestChannelTypeResolution() { - ITextChannel someChannel = null; // null channels will throw exception - Assert.Throws(() => ChannelPermissions.All(someChannel)); + Assert.Throws(() => ChannelPermissions.All(null)); return Task.CompletedTask; } } diff --git a/test/Discord.Net.Tests/Tests.Channels.cs b/test/Discord.Net.Tests/Tests.Channels.cs index 46e28b9da..7ee08f856 100644 --- a/test/Discord.Net.Tests/Tests.Channels.cs +++ b/test/Discord.Net.Tests/Tests.Channels.cs @@ -1,7 +1,6 @@ -using Discord.Rest; -using System; using System.Linq; using System.Threading.Tasks; +using Discord.Rest; using Xunit; namespace Discord @@ -19,11 +18,7 @@ namespace Discord // create a channel category var cat1 = await guild.CreateCategoryChannelAsync("cat1"); - if (text1 == null) - { - // the guild did not have a default channel, so make a new one - text1 = await guild.CreateTextChannelAsync("default"); - } + if (text1 == null) text1 = await guild.CreateTextChannelAsync("default"); //Modify #general await text1.ModifyAsync(x => @@ -39,26 +34,17 @@ namespace Discord x.Position = 2; x.CategoryId = cat1.Id; }); - await text3.ModifyAsync(x => - { - x.Topic = "Topic2"; - }); + await text3.ModifyAsync(x => { x.Topic = "Topic2"; }); await text4.ModifyAsync(x => { x.Position = 3; x.Topic = "Topic2"; }); - await text5.ModifyAsync(x => - { - }); + await text5.ModifyAsync(x => { }); CheckTextChannels(guild, text1, text2, text3, text4, text5); } - [Fact] - public async Task TestTextChannels() - { - CheckTextChannels(_guild, (await _guild.GetTextChannelsAsync()).ToArray()); - } + private static void CheckTextChannels(RestGuild guild, params RestTextChannel[] textChannels) { Assert.Equal(5, textChannels.Length); @@ -109,10 +95,7 @@ namespace Discord x.Position = 1; x.CategoryId = cat2.Id; }); - await voice2.ModifyAsync(x => - { - x.UserLimit = null; - }); + await voice2.ModifyAsync(x => { x.UserLimit = null; }); await voice3.ModifyAsync(x => { x.Bitrate = 8000; @@ -123,11 +106,7 @@ namespace Discord CheckVoiceChannels(voice1, voice2, voice3); } - [Fact] - public async Task TestVoiceChannels() - { - CheckVoiceChannels((await _guild.GetVoiceChannelsAsync()).ToArray()); - } + private static void CheckVoiceChannels(params RestVoiceChannel[] voiceChannels) { Assert.Equal(3, voiceChannels.Length); @@ -157,15 +136,6 @@ namespace Discord Assert.Equal(16, voice3.UserLimit); } - [Fact] - public async Task TestChannelCategories() - { - // (await _guild.GetVoiceChannelsAsync()).ToArray() - var channels = await _guild.GetCategoryChannelsAsync(); - - await CheckChannelCategories(channels.ToArray(), (await _guild.GetChannelsAsync()).ToArray()); - } - private async Task CheckChannelCategories(RestCategoryChannel[] categories, RestGuildChannel[] allChannels) { // 2 categories @@ -212,7 +182,22 @@ namespace Discord var voice3Cat = await voice3.GetCategoryAsync(); Assert.Equal(voice3Cat.Id, cat2.Id); Assert.Equal(voice3Cat.Name, cat2.Name); + } + + [Fact] + public async Task TestChannelCategories() + { + // (await _guild.GetVoiceChannelsAsync()).ToArray() + var channels = await _guild.GetCategoryChannelsAsync(); + await CheckChannelCategories(channels.ToArray(), (await _guild.GetChannelsAsync()).ToArray()); } + + [Fact] + public async Task TestTextChannels() => + CheckTextChannels(_guild, (await _guild.GetTextChannelsAsync()).ToArray()); + + [Fact] + public async Task TestVoiceChannels() => CheckVoiceChannels((await _guild.GetVoiceChannelsAsync()).ToArray()); } } diff --git a/test/Discord.Net.Tests/Tests.Colors.cs b/test/Discord.Net.Tests/Tests.Colors.cs index 10b0bbdac..939b6517f 100644 --- a/test/Discord.Net.Tests/Tests.Colors.cs +++ b/test/Discord.Net.Tests/Tests.Colors.cs @@ -6,12 +6,8 @@ namespace Discord public class ColorTests { [Fact] - public void Color_New() - { - Assert.Equal(0u, new Color().RawValue); - Assert.Equal(uint.MinValue, new Color(uint.MinValue).RawValue); - Assert.Equal(uint.MaxValue, new Color(uint.MaxValue).RawValue); - } + public void Color_Blue() => Assert.Equal(0x90, new Color(0xAF1390).B); + [Fact] public void Color_Default() { @@ -20,6 +16,7 @@ namespace Discord Assert.Equal(0, Color.Default.G); Assert.Equal(0, Color.Default.B); } + [Fact] public void Color_FromRgb_Byte() { @@ -28,26 +25,7 @@ namespace Discord Assert.Equal(0x0000FFu, new Color((byte)0, (byte)0, (byte)255).RawValue); Assert.Equal(0xFFFFFFu, new Color((byte)255, (byte)255, (byte)255).RawValue); } - [Fact] - public void Color_FromRgb_Int() - { - Assert.Equal(0xFF0000u, new Color(255, 0, 0).RawValue); - Assert.Equal(0x00FF00u, new Color(0, 255, 0).RawValue); - Assert.Equal(0x0000FFu, new Color(0, 0, 255).RawValue); - Assert.Equal(0xFFFFFFu, new Color(255, 255, 255).RawValue); - } - [Fact] - public void Color_FromRgb_Int_OutOfRange() - { - Assert.Throws("r", () => new Color(-1024, 0, 0)); - Assert.Throws("r", () => new Color(1024, 0, 0)); - Assert.Throws("g", () => new Color(0, -1024, 0)); - Assert.Throws("g", () => new Color(0, 1024, 0)); - Assert.Throws("b", () => new Color(0, 0, -1024)); - Assert.Throws("b", () => new Color(0, 0, 1024)); - Assert.Throws(() => new Color(-1024, -1024, -1024)); - Assert.Throws(() => new Color(1024, 1024, 1024)); - } + [Fact] public void Color_FromRgb_Float() { @@ -56,6 +34,7 @@ namespace Discord Assert.Equal(0x0000FFu, new Color(0, 0, 1.0f).RawValue); Assert.Equal(0xFFFFFFu, new Color(1.0f, 1.0f, 1.0f).RawValue); } + [Fact] public void Color_FromRgb_Float_OutOfRange() { @@ -68,20 +47,41 @@ namespace Discord Assert.Throws(() => new Color(-2.0f, -2.0f, -2.0f)); Assert.Throws(() => new Color(2.0f, 2.0f, 2.0f)); } + [Fact] - public void Color_Red() + public void Color_FromRgb_Int() { - Assert.Equal(0xAF, new Color(0xAF1390).R); + Assert.Equal(0xFF0000u, new Color(255, 0, 0).RawValue); + Assert.Equal(0x00FF00u, new Color(0, 255, 0).RawValue); + Assert.Equal(0x0000FFu, new Color(0, 0, 255).RawValue); + Assert.Equal(0xFFFFFFu, new Color(255, 255, 255).RawValue); } + [Fact] - public void Color_Green() + public void Color_FromRgb_Int_OutOfRange() { - Assert.Equal(0x13, new Color(0xAF1390).G); + Assert.Throws("r", () => new Color(-1024, 0, 0)); + Assert.Throws("r", () => new Color(1024, 0, 0)); + Assert.Throws("g", () => new Color(0, -1024, 0)); + Assert.Throws("g", () => new Color(0, 1024, 0)); + Assert.Throws("b", () => new Color(0, 0, -1024)); + Assert.Throws("b", () => new Color(0, 0, 1024)); + Assert.Throws(() => new Color(-1024, -1024, -1024)); + Assert.Throws(() => new Color(1024, 1024, 1024)); } + + [Fact] + public void Color_Green() => Assert.Equal(0x13, new Color(0xAF1390).G); + [Fact] - public void Color_Blue() + public void Color_New() { - Assert.Equal(0x90, new Color(0xAF1390).B); + Assert.Equal(0u, new Color().RawValue); + Assert.Equal(uint.MinValue, new Color(uint.MinValue).RawValue); + Assert.Equal(uint.MaxValue, new Color(uint.MaxValue).RawValue); } + + [Fact] + public void Color_Red() => Assert.Equal(0xAF, new Color(0xAF1390).R); } } diff --git a/test/Discord.Net.Tests/Tests.Emotes.cs b/test/Discord.Net.Tests/Tests.Emotes.cs index eeadbddf8..a9ca01045 100644 --- a/test/Discord.Net.Tests/Tests.Emotes.cs +++ b/test/Discord.Net.Tests/Tests.Emotes.cs @@ -6,34 +6,29 @@ namespace Discord public class EmoteTests { [Fact] - public void Test_Emote_Parse() + public void Test_Animated_Emote_Parse() { - Assert.True(Emote.TryParse("<:typingstatus:394207658351263745>", out Emote emote)); + Assert.True(Emote.TryParse("", out var emote)); Assert.NotNull(emote); Assert.Equal("typingstatus", emote.Name); Assert.Equal(394207658351263745UL, emote.Id); - Assert.False(emote.Animated); + Assert.True(emote.Animated); Assert.Equal(DateTimeOffset.FromUnixTimeMilliseconds(1514056829775), emote.CreatedAt); - Assert.EndsWith("png", emote.Url); - } - [Fact] - public void Test_Invalid_Emote_Parse() - { - Assert.False(Emote.TryParse("invalid", out _)); - Assert.False(Emote.TryParse("<:typingstatus:not_a_number>", out _)); - Assert.Throws(() => Emote.Parse("invalid")); + Assert.EndsWith("gif", emote.Url); } + [Fact] - public void Test_Animated_Emote_Parse() + public void Test_Emote_Parse() { - Assert.True(Emote.TryParse("", out Emote emote)); + Assert.True(Emote.TryParse("<:typingstatus:394207658351263745>", out var emote)); Assert.NotNull(emote); Assert.Equal("typingstatus", emote.Name); Assert.Equal(394207658351263745UL, emote.Id); - Assert.True(emote.Animated); + Assert.False(emote.Animated); Assert.Equal(DateTimeOffset.FromUnixTimeMilliseconds(1514056829775), emote.CreatedAt); - Assert.EndsWith("gif", emote.Url); + Assert.EndsWith("png", emote.Url); } + [Fact] public void Test_Invalid_Amimated_Emote_Parse() { @@ -41,5 +36,13 @@ namespace Discord Assert.False(Emote.TryParse("", out _)); Assert.False(Emote.TryParse("", out _)); } + + [Fact] + public void Test_Invalid_Emote_Parse() + { + Assert.False(Emote.TryParse("invalid", out _)); + Assert.False(Emote.TryParse("<:typingstatus:not_a_number>", out _)); + Assert.Throws(() => Emote.Parse("invalid")); + } } } diff --git a/test/Discord.Net.Tests/Tests.GuildPermissions.cs b/test/Discord.Net.Tests/Tests.GuildPermissions.cs index bbd6621b5..0c677399e 100644 --- a/test/Discord.Net.Tests/Tests.GuildPermissions.cs +++ b/test/Discord.Net.Tests/Tests.GuildPermissions.cs @@ -44,11 +44,7 @@ namespace Discord .Distinct() .ToArray(); // test GuildPermissions.All - ulong sumOfAllGuildPermissions = 0; - foreach(var v in enumValues) - { - sumOfAllGuildPermissions |= (ulong)v; - } + var sumOfAllGuildPermissions = enumValues.Aggregate(0, (current, v) => current | (ulong)v); // assert that the raw values match Assert.Equal(sumOfAllGuildPermissions, GuildPermissions.All.RawValue); @@ -59,7 +55,7 @@ namespace Discord Assert.Equal(enumValues.Length, GuildPermissions.All.ToList().Count); // assert that webhook has the same raw value - ulong webHookPermissions = (ulong)( + var webHookPermissions = (ulong)( GuildPermission.SendMessages | GuildPermission.SendTTSMessages | GuildPermission.EmbedLinks | GuildPermission.AttachFiles); Assert.Equal(webHookPermissions, GuildPermissions.Webhook.RawValue); @@ -79,12 +75,12 @@ namespace Discord Assert.False(perm.CreateInstantInvite); // ensure that when we modify it the parameter works - perm = perm.Modify(createInstantInvite: true); + perm = perm.Modify(true); Assert.True(perm.CreateInstantInvite); Assert.Equal((ulong)GuildPermission.CreateInstantInvite, perm.RawValue); // set it false again, then move on to the next permission - perm = perm.Modify(createInstantInvite: false); + perm = perm.Modify(false); Assert.False(perm.CreateInstantInvite); Assert.Equal(GuildPermissions.None.RawValue, perm.RawValue); @@ -319,6 +315,5 @@ namespace Discord return Task.CompletedTask; } - } } diff --git a/test/Discord.Net.Tests/Tests.Migrations.cs b/test/Discord.Net.Tests/Tests.Migrations.cs index 23e55a737..6689f9f66 100644 --- a/test/Discord.Net.Tests/Tests.Migrations.cs +++ b/test/Discord.Net.Tests/Tests.Migrations.cs @@ -23,7 +23,7 @@ namespace Discord guild = await client.GetGuildAsync(_config.GuildId); } - uint nextVer = _cache.Info.Version + 1; + var nextVer = _cache.Info.Version + 1; try { await DoMigrateAsync(client, guild, nextVer).ConfigureAwait(false); @@ -54,19 +54,15 @@ namespace Discord var textChannels = await guild.GetTextChannelsAsync(); var voiceChannels = await guild.GetVoiceChannelsAsync(); var roles = guild.Roles; - + foreach (var channel in textChannels) - { //if (channel.Id != guild.DefaultChannelId) - await channel.DeleteAsync(); - } + await channel.DeleteAsync(); foreach (var channel in voiceChannels) await channel.DeleteAsync(); foreach (var role in roles) - { if (role.Id != guild.EveryoneRole.Id) await role.DeleteAsync(); - } } } -} \ No newline at end of file +} diff --git a/test/Discord.Net.Tests/Tests.Permissions.cs b/test/Discord.Net.Tests/Tests.Permissions.cs index 2f72f272d..a7f9e7efd 100644 --- a/test/Discord.Net.Tests/Tests.Permissions.cs +++ b/test/Discord.Net.Tests/Tests.Permissions.cs @@ -1,4 +1,3 @@ -using System; using System.Threading.Tasks; using Xunit; @@ -13,8 +12,8 @@ namespace Discord => TestHelper(value.RawValue, (ulong)permission, expected); /// - /// Tests the flag of the given permissions value to the expected output - /// and then tries to toggle the flag on and off + /// Tests the flag of the given permissions value to the expected output + /// and then tries to toggle the flag on and off /// /// /// @@ -37,9 +36,9 @@ namespace Discord } /// - /// Tests that flag of the given permissions value to be the expected output - /// and then tries cycling through the states of the allow and deny values - /// for that flag + /// Tests that flag of the given permissions value to be the expected output + /// and then tries cycling through the states of the allow and deny values + /// for that flag /// /// /// @@ -51,8 +50,8 @@ namespace Discord // check toggling bits for both allow and deny // have to make copies to get around read only property - ulong allow = value.AllowValue; - ulong deny = value.DenyValue; + var allow = value.AllowValue; + var deny = value.DenyValue; // both unset should be inherit Permissions.UnsetFlag(ref allow, (ulong)flag); @@ -76,434 +75,200 @@ namespace Discord } /// - /// Tests for the class. - /// - /// Tests that text channel permissions get the right value - /// from the Has method. + /// Tests for the + /// + /// method to ensure that the default no-param call does not modify the resulting value + /// of the OverwritePermissions. /// /// [Fact] - public Task TestPermissionsHasChannelPermissionText() - { - var value = ChannelPermissions.Text; - // check that the result of GetValue matches for all properties of text channel - TestHelper(value, ChannelPermission.CreateInstantInvite, true); - TestHelper(value, ChannelPermission.ManageChannels, true); - TestHelper(value, ChannelPermission.AddReactions, true); - TestHelper(value, ChannelPermission.ViewChannel, true); - TestHelper(value, ChannelPermission.SendMessages, true); - TestHelper(value, ChannelPermission.SendTTSMessages, true); - TestHelper(value, ChannelPermission.ManageMessages, true); - TestHelper(value, ChannelPermission.EmbedLinks, true); - TestHelper(value, ChannelPermission.AttachFiles, true); - TestHelper(value, ChannelPermission.ReadMessageHistory, true); - TestHelper(value, ChannelPermission.MentionEveryone, true); - TestHelper(value, ChannelPermission.UseExternalEmojis, true); - TestHelper(value, ChannelPermission.ManageRoles, true); - TestHelper(value, ChannelPermission.ManageWebhooks, true); - - TestHelper(value, ChannelPermission.Connect, false); - TestHelper(value, ChannelPermission.Speak, false); - TestHelper(value, ChannelPermission.MuteMembers, false); - TestHelper(value, ChannelPermission.DeafenMembers, false); - TestHelper(value, ChannelPermission.MoveMembers, false); - TestHelper(value, ChannelPermission.UseVAD, false); - - return Task.CompletedTask; - } - - /// - /// Tests for the class. - /// - /// Tests that no channel permissions get the right value - /// from the Has method. - /// - /// - [Fact] - public Task TestPermissionsHasChannelPermissionNone() - { - // check that none will fail all - var value = ChannelPermissions.None; - - TestHelper(value, ChannelPermission.CreateInstantInvite, false); - TestHelper(value, ChannelPermission.ManageChannels, false); - TestHelper(value, ChannelPermission.AddReactions, false); - TestHelper(value, ChannelPermission.ViewChannel, false); - TestHelper(value, ChannelPermission.SendMessages, false); - TestHelper(value, ChannelPermission.SendTTSMessages, false); - TestHelper(value, ChannelPermission.ManageMessages, false); - TestHelper(value, ChannelPermission.EmbedLinks, false); - TestHelper(value, ChannelPermission.AttachFiles, false); - TestHelper(value, ChannelPermission.ReadMessageHistory, false); - TestHelper(value, ChannelPermission.MentionEveryone, false); - TestHelper(value, ChannelPermission.UseExternalEmojis, false); - TestHelper(value, ChannelPermission.ManageRoles, false); - TestHelper(value, ChannelPermission.ManageWebhooks, false); - TestHelper(value, ChannelPermission.Connect, false); - TestHelper(value, ChannelPermission.Speak, false); - TestHelper(value, ChannelPermission.MuteMembers, false); - TestHelper(value, ChannelPermission.DeafenMembers, false); - TestHelper(value, ChannelPermission.MoveMembers, false); - TestHelper(value, ChannelPermission.UseVAD, false); - - return Task.CompletedTask; - } - - /// - /// Tests for the class. - /// - /// Tests that the dm channel permissions get the right value - /// from the Has method. - /// - /// - [Fact] - public Task TestPermissionsHasChannelPermissionDM() - { - // check that none will fail all - var value = ChannelPermissions.DM; - - TestHelper(value, ChannelPermission.CreateInstantInvite, false); - TestHelper(value, ChannelPermission.ManageChannels, false); - TestHelper(value, ChannelPermission.AddReactions, false); - TestHelper(value, ChannelPermission.ViewChannel, true); - TestHelper(value, ChannelPermission.SendMessages, true); - TestHelper(value, ChannelPermission.SendTTSMessages, false); - TestHelper(value, ChannelPermission.ManageMessages, false); - TestHelper(value, ChannelPermission.EmbedLinks, true); - TestHelper(value, ChannelPermission.AttachFiles, true); - TestHelper(value, ChannelPermission.ReadMessageHistory, true); - TestHelper(value, ChannelPermission.MentionEveryone, false); - TestHelper(value, ChannelPermission.UseExternalEmojis, true); - TestHelper(value, ChannelPermission.ManageRoles, false); - TestHelper(value, ChannelPermission.ManageWebhooks, false); - TestHelper(value, ChannelPermission.Connect, true); - TestHelper(value, ChannelPermission.Speak, true); - TestHelper(value, ChannelPermission.MuteMembers, false); - TestHelper(value, ChannelPermission.DeafenMembers, false); - TestHelper(value, ChannelPermission.MoveMembers, false); - TestHelper(value, ChannelPermission.UseVAD, true); - - return Task.CompletedTask; - } - - /// - /// Tests for the class. - /// - /// Tests that the group channel permissions get the right value - /// from the Has method. - /// - /// - [Fact] - public Task TestPermissionsHasChannelPermissionGroup() - { - var value = ChannelPermissions.Group; - - TestHelper(value, ChannelPermission.CreateInstantInvite, false); - TestHelper(value, ChannelPermission.ManageChannels, false); - TestHelper(value, ChannelPermission.AddReactions, false); - TestHelper(value, ChannelPermission.ViewChannel, false); - TestHelper(value, ChannelPermission.SendMessages, true); - TestHelper(value, ChannelPermission.SendTTSMessages, true); - TestHelper(value, ChannelPermission.ManageMessages, false); - TestHelper(value, ChannelPermission.EmbedLinks, true); - TestHelper(value, ChannelPermission.AttachFiles, true); - TestHelper(value, ChannelPermission.ReadMessageHistory, false); - TestHelper(value, ChannelPermission.MentionEveryone, false); - TestHelper(value, ChannelPermission.UseExternalEmojis, false); - TestHelper(value, ChannelPermission.ManageRoles, false); - TestHelper(value, ChannelPermission.ManageWebhooks, false); - TestHelper(value, ChannelPermission.Connect, true); - TestHelper(value, ChannelPermission.Speak, true); - TestHelper(value, ChannelPermission.MuteMembers, false); - TestHelper(value, ChannelPermission.DeafenMembers, false); - TestHelper(value, ChannelPermission.MoveMembers, false); - TestHelper(value, ChannelPermission.UseVAD, true); - - return Task.CompletedTask; - } - - - /// - /// Tests for the class. - /// - /// Tests that the voice channel permissions get the right value - /// from the Has method. - /// - /// - [Fact] - public Task TestPermissionsHasChannelPermissionVoice() + public Task TestOverwritePermissionModifyNoParam() { - // make a flag with all possible values for Voice channel permissions - var value = ChannelPermissions.Voice; - - TestHelper(value, ChannelPermission.CreateInstantInvite, true); - TestHelper(value, ChannelPermission.ManageChannels, true); - TestHelper(value, ChannelPermission.AddReactions, false); - TestHelper(value, ChannelPermission.ViewChannel, true); - TestHelper(value, ChannelPermission.SendMessages, false); - TestHelper(value, ChannelPermission.SendTTSMessages, false); - TestHelper(value, ChannelPermission.ManageMessages, false); - TestHelper(value, ChannelPermission.EmbedLinks, false); - TestHelper(value, ChannelPermission.AttachFiles, false); - TestHelper(value, ChannelPermission.ReadMessageHistory, false); - TestHelper(value, ChannelPermission.MentionEveryone, false); - TestHelper(value, ChannelPermission.UseExternalEmojis, false); - TestHelper(value, ChannelPermission.ManageRoles, true); - TestHelper(value, ChannelPermission.ManageWebhooks, false); - TestHelper(value, ChannelPermission.Connect, true); - TestHelper(value, ChannelPermission.Speak, true); - TestHelper(value, ChannelPermission.MuteMembers, true); - TestHelper(value, ChannelPermission.DeafenMembers, true); - TestHelper(value, ChannelPermission.MoveMembers, true); - TestHelper(value, ChannelPermission.UseVAD, true); + // test for all Text allowed, none denied + var original = new OverwritePermissions(ChannelPermissions.Text.RawValue, ChannelPermissions.None.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); - return Task.CompletedTask; - } + // none allowed, text denied + original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Text.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); - /// - /// Tests for the class. - /// - /// Test that that the Has method of - /// returns the correct value when no permissions are set. - /// - /// - [Fact] - public Task TestPermissionsHasGuildPermissionNone() - { - var value = GuildPermissions.None; + // category allowed, none denied + original = new OverwritePermissions(ChannelPermissions.Category.RawValue, ChannelPermissions.None.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); - TestHelper(value, GuildPermission.CreateInstantInvite, false); - TestHelper(value, GuildPermission.KickMembers, false); - TestHelper(value, GuildPermission.BanMembers, false); - TestHelper(value, GuildPermission.Administrator, false); - TestHelper(value, GuildPermission.ManageChannels, false); - TestHelper(value, GuildPermission.ManageGuild, false); - TestHelper(value, GuildPermission.AddReactions, false); - TestHelper(value, GuildPermission.ViewAuditLog, false); - TestHelper(value, GuildPermission.ViewChannel, false); - TestHelper(value, GuildPermission.SendMessages, false); - TestHelper(value, GuildPermission.SendTTSMessages, false); - TestHelper(value, GuildPermission.ManageMessages, false); - TestHelper(value, GuildPermission.EmbedLinks, false); - TestHelper(value, GuildPermission.AttachFiles, false); - TestHelper(value, GuildPermission.ReadMessageHistory, false); - TestHelper(value, GuildPermission.MentionEveryone, false); - TestHelper(value, GuildPermission.UseExternalEmojis, false); - TestHelper(value, GuildPermission.Connect, false); - TestHelper(value, GuildPermission.Speak, false); - TestHelper(value, GuildPermission.MuteMembers, false); - TestHelper(value, GuildPermission.MoveMembers, false); - TestHelper(value, GuildPermission.UseVAD, false); - TestHelper(value, GuildPermission.ChangeNickname, false); - TestHelper(value, GuildPermission.ManageNicknames, false); - TestHelper(value, GuildPermission.ManageRoles, false); - TestHelper(value, GuildPermission.ManageWebhooks, false); - TestHelper(value, GuildPermission.ManageEmojis, false); + // none allowed, category denied + original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Category.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); - return Task.CompletedTask; - } + // DM allowed, none denied + original = new OverwritePermissions(ChannelPermissions.DM.RawValue, ChannelPermissions.None.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); - /// - /// Tests for the class. - /// - /// Test that that the Has method of - /// returns the correct value when all permissions are set. - /// - /// - [Fact] - public Task TestPermissionsHasGuildPermissionAll() - { - var value = GuildPermissions.All; + // none allowed, DM denied + original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.DM.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); - TestHelper(value, GuildPermission.CreateInstantInvite, true); - TestHelper(value, GuildPermission.KickMembers, true); - TestHelper(value, GuildPermission.BanMembers, true); - TestHelper(value, GuildPermission.Administrator, true); - TestHelper(value, GuildPermission.ManageChannels, true); - TestHelper(value, GuildPermission.ManageGuild, true); - TestHelper(value, GuildPermission.AddReactions, true); - TestHelper(value, GuildPermission.ViewAuditLog, true); - TestHelper(value, GuildPermission.ViewChannel, true); - TestHelper(value, GuildPermission.SendMessages, true); - TestHelper(value, GuildPermission.SendTTSMessages, true); - TestHelper(value, GuildPermission.ManageMessages, true); - TestHelper(value, GuildPermission.EmbedLinks, true); - TestHelper(value, GuildPermission.AttachFiles, true); - TestHelper(value, GuildPermission.ReadMessageHistory, true); - TestHelper(value, GuildPermission.MentionEveryone, true); - TestHelper(value, GuildPermission.UseExternalEmojis, true); - TestHelper(value, GuildPermission.Connect, true); - TestHelper(value, GuildPermission.Speak, true); - TestHelper(value, GuildPermission.MuteMembers, true); - TestHelper(value, GuildPermission.MoveMembers, true); - TestHelper(value, GuildPermission.UseVAD, true); - TestHelper(value, GuildPermission.ChangeNickname, true); - TestHelper(value, GuildPermission.ManageNicknames, true); - TestHelper(value, GuildPermission.ManageRoles, true); - TestHelper(value, GuildPermission.ManageWebhooks, true); - TestHelper(value, GuildPermission.ManageEmojis, true); + // voice allowed, none denied + original = new OverwritePermissions(ChannelPermissions.Voice.RawValue, ChannelPermissions.None.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); + // none allowed, voice denied + original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Voice.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); - return Task.CompletedTask; - } + // group allowed, none denied + original = new OverwritePermissions(ChannelPermissions.Group.RawValue, ChannelPermissions.None.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); - /// - /// Tests for the class. - /// - /// Test that that the Has method of - /// returns the correct value when webhook permissions are set. - /// - /// - [Fact] - public Task TestPermissionsHasGuildPermissionWebhook() - { - var value = GuildPermissions.Webhook; + // none allowed, group denied + original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Group.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); - TestHelper(value, GuildPermission.CreateInstantInvite, false); - TestHelper(value, GuildPermission.KickMembers, false); - TestHelper(value, GuildPermission.BanMembers, false); - TestHelper(value, GuildPermission.Administrator, false); - TestHelper(value, GuildPermission.ManageChannels, false); - TestHelper(value, GuildPermission.ManageGuild, false); - TestHelper(value, GuildPermission.AddReactions, false); - TestHelper(value, GuildPermission.ViewAuditLog, false); - TestHelper(value, GuildPermission.ViewChannel, false); - TestHelper(value, GuildPermission.SendMessages, true); - TestHelper(value, GuildPermission.SendTTSMessages, true); - TestHelper(value, GuildPermission.ManageMessages, false); - TestHelper(value, GuildPermission.EmbedLinks, true); - TestHelper(value, GuildPermission.AttachFiles, true); - TestHelper(value, GuildPermission.ReadMessageHistory, false); - TestHelper(value, GuildPermission.MentionEveryone, false); - TestHelper(value, GuildPermission.UseExternalEmojis, false); - TestHelper(value, GuildPermission.Connect, false); - TestHelper(value, GuildPermission.Speak, false); - TestHelper(value, GuildPermission.MuteMembers, false); - TestHelper(value, GuildPermission.MoveMembers, false); - TestHelper(value, GuildPermission.UseVAD, false); - TestHelper(value, GuildPermission.ChangeNickname, false); - TestHelper(value, GuildPermission.ManageNicknames, false); - TestHelper(value, GuildPermission.ManageRoles, false); - TestHelper(value, GuildPermission.ManageWebhooks, false); - TestHelper(value, GuildPermission.ManageEmojis, false); + // none allowed, none denied + original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.None.RawValue); + Assert.Equal(original.AllowValue, original.Modify().AllowValue); + Assert.Equal(original.DenyValue, original.Modify().DenyValue); return Task.CompletedTask; } /// - /// Test - /// for when all text permissions are allowed and denied + /// Test + /// for when all dm permissions are allowed and denied /// /// [Fact] - public Task TestOverwritePermissionsText() + public Task TestOverwritePermissionsDM() { // allow all for text channel - var value = new OverwritePermissions(ChannelPermissions.Text.RawValue, ChannelPermissions.None.RawValue); + var value = new OverwritePermissions(ChannelPermissions.DM.RawValue, ChannelPermissions.None.RawValue); - TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Allow); - TestHelper(value, ChannelPermission.ManageChannels, PermValue.Allow); - TestHelper(value, ChannelPermission.AddReactions, PermValue.Allow); + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); TestHelper(value, ChannelPermission.ViewChannel, PermValue.Allow); TestHelper(value, ChannelPermission.SendMessages, PermValue.Allow); - TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Allow); - TestHelper(value, ChannelPermission.ManageMessages, PermValue.Allow); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Allow); TestHelper(value, ChannelPermission.AttachFiles, PermValue.Allow); TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Allow); - TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Allow); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Allow); - TestHelper(value, ChannelPermission.ManageRoles, PermValue.Allow); - TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Allow); - TestHelper(value, ChannelPermission.Connect, PermValue.Inherit); - TestHelper(value, ChannelPermission.Speak, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); + TestHelper(value, ChannelPermission.Connect, PermValue.Allow); + TestHelper(value, ChannelPermission.Speak, PermValue.Allow); TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); - TestHelper(value, ChannelPermission.UseVAD, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Allow); - value = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Text.RawValue); + value = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.DM.RawValue); - TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Deny); - TestHelper(value, ChannelPermission.ManageChannels, PermValue.Deny); - TestHelper(value, ChannelPermission.AddReactions, PermValue.Deny); + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); TestHelper(value, ChannelPermission.ViewChannel, PermValue.Deny); TestHelper(value, ChannelPermission.SendMessages, PermValue.Deny); - TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Deny); - TestHelper(value, ChannelPermission.ManageMessages, PermValue.Deny); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Deny); TestHelper(value, ChannelPermission.AttachFiles, PermValue.Deny); TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Deny); - TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Deny); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Deny); - TestHelper(value, ChannelPermission.ManageRoles, PermValue.Deny); - TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Deny); - TestHelper(value, ChannelPermission.Connect, PermValue.Inherit); - TestHelper(value, ChannelPermission.Speak, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); + TestHelper(value, ChannelPermission.Connect, PermValue.Deny); + TestHelper(value, ChannelPermission.Speak, PermValue.Deny); TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); - TestHelper(value, ChannelPermission.UseVAD, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Deny); return Task.CompletedTask; } /// - /// Test - /// for when none of the permissions are set. + /// Test + /// for when all group permissions are allowed and denied /// /// [Fact] - public Task TestOverwritePermissionsNone() + public Task TestOverwritePermissionsGroup() { - // allow all for text channel - var value = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.None.RawValue); + // allow all for group channels + var value = new OverwritePermissions(ChannelPermissions.Group.RawValue, ChannelPermissions.None.RawValue); TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); - TestHelper(value, ChannelPermission.SendMessages, PermValue.Inherit); - TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Allow); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Allow); TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); - TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Inherit); - TestHelper(value, ChannelPermission.AttachFiles, PermValue.Inherit); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Allow); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Allow); TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Inherit); TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); - TestHelper(value, ChannelPermission.Connect, PermValue.Inherit); - TestHelper(value, ChannelPermission.Speak, PermValue.Inherit); + TestHelper(value, ChannelPermission.Connect, PermValue.Allow); + TestHelper(value, ChannelPermission.Speak, PermValue.Allow); TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); - TestHelper(value, ChannelPermission.UseVAD, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Allow); - value = new OverwritePermissions(); + value = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Group.RawValue); TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); - TestHelper(value, ChannelPermission.SendMessages, PermValue.Inherit); - TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Deny); + TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Deny); TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); - TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Inherit); - TestHelper(value, ChannelPermission.AttachFiles, PermValue.Inherit); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Deny); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Deny); TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Inherit); TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); - TestHelper(value, ChannelPermission.Connect, PermValue.Inherit); - TestHelper(value, ChannelPermission.Speak, PermValue.Inherit); + TestHelper(value, ChannelPermission.Connect, PermValue.Deny); + TestHelper(value, ChannelPermission.Speak, PermValue.Deny); TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); - TestHelper(value, ChannelPermission.UseVAD, PermValue.Inherit); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Deny); - value = OverwritePermissions.InheritAll; + return Task.CompletedTask; + } + + /// + /// Test + /// for when none of the permissions are set. + /// + /// + [Fact] + public Task TestOverwritePermissionsNone() + { + // allow all for text channel + var value = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.None.RawValue); TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); @@ -526,128 +291,116 @@ namespace Discord TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.UseVAD, PermValue.Inherit); - return Task.CompletedTask; - } - - /// - /// Test - /// for when all dm permissions are allowed and denied - /// - /// - [Fact] - public Task TestOverwritePermissionsDM() - { - // allow all for text channel - var value = new OverwritePermissions(ChannelPermissions.DM.RawValue, ChannelPermissions.None.RawValue); + value = new OverwritePermissions(); TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); - TestHelper(value, ChannelPermission.ViewChannel, PermValue.Allow); - TestHelper(value, ChannelPermission.SendMessages, PermValue.Allow); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Inherit); TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); - TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Allow); - TestHelper(value, ChannelPermission.AttachFiles, PermValue.Allow); - TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Allow); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Inherit); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Inherit); TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); - TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Allow); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); - TestHelper(value, ChannelPermission.Connect, PermValue.Allow); - TestHelper(value, ChannelPermission.Speak, PermValue.Allow); + TestHelper(value, ChannelPermission.Connect, PermValue.Inherit); + TestHelper(value, ChannelPermission.Speak, PermValue.Inherit); TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); - TestHelper(value, ChannelPermission.UseVAD, PermValue.Allow); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Inherit); - value = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.DM.RawValue); + value = OverwritePermissions.InheritAll; TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); - TestHelper(value, ChannelPermission.ViewChannel, PermValue.Deny); - TestHelper(value, ChannelPermission.SendMessages, PermValue.Deny); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); + TestHelper(value, ChannelPermission.SendMessages, PermValue.Inherit); TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); - TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Deny); - TestHelper(value, ChannelPermission.AttachFiles, PermValue.Deny); - TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Deny); + TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Inherit); + TestHelper(value, ChannelPermission.AttachFiles, PermValue.Inherit); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Inherit); TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); - TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Deny); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); - TestHelper(value, ChannelPermission.Connect, PermValue.Deny); - TestHelper(value, ChannelPermission.Speak, PermValue.Deny); + TestHelper(value, ChannelPermission.Connect, PermValue.Inherit); + TestHelper(value, ChannelPermission.Speak, PermValue.Inherit); TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); - TestHelper(value, ChannelPermission.UseVAD, PermValue.Deny); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Inherit); return Task.CompletedTask; } /// - /// Test - /// for when all group permissions are allowed and denied + /// Test + /// for when all text permissions are allowed and denied /// /// [Fact] - public Task TestOverwritePermissionsGroup() - { - // allow all for group channels - var value = new OverwritePermissions(ChannelPermissions.Group.RawValue, ChannelPermissions.None.RawValue); + public Task TestOverwritePermissionsText() + { + // allow all for text channel + var value = new OverwritePermissions(ChannelPermissions.Text.RawValue, ChannelPermissions.None.RawValue); - TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); - TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); - TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); - TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Allow); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Allow); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Allow); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Allow); TestHelper(value, ChannelPermission.SendMessages, PermValue.Allow); TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Allow); - TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Allow); TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Allow); TestHelper(value, ChannelPermission.AttachFiles, PermValue.Allow); - TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Inherit); - TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); - TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Inherit); - TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); - TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); - TestHelper(value, ChannelPermission.Connect, PermValue.Allow); - TestHelper(value, ChannelPermission.Speak, PermValue.Allow); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Allow); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Allow); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Allow); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Allow); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Allow); + TestHelper(value, ChannelPermission.Connect, PermValue.Inherit); + TestHelper(value, ChannelPermission.Speak, PermValue.Inherit); TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); - TestHelper(value, ChannelPermission.UseVAD, PermValue.Allow); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Inherit); - value = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Group.RawValue); + value = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Text.RawValue); - TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Inherit); - TestHelper(value, ChannelPermission.ManageChannels, PermValue.Inherit); - TestHelper(value, ChannelPermission.AddReactions, PermValue.Inherit); - TestHelper(value, ChannelPermission.ViewChannel, PermValue.Inherit); + TestHelper(value, ChannelPermission.CreateInstantInvite, PermValue.Deny); + TestHelper(value, ChannelPermission.ManageChannels, PermValue.Deny); + TestHelper(value, ChannelPermission.AddReactions, PermValue.Deny); + TestHelper(value, ChannelPermission.ViewChannel, PermValue.Deny); TestHelper(value, ChannelPermission.SendMessages, PermValue.Deny); TestHelper(value, ChannelPermission.SendTTSMessages, PermValue.Deny); - TestHelper(value, ChannelPermission.ManageMessages, PermValue.Inherit); + TestHelper(value, ChannelPermission.ManageMessages, PermValue.Deny); TestHelper(value, ChannelPermission.EmbedLinks, PermValue.Deny); TestHelper(value, ChannelPermission.AttachFiles, PermValue.Deny); - TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Inherit); - TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Inherit); - TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Inherit); - TestHelper(value, ChannelPermission.ManageRoles, PermValue.Inherit); - TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Inherit); - TestHelper(value, ChannelPermission.Connect, PermValue.Deny); - TestHelper(value, ChannelPermission.Speak, PermValue.Deny); + TestHelper(value, ChannelPermission.ReadMessageHistory, PermValue.Deny); + TestHelper(value, ChannelPermission.MentionEveryone, PermValue.Deny); + TestHelper(value, ChannelPermission.UseExternalEmojis, PermValue.Deny); + TestHelper(value, ChannelPermission.ManageRoles, PermValue.Deny); + TestHelper(value, ChannelPermission.ManageWebhooks, PermValue.Deny); + TestHelper(value, ChannelPermission.Connect, PermValue.Inherit); + TestHelper(value, ChannelPermission.Speak, PermValue.Inherit); TestHelper(value, ChannelPermission.MuteMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.DeafenMembers, PermValue.Inherit); TestHelper(value, ChannelPermission.MoveMembers, PermValue.Inherit); - TestHelper(value, ChannelPermission.UseVAD, PermValue.Deny); + TestHelper(value, ChannelPermission.UseVAD, PermValue.Inherit); return Task.CompletedTask; } /// - /// Test - /// for when all group permissions are allowed and denied + /// Test + /// for when all group permissions are allowed and denied /// /// [Fact] @@ -704,68 +457,308 @@ namespace Discord } /// - /// Tests for the - /// method to ensure that the default no-param call does not modify the resulting value - /// of the OverwritePermissions. + /// Tests for the class. + /// Tests that the dm channel permissions get the right value + /// from the Has method. /// /// [Fact] - public Task TestOverwritePermissionModifyNoParam() + public Task TestPermissionsHasChannelPermissionDM() { - // test for all Text allowed, none denied - var original = new OverwritePermissions(ChannelPermissions.Text.RawValue, ChannelPermissions.None.RawValue); - Assert.Equal(original.AllowValue, original.Modify().AllowValue); - Assert.Equal(original.DenyValue, original.Modify().DenyValue); + // check that none will fail all + var value = ChannelPermissions.DM; - // none allowed, text denied - original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Text.RawValue); - Assert.Equal(original.AllowValue, original.Modify().AllowValue); - Assert.Equal(original.DenyValue, original.Modify().DenyValue); + TestHelper(value, ChannelPermission.CreateInstantInvite); + TestHelper(value, ChannelPermission.ManageChannels); + TestHelper(value, ChannelPermission.AddReactions); + TestHelper(value, ChannelPermission.ViewChannel, true); + TestHelper(value, ChannelPermission.SendMessages, true); + TestHelper(value, ChannelPermission.SendTTSMessages); + TestHelper(value, ChannelPermission.ManageMessages); + TestHelper(value, ChannelPermission.EmbedLinks, true); + TestHelper(value, ChannelPermission.AttachFiles, true); + TestHelper(value, ChannelPermission.ReadMessageHistory, true); + TestHelper(value, ChannelPermission.MentionEveryone); + TestHelper(value, ChannelPermission.UseExternalEmojis, true); + TestHelper(value, ChannelPermission.ManageRoles); + TestHelper(value, ChannelPermission.ManageWebhooks); + TestHelper(value, ChannelPermission.Connect, true); + TestHelper(value, ChannelPermission.Speak, true); + TestHelper(value, ChannelPermission.MuteMembers); + TestHelper(value, ChannelPermission.DeafenMembers); + TestHelper(value, ChannelPermission.MoveMembers); + TestHelper(value, ChannelPermission.UseVAD, true); - // category allowed, none denied - original = new OverwritePermissions(ChannelPermissions.Category.RawValue, ChannelPermissions.None.RawValue); - Assert.Equal(original.AllowValue, original.Modify().AllowValue); - Assert.Equal(original.DenyValue, original.Modify().DenyValue); + return Task.CompletedTask; + } - // none allowed, category denied - original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Category.RawValue); - Assert.Equal(original.AllowValue, original.Modify().AllowValue); - Assert.Equal(original.DenyValue, original.Modify().DenyValue); + /// + /// Tests for the class. + /// Tests that the group channel permissions get the right value + /// from the Has method. + /// + /// + [Fact] + public Task TestPermissionsHasChannelPermissionGroup() + { + var value = ChannelPermissions.Group; - // DM allowed, none denied - original = new OverwritePermissions(ChannelPermissions.DM.RawValue, ChannelPermissions.None.RawValue); - Assert.Equal(original.AllowValue, original.Modify().AllowValue); - Assert.Equal(original.DenyValue, original.Modify().DenyValue); + TestHelper(value, ChannelPermission.CreateInstantInvite); + TestHelper(value, ChannelPermission.ManageChannels); + TestHelper(value, ChannelPermission.AddReactions); + TestHelper(value, ChannelPermission.ViewChannel); + TestHelper(value, ChannelPermission.SendMessages, true); + TestHelper(value, ChannelPermission.SendTTSMessages, true); + TestHelper(value, ChannelPermission.ManageMessages); + TestHelper(value, ChannelPermission.EmbedLinks, true); + TestHelper(value, ChannelPermission.AttachFiles, true); + TestHelper(value, ChannelPermission.ReadMessageHistory); + TestHelper(value, ChannelPermission.MentionEveryone); + TestHelper(value, ChannelPermission.UseExternalEmojis); + TestHelper(value, ChannelPermission.ManageRoles); + TestHelper(value, ChannelPermission.ManageWebhooks); + TestHelper(value, ChannelPermission.Connect, true); + TestHelper(value, ChannelPermission.Speak, true); + TestHelper(value, ChannelPermission.MuteMembers); + TestHelper(value, ChannelPermission.DeafenMembers); + TestHelper(value, ChannelPermission.MoveMembers); + TestHelper(value, ChannelPermission.UseVAD, true); - // none allowed, DM denied - original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.DM.RawValue); - Assert.Equal(original.AllowValue, original.Modify().AllowValue); - Assert.Equal(original.DenyValue, original.Modify().DenyValue); + return Task.CompletedTask; + } - // voice allowed, none denied - original = new OverwritePermissions(ChannelPermissions.Voice.RawValue, ChannelPermissions.None.RawValue); - Assert.Equal(original.AllowValue, original.Modify().AllowValue); - Assert.Equal(original.DenyValue, original.Modify().DenyValue); + /// + /// Tests for the class. + /// Tests that no channel permissions get the right value + /// from the Has method. + /// + /// + [Fact] + public Task TestPermissionsHasChannelPermissionNone() + { + // check that none will fail all + var value = ChannelPermissions.None; - // none allowed, voice denied - original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Voice.RawValue); - Assert.Equal(original.AllowValue, original.Modify().AllowValue); - Assert.Equal(original.DenyValue, original.Modify().DenyValue); + TestHelper(value, ChannelPermission.CreateInstantInvite); + TestHelper(value, ChannelPermission.ManageChannels); + TestHelper(value, ChannelPermission.AddReactions); + TestHelper(value, ChannelPermission.ViewChannel); + TestHelper(value, ChannelPermission.SendMessages); + TestHelper(value, ChannelPermission.SendTTSMessages); + TestHelper(value, ChannelPermission.ManageMessages); + TestHelper(value, ChannelPermission.EmbedLinks); + TestHelper(value, ChannelPermission.AttachFiles); + TestHelper(value, ChannelPermission.ReadMessageHistory); + TestHelper(value, ChannelPermission.MentionEveryone); + TestHelper(value, ChannelPermission.UseExternalEmojis); + TestHelper(value, ChannelPermission.ManageRoles); + TestHelper(value, ChannelPermission.ManageWebhooks); + TestHelper(value, ChannelPermission.Connect); + TestHelper(value, ChannelPermission.Speak); + TestHelper(value, ChannelPermission.MuteMembers); + TestHelper(value, ChannelPermission.DeafenMembers); + TestHelper(value, ChannelPermission.MoveMembers); + TestHelper(value, ChannelPermission.UseVAD); - // group allowed, none denied - original = new OverwritePermissions(ChannelPermissions.Group.RawValue, ChannelPermissions.None.RawValue); - Assert.Equal(original.AllowValue, original.Modify().AllowValue); - Assert.Equal(original.DenyValue, original.Modify().DenyValue); + return Task.CompletedTask; + } - // none allowed, group denied - original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.Group.RawValue); - Assert.Equal(original.AllowValue, original.Modify().AllowValue); - Assert.Equal(original.DenyValue, original.Modify().DenyValue); + /// + /// Tests for the class. + /// Tests that text channel permissions get the right value + /// from the Has method. + /// + /// + [Fact] + public Task TestPermissionsHasChannelPermissionText() + { + var value = ChannelPermissions.Text; + // check that the result of GetValue matches for all properties of text channel + TestHelper(value, ChannelPermission.CreateInstantInvite, true); + TestHelper(value, ChannelPermission.ManageChannels, true); + TestHelper(value, ChannelPermission.AddReactions, true); + TestHelper(value, ChannelPermission.ViewChannel, true); + TestHelper(value, ChannelPermission.SendMessages, true); + TestHelper(value, ChannelPermission.SendTTSMessages, true); + TestHelper(value, ChannelPermission.ManageMessages, true); + TestHelper(value, ChannelPermission.EmbedLinks, true); + TestHelper(value, ChannelPermission.AttachFiles, true); + TestHelper(value, ChannelPermission.ReadMessageHistory, true); + TestHelper(value, ChannelPermission.MentionEveryone, true); + TestHelper(value, ChannelPermission.UseExternalEmojis, true); + TestHelper(value, ChannelPermission.ManageRoles, true); + TestHelper(value, ChannelPermission.ManageWebhooks, true); - // none allowed, none denied - original = new OverwritePermissions(ChannelPermissions.None.RawValue, ChannelPermissions.None.RawValue); - Assert.Equal(original.AllowValue, original.Modify().AllowValue); - Assert.Equal(original.DenyValue, original.Modify().DenyValue); + TestHelper(value, ChannelPermission.Connect); + TestHelper(value, ChannelPermission.Speak); + TestHelper(value, ChannelPermission.MuteMembers); + TestHelper(value, ChannelPermission.DeafenMembers); + TestHelper(value, ChannelPermission.MoveMembers); + TestHelper(value, ChannelPermission.UseVAD); + + return Task.CompletedTask; + } + + + /// + /// Tests for the class. + /// Tests that the voice channel permissions get the right value + /// from the Has method. + /// + /// + [Fact] + public Task TestPermissionsHasChannelPermissionVoice() + { + // make a flag with all possible values for Voice channel permissions + var value = ChannelPermissions.Voice; + + TestHelper(value, ChannelPermission.CreateInstantInvite, true); + TestHelper(value, ChannelPermission.ManageChannels, true); + TestHelper(value, ChannelPermission.AddReactions); + TestHelper(value, ChannelPermission.ViewChannel, true); + TestHelper(value, ChannelPermission.SendMessages); + TestHelper(value, ChannelPermission.SendTTSMessages); + TestHelper(value, ChannelPermission.ManageMessages); + TestHelper(value, ChannelPermission.EmbedLinks); + TestHelper(value, ChannelPermission.AttachFiles); + TestHelper(value, ChannelPermission.ReadMessageHistory); + TestHelper(value, ChannelPermission.MentionEveryone); + TestHelper(value, ChannelPermission.UseExternalEmojis); + TestHelper(value, ChannelPermission.ManageRoles, true); + TestHelper(value, ChannelPermission.ManageWebhooks); + TestHelper(value, ChannelPermission.Connect, true); + TestHelper(value, ChannelPermission.Speak, true); + TestHelper(value, ChannelPermission.MuteMembers, true); + TestHelper(value, ChannelPermission.DeafenMembers, true); + TestHelper(value, ChannelPermission.MoveMembers, true); + TestHelper(value, ChannelPermission.UseVAD, true); + + return Task.CompletedTask; + } + + /// + /// Tests for the class. + /// Test that that the Has method of + /// returns the correct value when all permissions are set. + /// + /// + [Fact] + public Task TestPermissionsHasGuildPermissionAll() + { + var value = GuildPermissions.All; + + TestHelper(value, GuildPermission.CreateInstantInvite, true); + TestHelper(value, GuildPermission.KickMembers, true); + TestHelper(value, GuildPermission.BanMembers, true); + TestHelper(value, GuildPermission.Administrator, true); + TestHelper(value, GuildPermission.ManageChannels, true); + TestHelper(value, GuildPermission.ManageGuild, true); + TestHelper(value, GuildPermission.AddReactions, true); + TestHelper(value, GuildPermission.ViewAuditLog, true); + TestHelper(value, GuildPermission.ViewChannel, true); + TestHelper(value, GuildPermission.SendMessages, true); + TestHelper(value, GuildPermission.SendTTSMessages, true); + TestHelper(value, GuildPermission.ManageMessages, true); + TestHelper(value, GuildPermission.EmbedLinks, true); + TestHelper(value, GuildPermission.AttachFiles, true); + TestHelper(value, GuildPermission.ReadMessageHistory, true); + TestHelper(value, GuildPermission.MentionEveryone, true); + TestHelper(value, GuildPermission.UseExternalEmojis, true); + TestHelper(value, GuildPermission.Connect, true); + TestHelper(value, GuildPermission.Speak, true); + TestHelper(value, GuildPermission.MuteMembers, true); + TestHelper(value, GuildPermission.MoveMembers, true); + TestHelper(value, GuildPermission.UseVAD, true); + TestHelper(value, GuildPermission.ChangeNickname, true); + TestHelper(value, GuildPermission.ManageNicknames, true); + TestHelper(value, GuildPermission.ManageRoles, true); + TestHelper(value, GuildPermission.ManageWebhooks, true); + TestHelper(value, GuildPermission.ManageEmojis, true); + + + return Task.CompletedTask; + } + + /// + /// Tests for the class. + /// Test that that the Has method of + /// returns the correct value when no permissions are set. + /// + /// + [Fact] + public Task TestPermissionsHasGuildPermissionNone() + { + var value = GuildPermissions.None; + + TestHelper(value, GuildPermission.CreateInstantInvite); + TestHelper(value, GuildPermission.KickMembers); + TestHelper(value, GuildPermission.BanMembers); + TestHelper(value, GuildPermission.Administrator); + TestHelper(value, GuildPermission.ManageChannels); + TestHelper(value, GuildPermission.ManageGuild); + TestHelper(value, GuildPermission.AddReactions); + TestHelper(value, GuildPermission.ViewAuditLog); + TestHelper(value, GuildPermission.ViewChannel); + TestHelper(value, GuildPermission.SendMessages); + TestHelper(value, GuildPermission.SendTTSMessages); + TestHelper(value, GuildPermission.ManageMessages); + TestHelper(value, GuildPermission.EmbedLinks); + TestHelper(value, GuildPermission.AttachFiles); + TestHelper(value, GuildPermission.ReadMessageHistory); + TestHelper(value, GuildPermission.MentionEveryone); + TestHelper(value, GuildPermission.UseExternalEmojis); + TestHelper(value, GuildPermission.Connect); + TestHelper(value, GuildPermission.Speak); + TestHelper(value, GuildPermission.MuteMembers); + TestHelper(value, GuildPermission.MoveMembers); + TestHelper(value, GuildPermission.UseVAD); + TestHelper(value, GuildPermission.ChangeNickname); + TestHelper(value, GuildPermission.ManageNicknames); + TestHelper(value, GuildPermission.ManageRoles); + TestHelper(value, GuildPermission.ManageWebhooks); + TestHelper(value, GuildPermission.ManageEmojis); + + return Task.CompletedTask; + } + + /// + /// Tests for the class. + /// Test that that the Has method of + /// returns the correct value when webhook permissions are set. + /// + /// + [Fact] + public Task TestPermissionsHasGuildPermissionWebhook() + { + var value = GuildPermissions.Webhook; + + TestHelper(value, GuildPermission.CreateInstantInvite); + TestHelper(value, GuildPermission.KickMembers); + TestHelper(value, GuildPermission.BanMembers); + TestHelper(value, GuildPermission.Administrator); + TestHelper(value, GuildPermission.ManageChannels); + TestHelper(value, GuildPermission.ManageGuild); + TestHelper(value, GuildPermission.AddReactions); + TestHelper(value, GuildPermission.ViewAuditLog); + TestHelper(value, GuildPermission.ViewChannel); + TestHelper(value, GuildPermission.SendMessages, true); + TestHelper(value, GuildPermission.SendTTSMessages, true); + TestHelper(value, GuildPermission.ManageMessages); + TestHelper(value, GuildPermission.EmbedLinks, true); + TestHelper(value, GuildPermission.AttachFiles, true); + TestHelper(value, GuildPermission.ReadMessageHistory); + TestHelper(value, GuildPermission.MentionEveryone); + TestHelper(value, GuildPermission.UseExternalEmojis); + TestHelper(value, GuildPermission.Connect); + TestHelper(value, GuildPermission.Speak); + TestHelper(value, GuildPermission.MuteMembers); + TestHelper(value, GuildPermission.MoveMembers); + TestHelper(value, GuildPermission.UseVAD); + TestHelper(value, GuildPermission.ChangeNickname); + TestHelper(value, GuildPermission.ManageNicknames); + TestHelper(value, GuildPermission.ManageRoles); + TestHelper(value, GuildPermission.ManageWebhooks); + TestHelper(value, GuildPermission.ManageEmojis); return Task.CompletedTask; } diff --git a/test/Discord.Net.Tests/Tests.cs b/test/Discord.Net.Tests/Tests.cs index df156d254..dee9636c9 100644 --- a/test/Discord.Net.Tests/Tests.cs +++ b/test/Discord.Net.Tests/Tests.cs @@ -7,9 +7,9 @@ namespace Discord { public partial class TestsFixture : IDisposable { - private readonly TestConfig _config; private readonly CachedRestClient _cache; internal readonly DiscordRestClient _client; + private readonly TestConfig _config; internal readonly RestGuild _guild; public TestsFixture() @@ -42,7 +42,7 @@ namespace Discord public partial class Tests : IClassFixture { private DiscordRestClient _client; - private RestGuild _guild; + private readonly RestGuild _guild; public Tests(TestsFixture fixture) { @@ -50,4 +50,4 @@ namespace Discord _guild = fixture._guild; } } -} \ No newline at end of file +} From 7b87911351f4712e23b4e27d3c9f51bd27c5967d Mon Sep 17 00:00:00 2001 From: Sleepy Boyy Date: Sun, 19 Aug 2018 21:24:24 +0200 Subject: [PATCH 2/2] Code Cleanup --- .../RequireBotPermissionAttribute.cs | 4 +- .../Preconditions/RequireContextAttribute.cs | 4 +- .../RequireUserPermissionAttribute.cs | 4 +- .../Extensions/IEnumerableExtensions.cs | 7 ++- .../Extensions/MessageExtensions.cs | 3 -- src/Discord.Net.Commands/Info/CommandInfo.cs | 16 ++++--- src/Discord.Net.Commands/Info/ModuleInfo.cs | 6 +-- .../Map/CommandMapNode.cs | 4 +- .../Readers/RoleTypeReader.cs | 4 +- .../Results/ParseResult.cs | 4 -- .../Utilities/ReflectionUtils.cs | 4 +- src/Discord.Net.Core/Entities/Emotes/Emote.cs | 1 - .../Entities/Users/GuildUserProperties.cs | 3 +- src/Discord.Net.Core/Format.cs | 6 +-- src/Discord.Net.Core/Net/HttpException.cs | 12 ++--- src/Discord.Net.Core/Utils/MentionUtils.cs | 7 ++- src/Discord.Net.Core/Utils/Permissions.cs | 3 +- .../WS4NetClient.cs | 2 +- .../Entities/AuditLogs/AuditLogHelper.cs | 6 +-- .../Entities/Channels/RestDMChannel.cs | 21 ++++----- .../Entities/Channels/RestGroupChannel.cs | 26 ++++------ .../Entities/Channels/RestTextChannel.cs | 38 +++++++-------- .../Channels/RpcVirtualMessageChannel.cs | 21 ++++----- .../Entities/Guilds/RestGuild.cs | 6 +-- .../Entities/Messages/MessageHelper.cs | 4 +- .../Entities/Users/RestUser.cs | 4 +- .../Net/Converters/DiscordContractResolver.cs | 4 +- .../Net/Queue/RequestQueue.cs | 2 +- .../Audio/AudioClient.cs | 6 +-- .../Audio/Streams/BufferedWriteStream.cs | 4 +- .../Audio/Streams/InputStream.cs | 2 +- .../Audio/Streams/JitterBuffer.cs | 1 + .../Audio/Streams/RTPWriteStream.cs | 2 +- src/Discord.Net.WebSocket/ClientState.cs | 32 +++---------- .../DiscordShardedClient.cs | 47 +++++-------------- .../DiscordSocketApiClient.cs | 2 +- .../DiscordSocketClient.cs | 15 +++--- .../DiscordVoiceApiClient.cs | 2 +- .../Entities/Channels/SocketChannelHelper.cs | 11 +++-- .../Entities/Channels/SocketGroupChannel.cs | 8 +--- .../Entities/Guilds/SocketGuild.cs | 16 ++----- .../Entities/Messages/MessageCache.cs | 10 +--- .../Entities/Messages/SocketUserMessage.cs | 4 +- .../Net/DefaultWebSocketClient.cs | 2 +- .../Discord.Net.Tests/Net/CachedRestClient.cs | 4 +- .../Tests.GuildPermissions.cs | 3 +- test/Discord.Net.Tests/Tests.cs | 2 +- 47 files changed, 163 insertions(+), 236 deletions(-) diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs index 2db7afeb4..430cdb4b6 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireBotPermissionAttribute.cs @@ -70,7 +70,9 @@ namespace Discord.Commands else perms = ChannelPermissions.All(context.Channel); - return !perms.Has(ChannelPermission.Value) ? PreconditionResult.FromError($"Bot requires channel permission {ChannelPermission.Value}") : PreconditionResult.FromSuccess(); + return !perms.Has(ChannelPermission.Value) + ? PreconditionResult.FromError($"Bot requires channel permission {ChannelPermission.Value}") + : PreconditionResult.FromSuccess(); } } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs index 3f1988d80..79d3db652 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireContextAttribute.cs @@ -52,7 +52,9 @@ namespace Discord.Commands if ((Contexts & ContextType.Group) != 0) isValid = isValid || context.Channel is IGroupChannel; - return Task.FromResult(isValid ? PreconditionResult.FromSuccess() : PreconditionResult.FromError($"Invalid context for command; accepted contexts: {Contexts}")); + return Task.FromResult(isValid + ? PreconditionResult.FromSuccess() + : PreconditionResult.FromError($"Invalid context for command; accepted contexts: {Contexts}")); } } } diff --git a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs index 38d222397..eda41cd09 100644 --- a/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs +++ b/src/Discord.Net.Commands/Attributes/Preconditions/RequireUserPermissionAttribute.cs @@ -70,7 +70,9 @@ namespace Discord.Commands else perms = ChannelPermissions.All(context.Channel); - return Task.FromResult(!perms.Has(ChannelPermission.Value) ? PreconditionResult.FromError($"User requires channel permission {ChannelPermission.Value}") : PreconditionResult.FromSuccess()); + return Task.FromResult(!perms.Has(ChannelPermission.Value) + ? PreconditionResult.FromError($"User requires channel permission {ChannelPermission.Value}") + : PreconditionResult.FromSuccess()); } } } diff --git a/src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs b/src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs index 14d9b379c..318f433f8 100644 --- a/src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs +++ b/src/Discord.Net.Commands/Extensions/IEnumerableExtensions.cs @@ -9,9 +9,8 @@ namespace Discord.Commands public static IEnumerable Permutate( this IEnumerable set, IEnumerable others, - Func func) - { - return from elem in set from elem2 in others select func(elem, elem2); - } + Func func) => from elem in set + from elem2 in others + select func(elem, elem2); } } diff --git a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs index 0d994fd80..dfbec1a97 100644 --- a/src/Discord.Net.Commands/Extensions/MessageExtensions.cs +++ b/src/Discord.Net.Commands/Extensions/MessageExtensions.cs @@ -10,7 +10,6 @@ namespace Discord.Commands if (text.Length <= 0 || text[0] != c) return false; argPos = 1; return true; - } public static bool HasStringPrefix(this IUserMessage msg, string str, ref int argPos, @@ -20,7 +19,6 @@ namespace Discord.Commands if (!text.StartsWith(str, comparisonType)) return false; argPos = str.Length; return true; - } public static bool HasMentionPrefix(this IUserMessage msg, IUser user, ref int argPos) @@ -37,7 +35,6 @@ namespace Discord.Commands if (userId != user.Id) return false; argPos = endPos + 2; return true; - } } } diff --git a/src/Discord.Net.Commands/Info/CommandInfo.cs b/src/Discord.Net.Commands/Info/CommandInfo.cs index acf648ca0..30d6d8a0e 100644 --- a/src/Discord.Net.Commands/Info/CommandInfo.cs +++ b/src/Discord.Net.Commands/Info/CommandInfo.cs @@ -202,7 +202,8 @@ namespace Discord.Commands case Task resultTask: { var result = await resultTask.ConfigureAwait(false); - await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); + await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result) + .ConfigureAwait(false); if (result is RuntimeResult execResult) return execResult; break; @@ -210,14 +211,16 @@ namespace Discord.Commands case Task execTask: { var result = await execTask.ConfigureAwait(false); - await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); + await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result) + .ConfigureAwait(false); return result; } default: { await task.ConfigureAwait(false); var result = ExecuteResult.FromSuccess(); - await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result).ConfigureAwait(false); + await Module.Service._commandExecutedEvent.InvokeAsync(this, context, result) + .ConfigureAwait(false); break; } } @@ -280,9 +283,8 @@ namespace Discord.Commands private static T[] ConvertParamsList(IEnumerable paramsList) => paramsList.Cast().ToArray(); - internal string GetLogText(ICommandContext context) - { - return context.Guild != null ? $"\"{Name}\" for {context.User} in {context.Guild}/{context.Channel}" : $"\"{Name}\" for {context.User} in {context.Channel}"; - } + internal string GetLogText(ICommandContext context) => context.Guild != null + ? $"\"{Name}\" for {context.User} in {context.Guild}/{context.Channel}" + : $"\"{Name}\" for {context.User} in {context.Channel}"; } } diff --git a/src/Discord.Net.Commands/Info/ModuleInfo.cs b/src/Discord.Net.Commands/Info/ModuleInfo.cs index 7ef7e60be..88435ddd9 100644 --- a/src/Discord.Net.Commands/Info/ModuleInfo.cs +++ b/src/Discord.Net.Commands/Info/ModuleInfo.cs @@ -68,10 +68,8 @@ namespace Discord.Commands } private IEnumerable BuildSubmodules(ModuleBuilder parent, CommandService service, - IServiceProvider services) - { - return parent.Modules.Select(submodule => submodule.Build(service, services, this)).ToList(); - } + IServiceProvider services) => + parent.Modules.Select(submodule => submodule.Build(service, services, this)).ToList(); private static IEnumerable BuildPreconditions(ModuleBuilder builder) { diff --git a/src/Discord.Net.Commands/Map/CommandMapNode.cs b/src/Discord.Net.Commands/Map/CommandMapNode.cs index 59c9ef0fa..1c0e368ab 100644 --- a/src/Discord.Net.Commands/Map/CommandMapNode.cs +++ b/src/Discord.Net.Commands/Map/CommandMapNode.cs @@ -36,7 +36,9 @@ namespace Discord.Commands _commands = _commands.Add(command); break; default: - var name = nextSegment == -1 ? text.Substring(index) : text.Substring(index, nextSegment - index); + var name = nextSegment == -1 + ? text.Substring(index) + : text.Substring(index, nextSegment - index); var fullName = _name == "" ? name : _name + service._separatorChar + name; var nextNode = _nodes.GetOrAdd(name, x => new CommandMapNode(fullName)); diff --git a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs index 7e3d0a096..408063588 100644 --- a/src/Discord.Net.Commands/Readers/RoleTypeReader.cs +++ b/src/Discord.Net.Commands/Readers/RoleTypeReader.cs @@ -29,7 +29,9 @@ namespace Discord.Commands foreach (var role in roles.Where(x => string.Equals(input, x.Name, StringComparison.OrdinalIgnoreCase))) AddResult(results, role as T, role.Name == input ? 0.80f : 0.70f); - return Task.FromResult(results.Count > 0 ? TypeReaderResult.FromSuccess(results.Values.ToReadOnlyCollection()) : TypeReaderResult.FromError(CommandError.ObjectNotFound, "Role not found.")); + return Task.FromResult(results.Count > 0 + ? TypeReaderResult.FromSuccess(results.Values.ToReadOnlyCollection()) + : TypeReaderResult.FromError(CommandError.ObjectNotFound, "Role not found.")); } private void AddResult(Dictionary results, T role, float score) diff --git a/src/Discord.Net.Commands/Results/ParseResult.cs b/src/Discord.Net.Commands/Results/ParseResult.cs index 96aa1648c..a86e15f63 100644 --- a/src/Discord.Net.Commands/Results/ParseResult.cs +++ b/src/Discord.Net.Commands/Results/ParseResult.cs @@ -29,15 +29,11 @@ namespace Discord.Commands IReadOnlyList paramValues) { if (argValues.Any(t => t.Values.Count > 1)) - { return new ParseResult(argValues, paramValues, CommandError.MultipleMatches, "Multiple matches found."); - } if (paramValues.Any(t => t.Values.Count > 1)) - { return new ParseResult(argValues, paramValues, CommandError.MultipleMatches, "Multiple matches found."); - } return new ParseResult(argValues, paramValues, null, null); } diff --git a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs index a92888105..e1799d63b 100644 --- a/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs +++ b/src/Discord.Net.Commands/Utilities/ReflectionUtils.cs @@ -58,7 +58,9 @@ namespace Discord.Commands var result = new List(); while (ownerType != _objectTypeInfo) { - result.AddRange(ownerType.DeclaredProperties.Where(prop => prop.SetMethod?.IsStatic == false && prop.SetMethod?.IsPublic == true && prop.GetCustomAttribute() == null)); + result.AddRange(ownerType.DeclaredProperties.Where(prop => + prop.SetMethod?.IsStatic == false && prop.SetMethod?.IsPublic == true && + prop.GetCustomAttribute() == null)); ownerType = ownerType.BaseType.GetTypeInfo(); } diff --git a/src/Discord.Net.Core/Entities/Emotes/Emote.cs b/src/Discord.Net.Core/Entities/Emotes/Emote.cs index 8719a652e..54d33dce8 100644 --- a/src/Discord.Net.Core/Entities/Emotes/Emote.cs +++ b/src/Discord.Net.Core/Entities/Emotes/Emote.cs @@ -86,7 +86,6 @@ namespace Discord var name = text.Substring(startIndex, splitIndex - startIndex); result = new Emote(id, name, animated); return true; - } public override string ToString() => $"<{(Animated ? "a" : "")}:{Name}:{Id}>"; diff --git a/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs b/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs index c4ab8ac34..952b0a84a 100644 --- a/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs +++ b/src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs @@ -46,7 +46,8 @@ namespace Discord /// /// To add a role to a user: /// - /// To remove a role from a user: + /// To remove a role from a user: + /// /// public Optional> Roles { get; set; } diff --git a/src/Discord.Net.Core/Format.cs b/src/Discord.Net.Core/Format.cs index b9ebb5d3e..05e3e6717 100644 --- a/src/Discord.Net.Core/Format.cs +++ b/src/Discord.Net.Core/Format.cs @@ -28,9 +28,7 @@ namespace Discord } /// Sanitizes the string, safely escaping any Markdown sequences. - public static string Sanitize(string text) - { - return SensitiveCharacters.Aggregate(text, (current, unsafeChar) => current.Replace(unsafeChar, $"\\{unsafeChar}")); - } + public static string Sanitize(string text) => SensitiveCharacters.Aggregate(text, + (current, unsafeChar) => current.Replace(unsafeChar, $"\\{unsafeChar}")); } } diff --git a/src/Discord.Net.Core/Net/HttpException.cs b/src/Discord.Net.Core/Net/HttpException.cs index 1db2c3d3e..e1c8f6f5f 100644 --- a/src/Discord.Net.Core/Net/HttpException.cs +++ b/src/Discord.Net.Core/Net/HttpException.cs @@ -23,13 +23,13 @@ namespace Discord.Net { string msg; if (discordCode != null && discordCode != 0) - { - msg = reason != null ? $"The server responded with error {(int)discordCode}: {reason}" : $"The server responded with error {(int)discordCode}: {httpCode}"; - } + msg = reason != null + ? $"The server responded with error {(int)discordCode}: {reason}" + : $"The server responded with error {(int)discordCode}: {httpCode}"; else - { - msg = reason != null ? $"The server responded with error {(int)httpCode}: {reason}" : $"The server responded with error {(int)httpCode}: {httpCode}"; - } + msg = reason != null + ? $"The server responded with error {(int)httpCode}: {reason}" + : $"The server responded with error {(int)httpCode}: {httpCode}"; return msg; } diff --git a/src/Discord.Net.Core/Utils/MentionUtils.cs b/src/Discord.Net.Core/Utils/MentionUtils.cs index 20d027d04..059e758c2 100644 --- a/src/Discord.Net.Core/Utils/MentionUtils.cs +++ b/src/Discord.Net.Core/Utils/MentionUtils.cs @@ -214,6 +214,7 @@ namespace Discord case TagHandling.Sanitize: return $"@{SanitizeChar}everyone"; } + return ""; } @@ -230,6 +231,7 @@ namespace Discord case TagHandling.Sanitize: return $"@{SanitizeChar}here"; } + return ""; } @@ -239,10 +241,7 @@ namespace Discord var emoji = (Emote)tag.Value; //Remove if its name contains any bad chars (prevents a few tag exploits) - if (emoji.Name.Any(c => !char.IsLetterOrDigit(c) && c != '_' && c != '-')) - { - return ""; - } + if (emoji.Name.Any(c => !char.IsLetterOrDigit(c) && c != '_' && c != '-')) return ""; switch (mode) { diff --git a/src/Discord.Net.Core/Utils/Permissions.cs b/src/Discord.Net.Core/Utils/Permissions.cs index 0683496ea..d046ee1a4 100644 --- a/src/Discord.Net.Core/Utils/Permissions.cs +++ b/src/Discord.Net.Core/Utils/Permissions.cs @@ -111,7 +111,8 @@ namespace Discord resolvedPermissions = GuildPermissions.Webhook.RawValue; else { - resolvedPermissions = user.RoleIds.Aggregate(resolvedPermissions, (current, roleId) => current | (guild.GetRole(roleId)?.Permissions.RawValue ?? 0)); + resolvedPermissions = user.RoleIds.Aggregate(resolvedPermissions, + (current, roleId) => current | (guild.GetRole(roleId)?.Permissions.RawValue ?? 0)); if (GetValue(resolvedPermissions, GuildPermission.Administrator)) resolvedPermissions = GuildPermissions.All.RawValue; //Administrators always have all permissions } diff --git a/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs b/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs index 6d25b23cf..f8ad89008 100644 --- a/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs +++ b/src/Discord.Net.Providers.WS4Net/WS4NetClient.cs @@ -16,11 +16,11 @@ namespace Discord.Net.Providers.WS4Net private readonly Dictionary _headers; private readonly SemaphoreSlim _lock; + private readonly ManualResetEventSlim _waitUntilConnect; private CancellationToken _cancelToken, _parentToken; private CancellationTokenSource _cancelTokenSource; private WS4NetSocket _client; private bool _isDisposed; - private readonly ManualResetEventSlim _waitUntilConnect; public WS4NetClient() { diff --git a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs index 969f9788c..576f78407 100644 --- a/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs +++ b/src/Discord.Net.Rest/Entities/AuditLogs/AuditLogHelper.cs @@ -47,9 +47,7 @@ namespace Discord.Rest [ActionType.MessageDeleted] = MessageDeleteAuditLogData.Create }; - public static IAuditLogData CreateData(BaseDiscordClient discord, Model log, EntryModel entry) - { - return CreateMapping.TryGetValue(entry.Action, out var func) ? func(discord, log, entry) : null; - } + public static IAuditLogData CreateData(BaseDiscordClient discord, Model log, EntryModel entry) => + CreateMapping.TryGetValue(entry.Action, out var func) ? func(discord, log, entry) : null; } } diff --git a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs index d5ea56038..c69f2b052 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestDMChannel.cs @@ -52,22 +52,19 @@ namespace Discord.Rest } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, - RequestOptions options) - { - return mode == CacheMode.AllowDownload ? GetMessagesAsync(limit, options) : AsyncEnumerable.Empty>(); - } + RequestOptions options) => mode == CacheMode.AllowDownload + ? GetMessagesAsync(limit, options) + : AsyncEnumerable.Empty>(); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, - Direction dir, int limit, CacheMode mode, RequestOptions options) - { - return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessageId, dir, limit, options) : AsyncEnumerable.Empty>(); - } + Direction dir, int limit, CacheMode mode, RequestOptions options) => mode == CacheMode.AllowDownload + ? GetMessagesAsync(fromMessageId, dir, limit, options) + : AsyncEnumerable.Empty>(); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, - Direction dir, int limit, CacheMode mode, RequestOptions options) - { - return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessage, dir, limit, options) : AsyncEnumerable.Empty>(); - } + Direction dir, int limit, CacheMode mode, RequestOptions options) => mode == CacheMode.AllowDownload + ? GetMessagesAsync(fromMessage, dir, limit, options) + : AsyncEnumerable.Empty>(); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs index 4be8d2c75..82129ffbb 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestGroupChannel.cs @@ -57,22 +57,19 @@ namespace Discord.Rest } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, - RequestOptions options) - { - return mode == CacheMode.AllowDownload ? GetMessagesAsync(limit, options) : AsyncEnumerable.Empty>(); - } + RequestOptions options) => mode == CacheMode.AllowDownload + ? GetMessagesAsync(limit, options) + : AsyncEnumerable.Empty>(); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, - Direction dir, int limit, CacheMode mode, RequestOptions options) - { - return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessageId, dir, limit, options) : AsyncEnumerable.Empty>(); - } + Direction dir, int limit, CacheMode mode, RequestOptions options) => mode == CacheMode.AllowDownload + ? GetMessagesAsync(fromMessageId, dir, limit, options) + : AsyncEnumerable.Empty>(); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, - Direction dir, int limit, CacheMode mode, RequestOptions options) - { - return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessage, dir, limit, options) : AsyncEnumerable.Empty>(); - } + Direction dir, int limit, CacheMode mode, RequestOptions options) => mode == CacheMode.AllowDownload + ? GetMessagesAsync(fromMessage, dir, limit, options) + : AsyncEnumerable.Empty>(); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); @@ -170,10 +167,7 @@ namespace Discord.Rest _users = users.ToImmutable(); } - public RestUser GetUser(ulong id) - { - return _users.TryGetValue(id, out var user) ? user : null; - } + public RestUser GetUser(ulong id) => _users.TryGetValue(id, out var user) ? user : null; public IDisposable EnterTypingState(RequestOptions options = null) => ChannelHelper.EnterTypingState(this, Discord, options); diff --git a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs index 75701a688..adbff15bb 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RestTextChannel.cs @@ -66,24 +66,19 @@ namespace Discord.Rest } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, - RequestOptions options) - { - return mode == CacheMode.AllowDownload - ? GetMessagesAsync(limit, options) - : AsyncEnumerable.Empty>(); - } + RequestOptions options) => mode == CacheMode.AllowDownload + ? GetMessagesAsync(limit, options) + : AsyncEnumerable.Empty>(); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, - Direction dir, int limit, CacheMode mode, RequestOptions options) - { - return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessageId, dir, limit, options) : AsyncEnumerable.Empty>(); - } + Direction dir, int limit, CacheMode mode, RequestOptions options) => mode == CacheMode.AllowDownload + ? GetMessagesAsync(fromMessageId, dir, limit, options) + : AsyncEnumerable.Empty>(); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, - Direction dir, int limit, CacheMode mode, RequestOptions options) - { - return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessage, dir, limit, options) : AsyncEnumerable.Empty>(); - } + Direction dir, int limit, CacheMode mode, RequestOptions options) => mode == CacheMode.AllowDownload + ? GetMessagesAsync(fromMessage, dir, limit, options) + : AsyncEnumerable.Empty>(); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options).ConfigureAwait(false); @@ -111,10 +106,10 @@ namespace Discord.Rest return null; } - IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) - { - return mode == CacheMode.AllowDownload ? GetUsersAsync(options) : AsyncEnumerable.Empty>(); - } + IAsyncEnumerable> IChannel.GetUsersAsync(CacheMode mode, RequestOptions options) => + mode == CacheMode.AllowDownload + ? GetUsersAsync(options) + : AsyncEnumerable.Empty>(); public string Topic { get; private set; } public ulong? CategoryId { get; private set; } @@ -153,10 +148,9 @@ namespace Discord.Rest } IAsyncEnumerable> IGuildChannel.GetUsersAsync(CacheMode mode, - RequestOptions options) - { - return mode == CacheMode.AllowDownload ? GetUsersAsync(options) : AsyncEnumerable.Empty>(); - } + RequestOptions options) => mode == CacheMode.AllowDownload + ? GetUsersAsync(options) + : AsyncEnumerable.Empty>(); // INestedChannel async Task INestedChannel.GetCategoryAsync(CacheMode mode, RequestOptions options) diff --git a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs index 3cc1a8d4f..2dba07a83 100644 --- a/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs +++ b/src/Discord.Net.Rest/Entities/Channels/RpcVirtualMessageChannel.cs @@ -38,22 +38,19 @@ namespace Discord.Rest } IAsyncEnumerable> IMessageChannel.GetMessagesAsync(int limit, CacheMode mode, - RequestOptions options) - { - return mode == CacheMode.AllowDownload ? GetMessagesAsync(limit, options) : AsyncEnumerable.Empty>(); - } + RequestOptions options) => mode == CacheMode.AllowDownload + ? GetMessagesAsync(limit, options) + : AsyncEnumerable.Empty>(); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(ulong fromMessageId, - Direction dir, int limit, CacheMode mode, RequestOptions options) - { - return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessageId, dir, limit, options) : AsyncEnumerable.Empty>(); - } + Direction dir, int limit, CacheMode mode, RequestOptions options) => mode == CacheMode.AllowDownload + ? GetMessagesAsync(fromMessageId, dir, limit, options) + : AsyncEnumerable.Empty>(); IAsyncEnumerable> IMessageChannel.GetMessagesAsync(IMessage fromMessage, - Direction dir, int limit, CacheMode mode, RequestOptions options) - { - return mode == CacheMode.AllowDownload ? GetMessagesAsync(fromMessage, dir, limit, options) : AsyncEnumerable.Empty>(); - } + Direction dir, int limit, CacheMode mode, RequestOptions options) => mode == CacheMode.AllowDownload + ? GetMessagesAsync(fromMessage, dir, limit, options) + : AsyncEnumerable.Empty>(); async Task> IMessageChannel.GetPinnedMessagesAsync(RequestOptions options) => await GetPinnedMessagesAsync(options); diff --git a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs index dd2a6425e..55f382a2c 100644 --- a/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs +++ b/src/Discord.Net.Rest/Entities/Guilds/RestGuild.cs @@ -4,7 +4,6 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using Discord.API; using Discord.Audio; using EmbedModel = Discord.API.GuildEmbed; using Model = Discord.API.Guild; @@ -473,10 +472,7 @@ namespace Discord.Rest => GuildHelper.GetVanityInviteAsync(this, Discord, options); //Roles - public RestRole GetRole(ulong id) - { - return _roles.TryGetValue(id, out var value) ? value : null; - } + public RestRole GetRole(ulong id) => _roles.TryGetValue(id, out var value) ? value : null; public async Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), diff --git a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs index db30ddb40..eda968105 100644 --- a/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs +++ b/src/Discord.Net.Rest/Entities/Messages/MessageHelper.cs @@ -204,7 +204,9 @@ namespace Discord.Rest return MessageSource.System; if (msg.WebhookId.IsSpecified) return MessageSource.Webhook; - return msg.Author.GetValueOrDefault()?.Bot.GetValueOrDefault(false) == true ? MessageSource.Bot : MessageSource.User; + return msg.Author.GetValueOrDefault()?.Bot.GetValueOrDefault(false) == true + ? MessageSource.Bot + : MessageSource.User; } } } diff --git a/src/Discord.Net.Rest/Entities/Users/RestUser.cs b/src/Discord.Net.Rest/Entities/Users/RestUser.cs index e9a2880f1..4d6f350c2 100644 --- a/src/Discord.Net.Rest/Entities/Users/RestUser.cs +++ b/src/Discord.Net.Rest/Entities/Users/RestUser.cs @@ -49,7 +49,9 @@ namespace Discord.Rest internal static RestUser Create(BaseDiscordClient discord, IGuild guild, Model model, ulong? webhookId) { RestUser entity; - entity = webhookId.HasValue ? new RestWebhookUser(discord, guild, model.Id, webhookId.Value) : new RestUser(discord, model.Id); + entity = webhookId.HasValue + ? new RestWebhookUser(discord, guild, model.Id, webhookId.Value) + : new RestUser(discord, model.Id); entity.Update(model); return entity; } diff --git a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs index b043b9ef1..aee1cc2ff 100644 --- a/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs +++ b/src/Discord.Net.Rest/Net/Converters/DiscordContractResolver.cs @@ -89,7 +89,9 @@ namespace Discord.Net.Converters var typeInfo = type.GetTypeInfo(); if (typeInfo.ImplementedInterfaces.Any(x => x == typeof(IEntity))) return UInt64EntityConverter.Instance; - return typeInfo.ImplementedInterfaces.Any(x => x == typeof(IEntity)) ? StringEntityConverter.Instance : null; + return typeInfo.ImplementedInterfaces.Any(x => x == typeof(IEntity)) + ? StringEntityConverter.Instance + : null; } private static bool ShouldSerialize(object owner, Delegate getter) => diff --git a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs index bbdfbcbfe..099c820b0 100644 --- a/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs +++ b/src/Discord.Net.Rest/Net/Queue/RequestQueue.cs @@ -14,8 +14,8 @@ namespace Discord.Net.Queue internal class RequestQueue : IDisposable { private readonly ConcurrentDictionary _buckets; - private readonly SemaphoreSlim _tokenLock; private readonly CancellationTokenSource _cancelToken; //Dispose token + private readonly SemaphoreSlim _tokenLock; private Task _cleanupTask; private CancellationTokenSource _clearToken; diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index b3f913201..ec85cbf5c 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -198,10 +198,8 @@ namespace Discord.Audio } } - internal AudioInStream GetInputStream(ulong id) - { - return _streams.TryGetValue(id, out var streamPair) ? streamPair.Reader : null; - } + internal AudioInStream GetInputStream(ulong id) => + _streams.TryGetValue(id, out var streamPair) ? streamPair.Reader : null; internal async Task RemoveInputStreamAsync(ulong userId) { diff --git a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs index 7211c3c19..e4cb2f7c7 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/BufferedWriteStream.cs @@ -133,7 +133,9 @@ namespace Discord.Audio.Streams public override async Task WriteAsync(byte[] data, int offset, int count, CancellationToken cancelToken) { - cancelToken = cancelToken.CanBeCanceled ? CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _cancelToken).Token : _cancelToken; + cancelToken = cancelToken.CanBeCanceled + ? CancellationTokenSource.CreateLinkedTokenSource(cancelToken, _cancelToken).Token + : _cancelToken; await _queueLock.WaitAsync(-1, cancelToken).ConfigureAwait(false); if (!_bufferPool.TryDequeue(out var buffer)) diff --git a/src/Discord.Net.WebSocket/Audio/Streams/InputStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/InputStream.cs index d8449d422..bce728d14 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/InputStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/InputStream.cs @@ -11,12 +11,12 @@ namespace Discord.Audio.Streams private const int MaxFrames = 100; //1-2 Seconds private readonly ConcurrentQueue _frames; + private readonly SemaphoreSlim _signal; private bool _hasHeader; private bool _isDisposed; private bool _nextMissed; private ushort _nextSeq; private uint _nextTimestamp; - private readonly SemaphoreSlim _signal; public InputStream() { diff --git a/src/Discord.Net.WebSocket/Audio/Streams/JitterBuffer.cs b/src/Discord.Net.WebSocket/Audio/Streams/JitterBuffer.cs index be54ae6fe..242296993 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/JitterBuffer.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/JitterBuffer.cs @@ -246,3 +246,4 @@ namespace Discord.Audio.Streams }*/ + diff --git a/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs index 34133116d..1f5ffa702 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs @@ -10,10 +10,10 @@ namespace Discord.Audio.Streams protected readonly byte[] _buffer; private readonly byte[] _header; private readonly AudioStream _next; + private readonly uint _ssrc; private bool _hasHeader; private ushort _nextSeq; private uint _nextTimestamp; - private readonly uint _ssrc; public RTPWriteStream(AudioStream next, uint ssrc, int bufferSize = 4000) { diff --git a/src/Discord.Net.WebSocket/ClientState.cs b/src/Discord.Net.WebSocket/ClientState.cs index 525a8f173..79a1a0edd 100644 --- a/src/Discord.Net.WebSocket/ClientState.cs +++ b/src/Discord.Net.WebSocket/ClientState.cs @@ -47,15 +47,10 @@ namespace Discord.WebSocket _groupChannels.Select(x => GetChannel(x) as ISocketPrivateChannel)) .ToReadOnlyCollection(() => _dmChannels.Count + _groupChannels.Count); - internal SocketChannel GetChannel(ulong id) - { - return _channels.TryGetValue(id, out var channel) ? channel : null; - } + internal SocketChannel GetChannel(ulong id) => _channels.TryGetValue(id, out var channel) ? channel : null; - internal SocketDMChannel GetDMChannel(ulong userId) - { - return _dmChannels.TryGetValue(userId, out var channel) ? channel : null; - } + internal SocketDMChannel GetDMChannel(ulong userId) => + _dmChannels.TryGetValue(userId, out var channel) ? channel : null; internal void AddChannel(SocketChannel channel) { @@ -86,32 +81,19 @@ namespace Discord.WebSocket } return channel; - } - internal SocketGuild GetGuild(ulong id) - { - return _guilds.TryGetValue(id, out var guild) ? guild : null; - } + internal SocketGuild GetGuild(ulong id) => _guilds.TryGetValue(id, out var guild) ? guild : null; internal void AddGuild(SocketGuild guild) => _guilds[guild.Id] = guild; - internal SocketGuild RemoveGuild(ulong id) - { - return _guilds.TryRemove(id, out var guild) ? guild : null; - } + internal SocketGuild RemoveGuild(ulong id) => _guilds.TryRemove(id, out var guild) ? guild : null; - internal SocketGlobalUser GetUser(ulong id) - { - return _users.TryGetValue(id, out var user) ? user : null; - } + internal SocketGlobalUser GetUser(ulong id) => _users.TryGetValue(id, out var user) ? user : null; internal SocketGlobalUser GetOrAddUser(ulong id, Func userFactory) => _users.GetOrAdd(id, userFactory); - internal SocketGlobalUser RemoveUser(ulong id) - { - return _users.TryRemove(id, out var user) ? user : null; - } + internal SocketGlobalUser RemoveUser(ulong id) => _users.TryRemove(id, out var user) ? user : null; } } diff --git a/src/Discord.Net.WebSocket/DiscordShardedClient.cs b/src/Discord.Net.WebSocket/DiscordShardedClient.cs index ff2525c64..64b8c5f59 100644 --- a/src/Discord.Net.WebSocket/DiscordShardedClient.cs +++ b/src/Discord.Net.WebSocket/DiscordShardedClient.cs @@ -11,11 +11,11 @@ namespace Discord.WebSocket { public partial class DiscordShardedClient : BaseSocketClient, IDiscordClient { + private readonly bool _automaticShards; private readonly DiscordSocketConfig _baseConfig; private readonly SemaphoreSlim _connectionGroupLock; - private readonly bool _automaticShards; - private int[] _shardIds; private readonly Dictionary _shardIdsToIndex; + private int[] _shardIds; private DiscordSocketClient[] _shards; private int _totalShards; @@ -192,10 +192,7 @@ namespace Discord.WebSocket } } - public DiscordSocketClient GetShard(int id) - { - return _shardIdsToIndex.TryGetValue(id, out id) ? _shards[id] : null; - } + public DiscordSocketClient GetShard(int id) => _shardIdsToIndex.TryGetValue(id, out id) ? _shards[id] : null; private int GetShardIdFor(ulong guildId) => (int)((guildId >> 22) % (uint)_totalShards); @@ -218,42 +215,24 @@ namespace Discord.WebSocket => GetShardFor(id).GetGuild(id); /// - public override SocketChannel GetChannel(ulong id) - { - return _shards.Select(t => t.GetChannel(id)).FirstOrDefault(channel => channel != null); - } + public override SocketChannel GetChannel(ulong id) => + _shards.Select(t => t.GetChannel(id)).FirstOrDefault(channel => channel != null); - private IEnumerable GetPrivateChannels() - { - return _shards.SelectMany(t => t.PrivateChannels); - } + private IEnumerable GetPrivateChannels() => _shards.SelectMany(t => t.PrivateChannels); - private int GetPrivateChannelCount() - { - return _shards.Sum(t => t.PrivateChannels.Count); - } + private int GetPrivateChannelCount() => _shards.Sum(t => t.PrivateChannels.Count); - private IEnumerable GetGuilds() - { - return _shards.SelectMany(t => t.Guilds); - } + private IEnumerable GetGuilds() => _shards.SelectMany(t => t.Guilds); - private int GetGuildCount() - { - return _shards.Sum(t => t.Guilds.Count); - } + private int GetGuildCount() => _shards.Sum(t => t.Guilds.Count); /// - public override SocketUser GetUser(ulong id) - { - return _shards.Select(t => t.GetUser(id)).FirstOrDefault(user => user != null); - } + public override SocketUser GetUser(ulong id) => + _shards.Select(t => t.GetUser(id)).FirstOrDefault(user => user != null); /// - public override SocketUser GetUser(string username, string discriminator) - { - return _shards.Select(t => t.GetUser(username, discriminator)).FirstOrDefault(user => user != null); - } + public override SocketUser GetUser(string username, string discriminator) => _shards + .Select(t => t.GetUser(username, discriminator)).FirstOrDefault(user => user != null); /// public override RestVoiceRegion GetVoiceRegion(string id) diff --git a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs index f1cd10c02..b90d77f4c 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketApiClient.cs @@ -18,6 +18,7 @@ namespace Discord.API internal class DiscordSocketApiClient : DiscordRestApiClient { private readonly AsyncEvent> _disconnectedEvent = new AsyncEvent>(); + private readonly bool _isExplicitUrl; private readonly AsyncEvent> _receivedGatewayEvent = new AsyncEvent>(); @@ -31,7 +32,6 @@ namespace Discord.API private CancellationTokenSource _connectCancelToken; private DeflateStream _decompressor; private string _gatewayUrl; - private readonly bool _isExplicitUrl; public DiscordSocketApiClient(RestClientProvider restClientProvider, WebSocketProvider webSocketProvider, string userAgent, diff --git a/src/Discord.Net.WebSocket/DiscordSocketClient.cs b/src/Discord.Net.WebSocket/DiscordSocketClient.cs index ea41a2064..1654028c4 100644 --- a/src/Discord.Net.WebSocket/DiscordSocketClient.cs +++ b/src/Discord.Net.WebSocket/DiscordSocketClient.cs @@ -357,10 +357,8 @@ namespace Discord.WebSocket => State.RemoveUser(id); /// - public override RestVoiceRegion GetVoiceRegion(string id) - { - return _voiceRegions.TryGetValue(id, out var region) ? region : null; - } + public override RestVoiceRegion GetVoiceRegion(string id) => + _voiceRegions.TryGetValue(id, out var region) ? region : null; /// Downloads the users list for the provided guilds, if they don't have a complete list. public override async Task DownloadUsersAsync(IEnumerable guilds) @@ -1152,7 +1150,7 @@ namespace Discord.WebSocket author = guild.GetUser(data.Author.Value.Id); } else - author = ((SocketChannel) channel).GetUser(data.Author.Value.Id); + author = ((SocketChannel)channel).GetUser(data.Author.Value.Id); if (author == null) { @@ -1205,8 +1203,11 @@ namespace Discord.WebSocket else if (data.Author.IsSpecified) { //Edited message isnt in cache, create a detached one - var author = (guild != null ? guild.GetUser(data.Author.Value.Id) : ((SocketChannel) channel).GetUser(data.Author.Value.Id)) ?? - SocketUnknownUser.Create(this, State, data.Author.Value); + var author = + (guild != null + ? guild.GetUser(data.Author.Value.Id) + : ((SocketChannel)channel).GetUser(data.Author.Value.Id)) ?? + SocketUnknownUser.Create(this, State, data.Author.Value); after = SocketMessage.Create(this, State, author, channel, data); } diff --git a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs index df54def6e..346450e81 100644 --- a/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs +++ b/src/Discord.Net.WebSocket/DiscordVoiceApiClient.cs @@ -37,10 +37,10 @@ namespace Discord.Audio new AsyncEvent>(); private readonly JsonSerializer _serializer; + private readonly IUdpSocket _udp; private CancellationTokenSource _connectCancelToken; private bool _isDisposed; private ulong _nextKeepalive; - private readonly IUdpSocket _udp; internal DiscordVoiceAPIClient(ulong guildId, WebSocketProvider webSocketProvider, UdpSocketProvider udpSocketProvider, JsonSerializer serializer = null) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs index 55a980ccb..26d7c8639 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketChannelHelper.cs @@ -23,7 +23,9 @@ namespace Discord.WebSocket if (dir == Direction.Before || mode == CacheMode.CacheOnly) { - cachedMessages = messages != null ? messages.GetMany(fromMessageId, dir, limit) : ImmutableArray.Create(); + cachedMessages = messages != null + ? messages.GetMany(fromMessageId, dir, limit) + : ImmutableArray.Create(); result = ImmutableArray.Create(cachedMessages).ToAsyncEnumerable>(); } @@ -45,10 +47,9 @@ namespace Discord.WebSocket public static IReadOnlyCollection GetCachedMessages(SocketChannel channel, DiscordSocketClient discord, MessageCache messages, - ulong? fromMessageId, Direction dir, int limit) - { - return messages != null ? messages.GetMany(fromMessageId, dir, limit) : ImmutableArray.Create(); - } + ulong? fromMessageId, Direction dir, int limit) => messages != null + ? messages.GetMany(fromMessageId, dir, limit) + : ImmutableArray.Create(); public static void AddMessage(ISocketMessageChannel channel, DiscordSocketClient discord, SocketMessage msg) diff --git a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs index 835889532..24c92db62 100644 --- a/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs +++ b/src/Discord.Net.WebSocket/Entities/Channels/SocketGroupChannel.cs @@ -19,10 +19,10 @@ namespace Discord.WebSocket ISocketAudioChannel { private readonly MessageCache _messages; + private readonly ConcurrentDictionary _voiceStates; private string _iconId; private ConcurrentDictionary _users; - private readonly ConcurrentDictionary _voiceStates; internal SocketGroupChannel(DiscordSocketClient discord, ulong id) : base(discord, id) @@ -211,10 +211,7 @@ namespace Discord.WebSocket => _messages?.Remove(id); //Users - public new SocketGroupUser GetUser(ulong id) - { - return _users.TryGetValue(id, out var user) ? user : null; - } + public new SocketGroupUser GetUser(ulong id) => _users.TryGetValue(id, out var user) ? user : null; internal SocketGroupUser GetOrAddUser(UserModel model) { @@ -231,7 +228,6 @@ namespace Discord.WebSocket if (!_users.TryRemove(id, out var user)) return null; user.GlobalUser.RemoveRef(Discord); return user; - } //Voice States diff --git a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs index 8c3fb3944..619c2129d 100644 --- a/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs +++ b/src/Discord.Net.WebSocket/Entities/Guilds/SocketGuild.cs @@ -523,10 +523,7 @@ namespace Discord.WebSocket => GuildHelper.GetVanityInviteAsync(this, Discord, options); //Roles - public SocketRole GetRole(ulong id) - { - return _roles.TryGetValue(id, out var value) ? value : null; - } + public SocketRole GetRole(ulong id) => _roles.TryGetValue(id, out var value) ? value : null; public Task CreateRoleAsync(string name, GuildPermissions? permissions = default(GuildPermissions?), Color? color = default(Color?), @@ -540,16 +537,10 @@ namespace Discord.WebSocket return role; } - internal SocketRole RemoveRole(ulong id) - { - return _roles.TryRemove(id, out var role) ? role : null; - } + internal SocketRole RemoveRole(ulong id) => _roles.TryRemove(id, out var role) ? role : null; //Users - public SocketGuildUser GetUser(ulong id) - { - return _members.TryGetValue(id, out var member) ? member : null; - } + public SocketGuildUser GetUser(ulong id) => _members.TryGetValue(id, out var member) ? member : null; internal SocketGuildUser AddOrUpdateUser(UserModel model) { @@ -602,7 +593,6 @@ namespace Discord.WebSocket DownloadedMemberCount--; member.GlobalUser.RemoveRef(Discord); return member; - } internal void CompleteDownloadUsers() => _downloaderPromise.TrySetResultAsync(true); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs index 613d910b6..d30a9a312 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/MessageCache.cs @@ -37,10 +37,7 @@ namespace Discord.WebSocket return msg; } - public SocketMessage Get(ulong id) - { - return _messages.TryGetValue(id, out var result) ? result : null; - } + public SocketMessage Get(ulong id) => _messages.TryGetValue(id, out var result) ? result : null; public IReadOnlyCollection GetMany(ulong? fromMessageId, Direction dir, int limit = DiscordConfig.MaxMessagesPerBatch) @@ -60,10 +57,7 @@ namespace Discord.WebSocket cachedMessageIds = cachedMessageIds.Reverse(); return cachedMessageIds - .Select(x => - { - return _messages.TryGetValue(x, out var msg) ? msg : null; - }) + .Select(x => { return _messages.TryGetValue(x, out var msg) ? msg : null; }) .Where(x => x != null) .Take(limit) .ToImmutableArray(); diff --git a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs index f5ff3e17f..a9a45b5d5 100644 --- a/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs +++ b/src/Discord.Net.WebSocket/Entities/Messages/SocketUserMessage.cs @@ -12,11 +12,11 @@ namespace Discord.WebSocket [DebuggerDisplay(@"{" + nameof(DebuggerDisplay) + @",nq}")] public class SocketUserMessage : SocketMessage, IUserMessage { + private readonly List _reactions = new List(); private ImmutableArray _attachments; private long? _editedTimestampTicks; private ImmutableArray _embeds; private bool _isMentioningEveryone, _isTTS, _isPinned; - private readonly List _reactions = new List(); private ImmutableArray _tags; internal SocketUserMessage(DiscordSocketClient discord, ulong id, ISocketMessageChannel channel, @@ -142,10 +142,8 @@ namespace Discord.WebSocket { var newMentions = ImmutableArray.CreateBuilder(value.Length); foreach (var val in value) - { if (val.Object != null) newMentions.Add(SocketUnknownUser.Create(Discord, state, val.Object)); - } mentions = newMentions.ToImmutable(); } diff --git a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs index 1aeffc73a..9771e52d5 100644 --- a/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs +++ b/src/Discord.Net.WebSocket/Net/DefaultWebSocketClient.cs @@ -18,11 +18,11 @@ namespace Discord.Net.WebSockets private readonly Dictionary _headers; private readonly SemaphoreSlim _lock; + private readonly IWebProxy _proxy; private CancellationToken _cancelToken, _parentToken; private CancellationTokenSource _cancelTokenSource; private ClientWebSocket _client; private bool _isDisposed, _isDisconnecting; - private readonly IWebProxy _proxy; private Task _task; public DefaultWebSocketClient(IWebProxy proxy = null) diff --git a/test/Discord.Net.Tests/Net/CachedRestClient.cs b/test/Discord.Net.Tests/Net/CachedRestClient.cs index e82f04419..ecd58b51d 100644 --- a/test/Discord.Net.Tests/Net/CachedRestClient.cs +++ b/test/Discord.Net.Tests/Net/CachedRestClient.cs @@ -15,11 +15,11 @@ namespace Discord.Net { internal class CachedRestClient : IRestClient { + private readonly IBlobCache _blobCache; + private readonly CancellationTokenSource _cancelTokenSource; private readonly Dictionary _headers; private string _baseUrl; - private readonly IBlobCache _blobCache; private CancellationToken _cancelToken, _parentToken; - private readonly CancellationTokenSource _cancelTokenSource; private bool _isDisposed; public CachedRestClient() diff --git a/test/Discord.Net.Tests/Tests.GuildPermissions.cs b/test/Discord.Net.Tests/Tests.GuildPermissions.cs index 0c677399e..835e57a3e 100644 --- a/test/Discord.Net.Tests/Tests.GuildPermissions.cs +++ b/test/Discord.Net.Tests/Tests.GuildPermissions.cs @@ -44,7 +44,8 @@ namespace Discord .Distinct() .ToArray(); // test GuildPermissions.All - var sumOfAllGuildPermissions = enumValues.Aggregate(0, (current, v) => current | (ulong)v); + var sumOfAllGuildPermissions = + enumValues.Aggregate(0, (current, v) => current | (ulong)v); // assert that the raw values match Assert.Equal(sumOfAllGuildPermissions, GuildPermissions.All.RawValue); diff --git a/test/Discord.Net.Tests/Tests.cs b/test/Discord.Net.Tests/Tests.cs index dee9636c9..673a267e9 100644 --- a/test/Discord.Net.Tests/Tests.cs +++ b/test/Discord.Net.Tests/Tests.cs @@ -41,8 +41,8 @@ namespace Discord public partial class Tests : IClassFixture { - private DiscordRestClient _client; private readonly RestGuild _guild; + private DiscordRestClient _client; public Tests(TestsFixture fixture) {